Read this reference when a root agent delegates work, coordinates a dynamic set, or needs retry/addon behavior.
Expose subagents through an object:
import { agent, s } from "rig";
// Agent role: extract the most important changes from a diff.
const summarizeDiff = agent({
model: "nano",
output: s.object({ summary: s.string }),
});
// Agent role: review a diff with focused summary support.
const reviewer = agent({
model: "mini",
agents: { summarizeDiff },
instructions: "Review the diff. Delegate summarization when useful, then combine the evidence.",
output: s.object({
summary: s.string,
issues: s.array(s.string),
}),
});
export default reviewer;agents: { summarizeDiff } is valid; agents: [summarizeDiff] is not. Keep subagents narrow and make the root instructions require one combined final response.
Every declared agent must remain reachable from the exported root. A detached const extractor = agent(...) is both unavailable to the harness and likely to trigger TS6133 as an unused variable.
// wrong: agents must be an object, not an array
const invalid = agent({
agents: [summarizeDiff],
});There is no built-in chain primitive. Attach the upstream agent to the downstream root and describe the sequence:
import { agent, s } from "rig";
// Agent role: extract key facts from input text.
const extractor = agent({
model: "nano",
output: s.object({ facts: s.array(s.string) }),
});
// Agent role: assess extracted facts and return a verdict.
const assessor = agent({
model: "mini",
agents: { extractor },
instructions: "Use extractor first, assess its facts, then return the final verdict.",
output: s.enum("healthy", "needs-work", "critical"),
});
export default assessor;Two-phase coordinator patterns can pass structured output from one subagent into the next:
import { agent, p, s } from "rig";
// Agent role: extract per-function line counts.
const extractor = agent({
model: "nano",
instructions: p`Run ${p.bash("rg -n '^function|^const .*=>|^export function' src --glob '*.ts'")} and return per-function line counts.`,
output: s.record(s.number),
});
// Agent role: classify complexity from extracted counts.
const reviewer = agent({
model: "nano",
input: s.object({ counts: s.record(s.number) }),
instructions: "Rate each function in input.counts as simple, moderate, complex, or critical.",
output: s.record(s.enum("simple", "moderate", "complex", "critical")),
});
// Agent role: orchestrate extraction then review.
const coordinator = agent({
model: "mini",
agents: { extractor, reviewer },
instructions: "Call extractor first, then call reviewer with { counts: extractor output }, and return the review result.",
output: s.record(s.enum("simple", "moderate", "complex", "critical")),
});
export default coordinator;There is no loop primitive for calling a subagent once per item. Give the coordinator the discovered items and explicit per-item delegation instructions; the model drives the loop:
import { agent, p, s } from "rig";
// Agent role: summarize one file.
const fileSummarizer = agent({
model: "nano",
input: s.object({ path: s.path }),
instructions: p`Summarize ${p.readInput("path")}.`,
output: s.object({
path: s.path,
summary: s.string,
}),
});
// Agent role: coordinate summaries for every discovered TypeScript file.
const coordinator = agent({
model: "mini",
agents: { fileSummarizer },
instructions: p`Find TypeScript files with ${p.glob("src/**/*.ts")}. For each path, delegate to fileSummarizer, then return a record containing every summary.`,
output: s.record(s.string, "summaries keyed by path"),
});
export default coordinator;For large lists the model may process a subset. Give it an adequate turn budget, explicit completeness requirements, and repair when structured completeness matters.
When a task asks for runnable markdown:
- include exactly one fenced
rigblock - include
import { ... } from "rig"or intentionally rely on inline injection - define one default-exported root with no required external input
- do not call the root in the snippet
- keep every subagent attached to the root graph
Rig starts with no addons. maxTurns is only the total budget; automatic parse/schema correction requires repair():
| Scenario | Addons | What happens |
|---|---|---|
| Invalid parsed or schema-validated output | repair() |
Retries with the validation error |
| The same failure needs stronger final guidance | [steering(), repair()] |
Adds a last-chance warning to repair's final retry |
import { agent, repair } from "rig";
// Agent role: return a valid concise summary.
const summarize = agent({
model: "mini",
maxTurns: 3,
addons: repair(),
});
export default summarize;repair() takes no options. The budget includes the initial attempt and all retries — for example, maxTurns: 3 means one initial attempt plus two repair retries. Configure maxTurns on the agent spec; a call-time value can override it.
A repair turn is a parse/schema retry attempt after invalid output; steering does not add extra retries, it only injects a stronger warning on the final repair retry.
// wrong: repair() does not accept maxTurns
addons: repair({ maxTurns: 3 });
// right: put maxTurns on the agent spec
maxTurns: 3,
addons: repair();steering() appends a last-chance warning to the final retry prompt produced by repair. Put it before repair so it can observe the repair prompt as the addon chain unwinds:
import { agent, repair, steering } from "rig";
// Agent role: return a valid concise summary with final-turn steering.
const summarize = agent({
model: "mini",
maxTurns: 3,
addons: [steering(), repair()],
});
export default summarize;// wrong: steering runs too late here
addons: [repair(), steering()];
// right: steering wraps repair
addons: [steering(), repair()];Use repair() alone when the validation error is enough guidance. Pass custom warning text in an options object, as in steering({ message: "Return valid JSON now." }); a positional string is invalid. Do not use steering() without repair(), because it only augments prompts generated by repair.
oncePerAgent(register) invokes its callback exactly once per runtime agent instance — not once per turn and not once per retry. The callback receives (agent: Agent, context: AgentAddonContext). Use it for one-time initialization such as registering a tool adapter or constructing a client:
import { agent, oncePerAgent, repair, s } from "rig";
// Agent role: answer after one-time runtime initialization.
const qa = agent({
model: "mini",
addons: [
oncePerAgent(async (runtimeAgent) => {
// e.g. register a tool adapter on the runtime agent once
await runtimeAgent.ask("initialize");
}),
repair(),
],
});
export default qa;oncePerAgent tracks initialization via its own internal WeakSet; do not add an external WeakSet to track the same thing. Repair retries reuse the same runtime agent, so the registration still runs once.
Both addons: singleAddon and addons: [addon1, addon2] are valid. Prefer the array form when combining addons.
Per-turn addon context contains:
prompt,response,outputturn,maxTurnssignal,agentnextPrompt,error,completed
Put stable addons in the agent spec. Add later behavior with agent.use(addon); .use() does not accept model, turn, timeout, or signal options.