Limits & capability governance
Three ways an agent process runs wild: concurrency blowout, call-count runaway, unauthorized writes. Agent Flow turns all three into language structures.
limits: the budget
limits {
concurrency: 4 // max concurrent agent calls
agent_runs: 45 // max total agent invocations for the workflow
duration: 30m // wall-clock cap
}
- Values must be positive (
invalid-limits); fields cannot repeat - The effective value is the smaller of host policy ∩ workflow limits — the host can only tighten
- Overruns:
agent_runs→LimitExceededError;duration→WorkflowTimeoutError; external cancellation →FlowCancelledError
Estimating agent_runs: count call sites. A parallel map contributes
up to input upper bound × 1, plus the fixed non-map calls. Prefer tight:
overrunning fails fast, which beats an exploding invoice.
Every retry execution passes through the semaphore and budget checks;
agent_runs counts actual invocations, retries included.
tools: requesting capabilities
tools none // request the empty set
tools [read_file, grep] // request some (commas required; trailing allowed)
The list is a request; the effective set = request ∩ host policy.allowedTools. Host policy is the ceiling: request write_file
against a policy that forbids it and you simply don't get it.
Practice: declare the minimum. Read-only phases (analysis,
classification) get [read_file, grep, list_dir]; write phases add
write_file/edit_file; pure reasoning (summarize, draft) gets
tools none.
write: declaring the write scope
write item.writes // per-item path[] field (the delivery pattern)
write input.write_scope // workflow-level path[] input (the kb-audit pattern)
- The expression's static type must be
path[]; homogeneous array literals infer their element type precisely - The write scope also intersects with host policy; out-of-scope writes are blocked
Two idiomatic patterns from the examples:
// Pattern 1: each work item carries its own scope, required disjoint
type WorkItem { id: text, owner: member<crew>, writes: path[] }
require disjoint(work[*].writes) else fail "write scopes must not overlap"
...
write item.writes
// Pattern 2: a workflow-level scope
type KbAuditRequest { kb_path: path, write_scope: path[], articles: ArticleRef[] }
...
write input.write_scope
member<Team>: human membership
use team "delivery-team" as crew
type WorkItem { id: text, owner: member<crew>, writes: path[] }
A member<crew> value is a member id of that team. It appears in two
positions:
- As a type (above): the Schema carries
x-flow-member - At a call site:
agent(crew.member(item.owner))— the Runtime validates thatitem.owneractually belongs todelivery-team, elseCapabilityViolationError
"Assign work to a person who really exists" is a runtime guarantee — a workflow with a mistyped name fails on first routing instead of silently assigning work to nobody.
use: resources are logical
use team "engineering-team" as engineering
use agent "secure-code-reviewer" as security
The string id points at no vendor product; resolution happens at runtime via
AgentRuntime.resolveAgent/resolveTeam. Aliases are workflow-unique
(duplicate-alias).
Governance at a glance
| Concern | Structure | Enforced by |
|---|---|---|
| Concurrency | limits.concurrency + map limit | host semaphore |
| Call count | limits.agent_runs | Runtime counter |
| Time | limits.duration + per-agent timeout | AbortSignal race |
| Tools | tools [...] ∩ policy | host narrowing |
| Write scope | write path[] ∩ policy | host policy |
| People | member<Team> membership | Runtime |
| Execution boundary | sandbox isolate, sole egress $host.invoke | the sandbox |
Next steps
- Runtime, sandbox & errors — how these guarantees land