Skip to content

OpenCode subagents: take a subagent's end from the parent's record, ready for anomalyco/opencode#57418 #1999

Open

Description

@devswha

Follow-up to #1987 (#893), from @Ploppy3's note there: #1987 (comment)

server/opencode-subagents.ts (runEnd) takes a child session's newest idle row as the end of its subagent. That matches OpenCode up to 2.1.4: the parent's subagent call is answered at the child's first idle, and a child woken again reads as running.

anomalyco/opencode#57418 ("fix(core): wait for background jobs before reporting subagent result", open, not merged) changes when a subagent is done. A child can go idle while background work it started still runs (a background shell, or its own background subagents). The parent's call is answered only after that work ends and the woken child goes idle again. Once that ships, this reader:

  • reads such a subagent as completed while the parent still waits on it;
  • drops the running count and releases the BG hold, so the turn is alerted as DONE once the 60 s grace runs out, while the work goes on;
  • flips the list back to running when the child's next run starts.

Proposed reading, as claude-subagents.ts takes ends from the parent transcript. It holds before and after #57418:

  • Foreground call: the end is the parent's subagent tool block (state.metadata.sessionID names the child). It ends with its own status: completed with a metadata.status other than running, or error (error.type: "aborted" is a cancel).
  • Background call: answered at once with metadata.status: "running", and ended by the parent's synthetic row carrying metadata.childID and metadata.state (completed, cancelled, error).
  • Several records for one child: the newest call or notice wins, so a call that resumes a child (sessionID in its input) runs it again.
  • Fallback: the child's own idle row decides only for a child woken after the parent was told it ended, or one no call in the parent names (a migrated 1.x store, or a call older than the window read).
  • Cost: the parent's rows stay cheap to read when SQL filters them first: instr(data, '"name":"subagent"') on assistant rows and instr(data, '"source":"subagent"') on synthetic rows. Read on from the last seq seen, and re-read a row whose call was not answered yet when its time_updated moves.

Expected

  • An OpenCode subagent counts as running, and holds its turn's BG, until the parent's own record says it ended. This should hold whether or not #57418 has shipped.
  • Tests should cover a child idle before its call is answered (rows as #57418 writes them).

The row shapes above were checked against an OpenCode 2.1.4 store (about 1,600 subagent calls written by 2.0.x to 2.1.4).

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions