Skip to content

Support resumable sub-agent conversations (stateful custom agents) #2111

Description

@vishal061994-hue

Custom sub-agents (CustomAgentConfig) are stateless per invocation. Each delegation starts a
fresh context, and there is no supported way to persist a sub-agent's conversation or resume a
previous sub-agent run. I'd like to request first-class support for **stateful (resumable)
sub-agents. Notably, the SDK already models a sub-agent task identity internally — it just isn't addressable
by callers (see "Current behavior" below). The ask is largely to expose what already exists.

We're building a multi-agent platform where a routing agent delegates to a catalog of
specialist sub-agents that are materialized dynamically per request. In practice, users
converse with a specialist across several turns:

Turn 1: "Show me my accounts."
→ routed to the Accounts specialist, which fetches and reasons over the result set.

Turn 2: "Now get the health scores for those."
→ routed to the same specialist, which no longer has any memory of "those."

Because each delegation is a cold start, the second turn either (a) redundantly re-runs the
first specialist's tool calls, or (b) fails because the referent is gone. We currently work
around this by having the parent re-serialize prior context into every delegation prompt, which:

inflates token cost — the same context is re-sent on every turn;
is lossy — the parent must guess what the specialist needs, and can only forward what it
happened to observe;
duplicates side effects — the specialist re-issues tool calls it already made;
doesn't scale with depth or turn count — the re-serialized preamble grows monotonically.

This is the central limitation we hit when using sub-agents for anything beyond single-shot,
fire-and-forget tasks.

CustomAgentConfig has no state, persistence, or session field (session.py:979-994):

class CustomAgentConfig(TypedDict, total=False):
    name: str
    display_name: NotRequired[str]
    description: NotRequired[str]
    tools: NotRequired[list[str] | None]
    prompt: str
    mcp_servers: NotRequired[dict[str, MCPServerConfig]]
    infer: NotRequired[bool]
    skills: NotRequired[list[str]]
    model: NotRequired[str]

There is no field to opt a sub-agent into persistence, and nowhere to attach a checkpointer or
store.

  1. Session.send() has no way to target a prior sub-agent run (session.py:1261-1270):
async def send(
    self,
    prompt: str,
    *,
    attachments: list[Attachment] | None = None,
    mode: Literal["enqueue", "immediate"] | None = None,
    agent_mode: Literal["interactive", "plan", "autopilot", "shell"] | None = None,
    request_headers: dict[str, str] | None = None,
    display_prompt: str | None = None,
) -> str:
  1. A sub-agent task identifier already exists — but is inbound-only.
    parentAgentTaskId appears on UserMessageData (generated/session_events.py:6743), so the
    runtime clearly tracks which agent task a message belongs to. It is only ever read off an
    observed event; there is no corresponding parameter to pass one back in.

For completeness, the persistence primitives that do exist are all session-scoped and cannot
be applied to an individual sub-agent:

Proposal

Two shapes, in increasing order of scope. Either would unblock us; Option A looks like the
smaller change given that the identifier already exists.

Option A — expose the existing agent task ID (minimal)

Make parentAgentTaskId round-trippable, and let a sub-agent opt into retention:

# opt in per sub-agent
CustomAgentConfig(
    name="accounts-specialist",
    prompt=...,
    persist_conversation=True,   # NEW: retain this agent's context across invocations
)


# address a prior run
await session.send(
    "Now get health scores for those accounts.",
    resume_agent_task_id=prior_task_id,   # NEW
)

Retention could reasonably be scoped to the parent session's lifetime, which sidesteps any new
durable-storage requirements.

Option B — first-class sub-agent handles

Surface a handle when a sub-agent is invoked and allow direct continuation:

handle = await session.get_agent_task(task_id)
await handle.send("Now get health scores for those accounts.")

This is more expressive (it also enables inspecting a sub-agent's transcript) but is a larger
API surface.

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