Open
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The Mental Model
A Team is itself a Handler — it takes a Task and returns a Result. This is the key design
choice: teams and individual agents share the same interface, so they nest arbitrarily.
What Handle() Actually Does
When you call team.Handle(ctx, task), five things happen in order:
selections, err := t.router.Select(ctx, task, t.agents)
This returns a []Selection — each pairing an agent with the task it should handle. Different
routers make different decisions:
Guard Check — Before each agent runs, the TeamGuard is consulted. If the guard returns an
error (e.g., cost limit exceeded), the agent is skipped or the whole thing aborts (depending
on ErrorPolicy).
Execution — Selected agents run either sequentially or in parallel:
before each one, respect FailFast/CollectErrors
writing to its own index in a pre-allocated slice (no mutex needed — cost.Tracker.Add()
handles its own locking)
Individual results are always preserved in Result.Children for observability.
OnTeamComplete fire at each stage for logging/metrics.
Nesting
Because Team implements Handler, you can call team.AsAgent() to get an Agent wrapping the
team:
devTeam := team.New("dev-team",
team.WithAgents(backendAgent, frontendAgent),
team.WithStrategy(team.Parallel),
)
superTeam := team.New("delivery",
team.WithAgents(devTeam.AsAgent(), reviewAgent),
team.WithStrategy(team.Sequential),
)
When superTeam.Handle() routes to devTeam.AsAgent(), it calls devTeam.Handle() recursively —
that inner team does its own route/execute/aggregate cycle. The outer team sees a single
Result with Children containing the inner results.
Plugging Into the Pipeline
The adapters (DeveloperTeam, PlannerTeam, etc.) bridge the gap between the team's generic
Task/Result types and the pipeline's typed interfaces. For example, DeveloperTeam.Execute():
result)
This means you can replace any single-agent pipeline stage with a multi-agent team without
changing the pipeline orchestration at all:
// Before: single developer
runner.WithDeveloper(myDev)
// After: team of specialists
devTeam := team.New("dev-team",
team.WithAgents(backendAgent, frontendAgent),
team.WithStrategy(team.Parallel),
)
runner.WithDeveloper(team.NewDeveloperTeam(devTeam))
The pipeline doesn't know or care that it's talking to a team instead of a single agent.