{"record":{"id":"c7cc308d6de016f8","repo":"deepseek-ai/deepseek-harness","slug":"agent-busy","errorCode":"agent-busy","errorMessage":"session \"${sessionId}\" is a subagent session; use subagent delivery","messagePattern":"session \"(.+?)\" is a subagent session; use subagent delivery","errorType":"exception","errorClass":"ApiRemoteSubagentSessionOwnership","httpStatus":null,"severity":"error","filePath":"packages/api/remotes/src/agent-lookup.ts","lineNumber":149,"sourceCode":"      return { error: apiRemoteSubagentOwnershipError(sessionId) }\n    }\n    return { agent: live }\n  }\n\n  const agentFor = async (sessionId: SessionId): Promise<ApiRemoteAgentResult> => {\n    const fenced = fencedLiveAgent(sessionId)\n    if (fenced !== undefined) return fenced\n    const attached = ctx.sessions.get(sessionId)\n    if (attached !== undefined && hasApiRemoteSubagentOwner(ctx, attached, undefined)) {\n      return { error: apiRemoteSubagentOwnershipError(sessionId) }\n    }\n    let resume = resumes.get(sessionId)\n    if (resume === undefined) {\n      resume = (async () => {\n        try {\n          const inspected = await inspectApiRemoteSession(ctx, sessionId)\n          if (hasApiRemoteSubagentOwner(ctx, { header: inspected.meta }, undefined)) {\n            throw new ApiRemoteSubagentSessionOwnership(sessionId)\n          }\n          // Built from the inspected session before the published re-checks\n          // below, so those stay adjacent to `resume` and a Host setup that\n          // awaits (composing a preset, say) does not widen the collision\n          // window.\n          const setup = options.setup === undefined ? undefined : await options.setup(inspected)\n          const publishedSession = ctx.sessions.get(sessionId)\n          const publishedAgent = ctx.agents.get(sessionId)\n          if (publishedSession !== undefined\n            && hasApiRemoteSubagentOwner(ctx, publishedSession, publishedAgent)) {\n            throw new ApiRemoteSubagentSessionOwnership(sessionId)\n          }\n          const handle = await ctx.agents.resume({\n            resumeSessionId: sessionId,\n            ...options.agentOptions === undefined ? {} : { agentOptions: options.agentOptions() },\n            ...setup === undefined ? {} : { setup },\n          })\n          return handle.agent","sourceCodeStart":131,"sourceCodeEnd":167,"githubUrl":"https://github.com/deepseek-ai/deepseek-harness/blob/b150a551b8d465e31e418e1b2eaf5e79bbb7d28e/packages/api/remotes/src/agent-lookup.ts#L131-L167","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nconst result = await agentFor(childSessionId)\n\n// after\nconst inspected = await inspectApiRemoteSession(ctx, childSessionId)\nconst target = inspected.meta.origin === 'subagent'\n  ? inspected.meta.parentSession\n  : childSessionId\nconst result = await agentFor(target)","handlingStrategy":"type-guard","validationCode":"const inspected = await inspectApiRemoteSession(ctx, sessionId)\nif (inspected.meta.origin === 'subagent') {\n  throw new Error(`route via parent session ${inspected.meta.parentSession}`)\n}","typeGuard":"const isSubagentFence = (r: ApiRemoteAgentResult): boolean =>\n  'error' in r && r.error.code === 'agent-busy'\n  && r.error.details.reason === 'use subagent delivery for this child session'","tryCatchPattern":"try {\n  const agent = await coldResume(sessionId)\n} catch (error) {\n  if (error instanceof ApiRemoteSubagentSessionOwnership) {\n    // permanent routing change: switch to the parent session, do not retry\n  }\n  throw error\n}","preventionTips":["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."],"tags":["subagent","agent-routing","api-remotes","agent-busy"],"backgroundTag":"agent-busy","analyzedSha":"b150a551b8d465e31e418e1b2eaf5e79bbb7d28e","analyzedAt":"2026-08-24T18:12:29.105Z","schemaVersion":2},"datasetVersion":"2026-08-24T22:17:12.610Z"}