agent()
agent() builds a Step that owns a model loop — unlike tool(), it has a model and instructions, and it decides how to get from input to output, calling its tools along the way.
Signature
Section titled “Signature”export interface Agent<Name, In, Out, Deps, Tools, Pass extends string = never> extends Step<Name, In, Out, Deps> { readonly "~kind": "agent"; readonly model: string; /** the concrete tool tuple is PRESERVED (not widened to AnyStep[]) so a * consumer's `ToolDepKeys<typeof agent.tools>` stays precise across .d.ts. */ readonly tools: Tools; /** phantom: union of declared passTo target NAMES — see team(). */ readonly "~passTo"?: Pass; readonly run: (input: InferOut<In>, ctx: AgentCtx<Deps>) => Promise<InferOut<Out>>;}
export function agent< const Name extends string, In extends IO<any, any>, Out extends IO<any, any>, const Tools extends readonly AnyStep[] = [], const D extends readonly (keyof LoopyDeps)[] = [], const Pass extends readonly string[] = [],>(def: { name: Name; model: string; instructions: string; input: In; output: Out; tools?: Tools & NoDuplicateTools<Tools>; deps?: D; passTo?: Pass;}): Agent<Name, In, Out, D[number] | ToolDepKeys<Tools>, Tools, Pass[number]>;Fields
Section titled “Fields”model— a plain string identifying the model, e.g."claude-opus","haiku". loopy doesn’t validate this today; it’s carried through as-is.instructions— the agent’s system prompt / role description.input/output—IO<...>schemas, same astool().tools— an array ofTools and/or otherAgents (see The Step spine for why an agent can be passed here). Defaults to[]. Duplicate tool names are a compile error viaNoDuplicateTools—tools: [editFile, editFile]won’t type-check.deps— dependencies the agent needs directly (beyond whatever its tools already declare). The agent’s effective dependency union isdeps[number] | ToolDepKeys<Tools>— see Dependency injection.passTo— used exclusively byteam(); a plain, standalone agent never needs it. Declares the names of other agents within the same team this agent is allowed to hand off to. At compile time, every name is checked against actual team membership (see team() → the passTo membership guard).
Example
Section titled “Example”export const codeGen = agent({ name: "codeGen", model: "sonnet", instructions: "Generate code changes in a think→act→observe loop.", input: io<{ task: string }>(), output: io<{ applied: readonly string[]; failed: readonly string[] }>(), // edit/create/read tools + a sub-agent passed where a tool is expected. tools: [editFile, createFile, readFile, fileAnalyzer], deps: ["repo"],});Example with passTo
Section titled “Example with passTo”export const triage = agent({ name: "triage", model: "opus", instructions: "Read the issue; hand to bugFixer or docsWriter.", input: io<{ issue: Issue }>(), output: io<{ kind: string }>(), passTo: ["bugFixer", "docsWriter"],});- workflow() — use an agent as one node in an explicit graph.
- team() — use agents as the nodes of a multi-agent loop.
- Guides → An agent with tools