Bug Description
The extension-facing background/async subagent API does not match its documentation or its own type definitions:
extbridge.SpawnSubagent (internal/extbridge/extbridge.go) always calls k.Subagent(ctx, sdkCfg) synchronously and returns handle == nil, regardless of Blocking: false. OnComplete fires synchronously before SpawnSubagent even returns.
SubagentHandle (internal/extensions/subagent.go) has dead fields (proc *os.Process, done chan, etc.) suggesting a subprocess design that was never wired up in the in-process bridge.
- The extension API docs (
internal/extensions/api.go:707) claim the subagent "runs as a separate subprocess with full tool access but isolated session and extensions (--no-session --no-extensions)". In reality it is an in-process child Kit with session persistence enabled by default and no extension isolation.
- The shipped example
examples/extensions/subagent-test.go (/subbg command) expects a usable handle — it receives nil and would panic or silently misbehave if exercised.
Expected: either non-blocking spawning works and returns a populated handle, or the API/docs/example clearly state that spawning is synchronous and in-process.
Steps to Reproduce
- Load
examples/extensions/subagent-test.go (output/kit -e examples/extensions/subagent-test.go --no-session)
- Run the
/subbg slash command, which calls ctx.SpawnSubagent(ext.SubagentConfig{Blocking: false, ...})
- Observe the call blocks until the subagent finishes and the returned handle is
nil
- Compare against the documented behavior in
internal/extensions/api.go:706-739
Relevant Code / Configuration
// internal/extbridge/extbridge.go — always synchronous, always nil handle:
result, err := k.Subagent(ctx, sdkCfg)
// ... handle is never constructed or returned
// internal/extensions/subagent.go — dead fields:
type SubagentHandle struct {
proc *os.Process // never populated by the in-process bridge
done chan struct{}
// ...
}
// internal/extensions/api.go:707 — inaccurate doc:
// "runs as a separate subprocess ... isolated session and extensions (--no-session --no-extensions)"
Affected Component
extensions, subagent bridge (extbridge), SDK
Kit Version
main (ed51910)
Additional Context
Two ways to resolve:
Option A (preferred): implement true non-blocking spawn — run k.Subagent in a goroutine, return a populated SubagentHandle (done channel, cancel func, status), invoke OnComplete/OnEvent callbacks asynchronously. Result injection back into the parent session could follow opencode's background task pattern.
Option B (minimal): remove the dead SubagentHandle subprocess fields, correct the doc comment in api.go to describe in-process synchronous execution, and fix examples/extensions/subagent-test.go to not rely on a handle.
Either way, docs, types, bridge behavior, and the shipped example must agree — right now the API is a trap for extension authors.
Related: #84 (named agents) builds on the same subagent machinery.
Checklist
Bug Description
The extension-facing background/async subagent API does not match its documentation or its own type definitions:
extbridge.SpawnSubagent(internal/extbridge/extbridge.go) always callsk.Subagent(ctx, sdkCfg)synchronously and returnshandle == nil, regardless ofBlocking: false.OnCompletefires synchronously beforeSpawnSubagenteven returns.SubagentHandle(internal/extensions/subagent.go) has dead fields (proc *os.Process,done chan, etc.) suggesting a subprocess design that was never wired up in the in-process bridge.internal/extensions/api.go:707) claim the subagent "runs as a separate subprocess with full tool access but isolated session and extensions (--no-session --no-extensions)". In reality it is an in-process child Kit with session persistence enabled by default and no extension isolation.examples/extensions/subagent-test.go(/subbgcommand) expects a usable handle — it receivesniland would panic or silently misbehave if exercised.Expected: either non-blocking spawning works and returns a populated handle, or the API/docs/example clearly state that spawning is synchronous and in-process.
Steps to Reproduce
examples/extensions/subagent-test.go(output/kit -e examples/extensions/subagent-test.go --no-session)/subbgslash command, which callsctx.SpawnSubagent(ext.SubagentConfig{Blocking: false, ...})nilinternal/extensions/api.go:706-739Relevant Code / Configuration
Affected Component
extensions, subagent bridge (extbridge), SDK
Kit Version
main (ed51910)
Additional Context
Two ways to resolve:
Option A (preferred): implement true non-blocking spawn — run
k.Subagentin a goroutine, return a populatedSubagentHandle(done channel, cancel func, status), invokeOnComplete/OnEventcallbacks asynchronously. Result injection back into the parent session could follow opencode's background task pattern.Option B (minimal): remove the dead
SubagentHandlesubprocess fields, correct the doc comment inapi.goto describe in-process synchronous execution, and fixexamples/extensions/subagent-test.goto not rely on a handle.Either way, docs, types, bridge behavior, and the shipped example must agree — right now the API is a trap for extension authors.
Related: #84 (named agents) builds on the same subagent machinery.
Checklist