Skip to content

subagent: tool remains advertised at the maximum session depth #53356

Open

Description

@carson2222

Description

With the default experimental.subagent_depth of 1, child sessions still get the subagent tool and its "Available subagents" list. Workers write full delegation briefs, then every call fails with:

Subagent depth limit reached (1). Increase "experimental.subagent_depth" to allow nested subagents.

The depth check only runs at execution (subagent.ts:117-133); the prompt hook adds the agent list without checking depth (subagent.ts:282-309). Hit 8+ times in one day. The error text also nudges agents to raise the limit.

Expected: hide the tool at the depth limit and keep the execution guard. Workaround: deny subagent in each worker agent's permissions, which hides it.

Plugins

[email protected]

OpenCode version

2.0.23

Steps to reproduce

  1. Keep experimental.subagent_depth at the default 1.
  2. From a primary session, spawn a subagent with a task that invites delegation.
  3. The child sees the subagent tool, calls it, and gets the depth error.

Screenshot and/or share link

No response

Operating System

macOS (Darwin 25.6.0, arm64)

Terminal

Ghostty

Activity

  1. added
    needs:complianceThis means the issue will auto-close after 2 hours.
    and removed
    needs:complianceThis means the issue will auto-close after 2 hours.
    on Oct 5, 2026
  2. github-actions commented on Oct 5, 2026

    @github-actions
    Contributor

    Thanks for updating the issue with a clear description and reproduction steps!

  3. opencodejr commented on Oct 27, 2026

    @opencodejr
    Contributor

    Thanks for the detailed report. I can reproduce this on 2.0.23, and it still happens on the latest dev (fd8268ea5).

    With the default experimental.subagent_depth (1), I had a simulated model in the primary session start a general subagent:

    • The child session's model request still included the subagent tool, with the full "Available subagents" list in its description.
    • When the child called it, the call failed with Subagent depth limit reached (1). Increase "experimental.subagent_depth" to allow nested subagents.

    The depth limit is only checked when the tool runs. The step that adds the subagent list to the tool description doesn't look at session depth, so the tool is still offered.

    The recording below shows the run, and the script below reproduces it. Until this is fixed, denying subagent in the worker agents' permissions hides the tool, as you found.

    recording.mp4
    Reproduction script (drive.ts)
    // Repro for anomalyco/opencode#53356: the `subagent` tool is still advertised to a
    // child session at the default experimental.subagent_depth (1), and every call fails.
    import { appendFileSync } from "node:fs"
    import { Effect, Stream } from "effect"
    const log = (m: string) => { console.log(m); appendFileSync("/tmp/opencode/repro.log", m + "\n") }
    import { Llm, OpenCodeDriver } from "opencode-drive"
    
    const toolNames = (body: any): string[] =>
      (body?.tools ?? []).map((t: any) => t?.function?.name ?? t?.name).filter(Boolean)
    const text = (body: unknown) => JSON.stringify(body)
    
    export default OpenCodeDriver.use(
      {
        ...(process.env.OC_DEV ? { opencode: { dev: process.env.OC_DEV } } : {}),
        tui: { recording: true },
      },
      ({ ui, llm }) =>
        Effect.gen(function* () {
          yield* llm.serve((request) => {
            const body = request.body as any
            const s = text(body)
            const isChild = s.includes("CHILD_TASK_MARKER")  && !s.includes("PRIMARY_MARKER")
            const tools = toolNames(body)
            log(`[request] ${isChild ? "CHILD" : "PRIMARY"} tools=${JSON.stringify(tools)}`)
            if (isChild) {
              const desc = (body.tools ?? []).find((t: any) => (t?.function?.name ?? t?.name) === "subagent")
              if (desc) log(`[child subagent description]\n${desc?.function?.description ?? desc?.description}`)
              if (s.includes("Subagent depth limit reached")) {
                log("[child] got depth-limit error from subagent tool")
                return Stream.make(Llm.text("Nested delegation failed: depth limit reached."))
              }
              if (tools.includes("subagent"))
                return Stream.make(
                  Llm.toolCall({
                    index: 0,
                    id: "call_nested",
                    name: "subagent",
                    input: { agent: "general", description: "Nested worker", prompt: "Do part of the work" },
                  }),
                  Llm.finish("tool-calls"),
                )
              return Stream.make(Llm.text("Child done without subagent tool."))
            }
            if (s.includes("Nested delegation failed") || s.includes("Child done"))
              return Stream.make(Llm.text("Primary: child finished."))
            return Stream.make(
              Llm.toolCall({
                index: 0,
                id: "call_child",
                name: "subagent",
                input: { agent: "general", description: "Worker task", prompt: "CHILD_TASK_MARKER: split this work up and delegate parts" },
              }),
              Llm.finish("tool-calls"),
            )
          })
          yield* ui.submit("PRIMARY_MARKER delegate this to a worker")
          yield* ui.waitFor("Primary: child finished.", { timeout: 60000 } as any)
          yield* ui.screenshot("final")
        }),
    )

    Run it with opencode-drive: opencode-drive run ./drive.ts. It drives opencode from your PATH; set OC_DEV to the path of an OpenCode checkout to run that from source instead.

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

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions