Skip to content

Human takeover primitive for channel sessions (request / take / return with TTL) #5027

Open

Description

@pcfds

Problem

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.

Activity

  1. roboeve commented on Oct 19, 2026

    @roboeve
    Contributor

    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:

    1. Does the human exchange enter session history while the agent is silent, and in what role?
    2. Where does the TTL live: a durable session timer, or a check on the next inbound message?
    3. What happens to a running turn, queued messages, and pending prompts (questions, approvals) on take?
    4. 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions