Skip to main content

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_runsLimitExceededError; durationWorkflowTimeoutError; 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.

Retries and budgets

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:

  1. As a type (above): the Schema carries x-flow-member
  2. At a call site: agent(crew.member(item.owner)) — the Runtime validates that item.owner actually belongs to delivery-team, else CapabilityViolationError

"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

ConcernStructureEnforced by
Concurrencylimits.concurrency + map limithost semaphore
Call countlimits.agent_runsRuntime counter
Timelimits.duration + per-agent timeoutAbortSignal race
Toolstools [...] ∩ policyhost narrowing
Write scopewrite path[] ∩ policyhost policy
Peoplemember<Team> membershipRuntime
Execution boundarysandbox isolate, sole egress $host.invokethe sandbox

Next steps