Skip to content

[design] Agent-mediated transfer — "transfer me to that agent", seamlessly, in hands-free #279

Description

@serge-ivo

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.tsxHeaderProvider, 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

  1. 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.)
  2. Should a transfer be reversible — "go back" returning to the Lead with its own handoff note?
  3. Should the Lead be able to transfer to an agent that is busy, or only to an idle one?

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions