deepseek-ai/deepseek-harness · error · ApiRemoteSubagentSessionOwnership
agent-busy
agent-busy
Error message
session "${sessionId}" is a subagent session; use subagent delivery What it means
Raised on the cold-resume path of the Host's shared Agent resolver when the persisted session header declares origin 'subagent' (the inspect-time check at agent-lookup.ts:148). Subagent child sessions belong to their parent agent's routing; generic Remote/legacy RPC must not resume them as top-level agents. The resolver converts it to the caller-facing error code 'agent-busy' with details.reason 'use subagent delivery for this child session' — the legacy fence shape is preserved on purpose.
Source
Thrown at packages/api/remotes/src/agent-lookup.ts:149
return { error: apiRemoteSubagentOwnershipError(sessionId) }
}
return { agent: live }
}
const agentFor = async (sessionId: SessionId): Promise<ApiRemoteAgentResult> => {
const fenced = fencedLiveAgent(sessionId)
if (fenced !== undefined) return fenced
const attached = ctx.sessions.get(sessionId)
if (attached !== undefined && hasApiRemoteSubagentOwner(ctx, attached, undefined)) {
return { error: apiRemoteSubagentOwnershipError(sessionId) }
}
let resume = resumes.get(sessionId)
if (resume === undefined) {
resume = (async () => {
try {
const inspected = await inspectApiRemoteSession(ctx, sessionId)
if (hasApiRemoteSubagentOwner(ctx, { header: inspected.meta }, undefined)) {
throw new ApiRemoteSubagentSessionOwnership(sessionId)
}
// Built from the inspected session before the published re-checks
// below, so those stay adjacent to `resume` and a Host setup that
// awaits (composing a preset, say) does not widen the collision
// window.
const setup = options.setup === undefined ? undefined : await options.setup(inspected)
const publishedSession = ctx.sessions.get(sessionId)
const publishedAgent = ctx.agents.get(sessionId)
if (publishedSession !== undefined
&& hasApiRemoteSubagentOwner(ctx, publishedSession, publishedAgent)) {
throw new ApiRemoteSubagentSessionOwnership(sessionId)
}
const handle = await ctx.agents.resume({
resumeSessionId: sessionId,
...options.agentOptions === undefined ? {} : { agentOptions: options.agentOptions() },
...setup === undefined ? {} : { setup },
})
return handle.agentView on GitHub (pinned to b150a551b8)
Solutions
- Route the request to the parent session instead: read parentSession from the child's header and resolve that id, letting subagent delivery manage the child.
- Use the subagent delivery mechanism for child sessions rather than generic session RPC.
- Filter origin === 'subagent' sessions out of client-facing session lists so they cannot be selected as continuation targets.
Example fix
// before const result = await agentFor(childSessionId) // after const inspected = await inspectApiRemoteSession(ctx, childSessionId) const target = inspected.meta.origin === 'subagent' ? inspected.meta.parentSession : childSessionId const result = await agentFor(target)
Defensive patterns
Strategy: type-guard
Validate before calling
const inspected = await inspectApiRemoteSession(ctx, sessionId)
if (inspected.meta.origin === 'subagent') {
throw new Error(`route via parent session ${inspected.meta.parentSession}`)
} Type guard
const isSubagentFence = (r: ApiRemoteAgentResult): boolean => 'error' in r && r.error.code === 'agent-busy' && r.error.details.reason === 'use subagent delivery for this child session'
Try / catch
try {
const agent = await coldResume(sessionId)
} catch (error) {
if (error instanceof ApiRemoteSubagentSessionOwnership) {
// permanent routing change: switch to the parent session, do not retry
}
throw error
} Prevention
- Never store child session ids as user-facing continuation targets.
- Exclude header.origin === 'subagent' records from selectable session lists.
- Treat 'agent-busy' with the subagent reason as a routing instruction, not transient load.
When it happens
Trigger: Calling agentFor (directly or via legacy API Proxy methods or Typert lookups) on a cold session id whose persisted header.origin === 'subagent', while no live agent for that id exists — the ownership check fires from the inspected metadata before any resume attempt.
Common situations: A client kept a child session id from a completed delegation and tries to continue it directly; a session-list UI shows subagent sessions next to top-level ones and a user selects one; a script iterates every session in the store and drives the generic session API against each.
Related errors
AI-assisted analysis of deepseek-ai/deepseek-harness@b150a551b8 (2026-08-24).
Data as JSON: /api/errors/c7cc308d6de016f8.
Report an issue: GitHub.