feat(orchestration): let a structured chat run orchestration as itself - #22568
brennanb2025 wants to merge 11 commits into
Conversation
There was a problem hiding this comment.
ℹ️ No critical issues — one minor correctness edge inline.
Reviewed changes
- Session id injected into every structured child.
structuredWorkerChildIdentityEnvbecomesstructuredSessionChildIdentityEnv, settingORCA_AGENT_SESSION_IDplus the CLI PATH entry for chats and workers alike; both native providers (Claude resolver, Codex builder) build their child env here. - Terminal view carries the same id.
ensureAgentSessionpasses the id in spawn env viawithStructuredSessionTerminalViewEnv, deliberately not in the persistedlaunchConfig.agentEnv. - CLI acts as the session. The shared resolver returns
undefinedwhen the injected id names the caller and refuses a conflicting caller flag withconsumer_fencedbefore any request; all 16 caller-naming verbs inherit the rule, with a per-verb and a 30-verb enumerated test. - Envelope and stripping. The id rides
orchestrationCompatibilityEvidence.agentSessionId; SSH host-CLI passthrough, inherited PTY env (local/daemon/relay), and WSL are handled, and the sharedstructworker_handle helpers move tosrc/shared. - Tests. Per-verb and enumerated CLI coverage plus env-propagation tests spanning native Claude/Codex (including a spawned real child), terminal view, WSL, PTY inheritance, and SSH.
DeepSeek Flash (free via Pullfrog for OSS) | 𝕏
| return undefined | ||
| } | ||
| const ownHandle = process.env.ORCA_TERMINAL_HANDLE | ||
| return isStructuredWorkerHandle(ownHandle) ? ownHandle : `session:${sessionId}` |
There was a problem hiding this comment.
A structured worker running in terminal view carries the PTY pane handle in ORCA_TERMINAL_HANDLE, not the minted structworker_ handle (only the native chat child gets that), so this returns session:<id> rather than the worker's handle. The host binds that worker's caller to its structworker_ handle, so a flagless dispatch-show --preamble previews different coordinator text than the real dispatch writes, and --from <own structworker_ handle> is refused here even though the host accepts it. Low impact since session:<id> is a valid alias in both places, but it diverges from the PR's stated "a worker's handle" invariant, and the tests only cover a native-chat worker.
Technical details
# `injectedSessionAddress` mis-names a worker's terminal view
## Affected sites
- `src/cli/handlers/orchestration/terminal-identity.ts:211` — derives the address from `ORCA_TERMINAL_HANDLE`; a terminal view's value is the PTY pane handle.
- `src/cli/handlers/orchestration/dispatch-handlers.ts:49` — uses `injectedSessionAddress()` for the preview's coordinator text.
- `src/main/runtime/rpc/orchestration-session-caller.ts:121-125` — the host prefers `worker.handle` for `caller.address`, so real dispatch text and the accepted `--from` set include the `structworker_` handle.
- `src/main/runtime/orca-runtime-get-agent-session-execution-namespace.ts:178` — terminal view gets only the id, so the worker handle never reaches the CLI there.
## Required outcome
- The flagless address a session presents should equal the address the host binds. Either deliver the worker handle to the terminal view (a dedicated env var the CLI reads) or state that `session:<id>` is the canonical flagless address for a session in terminal view.
## Open questions for the human
- Is a dispatched worker ever handed off to terminal view in practice? If not, this is unreachable and only the docs/claim need adjusting.There was a problem hiding this comment.
ℹ️ No new issues in this delta. The run-scoped caller-flag gap for
gate-list/task-listis closed cleanly, with tests that fail against the previous code. The one open thread from the prior review —injectedSessionAddress()for a worker in terminal view — is untouched by these commits and remains open.
Reviewed changes
- Added
refuseConflictingSessionCallertoterminal-identity.ts. A session still validates the caller flag on a--run-scoped listing: it throwsconsumer_fencedbefore any RPC when--fromnames another actor, and returnsundefinedotherwise. - Routed
gate-list --runandtask-list --runthrough it. Both previously short-circuited toundefinedwhen--runwas present, so a conflicting--fromon the run-scoped path was silently dropped instead of refused. They now match every other caller-naming verb. - Pinned the run-scoped rule. A per-command test asserts the flagless path sends no caller param, that another actor's handle is refused with
consumer_fencedand zero RPCs, and that the session's ownsession:<id>spelling is accepted.
DeepSeek Flash (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
Important
The new structured-session-cli-login-shell.test.ts hardcodes /bin/zsh, which the default Linux unit-test shards do not have installed, so it will fail the required Unit-tests check. Verified locally: 2 of its 3 cases exit with spawn /bin/zsh ENOENT.
Reviewed changes
- Named this app's CLI by absolute path.
prependOrcaCliDirToChildPathnow returns the launcher, andstructuredSessionChildIdentityEnvsetsORCA_CLI_COMMANDto it (devorca-dev, packagedresources/bin/orca/orca.exe, packaged Linux shim) instead of the literal'orca', falling back only when there is no app environment. - Added a real-login-shell test.
structured-session-cli-login-shell.test.tssets a tempHOMEwhose profile prepends a stand-in globalorca, then asserts bareorcaresolves to the global and"$ORCA_CLI_COMMAND"resolves to this app forzsh -lcandbash -lc. - Updated env expectations. Claude, Codex, and identity-env tests now expect the absolute dev launcher.
The ORCA_CLI_COMMAND value is intentionally kept to the bare-name union when sent on the wire (resolveCompatibilityCliCommand, schema in orchestration-params.ts:45,125), while the locally printed resume commands use the absolute path and are quoted by renderCommand — both correct. The prior review's open injectedSessionAddress() thread is untouched by this commit.
DeepSeek Flash (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
- Split the login-shell suite by shell. The zsh arm moved to a new
structured-session-cli-login-shell.live-shell.test.ts;structured-session-cli-login-shell.test.tskeeps only the bash arm, so it no longer spawns a shell the ordinary unit lane lacks. - Extracted the shared setup.
structured-session-login-shell-test-harness.tsholds the tempHOME, the stand-in globalorcastubs, and thestructuredSessionChildIdentityEnvwiring, consumed by both suites. - Wired the zsh arm into the real-shell lane.
pr.ymladds the new file to the shell-contracts job andunit-tests.ymladds it to the shard--excludelist, so the required Unit-tests check no longer runs it. - Taught the lane guard to see a
program-field zsh.realZshUsageinconfig/scripts/pr-workflow-parallelism.test.mjsgained aprogram:\s*['"](?:\/(?:usr\/)?bin\/)?zsh['"]alternative, and the new file joinedshellContractFiles— the mechanism that let the test escape the zsh-install lane.
Verified locally on the zsh-less runner: the lane guard passes (19/19), the bash arm passes (2/2), and the new live-shell file fails only when run directly outside the exclude.
DeepSeek Flash (free via Pullfrog for OSS) | 𝕏
22f54f2 to
3ace023
Compare
2d9dc67 to
fef6854
Compare
…ren and let the CLI act as it Every structured session's child (native Claude, native Codex, and the terminal view) carries ORCA_AGENT_SESSION_ID and reaches the Orca CLI. The CLI sends the id in the orchestration envelope; when present it is the caller, and a caller flag naming anyone else is refused before any request. The id is stripped from inherited PTY env and from the SSH host-CLI passthrough, and crosses into WSL so the host can refuse the cross-host claim.
3ace023 to
f771efd
Compare
…ve Codex, the terminal view, WSL, PTY inheritance and SSH
…verb that names its caller Adds the per-verb table (flagless acts as the session; a conflicting --from or --terminal is refused before any request; the session's own spellings are accepted), the enumerated guess population with its positive control, the structured worker's own handle, the identity-less refusal for an older child, the unchanged terminal agent, and the envelope. dispatch-show's --from only fills preview text, so it passes through unfenced and a session's flagless preview names the address the real dispatch writes.
…marker; the id is checked first
…verbatim from the CLI
…LIs that predate it A CLI older than the id, reached through a global install when a shell rc resets PATH, would otherwise guess a sibling's terminal in a chat that no longer carries the marker. It refuses on the marker instead; a current CLI checks the id first, so the marker never makes a session with an id identity-less.
…-list scoped by --run A --run listing needs no caller, so both handlers skipped the resolver and a --from naming another actor was dropped silently under a session. The conflict check now runs on that branch too; terminal callers are unchanged.
…ured session's login shells A provider can run each command in a login shell: Codex runs zsh -lc, and the profile rebuilds PATH, putting a global install (possibly an older Orca) ahead of the directory Orca prepended. ORCA_CLI_COMMAND, which an agent resolves the CLI from first, is now the absolute launcher in that directory (the native launcher on Windows), so no shell's startup files can swap it. The PATH prepend stays for shells that read no profile. Found by the live coordinator run of the next PR.
…p's absolute launcher
…ne that installs zsh The ordinary Linux unit lane has no /bin/zsh, so the zsh arm failed there with ENOENT. It moves to a live-shell file registered in the shell-contracts lane; the bash arm keeps running in every lane. The lane guard's detector now also sees a zsh spawned through the ProcessSpec program field, which is how this test escaped it.
fef6854 to
3af56b1
Compare
… session route

ELI5
A native chat now knows its own orchestration name. Orca gives every chat an id; this PR puts that id in the environment of everything the chat runs, and teaches the
orcaCLI to send it. So when a chat runsorca orchestration run-createorcheck, it acts as itself, instead of refusing or (worse) guessing another pane's terminal and eating that pane's mail. If the chat passes--from/--terminalnaming someone else, the CLI refuses before sending anything. Terminal agents behave exactly as before.What Changed
Third PR of the structured-chat orchestration stack, stacked on #22555. Supersedes #19648 (not closed here).
1. The id is injected into every structured session's child
structuredSessionChildIdentityEnv(renamed fromstructuredWorkerChildIdentityEnv, since it now covers every structured session) setsORCA_AGENT_SESSION_IDto the Orca-minted session id. Both native launches build their env here:claude-structured-launch-resolution.ts;codex-structured-child-environment.ts.ensureAgentSessionpasses the id in the terminal's spawn env when it runs under handoff authority (withStructuredSessionTerminalViewEnv).agentEnv, because the launch config persists. A later relaunch from it would replay the id without the handoff that binds the terminal to the session.structworker_handle it was minted. For orchestration the id wins and the host maps it back to that handle (refactor(orchestration): resolve every caller to one orchestration actor #22555), so the worker still has one identity.2. An ordinary chat can now reach the Orca CLI, by absolute path
ORCA_CLI_COMMANDand the PATH entry for this app's CLI. An ordinary chat inherited the ambient PATH. Itsorcatherefore resolved only if the user had installed the CLI globally, and on packaged Linux bareorcais GNOME's screen reader.prependOrcaCliDirToChildPathtreatment a PTY agent gets:userData/cli/bin;resources/bin;orcashim.ORCA_CLI_COMMANDis the absolute launcher in that directory.prependOrcaCliDirToChildPathnow returns the path:orca-dev(orca-dev.cmdon Windows);resources/bin/orca;resources/bin/orca.exe, the native launcher, becauseorca.cmdrefuses message bodies;orca./bin/zsh -lc. The login profile (macOSpath_helper, a user's.zprofile) rebuilds PATH and puts a global install ahead of the prepended directory. On the test machine bareorcabecame an older global CLI, which refused (safely, on the identity-less marker).ORCA_CLI_COMMANDfirst (the existing skill contract). An absolute path survives any shell's startup files, on every platform.orcastill works in shells that read no profile.shell_environment_policy.set.PATHis re-exported only when Codex wraps a command with its shell snapshot. When no snapshot applies (for example a cwd that differs from the snapshot's), the command runs aszsh -lcand the profile reorders PATH again.allow_login_shell = falsewould drop every login-profile variable except PATH, which is all Orca hydrates.orcatyped inside a login shell still resolves by the user's PATH. An older global Orca there refuses on the marker. A same-version or newer global CLI runs itself; which runtime it reaches was not verified.3. One CLI precedence rule, in one place
resolveOrchestrationTerminalHandle(andresolveCoordinatorTerminalHandle) now check the injected id first. When it is present, the caller is the session:undefined, so no caller param is sent;orchestrationCompatibilityEvidence.agentSessionIdcarries the id, and the host binds the caller.session:<id>, the bare id, or a structured worker's ownstructworker_handle (the acceptance set refactor(orchestration): resolve every caller to one orchestration actor #22555's host uses);consumer_fenced: "This command runs as agent session<id>, so --from<x>would act as a different caller. Drop --from: … No request was sent." That includes the terminal view's own pane handle.replyandworker-list, which fix: route native chat orchestration by agent session identity #19648 left guessing, are covered with no per-handler code.gate-list --runandtask-list --runneed no caller and skip the resolver. They still run the conflict check (refuseConflictingSessionCaller), so a conflicting--fromis refused there too, never dropped. Terminal callers on that branch are unchanged.checksends noterminalPaneKeyandsendsends nosenderPaneKeyunder a session.dispatch-show --preamble: its--fromonly fills the preview text and names no caller, so it passes through unfenced. A flagless preview under a session writes the address the real dispatch writes:session:<id>, or a worker's handle.createOrchestrationCompatibilityEnvelope). The SSH paths build evidence from a remote shell's environment, where a session id can never name a session on this host.session_caller_*, refactor(orchestration): resolve every caller to one orchestration actor #22555) pass through verbatim, including the worker-without-identity refusal.worker-listdoes not widen one into an unscoped listing: the list call is refused the same way.4. Where the id is stripped
buildHostCliEnvdeletesORCA_AGENT_SESSION_ID(and the marker) from the host env it spreads into a remote command's CLI. A remote command never claims a local session.REMOTE_CONTEXT_ENV_VARSalready never forwarded it from the remote side.envToDeletefor keys the caller did not set explicitly.ORCA_AGENT_SESSION_ID/uinWSLENV), beside the WSL host stamp. refactor(orchestration): resolve every caller to one orchestration actor #22555 then refuses the claim, naming the host boundary. Without it, the pane handle would silently become that chat's caller inside WSL.5. The
ORCA_STRUCTURED_SESSIONmarkerKept, and still written beside the id.
checkacts as the session.orchestration-structured-session-no-identity.test.ts.Other
STRUCTURED_WORKER_HANDLE_PREFIX/isStructuredWorkerHandlemoved tosrc/shared/structured-worker-handle.tsso the CLI uses the same definition. Main re-exports it, and the D1 actor codec uses it instead of its own literal.ORCA_AGENT_SESSION_IDandORCA_STRUCTURED_SESSIONinherited from whoever runs the suite. Without it, 37 existing CLI tests flip branch when the suite is run from a structured chat (measured by setting the id in the runner's env with and without the file).Why
Ported from #19648 unchanged in intent:
agentEnv);WSLENVentry for the id;checksuppresses the pane key under a session, and its output names the session;send's lifecycle guard accepts the id;ask's timed-out resume command omits--fromfor a session.Changed from #19648:
--terminalbeat the id incheck, let--frombeat it indispatch/ask, and silently ignored--frominrun-*. Now the id wins and a conflicting flag is refused.replyandworker-listare covered. At fix: route native chat orchestration by agent session identity #19648's head they still guessed a sibling's terminal. They go through the same resolver now.agentSessionId/runtimeFenceparams.ORCA_AGENT_SESSION_RUNTIME_FENCEis dropped.ORCA_TERMINAL_HANDLE/ORCA_PANE_KEY/marker inside the builder are dropped. They never took effect:process.envunder the builder's output (openCodexAppServerConnection,buildClaudeChildProcessEnv);Why the rule lives in the resolver: every verb that names a caller already asks
resolveOrchestrationTerminalHandle. Putting the id check there means a verb cannot forget it, and the enumerated test below proves no verb guesses.Linked Issue
None — part of the structured chat status/orchestration program. Supersedes #19648; stacked on #22555.
Visual Proof
N/A — no UI change. The change is child-process environment and CLI identity; see Testing for what was and was not run end to end.
Testing
New and updated tests
src/cli/orchestration-session-caller-cli.test.ts, per verb, for the 16 caller-naming verbs, with the env of a chat in terminal view (id + its pane handle + pane key, and a sibling for the guess to find):getTerminalHandle, noterminal.*RPC;consumer_fencedand zero RPCs, including the terminal view's own pane handle;session:<id>and the bare id are accepted..zprofileand.bash_profileput a stand-in globalorcafirst.-lc, bareorcaresolves to the global. This is the positive control: the profile really reorders PATH."$ORCA_CLI_COMMAND"resolves to this app's launcher.-c, bareorcastays on this app's CLI.structured-session-cli-login-shell.test.ts) runs in every lane.structured-session-cli-login-shell.live-shell.test.ts) runs in the real-shell lane, which installs zsh. It is registered inpr.ymland excluded inunit-tests.yml. The ordinary Linux unit lane has no/bin/zsh, which is why it failed there withENOENT.pr-workflow-parallelism.test.mjs) now also matches a zsh spawned through the ProcessSpecprogram:field, which is how this test escaped it. Removing either the lane entry or the new detector branch turns the guard red.dispatch-show --preamble). This proves the harness can see a guess.worker_doneis sent as the session;--fromadvice;askresume omits--from;dispatch-showpreview address;check/run-current/worker-list, and JSON keepsorcaSessionId;nodechild answering with its own env: id,ORCA_CLI_COMMAND, CLI dir first on PATH;ensureAgentSessionunder handoff passes the id in spawn env and not inlaunchConfig.agentEnv, and a plain resume has none;envToDeleteincludes it;WSLENVcarriesORCA_AGENT_SESSION_ID/ubeside the host stamp;buildHostCliEnvdrops the host's id and never takes the remote's;Ablations: 19 mechanisms, each deleted or reverted, each red. All 19 were re-run at head
c0767e101aagainst the 14 reachable test files (358 tests). The CLI-rule rows (80/33/33) are unchanged. The harness asserts that each replacement matched exactly once.--runlistings' flag)--runlistings' restated flag)checksends the terminal view's pane keysendsends the terminal view's pane keysendguard ignores the idaskresume command names--fromdispatch-showpreview ignores the sessionenvToDelete)ORCA_CLI_COMMANDis the bare nameorcaagainzsh -lcandbash -lccases)gate-list --rundrops a conflicting--fromtask-list --rundrops a conflicting--fromSeparately, with the vitest setup file removed and
ORCA_AGENT_SESSION_IDset in the runner's env, 37 of 61 tests in five existing CLI orchestration files go red. With the file present, all 61 pass.Suites run at
2a688ed73a(env -u ORCA_STRUCTURED_SESSION npx vitest run --config config/vitest.config.ts src/main src/cli src/shared src/relay config/scripts):22f54f283d):browser-manager-viewport-ownership(12);hook-plugin-module-contract(4);patched-dependencies-frozen-install(2, a real pnpm install);skill-recipe-shell(1).orca-runtimescan-cache timing test, which passes 1,298/1,298 alone.pnpm tc:node,tc:cliandtc:webare clean.check-changed-code-quality: 0 findings.oxlintandoxfmt --checkon the changed files are clean.audit:anti-slopis clean.pnpm-lock.yamlis absent from every commit in the range, and no documentation files are added.AI Disclosure
Review
resolveOrchestrationTerminalHandle? The enumerated test says no for all 30 verbs.--from: consistent with the host, which refuses it too.Agent skill upstream boundary
docs/reference/agent-skill-sharing-upstream-boundary.mdand copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.Notes
Can an ordinary structured chat run the CLI?
/usr/local/bin/orcaon this machine). On packaged Linux bareorcais not Orca at all.Host boundaries:
Folder workspaces: unaffected; the id carries no workspace.
Mobile: no mobile code changed; mobile never sends a session claim. Mobile tests were not run.
Mixed versions:
run-create/run-use/run-currentfail their schema. The rest fall back to placeholder attribution on that host:sendandaskfall back to the senderunknown;dispatchfall back to the coordinatorcoordinator;replyis attributed to the original message's recipient (params.from ?? original.to_handle,message-methods.tson main);checkgetsstable_pane_required.session:<id>would avoid those fallbacks, at the cost of writing a straysession:string as a terminal handle on an old host. I chose to omit.Not verified:
orca orchestration …through the built CLI against a live app; the next PR in the stack carries the full end-to-end acceptance.orca.exepath is unit-tested only.orcastill resolves by the profile's PATH order; that behavior predates this PR.claude-structured-real-cli.test.ts,claude-tui-resume-real-binary.integration.test.ts) fail here identically at refactor(orchestration): resolve every caller to one orchestration actor #22555's head. They drive the installedclaudebinary.