Skip to content

fix(orchestration): route @agent messages by resolved identity, not terminal title - #16237

Merged
brennanb2025 merged 17 commits into
mainfrom
brennanb2025/agent-group-routing-identity
Aug 27, 2026
Merged

brennanb2025 merged 17 commits into
mainfrom
brennanb2025/agent-group-routing-identity

Conversation

@brennanb2025

@brennanb2025 brennanb2025 commented Aug 24, 2026 •

Copy link
Copy Markdown
Contributor
Files Added Deleted Net
Test 10 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​500 $\color{#cf222e}{\Huge{\mathbf{−}}}$​227 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​273
Prod 8 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​196 $\color{#cf222e}{\Huge{\mathbf{−}}}$​30 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​166

ELI5

Sending @claude picked 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: RuntimeTerminalSummary carried no identity field at all. title was 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.
  • resolvePublishedPaneAgentIdentity lives in src/shared, not inside the runtime class, so the decision is testable without a runtime and routing, delivery and the UI can't drift apart.
  • Groups match agentIdentity. titleMatchesAgentNameGroup, buildAgentNameRe and the bespoke GROUP_TITLE_MATCHERS Cursor 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 @claude and @codex.

The old code already knew about the hazard — it carried a special-case predicate for cursor because "fix the text cursor blink" would route @cursor into 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. agentIdentity is 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 @agent send resolves to, covered by the tests below.

Testing

  • 27 assertions in groups.test.ts, rewritten to the new contract, including the three redirect cases and both fail-closed cases.
  • 8 for the publisher, including that a real Grok/Codex/Claude task title cannot rename its pane.
  • 3 RPC fan-out tests updated.
  • Full run: 894 files / 10,224 tests pass; typecheck, oxlint, oxfmt clean.

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.

  • I manually tested these changes locally
  • Automated tests added/updated

Notes

  • Remote/SSH & backwards compatibility: agentIdentity is 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.
  • The behavior loss, stated plainly: a pane with no launch record, no foreground-process evidence, and an unparseable title is no longer addressable by @agent. In practice orchestration panes are launched by Orca, so launchAgent is set. This is the fail-closed trade and it is the part to scrutinise hardest.
  • Cross-platform: the deleted buildAgentNameRe path carried a Windows launcher-suffix rule (grok.exe); identity matching makes it unnecessary rather than dropping coverage.
  • Security: strictly reduces what an untrusted string can authorize.
  • Performance: one map lookup per terminal per send, replacing a regex test.

Agent skill upstream boundary

  • Not applicable.

Checklist

  • This PR is small and focused
  • I explained what changed and why (including ELI5)
  • N/A with reason for visual proof
  • Self-reviewed for correctness, security, and performance
  • Cross-platform, SSH/remote, and version-skew impact considered
  • typecheck, oxlint, oxfmt and the full suite pass locally

@coderabbitai

coderabbitai Bot commented Aug 24, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It 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 reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The 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)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning 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:… Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the primary change: routing @agent messages by resolved identity instead of terminal title.
Description check ✅ Passed The description is mostly complete. It explains the problem, implementation, rationale, linked issue, visual-proof rationale, testing, compatibility, security, performance, and checklist status. The r…
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Full details: Description check

Explanation

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 Coverage

Explanation

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 💡
  • Create stacked PR
  • Commit on current branch
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
src/main/runtime/orchestration/groups.ts (1)

39-53: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Reduce 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 value

Replace as never with a typed table.

as never disables type checking on launchAgent. A misspelled agent name in the table would still compile. Add as const to the it.each table so the values narrow to TuiAgent literals, 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 win

Reuse the already-computed title instead of recomputing it.

In buildTerminalSummary, getLatestLeafTitle(leaf, tab?.title ?? null) runs once for the title field (Line 33043) and again for resolvePaneAgentIdentityField (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 for title (Line 35027) and again for resolvePaneAgentIdentityField (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 buildPtyTerminalSummary using getLatestPtyTitle(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

📥 Commits

Reviewing files that changed from the base of the PR and between afd76a4 and 1516919.

📒 Files selected for processing (15)
  • src/main/runtime/orca-runtime.ts
  • src/main/runtime/orchestration/groups.test.ts
  • src/main/runtime/orchestration/groups.ts
  • src/main/runtime/rpc/methods/orchestration-send.test.ts
  • src/shared/agent-title-core.ts
  • src/shared/agent-title-evidence.test.ts
  • src/shared/agent-title-evidence.ts
  • src/shared/agent-title-identity-characterization.test.ts
  • src/shared/pane-agent-identity-resolver.test.ts
  • src/shared/pane-agent-identity-resolver.ts
  • src/shared/published-pane-agent-identity.test.ts
  • src/shared/published-pane-agent-identity.ts
  • src/shared/runtime-types.ts
  • src/shared/synthetic-agent-title.ts
  • src/shared/terminal-title-agent-type.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 8 remain after this review.

Comment on lines +88 to +93
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')
})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 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.

@brennanb2025

Copy link
Copy Markdown
Contributor Author

Review status: PASS WITH NO CODE REGRESSIONS FOUND

  • Same-model opening and closing readiness passes are clean: no proven P0/P1/P2 findings across SSH/remote, crash/retry, security, performance, functional correctness, mobile backcompat, or Windows/Linux compatibility.
  • Focused fix pushed in 81369ac8df: terminal summary builders now compute each title once and reuse it, avoiding duplicate parsing in the terminal.list hot path.
  • Validation passed: 262 tests across 11 changed/adjacent suites, targeted orchestration/identity tests (206 passed on final rerun), node typecheck, targeted oxlint, max-lines ratchet, and git diff --check.
  • Grok Electron/CDP validation was attempted through orchestration. The branch app launched on CDP 9339 after one bounded retry, but the renderer target remained unavailable; no screenshot could be captured. This PR changes orchestration routing and has no UI surface, so the UI result is recorded as blocked/N/A rather than inventing a flow.

The PR remains focused and ready from code-review/readiness perspective.

@brennanb2025

Copy link
Copy Markdown
Contributor Author

Follow-up: resolved the current-main merge conflict and pushed merge commit 5a196bc8a2. The only conflict was an add/add wording revision in src/shared/agent-title-identity-characterization.test.ts; the test behavior is unchanged and the current-main characterization wording was retained. Post-resolution validation: 226 targeted tests passed, node typecheck passed, and git diff --check passed. GitHub now reports the PR as mergeable (UNSTABLE while checks recalculate).

@brennanb2025

Copy link
Copy Markdown
Contributor Author

Electron validation correction: I reran the check directly using the $electron workflow (dev app with CDP 9339, agent-browser --cdp 9339 tab, snapshot, and screenshot). The actual Orca page target was available at http://localhost:5176/, and the interactive snapshot loaded successfully. Screenshot captured locally at review-artifacts/pr-16237/orca-electron-main.png. The earlier “renderer unavailable” result came from the orchestrated worker’s incorrect/occupied renderer-port assumption and was not a limitation of the Electron skill.

@brennanb2025

Copy link
Copy Markdown
Contributor Author

Grok validation — c7788d918d37a4f7bb86c6fd889592025eb9a4f3

Supervised review of the current remote head c7788d918d37a4f7bb86c6fd889592025eb9a4f3 (brennanb2025/agent-group-routing-identity). Re-fetched immediately before this comment; head has not moved. No commits pushed. Merge status: MERGEABLE / CLEAN.

Review conclusion

Approve. @agent routing now matches the host-published RuntimeTerminalSummary.agentIdentity (launch + foreground process, title last and only when the evidence parser finds an unambiguous name). A pane whose title merely mentions Claude/Codex is no longer a recipient of @claude / @codex. Unknown identity fails closed. This is the intended contract, not a regression.

agentIdentity is a new optional field on an existing summary (wire rule 1). An old client ignores it; a new client reading an old host sees absence and does not guess. No existing field changed meaning. Folder workspaces and SSH summaries use the same builder; identity is resolved on the execution host from PTY evidence it owns.

No in-scope production defect rose to a senior-developer fix. Cosmetic oxfmt wrapping of two resolvePaneAgentIdentityField(...) call sites in orca-runtime.ts was left alone (CI static analysis already passed).

Checks / tests (with evidence)

GitHub PR Checks on this SHA (all observed SUCCESS unless noted):

  • typecheck, static analysis, Git compatibility, cross-version wire compatibility, package, package (windows), shell contracts, xterm patch sync, managed hooks on Node 18
  • tests node 24 1/16 … 16/16 and tests node 26 1/16 … 16/16
  • e2e SKIPPED (no changed e2e specs — detect changed e2e specs succeeded)

Local, this worktree, same SHA:

  • Vitest focused suites: 5 files / 206 tests passed
    • groups.test.ts (27)
    • orchestration-send.test.ts (43)
    • published-pane-agent-identity.test.ts (8)
    • pane-agent-identity-resolver.test.ts (18)
    • agent-title-evidence.test.ts (110)
  • pnpm typecheck — passed
  • oxlint on the 14 changed files — 0 warnings, 0 errors
  • oxfmt --check — would only reflow the two publisher call sites; not changed

Electron / CDP flow

Launched this branch with an isolated profile, not the user's running Orca:

  • Free CDP port 9340; renderer 5179 (5173 was already occupied by another worktree)
  • REMOTE_DEBUGGING_PORT=9340 + config/scripts/run-electron-vite-dev.mjs
  • Confirmed http://127.0.0.1:9340/json/list → type=page title Orca url http://127.0.0.1:5179/
  • agent-browser --cdp 9340 interactive snapshot + screenshots
  • window.api.app.getIdentity() → devRepoRoot=/Users/brennanbenson/orca/workspaces/orca/pr-16237-grok-validation, devBranch=brennanb2025/pr-16237-grok-validation

Negative/edge in the live app: a shell pane with no launchAgent and title Review the Claude session-history fix published agentIdentity: null (fail-closed). @cursor against the fixture set resolved to no recipients.

orchestration.send through renderer runtime:call is fenced (orchestration_migration_required / client_contract_missing) because desktop-ipc does not stamp orchestrationContractVersion. Routing was therefore proven with live terminal.list identities from this instance plus the same resolveGroupAddress send-time function the RPC uses.

Routing evidence (live)

Created four fixture panes on this SHA, then renamed titles so Grok/Codex mention Claude:

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 → only term_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.

01-fresh-app.png

Isolated branch app at CDP 9340 (empty profile, identity confirmed).

02-tabs-claude-mentions.png

Fixture tabs: Grok title mentions Claude, Codex title mentions Claude, real ✳ Claude Code.

03-grok-tab-title-mentions-claude.png

Selected Grok pane titled “Switch Claude and Codex off the load balancer…”. Host published agentIdentity: grok; @claude does not select it.

05-codex-tab-title-mentions-claude.png

Selected Codex pane titled “Review the Claude session-history fix”. Host published agentIdentity: codex.

04-claude-identity-tab.png

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 have launchAgent.
  • 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:call cannot drive orchestration.send without an orchestration contract stamp; CLI/host RPC is the production send path and is covered by orchestration-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.
@brennanb2025
brennanb2025 force-pushed the brennanb2025/agent-group-routing-identity branch from c7788d9 to 6f8344c Compare August 24, 2026 21:59

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.ts
  • src/shared/pane-agent-identity-resolver.test.ts
  • src/shared/pane-agent-identity-resolver.ts
  • src/shared/published-pane-agent-identity.test.ts
  • src/shared/published-pane-agent-identity.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 6 remain after this review.

Comment on lines +141 to +145
if (item.run.incarnation === input.currentRun.incarnation) {
return true
}
superseded.push(item.source)
return false

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 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.

Suggested change
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

@brennanb2025

Copy link
Copy Markdown
Contributor Author

Coordinator validation — live dev instance, real agent processes

Recording validation run directly by the coordinator against head 6f8344cdff, and stating plainly what it does and does not cover.

What ran

Dev instance launched from the PR worktree (agent-identity-plan @ rebuild-16237b), isolated ORCA_DEV_USER_DATA_PATH, CDP 9340, renderer 5178 (5173 belonged to another worktree and was left alone). Instance identity confirmed via window.api.app.getIdentity() before interacting, so this is not another running Orca.

Real claude and real codex processes, launched through terminal.create — not fixtures.

The premise, measured

✳ Claude Code   connected=true   agentIdentity = "claude"
demo-project    connected=true   agentIdentity = "codex"

The host does populate agentIdentity from launch/foreground evidence for real agent panes. Every unit test in this PR stubs that field in a fixture, which assumes exactly the thing under test — this is the first measurement of it.

The second row is the decisive one: the Codex pane's real title is demo-project, containing no agent name at all.

Routing, against those live summaries

@codex  -> [codex pane]     <- title matching could NEVER produce this recipient
@claude -> [claude pane]
@claude, codex pane titled "Fix the Claude session-history bug" -> []
@claude, unidentified pane titled "Claude Code"                 -> []

Reaching 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 finding

Orca titles a Codex pane after its worktree (demo-project), so the old title matching would have failed to deliver @codex to a real Codex pane at all. This fixes non-delivery, not only misdelivery. The bug was worse than the PR description characterizes.

Gaps — stated, not papered over

  • orchestration.send was never exercised end-to-end. It rejects the renderer's IPC client with orchestration_migration_required / client_contract_missing, because desktop-ipc does not stamp orchestrationContractVersion. Both this run and the earlier review fell back to calling resolveGroupAddress on live summaries — real code and real data, but not the full send path. A follow-up review is running specifically to close this via the orca CLI, which does stamp the contract.
  • SSH and WSL are unverified. minimumSource: 'launch' means a pane with no launch record and no readable foreground process is unaddressable by @agent. Real local panes carry that evidence; remote hosts were not tested, and that is where fail-closed could bite.
  • Not re-reviewed since the rebuild. The prior review covered c7788d918d on a base predating feat(agent-status): add an order-independent title evidence parser #16148 and feat(agent-status): add the pane agent identity resolver #16157; both have since merged and the resolver API changed underneath. A re-review is in flight.

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.

@brennanb2025

Copy link
Copy Markdown
Contributor Author

Codex review loop - SSH validation

Head: 6f8344cdff67d3ead031c656575594abf956ff19

Verdict: LAND. The SSH-specific regression question is answered yes: remote agent panes publish non-null agentIdentity, agent-name routing uses it correctly, and title-only evidence fails closed.

  • Target: throwaway Linux arm64 Docker container ff500ea213f5 with key-authenticated SSH and /work/demo-project. The direct GitHub clone was attempted first and failed with could not read Username; I transferred a bundle from a clean local clone of stablyai/demo-project and cloned that bundle remotely.
  • App identity: isolated dev app reported pr-16237 @ brennanb2025/agent-group-routing-identity from /Users/brennanbenson/orca/workspaces/orca/pr-16237; head matched the SHA above. pnpm build:relay passed first, then Orca deployed and started the linux-arm64 relay successfully.
  • SSH pane evidence from live terminal.list: the Codex pane titled Review Claude routing - Codex pane published executionHostId=ssh:ssh-1787615643456-5gmakr and agentIdentity=codex; the Claude pane titled Review Codex routing - Claude pane published the same SSH host and agentIdentity=claude. These were quick-launch-equivalent Orca agent panes with host-owned launchAgent and foreground argv0 evidence.
  • Positive routing: remote orca orchestration send --to @claude created one message to Claude handle term_06784b1d-a13d-4684-8c41-7143f2956f27, not the Codex pane whose title says Claude. --to @codex created one message to Codex handle term_70381506-a715-45d8-857d-5878841c6e82, not the Claude pane whose title says Codex. Read-only mailbox checks showed only the Claude subjects in the Claude inbox and only the Codex subject in the Codex inbox.
  • Fail closed: a third SSH pane titled Claude Codex title-only decoy had agentIdentity absent in the raw runtime object (None in the formatted proof) and its mailbox remained empty. When the real Claude or Codex pane was used as sender and therefore excluded, @claude and @codex each failed with No recipients resolved for group address, proving the title-only pane was unaddressable.
  • Backing state: the three remote foreground processes were present as codex, claude, and plain bash on distinct remote PTYs. /work/demo-project remained clean on main, with one worktree and no test branches or extra /work folders.
  • Logs: renderer console had 0 errors. The main log showed platform detection, relay upload, native dependency install, and Relay started successfully; there were no SSH or relay failures during the flow. Two legacy-worker liveness warnings occurred at isolated-app startup before any workspace existed and did not recur during SSH actions.
  • Cleanup: the Electron process group, container, relay, temp key, temp profile, config, and local screenshots were removed. The PR worktree returned to its pre-test state (?? review-artifacts/ was pre-existing).

SSH terminal identities

SSH routing mailboxes

@brennanb2025

brennanb2025 commented Aug 25, 2026 •

Copy link
Copy Markdown
Contributor Author

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): wsl-pane.png

Real agent: Codex, launched in the Orca-managed Windows pane through WSL with:
wsl -e bash -lc 'cd /mnt/c/Users/neil/orca/workspaces/orca/agent-group-routing-identity && exec codex'
A preceding real-process probe in that same pane returned codex-cli 0.148.0; the subsequent pane read showed the Codex TUI running (ANSI TUI output, no shell prompt). The task title was then renamed to mention the other agent: claude title while codex WSL.

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:

  • orca orchestration send ... --to @claude ... -> No recipients resolved for group address: @claude
  • orca orchestration send ... --to @codex ... -> No recipients resolved for group address: @codex
    Both failed before delivery, so there was no receiving-pane acknowledgement. This is the expected fail-closed outcome when WSL foreground-process identity is unreadable, but it is a shipping regression for this PR because a real Codex pane has no non-null host-resolved RuntimeTerminalSummary.agentIdentity.

@coderabbitai

coderabbitai Bot commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

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.

@brennanb2025

Copy link
Copy Markdown
Contributor Author

Electron UI validation (CDP, this worktree)

Head: 8927287e491370cefc7523e4d270eec572d21718
Instance: pr-16237 @ brennanb2025/agent-group-routing-identity via Playwright CDP on 127.0.0.1:9334 (isolated profile). Production code was not edited.

Exercised the real New tab menu: Orca-launched Codex, Orca-launched Claude, and a manual zsh pane. Codex and the shell were titled Review the Claude session-history fix (the recorded title that used to attract @claude).

Verbatim identity

Surface launchAgent foregroundAgent published agentIdentity
Manual shell (title contains “Claude”) null absent (zsh) absent (fail-closed)
Orca Codex (same Claude-containing tab title) "codex" "codex" (routingTrusted: true) "codex"
Orca Claude "claude" "claude" (routingTrusted: true) "claude"

@claude would resolve only to the Claude pane; @codex only to the Codex pane. Title-only evidence does not publish identity.

Screenshots

Orca-launched Codex under a Claude-containing tab title:

01-codex-titled-claude.png

Orca-launched Claude:

03-claude-after-trust.png

Manual shell with the same Claude-containing title (no launchAgent):

04-manual-shell-titled-claude.png

Agents sidebar (session history, not a live identity list):

05-agents-sidebar.png

Limitations

  • Desktop-IPC orchestration.send returned client_contract_missing; recipients were proven from published terminal.list.agentIdentity (the field send uses), not from a delivered mailbox message.
  • Isolated profile had no paired environments. Windows high spec is reachable from the user’s production Orca, but this PR instance could not launch a WSL-backed Codex pane without re-pairing. Local WSL is unavailable on this Mac.
  • Foreground-process takeover (start a different agent in an already-launched pane) was not exercised.
  • Live process also loaded uncommitted launch-authority / spawn-commit files present in the worktree.

Full write-up: review-artifacts/pr-16237/electron-validation-report.md (local; not committed).

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 win

Clear stale launch identity when a PTY incarnation changes. recordPtyWorktree updates pty.incarnationId for a reused ptyId but preserves pty.launchAgent. Non-reattach registerPty callers can therefore publish the previous agent through resolvePaneAgentIdentityField, 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

📥 Commits

Reviewing files that changed from the base of the PR and between 8927287 and bcc2471.

📒 Files selected for processing (9)
  • src/main/ipc/pty-daemon-spawn-wsl-runtime.test.ts
  • src/main/ipc/pty-ipc-mock-registry.ts
  • src/main/ipc/pty-spawn-runtime-handle-binding.test.ts
  • src/main/ipc/pty/ipc/spawn-commit.ts
  • src/main/ipc/pty/pane/launch-authority.test.ts
  • src/main/ipc/pty/pane/launch-authority.ts
  • src/main/ipc/pty/runtime/spawn-commit.ts
  • src/main/runtime/orca-runtime-provider-reattach-launch-identity.test.ts
  • src/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',

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 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

Comment on lines +39 to +41
!args.isReattach ||
!isTuiAgent(args.launchAgent) ||
!isPtyIncarnationId(args.incarnationId)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 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.

@brennanb2025

Copy link
Copy Markdown
Contributor Author

Final review status: ready to merge

Final head: 7543a91f467117d8913cbe04e5ddb20775fccb44

WSL verdict

Fresh 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:

launchAgent: codex
foregroundAgent: wsl.exe
agentIdentity: codex

foregroundAgent: wsl.exe reflects the Windows/WSL process boundary; trusted launchAgent still supplies the correct identity. No title fallback was restored.

Issues found and fixed during review

  • Fixed a separate daemon-reattach gap so provider-owned launch identity survives native, WSL, SSH, headless/runtime, renderer, CLI, and split-daemon reattach paths only when the pane and PTY incarnation match.
  • Fixed stale post-exit identity so a pane is no longer routable through @codex/@claude after the agent returns to a shell. Unverified SSH exits retain the existing remote-liveness behavior.
  • Normalized the remaining formatter-only EOF issue.

Validation

  • Same-model review loop: CLEAN on the final pushed SHA.
  • Closing readiness review: no P0/P1/P2 findings; no functional, security, compatibility, data-loss, performance, SSH, WSL, Windows, Linux, mobile, or wire regression found.
  • Focused validation: 8 test files passed; 1,312 tests passed, 1 skipped.
  • pnpm tc:node: passed.
  • Direct oxlint, oxfmt --check, and changed-span git diff --check: passed.
  • Final-head GitHub CI: PR Checks passed (49 jobs successful, 1 intentionally skipped); PR test LoC passed.
  • GitHub reports the PR mergeable with a clean merge state.

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 bcc2471b88 and 447a03f89a; the final formatting-only commit is 7543a91f46.

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.
@brennanb2025

Copy link
Copy Markdown
Contributor Author

Codex review loop - WSL shell-started retest

Retested head 3cdeb2189c on Windows high spec / Ubuntu 24.04 WSL.

Verdict: STILL REGRESSED.

How the tested pane was started: I created a fresh WSL terminal tab with no launch agent, clicked Orca's rendered Terminal input through Playwright CDP, typed exactly codex, and pressed Enter. I did not use Orca's agent picker, launcher, or any agent-launch flow.

For pane 72ce5a79-3a86-4427-b7a4-2e5a1f115f14:aa749739-7994-4a34-abfc-31f09cce0861, the requested terminal.list fields were:

{
  "agentIdentity": null,
  "launchAgent": null,
  "foregroundAgent": "wsl.exe",
  "title": "pr16237-wsl-prbuild-a..."
}

agentIdentity was omitted by terminal.list (recorded as null above).

Hook installation and events: Orca's Codex runtime hooks.json and config.toml existed, and hooks.json contained eight references to codex-hook.sh. However, /home/neil/.orca/agent-hooks/codex-hook.sh was missing and config.toml contained zero trust entries for it, so the managed hook installation was incomplete. The agent-hooks server snapshot for this exact pane returned hookRows: [] and tabRows: [], while renderer agentStatusByPaneKey[paneKey] was also null: this hand-started pane posted no hook/provider-session events.

Contract-stamped routing: From a separate plain WSL helper pane, I sent @codex using the branch's orca-dev CLI with subject WSL shell-started routing probe 3cdeb2189c. The CLI reported one recipient but selected term_fbde32aa-847e-46b9-b714-73810cb7bf9a, an older launcher-created Codex pane, not the shell-started pane. Receiving-mailbox inspection confirmed msg_0ad18257618b in that launcher pane and zero messages in the shell-started pane (term_aa8596f1-a49f-4970-bb49-f8df46c61a93).

The missing signal is managed hook evidence: the shell-started pane has no launcher signal, Windows foreground inspection sees only wsl.exe, and the hooks did not fire. This is a WSL shell-started-agent hook capability gap; I am not proposing title fallback, because that would restore the misdelivery this PR is meant to prevent.

Claude was not repeated; the Codex pass produced decisive evidence for the required regression question.

pr16237-wsl-shell-started-codex.png

… 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.
@brennanb2025
brennanb2025 merged commit 6ba6d58 into main Aug 27, 2026
51 checks passed
paidaxingyo666 pushed a commit to paidaxingyo666/Manta that referenced this pull request Aug 30, 2026
…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)
enrick-dev pushed a commit to enrick-dev/orca that referenced this pull request Oct 15, 2026
…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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant