Design ticket — for assessment before any implementation.
The ask: talking to the Coder Lead, it mentions something about a subordinate; I say "transfer me
to that agent" and I am seamlessly talking to it, still hands-free. Like a call transfer.
Distinct from #277 ("next" = I pick the next agent that wants me). Same destination, different
trigger: #277 is user-initiated selection, this is agent-mediated handoff. They should share one
switch primitive and differ only in who invokes it.
Is it possible? Yes. Nothing exotic is required.
What already exists:
| Piece |
Where |
| An agent knows its subordinates, with ids |
list_subordinates / subordinate_status |
| A scoping rule for who an agent may address |
supervision graph — "You may only delegate to agents that appear here" |
| An app-level slot above the routes |
lib/HeaderContext.tsx — HeaderProvider, useHeaderSlot |
| Durable per-instance transcript |
AgentDO messages |
| Voice stack |
useVoice (SDK) |
Three parts are missing. One of them is the real work.
Part 1 — the directive channel
An agent must be able to say "take the user to instance X". Two options:
(a) Make the transfer a message. A typed system message in the thread (transfer, with to +
reason). The console already refetches the transcript on its tiered poll, so it arrives with no
new transport.
(b) Use the WebSocket. AgentDO.broadcast() already emits {type:"message"|"status"} and
/v1/agents/:id/ws exists — but the console does not use it at all (it is poll-only via
useTieredPolling, and #83 deliberately moved toward tiered polling).
Recommend (a). A transfer is a conversational event: as a message it is durable, appears in the
transcript, is auditable after the fact, and survives reload. A WS directive that leaves no trace
would be invisible the moment you scroll back. Cost is one poll interval of latency, which is
acceptable for a handoff the user just asked for.
Part 2 — conversation state above the page (the prerequisite)
InstanceDetail currently owns the chat AND the voice session. A transfer unmounts it, which closes
the mic and cuts TTS — the opposite of seamless.
The conversation's identity and the voice session itself must live above the router:
useVoice instantiated once at app level, with a provider holding
{ instanceId, name, voiceMode, runActive }. HeaderContext already proves this shape works.
This is the actual engineering, and it is shared with #277 and #278. Both are blocked on it.
Mic/TTS lifecycle across a route change is the risk in this whole feature — not the transfer logic.
Part 3 — the handoff note (what makes it a transfer, not a navigation)
A transfer without context is just hanging up. The Lead should pass a short note that lands in the
target's thread before the user's first word:
Transferred from Coder Lead — about issue #36, the SSE ordering bug in fws/platform.
The target then opens knowing why. Without it the user re-explains everything and the feature is
worth nothing. This is a system message in the target instance — a primitive that already exists.
Constraints to design in, not bolt on
Scope. Only instances in the current agent's supervision graph, and only instances the user
owns. A Repo Coder reads untrusted repo files and issue bodies; if injected text can steer the user
somewhere, that is a navigation hijack. This is a new capability class — an agent acting on the
client rather than on the world — and deserves the same scoping discipline as connector
write-consent.
User-requested ≠ agent-initiated.
- "Transfer me to the FWS coder" → the user asked. Act.
- Lead spontaneously deciding to transfer → offer, do not act. "Want me to hand you to FWS
platform?" In hands-free, a spontaneous transfer means your next sentence goes to an agent you
did not choose.
Always announce out loud. Same requirement as #277. Hands-free means not looking; the switch
must be spoken, and the #278 indicator must update.
Never mid-turn. Do not transfer while the user is mid-utterance or TTS is mid-sentence — finish
or explicitly abandon. That is the #175 loss class.
Dependency order
#278 hoist conversation + voice above the page ← foundation, blocks both below
├── #277 "next" — user picks the next agent that wants them
└── this transfer — the agent hands the user over, with a note
Worth deciding as one design: #277 and this are two triggers for the same "switch the active
conversation to instance X" primitive. Building them separately would produce two switch paths that
disagree about voice state.
Open questions for assessment
- Does the mic stay open across the switch, or close and reopen with a spoken cue? (Reopening is
simpler and probably honest; "seamless" may not require literal continuity.)
- Should a transfer be reversible — "go back" returning to the Lead with its own handoff note?
- Should the Lead be able to transfer to an agent that is busy, or only to an idle one?
Design ticket — for assessment before any implementation.
The ask: talking to the Coder Lead, it mentions something about a subordinate; I say "transfer me
to that agent" and I am seamlessly talking to it, still hands-free. Like a call transfer.
Distinct from #277 ("next" = I pick the next agent that wants me). Same destination, different
trigger: #277 is user-initiated selection, this is agent-mediated handoff. They should share one
switch primitive and differ only in who invokes it.
Is it possible? Yes. Nothing exotic is required.
What already exists:
list_subordinates/subordinate_statuslib/HeaderContext.tsx—HeaderProvider,useHeaderSlotAgentDOmessagesuseVoice(SDK)Three parts are missing. One of them is the real work.
Part 1 — the directive channel
An agent must be able to say "take the user to instance X". Two options:
(a) Make the transfer a message. A typed system message in the thread (
transfer, withto+reason). The console already refetches the transcript on its tiered poll, so it arrives with nonew transport.
(b) Use the WebSocket.
AgentDO.broadcast()already emits{type:"message"|"status"}and/v1/agents/:id/wsexists — but the console does not use it at all (it is poll-only viauseTieredPolling, and #83 deliberately moved toward tiered polling).Recommend (a). A transfer is a conversational event: as a message it is durable, appears in the
transcript, is auditable after the fact, and survives reload. A WS directive that leaves no trace
would be invisible the moment you scroll back. Cost is one poll interval of latency, which is
acceptable for a handoff the user just asked for.
Part 2 — conversation state above the page (the prerequisite)
InstanceDetailcurrently owns the chat AND the voice session. A transfer unmounts it, which closesthe mic and cuts TTS — the opposite of seamless.
The conversation's identity and the voice session itself must live above the router:
useVoiceinstantiated once at app level, with a provider holding{ instanceId, name, voiceMode, runActive }.HeaderContextalready proves this shape works.This is the actual engineering, and it is shared with #277 and #278. Both are blocked on it.
Mic/TTS lifecycle across a route change is the risk in this whole feature — not the transfer logic.
Part 3 — the handoff note (what makes it a transfer, not a navigation)
A transfer without context is just hanging up. The Lead should pass a short note that lands in the
target's thread before the user's first word:
The target then opens knowing why. Without it the user re-explains everything and the feature is
worth nothing. This is a system message in the target instance — a primitive that already exists.
Constraints to design in, not bolt on
Scope. Only instances in the current agent's supervision graph, and only instances the user
owns. A Repo Coder reads untrusted repo files and issue bodies; if injected text can steer the user
somewhere, that is a navigation hijack. This is a new capability class — an agent acting on the
client rather than on the world — and deserves the same scoping discipline as connector
write-consent.
User-requested ≠ agent-initiated.
platform?" In hands-free, a spontaneous transfer means your next sentence goes to an agent you
did not choose.
Always announce out loud. Same requirement as #277. Hands-free means not looking; the switch
must be spoken, and the #278 indicator must update.
Never mid-turn. Do not transfer while the user is mid-utterance or TTS is mid-sentence — finish
or explicitly abandon. That is the #175 loss class.
Dependency order
Worth deciding as one design: #277 and this are two triggers for the same "switch the active
conversation to instance X" primitive. Building them separately would produce two switch paths that
disagree about voice state.
Open questions for assessment
simpler and probably honest; "seamless" may not require literal continuity.)