Patterns
Fleet scripts converge on a handful of shapes. Each one is ordinary TypeScript — the value is in knowing which agent should hold which part of the problem.
Review and check
The most common two-agent shape: one agent does the work, a second one with a different identity judges it.
// review.axon.ts
import { Fleet } from "@axon/core"
const { barry, checker } = await Fleet({
barry: "../barry",
checker: "../checker",
})
const review = await barry.request("review the changes on this branch")
const verdict = await checker.request(
`A reviewer produced the following. Is it fair, specific, and actionable?\n\n` +
`${review.text}`,
)
console.log(verdict.text)
The check is worth something because checker has no idea how the review was produced.
It never saw the diff, the tool calls, or the reasoning — only the output. An agent
grading its own work in its own context grades a conclusion it is already committed to.
Fan out, then synthesise
Independent questions run at once, and a third agent draws them together.
const { security, perf, lead } = await Fleet({
security: "../security-auditor",
perf: "../perf-auditor",
lead: "@arclabs/architect",
})
const [sec, prf] = await Promise.all([
security.request("audit this service for vulnerabilities"),
perf.request("profile this service and find the hot paths"),
])
const plan = await lead.request(
`Two audits completed.\n\n` +
`Security:\n${sec.text}\n\n` +
`Performance:\n${prf.text}\n\n` +
`Produce a single prioritised plan. Call out anything where the two conflict.`,
)
The two audits cost the time of the slower one, not the sum. And lead sees two
finished analyses rather than two agents' worth of intermediate work — which is what
makes the synthesis useful rather than overwhelming.
Pipeline
Each stage takes the previous stage's conclusion and does something narrower with it.
const { scout, planner, writer } = await Fleet({
scout: "scout",
planner: "planner",
writer: "writer",
})
const findings = await scout.request(`find every caller of ${symbol}`)
const plan = await planner.request(`plan a safe refactor.\n\n${findings.text}`)
const patch = await writer.request(`implement this plan.\n\n${plan.text}`)
The reason to split this rather than give one agent all three jobs is context. scout
may make fifty tool calls to answer its question; none of that reaches planner, which
sees a finding list. Each stage starts clean and reasons about one thing.
Delegation with a cheap agent
Not every step needs your most capable agent. Fleet makes the choice explicit, because the agents are named:
const { classifier, barry } = await Fleet({
classifier: "../classifier", // small engine, narrow policy
barry: "../barry", // full toolset
})
const kind = await classifier.request(`classify this issue: ${issue.title}`)
if (kind.text.trim() === "bug") {
await barry.request(`investigate this bug:\n\n${issue.body}`)
}
The expensive agent boots either way — booting is not where the cost is. What you save
is the loop: barry only runs when there is something worth its attention.
Streaming to a terminal
When a fleet script is something you sit and watch, stream the agent doing the long work and let the others report at the end.
const { stream } = barry.stream("scout the repository and report findings")
let output = ""
for await (const entry of stream) {
if (entry.type === "text") {
process.stdout.write(entry.content)
output += entry.content
}
}
const verdict = await checker.request(`assess these findings:\n\n${output}`)
console.log(`\n\n---\n${verdict.text}`)
Pure automation
An agent in a fleet script is not obligatory. Fetch, transform, and write with ordinary TypeScript; bring in an agent for the step that actually needs judgment.
const issues = await fetchOpenIssues()
const stale = issues.filter(i => daysSince(i.updatedAt) > 30)
if (stale.length === 0) process.exit(0)
const { triage } = await Fleet({ triage: "triage" })
const summary = await triage.request(
`${stale.length} issues have been quiet for a month. ` +
`Which should be closed, and which need a nudge?\n\n${format(stale)}`,
)
await postDigest(summary.text)
Note the early exit before Fleet(). Nothing booted, so nothing needed shutting down —
the cheapest agent call is the one you didn't make.
Choosing the split
Two questions decide whether work belongs to one agent or several:
Does the second step need to be uncontaminated by the first? Review-and-check works because the checker never saw the reasoning. If a fresh perspective is the point, it needs a fresh context, which means a second agent.
Do the steps need different permissions or different tools? An agent that reads the codebase and an agent that opens pull requests should not be the same agent. Policy is per-agent, so splitting the work is how you narrow what each part can reach.
If neither applies, one agent and consecutive request() calls is the simpler program —
and it keeps full context across the steps, which is often exactly what you want.
Back to Working with Agents — or on to The Agent Handle for the primitive these are built on.