foldrun 2.2.0 → 2.4.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/bin/foldrun.mjs +31 -11
- package/docs/agents.md +76 -2
- package/docs/api.md +23 -13
- package/docs/assistant.md +32 -2
- package/docs/budgets-and-billing.md +47 -6
- package/docs/checks.md +118 -4
- package/docs/cli.md +20 -11
- package/docs/evals.md +11 -6
- package/docs/flows.md +16 -2
- package/docs/four-jobs.md +95 -0
- package/docs/network.md +22 -7
- package/docs/notifications.md +59 -0
- package/docs/runs.md +196 -4
- package/docs/security.md +3 -2
- package/docs/tools.md +3 -1
- package/docs/workspaces.md +47 -0
- package/package.json +3 -3
- package/src/commands.mjs +758 -28
- package/src/guide.mjs +1 -1
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
|
|
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 → Governance → **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,38 @@ 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/conflict
|
|
355
|
+
|
|
356
|
+
*error.* A field written twice: at the top of the frontmatter and inside its
|
|
357
|
+
job block (`limits:` and `guardrail: {limits: …}`). Only the top one would be
|
|
358
|
+
read, so the file is refused until one is removed. See
|
|
359
|
+
[The four jobs](four-jobs).
|
|
360
|
+
|
|
361
|
+
### config/misplaced
|
|
362
|
+
|
|
363
|
+
*error.* A setting inside the wrong job's block — `network:` under
|
|
364
|
+
`governance:` — is not read, so a protection you wrote would not apply; the
|
|
365
|
+
file is refused until it moves. The message names the block it belongs in, or
|
|
366
|
+
says to write it at the top when it does none of the four jobs. A key foldrun
|
|
367
|
+
does not read at all, inside a block, is only a note.
|
|
368
|
+
|
|
369
|
+
### config/invalid-frontmatter
|
|
370
|
+
|
|
371
|
+
*warn.* Frontmatter that YAML cannot read. The commonest cause is a value
|
|
372
|
+
holding a colon followed by a space, which ends the value early:
|
|
373
|
+
`description: the file format (Markdown, frontmatter, ::: directives)`.
|
|
374
|
+
Quote it: `description: "the file format (…, ::: directives)"`. The
|
|
375
|
+
message gives YAML's own error and its line.
|
|
376
|
+
|
|
377
|
+
A **skill** like that still runs: foldrun reads a skill by quoting such a
|
|
378
|
+
value and trying again. But the dashboard editor and the source API refuse
|
|
379
|
+
to save it, and other tools that read skills may not load it. When even the
|
|
380
|
+
second read fails, the skill has no name or description and is never loaded.
|
|
381
|
+
For the account library's agents, tools and hooks, the runtime cannot read
|
|
382
|
+
the file at all. A workspace's own agent, flow, tool, eval, hook or
|
|
383
|
+
`AGENTS.md` with broken YAML is not this warning: a deploy refuses it, under
|
|
384
|
+
that file's own rule (`agent/config`, `flow/config` …).
|
|
385
|
+
|
|
284
386
|
### check/ignore-needs-reason
|
|
285
387
|
|
|
286
388
|
*warn.* A `foldrun-ignore` with no reason after a colon. It ignores nothing
|
|
@@ -294,10 +396,10 @@ filtered, ignored and baselined. Each problem keeps its own level.
|
|
|
294
396
|
| Rule | Covers |
|
|
295
397
|
|---|---|
|
|
296
398
|
| `agent/config` | frontmatter that cannot be read; a tool, skill, colleague, sub-agent or hook that does not exist; retired tool names |
|
|
297
|
-
| `flow/config` | a step naming nothing, an option the parser cannot read, `verify:` written as prose, an arrangement that cannot have what its instruction asks for |
|
|
399
|
+
| `flow/config` | a step naming nothing, an option the parser cannot read, `verify:` written as prose, a `verify: matches:` that is not a valid JavaScript regular expression, an arrangement that cannot have what its instruction asks for |
|
|
298
400
|
| `tool/config` | a `run:` program that is missing, an import nothing declares, a leftover `type:` |
|
|
299
401
|
| `skill/config` | a `SKILL.md` without a usable name or description |
|
|
300
|
-
| `eval/config` | an eval that names nothing,
|
|
402
|
+
| `eval/config` | an eval that names nothing, has no cases, or has a `matches:` that is not a valid JavaScript regular expression |
|
|
301
403
|
| `hook/config` | a hook folder that is not a valid hook; a `hooks:` name that resolves to nothing |
|
|
302
404
|
| `knowledge/okf` | a knowledge or memory document that is not a conformant bundle entry, an unreadable date, a stale document |
|
|
303
405
|
| `workspace/config` | an `AGENTS.md` setting that cannot be read; a path a deploy refuses |
|
|
@@ -312,3 +414,15 @@ the first line, not the only one: [network](network) policy, bound
|
|
|
312
414
|
[secrets](secrets), [hooks](hooks) and approvals hold at run time whatever
|
|
313
415
|
the files say. MCP servers are never started to inspect their tools, and no
|
|
314
416
|
URL is fetched.
|
|
417
|
+
|
|
418
|
+
On the platform (the Checks page, the editor, `GET …/check` and the daily
|
|
419
|
+
rescan) the checks read a bounded amount. In `state/` and `memory/` (the
|
|
420
|
+
workspace's, or one agent's), which is what agents write as they run, a file
|
|
421
|
+
over 5 MiB is not read, and nor is anything past 32 MiB or 5,000 files in all.
|
|
422
|
+
The rescan lists what it left out, and keeps what it found in those files
|
|
423
|
+
before. If everything else in the workspace is over 64 MiB, the workspace is
|
|
424
|
+
not checked at all, and the check says so (`GET …/check` answers 413).
|
|
425
|
+
`foldrun check` on your own machine reads everything.
|
|
426
|
+
|
|
427
|
+
Everywhere, `foldrun check` included, one rule is listed at most 100 times in
|
|
428
|
+
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) |
|
|
@@ -70,16 +70,24 @@ claude.ai or Claude Code there, nor any MCP configuration on disk.
|
|
|
70
70
|
| `foldrun hook test <name>` | run one hook HERE on one event, exactly as a step runs it (the event on stdin, a stripped environment, its `timeout:`), and print its answer — decision, reason, a rewritten input, context, reply or note — and how long it took. `--event '<json>'` or `--event @file`; without it, a sample event for the hook's `on:`. A deny is an answer (exit 0); a hook that crashes, times out or answers in the wrong shape is a failure (exit 1) — which, on a gate, refuses the call. `--to <workspace>` runs the DEPLOYED hook on the platform instead (`POST …/hooks/<name>/test { event }`) |
|
|
71
71
|
| `foldrun hook rm <name>` | delete a hook folder (`--library` for the account's). Says which agents and AGENTS.md files still turn it on — their steps are refused until it goes from their `hooks:` — and asks; `--yes` on a pipe |
|
|
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
|
-
| `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 |
|
|
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` with `async: true`, then polling `GET …/run?run=<id>` — an eval longer than a proxy's timeout still gets its own verdict), waiting for each (`--timeout <s>`, default 900; past it, the run id to read it with). `--run <id>` prints that run's verdict and runs nothing — a run that was lost or is unknown exits 1; `--last` prints each eval's latest result and runs nothing, 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,
|
|
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,9 @@ 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
|
|
138
|
-
| `foldrun
|
|
145
|
+
| `foldrun jobs` | the four jobs — governance, guardrail, observability, evaluation — and every `AGENTS.md` field of each, with what is set (`GET /api/account/jobs`). `--to <workspace>` shows the workspace's value beside the account's and how the two combine; `--json` the document. `account set <job>.<field>` and `workspace set <job>.<field>` refuse a field that scope cannot set and name the `AGENTS.md` to write it in. See [The four jobs](four-jobs) |
|
|
146
|
+
| `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), `set owner <email or name>`, `set alerts <threshold> <value>` (merged into what is set; `off` removes one), `set judge model|effort <value>`. Any setting may be named by its job — `set governance.deploy_block secrets`, `set observability.alerts stale_days 30` — and one named under the wrong job is refused with the right one, `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 |
|
|
147
|
+
| `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), `set owner`, `set alerts <threshold> <value>`, `set judge model|effort <value>`, and `<job>.<field>` as on `account`, `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
148
|
| `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
149
|
| `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
150
|
| `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 +153,7 @@ claude.ai or Claude Code there, nor any MCP configuration on disk.
|
|
|
144
153
|
| `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
154
|
| `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
155
|
| `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`) |
|
|
156
|
+
| `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
157
|
| `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
158
|
| `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
159
|
| `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
|
@@ -33,7 +33,7 @@ satisfy. The heading is the case's name, so make it the thing being checked.
|
|
|
33
33
|
|---|---|
|
|
34
34
|
| `name` | the eval's identity |
|
|
35
35
|
| `agent` **or** `flow` | what runs. A flow eval runs the whole flow per case |
|
|
36
|
-
| `model` | the **judge's** tier, not the subject's — cases run the agent on its own model |
|
|
36
|
+
| `model` | the **judge's** tier, not the subject's — cases run the agent on its own model. Unset, the workspace's `judge:` in `AGENTS.md` (see [The four jobs](four-jobs#judge--the-evaluation-default)), else the fast tier |
|
|
37
37
|
| `effort` | the judge's effort. PASS/FAIL against one sentence is not deep work |
|
|
38
38
|
| `trigger` | `deploy` (default) or `manual` |
|
|
39
39
|
| `inputs` | `true` makes the file a flow's saved inputs, not a test: its cases are named tasks for [Run with…](flows#run-with-saved-inputs) and `foldrun invoke --inputs`, and nothing runs them as an eval |
|
|
@@ -45,7 +45,7 @@ satisfy. The heading is the case's name, so make it the thing being checked.
|
|
|
45
45
|
|---|---|
|
|
46
46
|
| `contains:` | the text must appear |
|
|
47
47
|
| `not-contains:` | the text must not appear |
|
|
48
|
-
| `matches:` | a regular expression over the reply |
|
|
48
|
+
| `matches:` | a JavaScript regular expression over the reply, case-insensitive. No inline flags such as `(?m)`: write `(^\|\n)` for the start of a line. `foldrun check` reports one that does not compile |
|
|
49
49
|
| `file:` | the path exists and is non-empty |
|
|
50
50
|
| `run:` | a shell command that must exit 0 |
|
|
51
51
|
| `judge:` | a model grades the reply against this sentence |
|
|
@@ -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
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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.
|
|
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
|
|
@@ -288,7 +291,7 @@ decided by a number belongs in a command that can be tested:
|
|
|
288
291
|
verify: judge: quotes the real price
|
|
289
292
|
```
|
|
290
293
|
|
|
291
|
-
`contains:`, `not-contains:` and `matches:` test the step's **conclusion** —
|
|
294
|
+
`contains:`, `not-contains:` and `matches:` (a JavaScript regular expression, case-insensitive; no inline `(?m)`, write `(^|\n)` for a line start) test the step's **conclusion** —
|
|
292
295
|
its final reply, the same text the run's headline is read from — so a step
|
|
293
296
|
that narrated between tool calls and then opened its answer with `BAD — …`
|
|
294
297
|
passes `matches: ^BAD\b`. `judge:` grades the same conclusion as "the reply", with everything the step wrote before it shown alongside as context, so a claim about the work can still be checked.
|
|
@@ -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 |
|
|
@@ -487,6 +492,15 @@ before `low`. It adds to the account's lane, so a high flow on a low account
|
|
|
487
492
|
is normal. The account's `concurrency:` still bounds how many of its runs are
|
|
488
493
|
live at once.
|
|
489
494
|
|
|
495
|
+
## Grouped by job
|
|
496
|
+
|
|
497
|
+
A flow's frontmatter may group its fields by the job they do:
|
|
498
|
+
`governance:` (`owner`, `approvers`, `approve_within`, `budget`, `priority`),
|
|
499
|
+
`guardrail:` (`throttle`, `debounce`, `overlap`, `disable_after`, `live`) and
|
|
500
|
+
`observability:` (`issues`, `sla`). It reads exactly as the same fields at
|
|
501
|
+
the top; a field written both ways is refused. Step options stay under their
|
|
502
|
+
step. See [The four jobs](four-jobs).
|
|
503
|
+
|
|
490
504
|
## Model resolution
|
|
491
505
|
|
|
492
506
|
Nearest wins: the step, then the flow, then the agent's own frontmatter. The
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# The four jobs
|
|
2
|
+
|
|
3
|
+
Running agents is four jobs, and every setting that controls them does one:
|
|
4
|
+
|
|
5
|
+
| Job | What it decides |
|
|
6
|
+
|---|---|
|
|
7
|
+
| **Governance** | who answers for it, and what it may spend, decide and ship |
|
|
8
|
+
| **Guardrail** | what it may reach and call, and what stops it mid-run |
|
|
9
|
+
| **Observability** | what is recorded, and who is told |
|
|
10
|
+
| **Evaluation** | how its work is judged |
|
|
11
|
+
|
|
12
|
+
These are the same settings foldrun has always had. You can write each one at
|
|
13
|
+
the top of the file, as before, or group them under the job they do:
|
|
14
|
+
|
|
15
|
+
```yaml
|
|
16
|
+
---
|
|
17
|
+
name: blog-desk
|
|
18
|
+
governance:
|
|
19
|
+
owner: ops@acme.io
|
|
20
|
+
budget: 60/month
|
|
21
|
+
deploy_block: secrets
|
|
22
|
+
guardrail:
|
|
23
|
+
limits: {calls: 300, repeat: 5}
|
|
24
|
+
network: trusted
|
|
25
|
+
observability:
|
|
26
|
+
notify: {email: ops@acme.io, events: [failed]}
|
|
27
|
+
alerts: {failure_rate: 30%, new_host: true}
|
|
28
|
+
evaluation:
|
|
29
|
+
judge: {model: fast}
|
|
30
|
+
---
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
reads exactly as `owner:`, `budget:`, `limits:` and the rest written at the
|
|
34
|
+
top. Either way works, and the two can be mixed in one file.
|
|
35
|
+
|
|
36
|
+
## Which field belongs to which job
|
|
37
|
+
|
|
38
|
+
| File | `governance:` | `guardrail:` | `observability:` | `evaluation:` |
|
|
39
|
+
|---|---|---|---|---|
|
|
40
|
+
| `AGENTS.md` | `owner`, `budget`, `priority`, `concurrency`, `deploy_block` | `limits`, `network`, `network_enforce`, `hooks` | `alerts`, `telemetry`, `notify`, `capture` | `judge` |
|
|
41
|
+
| an agent | `owner`, `budget` | `limits`, `network`, `hooks`, `disallowedTools`, `permissionMode`, `secrets` | — | — |
|
|
42
|
+
| a flow | `owner`, `approvers`, `approve_within`, `budget`, `priority` | `throttle`, `debounce`, `overlap`, `disable_after`, `live` | `issues`, `sla` | — |
|
|
43
|
+
|
|
44
|
+
A field that does none of the four, such as `name`, `model`, `tools`,
|
|
45
|
+
`trigger` or `schedule`, is written at the top. Step options in a flow, such
|
|
46
|
+
as `verify:` and `limits:` under a step, stay where they are.
|
|
47
|
+
|
|
48
|
+
## The rules
|
|
49
|
+
|
|
50
|
+
- **One place, not both.** A field written at the top *and* inside its block
|
|
51
|
+
is refused: `foldrun check` and a deploy stop on
|
|
52
|
+
[`config/conflict`](checks#config-conflict) until one is removed.
|
|
53
|
+
- **The right block.** A setting inside another job's block is not read —
|
|
54
|
+
a guardrail under `governance:` would guard nothing — so check refuses it
|
|
55
|
+
and says which block it belongs in
|
|
56
|
+
([`config/misplaced`](checks#config-misplaced)).
|
|
57
|
+
- **Editing keeps the form.** Settings, the CLI and the flow canvas edit a
|
|
58
|
+
field where it is written, inside its block or at the top, and add a new
|
|
59
|
+
one at the top. A block written on one line (`guardrail: {network: [a.com]}`)
|
|
60
|
+
is not rewritten for you: the edit is refused (`409`) and asks for the block
|
|
61
|
+
one key per line.
|
|
62
|
+
- **Only the three settings files.** Blocks are read in an agent's
|
|
63
|
+
`agent.md`, a flow and `AGENTS.md`. In knowledge, memory, skills and every
|
|
64
|
+
other document, `governance:` and the rest are that document's own words
|
|
65
|
+
and are left as written.
|
|
66
|
+
- **The cascade is unchanged.** The account's `AGENTS.md`, then the
|
|
67
|
+
workspace's, then the agent, then the step: a block at one level and a flat
|
|
68
|
+
key at another combine exactly as two flat keys would.
|
|
69
|
+
|
|
70
|
+
## `judge:` — the evaluation default
|
|
71
|
+
|
|
72
|
+
`judge: {model, effort}` in `AGENTS.md` is the model, and how hard it
|
|
73
|
+
thinks, for every grader in the workspace: each eval's `judge:` checks and
|
|
74
|
+
every flow step's `verify: judge:`. An eval that names its own `model:` keeps
|
|
75
|
+
it. Unset, judges run on the fast tier. Field by field, the workspace's wins:
|
|
76
|
+
a workspace that sets only `effort` keeps the account's `model`.
|
|
77
|
+
|
|
78
|
+
## Where each job shows
|
|
79
|
+
|
|
80
|
+
| Job | In the dashboard | In the CLI |
|
|
81
|
+
|---|---|---|
|
|
82
|
+
| Governance | Settings → Governance: owner, budget, deploy rules, pause, team, API | `foldrun jobs`, `foldrun account set governance.<field>` |
|
|
83
|
+
| Guardrail | Settings → Guardrail: network enforcement; each agent's page | `foldrun check --security`, `foldrun agent network` |
|
|
84
|
+
| Observability | Settings → Observability: notify, alerts, telemetry; Observe, Issues | `foldrun issues`, `foldrun account telemetry` |
|
|
85
|
+
| Evaluation | Settings → Evaluation: the judge; the Evals page | `foldrun eval --to <workspace>` |
|
|
86
|
+
|
|
87
|
+
`foldrun jobs` prints all four; with `--to <workspace>`, each field's value
|
|
88
|
+
there, the account's, and how the two combine. They do not all simply
|
|
89
|
+
override: `network_enforce` is on when either turns it on, a workspace's
|
|
90
|
+
`budget` and the account's both apply, `hooks` run from both, `network`
|
|
91
|
+
takes its level from the nearest setting and its sites from every level, `limits` merge key by key, `judge`
|
|
92
|
+
field by field, and `priority` and `concurrency` are read from the account
|
|
93
|
+
alone. The rest — `owner`, `notify`, `alerts`, `telemetry` and others — the
|
|
94
|
+
workspace's replaces the account's whole. Fields the CLI cannot set (`limits`,
|
|
95
|
+
`network`, `hooks`, `capture`, …) are written in `AGENTS.md`.
|
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,
|
|
137
|
+
### Report-only, then refuse
|
|
138
138
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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
|
+
Guardrail, on the account's and a workspace's Settings, has 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
|
|
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.
|
|
149
|
-
|
|
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
|
|
package/docs/notifications.md
CHANGED
|
@@ -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,64 @@ 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
|
+
A workspace's `alerts:` replaces the account's whole: once it has its own,
|
|
116
|
+
the account's thresholds no longer apply there, and `alerts: {}` means none
|
|
117
|
+
for that workspace (`new_host: false` turns off one the account has on).
|
|
118
|
+
Without an `alerts:` of its own it takes the account's.
|
|
119
|
+
|
|
120
|
+
Each comes to `notify:` as event `alert` — like the three above it follows
|
|
121
|
+
`failed`, or name `alert` in `events:` — at most once a day per rule, and once
|
|
122
|
+
per slow run: "⚠ blog-desk: 6 of its last 15 runs failed", "⚠ blog-desk was
|
|
123
|
+
charged 2,340 credits in a day", "⚠ blog-desk: daily has been running 52
|
|
124
|
+
minutes". Test runs are left out.
|
|
125
|
+
|
|
126
|
+
`stale_days` is about the agents rather than the runs: an agent whose latest
|
|
127
|
+
step started more than that many days ago — or that has never run and whose
|
|
128
|
+
file has not changed in that long — is told once per idle spell, and again
|
|
129
|
+
weekly while it stays idle: "blog-desk: agent fact-checker has not run in 45
|
|
130
|
+
days". An agent nobody runs still holds its tools and secrets; this is the
|
|
131
|
+
nudge to retire it. The agent list marks it *not run in 30+ days*, and
|
|
132
|
+
`foldrun agent ls --stale` lists them. Counted from the runs the install
|
|
133
|
+
keeps, so an agent whose last run was pruned reads as never run.
|
|
134
|
+
|
|
135
|
+
Three watch for behaviour that is out of character rather than broken:
|
|
136
|
+
|
|
137
|
+
- `cost_spike` — a run that finished in the last day and cost at least that
|
|
138
|
+
many times the average of its flow's earlier runs this week (it needs
|
|
139
|
+
three of them first). Once per run: "blog-desk: a daily run cost 4.2× its
|
|
140
|
+
usual".
|
|
141
|
+
- `runs_per_hour` — one flow started that many times in the last hour: a
|
|
142
|
+
loop, a webhook sender retrying, a schedule written wrong. At most once a
|
|
143
|
+
day per flow. `throttle:` caps it for good; Settings → Governance → Pause stops it now.
|
|
144
|
+
- `new_host` — after each run, the sites every agent reached (from the
|
|
145
|
+
egress lines on its trail) are compared with what it reached before, and a
|
|
146
|
+
new one is told: "blog-desk: agent researcher reached a site it never
|
|
147
|
+
reached before — files.example.net". The first sign of a prompt injection,
|
|
148
|
+
a changed tool or a moved dependency. An agent's first run only records
|
|
149
|
+
its sites. Steps whose sandbox has no egress proxy record none.
|
|
150
|
+
|
|
151
|
+
To stop a loop rather than hear about it, see `limits: repeat` in
|
|
152
|
+
[Tools](tools#limits--how-many-calls-a-step-may-make). `foldrun check` and a deploy refuse an
|
|
153
|
+
`alerts:` they cannot read (a share that is not one, an unknown key). They are
|
|
154
|
+
listed with the other deliveries below.
|
|
155
|
+
|
|
97
156
|
## Proving it arrives
|
|
98
157
|
|
|
99
158
|
A notification path that has never been tested is not a notification path.
|