You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Human takeover primitive for channel sessions (request / take / return with TTL) #5027
Agents deployed on Slack, WhatsApp, Twilio or Telegram for support and operations need a person to step into the conversation. Today there is no way to say "a human owns this thread now": the agent keeps replying, or the integrator hand-rolls it with message.received + ctx.cancel(), with no shared state, no expiry and no audit.
What we run in production
Exception agent for a field-service / logistics platform, talking to truck drivers over WhatsApp:
The session has a handoff state: none, requested, accepted.
The agent can request a person and keep the conversation going (requested). Requesting a person is not the same as cutting the agent: only safety, limits or model unavailability cut it.
When an operator takes the thread (accepted) the agent stops replying. Operator messages go out through the same channel thread, so the driver stays in one chat.
The human turn has a TTL (30 minutes without operator messages); then the thread returns to the agent automatically. An accepted state with an expired turn counts as none.
Requests are edge-triggered: the agent asks for a person only when the fact that requires one appears, not on every turn. This avoids loops after the operator hands the thread back.
Closing the case closes any pending request.
Every transition (requested, cut, taken, returned, resolved) is audited with who did it, and shown in the case timeline.
Proposal
A session-level handoff API: request(reason), take(principal, { ttlMs }), return(), with the state readable from hooks and tools.
A hook so the host can notify its people when a person is requested (in-app, email, a Slack ping).
Channel adapters route operator messages into the same thread while the agent is silent.
Handoff events in the session stream and in Agent Runs.
Searched docs, changelog, issues/PRs and the registry for "takeover", "handoff", "handover", "live agent", "escalate to human" and found nothing equivalent. Related building blocks: message.received + ctx.cancel() (0.79.2), #671.
Happy to share the state machine and the edge cases our tests cover.
Triaged as a feature request (enhancement, p2, channels). No existing issue or primitive covers this: origin/main has no session-level human ownership state, and the closest issues (#2877 turn steering, #2885 Slack agent sessions, #671 session selection) solve different problems.
What works today without a primitive, using Slack as the example:
Keep the ownership flag and TTL in your own store and return null from the message hook while a person owns the thread. Messages filtered before send(...) never reach the agent (docs).
On take, call ctx.cancel() from the hook to stop a running turn (docs).
threadContext: { since: "last-agent-reply" } injects the human exchange into the agent's next turn once the thread returns (docs). Other channels have no equivalent, so there the agent comes back without that context.
A public API here needs a research/ plan first (AGENTS.md). Questions it would need to settle:
Does the human exchange enter session history while the agent is silent, and in what role?
Where does the TTL live: a durable session timer, or a check on the next inbound message?
What happens to a running turn, queued messages, and pending prompts (questions, approvals) on take?
Who may call take and return: an authenticated principal from the channel, or any host code?
The state machine and edge cases from your tests would help answer 1 to 3.
Activity
roboeve commentedon Oct 19, 2026
Triaged as a feature request (
enhancement,p2,channels). No existing issue or primitive covers this:origin/mainhas no session-level human ownership state, and the closest issues (#2877 turn steering, #2885 Slack agent sessions, #671 session selection) solve different problems.What works today without a primitive, using Slack as the example:
nullfrom the message hook while a person owns the thread. Messages filtered beforesend(...)never reach the agent (docs).ctx.cancel()from the hook to stop a running turn (docs).threadContext: { since: "last-agent-reply" }injects the human exchange into the agent's next turn once the thread returns (docs). Other channels have no equivalent, so there the agent comes back without that context.A public API here needs a
research/plan first (AGENTS.md). Questions it would need to settle:take?takeandreturn: an authenticated principal from the channel, or any host code?The state machine and edge cases from your tests would help answer 1 to 3.