agents are markdown
Browse docs

Docs/The format

Flows

A flow is a numbered list of steps in a markdown file under flows/. Steps run in ascending group order, and each receives the results of every earlier group, oldest first — a group-3 reporter sees what group 1 measured, not only what group 2 diagnosed. Output passing is the default, not configuration. The handoff is capped (about 30,000 characters); over it, the oldest groups are dropped whole and the newest kept.

---
name: weekly-digest
trigger: schedule
schedule: "0 8 * * MON"
timezone: Australia/Sydney
---

1. [[researcher]] — gather this week's competitor updates
2. [[writer]] — turn the research into a 300-word digest at outputs/digest.md

The step line

<group><marker>. [[<agent>]] — <instruction>
  • Group — steps sharing a number run in parallel; the next group starts when all of them finish and receives every result, labelled per agent.
  • Marker? makes the step optional (it fails without failing the flow); ! parks it for a human, the shorthand for approve: true.
  • Target — the first link on the line is structural: the agent that runs, or [[flow:name]] for a whole flow composed in place. Cycles, and nesting past three levels, are errors.
  • Instruction — everything after the dash. Other [[links]] in it resolve to real paths before the model sees them.

Options are indented lines under the step.

Every step option

option
when: run only if this text appears in the previous results — non-exclusive, every match runs
case: exclusive branch: the first matching case: in the group runs, the rest are routed past
else: runs only when no case: in the group matched
each: fan out — lines, items, or rows of <path>
max: cap the fan-out (default 10, hard cap 20; dropped items are logged)
loop: send the flow back one group up to N times (capped at 5)
until: the marker that ends the loop
output: json — the reply must end with one ```json block, parsed and passed on losslessly
model: override the tier for this step
effort: override how hard it thinks for this step
retry: attempts after the first, clamped to 5
timeout: seconds — the only clock there is
verify: a shell command, or an assertion (contains:, matches:, file:, judge:)
approve: park until a person releases it
ask: the same gate carrying a question; the typed answer reaches the prompt
wait: 3d (s/m/h/d, capped at 30 days) or event
on-fail: another agent takes the step over, with the failure as context
delegate: the step’s agent picks who runs next, from this set

Routing

1. [[classifier]] — reply with exactly one word, BUG or QUESTION
2. [[debugger]] — investigate and fix
   case: BUG
2. [[writer]] — answer it clearly
   case: QUESTION
2. [[triager]] — neither label fit; say what is missing
   else: true

when: is the non-exclusive sibling — every matching step runs — which is why routing has its own vocabulary instead of a mode on when:.

The critic loop

1. [[writer]] — draft the post
2. [[editor]] — review it; end your reply with APPROVED when it is ready
   loop: 3
   until: APPROVED

The marker must stand alone on its own line (case-insensitive; emphasis and trailing punctuation are ignored). “APPROVED once the citation is fixed” contains the word but is a rejection, and reading it as a pass would end the loop with the correction unmade. Notes may follow on later lines, so an approval with a list of nits still passes. When no such line appears, the previous group and this one run again, at most loop: extra cycles; a loop that exhausts its budget fails the step.

Fan-out

1. [[scout]] — list the competitor URLs, one per line
2. [[analyst]] — analyse this one site
   each: lines
   max: 10

each: lines splits what the previous step concluded — its final reply, not every turn it wrote along the way — into non-empty lines (list markers stripped). An agent that narrates while it works (“Now let me write the file…”) does not fan those sentences out. For a list that must survive exactly, have the step write a file and use each: rows of. each: items reads the array an earlier output: json step returned. each: rows of ../../storage/leads.csv runs one instance per data row, each receiving the header plus its row. Instances run in parallel and are labelled by their item in the next group’s context.

Structured handoff

1. [[extractor]] — return the rows you found
   output: json
2. [[checker]] — check this one row
   each: items
   verify: jq -e '.total > 0'

output: json is the one structured handoff in the grammar: the value reaches the next group beside the prose as <previous_step_data> — a URL step 1 found arrives at step 3 as the URL, not as a re-reading of step 1’s paragraph — and rides the run record as data. A shell verify: receives it on stdin.

Verification is where arithmetic goes

Step options are matched as text, deliberately. Anything that must be decided by a number belongs in a command that can be tested:

   verify: jq -e '.total > 0'
   verify: contains: $34
   verify: judge: quotes the real price

contains:, not-contains: and matches: test the step’s conclusion — its final reply, the same text the run’s headline is read from — so a step that narrated between tool calls and then opened its answer with BAD — … passes matches: ^BAD\b. judge: grades the whole result.

The command sees what the step’s scripts saw — its secrets, FOLDRUN_RUN_ID, FOLDRUN_AGENT, FOLDRUN_DATE, TZ — so a marker a step leaves on disk can be checked to name this run. That matters: a run’s copy-back never propagates a deletion, so a marker from an earlier run rides into every later sandbox. judge: is a toolless fast-tier grading call; the other four cost nothing.

Delegation

2. [[triager]] — decide who should take this
   delegate: enricher, emailer, writer

The agent ends its reply with agent: instruction lines, choosing only from the declared set (at most five). The picks run as a fresh group immediately after, and both the set and the picks are on the record. Choosing nobody is a normal outcome, recorded as one.

Flow frontmatter

field
name the flow’s identity
description what it is for
trigger how it starts — see below
schedule / timezone 5-field cron, IANA zone (default UTC)
at ISO 8601 instant, for trigger: once
after / on the flow to chain on, and whether on completed (default), failed or any
path the storage/ prefix to watch, for trigger: storage
url / every what to poll and how often (default 15m), for trigger: watch
signature / signing_secret github | stripe | slack | hmac, and the vault entry to check it against
model / effort defaults for every step
budget the most one run may spend, in USD
overlap skip or queue when a run is already live

Triggers

trigger starts when input
manual (default) a person clicks Run, foldrun invoke, or an API call optional task
schedule the cron matches none
once the instant in at: passes — fires once none
webhook an HTTP POST reaches the flow’s hook URL the request body
email a message arrives at the flow’s inbox URL from, to, subject, text
flow the flow in after: settles the way on: says that run’s id, status, summary, result
storage a file lands under path: the paths and who wrote them
watch the content at url: changes the new content
(composed) another flow reaches a [[flow:name]] step the previous step’s results

A flow may not chain on itself. First sightings for watch record without firing, and a once instant more than six hours gone at first sighting is recorded and never fired.

Cost and overlap

budget: 5 is checked between groups: the group that crosses it is the last one that runs, the rest are skipped as “over budget”, and the run fails saying so. It is a literal, never an expression, so the cap is readable off the file. The workspace’s monthly budget: still applies on top.

overlap: decides what a new fire does while a run of the same flow is live. skip consumes the occurrence — a cron refiring over yesterday’s long run almost never means “run two”. queue starts it but holds it until the live one finishes. Unset, runs may overlap.

Model resolution

Nearest wins: the step, then the flow, then the agent’s own frontmatter. The run trace names which level won, so “why did this run on haiku” is answerable from the run rather than by opening three files.

The consequence people miss: a flow with model: fast in its frontmatter runs every step on the fast tier, whatever each agent’s own model: says. To lift one step, put model: on that step. A file-composing step is the one to check — on the fast tier an agent asked to write a long file may write it a line per call, re-sending its whole context each time; one translator did that 92 times and cost $22 for twelve lines. The run trace shows the tier (model: haiku — flow) and the write count.

What the platform never does

It sets no clock of its own — not on a step, a script tool, an HTTP tool, a verify command, a consult, or a wait for a person. Every limit that exists is one written in a markdown file. The run’s events say which it is (timeout: 3000s or no timeout) at every step start.

A required step failing fails the flow, and the remaining groups are skipped. A workspace with one agent and no flows is normal — foldrun run <agent> runs it as a one-step flow, so there is a single execution path either way.