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.
This commit is contained in:
Brennan Benson
2026-08-24 14:59:06 -07:00
parent 174f381d8a
commit 6f8344cdff
2 changed files with 20 additions and 8 deletions
@@ -30,10 +30,17 @@ describe('resolvePublishedPaneAgentIdentity', () => {
expect(resolve({ launchAgent: 'claude', foregroundAgent: 'codex' })).toBe('codex')
})
it('falls back to the title when the host has no process or launch evidence', () => {
// Hook-less agents over SSH surface only a decorated title; dropping it would make those
// panes unaddressable. It is last, not absent.
expect(resolve({ title: '✳ Claude Code' })).toBe('claude')
it('publishes nothing from a title alone, even an unambiguous one', () => {
// Deliberate, and the sharpest trade in this change. What this publishes AUTHORIZES ACTIONS:
// orchestration routing uses it to pick which real agent pane receives a message. A title is
// a decoration channel, and a stale one outlives the agent that wrote it, so a pane whose only
// evidence is a parsed string publishes no identity and every action consumer fails closed.
//
// The cost: a hook-less agent over SSH that Orca did not launch, and whose foreground process
// the host cannot read, is not addressable by @agent. That is a real capability loss, accepted
// because a message delivered into the wrong agent's prompt is not recoverable and an
// undelivered one is — the sender sees zero recipients.
expect(resolve({ title: '✳ Claude Code' })).toBeUndefined()
})
it('publishes nothing when the title names no agent unambiguously', () => {
+9 -4
View File
@@ -8,10 +8,10 @@ import type { TuiAgent } from './tui-agent'
* Kept out of the runtime class so it can be tested without one, and so routing, delivery and the
* UI all read the same decision instead of each re-deriving it.
*
* Title is included but ranks last, and contributes only when the evidence parser finds an
* unambiguous name. A task title that merely mentions an agent yields no title evidence at all,
* which is the whole point: "Switch Claude and Codex off the load balancer… - grok" is a Grok
* pane, and used to receive both `@claude` and `@codex`.
* Title is NOT consulted. What this publishes authorizes actions, and a terminal title is a
* decoration channel a user can type any agent's name into — "Switch Claude and Codex off the
* load balancer… - grok" is a Grok pane that used to receive both `@claude` and `@codex`.
* Identity therefore comes only from launch and foreground-process evidence the host owns.
*
* Returns undefined when nothing is known, and absence is published as absence. A caller that
* authorizes an action must fail closed on it rather than falling back to parsing the title.
@@ -24,6 +24,11 @@ export function resolvePublishedPaneAgentIdentity(args: {
const titleAgent = args.title ? collectAgentTitleEvidence(args.title).agent : null
return (
resolvePaneAgentIdentity({
// Why the floor: what this publishes authorizes actions — orchestration routing decides
// which real agent pane receives a message. Ranking title last makes a bad delivery
// unlikely; refusing to see it makes one impossible. A pane whose only evidence is a
// parsed string publishes no identity, and every action consumer fails closed on absence.
minimumSource: 'launch',
evidence: [
...(args.foregroundAgent
? [{ source: 'process' as const, agent: args.foregroundAgent }]