agent() calls & output contracts
agent() is the language's only point of non-determinism. All model
capability flows through it, and every output carries a contract. This page
covers all options and runtime behavior.
Full form
agent(target) {
task "..." // required, unique: natural-language task
input { ... } // structured input (object literal)
tools [read_file, grep] // requested tool capabilities (tools none = empty)
write item.writes // write scope, must be exactly path[]
expect WorkItem // required, unique: output contract
timeout 5m // positive duration
retry { attempts: 1, on: [timeout, rate_limit, transient] }
}
The static type of an agent() expression equals its expect type — so
accesses like result.work[*].owner are checked at compile time.
The three target forms
| Form | Compiles to | Meaning |
|---|---|---|
agent(coder) (alias from use agent) | {kind:'alias'} | a fixed logical agent |
agent(team.main) | {kind:'team.main'} | the team's main agent (e.g. the lead) |
agent(team.member(expr)) | {kind:'team.member', member: <expr>} | dynamic member; expr must be text/member — membership validated at runtime |
Anything else is invalid-agent-target; team.main(...) is not callable.
Dynamic routing is the payoff of this design:
// Route each work item to its owner — routing as data, not hardcoding
return parallel map plan.work as item {
agent(crew.member(item.owner)) {
task "Implement the work item"
write item.writes
expect ChangeResult
}
}
expect: from type to enforced contract
The expect type must be Schema-generatable (primitives, member, objects,
arrays, composable). Verification and external types are not allowed.
Runtime chain:
Flow type → JSON Schema → OutputContract{typeName, schema, repairAttempts}
→ AgentRuntime invocation → structured result → Ajv validation
→ failed? re-invoke with {previousOutput, errors} (default 1 repair)
→ still failing → OutputValidationError
Key points:
- The repair loop is default behavior (
repairAttempts = 1): on the first invalid output the Runtime re-invokes the agent carrying the validation errors rather than failing immediately - Validation failures are structured — the Ajv error list feeds the repair prompt
tools and write: requests, not grants
tools [read_file, list_dir, grep] // request these tools
tools none // request the empty set
write item.writes // declare the write scope (path[] type)
Two things to internalize:
- Request ∩ host policy. The
toolslist is what the workflow wants; the effective set =request ∩ host policy.allowedTools. Declaring more never yields more. - write must be statically
path[]. Array literals infer a common element type, so[input.kb_path]is valid when that field is apath; mixing non-path elements failswrite-not-path-array.
retry: by error code, not message
retry { attempts: 1 } // on omitted = ['transient']
retry { attempts: 2, on: [timeout, rate_limit] }
attemptscounts extra tries:attempts: 1executes at most twice- Retrying is decided by error code (
timeout/rate_limit/transient), never by parsing the message — messages change, codes don't - Each attempt is still bounded by
timeoutand the workflow's remaining deadline
timeout and deadlines
timeout 3m caps a single invocation. The Runtime races the call against
min(timeout, remaining workflow deadline) via AbortSignal: a late
result never takes effect. Short values like 90s are perfectly legal.
A complete example
Excerpted from examples/bug-triage.flow:
use team "dev-team" as dev
use agent "repo-investigator" as investigator
stage reproduce -> Reproduction {
let result = agent(investigator) {
task """
Locate the suspect code for the reported bug.
Quote the decisive lines in hypothesis.
"""
input {
bug: input
logs: input.logs
}
tools [read_file, grep, list_dir] // request read-only tools
expect Reproduction // {reproduced, suspect_files: path[], hypothesis}
timeout 3m
retry { attempts: 1, on: [transient] }
}
require result.suspect_files.count in 0..10
else fail "suspicion list must stay focused"
return result
}
Deterministic judgment (require) sits outside the agent; the
non-deterministic output is fenced by expect; tools are requests; timeout
and retry are declarations. This is the canonical Agent Flow posture.
Next steps
- parallel & parallel map
- verify — who renders "passed"