Skip to content

feat(agent-status): add an order-independent title evidence parser - #16148

Merged
brennanb2025 merged 12 commits into
mainfrom
brennanb2025/agent-title-evidence-parser
Aug 24, 2026
Merged

brennanb2025 merged 12 commits into
mainfrom
brennanb2025/agent-title-evidence-parser

Conversation

@brennanb2025

@brennanb2025 brennanb2025 commented Aug 23, 2026 •

Copy link
Copy Markdown
Contributor
Files Added Deleted Net
Test 1 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​355 0 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​355
Prod 4 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​392 $\color{#cf222e}{\Huge{\mathbf{−}}}$​21 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​371

ELI5

Orca decides which agent is in a pane by scanning the terminal title for agent names in a fixed order — first match wins. So a Grok pane whose task text happens to mention Codex reads as Codex, because Codex is checked first. Four real recorded titles are wrong today for exactly that reason.

This adds a parser that gathers all the evidence first and ranks it by how trustworthy the signal is instead of by list position. Nothing uses it yet.

What Changed

One new module plus its tests. No consumer imports it — grep for collectAgentTitleEvidence outside the module returns nothing.

collectAgentTitleEvidence(title) sorts every signal into three classes:

Class What it is Can task text fake it?
vendor marker a sigil or control sequence the agent emits (✳, the four Gemini glyphs, the OC | envelope) no
anchored name a name where a grammar reserves the position for identity — Orca's - <agent> owner suffix, or the whole undecorated remainder no
free-text name a name anywhere else yes, trivially

Anchored beats vendor marker, so ✳ agy resolves to Antigravity rather than Claude — the strongest possible name evidence wins instead of losing to a sigil by position. Free text beats neither, and never becomes identity on its own even when it is the only name present: an absent icon is recoverable, a confidently wrong one is not. Conflicts inside a class resolve to null.

Antigravity's Gemini <n.n> <Name> models are handled by reading identity-plus-model as a grammar, so the gemini token is metadata. That is the general rule rather than an exception living inside the Gemini detector.

Why not swap getAgentLabel directly

It has ~20 importers and they include write-authorizing paths, not just icons. Replacing it in place moves all of them in one commit with no way to attribute a regression. Each consumer migrates behind the resolver, separately, with its own evidence.

Testing

24 assertions covering the recorded misclassifications, the ✳ agy and Antigravity-model cases, hyphenated worktree names, spinner-only titles, and same-class conflicts — plus an order-independence property test: swapping the two agent names in a title must not change the answer, which is precisely what fails on the current chain.

Measured against the live recorded-title corpus (747 distinct titles): 598 agree, 149 differ.

144  claude -> null   spinner-only titles (◐ ◑ ⠂). Many agents emit braille and
                      quarter-circle frames, so they prove the pane is busy and
                      nothing about who it is. Answered by stronger signals once
                      the resolver lands — which is why this is not wired up yet.
  4  codex  -> grok   the misattributed Grok panes, fixed.
  1  codex  -> null   a spinner-prefixed Claude pane whose task text names Codex.

That 144 is the load-bearing number in this PR: it is why the parser ships dark. On its own it would blank real Claude rows; behind the resolver those panes are identified by hook, launch, and process evidence long before the title is consulted.

Reviewing the delta found three parser defects that reasoning had missed:

  1. the OpenCode OC | envelope was a vetoable marker instead of an owning grammar, so 4 real titles lost their identity to session text naming another agent;
  2. free text was allowed to veto a vendor marker, blinding 13 real ✳ <task text naming Codex> Claude titles;
  3. the owner suffix matched the tail of a hyphenated worktree name (review-14600-codex).

typecheck, oxlint, oxfmt clean. Existing title/status suites: 59 files, 665 tests, all passing.

Linked Issue

Part of the agent-identity rearchitecture tracked by #9236. No single issue is closed by this PR on its own — it is one stage of a staged change, and behavior only moves once consumers migrate.

Visual Proof

N/A — this module has zero importers and renders nothing, so there is no user-visible behavior to capture. Screenshotting it would imply coverage that does not exist. The manual-validation arm was deliberately skipped for that reason.

Testing

pnpm exec vitest run --config config/vitest.config.ts src/shared/agent-title-evidence.test.ts — 24 passed.

Measured against the live recorded-title corpus (747 distinct titles): 598 agree with the current chain, 149 differ, and every delta group is enumerated above. Reviewing that delta — rather than reasoning about the code — is what found three defects during implementation.

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

Agent skill upstream boundary

  • Not applicable.

Notes

  • Security: no new input is trusted. The change reduces what a terminal title is allowed to decide.
  • Cross-platform: pure TypeScript in src/shared, no platform branches, no paths, no shell.
  • Remote SSH / backwards compatibility: no wire change in this PR. Nothing new is published, and nothing existing is read differently, because no consumer calls it yet.
  • Mobile: unaffected — no importers.
  • Performance: pure function over a short string, called by nobody yet; no allocation on any hot path.

Checklist

  • This PR is small and focused
  • I explained what changed and why (including ELI5)
  • Before/after screenshots or videos attached for UI changes, or N/A with reason
  • Self-reviewed for correctness, security, and performance
  • Cross-platform, SSH/remote, and path/shortcut impact considered
  • typecheck, oxlint, oxfmt and focused tests pass locally; CI covers the rest

@coderabbitai

coderabbitai Bot commented Aug 23, 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

Adds collectAgentTitleEvidence with typed results for terminal-title identity detection. The collector recognizes vendor markers, anchored names, canonical labels, native formats, owner suffixes, Antigravity model titles, and synthetic labels. It handles empty titles and Claude management screens, separates free-text activity from identity evidence, detects conflicts, and applies precedence rules. Shared Claude identity-frame detection is centralized, and tests cover evidence reasons and existing getAgentLabel behavior.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: an order-independent parser for agent title evidence.
Description check ✅ Passed The description is detailed and covers the change, rationale, linked issue, testing, visual proof, compatibility, performance, and checklist requirements.
Docstring Coverage ✅ Passed Docstring check was indeterminate for this PR — some files could not be analyzed in time. Not blocking.
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.
✨ Finishing Touches
📝 Generate docstrings
  • 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 (1)
src/shared/agent-title-evidence.ts (1)

18-34: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Reduce long TypeScript comments.

Keep comments brief and limited to non-obvious constraints. Move detailed rationale and corpus context to documentation or focused test names.

  • src/shared/agent-title-evidence.ts#L18-L34: Reduce the multi-paragraph evidence-model explanation to a brief contract comment.
  • src/shared/agent-title-evidence.test.ts#L98-L103: Reduce the spinner explanation to one brief test-specific comment.
  • src/shared/agent-title-identity-characterization.test.ts#L4-L16: Move the corpus and historical behavior narrative out of the file header.

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


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 20fec4ae-2535-484b-ad59-edd30d172ce0

📥 Commits

Reviewing files that changed from the base of the PR and between f0c718e and a662578.

📒 Files selected for processing (3)
  • src/shared/agent-title-evidence.test.ts
  • src/shared/agent-title-evidence.ts
  • src/shared/agent-title-identity-characterization.test.ts

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

Comment thread src/shared/agent-title-evidence.ts Resolved
@brennanb2025
brennanb2025 force-pushed the brennanb2025/agent-title-evidence-parser branch from a662578 to 565d177 Compare August 24, 2026 01:32
@brennanb2025

Copy link
Copy Markdown
Contributor Author

Status update: rebased onto current main and pushed the focused parser/test changes to the PR head. Validation is green: full pnpm typecheck, full oxlint, focused Vitest (142/142), oxfmt, and diff checks; the final same-model review returned CLEAN. Grok Electron QA confirmed the intended worktree launches and renders baseline UI through CDP; parser behavior remains intentionally untestable in the UI because the new module is deliberately unwired, and QA evidence stayed local. The PR remains ordered behind #16144.

@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 (1)
src/shared/agent-title-evidence.ts (1)

25-40: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Shorten the introductory block comment.

Lines 25-40 restate the implementation and include examples. Keep a short description of the evidence-ranking contract.

Proposed change
-/**
- * Order-independent identity evidence from a terminal title.
- *
- * The chain this replaces is a first-match-wins scan of substring predicates, so its answer is
- * decided by list position rather than by how strong the evidence is. That is why a Grok pane
- * whose task text mentions Codex reads as Codex, and why fixing one collision by hoisting a
- * branch breaks another. Here every signal is collected first and ranked afterwards, by class:
- *
- *   vendor marker  — a control sequence or sigil the agent itself emits. Task text cannot forge it.
- *   anchored name  — a name in a position some grammar reserves for identity (Orca's `- <agent>`
- *                    owner suffix, or the whole undecorated remainder).
- *   free-text name — a name anywhere else. Anyone can type it.
- *
- * A free-text name never becomes identity on its own, even when it is the only name present: an
- * absent icon is recoverable, a confidently wrong one is not. Callers that only need "is this pane
- * busy" want activity parsing, which lives elsewhere and does not go through here.
- */
+/** Collects terminal-title identity evidence and resolves it by evidence strength. */

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


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 2f0f6ee4-e003-4a95-818e-9c6460558669

📥 Commits

Reviewing files that changed from the base of the PR and between a662578 and 565d17767986b26eced2bd08ed312ad07b6834bb.

📒 Files selected for processing (5)
  • src/shared/agent-title-core.ts
  • src/shared/agent-title-evidence.test.ts
  • src/shared/agent-title-evidence.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 thread src/shared/agent-title-evidence.ts Resolved
@brennanb2025
brennanb2025 force-pushed the brennanb2025/agent-title-evidence-parser branch from 05c3dc7 to e905d0d Compare August 24, 2026 01:42

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

🧹 Nitpick comments (1)
src/shared/agent-title-evidence.ts (1)

26-42: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Shorten the module documentation.

Keep a short contract summary here. Move extended design rationale to focused tests or project documentation.

As per coding guidelines, comments must be concise, non-obvious, and brief.

Source: Coding guidelines


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 408e3a23-411a-41cd-849b-c145f71f4638

📥 Commits

Reviewing files that changed from the base of the PR and between 05c3dc778647f96e6ff0105052eecae43564c503 and 06ce7a0c07563fe55d1d06be7ecf31e45aaa31a6.

📒 Files selected for processing (2)
  • src/shared/agent-title-evidence.test.ts
  • src/shared/agent-title-evidence.ts

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

@brennanb2025
brennanb2025 force-pushed the brennanb2025/agent-title-evidence-parser branch from 06ce7a0 to 50d57c1 Compare August 24, 2026 01:52
@brennanb2025

Copy link
Copy Markdown
Contributor Author

Follow-up status: addressed the final review findings by hardening wrapper traversal against leading-separator hangs, keeping Claude-sigil titles from overclaiming bare agent names, restricting owner suffixes to recognized agent IDs, and honoring opted-out synthetic profiles. Rebased again onto current origin/main (0 commits behind), reran full typecheck/oxlint plus 149 focused tests and formatting/diff checks, and the final same-model review returned CLEAN. Electron baseline QA remains local-only and confirms the intended worktree launches via CDP; parser behavior is intentionally not rendered because the module remains unwired.

@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 (1)
src/shared/agent-title-evidence.ts (1)

26-42: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Reduce multi-line explanatory comments. Keep only concise, non-obvious behavior notes.

  • src/shared/agent-title-evidence.ts#L26-L42: replace implementation history with a short API contract.
  • src/shared/agent-title-evidence.test.ts#L281-L286: replace the fixture walkthrough with one behavior statement.

As per coding guidelines, comments must be concise, non-obvious, and brief—prefer one line.

Source: Coding guidelines


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 52a0c0b8-d2aa-41b8-82d0-445cf0669f56

📥 Commits

Reviewing files that changed from the base of the PR and between 06ce7a0c07563fe55d1d06be7ecf31e45aaa31a6 and 587455b.

📒 Files selected for processing (2)
  • src/shared/agent-title-evidence.test.ts
  • src/shared/agent-title-evidence.ts

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

Comment on lines +88 to +92
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.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Correct the ✳ agy expectation.

AGY_AGENT_NAME_RE identifies agy as Antigravity. The collector strips the Claude sigil and records anchored Antigravity evidence. Anchored evidence has precedence over the Claude vendor marker. Lines 89-90 therefore fail.

Proposed fix
-      expect(agentFor('✳ agy')).toBe('claude')
-      expect(reasonFor('✳ agy')).toBe('vendor-marker')
+      expect(agentFor('✳ agy')).toBe('antigravity')
+      expect(reasonFor('✳ agy')).toBe('anchored')
📝 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
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')
it('lets an anchored name outrank a foreign vendor marker', () => {
expect(agentFor('✳ agy')).toBe('antigravity')
expect(reasonFor('✳ agy')).toBe('anchored')
expect(agentFor('✳ codex')).toBe('claude')
expect(reasonFor('✳ codex')).toBe('vendor-marker')

The chain this will replace is a first-match-wins scan of substring predicates, so its answer
is decided by list position rather than by how strong the evidence is. Four real recorded Grok
panes read as Codex today because Codex is checked first and their task text mentions it.
Reordering cannot fix that: whichever branch is hoisted, some other pair breaks.

collectAgentTitleEvidence collects every signal first and ranks by class afterwards:

  vendor marker  — a sigil or control sequence the agent itself emits; task text cannot forge it
  anchored name  — a name where a grammar reserves the position for identity (Orca's `- <agent>`
                   owner suffix, or the whole undecorated remainder)
  free-text name — a name anywhere else

Anchored beats vendor marker, so `✳ agy` is Antigravity rather than Claude. Free text never
beats either, and never becomes identity on its own even as the only name present — an absent
icon is recoverable, a confidently wrong one is not. Conflicts within a class resolve to null.

No consumer imports this yet. Swapping getAgentLabel in place would move ~20 call sites at
once; each migrates behind the resolver, with its own evidence.

Measured against the live recorded-title corpus (747 distinct titles), 598 agree with the
current chain and 149 differ:

  144  claude -> null   spinner-only titles. Braille and quarter-circle frames are emitted by
                        many agents, so they prove the pane is busy and nothing about who it
                        is. These are answered by stronger signals once the resolver lands,
                        which is why the parser is not wired up on its own.
    4  codex  -> grok   the misattributed Grok panes, fixed.
    1  codex  -> null   a spinner-prefixed Claude pane whose task text names Codex.

Three parser defects were found and fixed by reviewing that delta rather than reasoning about
it: the OpenCode envelope was treated as a vetoable marker instead of an owning grammar, free
text was allowed to veto a vendor marker (which blinded 13 real Claude titles), and the owner
suffix matched the tail of a hyphenated worktree name (`review-14600-codex`).
@brennanb2025

Copy link
Copy Markdown
Contributor Author

Manual QA captured against PR #16237 head (c7788d918d37a4f7bb86c6fd889592025eb9a4f3) because #16148 intentionally has no importer or UI surface. The host routing test on that caller passed 27/27: an @claude send excludes a Codex pane whose task title mentions Claude, while real Claude identity panes remain eligible; the complementary @codex/Grok case also passed.

Rendered Orca evidence from the #16237 build (attached here; it shows the real app surface and confirms the intended worktree instance):

Agent dashboard

Terminal surface

The parser itself is not claimed to be exercised by these screenshots; they document the real caller build and the host-level routing evidence.

@brennanb2025
brennanb2025 force-pushed the brennanb2025/agent-title-evidence-parser branch from e5bf637 to 212d935 Compare August 24, 2026 18:36
@brennanb2025

Copy link
Copy Markdown
Contributor Author

Codex review loop - status

Verdict: CLEAN / PASS WITH NO PROVEN IN-SCOPE ISSUES. This status covers head 212d9356d4cc5a9715a28f0e3feefab9626fea35, rebased onto current origin/main and force-pushed before this comment.

  • Independent same-model adversarial review: CLEAN; no fixes required and no scope expansion.
  • Readiness checklist: PASS; no proven P0/P1/P2 findings.
  • Re-verified focused title/agent suites: 248 tests passed across 9 files.
  • Full typecheck, oxlint on touched files, oxfmt, and diff checks passed.
  • Fresh local corpus scan: 2,885 checkpoints, 1,149 populated, 789 distinct titles; 629 unchanged. Deltas were 153 intentional spinner-only claude -> null, 5 owner-suffix codex -> grok corrections, plus strict bare-name nulls (opencode -> null, grok -> null) consistent with the parser contract. Titles stayed local and were not included here.
  • Grok/$electron arm ran against fix(orchestration): route @agent messages by resolved identity, not terminal title #16237 head c7788d918d37a4f7bb86c6fd889592025eb9a4f3: host routing test passed 27/27, proving @claude does not reach a Codex pane whose task title mentions Claude and still reaches real Claude identity panes; complementary Grok/Codex routing passed. Full-app screenshots from that build were attached in the preceding QA comment.

Residual risk is intentional: #16148 adds an unwired parser, so its parser output is not itself rendered on this branch; the real caller behavior is validated on #16237 as documented above.

@brennanb2025
brennanb2025 merged commit 5261206 into main Aug 24, 2026
47 checks passed
brennanb2025 added a commit that referenced this pull request Aug 24, 2026
…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.
paidaxingyo666 pushed a commit to paidaxingyo666/Manta that referenced this pull request Aug 25, 2026
…tablyai#16148)

* feat(agent-status): add an order-independent title evidence parser

The chain this will replace is a first-match-wins scan of substring predicates, so its answer
is decided by list position rather than by how strong the evidence is. Four real recorded Grok
panes read as Codex today because Codex is checked first and their task text mentions it.
Reordering cannot fix that: whichever branch is hoisted, some other pair breaks.

collectAgentTitleEvidence collects every signal first and ranks by class afterwards:

  vendor marker  — a sigil or control sequence the agent itself emits; task text cannot forge it
  anchored name  — a name where a grammar reserves the position for identity (Orca's `- <agent>`
                   owner suffix, or the whole undecorated remainder)
  free-text name — a name anywhere else

Anchored beats vendor marker, so `✳ agy` is Antigravity rather than Claude. Free text never
beats either, and never becomes identity on its own even as the only name present — an absent
icon is recoverable, a confidently wrong one is not. Conflicts within a class resolve to null.

No consumer imports this yet. Swapping getAgentLabel in place would move ~20 call sites at
once; each migrates behind the resolver, with its own evidence.

Measured against the live recorded-title corpus (747 distinct titles), 598 agree with the
current chain and 149 differ:

  144  claude -> null   spinner-only titles. Braille and quarter-circle frames are emitted by
                        many agents, so they prove the pane is busy and nothing about who it
                        is. These are answered by stronger signals once the resolver lands,
                        which is why the parser is not wired up on its own.
    4  codex  -> grok   the misattributed Grok panes, fixed.
    1  codex  -> null   a spinner-prefixed Claude pane whose task text names Codex.

Three parser defects were found and fixed by reviewing that delta rather than reasoning about
it: the OpenCode envelope was treated as a vetoable marker instead of an owning grammar, free
text was allowed to veto a vendor marker (which blinded 13 real Claude titles), and the owner
suffix matched the tail of a hyphenated worktree name (`review-14600-codex`).

* fix(agent-status): harden title evidence collection

* fix(agent-status): limit emitted display evidence

* fix(agent-status): anchor explicit title evidence

* test(agent-status): cover anchored token filtering

* fix(agent-status): complete explicit marker evidence

* fix(agent-status): recognize reserved owner ids

* fix(agent-status): reject cwd path titles

* fix(agent-status): avoid bare-name identity guesses

* fix(agent-status): require spinner for working labels

* fix(agent-status): honor opted-out synthetic profiles

* fix(agent-status): harden wrapper evidence boundaries

(cherry picked from commit 5261206)
brennanb2025 added a commit that referenced this pull request Aug 26, 2026
* feat(agent-status): measure identity evidence before migrating any consumer

PR 1 of the identity migration. It changes no displayed or routed identity — it only measures.

Why measure first: the hierarchy shipped in #16148/#16157 has zero consumers, while ~31 sites still
derive identity independently. Every migration decision after this is currently a guess, including
the one that matters most — how often a real pane has no evidence at all. A live P0 reports "No
Claude status shown", and this design trades toward showing nothing when uncertain, so the blank
rate has to be a number before any surface moves.

- `pane-agent-identity-evidence.ts` — one assembler that gathers a pane's evidence, so consumers
  stop each inventing their own ladder.
- `pane-agent-identity-census.ts` — shadow-only counters keyed by host kind (native / wsl-host /
  wsl-distro / ssh / relay) and launch mode (typed / orca-launch / resume). Records a bitmask of
  which sources were present and whether the resolver returned null or ambiguous. No titles,
  prompts, paths, handles, or agent text.
- `pane-agent-identity-inventory.test.ts` — a ratchet that fails when a legacy identity helper
  gains a new production caller, so the surface cannot grow while the migration runs.

Three review findings are encoded rather than deferred: launch stays above run-key-less completed
hooks (promoting the hook lets a stale record hijack a pane); OMP/Pi evidence is owner-normalized
before assembly, since OMP emits Pi-compatible frames and a wrapper's hook would otherwise be read
as the agent it wraps; and Windows-side `wsl.exe` is rejected as process evidence, because the host
observes the distro wrapper rather than the agent inside it.

The census cannot be completed from a worktree. It needs representative native, SSH, WSL and relay
cohorts collected from real use, and that review is the gate on PR 3 — not this PR.

* test(agent-status): keep identity migration inventory-only

* test(agent-status): reuse reliable source scanner

* test(agent-status): bound inventory scan work

* test(agent-status): avoid inventory path false negatives

* test(agent-status): refresh identity inventory after base repair

* test(agent-status): correct inventory classifications

* test(agent-status): correct action boundary inventory

* test(agent-status): pin inventory occurrence counts

* test(agent-status): fail closed on scanner desync
dallascrilley pushed a commit to dallascrilley/orca that referenced this pull request Aug 27, 2026
…tablyai#16148)

* feat(agent-status): add an order-independent title evidence parser

The chain this will replace is a first-match-wins scan of substring predicates, so its answer
is decided by list position rather than by how strong the evidence is. Four real recorded Grok
panes read as Codex today because Codex is checked first and their task text mentions it.
Reordering cannot fix that: whichever branch is hoisted, some other pair breaks.

collectAgentTitleEvidence collects every signal first and ranks by class afterwards:

  vendor marker  — a sigil or control sequence the agent itself emits; task text cannot forge it
  anchored name  — a name where a grammar reserves the position for identity (Orca's `- <agent>`
                   owner suffix, or the whole undecorated remainder)
  free-text name — a name anywhere else

Anchored beats vendor marker, so `✳ agy` is Antigravity rather than Claude. Free text never
beats either, and never becomes identity on its own even as the only name present — an absent
icon is recoverable, a confidently wrong one is not. Conflicts within a class resolve to null.

No consumer imports this yet. Swapping getAgentLabel in place would move ~20 call sites at
once; each migrates behind the resolver, with its own evidence.

Measured against the live recorded-title corpus (747 distinct titles), 598 agree with the
current chain and 149 differ:

  144  claude -> null   spinner-only titles. Braille and quarter-circle frames are emitted by
                        many agents, so they prove the pane is busy and nothing about who it
                        is. These are answered by stronger signals once the resolver lands,
                        which is why the parser is not wired up on its own.
    4  codex  -> grok   the misattributed Grok panes, fixed.
    1  codex  -> null   a spinner-prefixed Claude pane whose task text names Codex.

Three parser defects were found and fixed by reviewing that delta rather than reasoning about
it: the OpenCode envelope was treated as a vetoable marker instead of an owning grammar, free
text was allowed to veto a vendor marker (which blinded 13 real Claude titles), and the owner
suffix matched the tail of a hyphenated worktree name (`review-14600-codex`).

* fix(agent-status): harden title evidence collection

* fix(agent-status): limit emitted display evidence

* fix(agent-status): anchor explicit title evidence

* test(agent-status): cover anchored token filtering

* fix(agent-status): complete explicit marker evidence

* fix(agent-status): recognize reserved owner ids

* fix(agent-status): reject cwd path titles

* fix(agent-status): avoid bare-name identity guesses

* fix(agent-status): require spinner for working labels

* fix(agent-status): honor opted-out synthetic profiles

* fix(agent-status): harden wrapper evidence boundaries
dallascrilley pushed a commit to dallascrilley/orca that referenced this pull request Aug 27, 2026
…#16575)

* feat(agent-status): measure identity evidence before migrating any consumer

PR 1 of the identity migration. It changes no displayed or routed identity — it only measures.

Why measure first: the hierarchy shipped in stablyai#16148/stablyai#16157 has zero consumers, while ~31 sites still
derive identity independently. Every migration decision after this is currently a guess, including
the one that matters most — how often a real pane has no evidence at all. A live P0 reports "No
Claude status shown", and this design trades toward showing nothing when uncertain, so the blank
rate has to be a number before any surface moves.

- `pane-agent-identity-evidence.ts` — one assembler that gathers a pane's evidence, so consumers
  stop each inventing their own ladder.
- `pane-agent-identity-census.ts` — shadow-only counters keyed by host kind (native / wsl-host /
  wsl-distro / ssh / relay) and launch mode (typed / orca-launch / resume). Records a bitmask of
  which sources were present and whether the resolver returned null or ambiguous. No titles,
  prompts, paths, handles, or agent text.
- `pane-agent-identity-inventory.test.ts` — a ratchet that fails when a legacy identity helper
  gains a new production caller, so the surface cannot grow while the migration runs.

Three review findings are encoded rather than deferred: launch stays above run-key-less completed
hooks (promoting the hook lets a stale record hijack a pane); OMP/Pi evidence is owner-normalized
before assembly, since OMP emits Pi-compatible frames and a wrapper's hook would otherwise be read
as the agent it wraps; and Windows-side `wsl.exe` is rejected as process evidence, because the host
observes the distro wrapper rather than the agent inside it.

The census cannot be completed from a worktree. It needs representative native, SSH, WSL and relay
cohorts collected from real use, and that review is the gate on PR 3 — not this PR.

* test(agent-status): keep identity migration inventory-only

* test(agent-status): reuse reliable source scanner

* test(agent-status): bound inventory scan work

* test(agent-status): avoid inventory path false negatives

* test(agent-status): refresh identity inventory after base repair

* test(agent-status): correct inventory classifications

* test(agent-status): correct action boundary inventory

* test(agent-status): pin inventory occurrence counts

* test(agent-status): fail closed on scanner desync
rahuljangirworks pushed a commit to rahuljangirworks/veer that referenced this pull request Aug 27, 2026
…#16575)

* feat(agent-status): measure identity evidence before migrating any consumer

PR 1 of the identity migration. It changes no displayed or routed identity — it only measures.

Why measure first: the hierarchy shipped in stablyai#16148/stablyai#16157 has zero consumers, while ~31 sites still
derive identity independently. Every migration decision after this is currently a guess, including
the one that matters most — how often a real pane has no evidence at all. A live P0 reports "No
Claude status shown", and this design trades toward showing nothing when uncertain, so the blank
rate has to be a number before any surface moves.

- `pane-agent-identity-evidence.ts` — one assembler that gathers a pane's evidence, so consumers
  stop each inventing their own ladder.
- `pane-agent-identity-census.ts` — shadow-only counters keyed by host kind (native / wsl-host /
  wsl-distro / ssh / relay) and launch mode (typed / orca-launch / resume). Records a bitmask of
  which sources were present and whether the resolver returned null or ambiguous. No titles,
  prompts, paths, handles, or agent text.
- `pane-agent-identity-inventory.test.ts` — a ratchet that fails when a legacy identity helper
  gains a new production caller, so the surface cannot grow while the migration runs.

Three review findings are encoded rather than deferred: launch stays above run-key-less completed
hooks (promoting the hook lets a stale record hijack a pane); OMP/Pi evidence is owner-normalized
before assembly, since OMP emits Pi-compatible frames and a wrapper's hook would otherwise be read
as the agent it wraps; and Windows-side `wsl.exe` is rejected as process evidence, because the host
observes the distro wrapper rather than the agent inside it.

The census cannot be completed from a worktree. It needs representative native, SSH, WSL and relay
cohorts collected from real use, and that review is the gate on PR 3 — not this PR.

* test(agent-status): keep identity migration inventory-only

* test(agent-status): reuse reliable source scanner

* test(agent-status): bound inventory scan work

* test(agent-status): avoid inventory path false negatives

* test(agent-status): refresh identity inventory after base repair

* test(agent-status): correct inventory classifications

* test(agent-status): correct action boundary inventory

* test(agent-status): pin inventory occurrence counts

* test(agent-status): fail closed on scanner desync
brennanb2025 added a commit that referenced this pull request Aug 27, 2026
…erminal title (#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 #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.

* 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
paidaxingyo666 pushed a commit to paidaxingyo666/Manta that referenced this pull request Aug 28, 2026
…#16575)

* feat(agent-status): measure identity evidence before migrating any consumer

PR 1 of the identity migration. It changes no displayed or routed identity — it only measures.

Why measure first: the hierarchy shipped in stablyai#16148/stablyai#16157 has zero consumers, while ~31 sites still
derive identity independently. Every migration decision after this is currently a guess, including
the one that matters most — how often a real pane has no evidence at all. A live P0 reports "No
Claude status shown", and this design trades toward showing nothing when uncertain, so the blank
rate has to be a number before any surface moves.

- `pane-agent-identity-evidence.ts` — one assembler that gathers a pane's evidence, so consumers
  stop each inventing their own ladder.
- `pane-agent-identity-census.ts` — shadow-only counters keyed by host kind (native / wsl-host /
  wsl-distro / ssh / relay) and launch mode (typed / orca-launch / resume). Records a bitmask of
  which sources were present and whether the resolver returned null or ambiguous. No titles,
  prompts, paths, handles, or agent text.
- `pane-agent-identity-inventory.test.ts` — a ratchet that fails when a legacy identity helper
  gains a new production caller, so the surface cannot grow while the migration runs.

Three review findings are encoded rather than deferred: launch stays above run-key-less completed
hooks (promoting the hook lets a stale record hijack a pane); OMP/Pi evidence is owner-normalized
before assembly, since OMP emits Pi-compatible frames and a wrapper's hook would otherwise be read
as the agent it wraps; and Windows-side `wsl.exe` is rejected as process evidence, because the host
observes the distro wrapper rather than the agent inside it.

The census cannot be completed from a worktree. It needs representative native, SSH, WSL and relay
cohorts collected from real use, and that review is the gate on PR 3 — not this PR.

* test(agent-status): keep identity migration inventory-only

* test(agent-status): reuse reliable source scanner

* test(agent-status): bound inventory scan work

* test(agent-status): avoid inventory path false negatives

* test(agent-status): refresh identity inventory after base repair

* test(agent-status): correct inventory classifications

* test(agent-status): correct action boundary inventory

* test(agent-status): pin inventory occurrence counts

* test(agent-status): fail closed on scanner desync

(cherry picked from commit a624e7c)
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
…tablyai#16148)

* feat(agent-status): add an order-independent title evidence parser

The chain this will replace is a first-match-wins scan of substring predicates, so its answer
is decided by list position rather than by how strong the evidence is. Four real recorded Grok
panes read as Codex today because Codex is checked first and their task text mentions it.
Reordering cannot fix that: whichever branch is hoisted, some other pair breaks.

collectAgentTitleEvidence collects every signal first and ranks by class afterwards:

  vendor marker  — a sigil or control sequence the agent itself emits; task text cannot forge it
  anchored name  — a name where a grammar reserves the position for identity (Orca's `- <agent>`
                   owner suffix, or the whole undecorated remainder)
  free-text name — a name anywhere else

Anchored beats vendor marker, so `✳ agy` is Antigravity rather than Claude. Free text never
beats either, and never becomes identity on its own even as the only name present — an absent
icon is recoverable, a confidently wrong one is not. Conflicts within a class resolve to null.

No consumer imports this yet. Swapping getAgentLabel in place would move ~20 call sites at
once; each migrates behind the resolver, with its own evidence.

Measured against the live recorded-title corpus (747 distinct titles), 598 agree with the
current chain and 149 differ:

  144  claude -> null   spinner-only titles. Braille and quarter-circle frames are emitted by
                        many agents, so they prove the pane is busy and nothing about who it
                        is. These are answered by stronger signals once the resolver lands,
                        which is why the parser is not wired up on its own.
    4  codex  -> grok   the misattributed Grok panes, fixed.
    1  codex  -> null   a spinner-prefixed Claude pane whose task text names Codex.

Three parser defects were found and fixed by reviewing that delta rather than reasoning about
it: the OpenCode envelope was treated as a vetoable marker instead of an owning grammar, free
text was allowed to veto a vendor marker (which blinded 13 real Claude titles), and the owner
suffix matched the tail of a hyphenated worktree name (`review-14600-codex`).

* fix(agent-status): harden title evidence collection

* fix(agent-status): limit emitted display evidence

* fix(agent-status): anchor explicit title evidence

* test(agent-status): cover anchored token filtering

* fix(agent-status): complete explicit marker evidence

* fix(agent-status): recognize reserved owner ids

* fix(agent-status): reject cwd path titles

* fix(agent-status): avoid bare-name identity guesses

* fix(agent-status): require spinner for working labels

* fix(agent-status): honor opted-out synthetic profiles

* fix(agent-status): harden wrapper evidence boundaries
enrick-dev pushed a commit to enrick-dev/orca that referenced this pull request Oct 15, 2026
…#16575)

* feat(agent-status): measure identity evidence before migrating any consumer

PR 1 of the identity migration. It changes no displayed or routed identity — it only measures.

Why measure first: the hierarchy shipped in stablyai#16148/stablyai#16157 has zero consumers, while ~31 sites still
derive identity independently. Every migration decision after this is currently a guess, including
the one that matters most — how often a real pane has no evidence at all. A live P0 reports "No
Claude status shown", and this design trades toward showing nothing when uncertain, so the blank
rate has to be a number before any surface moves.

- `pane-agent-identity-evidence.ts` — one assembler that gathers a pane's evidence, so consumers
  stop each inventing their own ladder.
- `pane-agent-identity-census.ts` — shadow-only counters keyed by host kind (native / wsl-host /
  wsl-distro / ssh / relay) and launch mode (typed / orca-launch / resume). Records a bitmask of
  which sources were present and whether the resolver returned null or ambiguous. No titles,
  prompts, paths, handles, or agent text.
- `pane-agent-identity-inventory.test.ts` — a ratchet that fails when a legacy identity helper
  gains a new production caller, so the surface cannot grow while the migration runs.

Three review findings are encoded rather than deferred: launch stays above run-key-less completed
hooks (promoting the hook lets a stale record hijack a pane); OMP/Pi evidence is owner-normalized
before assembly, since OMP emits Pi-compatible frames and a wrapper's hook would otherwise be read
as the agent it wraps; and Windows-side `wsl.exe` is rejected as process evidence, because the host
observes the distro wrapper rather than the agent inside it.

The census cannot be completed from a worktree. It needs representative native, SSH, WSL and relay
cohorts collected from real use, and that review is the gate on PR 3 — not this PR.

* test(agent-status): keep identity migration inventory-only

* test(agent-status): reuse reliable source scanner

* test(agent-status): bound inventory scan work

* test(agent-status): avoid inventory path false negatives

* test(agent-status): refresh identity inventory after base repair

* test(agent-status): correct inventory classifications

* test(agent-status): correct action boundary inventory

* test(agent-status): pin inventory occurrence counts

* test(agent-status): fail closed on scanner desync
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