Repository navigation
feat(agents): capture a terminal session's tenant at creation (#16975) - #16978
Merged
Merged
Conversation
A terminal session had no tenant, and none could be recovered afterwards -- presence (#16947) had to report every session as UNKNOWN_TENANT. The route that looked like it might work does not exist: conversation_id is a log- linkage id only, chat_history's session/conversation stores carry no user/tenant field, and the owner username threaded through SessionManager.create_session was never persisted for a later lookup to recover. - AgentTerminalSession gains an explicit tenant_id field. - Threaded through SessionManager.create_session -> AgentTerminalService.create_session -> the one real call site with authenticated context: POST /api/agent-terminal/sessions, which already resolves `owner` from current_user.get("username") for the WebSocket ownership gate (#14989/#14960) -- tenant_id is current_user.get("org_id") alongside it, the JWT claim only. TerminalCreateSessionRequest has no org_id/tenant_id field, so a caller cannot supply one through this route. - sync_session_presence now reads session.tenant_id, falling back to UNKNOWN_TENANT only when it's unset -- a pre-#16975 session, or one created via a path with no authenticated context (e.g. the chat-tool internal session creation in tools/terminal_tool.py, which passes neither owner nor tenant_id today and is left alone: no code touches those paths in this PR, so they correctly keep reporting UNKNOWN_TENANT rather than a guess). - 6 new tests: SessionManager.create_session threads tenant_id onto the session (and leaves it unset without one); presence reports a session under its real tenant and it is invisible to a different tenant's query (the AC's own discoverability negative control); a session with no tenant stays UNKNOWN_TENANT and is never listed. - service.py's ratchet lowered 962 -> 959 (landed below ceiling after trimming its own docstring to make room for the new parameter). Refs #16975
Contributor
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. 🗂️ Base branches to auto review (2)
Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Advanced Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Contributor
Review found to_persist_dict() left tenant_id out, and get_session()'s
Redis-reload branch rebuilt AgentTerminalSession without it -- a session
stamped with its tenant at creation lost the stamp on any reload (a
restart, an eviction, another worker) and reappeared as UNKNOWN_TENANT.
Fails closed, so nothing leaked, but it broke AC1 across a reload.
- to_persist_dict() now includes tenant_id.
- get_session()'s Redis-reload branch restores it via
session_data.get("tenant_id").
- The pending-approval rebuild path is left as UNKNOWN_TENANT deliberately
-- it predates this PR and never guessing is correct there.
- New test drives the real persist -> drop from memory -> get_session
round trip (not the two methods in isolation), then confirms the
reloaded session is discoverable through the real presence registry in
its own tenant only.
Refs #16975, #16978 review
This was referenced Sep 18, 2026
… issue-16975-session-tenant
7 tasks done
… issue-16975-session-tenant-work
… issue-16975-session-tenant-fix2
This was referenced Sep 18, 2026
Merged
Closed
Closed
Merged
mrveiss
added a commit
that referenced
this pull request
Sep 18, 2026
…17053) session_manager.py reached 596 lines, and #16978 (in the agent-coordination vehicle) adds 5 to it, which would have crossed the 600-line limit on rebase. The file-then-grant owner lookup moves to services/agent_terminal/conversation_owner.py unchanged; SessionManager._conversation_owner delegates to it.
This was referenced Sep 18, 2026
Merged
3 tasks done
This was referenced Sep 19, 2026
Closed
This was referenced Sep 19, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #16965 (#16947) -- targets that branch, not
main, since this needsAgentPresenceRegistry/sync_session_presenceto exist. Rebase ontomainonce #16965 merges.Problem
A terminal session had no tenant, and none could be recovered after the fact -- presence (#16947) had to report every session as
UNKNOWN_TENANT, so no session could be discovered or addressed within its own tenant.The route that looked like it might work does not exist (investigated and reported back during #16947's review rather than picking something else):
conversation_idis a log-linkage id only, everywhere it's used.chat_history's session/conversation stores (base.py,session.py,security.py) carry no user/tenant field anywhere.owner(username) is threaded throughSessionManager.create_session, but only for PTY registration -- never persisted on the session for a later lookup to recover.Fix
Capture the tenant at creation, while the authenticated principal is still in hand:
AgentTerminalSessiongains an explicittenant_idfield.SessionManager.create_session->AgentTerminalService.create_session-> the one real call site with authenticated context:POST /api/agent-terminal/sessions, which already resolvesownerfromcurrent_user.get("username")for the WebSocket ownership gate (fix(remote-control): authenticate the VNC and terminal websockets, deny unknown terminal sessions #14989/Terminal WebSocket accepts unauthenticated connections #14960).tenant_idiscurrent_user.get("org_id")alongside it -- the JWT claim only.TerminalCreateSessionRequesthas noorg_id/tenant_idfield, so a caller cannot supply one through this route's request body.sync_session_presencenow readssession.tenant_id, falling back toUNKNOWN_TENANTonly when it's unset.tools/terminal_tool.py, which passes neitherownernortenant_id) are untouched by this PR and correctly keep reportingUNKNOWN_TENANT-- not guessed.Acceptance criteria (from #16975)
session_manager_tenant_16975_test.pycurrent_user.get("org_id")(JWT claim), and the request schema has no field a caller could use to override itUNKNOWN_TENANTtest_a_session_with_a_known_tenant_is_discoverable_within_it_onlyUNKNOWN_TENANT, not guessed (thesession.tenant_id or UNKNOWN_TENANTfallback)Test plan
session_manager_tenant_16975_test.py, 4 inagent_presence_feeds_test.py) -- all passingservices/agent_terminal/full suite -- 68/68 passing, no regressionsblack,isort,flake8,bandit-- clean (pre-existing, untouched-by-this-diff E402s left alone, same convention as feat(agents): live presence — one registry of named agents with busy/idle state, across all three kinds #16947)scripts/check_python_file_size.py --audit-ceilings-- clean;service.py's ratchet lowered 962 -> 959 after landing below ceilingpipeline-scripts/detect-hardcoded-values.sh-- zero new findings.secrets.baseline-- scoped scan confirms zero new findings; baseline untouchedRefs #16975