foldrun 2.2.0 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/docs/checks.md CHANGED
@@ -16,7 +16,7 @@ foldrun check --format sarif --out check.sarif # for GitHub code scanning
16
16
  foldrun check --rules # every rule, its level, what it guards against
17
17
  ```
18
18
 
19
- The same findings appear in three other places, from the same rules in
19
+ The same findings appear in four other places, from the same rules in
20
20
  `@foldrun/core`:
21
21
 
22
22
  - **The dashboard**: the workspace's **Checks** page lists every finding on
@@ -24,9 +24,17 @@ The same findings appear in three other places, from the same rules in
24
24
  shows a file's own findings above it.
25
25
  - **The API**: `GET /api/workspaces/<ws>/check` answers with the same
26
26
  findings as JSON (`?security=1`, `?rule=`). See [api](api).
27
+ - **A save**: saving a file in the dashboard editor checks the workspace as
28
+ it would be with that file. An error a deploy would refuse is refused
29
+ there too and nothing is written, because a saved file goes live at once;
30
+ **Save anyway** writes it. Warnings never stop a save. A History restore
31
+ is saved as picked and says when it fails today's checks. Files a deploy
32
+ does not carry, such as `state/`, are not checked. Through the API it is
33
+ `check: true` on `PUT …/source`, and every save answers with the
34
+ file's findings.
27
35
  - **A deploy**: what a deploy refuses is still refused. A credential written
28
36
  into a file and hidden text are reported as deploy warnings: the deploy
29
- goes ahead and says so.
37
+ goes ahead and says so, unless `deploy_block:` says to refuse it (below).
30
38
 
31
39
  ## Reading a finding
32
40
 
@@ -61,6 +69,51 @@ fails on a warning) or `--fail-on warn`.
61
69
  | `--rules` | list every rule (`--json` for the table) |
62
70
  | `--to <ws>` | check the copy deployed on the platform instead of this folder |
63
71
 
72
+ ## Blocking a deploy
73
+
74
+ By default the scanner's findings never stop a deploy: they are warnings,
75
+ listed with it. `deploy_block:` in `AGENTS.md` turns them into refusals:
76
+
77
+ | `deploy_block:` | refuses a deploy that carries |
78
+ |---|---|
79
+ | `none` (default) | nothing — findings are warnings |
80
+ | `secrets` | a key or password written into a file (`secret/hardcoded`) |
81
+ | `security` | any security finding: `secret/*`, `content/*`, `security/*`, `supply-chain/*`, and `flow/outward-ungated` (a step that acts outside with no check or approval) |
82
+
83
+ Set it in the account's `AGENTS.md` for every workspace, or in one
84
+ workspace's `AGENTS.md`, which wins. Settings → Defaults → **Refuse a
85
+ deploy on**, or `foldrun account set deploy_block secrets`. A refused
86
+ deploy names each finding and its rule; a `foldrun-ignore` with a reason
87
+ (below) still accepts one.
88
+
89
+ ## Online checks and the daily rescan
90
+
91
+ `foldrun check` stays offline. Two checks need the network, so they run on
92
+ the platform against the deployed copy: known vulnerabilities for exact
93
+ runtime pins ([`supply-chain/known-vulnerability`](#supply-chainknown-vulnerability))
94
+ and the tool text of MCP servers reached by URL
95
+ ([`content/mcp-tool-text`](#contentmcp-tool-text)).
96
+
97
+ - **When you ask:** Checks → **Online checks**, `foldrun check --to <ws>
98
+ --online`, or `GET /api/workspaces/<ws>/check?online=1`.
99
+ - **Every day by itself:** the platform rescans every workspace, offline and
100
+ online. The first rescan of each source records what is there. After
101
+ that, a security finding that was not there before goes to the
102
+ workspace's `notify:` as the `security` event, once. A finding that goes
103
+ away and comes back is new again.
104
+ - **What is never done:** a server started as a command is never run to
105
+ check it, and no credential is sent. A server that needs one is reported
106
+ as not checked.
107
+ - **Not checked is never clean:** OSV unreachable, a malformed answer, or a
108
+ server that did not answer is listed as not checked, and keeps what it
109
+ found last time.
110
+ - **A rescan that fails or never finishes** (the platform restarted in the
111
+ middle of it) is not tried again for a day. Findings waiting to be told
112
+ are still sent.
113
+
114
+ Online findings are warnings and never block a deploy, even with
115
+ `deploy_block: security`: a deploy does not wait on the network.
116
+
64
117
  ## Ignoring one
65
118
 
66
119
  Put a comment on the line before the finding, or on the same line, naming
@@ -236,6 +289,23 @@ last, so a compromised release reaches you the moment it ships. Pin it:
236
289
  range (`pandas>=2,<3`, `lodash@^4`) so tomorrow's run uses what was tested
237
290
  today.
238
291
 
292
+ ### supply-chain/known-vulnerability
293
+
294
+ *warn · ASI04 · online.* A `runtime: packages:` or `npm:` entry pinned to an
295
+ exact version (`requests==2.19.0`, `lodash@4.17.15`) that
296
+ [OSV.dev](https://osv.dev) lists advisories for. Upgrade to a fixed version.
297
+ Only exact pins are asked about; ranges and transitive dependencies are not
298
+ resolved.
299
+
300
+ ### content/mcp-tool-text
301
+
302
+ *warn · ASI01, ASI04 · online.* What an MCP server reached by `url:` tells
303
+ the model about its tools (names, titles, descriptions, argument
304
+ descriptions) carries hidden characters or an instruction. That text reaches
305
+ the model as guidance: a server can steer your agent through it. It is
306
+ scanned as untrusted text, so a `foldrun-ignore` or a code fence inside it
307
+ cannot hide it; a `foldrun-ignore` in your own file still accepts one.
308
+
239
309
  ### config/unknown-field
240
310
 
241
311
  *warn or info.* A frontmatter key that this kind of file does not read. There
@@ -281,6 +351,23 @@ place in `@foldrun/core` (`frontmatter-fields.ts`). The editor's completions
281
351
  come from the same list, so what the editor suggests and what `check`
282
352
  accepts cannot drift apart.
283
353
 
354
+ ### config/invalid-frontmatter
355
+
356
+ *warn.* Frontmatter that YAML cannot read. The commonest cause is a value
357
+ holding a colon followed by a space, which ends the value early:
358
+ `description: the file format (Markdown, frontmatter, ::: directives)`.
359
+ Quote it: `description: "the file format (…, ::: directives)"`. The
360
+ message gives YAML's own error and its line.
361
+
362
+ A **skill** like that still runs: foldrun reads a skill by quoting such a
363
+ value and trying again. But the dashboard editor and the source API refuse
364
+ to save it, and other tools that read skills may not load it. When even the
365
+ second read fails, the skill has no name or description and is never loaded.
366
+ For the account library's agents, tools and hooks, the runtime cannot read
367
+ the file at all. A workspace's own agent, flow, tool, eval, hook or
368
+ `AGENTS.md` with broken YAML is not this warning: a deploy refuses it, under
369
+ that file's own rule (`agent/config`, `flow/config` …).
370
+
284
371
  ### check/ignore-needs-reason
285
372
 
286
373
  *warn.* A `foldrun-ignore` with no reason after a colon. It ignores nothing
@@ -312,3 +399,15 @@ the first line, not the only one: [network](network) policy, bound
312
399
  [secrets](secrets), [hooks](hooks) and approvals hold at run time whatever
313
400
  the files say. MCP servers are never started to inspect their tools, and no
314
401
  URL is fetched.
402
+
403
+ On the platform (the Checks page, the editor, `GET …/check` and the daily
404
+ rescan) the checks read a bounded amount. In `state/` and `memory/` (the
405
+ workspace's, or one agent's), which is what agents write as they run, a file
406
+ over 5 MiB is not read, and nor is anything past 32 MiB or 5,000 files in all.
407
+ The rescan lists what it left out, and keeps what it found in those files
408
+ before. If everything else in the workspace is over 64 MiB, the workspace is
409
+ not checked at all, and the check says so (`GET …/check` answers 413).
410
+ `foldrun check` on your own machine reads everything.
411
+
412
+ Everywhere, `foldrun check` included, one rule is listed at most 100 times in
413
+ one file. The rest are counted in one more finding ("and N more like this").
package/docs/cli.md CHANGED
@@ -36,13 +36,13 @@ claude.ai or Claude Code there, nor any MCP configuration on disk.
36
36
  | `foldrun new <name>` | another workspace in this account — blank: an `AGENTS.md` and empty `agents/` and `flows/`. `--starter` adds the example researcher, writer and `publish` flow; `--from <template>` copies a template |
37
37
  | `foldrun agent new <name>` | one more agent inside a workspace, with the frontmatter the format requires — and every key explained beside it. Also `foldrun flow new <name>`, whose first step names an agent this workspace already has, and `foldrun tool new <name>`, which writes a `transport: script` folder with its program in a file beside the definition (`--transport script\|http\|mcp`, `--language javascript\|python\|bash`). In an account with more than one workspace, `--to <name>` says which; inside one, it is already known. The templates are the same ones the dashboard's New button writes, so what is scaffolded is what `foldrun check` accepts |
38
38
  | `foldrun agent new <name> --platform` | the dashboard's **New agent** on the DEPLOYED workspace (`POST …/agents { name, description, model }`): the platform writes its own template as a revision, and nothing is written in this folder. `--to <workspace>` (or the folder you stand in), `--description "…"`, `--model fast\|default\|max`. Without `--platform`, `--to` keeps naming a workspace of this account folder. Also `flow new <name> --platform` (`POST …/flows { name, pattern }`, `--pattern pipeline\|review-loop\|fan-out\|debate\|router`, default `pipeline`) and `tool new <name> --platform` (`POST …/assets { kind: "tools" }` — a `script` tool as here unless `--transport`/`--template` says otherwise; `--template` also takes a gallery entry, `--language`). `foldrun pull` brings them down |
39
- | `foldrun agent ls` | the agents deployed in a workspace — name, model, description (`GET …/agents`); `flow ls` the flows and how each fires (`GET …/flows`). `--to <workspace>`, `--json` for the list as the platform returns it |
39
+ | `foldrun agent ls` | the agents deployed in a workspace — name, model, last run, owner (its own `owner:`, else the workspace's, else the account's), description (`GET …/agents`); `flow ls` the flows and how each fires (`GET …/flows`). `--stale <days>` keeps the agents not run in that many days (never run included), bare `--stale` those past `alerts: stale_days`. `--to <workspace>`, `--json` for the list as the platform returns it |
40
40
  | `foldrun guide` | the coding-agent rules, copied from what Next.js does: a short managed block in the account's `AGENTS.md`, between `<!-- BEGIN:foldrun-agent-rules -->` markers, telling a coding agent (Claude Code, Cursor, Codex) to read `foldrun docs` before it writes an agent, flow or tool, and a `CLAUDE.md` that imports it with `@AGENTS.md`. `foldrun init` writes them, `foldrun pull` refreshes them, and `foldrun check` re-adds them when a coding agent runs it and they are missing or old — as `next dev` does. Only the block is rewritten, in place; text around it is kept. The runtime strips the block, so the agents that run in the account never see it; the rest of `AGENTS.md` stays their shared context. `--check` exits 1 when the block is missing or old (for CI), `--print` writes it to stdout |
41
41
  | `foldrun docs [page]` | these docs, from the copy that ships inside the CLI — the way Next.js ships `node_modules/next/dist/docs` — so a coding agent reads the version it is driving, offline. No page lists them; `foldrun docs flows` prints one; `--path` prints the folder. Design records and the platform operator's routes are left out |
42
42
  | `foldrun check [dir]` | validate agents, flows, tools, evals, knowledge — offline. At an account root: every workspace, then the shared library. Includes each agent's colleagues: an `agents:` or `subagents:` name that is not an agent, a sub-agent with no `description:`, a sub-agent that would hold none of its tools inside its parent's. Signed in, it also asks the platform's open `/api/version` which Chrome its runner image has, and warns when a pinned `web.browse.user_agent:` claims another major ([why](tools#how-this-agents-browser-presents-itself)); it warns about the old `web.browse.session:` key too (now `vendor_session:`). Hooks: a `hooks/<name>/` folder that is not a valid hook (`on:`, `run.mjs` beside `hook.md`, `tool:` only on tool events, `timeout:` 1 to 120), and a `hooks:` name in an agent or an AGENTS.md that is in neither `hooks/` nor the library, are errors; a `tool.call` hook on an agent whose `web.search:` (or `web.fetch:`) the model provider runs on its own servers is a warning — those calls never reach the hook. It is also the security scanner: a credential written into any file, invisible Unicode, override phrases in knowledge and skills, an agent that reads the web while holding a secret and an open way out, `curl \| sh`, an MCP server at no fixed version, a typo of a frontmatter field. Every finding names its rule (`secret/hardcoded`, `agent/config`, …); see [Checks](checks) |
43
43
  | `foldrun check --security` | only the security rules (secret, security, content, supply-chain), exiting 1 on a warning. Also `--rule <id or category>` (repeatable), `--fail-on error\|warn\|info\|never`, `--format text\|json\|sarif` (`--json`; `--out <file>`), `--changed` (only files changed against git HEAD; `--base <ref>`), `--write-baseline` (accept today's findings into `.foldrun/check-baseline.json`; `--no-baseline`), `--rules` (list every rule). A `foldrun-ignore <rule>: <reason>` comment on the line before a finding silences it; the reason is required. [Checks](checks) |
44
44
  | `foldrun extract [dir]` | move each single-file script tool — a `tools/<name>.md` with its program in a fenced block — into a folder: `tools/<name>/tool.md` with `run:` pointing at `run.js`, `run.py` or `run.sh` beside it. Frontmatter is edited as text, never re-serialised, so the diff is only what moved. A tool that already has a folder is left for you; `--dry-run` lists what it would do |
45
- | `foldrun check --to <ws>` | validate the copy that is DEPLOYED, rather than the folder in front of you — a workspace edited through the API, by an agent or by `foldrun source put`, has had no way to be checked short of spending a run. The files are fetched into a temporary folder and the ordinary check runs over them, so there is one copy of the rules; the output names the workspace and the platform it checked |
45
+ | `foldrun check --to <ws>` | validate the copy that is DEPLOYED, rather than the folder in front of you — a workspace edited through the API, by an agent or by `foldrun source put`, has had no way to be checked short of spending a run. The files are fetched into a temporary folder and the ordinary check runs over them, so there is one copy of the rules; the output names the workspace and the platform it checked. `--online` adds the network checks the platform runs: known vulnerabilities for exact runtime pins and remote MCP servers' tool text, and what could not be checked |
46
46
  | `foldrun agent run <name>` | run one agent once on a platform, with no flow file: a flow of one step. `--task "…"` is the instruction and is required. Prints the run id and how to follow it; `--wait` holds on (in 25-second pieces, as `invoke` does) and then prints the same report `foldrun report` would; `--test` makes it a [test run](runs#test-runs) |
47
47
  | `foldrun agent link <agent>` | add to an agent's team, in its own frontmatter — `--subagent <worker>` (`subagents:`, a worker it delegates to), `--consult <agent>` (`agents:`, a colleague it asks) or `--can-ask` (`ask` in `tools:`, so it may ask you mid-step). The edit is core's, the one the canvas's docks make: only the lines of that one list change, flow style stays flow style, comments and the prose stay byte for byte. A sub-agent without a `description:` needs one, because the model picks a sub-agent by it — asked for at a terminal, `--description "…"` otherwise, and written into the worker's file first. Says which flows use the agent ("used by 3 flows"), since the change reaches all of them. Prints the diff, and refuses an edit `foldrun check` would call an error; `--dry-run` writes nothing. In this folder by default (`--workspace <name>` in an account with several); `--to <workspace>` edits the deployed copy through `PATCH /api/workspaces/<ws>/agents/<name>`. In this folder it re-reads each file just before writing — after the description prompt, after `check` — and refuses, writing nothing to either file, when one changed since it was read |
48
48
  | `foldrun agent import <workspace>/<agent>` | copy an agent you already have in another workspace into this one: its `agent.md` and own `skills/`, `scripts/`, `knowledge/` — never `memory/`, `outputs/` or `state/`. Into the workspace you stand in, or `--workspace <name>`; `--as <name>` imports it under another name (and rewrites `name:`), which a name clash requires. `--to <workspace>` does it on the platform (`POST /api/workspaces/<ws>/agents/import`) and lists the tools, skills, scripts and secrets the copy uses that the workspace lacks. See [Importing an agent](workspaces#importing-an-agent-you-already-have) |
@@ -72,14 +72,22 @@ claude.ai or Claude Code there, nor any MCP configuration on disk.
72
72
  | `foldrun run <target>` | run an agent or a flow HERE, on this machine (`name`, or `flow:name`) — `--test` for a [test run](runs#test-runs). On a platform: `flow run`/`invoke` for a flow, `agent run` for one agent |
73
73
  | `foldrun eval [name]` | one eval, or all of them. `--to <workspace>` runs the DEPLOYED workspace's evals on the platform (`POST …/evals/<name>/run`), waiting for each verdict, and prints the same lines; exits 1 when a case fails |
74
74
  | `foldrun eval new <name>` | the Evals page's New, on the deployed workspace (`POST …/evals { name }`): an eval whose `agent:` is the workspace's first agent and whose case is a placeholder — edit it (`source cat`/`put`), then `foldrun eval <name> --to <workspace>` runs it. `--to <workspace>` |
75
- | `foldrun promote <run-id>` | keep a finished run as a regression case: its task and what the next run must still say go into `evals/<target>-regressions.md` (or `--eval <name>`), appended when the file exists. `--expect "contains: $34"` — repeatable, eval syntax — replaces the default judge line that holds the next run to the recorded conclusion; `--case <name>` names the case |
75
+ | `foldrun promote <run-id>` | keep a finished run as a regression case: its task and what the next run must still say go into `evals/<target>-regressions.md` (or `--eval <name>`), appended when the file exists. `--expect "contains: $34"` — repeatable, eval syntax — replaces the default judge line that holds the next run to the recorded conclusion; `--case <name>` names the case. A run that went wrong (failed, BLOCKED, BAD) takes `--should "what it should have done"` — the judge's claim — or `--expect` |
76
76
  | `foldrun probe <model>` | can this model hold a tool loop here? A live check |
77
77
  | `foldrun logs [run-id]` | recent runs in one workspace, or one run's whole event trail. `--local` reads this machine's store even when signed in; `foldrun runs --local` is the same command and prints the same thing |
78
- | `foldrun runs` | what has run lately across the whole account: when, workspace, flow, status, steps done, how long, what it cost and the run's own one-line summary. A completed run shows its verdict beside the status — `GOOD`, `BAD`, `QUIET`, or `BLOCKED` in red with a ⛔ mark. `--status failed`, `--verdict blocked`, `--since 24h`, `--to <workspace>`, `--limit <n>`. `--local` prints exactly what `logs --local` prints — this machine's runs, one workspace |
78
+ | `foldrun runs` | what has run lately across the whole account: when, workspace, flow, status, steps done, how long, the credits it was charged (its token cost in dollars until the ledger settles it) and the run's own one-line summary. A completed run shows its verdict beside the status — `GOOD`, `BAD`, `QUIET`, or `BLOCKED` in red with a ⛔ mark. `--status failed`, `--verdict blocked`, search by what happened inside (`--tool web`, `--tool-error`, `--agent`, `--model haiku`, `--min-tokens 50k`, `--min-secs 120`, or `--view <name>` for a saved one — `foldrun runs views` lists yours), `--outcome blocked,failed` (the dashboard's word; `needs-you` is `waiting`), `--since 24h`, `--to <workspace>`, `--limit <n>`. `--local` prints exactly what `logs --local` prints — this machine's runs, one workspace |
79
+ | `foldrun runs doctor <run-id>` | the run doctor: the assistant reads a failed run and the files it names, says why (platform, check, work or outside), and proposes the fix as diffs, saved on yes — the run page's **Doctor** (`--to`, `--yes`, `--dry-run`, `--json`). Charged like one assistant reply |
79
80
  | `foldrun runs rm <run-id>` | erase a run from history: its record and its archived outputs. A run still in flight is stopped first. The ledger line stays — money is not deleted because a trace was. Asks first; `--yes` with no terminal |
80
- | `foldrun report <run-id>` | one run, whole, without opening the dashboard: the header, then every step with its group, agent, attempts, the runner image it ran on (`slim · browser pod` when it browsed through the account's pod from slim), cost, how much of its input the prompt cache served (`90% cached`; nothing for a step recorded before 18 Oct 2026), how many model turns it took, duration, what happened to the browser pod under it (reconnects, `browser pod lost; re-ran on full` with its tries — `slim · lost` then `full` — or why it was not re-run), whether its `verify:` held and the first line of what it said — and for a failed step, the error and the last events before it. Then what it wrote, what it published and what is still waiting on a person. `--json` prints the run record instead |
81
+ | `foldrun report <run-id>` | one run, whole, without opening the dashboard: the header, then every step with its group, agent, attempts, the runner image it ran on (`slim · browser pod` when it browsed through the account's pod from slim), cost, how much of its input the prompt cache served (`90% cached`; nothing for a step recorded before 18 Oct 2026) and, under a step that wrote a fifth or more of its input to the cache, why (`prompt cache: 75% of input written to the cache — memory changed since step 1`), how many model turns it took, duration, what happened to the browser pod under it (reconnects, `browser pod lost; re-ran on full` with its tries — `slim · lost` then `full` — or why it was not re-run), whether its `verify:` held and the first line of what it said — and for a failed step, the error and the last events before it. Then what it wrote, what it published and what is still waiting on a person. `--json` prints the run record instead |
82
+ | `foldrun report <run-id> --compare [<run-id>]` | this run beside the flow's [last good run](runs#compare-with-the-last-good-run), or the one named: whether they ran the same files and which changed, then what differs step by step (status, new errors, instruction, model, tools, time, tokens). `--json` |
83
+ | `foldrun report <run-id> --tree` | the run as [one tree](runs#the-trace-tree): each step, each model call (model, tokens, why it stopped) and each tool call under the call that asked for it, with how long each took and `← what went wrong` on the first failure. `--json` gives the tree and the failure path |
84
+ | `foldrun report <run-id> --calls` · `foldrun report <run-id> call <call-id>` | what the run [recorded](runs#what-a-run-records): `--calls` adds every model call (model, time, tokens and cache share, why it stopped, tools) and lists the tool calls whose input and output were kept; `call <call-id>` prints one of those, secrets masked (`--json`). The header says which commit the run ran and how long it queued |
81
85
  | `foldrun report <run-id> get <agent>/<path>` | download one file the run archived — whatever that agent left in `outputs/`, copied under the run when it ended. Writes it beside you under its own name; `--file <path>` names somewhere else (`-` for stdout), `--force` overwrites |
82
86
  | `foldrun report <run-id> live` | the run page's live browser view: which agents' browsers have posted a frame, and when; `--agent <name>` saves the newest frame as a JPEG (`--file <path>`, `-` for stdout) and prints the page's address, the engine and whether the browser has closed (`GET …/runs/<id>/live`) |
87
+ | `foldrun insights` | recurring [problems](runs#insights) found in the last week of runs — a prompt mostly uncached, a big tool result sent again, a retry loop, steps near their timeout — each with why, what to do, about what it cost and the runs it was seen in. `--days <1-30>`, `--to <workspace>`, `--json` |
88
+ | `foldrun issues` | the account's [issues](runs#issues): what went wrong, grouped, open until someone resolves it — kind, desk and flow (or the platform cause and every desk it hit), how many times since when, the newest reason and what to do. `--to <workspace>` narrows it, `--all` adds resolved ones (`--status open\|acknowledged\|snoozed\|resolved\|all`), `--json` |
89
+ | `foldrun issues show\|ack\|snooze\|resolve\|reopen <id>` | one issue: `show` adds its runs and history; `ack` says you have seen it, `snooze --for 30m\|4h\|1d\|1w` (or `--until <ISO>`, or `--times <n>`: until it happens n more times) hides it for a while, `resolve` closes it until it happens again, `reopen` undoes that. `--note "…"` is kept in its history |
90
+ | `foldrun issues doctor <id>` | the run doctor on an issue's newest run, in that run's workspace — the issue card's **Doctor** button: why (platform, check, work or outside), and the fix as diffs, saved on yes (`--yes`, `--dry-run`, `--json`). A missed schedule has no run and is refused |
83
91
  | `foldrun approvals` | every gate waiting on a person, anywhere in the account: which workspace, flow and run, which step and agent, the question, how long it has been waiting and which files under `storage/` the gate previews — and, first, every question an agent is asking mid-step, with its choices. `--to <workspace>` narrows it |
84
92
  | `foldrun approve <run-id>` | release a waiting gate. Approving is outward — the step behind it publishes, sends or posts — so it prints what it is about to release and asks you to type the run id back, unless `--yes`. `--note "…"` is read by the agent as guidance, `--step <n>` decides one gate of several. `--payload <json\|text\|@file>` releases a step parked on `wait: event` with that body instead, as its event URL would — the trace names you, not "an external event" |
85
93
  | `foldrun reject <run-id>` | refuse one, failing the step and the run with it. `--note` is the reason, and it goes on the trace |
@@ -113,7 +121,7 @@ claude.ai or Claude Code there, nor any MCP configuration on disk.
113
121
  | `foldrun login <site> --url <address>` | a browser window to sign in to a site by hand; the session is stored for an agent |
114
122
  | `foldrun logout` | forget this machine's key, and revoke it if `login` made it |
115
123
  | `foldrun whoami` | who you are on the platform: account, role, which workspaces |
116
- | `foldrun team` | who is in the account (`GET /api/team`): each member's email, role (the owner marked), the workspaces they may open — a scoped key sees only the ones it shares — and when they joined. `--json`. Read-only on purpose: inviting, changing a role, removing someone and transferring the account are a signed-in person's, in Settings → Team, and the platform refuses them from a key |
124
+ | `foldrun team` | who is in the account (`GET /api/team`): each member's email, role (the owner marked), the workspaces they may open — a scoped key sees only the ones it shares — and when they joined. `--json`. Read-only on purpose: inviting, changing a role or workspace access, removing someone and transferring the account are a signed-in person's, in Settings → Team, and the platform refuses them from a key |
117
125
  | `foldrun onboarding` | the account's getting-started steps — the model key (where the account brings its own), workspace, agent, a run, a run that completes, a secret, a schedule or webhook, a teammate, billing where the plan asks for it, the CLI — each worked out from what is really there, with the next one first and where to do it (`--json`). The dashboard card is hidden per person from the dashboard |
118
126
  | `foldrun preferences` | your own settings on the platform — today the theme (`system`, `light`, `dark`), kept with you on every browser; `preferences set theme dark` changes it (`/api/me/preferences`; a key acts for whoever minted it) |
119
127
  | `foldrun api spec` | the platform's OpenAPI 3.1 document, from `GET /api/openapi.json` (no key needed) — to stdout, or `--out <file>`. Every other command sends `Foldrun-Version` (the API version this CLI was built for), an `Idempotency-Key` on each write (reused on its own retry), and waits out one `429` for `Retry-After` before saying so. See [Using the API](api-usage) |
@@ -125,7 +133,7 @@ claude.ai or Claude Code there, nor any MCP configuration on disk.
125
133
  | `foldrun runtimes` | the environments a deployed workspace's agents need — each distinct `runtime:` (an agent's own merged with its tools'), the packages in it, which agents use it, and whether the platform has built it: ready, building, or failed with the installer's own error. `foldrun deploy` prints the same thing after a deploy and waits up to two minutes for builds in progress (`--no-runtimes` skips the wait); this asks again later, and `--wait` holds on while any is building. Exits 1 when one has failed. `--to <workspace>` from anywhere |
126
134
  | `foldrun schedule` | every flow in the account whose `trigger:` is a clock: workspace, flow, the cron line, its timezone, how many steps, whether the scheduler can parse it, and the next three times it fires. The times are the point — `0 5 1-7 * 5` reads as "the first Friday" and fires eight times in twenty-eight days, because day-of-month and day-of-week are OR'd. Exits 1 if any line is unparseable, since an invalid schedule errors nowhere and simply never runs. `--to <workspace>` narrows it |
127
135
  | `foldrun schedule tick` | Settings' **Run scheduler now**: fire whatever is due this minute and print which flows went (`POST /api/schedule`). They were due anyway; this only makes them go now |
128
- | `foldrun rerun <run-id>` | the same flow again, from a step: `--from <n>` as the flow file numbers them, or `--agent <name>` for the first step that agent runs. Earlier steps are recorded as skipped. This is the debug loop from the terminal: fix the file, `deploy`, `rerun --from` the step that failed. `--wait` follows the new run like `invoke --wait`; `--to <workspace>` from anywhere. With no run id and a filter (`--status failed`, `--since 24h`, `--until 2h`, `--flow <name>`) it reruns every match from `--from <n>` (default 1), listing and asking first as a bulk `stop` does; `--dry-run` lists only |
136
+ | `foldrun rerun <run-id>` | the same flow again, from a step: `--from <n>` as the flow file numbers them, or `--agent <name>` for the first step that agent runs. `--instruction "…"` tells that step something else this once, without changing the flow file. Earlier steps are recorded as skipped. This is the debug loop from the terminal: fix the file, `deploy`, `rerun --from` the step that failed. `--wait` follows the new run like `invoke --wait`; `--to <workspace>` from anywhere. With no run id and a filter (`--status failed`, `--since 24h`, `--until 2h`, `--flow <name>`) it reruns every match from `--from <n>` (default 1), listing and asking first as a bulk `stop` does; `--dry-run` lists only |
129
137
  | `foldrun triggers` | why nothing ran. One row per flow that fired in the window: its trigger, how many fires became runs, when it last ran, and every reason for the difference with a count — a duplicate delivery, a throttled or debounced burst, a fire the platform slept through, an `overlap: skip`, a flow switched off by `disable_after:`. A switched-off flow is a non-zero exit; one successful run clears it. `--since <days>` (default 7), `--to <workspace>` |
130
138
  | `foldrun observe` | where the account fails, retries and spends: one row per workspace — runs, failed and what share, steps, cost — then the recent failures in their own words. `--to <workspace>` prints that workspace in full, as the Observe page does: per flow (runs, failed, p50/p95 duration, cost), per agent (failed, retried, skipped, p95, turns, cache hit rate), per tool (calls, errors, p95 latency, result size p50/p95 and how many results were over 10k characters, which agents use it), and start-up — each phase of a step's first seconds, its own share and when it was done, p50/p95. `--since <days>` (default 30), `--json` for the document |
131
139
  | `foldrun usage` | what the account consumed — runs, steps, tokens (with the share the prompt cache served), compute, storage, per workspace — and what it was charged over `--days <n>` (default 56) from the ledger, per workspace with the change against the period before. `--json` for both documents |
@@ -134,8 +142,8 @@ claude.ai or Claude Code there, nor any MCP configuration on disk.
134
142
  | `foldrun billing top-up <usd>` | the Payment page's top-up: Stripe's checkout page for that amount, opened in the browser (`--no-browser`, or no terminal, prints it). The credit lands when Stripe confirms the payment. `billing card` is the same for saving a card for auto top-up. Both need `billing:manage` |
135
143
  | `foldrun billing credit <usd>` | put credit on the balance by hand (`POST /api/billing { usd, note }`, `--note "…"` on the ledger line) — only on an install without Stripe, a self-hosted or dev platform; once billing is live the platform refuses it and top-ups go through `billing top-up`. The dashboard's Billing → Add credit does the same while billing is off. Needs `billing:manage` |
136
144
  | `foldrun gallery` | the tools the platform ships to every account (`web`), and for each whether this account keeps its own copy and whether that copy still matches. `pull` lays the shelf down under `~/.foldrun/gallery/<host>/` for offline runs (`foldrun run` and `foldrun eval` refresh it themselves before each run); `upgrade <tool>` replaces the account's copy with the current gallery version, the old one a revision back |
137
- | `foldrun account` | the account's own defaults — the AGENTS.md frontmatter every workspace under it inherits: `timezone`, `notify`, `budget`, `concurrency`. `foldrun account set timezone Australia/Sydney`, `set notify email you@example.com` (`--events failed,awaiting-approval,completed`), `set notify url https://…`, `set budget 60/month`, `set concurrency 4`, and `clear <key>` to unset one. `set notify` merges against what is there, because the platform replaces the whole block. `providers` lists the model providers the account's files use and the last daily key check — `--check` asks now, and a dead key is a non-zero exit. `model` is the account's own model key, what every step runs on unless a `provider:` block names another: `foldrun account model` shows it (never the key, only its last four; exit 1 when there is none and runs are refused), `model set <provider> --key <api key>` sets it — a preset name (anthropic, openrouter, openai, groq, …) or `custom --base-url https://… --format anthropic|openai`, `--key -` reads the key from stdin — and `model remove` removes it; a Claude login token is refused for any account not allowed the platform's own credential. `export` downloads everything the platform holds about the account — workspaces, runs, members and the whole ledger — as one JSON file (owner only; `--file <path>`, `--force`). Singular — `accounts` below is the list of logins on this machine |
138
- | `foldrun workspace` | one deployed workspace's own settings (`--to <workspace>`): description, timezone, budget and notify, read from its `AGENTS.md` — unset means the account's. `set timezone <tz>`, `set budget 20/week`, `set notify email <address>` / `set notify url <https://…>` (`--events`; merged with what the file says), `set description "…"`, `set name <new>` — a rename changes every webhook URL, so it asks first and names the flows whose URLs changed — and `clear <key>`. Singular — `workspaces` is the list |
145
+ | `foldrun account` | the account's own defaults — the AGENTS.md frontmatter every workspace under it inherits: `timezone`, `notify`, `budget`, `concurrency`, `deploy_block`, `network_enforce`. `foldrun account set timezone Australia/Sydney`, `set notify email you@example.com` (`--events failed,awaiting-approval,completed`), `set notify url https://…`, `set budget 60/month`, `set concurrency 4`, `set deploy_block secrets` (`none`, `secrets` or `security` — what the scanner's findings do to a deploy), `set network_enforce on` (every workspace refuses a site off an agent's `network:` list; `off` reports only), `pause` (`--reason "…"`, `--stop` to also stop every run going now — asks, or `--yes`) and `resume`: the kill switch, no new run starts in any workspace while paused, `telemetry` — where each finished run goes as an OpenTelemetry trace: bare shows it, `set <url> --header authorization='${OTEL_TOKEN}'` (repeatable; a value is a vault secret's name), `clear`, `test` (one made-up run, and what the collector answered; exit 1 when not delivered), and `clear <key>` to unset one. `set notify` merges against what is there, because the platform replaces the whole block. `providers` lists the model providers the account's files use and the last daily key check — `--check` asks now, and a dead key is a non-zero exit. `model` is the account's own model key, what every step runs on unless a `provider:` block names another: `foldrun account model` shows it (never the key, only its last four; exit 1 when there is none and runs are refused), `model set <provider> --key <api key>` sets it — a preset name (anthropic, openrouter, openai, groq, …) or `custom --base-url https://… --format anthropic|openai`, `--key -` reads the key from stdin — and `model remove` removes it; a Claude login token is refused for any account not allowed the platform's own credential. `export` downloads everything the platform holds about the account — workspaces, runs, members and the whole ledger — as one JSON file (owner only; `--file <path>`, `--force`). Singular — `accounts` below is the list of logins on this machine |
146
+ | `foldrun workspace` | one deployed workspace's own settings (`--to <workspace>`): description, timezone, budget and notify, read from its `AGENTS.md` — unset means the account's. `set timezone <tz>`, `set budget 20/week`, `set notify email <address>` / `set notify url <https://…>` (`--events`; merged with what the file says), `set network_enforce on` (refuse a site off an agent's `network:` list here; `off` or `clear` goes back to the account's), `pause` (`--reason`, `--stop`) and `resume` — this workspace's kill switch, `set description "…"`, `set name <new>` — a rename changes every webhook URL, so it asks first and names the flows whose URLs changed — and `clear <key>`. Singular — `workspaces` is the list |
139
147
  | `foldrun workspace vocabulary` | what the dashboard's editor completes as you type in a workspace (`--to`): agents, flows, skills, tools, secrets (names only), scripts, document types and linkable documents (`GET …/vocabulary`; `--json`) |
140
148
  | `foldrun notify test` | send one test notification to whatever the workspace's `notify:` resolves to, and print what happened — the provider's own words when it failed ("domain not verified"). Exits 1 when it did not go. `--to <workspace>` |
141
149
  | `foldrun webhooks deliveries` | the `notify:` webhooks a workspace sent, newest first: event, status (`delivered`, `retrying`, `failed`, `skipped`), attempts, the last answer and the delivery id — and a switched-off endpoint first, with the way back. `--failed` for the ones that gave up, `--status <s>`, `--event <e>`, `--limit`, `--offset`, `--json`. `--to <workspace>` |
@@ -144,7 +152,7 @@ claude.ai or Claude Code there, nor any MCP configuration on disk.
144
152
  | `foldrun notifications set <category> on\|off` | turn one category off, or back on — account-wide, or for one workspace with `--to <workspace>` (`run-alerts`, `approvals`). A required category is refused with the reason |
145
153
  | `foldrun flags` | the feature flags for this account: each one `on` or `off` and why — `account` (set for this account), `global` (set for every account), `rollout` (a share of accounts, with its percentage) or `default` — and what it does. Read-only: only the platform's super admin changes a flag, in the console. `--json` for the API's answer as it came. See [Feature flags](feature-flags.md) |
146
154
  | `foldrun history [path]` | every change to a deployed workspace, newest first: revision id, when, who, the message and the files. A path narrows it to one file; `--id <revision>` prints that revision as diffs; `--limit <n>`; `--to <workspace>` |
147
- | `foldrun history restore <path> --id <revision>` | the History drawer's **Restore this version**: the file is rewritten with that revision's text as a new revision, `restored <rev> from history`, so the restore is itself undone the same way. Shows the diff against the file now and asks (`--yes`, `--dry-run`); sent with `ifMatch`, so a file that changed since is refused (`--to`) |
155
+ | `foldrun history restore <path> --id <revision>` | the History drawer's **Restore this version**: the file is rewritten with that revision's text as a new revision, `restored <rev> from history`, so the restore is itself undone the same way. Shows the diff against the file now and asks (`--yes`, `--dry-run`); sent with `ifMatch`, so a file that changed since is refused. A version that does not pass today's checks is still restored, and the errors are printed (`--to`) |
148
156
  | `foldrun restore <workspace> --to <revision\|time\|age>` | put a workspace's source back as it was then — `--to` is a revision id (`r-…`, from `foldrun history`), the commit a deploy named, a date/time (the last revision at or before it) or an age (`90m`, `12h`, `3d`, `2w`). Always shows the change first (`--dry-run`: the full diff and nothing else); the restore asks for the workspace's name typed back (`--yes` with no terminal) and sends the removals it showed, so a file added in between refuses it. A new revision, so it prints the `foldrun restore … --to <from>` that undoes it. Runs, `state/`, storage and secrets are not touched. See [Backups and restore](backups-and-restore.md) |
149
157
  | `foldrun backups` | how the account is backed up: the schedule, how many are kept, the last snapshot (verified? copied off the server? encrypted?), the snapshots that hold the account, each marked encrypted or not, each workspace's last change, and earlier restore requests (`--limit`, `--json`). `backups request --what runs\|state\|storage\|everything --at "<when>" [--to <workspace>] [--backup <id>] [--note "…"]` asks the platform team for a restore from a snapshot — recorded and emailed; nothing is restored until they do it. `backups requests` lists every request this account has made, and where each stands (`GET /api/account/backups/requests`, `--json`) |
150
158
  | `foldrun accounts` | every account signed in on this machine, and which one a bare command acts as. `foldrun profiles` is the same command |
package/docs/evals.md CHANGED
@@ -71,10 +71,15 @@ flow is three hours and the full cost, on every deploy.
71
71
 
72
72
  ## Promoting a run
73
73
 
74
- A run that went wrong is a case you already have — the task is on record, and
75
- so is the answer. **Promote to eval** on the run page writes it into `evals/`
76
- with the task filled in, so the fix has a test before it has a diff. A run that
77
- was itself an eval case is refused: promote the run it was checking.
74
+ A run is a case you already have — the task is on record. A run that worked
75
+ is kept with **Save as eval** on its page: its task, and a judge that holds the
76
+ next run to the same conclusion. A run that went wrong (failed, BLOCKED or
77
+ BAD) is kept with **Turn into a test**: you say what it should have done, and
78
+ that is the judge's claim — its own answer is not the one to keep. Either way
79
+ the case goes into `evals/<flow or agent>-regressions.md`, so the fix has a
80
+ test before it has a diff. `foldrun promote <run-id>` does the same (`--should
81
+ "…"` for one that went wrong, `--expect` for assertion lines). A run that was
82
+ itself an eval case is refused: promote the run it was checking.
78
83
 
79
84
  ## The loop
80
85
 
package/docs/flows.md CHANGED
@@ -26,7 +26,10 @@ timezone: Australia/Sydney
26
26
  ```
27
27
 
28
28
  - **Group** — steps sharing a number run **in parallel**; the next group starts
29
- when all of them finish and receives every result, labelled per agent. A
29
+ when all of them finish and receives every result, labelled per agent. Steps
30
+ of one group can talk while they run when their agents hold `tools: [peers]`
31
+ (a writer asks the image-maker beside it for the file name instead of
32
+ guessing — [agents](agents.md#asking-a-colleague-beside-you)). A
30
33
  gate (`!`, `ask:`, `wait: event`) holds its whole group, so a gated step
31
34
  numbered the same as another starts beside it on approval, not after it —
32
35
  `check` warns about that, and about a gap in the numbers (3, 3, 4, 5, 5!, 7
@@ -318,6 +321,7 @@ normal outcome, recorded as one.
318
321
  |---|---|
319
322
  | `name` | the flow's identity |
320
323
  | `description` | what it is for |
324
+ | `owner` | the person who answers for it — an email or a name; unset, the workspace's `owner:`, then the account's |
321
325
  | `trigger` | how it starts — see below |
322
326
  | `schedule` / `timezone` | 5-field cron, and the zone it fires in — which is also the calendar every step in this flow works to (see below) |
323
327
  | `at` | ISO 8601 instant, for `trigger: once` |
@@ -328,6 +332,7 @@ normal outcome, recorded as one.
328
332
  | `model` / `effort` | defaults for every step |
329
333
  | `budget` | the most one run may spend, in USD — a number, or `unlimited`; unset is no limit |
330
334
  | `overlap` | `skip` or `queue` when a run is already live |
335
+ | `issues` | `stay-open` (the default: a person resolves this flow's [issues](runs#issues)) or `auto-close` (a run that goes fine closes them) |
331
336
  | `priority` | `high`, `normal` or `low` — where this flow's runs stand in the queue against everyone else's |
332
337
  | `idempotency` | the header (or `body:<field>`) that identifies a delivery, so the same one twice is one run |
333
338
  | `throttle` | the least time between two runs of this flow |
package/docs/network.md CHANGED
@@ -134,19 +134,34 @@ egress: CONNECT pypi.org:443 × 3 (840ms total)
134
134
  network: example.com is not a site this step may reach (network: trusted). Add it to the agent's network: list — network: [example.com] — or set network: open. Refused.
135
135
  ```
136
136
 
137
- ### Report-only, while an install learns
137
+ ### Report-only, then refuse
138
138
 
139
- An install enforces the list when its worker has `FOLDRUN_NETWORK_ENFORCE=1`.
140
- Until then the proxy only **reports**: a site off the list is allowed, and
141
- the trace names it once per step —
139
+ The list is refused when any one of three says so:
140
+
141
+ - `network_enforce: true` in the account's `AGENTS.md` — every workspace;
142
+ - `network_enforce: true` in a workspace's `AGENTS.md` — that workspace;
143
+ - `FOLDRUN_NETWORK_ENFORCE=1` on the install's worker — every account.
144
+
145
+ Each only switches it on. A workspace cannot turn off what its account
146
+ turned on, and nothing in an account turns off the install's. Settings →
147
+ Defaults (account) and a workspace's Settings have the switch, and so does
148
+ `foldrun account set network_enforce on` / `foldrun workspace set
149
+ network_enforce on --to <workspace>`.
150
+
151
+ Until one of them says so, the proxy only **reports**: a site off the list
152
+ is allowed, and the trace names it once per step —
142
153
 
143
154
  ```
144
- network: api.example.com would be refused (network: trusted) — allowed, because this install only reports. Add it to the agent's network: list to keep it reachable.
155
+ network: api.example.com would be refused (network: trusted) — allowed, because nothing here asks to refuse yet. Add it to the agent's network: list to keep it reachable; network_enforce: true in AGENTS.md refuses it.
145
156
  ```
146
157
 
147
158
  — so the sites your agents really use can be added before anything is
148
- refused. The trace line `network: … — report only on this install` says
149
- which mode a step ran in.
159
+ refused. Read a week of traces for `would be refused`, add what belongs,
160
+ then switch it on. The trace line `network: … — report only` says which
161
+ mode a step ran in.
162
+
163
+ On a cluster, a refused step's pod also loses its direct route to the
164
+ world: the network policy gives it the proxy and DNS only.
150
165
 
151
166
  ### Where it is not held
152
167
 
@@ -82,6 +82,7 @@ which is the class of failure that otherwise reaches nobody:
82
82
  | `quarantined` | a flow's `disable_after:` tripped and it has stopped firing |
83
83
  | `sla` | a run has passed its flow's `sla:` and is still going |
84
84
  | `budget` | a workspace has spent 80%, then 95%, of its `budget:` for the window — today, this week or this month |
85
+ | `security` | the daily rescan found a security finding that was not there before — see [Checks](checks#online-checks-and-the-daily-rescan) |
85
86
 
86
87
  Each is sent once per occurrence, not once per check: an alarm that repeats
87
88
  every thirty seconds gets muted, and a muted alarm is the same as no alarm.
@@ -94,6 +95,59 @@ and nobody thinks to opt in to hearing about something that has not
94
95
  happened. Naming one explicitly in `events:` also turns it on. To have none
95
96
  of them, leave `failed` out of the list.
96
97
 
98
+ ## Thresholds: `alerts:`
99
+
100
+ A failure is told when it happens. A workspace can also ask to hear when a
101
+ pattern crosses a line, with `alerts:` in its `AGENTS.md` (or the account's):
102
+
103
+ ```yaml
104
+ alerts:
105
+ failure_rate: 30% # of the runs that finished in the last 24 hours…
106
+ min_runs: 5 # …once at least this many have (default 5)
107
+ credits_day: 2000 # credits charged in the last 24 hours
108
+ run_minutes: 45 # a run still going after this long
109
+ stale_days: 30 # an agent that has not run in this many days
110
+ cost_spike: 3 # a run that cost 3× its flow's average over the week
111
+ runs_per_hour: 20 # one flow started this many times in an hour
112
+ new_host: true # an agent reached a site it never reached before
113
+ ```
114
+
115
+ Each comes to `notify:` as event `alert` — like the three above it follows
116
+ `failed`, or name `alert` in `events:` — at most once a day per rule, and once
117
+ per slow run: "⚠ blog-desk: 6 of its last 15 runs failed", "⚠ blog-desk was
118
+ charged 2,340 credits in a day", "⚠ blog-desk: daily has been running 52
119
+ minutes". Test runs are left out.
120
+
121
+ `stale_days` is about the agents rather than the runs: an agent whose latest
122
+ step started more than that many days ago — or that has never run and whose
123
+ file has not changed in that long — is told once per idle spell, and again
124
+ weekly while it stays idle: "blog-desk: agent fact-checker has not run in 45
125
+ days". An agent nobody runs still holds its tools and secrets; this is the
126
+ nudge to retire it. The agent list marks it *not run in 30+ days*, and
127
+ `foldrun agent ls --stale` lists them. Counted from the runs the install
128
+ keeps, so an agent whose last run was pruned reads as never run.
129
+
130
+ Three watch for behaviour that is out of character rather than broken:
131
+
132
+ - `cost_spike` — a run that finished in the last day and cost at least that
133
+ many times the average of its flow's earlier runs this week (it needs
134
+ three of them first). Once per run: "blog-desk: a daily run cost 4.2× its
135
+ usual".
136
+ - `runs_per_hour` — one flow started that many times in the last hour: a
137
+ loop, a webhook sender retrying, a schedule written wrong. At most once a
138
+ day per flow. `throttle:` caps it for good; Settings → Pause stops it now.
139
+ - `new_host` — after each run, the sites every agent reached (from the
140
+ egress lines on its trail) are compared with what it reached before, and a
141
+ new one is told: "blog-desk: agent researcher reached a site it never
142
+ reached before — files.example.net". The first sign of a prompt injection,
143
+ a changed tool or a moved dependency. An agent's first run only records
144
+ its sites. Steps whose sandbox has no egress proxy record none.
145
+
146
+ To stop a loop rather than hear about it, see `limits: repeat` in
147
+ [Tools](tools#limits--how-many-calls-a-step-may-make). `foldrun check` and a deploy refuse an
148
+ `alerts:` they cannot read (a share that is not one, an unknown key). They are
149
+ listed with the other deliveries below.
150
+
97
151
  ## Proving it arrives
98
152
 
99
153
  A notification path that has never been tested is not a notification path.