Skip to content

Add background agent task tools for concurrent sub-agent dispatch (#863)#1908

Merged
dgageot merged 1 commit intodocker:mainfrom
jfrey:feature/parallel-task-863
Mar 6, 2026
Merged

Add background agent task tools for concurrent sub-agent dispatch (#863)#1908
dgageot merged 1 commit intodocker:mainfrom
jfrey:feature/parallel-task-863

Conversation

@jfrey
Copy link
Contributor

@jfrey jfrey commented Mar 3, 2026

Summary

Implements concurrent sub-agent orchestration from #863 by adding four background agent tools that mirror the existing shell background job pattern:

  • run_background_agent — dispatch a named sub-agent task asynchronously, returns a task ID immediately
  • list_background_agents — show all tasks with status and runtime
  • view_background_agent — inspect live buffered output or final result by task ID
  • stop_background_agent — cancel a running task

Tools are registered automatically for any agent that has sub_agents configured, alongside the existing transfer_task tool.

Architecture

Handler and task tracking live in pkg/tools/builtin/agent/, following the same self-contained pattern as the shell tool in builtin/shell.go. A Runner interface abstracts the runtime dependency:

type Runner interface {
    CurrentAgentSubAgentNames() []string
    RunAgent(ctx context.Context, params RunParams) *RunResult
}

LocalRuntime implements Runner and registers the handlers via thin delegation methods, matching the existing pattern used by handleTaskTransfer, handleHandoff, etc.

Key design decisions

  • Session-scoped agent identitysession.WithAgentName pins each background session to its target agent, so RunStream resolves configuration from the session rather than the shared currentAgent field. This eliminates the race between concurrent background tasks.
  • Async / fire-and-forget — caller gets a task ID back immediately and polls with view_background_agent, matching the UX of shell background jobs
  • ToolsApproved=true for background sessions — no user present to answer approval prompts; documented with a TODO for per-session permission scoping
  • Resource caps — max 20 concurrent tasks, 100 total; completed tasks auto-pruned; live output buffer capped at 10 MB per task
  • Typed enumtaskStatus int32 with iota, google/uuid for task IDs
  • CAS-based status transitions for safe concurrent stop/complete

Test plan

  • 28 unit tests in pkg/tools/builtin/agent/ with race detector
  • go build ./..., go vet ./... clean
  • Manual test: build binary and exercise background agent dispatch in a team with sub-agents

🤖 Generated with Claude Code

Copy link
Member

@dgageot dgageot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM in general! Sorry, I left a few nit-picky comments. Feel free to ignore.
One of the things that could be better is that most of the code lives in the runtime package that we wish would be simpler. Do you think most of it could be extracted?


// agent task status constants — mirrors backgroundJob in shell.go
const (
agentTaskRunning int32 = iota
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should maybe introduce a type for the enum rather than int32?

@jfrey
Copy link
Contributor Author

jfrey commented Mar 6, 2026

LGTM in general! Sorry, I left a few nit-picky comments. Feel free to ignore. One of the things that could be better is that most of the code lives in the runtime package that we wish would be simpler. Do you think most of it could be extracted?

I'll take care of all those and will refactor the background agent into a builtin task. It is similar to shell but has a dependency on some of the stream interfaces so I can work out an injectable interface for that to segregate the code more.

@jfrey jfrey force-pushed the feature/parallel-task-863 branch 3 times, most recently from 2613048 to 37c1ce2 Compare March 6, 2026 12:39
@jfrey jfrey marked this pull request as ready for review March 6, 2026 12:51
@jfrey jfrey requested a review from a team as a code owner March 6, 2026 12:51
…cker#863)

Add four tools mirroring the existing shell background job pattern,
enabling orchestrating agents to dispatch sub-agent tasks concurrently:

  - run_background_agent: fire-and-forget dispatch, returns task ID
  - list_background_agents: show all tasks with status and runtime
  - view_background_agent: inspect live output or final result
  - stop_background_agent: cancel a running task

Tools are registered for any agent with sub_agents configured.

The handler and task tracking live in pkg/tools/builtin/agent/, following
the same self-contained pattern as the shell tool. A Runner interface
abstracts the runtime dependency, with LocalRuntime implementing it via
thin delegation methods matching the existing tool handler pattern.

Key design decisions:
- Session-scoped agent identity (session.WithAgentName) eliminates the
  shared currentAgent race for concurrent background tasks
- ToolsApproved=true for background sessions (no user to approve)
- Resource caps: 20 concurrent, 100 total, 10MB output buffer per task
- Typed taskStatus enum with google/uuid for task IDs
- CAS-based status transitions with result written before CAS to prevent
  data race with concurrent readers
- RunAgent drains the event channel on early error/cancellation to
  prevent RunStream goroutine from blocking

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@jfrey jfrey force-pushed the feature/parallel-task-863 branch from 37c1ce2 to 097de4e Compare March 6, 2026 13:00
@docker docker deleted a comment from docker-agent bot Mar 6, 2026
@dgageot dgageot merged commit 3791869 into docker:main Mar 6, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants