Repository navigation
fix(orchestration): route @agent messages by resolved identity, not terminal title - #16237
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThe change adds host-resolved pane agent identities to terminal and PTY runtime summaries. Agent-name orchestration groups now route by published identity instead of terminal titles. Provider reattach metadata is validated, propagated through PTY spawn commits, and restored when the PTY incarnation matches. Tests cover identity precedence, fail-closed resolution, group routing, reattachment, and rejected metadata. 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
Full details: Description checkExplanation The description is mostly complete. It explains the problem, implementation, rationale, linked issue, visual-proof rationale, testing, compatibility, security, performance, and checklist status. The required AI Disclosure and Review headings are not included, but these omissions are non-critical given the detailed content. Full details: Docstring CoverageExplanation Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 12 functions across 15 files. (1 skipped: 1 too large.) ✨ Finishing Touches 💡 2📝 Generate docstrings 💡
🛠️ Fix failing CI checks 💡
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 |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (3)
src/main/runtime/orchestration/groups.ts (1)
39-53: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winReduce the rationale comments.
Keep only a short comment for the non-obvious invariant. Remove comments that restate the test name or code behavior.
src/main/runtime/orchestration/groups.ts#L39-L53: replace the long policy explanation with one concise identity-routing invariant.src/main/runtime/orchestration/groups.ts#L91-L93: reduce this to one line or remove it.src/main/runtime/orchestration/groups.test.ts#L21-L24: use a brief fixture invariant.src/main/runtime/orchestration/groups.test.ts#L103-L105: remove this setup explanation.src/main/runtime/orchestration/groups.test.ts#L186-L190: reduce this to one fail-closed invariant.src/main/runtime/rpc/methods/orchestration-send.test.ts#L590-L593: use a brief fixture invariant.src/main/runtime/rpc/methods/orchestration-send.test.ts#L749-L750: remove or reduce this test explanation.As per coding guidelines, “Comments must be concise, non-obvious, and brief—prefer one line; do not explain obvious behavior or walk through code.”
Source: Coding guidelines
src/shared/published-pane-agent-identity.test.ts (1)
10-16: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueReplace
as neverwith a typed table.
as neverdisables type checking onlaunchAgent. A misspelled agent name in the table would still compile. Addas constto theit.eachtable so the values narrow toTuiAgentliterals, then drop the cast.♻️ Proposed refactor
it.each([ ['Switch Claude and Codex off the load balancer… - grok', 'grok'], ['Review the Claude session-history fix', 'codex'], ['✳ Fix the text cursor blink', 'claude'] - ])('keeps %j as its launched agent', (title, launchAgent) => { - expect(resolve({ launchAgent: launchAgent as never, title })).toBe(launchAgent) + ] as const)('keeps %j as its launched agent', (title, launchAgent) => { + expect(resolve({ launchAgent, title })).toBe(launchAgent) })src/main/runtime/orca-runtime.ts (1)
33043-33055: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winReuse the already-computed title instead of recomputing it.
In
buildTerminalSummary,getLatestLeafTitle(leaf, tab?.title ?? null)runs once for thetitlefield (Line 33043) and again forresolvePaneAgentIdentityField(Line 33053). Store the result in a local variable and pass it to both places.The same pattern occurs in
buildPtyTerminalSummary:getLatestPtyTitle(pty)runs once fortitle(Line 35027) and again forresolvePaneAgentIdentityField(Line 35037).Both calls scan the same title sources on the same object and always return the same result, so the second call is redundant work and adds a duplicate line to maintain.
♻️ Proposed fix for `buildTerminalSummary`
+ const latestTitle = getLatestLeafTitle(leaf, tab?.title ?? null) return { handle: this.issueHandle(leaf), ptyId: leaf.ptyId, incarnationId: pty?.incarnationId ?? null, orphaned: false, worktreeId: leaf.worktreeId, worktreePath: worktree?.path ?? '', branch: worktree?.branch ?? '', tabId: leaf.tabId, leafId: leaf.leafId, - title: getLatestLeafTitle(leaf, tab?.title ?? null), + title: latestTitle, connected: provenAbsent ? false : leaf.connected, writable: provenAbsent ? false : leaf.writable, lastOutputAt: leaf.lastOutputAt, preview: leaf.preview, ...(leaf.lastExitCause ? { exitCause: leaf.lastExitCause } : {}), ...this.terminalExecutionHostField(leaf.ptyId, leaf.worktreeId), - ...this.resolvePaneAgentIdentityField( - pty?.launchAgent, - pty?.foregroundAgent, - getLatestLeafTitle(leaf, tab?.title ?? null) - ) + ...this.resolvePaneAgentIdentityField(pty?.launchAgent, pty?.foregroundAgent, latestTitle) }Apply the same change to
buildPtyTerminalSummaryusinggetLatestPtyTitle(pty).Also applies to: 35027-35039
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 8130f4ca-7520-401c-822a-a43767b2f0cc
📒 Files selected for processing (15)
src/main/runtime/orca-runtime.tssrc/main/runtime/orchestration/groups.test.tssrc/main/runtime/orchestration/groups.tssrc/main/runtime/rpc/methods/orchestration-send.test.tssrc/shared/agent-title-core.tssrc/shared/agent-title-evidence.test.tssrc/shared/agent-title-evidence.tssrc/shared/agent-title-identity-characterization.test.tssrc/shared/pane-agent-identity-resolver.test.tssrc/shared/pane-agent-identity-resolver.tssrc/shared/published-pane-agent-identity.test.tssrc/shared/published-pane-agent-identity.tssrc/shared/runtime-types.tssrc/shared/synthetic-agent-title.tssrc/shared/terminal-title-agent-type.ts
Included review availability: Your plan provides up to 10 included reviews per hour; 8 remain after this review.
| it('lets an anchored name outrank a foreign vendor marker', () => { | ||
| expect(agentFor('✳ agy')).toBe('claude') | ||
| expect(reasonFor('✳ agy')).toBe('vendor-marker') | ||
| expect(agentFor('✳ codex')).toBe('claude') | ||
| expect(reasonFor('✳ codex')).toBe('vendor-marker') | ||
| }) |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
The test name contradicts its own assertions and the resolver documentation.
The test is named "lets an anchored name outrank a foreign vendor marker", but both assertions expect 'claude' with reason 'vendor-marker'. No anchored name wins here.
The assertions match the current implementation. collectAnchoredNames strips the ✳ sigil and calls agentForWholeTitle(' agy'); that function returns null unless the remainder is a display label or carries a Windows launcher suffix. So agy never anchors.
The documentation in src/shared/agent-title-evidence.ts states the opposite. Lines 288-289 say ✳ agy is "the strongest name evidence a title can carry", and lines 355-356 say ✳ agy resolves to Antigravity. src/shared/agent-title-identity-characterization.test.ts line 101 also records ✳ agy → Claude as a DEFECT that the resolver is meant to fix.
Decide which behavior is intended. If ✳ agy must stay Claude, rename this test and correct the three comment blocks in agent-title-evidence.ts. If agy must anchor, extend agentForWholeTitle to accept a bare single-token name.
|
Review status: PASS WITH NO CODE REGRESSIONS FOUND
The PR remains focused and ready from code-review/readiness perspective. |
|
Follow-up: resolved the current-main merge conflict and pushed merge commit |
|
Electron validation correction: I reran the check directly using the |
5a196bc to
c7788d9
Compare
Grok validation —
|
| Published title | launchAgent |
live agentIdentity |
|---|---|---|
Switch Claude and Codex off the load balancer… - grok |
grok | grok |
Review the Claude session-history fix |
codex | codex |
✳ Claude Code |
claude | claude |
Review the Claude session-history fix |
(none) | null (fail-closed) |
resolveGroupAddress on those live summaries:
@claude→ onlyterm_f2cdb98c-…(the Claude pane)@codex→ only the Codex pane — not the Grok tab whose title contains “Codex” and “Claude”@grok→ only the Grok pane@cursor→[]
Before/after UI proof is N/A: this is a non-visual routing-correctness change. The screenshots below are runtime validation evidence of the launched branch instance and the title/identity fixture, not a visual delta.
Isolated branch app at CDP 9340 (empty profile, identity confirmed).
Fixture tabs: Grok title mentions Claude, Codex title mentions Claude, real ✳ Claude Code.
Selected Grok pane titled “Switch Claude and Codex off the load balancer…”. Host published agentIdentity: grok; @claude does not select it.
Selected Codex pane titled “Review the Claude session-history fix”. Host published agentIdentity: codex.
The only @claude recipient: ✳ Claude Code with agentIdentity: claude.
Residual risks
- Fail-closed is a behavior loss, as the PR states: a pane with no launch record, no foreground-process evidence, and an unparseable/free-text title is no longer addressable by
@agent. Orchestration panes Orca launched still havelaunchAgent. - Publisher evidence is PTY launch/foreground + title, not tab
launchAgent/ live-hook rows. A restored or mirrored leaf whose PTY launch is empty can fail closed even if the tab still knows the owner. That is the documented unknown-fails-closed trade, not a title-matching regression. - Mixed-version send: an old host still title-matches; a new host with an old peer’s summaries (no
agentIdentity) fails closed. Preferable to misdelivery. - Renderer
runtime:callcannot driveorchestration.sendwithout an orchestration contract stamp; CLI/host RPC is the production send path and is covered byorchestration-send.test.ts. - Mobile: no mobile-facing surface change (additive optional summary field only).
Fixes/commits pushed: none. Conflicts: none (current, CLEAN).
Four ladders answer "which agent is in this pane" independently — the tab icon, the open-tab/search occupant, the sidebar title rows, and the sidebar hook-row fallback — and they disagree. Two consult the terminal title before the launch record, so a string Orca parsed outranks a fact Orca owns. resolvePaneAgentIdentity is the single ranked answer. Two rules, one of which is not an ordering: 1. Evidence is ranked by how directly it observes the process; a display title is last. 2. Each observation carries the runId of the agent run it describes. Evidence from a superseded run is INELIGIBLE, not merely outranked. Rule 2 is the part reordering could never supply. A completed hook naming A plus a title naming B is either a bug (hook right, title stale) or a legitimate pane reclaim (title right) — identical signals, opposite correct answers. Run ids make them different facts: in the bug both belong to the current run; in the reclaim the hook belongs to a previous one. That pair ships as a test asserting the two produce opposite answers from the same evidence. Missing run ids are treated as eligible. Absence means "this peer does not publish them", not "this is stale", so an old host's rows are never blanked. Sibling evidence is opt-in so pane-scoped consumers cannot inherit another pane's agent. No consumer imports this yet; each migrates separately with its own evidence. Verified non-vacuous: reversing the authority order fails 10 of 18 assertions and removing the run filter fails 3.
**Duplicate evidence of one source resolved by array order.** `eligible.find(...)` returned the first match, so two live hooks naming different agents were settled by input position — the exact property this resolver exists to remove. The original order-independence test only used DISTINCT sources, so it never exercised it. Conflicting same-class evidence now returns null with `ambiguousAt`, and does NOT fall through to a weaker source: letting a title answer whenever two hooks disagree is worse than saying nothing. **A bare numeric runId collided across authority restarts.** `incarnation` is a total order only within one `authorityId` (agent-status-observation.ts states this), and the id is regenerated per authority instance, so a restarted host counting from its own floor would report `1` and match an unrelated live run 1. The run key now carries its authority, and evidence from a DIFFERENT authority is treated as incomparable — kept, like an absent key — rather than as stale. **Title stayed reachable by consumers that authorize writes.** Ranking it last makes misuse unlikely; `minimumSource` makes it impossible. An action consumer passes `'launch'` and weaker evidence is dropped before ranking, so routing or delivery cannot name a target from a parsed string even by reordering its inputs. Display surfaces omit it and are unaffected. Also restores the generic agent-vocabulary parameter, which lives on the routing branch and was lost when this branch was rebased. Each fix is mutation-verified: first-match restored fails 3, ignoring authority fails 1, dropping the floor fails 2. The authority test was itself vacuous on the first attempt — both sides used `incarnation: 1`, so a resolver ignoring authority still passed on the numeric compare. It now uses differing incarnations. The remaining review finding, that `process > launch` has no freshness bound, is NOT fixed here: it needs an observation timestamp the evidence type does not yet carry. Recorded rather than silently dropped.
…erminal title
`@claude` picked its recipients with `buildAgentNameRe('claude').test(title)`, so any pane whose
TITLE contained the word received Claude's messages. Terminal titles carry task text, and people
describe agent work in them, so this is the ordinary case rather than a contrived one: the
recorded title "Switch Claude and Codex off the load balancer… - grok" is a Grok pane that
received both @claude and @codex. Misdelivered instructions, not a cosmetic slip.
The cause is that `RuntimeTerminalSummary` carried no identity at all — `title` was the only
identity-ish field on it, so routing by title was the only option available. Fix the input:
- `RuntimeTerminalSummary.agentIdentity?: TuiAgent` — optional, host-resolved from launch and
foreground-process evidence the host owns, with the title ranked last and contributing only
when the evidence parser finds an unambiguous name. A title that merely mentions an agent
yields no evidence, which is the whole point.
- `resolvePublishedPaneAgentIdentity` in `src/shared` rather than inside the runtime class, so
the decision is testable without a runtime and so routing, delivery and the UI cannot drift.
- Groups match `agentIdentity`; the title matcher and its bespoke Cursor predicate are deleted.
Unknown fails closed. `agentIdentity` is absent when the host predates the field or had no
evidence beyond the title, and delivery is an action: not delivering is visible and recoverable
(the sender sees no recipients), while delivering to the wrong agent is neither. The optional
field is additive, so an old client simply ignores it (wire rule 1).
This is also the first real caller of the evidence parser and the identity resolver.
Tests: 27 in groups, 8 for the publisher, 3 RPC fan-out cases updated to the new contract. The
`@cursor`-must-not-match-"text cursor blink" hazard is now excluded structurally instead of by a
per-agent predicate.
Verified non-vacuous by mutation: swapping the process/title ranks fails 2 publisher assertions,
and reverting groups to title matching fails 15 of 27. One earlier mutation silently failed to
apply after formatting reflowed the block — the file was checked before trusting the result.
…r routing Rebuilt on current main so this carries the hardened parser from #16148 and the corrected resolver from #16157 (authority-scoped run keys, no order-dependent duplicate resolution). Applies the resolver's new `minimumSource` floor at the publisher. What this publishes authorizes an action — routing decides which real agent pane receives a message — so ranking title last is not enough; the floor removes it from consideration entirely, and no amount of reordering by a caller can bring it back. The trade, stated because it is a real capability loss: a hook-less agent over SSH that Orca did not launch, and whose foreground process the host cannot read, is no longer addressable by @agent. Accepted because a message delivered into the wrong agent's prompt is unrecoverable while an undelivered one is visible — the sender sees zero recipients. Whether real panes actually carry launch/foreground evidence is the open question, and is what live validation must answer.
c7788d9 to
6f8344c
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: a61ef37a-cce3-46b7-a4a8-07c74c6575d3
📥 Commits
Reviewing files that changed from the base of the PR and between 81369ac8dfef4ea3816d3cff42237ec3963492e3 and 6f8344c.
📒 Files selected for processing (5)
src/main/runtime/orca-runtime.tssrc/shared/pane-agent-identity-resolver.test.tssrc/shared/pane-agent-identity-resolver.tssrc/shared/published-pane-agent-identity.test.tssrc/shared/published-pane-agent-identity.ts
Included review availability: Your plan provides up to 10 included reviews per hour; 6 remain after this review.
| if (item.run.incarnation === input.currentRun.incarnation) { | ||
| return true | ||
| } | ||
| superseded.push(item.source) | ||
| return false |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Discard only older runs.
At Line 141, a higher incarnation is newer than currentRun, not superseded. The current !== check discards it and can select stale title evidence or return no identity. Keep equal or higher incarnations. Add a regression test for evidence at incarnation 8 with currentRun at incarnation 7.
Proposed fix
- if (item.run.incarnation === input.currentRun.incarnation) {
+ if (item.run.incarnation >= input.currentRun.incarnation) {
return true
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if (item.run.incarnation === input.currentRun.incarnation) { | |
| return true | |
| } | |
| superseded.push(item.source) | |
| return false | |
| if (item.run.incarnation >= input.currentRun.incarnation) { | |
| return true | |
| } | |
| superseded.push(item.source) | |
| return false |
Coordinator validation — live dev instance, real agent processesRecording validation run directly by the coordinator against head What ranDev instance launched from the PR worktree ( Real The premise, measuredThe host does populate The second row is the decisive one: the Codex pane's real title is Routing, against those live summariesReaching a pane whose title contains no agent name proves routing is identity-based rather than title-based — stronger evidence than the redirect case alone. Unexpected findingOrca titles a Codex pane after its worktree ( Gaps — stated, not papered over
Cleanup: dev instance stopped by process group after verifying the command line was this run's, both ports released, temp profile removed, other worktree's server untouched. |
|
Codex review loop - SSH validation Head: Verdict: LAND. The SSH-specific regression question is answered yes: remote agent panes publish non-null
|
|
Codex review loop - Windows WSL validation Head SHA: 6f8344c. Environment: Orca dev Electron instance on the paired runtime Windows high spec, worktree C:/Users/neil/orca/workspaces/orca/agent-group-routing-identity, branch brennanb2025/agent-group-routing-identity. The visible app was attached over CDP and showed the active branch/worktree, a renamed pane title codex WSL routing probe, and a WSL path (/mnt/c/Users/neil/orca/workspaces/orca/agent-group-routing-identity). Evidence screenshot (gh image extension): Real agent: Codex, launched in the Orca-managed Windows pane through WSL with: REGRESSION — FAIL CLOSED ON WINDOWS/WSL: orca terminal list --environment a13452d2-3e6c-4766-8281-4568160e82b1 --worktree path:C:/Users/neil/orca/workspaces/orca/agent-group-routing-identity --json returned the live Codex pane but published no agentIdentity field (identity is null/absent). Exact live row: handle term_904ed467-f2bd-4a75-9708-f68a84efdd3b, title claude title while codex WSL, connected true, writable true, preview was Codex TUI output. Routing proof at the receiving pane: CLI contract sends were attempted from that pane:
|
|
Note GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer. |
Electron UI validation (CDP, this worktree)Head: Exercised the real New tab menu: Orca-launched Codex, Orca-launched Claude, and a manual zsh pane. Codex and the shell were titled Verbatim identity
ScreenshotsOrca-launched Codex under a Claude-containing tab title: Orca-launched Claude: Manual shell with the same Claude-containing title (no Agents sidebar (session history, not a live identity list): Limitations
Full write-up: |
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/main/runtime/orca-runtime.ts (1)
10896-10923: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick winClear stale launch identity when a PTY incarnation changes.
recordPtyWorktreeupdatespty.incarnationIdfor a reusedptyIdbut preservespty.launchAgent. Non-reattachregisterPtycallers can therefore publish the previous agent throughresolvePaneAgentIdentityField, routing orchestration messages to a plain-shell respawn. Clear the launch identity when accepting a different incarnation.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 511abf3d-1ef0-4a95-b598-2266aac73b38
📒 Files selected for processing (9)
src/main/ipc/pty-daemon-spawn-wsl-runtime.test.tssrc/main/ipc/pty-ipc-mock-registry.tssrc/main/ipc/pty-spawn-runtime-handle-binding.test.tssrc/main/ipc/pty/ipc/spawn-commit.tssrc/main/ipc/pty/pane/launch-authority.test.tssrc/main/ipc/pty/pane/launch-authority.tssrc/main/ipc/pty/runtime/spawn-commit.tssrc/main/runtime/orca-runtime-provider-reattach-launch-identity.test.tssrc/main/runtime/orca-runtime.ts
Included review availability: Your plan provides up to 10 included reviews per hour; 7 remain after this review.
| chmodSync: chmodSyncMock, | ||
| renameSync: renameSyncMock, | ||
| rmSync: rmSyncMock, | ||
| mkdtempSync: () => '/tmp/orca-watcher-canary-test', |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Use a platform-neutral temporary path.
Return join(tmpdir(), 'orca-watcher-canary-test') instead of a hardcoded /tmp/... path. As per coding guidelines, “File paths: Use path.join or Electron/Node path utilities — never assume / or \.”
Source: Coding guidelines
| !args.isReattach || | ||
| !isTuiAgent(args.launchAgent) || | ||
| !isPtyIncarnationId(args.incarnationId) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Require literal true for reattach admission.
Line 39 admits a truthy non-boolean value such as "false". This can restore a provider launch identity for a fresh PTY. Use args.isReattach !== true.
Final review status: ready to mergeFinal head: WSL verdictFresh Orca-launched WSL usage is not regressed. The original failed pane was a generic pane where Codex was started manually from a shell, so it correctly had no trusted launch authority. Re-testing a pane created through Orca's own agent launch flow returned:
Issues found and fixed during review
Validation
Grok Electron visual proof and screenshots: #16237 (comment) The Grok comment records the earlier committed SHA, but the running app loaded the then-present source changes that were subsequently committed in Verdict: mergeable and ready. |
… was started
Two defects, one cause: identity was inferred from the outside instead of read from the agent.
**Hook evidence was never plumbed in.** The publisher considered `process`, `launch` and `title`
and contained zero hook references — while the resolver ranks `live-hook` first. The top rung of
the ladder was never connected.
That made identity depend on Orca having launched the agent. Most agents are started by typing
`claude` or `codex` at a shell, which leaves no launch record. On macOS the foreground process
still names them, so the gap was invisible. On WSL the Windows host reads the foreground process
as `wsl.exe` — the distro wrapper, not the agent inside it — so those panes had no signal at all
and became unaddressable by `@agent`.
A hook is the agent reporting itself, so it survives both: no launch record needed, and no
dependency on reading a process across the WSL boundary.
**`launch` outranked `completed-hook`.** Ranking is now by TENSE rather than by how authoritative
a source sounds:
present: live-hook > process
past: completed-hook > launch > sleeping-session > sibling > title
A launch record is an event, not a state — it stays true after the agent exits, which is why a
pane reused after closing its agent kept reading as the old one. A completed hook at least proves
the agent actually ran in that pane; a launch record only proves Orca tried to start one.
Neither rank was covered: all 392 existing tests passed unchanged after reordering. Mutation now
fails 2 on the old order and 4 with hook evidence removed.
Known remaining gap, deliberately not papered over: a hand-started WSL agent with no managed hooks
has no identity signal at all. Restoring a title guess there would reinstate the misdelivery this
PR exists to prevent.
|
Codex review loop - WSL shell-started retest Retested head Verdict: STILL REGRESSED. How the tested pane was started: I created a fresh WSL terminal tab with no launch agent, clicked Orca's rendered For pane {
"agentIdentity": null,
"launchAgent": null,
"foregroundAgent": "wsl.exe",
"title": "pr16237-wsl-prbuild-a..."
}
Hook installation and events: Orca's Codex runtime Contract-stamped routing: From a separate plain WSL helper pane, I sent The missing signal is managed hook evidence: the shell-started pane has no launcher signal, Windows foreground inspection sees only Claude was not repeated; the Codex pass produced decisive evidence for the required regression question. |
… source
An earlier revision passed `minimumSource: 'launch'` so routing could not see a title at any rank,
reasoning that a display string must never authorize a write. That conflated the evidence parser
with the raw substring match it replaced.
`buildAgentNameRe('claude').test(title)` was the misdelivery. `collectAgentTitleEvidence` returns
null on exactly those shapes: "Review the Claude session-history fix" on a Codex pane yields
nothing, and "Switch Claude and Codex off the load balancer… - grok" yields grok from its owner
suffix. Ranking title last is therefore sufficient; refusing it is not necessary.
Refusing it had a real cost. An agent a user starts by hand inside an Orca WSL terminal has no
launch record, no readable foreground process (the Windows host sees `wsl.exe`, not the agent in
the distro), and — until managed Codex hooks install there — no hook either. An unambiguous title
was the only thing left, and dropping it made that pane unaddressable by @agent where the previous
code could reach it. That is a regression, and most agents are started that way.
End-to-end coverage added at the routing layer with title allowed: @claude still does not reach a
Codex pane whose task text names Claude, @codex still does not reach a Grok pane whose task text
names Codex, and a pane identified only by an unambiguous title is reachable again.
… exist Reverts the tense-based reorder from this branch. The reasoning behind it was sound as far as it went — a launch record is a past event, not an observation, which is why a reused pane kept reading as its previous agent — but it fixed one staleness by opening a worse one. A completed hook is past tense too, and without an agent-run key it never expires at all. Ranking it above `launch` lets a stale hook from a previous agent outrank the launch record Orca stamped for the process running NOW. pane-agent-owner.ts already says this in its own comment: "Ranking launch/live-hook above the completed/sleeping records keeps a genuine pane on its real agent and stops a stale record from hijacking it." The reorder belongs with authority-scoped run generation, which is what makes any past-tense evidence expire. It is staged in the migration plan rather than shipped here. What this branch keeps: hook evidence feeding pane identity (so an agent a user starts by hand is identified from its own report rather than needing a launch record), and title restored as a genuine last resort behind the evidence parser.
… leaf `makePaneKey` throws on a leaf id that is not a UUID. The hook-evidence lookup called it unguarded inside `buildTerminalSummary`, so a single such leaf took down `terminal.list` for the whole list rather than degrading that one pane — 136 tests across 5 files, and the native code-quality gate tripped separately on a duplicate test title. Both were mine, and both were caught by CI rather than by me: I ran the focused suites before pushing instead of the affected directories.
…oup-routing-identity
…oup-routing-identity
…erminal title (stablyai#16237) * feat(agent-status): add the pane agent identity resolver Four ladders answer "which agent is in this pane" independently — the tab icon, the open-tab/search occupant, the sidebar title rows, and the sidebar hook-row fallback — and they disagree. Two consult the terminal title before the launch record, so a string Orca parsed outranks a fact Orca owns. resolvePaneAgentIdentity is the single ranked answer. Two rules, one of which is not an ordering: 1. Evidence is ranked by how directly it observes the process; a display title is last. 2. Each observation carries the runId of the agent run it describes. Evidence from a superseded run is INELIGIBLE, not merely outranked. Rule 2 is the part reordering could never supply. A completed hook naming A plus a title naming B is either a bug (hook right, title stale) or a legitimate pane reclaim (title right) — identical signals, opposite correct answers. Run ids make them different facts: in the bug both belong to the current run; in the reclaim the hook belongs to a previous one. That pair ships as a test asserting the two produce opposite answers from the same evidence. Missing run ids are treated as eligible. Absence means "this peer does not publish them", not "this is stale", so an old host's rows are never blanked. Sibling evidence is opt-in so pane-scoped consumers cannot inherit another pane's agent. No consumer imports this yet; each migrates separately with its own evidence. Verified non-vacuous: reversing the authority order fails 10 of 18 assertions and removing the run filter fails 3. * fix(agent-status): close three resolver contract holes found in review **Duplicate evidence of one source resolved by array order.** `eligible.find(...)` returned the first match, so two live hooks naming different agents were settled by input position — the exact property this resolver exists to remove. The original order-independence test only used DISTINCT sources, so it never exercised it. Conflicting same-class evidence now returns null with `ambiguousAt`, and does NOT fall through to a weaker source: letting a title answer whenever two hooks disagree is worse than saying nothing. **A bare numeric runId collided across authority restarts.** `incarnation` is a total order only within one `authorityId` (agent-status-observation.ts states this), and the id is regenerated per authority instance, so a restarted host counting from its own floor would report `1` and match an unrelated live run 1. The run key now carries its authority, and evidence from a DIFFERENT authority is treated as incomparable — kept, like an absent key — rather than as stale. **Title stayed reachable by consumers that authorize writes.** Ranking it last makes misuse unlikely; `minimumSource` makes it impossible. An action consumer passes `'launch'` and weaker evidence is dropped before ranking, so routing or delivery cannot name a target from a parsed string even by reordering its inputs. Display surfaces omit it and are unaffected. Also restores the generic agent-vocabulary parameter, which lives on the routing branch and was lost when this branch was rebased. Each fix is mutation-verified: first-match restored fails 3, ignoring authority fails 1, dropping the floor fails 2. The authority test was itself vacuous on the first attempt — both sides used `incarnation: 1`, so a resolver ignoring authority still passed on the numeric compare. It now uses differing incarnations. The remaining review finding, that `process > launch` has no freshness bound, is NOT fixed here: it needs an observation timestamp the evidence type does not yet carry. Recorded rather than silently dropped. * fix(orchestration): route @agent messages by resolved identity, not terminal title `@claude` picked its recipients with `buildAgentNameRe('claude').test(title)`, so any pane whose TITLE contained the word received Claude's messages. Terminal titles carry task text, and people describe agent work in them, so this is the ordinary case rather than a contrived one: the recorded title "Switch Claude and Codex off the load balancer… - grok" is a Grok pane that received both @claude and @codex. Misdelivered instructions, not a cosmetic slip. The cause is that `RuntimeTerminalSummary` carried no identity at all — `title` was the only identity-ish field on it, so routing by title was the only option available. Fix the input: - `RuntimeTerminalSummary.agentIdentity?: TuiAgent` — optional, host-resolved from launch and foreground-process evidence the host owns, with the title ranked last and contributing only when the evidence parser finds an unambiguous name. A title that merely mentions an agent yields no evidence, which is the whole point. - `resolvePublishedPaneAgentIdentity` in `src/shared` rather than inside the runtime class, so the decision is testable without a runtime and so routing, delivery and the UI cannot drift. - Groups match `agentIdentity`; the title matcher and its bespoke Cursor predicate are deleted. Unknown fails closed. `agentIdentity` is absent when the host predates the field or had no evidence beyond the title, and delivery is an action: not delivering is visible and recoverable (the sender sees no recipients), while delivering to the wrong agent is neither. The optional field is additive, so an old client simply ignores it (wire rule 1). This is also the first real caller of the evidence parser and the identity resolver. Tests: 27 in groups, 8 for the publisher, 3 RPC fan-out cases updated to the new contract. The `@cursor`-must-not-match-"text cursor blink" hazard is now excluded structurally instead of by a per-agent predicate. Verified non-vacuous by mutation: swapping the process/title ranks fails 2 publisher assertions, and reverting groups to title matching fails 15 of 27. One earlier mutation silently failed to apply after formatting reflowed the block — the file was checked before trusting the result. * perf(runtime): reuse terminal title during summary build * fix(orchestration): refuse title evidence when publishing identity for routing Rebuilt on current main so this carries the hardened parser from stablyai#16148 and the corrected resolver from stablyai#16157 (authority-scoped run keys, no order-dependent duplicate resolution). Applies the resolver's new `minimumSource` floor at the publisher. What this publishes authorizes an action — routing decides which real agent pane receives a message — so ranking title last is not enough; the floor removes it from consideration entirely, and no amount of reordering by a caller can bring it back. The trade, stated because it is a real capability loss: a hook-less agent over SSH that Orca did not launch, and whose foreground process the host cannot read, is no longer addressable by @agent. Accepted because a message delivered into the wrong agent's prompt is unrecoverable while an undelivered one is visible — the sender sees zero recipients. Whether real panes actually carry launch/foreground evidence is the open question, and is what live validation must answer. * fix(pty): preserve agent identity on daemon reattach * fix(runtime): retire stale pane agent identity * chore: normalize runtime types formatting * fix(agent-status): identify a pane from its own hook, not from how it was started Two defects, one cause: identity was inferred from the outside instead of read from the agent. **Hook evidence was never plumbed in.** The publisher considered `process`, `launch` and `title` and contained zero hook references — while the resolver ranks `live-hook` first. The top rung of the ladder was never connected. That made identity depend on Orca having launched the agent. Most agents are started by typing `claude` or `codex` at a shell, which leaves no launch record. On macOS the foreground process still names them, so the gap was invisible. On WSL the Windows host reads the foreground process as `wsl.exe` — the distro wrapper, not the agent inside it — so those panes had no signal at all and became unaddressable by `@agent`. A hook is the agent reporting itself, so it survives both: no launch record needed, and no dependency on reading a process across the WSL boundary. **`launch` outranked `completed-hook`.** Ranking is now by TENSE rather than by how authoritative a source sounds: present: live-hook > process past: completed-hook > launch > sleeping-session > sibling > title A launch record is an event, not a state — it stays true after the agent exits, which is why a pane reused after closing its agent kept reading as the old one. A completed hook at least proves the agent actually ran in that pane; a launch record only proves Orca tried to start one. Neither rank was covered: all 392 existing tests passed unchanged after reordering. Mutation now fails 2 on the old order and 4 with hook evidence removed. Known remaining gap, deliberately not papered over: a hand-started WSL agent with no managed hooks has no identity signal at all. Restoring a title guess there would reinstate the misdelivery this PR exists to prevent. * fix(orchestration): restore title as the last resort, not a forbidden source An earlier revision passed `minimumSource: 'launch'` so routing could not see a title at any rank, reasoning that a display string must never authorize a write. That conflated the evidence parser with the raw substring match it replaced. `buildAgentNameRe('claude').test(title)` was the misdelivery. `collectAgentTitleEvidence` returns null on exactly those shapes: "Review the Claude session-history fix" on a Codex pane yields nothing, and "Switch Claude and Codex off the load balancer… - grok" yields grok from its owner suffix. Ranking title last is therefore sufficient; refusing it is not necessary. Refusing it had a real cost. An agent a user starts by hand inside an Orca WSL terminal has no launch record, no readable foreground process (the Windows host sees `wsl.exe`, not the agent in the distro), and — until managed Codex hooks install there — no hook either. An unambiguous title was the only thing left, and dropping it made that pane unaddressable by @agent where the previous code could reach it. That is a regression, and most agents are started that way. End-to-end coverage added at the routing layer with title allowed: @claude still does not reach a Codex pane whose task text names Claude, @codex still does not reach a Grok pane whose task text names Codex, and a pane identified only by an unambiguous title is reachable again. * revert(agent-status): keep launch above completed-hook until run keys exist Reverts the tense-based reorder from this branch. The reasoning behind it was sound as far as it went — a launch record is a past event, not an observation, which is why a reused pane kept reading as its previous agent — but it fixed one staleness by opening a worse one. A completed hook is past tense too, and without an agent-run key it never expires at all. Ranking it above `launch` lets a stale hook from a previous agent outrank the launch record Orca stamped for the process running NOW. pane-agent-owner.ts already says this in its own comment: "Ranking launch/live-hook above the completed/sleeping records keeps a genuine pane on its real agent and stops a stale record from hijacking it." The reorder belongs with authority-scoped run generation, which is what makes any past-tense evidence expire. It is staged in the migration plan rather than shipped here. What this branch keeps: hook evidence feeding pane identity (so an agent a user starts by hand is identified from its own report rather than needing a launch record), and title restored as a genuine last resort behind the evidence parser. * fix(runtime): guard the pane key so terminal.list survives a non-UUID leaf `makePaneKey` throws on a leaf id that is not a UUID. The hook-evidence lookup called it unguarded inside `buildTerminalSummary`, so a single such leaf took down `terminal.list` for the whole list rather than degrading that one pane — 136 tests across 5 files, and the native code-quality gate tripped separately on a duplicate test title. Both were mine, and both were caught by CI rather than by me: I ran the focused suites before pushing instead of the affected directories. * fix(runtime): declare published terminal agent identity * fix(runtime): demote completed hook identity evidence (cherry picked from commit 6ba6d58)
…erminal title (stablyai#16237) * feat(agent-status): add the pane agent identity resolver Four ladders answer "which agent is in this pane" independently — the tab icon, the open-tab/search occupant, the sidebar title rows, and the sidebar hook-row fallback — and they disagree. Two consult the terminal title before the launch record, so a string Orca parsed outranks a fact Orca owns. resolvePaneAgentIdentity is the single ranked answer. Two rules, one of which is not an ordering: 1. Evidence is ranked by how directly it observes the process; a display title is last. 2. Each observation carries the runId of the agent run it describes. Evidence from a superseded run is INELIGIBLE, not merely outranked. Rule 2 is the part reordering could never supply. A completed hook naming A plus a title naming B is either a bug (hook right, title stale) or a legitimate pane reclaim (title right) — identical signals, opposite correct answers. Run ids make them different facts: in the bug both belong to the current run; in the reclaim the hook belongs to a previous one. That pair ships as a test asserting the two produce opposite answers from the same evidence. Missing run ids are treated as eligible. Absence means "this peer does not publish them", not "this is stale", so an old host's rows are never blanked. Sibling evidence is opt-in so pane-scoped consumers cannot inherit another pane's agent. No consumer imports this yet; each migrates separately with its own evidence. Verified non-vacuous: reversing the authority order fails 10 of 18 assertions and removing the run filter fails 3. * fix(agent-status): close three resolver contract holes found in review **Duplicate evidence of one source resolved by array order.** `eligible.find(...)` returned the first match, so two live hooks naming different agents were settled by input position — the exact property this resolver exists to remove. The original order-independence test only used DISTINCT sources, so it never exercised it. Conflicting same-class evidence now returns null with `ambiguousAt`, and does NOT fall through to a weaker source: letting a title answer whenever two hooks disagree is worse than saying nothing. **A bare numeric runId collided across authority restarts.** `incarnation` is a total order only within one `authorityId` (agent-status-observation.ts states this), and the id is regenerated per authority instance, so a restarted host counting from its own floor would report `1` and match an unrelated live run 1. The run key now carries its authority, and evidence from a DIFFERENT authority is treated as incomparable — kept, like an absent key — rather than as stale. **Title stayed reachable by consumers that authorize writes.** Ranking it last makes misuse unlikely; `minimumSource` makes it impossible. An action consumer passes `'launch'` and weaker evidence is dropped before ranking, so routing or delivery cannot name a target from a parsed string even by reordering its inputs. Display surfaces omit it and are unaffected. Also restores the generic agent-vocabulary parameter, which lives on the routing branch and was lost when this branch was rebased. Each fix is mutation-verified: first-match restored fails 3, ignoring authority fails 1, dropping the floor fails 2. The authority test was itself vacuous on the first attempt — both sides used `incarnation: 1`, so a resolver ignoring authority still passed on the numeric compare. It now uses differing incarnations. The remaining review finding, that `process > launch` has no freshness bound, is NOT fixed here: it needs an observation timestamp the evidence type does not yet carry. Recorded rather than silently dropped. * fix(orchestration): route @agent messages by resolved identity, not terminal title `@claude` picked its recipients with `buildAgentNameRe('claude').test(title)`, so any pane whose TITLE contained the word received Claude's messages. Terminal titles carry task text, and people describe agent work in them, so this is the ordinary case rather than a contrived one: the recorded title "Switch Claude and Codex off the load balancer… - grok" is a Grok pane that received both @claude and @codex. Misdelivered instructions, not a cosmetic slip. The cause is that `RuntimeTerminalSummary` carried no identity at all — `title` was the only identity-ish field on it, so routing by title was the only option available. Fix the input: - `RuntimeTerminalSummary.agentIdentity?: TuiAgent` — optional, host-resolved from launch and foreground-process evidence the host owns, with the title ranked last and contributing only when the evidence parser finds an unambiguous name. A title that merely mentions an agent yields no evidence, which is the whole point. - `resolvePublishedPaneAgentIdentity` in `src/shared` rather than inside the runtime class, so the decision is testable without a runtime and so routing, delivery and the UI cannot drift. - Groups match `agentIdentity`; the title matcher and its bespoke Cursor predicate are deleted. Unknown fails closed. `agentIdentity` is absent when the host predates the field or had no evidence beyond the title, and delivery is an action: not delivering is visible and recoverable (the sender sees no recipients), while delivering to the wrong agent is neither. The optional field is additive, so an old client simply ignores it (wire rule 1). This is also the first real caller of the evidence parser and the identity resolver. Tests: 27 in groups, 8 for the publisher, 3 RPC fan-out cases updated to the new contract. The `@cursor`-must-not-match-"text cursor blink" hazard is now excluded structurally instead of by a per-agent predicate. Verified non-vacuous by mutation: swapping the process/title ranks fails 2 publisher assertions, and reverting groups to title matching fails 15 of 27. One earlier mutation silently failed to apply after formatting reflowed the block — the file was checked before trusting the result. * perf(runtime): reuse terminal title during summary build * fix(orchestration): refuse title evidence when publishing identity for routing Rebuilt on current main so this carries the hardened parser from stablyai#16148 and the corrected resolver from stablyai#16157 (authority-scoped run keys, no order-dependent duplicate resolution). Applies the resolver's new `minimumSource` floor at the publisher. What this publishes authorizes an action — routing decides which real agent pane receives a message — so ranking title last is not enough; the floor removes it from consideration entirely, and no amount of reordering by a caller can bring it back. The trade, stated because it is a real capability loss: a hook-less agent over SSH that Orca did not launch, and whose foreground process the host cannot read, is no longer addressable by @agent. Accepted because a message delivered into the wrong agent's prompt is unrecoverable while an undelivered one is visible — the sender sees zero recipients. Whether real panes actually carry launch/foreground evidence is the open question, and is what live validation must answer. * fix(pty): preserve agent identity on daemon reattach * fix(runtime): retire stale pane agent identity * chore: normalize runtime types formatting * fix(agent-status): identify a pane from its own hook, not from how it was started Two defects, one cause: identity was inferred from the outside instead of read from the agent. **Hook evidence was never plumbed in.** The publisher considered `process`, `launch` and `title` and contained zero hook references — while the resolver ranks `live-hook` first. The top rung of the ladder was never connected. That made identity depend on Orca having launched the agent. Most agents are started by typing `claude` or `codex` at a shell, which leaves no launch record. On macOS the foreground process still names them, so the gap was invisible. On WSL the Windows host reads the foreground process as `wsl.exe` — the distro wrapper, not the agent inside it — so those panes had no signal at all and became unaddressable by `@agent`. A hook is the agent reporting itself, so it survives both: no launch record needed, and no dependency on reading a process across the WSL boundary. **`launch` outranked `completed-hook`.** Ranking is now by TENSE rather than by how authoritative a source sounds: present: live-hook > process past: completed-hook > launch > sleeping-session > sibling > title A launch record is an event, not a state — it stays true after the agent exits, which is why a pane reused after closing its agent kept reading as the old one. A completed hook at least proves the agent actually ran in that pane; a launch record only proves Orca tried to start one. Neither rank was covered: all 392 existing tests passed unchanged after reordering. Mutation now fails 2 on the old order and 4 with hook evidence removed. Known remaining gap, deliberately not papered over: a hand-started WSL agent with no managed hooks has no identity signal at all. Restoring a title guess there would reinstate the misdelivery this PR exists to prevent. * fix(orchestration): restore title as the last resort, not a forbidden source An earlier revision passed `minimumSource: 'launch'` so routing could not see a title at any rank, reasoning that a display string must never authorize a write. That conflated the evidence parser with the raw substring match it replaced. `buildAgentNameRe('claude').test(title)` was the misdelivery. `collectAgentTitleEvidence` returns null on exactly those shapes: "Review the Claude session-history fix" on a Codex pane yields nothing, and "Switch Claude and Codex off the load balancer… - grok" yields grok from its owner suffix. Ranking title last is therefore sufficient; refusing it is not necessary. Refusing it had a real cost. An agent a user starts by hand inside an Orca WSL terminal has no launch record, no readable foreground process (the Windows host sees `wsl.exe`, not the agent in the distro), and — until managed Codex hooks install there — no hook either. An unambiguous title was the only thing left, and dropping it made that pane unaddressable by @agent where the previous code could reach it. That is a regression, and most agents are started that way. End-to-end coverage added at the routing layer with title allowed: @claude still does not reach a Codex pane whose task text names Claude, @codex still does not reach a Grok pane whose task text names Codex, and a pane identified only by an unambiguous title is reachable again. * revert(agent-status): keep launch above completed-hook until run keys exist Reverts the tense-based reorder from this branch. The reasoning behind it was sound as far as it went — a launch record is a past event, not an observation, which is why a reused pane kept reading as its previous agent — but it fixed one staleness by opening a worse one. A completed hook is past tense too, and without an agent-run key it never expires at all. Ranking it above `launch` lets a stale hook from a previous agent outrank the launch record Orca stamped for the process running NOW. pane-agent-owner.ts already says this in its own comment: "Ranking launch/live-hook above the completed/sleeping records keeps a genuine pane on its real agent and stops a stale record from hijacking it." The reorder belongs with authority-scoped run generation, which is what makes any past-tense evidence expire. It is staged in the migration plan rather than shipped here. What this branch keeps: hook evidence feeding pane identity (so an agent a user starts by hand is identified from its own report rather than needing a launch record), and title restored as a genuine last resort behind the evidence parser. * fix(runtime): guard the pane key so terminal.list survives a non-UUID leaf `makePaneKey` throws on a leaf id that is not a UUID. The hook-evidence lookup called it unguarded inside `buildTerminalSummary`, so a single such leaf took down `terminal.list` for the whole list rather than degrading that one pane — 136 tests across 5 files, and the native code-quality gate tripped separately on a duplicate test title. Both were mine, and both were caught by CI rather than by me: I ran the focused suites before pushing instead of the affected directories. * fix(runtime): declare published terminal agent identity * fix(runtime): demote completed hook identity evidence













ELI5
Sending
@claudepicked its recipients by checking whether each terminal's title contained the word "claude". Titles contain task text, and people describe agent work in them — so a Codex pane reviewing a Claude PR received Claude's messages.This routes by the agent the host actually resolved instead. It's also the first real caller of the evidence parser (#16148) and the identity resolver (#16157), which until now had no consumers.
What Changed
The cause was the input, not the matcher:
RuntimeTerminalSummarycarried no identity field at all.titlewas the only identity-ish thing on it, so routing by title was the only option available.RuntimeTerminalSummary.agentIdentity?: TuiAgent— optional, resolved by the host from launch and foreground-process evidence it owns. Title ranks last and contributes only when the evidence parser finds an unambiguous name.resolvePublishedPaneAgentIdentitylives insrc/shared, not inside the runtime class, so the decision is testable without a runtime and routing, delivery and the UI can't drift apart.agentIdentity.titleMatchesAgentNameGroup,buildAgentNameReand the bespokeGROUP_TITLE_MATCHERSCursor predicate are deleted.Why this is the right shape
A real recorded title:
"Switch Claude and Codex off the load balancer… - grok". That is a Grok pane. It matched@claudeand@codex.The old code already knew about the hazard — it carried a special-case predicate for
cursorbecause "fix the text cursor blink" would route@cursorinto a live Claude prompt. That's one agent's worth of a general problem, and every future agent needed its own clause. Now it's excluded structurally.Unknown fails closed.
agentIdentityis absent when the host predates the field, or when it had no evidence beyond the title. Delivery is an action: not delivering is visible and recoverable — the sender sees no recipients — while delivering into the wrong agent's prompt is neither.Linked Issue
Part of the agent-identity rearchitecture tracked by #9236.
Visual Proof
N/A— orchestration message routing has no UI surface. The observable change is which handles a@agentsend resolves to, covered by the tests below.Testing
groups.test.ts, rewritten to the new contract, including the three redirect cases and both fail-closed cases.Verified non-vacuous by mutation: reverting groups to title matching fails 15 of 27; swapping the process/title ranks fails 2 publisher assertions.
Worth recording: an earlier mutation attempt silently failed to apply because formatting had reflowed the target block, and it read as "the test is fine." The file was diffed before trusting the second result. A mutation test that doesn't mutate proves nothing.
Notes
agentIdentityis a new optional field (wire rule 1). An old client ignores it; a new client reading an old host's summary sees absence and fails closed rather than guessing. No existing field changes meaning.@agent. In practice orchestration panes are launched by Orca, solaunchAgentis set. This is the fail-closed trade and it is the part to scrutinise hardest.buildAgentNameRepath carried a Windows launcher-suffix rule (grok.exe); identity matching makes it unnecessary rather than dropping coverage.Agent skill upstream boundary
Checklist
N/Awith reason for visual proof