foldrun 2.6.0 → 2.7.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 +18 -1
- package/docs/agents.md +11 -5
- package/docs/api.md +10 -2
- package/docs/cli.md +16 -2
- package/docs/evals.md +151 -1
- package/docs/flows.md +63 -2
- package/docs/hooks.md +2 -2
- package/docs/network.md +6 -2
- package/docs/runs.md +28 -0
- package/docs/tools.md +1 -1
- package/package.json +6 -3
- package/src/commands.mjs +482 -9
- package/src/guide.mjs +1 -1
package/bin/foldrun.mjs
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
// foldrun runs what has run lately, across the account
|
|
16
16
|
// foldrun report <run-id> one run, whole: every step, what it cost, what it wrote
|
|
17
17
|
// foldrun approvals what is waiting for a person, anywhere in the account
|
|
18
|
+
// foldrun inbox what is waiting on YOU — assigned, started by you, the team's (read, dismiss)
|
|
18
19
|
// foldrun approve <run-id> release a waiting gate (asks first) — reject refuses it
|
|
19
20
|
// foldrun stop <run-id> kill a run in flight
|
|
20
21
|
// foldrun account the account's own defaults — timezone, notify, budget, concurrency, deploy_block, network_enforce
|
|
@@ -36,6 +37,9 @@
|
|
|
36
37
|
// foldrun agent new <name> one more agent in this workspace — also flow new, tool new (--platform: in the deployed one)
|
|
37
38
|
// foldrun agent ls the agents deployed in a workspace — also flow ls
|
|
38
39
|
// foldrun eval new <name> a new eval in a deployed workspace
|
|
40
|
+
// foldrun eval compare <eval> its cases under two to four models, side by side
|
|
41
|
+
// foldrun proposals proposed fixes for repeated failures — show, apply, dismiss
|
|
42
|
+
// foldrun eval grades <eval> what a grader found in the real runs it graded — eval grade <eval> --run <id> grades one
|
|
39
43
|
// foldrun agent run <name> run one agent once on a platform, no flow
|
|
40
44
|
// foldrun tool test <name> exercise one tool alone — no model, no run
|
|
41
45
|
// foldrun hook <verb> ls / add / test / rm — your own code at every tool call and step
|
|
@@ -122,6 +126,7 @@ const HELP = `foldrun — agents are just folders
|
|
|
122
126
|
foldrun flow rotate-hook <flow> a new webhook URL for a flow; the old one stops at once — asks first, --yes means it
|
|
123
127
|
foldrun flow set <flow> --step <n|agent> key=value … the step editor: model, effort, retry, timeout, verify, when, case, else, loop, until, each, max, limits, instruction (key= clears) — diff, checked, --dry-run
|
|
124
128
|
foldrun flow trigger <flow> manual|webhook|schedule the trigger picker (--schedule "<cron>", --timezone <zone>) — diff, checked, --dry-run
|
|
129
|
+
foldrun flow filter <flow> --payload <file|-|json> would this delivery start it? its filter: and the condition that held it back (--to <ws>: the deployed flow); exit 1 when not
|
|
125
130
|
foldrun flow move-step <flow> --step <n|agent> drag a card: --group <g> runs it beside group g, --after <g> gives it its own group after g (0 = first) — diff, checked, --dry-run
|
|
126
131
|
foldrun flow copy-step <flow> --step <n|agent>[,<n>…] the steps' markdown on stdout, as the canvas's Copy
|
|
127
132
|
foldrun flow paste <flow> step markdown from stdin or --file in as new groups (--after <g>, default last) or beside one (--group <g>) — diff, checked, --dry-run
|
|
@@ -137,6 +142,11 @@ const HELP = `foldrun — agents are just folders
|
|
|
137
142
|
foldrun run <target> run an agent or flow HERE (target: name, or flow:name) — on the platform: flow run / invoke a flow, agent run one agent
|
|
138
143
|
foldrun eval [name] run one eval, or all of them (--to <workspace>: the deployed evals, run on the platform; --last reads their latest results, --run <id> one run's verdict, running nothing)
|
|
139
144
|
foldrun eval new <name> a new eval in the deployed workspace, its agent the first one there — edit its cases, then eval <name> --to (--to)
|
|
145
|
+
foldrun eval compare <eval> --models fast,default --to <ws> the cases under 2–4 models side by side (m:effort, --agent, --repeat, --max-usd; --list, --run <id>)
|
|
146
|
+
foldrun eval improve <eval> --to <ws> ask the assistant for a proposed fix from the failures, wait for the draft (--no-wait)
|
|
147
|
+
foldrun proposals --to <ws> proposed fixes (--status ready, --eval) — show|apply|dismiss <id> (apply asks; --yes)
|
|
148
|
+
foldrun eval grades <eval> --to <ws> a grader's findings (trigger: runs): pass rate over --days (30), each graded run (--status fail, --limit); exit 1 on a failure
|
|
149
|
+
foldrun eval grade <eval> --run <id> --to <ws> grade one finished run now, sampled or not; exit 1 unless it passed
|
|
140
150
|
foldrun promote <run-id> keep a finished run as a regression case in evals/ (--eval <name>, --expect "contains: …" repeatable, --case <name>); a run that went wrong takes --should "what it should have done"
|
|
141
151
|
foldrun probe <model> live check: can this model hold a tool loop here?
|
|
142
152
|
foldrun logs [run-id] recent runs in one workspace, or one run's full event trail (--local: this machine's)
|
|
@@ -153,6 +163,8 @@ const HELP = `foldrun — agents are just folders
|
|
|
153
163
|
foldrun report <run-id> live the live browser view: which agents have a frame; --agent <name> saves the newest as a JPEG (--file <path>|-)
|
|
154
164
|
foldrun approvals every gate waiting on a person, with its question (--to <workspace> for one)
|
|
155
165
|
foldrun insights recurring problems found in the runs, with what they cost and what to do (--days, --to, --json)
|
|
166
|
+
foldrun inbox what waits on you: approvals, questions, issues, proposed fixes, your failed runs (--team, --all, --kind, --unread, --dismissed)
|
|
167
|
+
foldrun inbox read|unread|dismiss|restore <id>… mark items in your own view (dismiss sets aside; it still waits where it lives)
|
|
156
168
|
foldrun issues what went wrong, grouped, open until resolved (--to <workspace>, --all, --status, --json)
|
|
157
169
|
foldrun issues show|ack|snooze|resolve|reopen <id> one issue — snooze --for 1d or --times 5, --note "…"
|
|
158
170
|
foldrun issues doctor <id> the run doctor on the issue's newest run, in its workspace: why, and the fix as diffs, saved on yes (--yes, --dry-run, --json)
|
|
@@ -278,6 +290,11 @@ Options
|
|
|
278
290
|
--on-fail <agent> flow add: who takes the step over when it fails
|
|
279
291
|
--off flow add: take the pattern off the step instead (fan-out, loop, approval, ask, wait, rescue)
|
|
280
292
|
--schedule "<cron>" flow trigger: the cron line a schedule fires on (5 fields, or @daily); --timezone <zone> the zone it is read in
|
|
293
|
+
--team, --started, --mine, --all inbox: which bucket (default: assigned to you and started by you)
|
|
294
|
+
--dismissed, --unread inbox: what you set aside; only what you have not read
|
|
295
|
+
--models <m[:effort],…> eval compare: two to four tiers or model ids to compare, each with an optional :effort
|
|
296
|
+
--max-usd <n> eval compare: the spend limit — no run starts once the runs and judges have spent it (default 5)
|
|
297
|
+
--payload <file|-|json> flow filter: the delivery to test — a file, - for stdin, or JSON inline
|
|
281
298
|
--kind <k> secrets set: file (--file <path>), ssh (--host, --user, --port, --key-file or a prompted password), api (--base-url, --header "Name: value" repeatable, or --basic-user <u> with the password prompted or --basic-password), service-account (--file <key.json>, --scopes, --subject <user to act as>), client-cert (--cert-file, --key-file, --passphrase, --export to put it in the sandbox as files too), identity (--audience: keyless cloud access, no credential stored), m2m (--token-url, --client-id, --scopes; secret prompted)
|
|
282
299
|
--client <name> connect: run the consent from that OAuth client saved on the platform (secrets clients lists them)
|
|
283
300
|
--subagent <name> agent link, agent unlink: a worker it delegates to (subagents:)
|
|
@@ -385,7 +402,7 @@ if (!command || command === "--help" || command === "-h") {
|
|
|
385
402
|
// `--value` as the account's argument and stored an empty secret; `--force
|
|
386
403
|
// ./dir` swallowed the directory. A flag followed by another flag is also
|
|
387
404
|
// boolean, so an unlisted switch at least does not eat its neighbour.
|
|
388
|
-
const BOOLEAN_FLAGS = new Set(["account", "follow", "force", "oauth2", "wait", "watch", "print", "dry-run", "help", "no-browser", "local", "test", "flat", "yes", "platform", "json", "forever", "all", "check", "new-client", "quiet", "off", "can-ask", "preview", "csv", "failed", "starter", "history", "list", "library", "clear", "security", "changed", "write-baseline", "no-baseline", "rules", "calls", "tree", "tool-error", "stop", "last", "no-pkce", "export", "isolated"]);
|
|
405
|
+
const BOOLEAN_FLAGS = new Set(["account", "follow", "force", "oauth2", "wait", "watch", "print", "dry-run", "help", "no-browser", "local", "test", "flat", "yes", "platform", "json", "forever", "all", "check", "new-client", "quiet", "off", "can-ask", "preview", "csv", "failed", "starter", "history", "list", "library", "clear", "security", "changed", "write-baseline", "no-baseline", "rules", "calls", "tree", "tool-error", "stop", "last", "no-pkce", "export", "isolated", "no-wait", "dismissed", "unread", "team", "started", "mine"]);
|
|
389
406
|
// Flags said more than once collect into a list: `--tag a --tag b`.
|
|
390
407
|
const REPEATABLE = new Set(["tag", "expect", "header", "site", "add", "remove", "rule", "authorize-param"]);
|
|
391
408
|
// `--wait` is a switch everywhere but `flow add <flow> wait`, where it takes
|
package/docs/agents.md
CHANGED
|
@@ -292,7 +292,7 @@ The parent is unchanged (`subagents: [reader]`). Its model gets a
|
|
|
292
292
|
| where it runs | inside the parent's sandbox | a sandbox of its own, started for the job |
|
|
293
293
|
| tools | its own, cut to the parent's | its own — the parent may hold less |
|
|
294
294
|
| secrets, network, size, runtime | the parent's | its own file's |
|
|
295
|
-
| gives back | its reply | its reply, and the
|
|
295
|
+
| gives back | its reply | its reply, and the files it changed, copied into the parent's sandbox |
|
|
296
296
|
| on the run | lines in the parent's trace | a step of its own after the parent's, marked `subagentOf` |
|
|
297
297
|
| cost | on the parent's step | on its own step, in the run's total |
|
|
298
298
|
|
|
@@ -308,10 +308,16 @@ What holds:
|
|
|
308
308
|
- **Only the reply comes back.** The reply is still text the parent reads —
|
|
309
309
|
it can carry an instruction — but it reaches a model whose sandbox never
|
|
310
310
|
held the page.
|
|
311
|
-
- **Files.** What it writes reaches the workspace when its job ends,
|
|
312
|
-
|
|
313
|
-
the parent
|
|
314
|
-
|
|
311
|
+
- **Files.** What it writes reaches the workspace when its job ends, and is
|
|
312
|
+
copied into the parent's sandbox at the same paths before the reply comes
|
|
313
|
+
back — so the parent's model, its `verify: file:` and the next step all see
|
|
314
|
+
it. The reply lists the paths. Up to 4 MiB and 100 files a job are copied
|
|
315
|
+
back; what is larger is listed as not in the parent's sandbox and reaches
|
|
316
|
+
the workspace for the next step only. A file the parent had already
|
|
317
|
+
changed in its own sandbox is not overwritten: the job's version is put
|
|
318
|
+
beside it as `<name>.from-<agent><ext>`, and the reply says so. If both
|
|
319
|
+
write one path, the parent, finishing later, wins — as between two steps of
|
|
320
|
+
a parallel group.
|
|
315
321
|
- **One level deep.** Its own `subagents:` are not offered to it.
|
|
316
322
|
- **Bounded by its parent.** A job runs within the parent step's `timeout:`
|
|
317
323
|
and under two budgets. The sub-agent's own `budget:`: a job holds what is
|
package/docs/api.md
CHANGED
|
@@ -136,6 +136,7 @@ installer's `error`, its cause on the first line. `foldrun deploy` polls it afte
|
|
|
136
136
|
| `/api/workspaces/<ws>/flows/<flow>` | POST, PATCH, DELETE | edit a step (`step`, `instruction`, `options`, `target`, `subflow`), rearrange (`groups`), set `trigger`, a canvas edit (`edit`, `edits`), undo (`restore` + `expect`); delete. Every PATCH and DELETE takes `expect` — the text the step numbers were read from, or its sha256 — and is refused with `409` and the current `text`, nothing written, when the file no longer reads it (the canvas, the list view's board and step options, and the CLI's `flow add`/`rm-step`/`dup-step --to` send it; the dashboard shows the 409 as "this flow changed since you opened it — reload") |
|
|
137
137
|
| `/api/workspaces/<ws>/flows/draft` | POST, PATCH, DELETE | POST draft a flow with a model: `{ description, flow? }` (`flow` redrafts that one). Returns `{ ok, files: [{ path, content, before, sha256 }], issues: [{ where, message, level }], repaired, notes }` — checked with the deploy's checks and the flow lint, repaired once. **Writes nothing**; save with the source PUT. Needs `workspace:write`; a description over 4,000 characters is `413`; a Chat-Completions `provider:` is `422`. On the platform's models the balance is checked first (`402`) and the call is charged as one ledger line, flow `ai-draft`. PATCH and DELETE, and a POST without `description`, are the edits of a flow that happens to be named `draft`, exactly as at `flows/<flow>` |
|
|
138
138
|
| `/api/workspaces/<ws>/flows/<flow>/run` | POST | start it. An `Idempotency-Key` header, or `idempotencyKey` in the body, makes a retried call safe: a dropped response, a proxy timeout or a CI step that ran twice is answered `started: false` with the `runId` it already started, rather than starting a second run and billing for it. Keys are remembered for 24 hours, per flow |
|
|
139
|
+
| `/api/workspaces/<ws>/flows/<flow>/filter` | POST | test the flow's `filter:` against a delivery without starting anything: `{ payload }` — a webhook's JSON body (or the body as a string, read as JSON), an email's `{ from, to, subject, text }`, a chained run's `{ run, flow, status, ending, verdict, summary }`, a storage event's `{ by, paths }`. Answers `{ flow, trigger, filter, applies, match, reason }` — `reason` names the first condition that failed; a flow with no filter matches everything. Reads the deployed flow; nothing is recorded or charged |
|
|
139
140
|
|
|
140
141
|
`POST .../run` takes `task` (the input), `from` (start at step N — a rerun of
|
|
141
142
|
the interesting half), `tags`, and `test` (`true` for a [test run](runs#test-runs):
|
|
@@ -176,10 +177,16 @@ history with each message.
|
|
|
176
177
|
| `/api/workspaces/<ws>/agents/<agent>` | PATCH | edit one line of an agent's frontmatter in place, as the flow canvas does: `{ list: { key, name, action } }` — `key` one of `subagents`, `agents`, `tools`; `action` `add` or `remove` — or `{ set: { key: "description" | "sandbox", value } }` (`sandbox` takes `own` or `parent`, else 400), or `{ network }` — the sites its sandbox may reach: `"open"`, `"trusted"`, `"none"`, a list of sites, or `{ level, allow, defaults }`; `null` removes the line so it inherits ([Network](network)). A `network` nobody could read is a 400 that says why. Only that key's lines change — comments, key order and prose stay. Answers `{ ok, changed }`; 404 for an unknown agent. `workspace:write` |
|
|
177
178
|
| `/api/workspaces/<ws>/agents/<agent>/run` | POST | run one agent alone (`task`; `test: true` for a test run) |
|
|
178
179
|
| `/api/workspaces/<ws>/agents/import` | GET, POST | copy an agent in from another workspace of the account. GET lists what could be imported (every other workspace you can read, with its agents). POST `{ from, agent, as? }` copies `agents/<agent>/` — its agent.md and own skills/, scripts/, knowledge/; never memory/, outputs/, state/ — and answers `{ name, files, warnings }`; `warnings` names tools, skills, scripts and secrets the copy uses that this workspace lacks. 409 on a name clash (pass `as`), 404 for an unknown workspace or agent, 403 when `from` is outside your key's scope. Idempotent |
|
|
180
|
+
| `/api/workspaces/<ws>/proposals` | GET | the workspace's proposed fixes, newest first: `{ proposals: [{ id, workspace, eval, by (auto, or who asked), createdAt, updatedAt, status, failures: [{ runId?, case?, check, detail, at }], jobId, reply (the assistant's reasoning), files: [{ path, content, before, sha256 }], issues: [{ where, message, level }], error?, notes?, decidedBy?, decidedAt?, applied? }] }`. `status` is `drafting`, `ready`, `failed`, `applying`, `applied`, `partial` (a write failed part-way; `applied` names what was written) or `dismissed`. `?status=`, `?eval=`. A drafting one is settled as it is read |
|
|
181
|
+
| `/api/workspaces/<ws>/proposals/<id>` | GET, POST | one proposal. `POST { action: "apply" }` writes its files, each a revision by the caller, only when every one still reads what it was drawn against (`409` naming the file that changed) and can be written (`422`; a check error the deploy would refuse is `422` too); `POST { action: "dismiss" }` sets it aside — its failures stay looked at. Only agents, flows and skills are ever in one. Needs `workspace:write` |
|
|
179
182
|
| `/api/workspaces/<ws>/export` | GET | this workspace, one flow or one agent as a `.zip` — `?kind=flow&name=<flow>` (the flow, every flow it runs as a step, every agent those steps and their colleagues name, and the workspace tools, skills and scripts they grant), `?kind=agent&name=<agent>` (its folder and what it grants), or neither for the whole authored tree. Never memory/, state/, outputs/, storage, runs or a secret's value; `foldrun-package.json` inside says what it is. Any role that can read the workspace |
|
|
180
183
|
| `/api/workspaces/<ws>/import` | POST | take a package in; the body is the `.zip` (`content-type: application/zip`, at most 20 MB). `?dryRun=1` answers `{ kind, name, from, creates, added, overwritten, unchanged, needs: { secrets, tools, skills, scripts, agents, flows } }` plus `skipped` (entries it will not write: memory, state, anything outside the authored folders) and writes nothing. Without it, applies as one revision; 409 when it would replace a file with different text unless `?overwrite=1`, or when a file cannot be written there (all or nothing). Never deletes. A `Content-Length` is required (411 without). A workspace package into a name that does not exist makes that workspace (`workspace:manage`, the plan's workspace count). 400 for anything not a package, or one holding memory/, state/ or outputs/. Idempotent |
|
|
181
184
|
| `/api/workspaces/<ws>/evals` | GET, POST | list; create (`name`) |
|
|
182
185
|
| `/api/workspaces/<ws>/evals/<eval>/run` | GET, POST | POST runs one eval and answers with its result; with `{ async: true }` it answers `202 { started, eval, runId, startedAt }` at once and the eval runs on — an eval of slow cases outlives a proxy's read timeout (Cloudflare's is 125 s by default). One run of an eval at a time: a second is `409` naming the run that holds it. `GET ?run=<runId>` is what to poll — that run alone, whatever else runs or writes the eval's latest result meanwhile: `{ runId, state, running }` where `state` is `running`, `done` (with its `result`), `failed` (with `error`), `lost` (it stopped reporting before saving a result, e.g. a restart) or `unavailable` (an id not on record — the last 20 runs are kept). Plain `GET` answers who holds the eval now (`running`, `runId`, `startedAt`) and the latest `result`. The dashboard and `foldrun eval --to` start async and poll by run id |
|
|
186
|
+
| `/api/workspaces/<ws>/evals/<eval>/compare` | GET, POST | compare models on one eval: its cases under two to four settings, side by side. `POST { configs: [{ model, effort?, agent? }], repeat?, maxUsd? }` — `model` a tier (`fast`, `default`, `max`) or a model id; `effort` low…max; `agent` (flow evals) forces only that agent's steps, else every step; `repeat` 1–5 (1); `maxUsd` the spend limit, above 0 and at most 100 (5). At most 60 runs (cases × settings × repeat, else `413`). Answers `202 { started, eval, runId, startedAt, trials, maxUsd }` and runs on: every run a test run, the eval's own judge for all. `GET ?run=<runId>` → `{ runId, state, running }` — `running` (with `progress: { done, total }`), `done` (with `result`), `failed` (with `error`), `lost` or `unavailable`. `result` is `{ eval, target, judge, startedAt, finishedAt, repeat, maxUsd, spentUsd, stopped, configs: [{ model, effort, agent, label, trials, passed, notRun, rate, costUsd (runs and judges), judgeCostUsd, avgCostUsd, avgSecs (worker pick-up to end), runIds }], cases: [{ case, passed: [per setting], trials: [per setting] }] }`. Plain `GET` → `{ running, runId?, startedAt?, progress?, comparisons }` (the last 20, newest first). One comparison of an eval at a time (`409`); a grader, saved inputs or an eval with a `run:` check `400`. Needs `run:start` to start |
|
|
187
|
+
| `/api/workspaces/<ws>/evals/<eval>/improve` | POST | ask for a proposed fix now: for a grader, from its failed grades no proposal has covered; for an eval that runs cases, from its last result's failed cases. Answers `202` with the proposal, `status: "drafting"` — the assistant drafts it (one reply, paid as Ask AI's are); poll `GET …/proposals/<id>`. `409` when there is nothing to fix, a proposal for this eval is open, or the account or workspace is paused. Needs `run:start` |
|
|
188
|
+
| `/api/workspaces/<ws>/evals/<eval>/grades` | GET | a grader's grades (an eval with `trigger: runs`): `{ eval, flow, agent, sample, improve, days, summary, grades }`. `summary` is the window's `{ graded, passed, failed, errors, rate, costUsd, byDay: [{ day, graded, passed, failed }] }` (`rate` is passed ÷ decided, `null` when nothing was decided); `grades` the newest first, one per run — `{ eval, runId, flow, at, status (pass, fail or error — not decided), checks: [{ name, passed, error?, assertions: [{ type, value, passed, detail }] }], costUsd (the judges'), model, evalRevision, runRevision, runFinishedAt, by }`. `?days=` 1–90 (30), `?status=` pass, fail or error, `?limit=` 1–500 (100). An eval that runs cases is `400` |
|
|
189
|
+
| `/api/workspaces/<ws>/evals/<eval>/grade` | POST | grade one finished run now, sampled or not: `{ run }` → the grade (the shape above), kept like any other — the newest grade of a run is the one shown. Needs `run:start`; the judges run on the account's model key. A run that is not this grader's (another flow, or no step by its agent) is `400`, a run still going `409`, one with no reply to grade `409`, an eval that runs cases `400`, a paused account or workspace `409` |
|
|
183
190
|
| `/api/workspaces/<ws>/tools/<tool>/test` | POST | exercise one tool (`args`, `path`). An http tool with an `operations:` allowlist is probed only with a GET to one of its operations (no `path`: the first GET without parameters), and fails if the allowlist resolves nothing, as a run does |
|
|
184
191
|
| `/api/workspaces/<ws>/hooks` | GET | the [hooks](hooks) this workspace can turn on — its own and the library's (shadowing marked), invalid ones with the reason, and who turns each on — plus `recent`: every deny, rewrite and failure hooks wrote in the last `?runs=` runs (1–500, default 50), newest first; `?all=1` includes notes and context |
|
|
185
192
|
| `/api/workspaces/<ws>/hooks/<name>/test` | POST | run one [hook](hooks) on a sample event: body `{ event }`, answer the hook's own (`ok`, `decision`, `reason`, `input`, `context`, `reply`, `note`, `failure`, `ms`). Shares the `hooks/` path with flow webhooks below — `/test` is only ever a hook's. Answers `404` for no such hook, `422` for an invalid `hook.md`, `400` for an event that is not an object. On an isolated install hook code never runs in the platform's own process, so this answers with where to test it instead (`foldrun hook test` locally, or a test run) |
|
|
@@ -192,7 +199,7 @@ paying for the steps around it.
|
|
|
192
199
|
| Route | Methods | |
|
|
193
200
|
|---|---|---|
|
|
194
201
|
| `/api/workspaces/<ws>/runs?limit=` | GET | `{ runs: [{ id, flow, status, verdict, outcome, startedAt, finishedAt, ... }] }`; search (see [Runs](runs#searching-runs)) with `tool` (a step called a tool whose name contains it), `tool_error=1`, `agent`, `model` (contains), `min_tokens` (`50k`), `min_secs` — `limit` counts the matches — `verdict` is the word the summary leads with (`GOOD`, `QUIET`, `BLOCKED`, `BAD` or null) and `outcome` the one word every page shows (`running`, `queued`, `waiting`, `good`, `done`, `quiet`, `blocked`, `bad`, `failed`, `stopped`); `failure` is `{ agent, reason }` for a run that failed (null otherwise), `chargedUsd` is what the account was charged for the run (one credit is one cent), null until the ledger settles it, while `costUsd` is the provider's token cost — off the run index (see [Runs](runs#the-runs-list)); a finished run's steps carry `agent`, `status`, `costUsd` and `tokens` (`{ input, output, cacheRead, cacheWrite }`, see [Runs](runs#the-record)), and the run its summed `tokens` — not their reply or trace |
|
|
195
|
-
| `/api/workspaces/<ws>/runs/<id>/calls/<call>` | GET | one tool call's recorded content (see [Runs](runs#what-a-run-records)): `{ call: { id, tool, input, output, inputChars, outputChars, truncated, inputCut, outputCut } }` — what it was given and what it gave back, secrets masked, each at most 65,536 characters; `inputChars`/`outputChars` are the whole length, `inputCut`/`outputCut` say which part was cut (`truncated`: either). `<call>` is the tool event's `call`; an event with `captured: true` has content here. 404 when nothing was recorded: the workspace says `capture: metadata`, or the run is from before 19 Oct 2026. A run's steps also carry `calls`, each model call (`n`, `at`, `ms`, `model`, `stop`, `tokens`, `tools`, and `subagent` / `attempt` when a sub-agent or a later attempt made it); a step that handed jobs to `sandbox: own` sub-agents carries `delegations` (`id`, `agent`, `status`, `costUsd`, `ms`), and each job is a step of its own with `subagentOf` (the delegating agent), `delegation` (that `id`) and `wrote` (the workspace paths it changed); and the run its `revision` (the workspace's latest revision when a worker picked it up — a deploy's commit, or the id an edit through `/source` was saved as) and `drivenAt` (when that was) |
|
|
202
|
+
| `/api/workspaces/<ws>/runs/<id>/calls/<call>` | GET | one tool call's recorded content (see [Runs](runs#what-a-run-records)): `{ call: { id, tool, input, output, inputChars, outputChars, truncated, inputCut, outputCut } }` — what it was given and what it gave back, secrets masked, each at most 65,536 characters; `inputChars`/`outputChars` are the whole length, `inputCut`/`outputCut` say which part was cut (`truncated`: either). `<call>` is the tool event's `call`; an event with `captured: true` has content here. 404 when nothing was recorded: the workspace says `capture: metadata`, or the run is from before 19 Oct 2026. A run's steps also carry `calls`, each model call (`n`, `at`, `ms`, `model`, `stop`, `tokens`, `tools`, and `subagent` / `attempt` when a sub-agent or a later attempt made it); a step that handed jobs to `sandbox: own` sub-agents carries `delegations` (`id`, `agent`, `status`, `costUsd`, `ms`), and each job is a step of its own with `subagentOf` (the delegating agent), `delegation` (that `id`) and `wrote` (the workspace paths it changed — copied into the delegating step's sandbox too, up to 4 MiB a job); and the run its `revision` (the workspace's latest revision when a worker picked it up — a deploy's commit, or the id an edit through `/source` was saved as) and `drivenAt` (when that was) |
|
|
196
203
|
| `/api/workspaces/<ws>/runs/<id>/trace` | GET | the run as one tree (see [Runs](runs#the-trace-tree)): `{ tree, failure }`. Each span is `{ id, kind, label, start, end, status, children, … }`, where `kind` is `run`, `step`, `model` (one model call: `model`, `stop`, `tokens`), `tool` (`call`, `captured` when its input and output were recorded) or `error` (`text`); `start`/`end` are epoch milliseconds and `status` is `ok`, `error`, `running`, `waiting`, `skipped` or `pending`. A tool call sits under the model call that asked for it. `failure` is the ids from the root to the first thing that went wrong, which the run page opens on; `[]` when nothing did |
|
|
197
204
|
| `/api/workspaces/<ws>/runs/<id>/compare?with=` | GET | this run beside another of the same flow (see [Runs](runs#compare-with-the-last-good-run)): `{ comparison: { base, run, revisionChanged, changedFiles, steps }, chosen }`. Each step is `{ step, agent, base, run, changes }`: both sides' status, instruction, model, seconds, tokens, model calls, tool counts, tool errors, first errors and first line said, and `changes` in words (`status: completed → failed`, `new error: …`, `instruction changed`, `tools: +web ×3`, `took 12s → 48s`, …). `changedFiles` lists the files changed between the revisions the two ran (null when one is unknown). `with` names the other run; unasked it is the newest earlier run of the flow that finished well, else the run this one re-ran (`chosen` says which) |
|
|
198
205
|
| `/api/workspaces/<ws>/runs/<id>` | GET, DELETE | the whole run: every step, its output, cost, timing, the runner `image` it ran on and — for a step that browsed through the account's pod from slim — its `browserPod` reconnects and fallback ([runs](runs#the-record)). `?wait=true&timeout=<s>` instead blocks until the run stops (or `timeout`, default 300, max 900) and answers the verdict the run routes give — `200` completed, `500` failed with the record, `202` still going (`timedOut`) or parked on a person (`awaitingApproval`). Ask in short pieces: whatever sits in front of the platform cuts a request that stays silent too long (Cloudflare at 100 s, an AWS ALB at 60 s), and a flow can run for many minutes — the CLI's `--wait` asks 25 s at a time and comes back here on every `202` |
|
|
@@ -353,7 +360,7 @@ Reads return which secrets exist, never their values.
|
|
|
353
360
|
| `/api/openapi.json` | GET | this API as an OpenAPI 3.1 document — every route and method, bearer auth, the rate-limit, version and Idempotency-Key headers, and request/response schemas for runs, flow runs, deploy, source, secrets, keys and billing. No key needed. Generated from the route files by `scripts/gen-openapi.mjs`; the dashboard renders it at Docs → API reference, `foldrun api spec` saves it |
|
|
354
361
|
| `/api/account` | GET, PATCH | the account's defaults from its AGENTS.md frontmatter — `timezone`, `budget` (a USD cap — a number for a month, `"60/day"`, `"60/week"`, or `{ usd, period }`; null is no limit), `notify` (`{ url?, email?, events }`), `concurrency`, `deploy_block` (`none`, `secrets` or `security` — answered as `deployBlock`), `network_enforce` (answered as `networkEnforce`), `owner`, `alerts` (as written), `judge` (`{ model?, effort? }`), `telemetry` (`{ otlp, headers }` — header values are `${SECRET}` names; answered as `{ endpoint, headers }`); PATCH sets the keys sent, null clears one, and edits the file in place |
|
|
355
362
|
| `/api/audit?since&until&action&actor&workspace&limit&cursor&format` | GET | the account's audit log, newest first — the caller's account only, whatever a header, query or body names. The owner and unscoped admins (people, or `admin` keys); `audit:read`, and refused to a member or key scoped to some workspaces, because the log is the whole account's. `since`/`until`: `7d`, `24h`, `30m` or a date; `action`: exact (`apikey.created`) or a family (`apikey`); `actor`: exact (an email, `key:<label>`, `support:<email>`); `workspace`: entries about one; `limit` 1–1000 (100; 1000 for CSV); `cursor`: the `next` of the page before. JSON `{ entries: [{ id, at, actor, action, subject, detail }], next, retention }`; `format=csv` downloads the same rows (`x-next-cursor` header when there are more). Records sign-ins and refusals, sign-outs, session revokes, password and email changes, MFA on/off, key mint/rotate/revoke, invites, joins, role/scope changes, removals, ownership transfers, secrets set/deleted (by name — never a value), deploys, workspace deletes, plan and billing changes, export and close requests, and `support.viewed`/`support.ended`. The super admin's own levers (`operator.*`) are the platform's record and not listed. Kept for the life of the account |
|
|
356
|
-
| `/api/avatars/<kind>/<id>` | GET, PUT, DELETE | a picture for a `user` (their own id), a `workspace` (its name) or the `account` (the tenant). PUT the bytes of a PNG, JPEG or WebP under 1 MB; a person sets their own, an admin a workspace's or the account's. GET is cached for a year — the URL's `?v=` changes with the picture |
|
|
363
|
+
| `/api/avatars/<kind>/<id>` | GET, PUT, DELETE | a picture for a `user` (their own id), a `workspace` (its name) or the `account` (the tenant). PUT the bytes of a PNG, JPEG or WebP under 1 MB; a person sets their own, an admin a workspace's or the account's. GET needs a signed-in tenant and is privately cached for a year — the URL's `?v=` changes with the picture |
|
|
357
364
|
| `/api/search?q=` | GET | find anything in the account by name — workspaces, agents, flows, tools, skills, knowledge, memory, runs (by id, flow or headline); ranked, at most six per kind. The dashboard's Find palette reads it |
|
|
358
365
|
| `/api/schedule` | GET, POST | GET: every scheduled flow with `valid` (the cron parses) and `upcoming` (the next three fires). POST: tick the scheduler now — install-wide, so `workspace:manage`; returns `{ ok, fired }` |
|
|
359
366
|
| `/api/library/<kind>?path=` | GET, POST, PUT, DELETE | account-level shared documents (`name`, `content`, `path`, `template`) |
|
|
@@ -454,6 +461,7 @@ signed-in person, never by a key — mints an ordinary API key at their role.
|
|
|
454
461
|
| `/api/cli/authorize` | GET, POST | `?code=` describes the request (machine, expiry). POST `{ code, decision }` from a session approves (mints the key, labelled `cli · <hostname>`) or denies — `decision` is `approve` or `deny`, and absent approves; any other word is a `400`. Every role may sign its own CLI in; an owner's key is an admin's |
|
|
455
462
|
| `/api/me` | GET | who is calling: `account`, `owner`, `role`, `workspaces` (null: all), and the `actor` — a person's email, or a key's id, label, prefix and who minted it |
|
|
456
463
|
| `/api/me/onboarding` | GET, PATCH | the account's getting-started steps, each derived from what is really there — `{ steps: [{ id, title, why, href, cli?, done }], done, total, complete, next, dismissed, dismissedAt }`. Steps: `model` (first; only for an account that must bring its own key — done once it has one, or a `provider:` block in the account's AGENTS.md), `workspace`, `agent` (an agent written or deployed after the workspace was made), `run`, `success` (a completed run), `secret`, `trigger` (a schedule or webhook), `teammate` (a second member), `billing` (hosted, not fully waived: a saved card or a plan), `cli` (any API key or CLI login). PATCH `{ dismissed: true or false }` hides or brings back the dashboard card for the signed-in person (kept in their prefs); a key reads `dismissed: false` and cannot PATCH |
|
|
464
|
+
| `/api/me/inbox` | GET, POST | your inbox — what waits on you across the workspaces you may see. GET `{ person, items: [{ id, kind (approval, question, issue, proposal, run), bucket (mine, started, team), why, workspace, title, detail, at, href, read, dismissed }], counts: { mine, started, team, unread } }`, newest first. `mine` is assigned to you (named in `approvers:`, owner of the flow or agent, or you asked for the proposal); `started` is runs you started — failed or BLOCKED/BAD this week, a gate on one (someone else decides it), a question it asked; `team` is the rest that waits in your workspaces. By default `mine` and `started`; `?bucket=mine\|started\|team\|all`, `?kind=`, `?workspace=`, `?dismissed=1` for what you set aside. `counts.unread` is unread `mine` + `started`. POST `{ ids \| id, action: read\|unread\|dismiss\|restore }` changes your view only — dismissing never decides a gate or resolves an issue; ids name an occurrence, so a thing that happens again is new. A key acts for the person who minted it, inside the key's own scope; one no person minted is a `400` |
|
|
457
465
|
| `/api/me/notifications` | GET, PATCH | what mail you get. GET `{ email, categories: [{ category, label, description, required, why?, perWorkspace, enabled, workspaces: { <ws>: bool } }] }` for `security`, `invites`, `billing`, `account` (required — always sent) and `run-alerts`, `approvals` (per workspace), `low-balance`, `product-updates`. PATCH `{ category, enabled, workspace? }` or `{ preferences: [ … ] }`; answers the same as GET. Turning a required category off is a `409` with the reason; `workspace` on an account-wide category is a `400`; `enabled: true` account-wide clears that category's per-workspace offs. A key acts for the person who minted it |
|
|
458
466
|
| `/api/me/preferences` | GET, PATCH | your own display preferences: `{ theme, runViews }` — `theme` is `system` (the default: follow the computer's light or dark setting), `light` or `dark`; `runViews` your saved run searches, `[{ name, query }]` (see [Runs](runs#searching-runs)). PATCH `{ theme }` and/or `{ runViews }` (the whole list, at most 20, each query of the runs filters) answers the same; anything else is a `400`. Kept in your prefs, so it follows you to every browser; the dashboard applies it on the server, before the first paint. A key acts for the person who minted it; a key minted by nobody is a `400` |
|
|
459
467
|
| `/api/unsubscribe?token=` | POST | one-click unsubscribe (RFC 8058) — no login, the signed token is the credential. It is the `List-Unsubscribe` URL on every optional mail, beside `List-Unsubscribe-Post: List-Unsubscribe=One-Click`; a mail client POSTs `List-Unsubscribe=One-Click` to it. Turns off the one category (for the one workspace) the token was minted for, for its address. The token may also come as a form field (`token`, and `from=page` to be sent back 303 to the `/unsubscribe` confirmation page) or JSON. `200 { ok, category, workspace }`; `400` no token, `401` forged, `410` expired (180 days; `FOLDRUN_UNSUBSCRIBE_TTL_DAYS`). Never on a GET — `/unsubscribe?token=` is the page that asks first |
|
package/docs/cli.md
CHANGED
|
@@ -44,7 +44,7 @@ claude.ai or Claude Code there, nor any MCP configuration on disk.
|
|
|
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
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
|
-
| `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. `--isolated` also writes `sandbox: own` on the worker: each job it is handed runs in its own sandbox, with its own tools, secrets and network, not cut to the delegating agent's
|
|
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. `--isolated` also writes `sandbox: own` on the worker: each job it is handed runs in its own sandbox, with its own tools, secrets and network, not cut to the delegating agent's; its reply comes back with the files it wrote (`foldrun docs agents`). 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) |
|
|
49
49
|
| `foldrun agent import --list` | what could be imported into a deployed workspace: every other workspace this key may read, and each one's agents with their descriptions (`GET …/agents/import`). Imports nothing. `--to <workspace>`, `--json` |
|
|
50
50
|
| `foldrun agent unlink <agent>` | take one away: `--subagent <name>`, `--consult <name>` or `--can-ask`. An emptied list is written `key: []`, not deleted. In this folder, refused the same way, nothing written, when the file changed since it was read |
|
|
@@ -58,6 +58,7 @@ claude.ai or Claude Code there, nor any MCP configuration on disk.
|
|
|
58
58
|
| `foldrun flow rotate-hook <flow>` | a new webhook URL for the flow (`POST …/hooks/<flow>/rotate`). The old URL stops working the moment it returns, so it asks first — `--yes` with no terminal. Prints the new URL, which is itself a credential |
|
|
59
59
|
| `foldrun flow set <flow> --step <n\|agent> key=value …` | the step editor on the Flows page, from a terminal (core's `updateFlowStep`, and `updateFlowStepInstruction` for `instruction=`): `model`, `effort`, `retry`, `timeout`, `verify`, `when`, `case`, `else`, `loop`, `until`, `each`, `max`, `limits` — only the keys named are touched, `key=` clears one. `each=` takes `lines` only; the other fan-outs are `flow add … fan-out`. A value the rewriter would drop or clamp is refused, nothing written: `effort` one of low, medium, high, xhigh, max (or an alias core reads, e.g. `deep`); `timeout` a whole number of seconds (`timeout=900`, not `15m`); `retry` 0–5, `loop` 1–5, `max` 1–20, whole numbers. Shows the diff, refuses an edit `check` would call a new error, `--dry-run` writes nothing; `--to <workspace>` sends the editor's `PATCH …/flows/<flow> { step, options }` / `{ step, instruction }` with `expect` |
|
|
60
60
|
| `foldrun flow trigger <flow> manual\|webhook\|schedule` | the trigger picker on a flow card (core's `setFlowTrigger`): only the `trigger:`, `schedule:` and `timezone:` lines change. `--schedule "0 9 * * 1"` (5 fields, or `@daily`) and `--timezone <zone>` go with `schedule`. Diff, checked, `--dry-run`; `--to` sends `PATCH { trigger }` |
|
|
61
|
+
| `foldrun flow filter <flow> --payload <file\|-\|json>` | would this delivery start the flow? Its `filter:` against a payload — a webhook's JSON body, an email's `{ from, to, subject, text }`, a chained run's `{ run, flow, status, ending, verdict, summary }`, a storage event's `{ by, paths }` — and the condition that held it back. Reads the local flow; `--to <workspace>` asks the deployed one (`POST …/flows/<flow>/filter`, the Triggers page's Test box). Starts nothing; exits `0` when it would start a run, `1` when not; `--json` for the whole answer |
|
|
61
62
|
| `foldrun flow move-step <flow> --step <n\|agent>` | dragging a card on the board: `--group <g>` runs it beside group g, `--after <g>` gives it a group of its own after g (`0` = first). Only step numbers change (core's `reorderFlowSteps`). Diff, checked, `--dry-run`; `--to` sends `PATCH { groups }` |
|
|
62
63
|
| `foldrun flow copy-step <flow> --step <n\|agent>[,<n>…]` | the canvas's Copy: the steps' markdown — line, marker and every option (core's `stepSource`) — on stdout. Reads only |
|
|
63
64
|
| `foldrun flow paste <flow>` | the canvas's Paste: step markdown from stdin or `--file <path>` goes in as new groups after `--after <g>` (default last), or beside group `--group <g>`, renumbered to fit (core's `pasteSteps`) — `foldrun flow copy-step a --step 2 \| foldrun flow paste b`. Diff, checked, `--dry-run`; `--to` sends `PATCH { edit: { op: "paste" } }` |
|
|
@@ -70,7 +71,13 @@ claude.ai or Claude Code there, nor any MCP configuration on disk.
|
|
|
70
71
|
| `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
72
|
| `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
73
|
| `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` 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
|
+
| `foldrun eval [name]` | one eval, or all of them — a grader (`trigger: runs`) is listed and skipped, since it has no cases. `--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 |
|
|
75
|
+
| `foldrun eval compare <eval> --models <m[:effort],…> --to <workspace>` | an eval's cases under two to four models, side by side (`POST …/evals/<eval>/compare`, then polled) — the Evals page's **Compare models**. `--models fast,default,max` or `fast:low,max:high`; `--effort` for the items that name none; `--agent <name>` compares only that agent's steps of a flow eval; `--repeat` 1–5; `--max-usd` the spend limit (default 5). Every run is a test run. Waits (`--timeout <s>`, default 3600; past it, the id to read it with) and prints each setting's passing, cost, cost and time per run, then the cases where they differ. `--list` the kept comparisons; `--run <id>` one of them; `--json` |
|
|
76
|
+
| `foldrun eval improve <eval> --to <workspace>` | ask for a proposed fix now (`POST …/evals/<eval>/improve`) — from a grader's failed grades no proposal covers, or an eval's last failed cases — and wait for the draft (`--timeout`, default 300), then print it like `proposals show`. `--no-wait` returns with the id. Exits 1 unless the draft is ready |
|
|
77
|
+
| `foldrun proposals --to <workspace>` | the proposed fixes in a workspace, newest first: status, the eval, the failures it covers, the files it changes. `--status ready` for the ones waiting on you, `--eval <name>`, `--json`. The Evals page's Proposed fixes |
|
|
78
|
+
| `foldrun proposals show\|apply\|dismiss <id> --to <workspace>` | `show`: the assistant's reasoning, the failures, the change per file as `-`/`+` lines. `apply`: prints the change and asks before writing it (`--yes` without a terminal); each file is a revision by you; refused when a file changed since. `dismiss`: set it aside |
|
|
79
|
+
| `foldrun eval grades <eval> --to <workspace>` | what a grader (an eval with `trigger: runs`) found in the real runs it graded: the pass rate over `--days` (default 30), then each graded run, newest first, with the check that failed — `--status fail` for the failures alone, `--limit`, `--json`. `GET …/evals/<eval>/grades`, the Evals page's grader card. Exits 1 when a graded run in the window failed |
|
|
80
|
+
| `foldrun eval grade <eval> --run <id> --to <workspace>` | grade one finished run now, sampled or not (`POST …/evals/<eval>/grade`) — **Grade a run** on the card. Prints each check; exits 1 unless it passed |
|
|
74
81
|
| `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
82
|
| `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
83
|
| `foldrun probe <model>` | can this model hold a tool loop here? A live check |
|
|
@@ -85,6 +92,8 @@ claude.ai or Claude Code there, nor any MCP configuration on disk.
|
|
|
85
92
|
| `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 |
|
|
86
93
|
| `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
94
|
| `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` |
|
|
95
|
+
| `foldrun inbox` | what waits on you across your workspaces ([your inbox](runs#your-inbox)): gates, questions agents ask, open issues, proposed fixes, your runs that went wrong this week — assigned to you and started by you by default; `--team`, `--started`, `--mine`, `--all`, `--dismissed`, `--kind approval\|question\|issue\|proposal\|run`, `--unread`, `--json`. Each item shows its id; exits 1 when something assigned to you is unread |
|
|
96
|
+
| `foldrun inbox read\|unread\|dismiss\|restore <id>…` | mark items in your own view: `dismiss` sets one aside (it still waits where it lives), `restore` puts it back |
|
|
88
97
|
| `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
98
|
| `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
99
|
| `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 |
|
|
@@ -186,6 +195,11 @@ claude.ai or Claude Code there, nor any MCP configuration on disk.
|
|
|
186
195
|
| `--flow <name>` | a bulk `stop` or `rerun`: only that flow's runs. `flow add … subflow`: the other flow to run as a step |
|
|
187
196
|
| `--agent`, `--instruction`, `--after`/`--before`, `--group`, `--step`, `--cases`/`--else`, `--each`/`--max`, `--loop`/`--until`/`--judge`, `--question`, `--wait`, `--on-fail`, `--off` | `flow add`: see the patterns above. `--agent` on `rerun` is the first step that agent runs; `--question` on `answer` picks one of several questions; `--wait` alone stays the switch `invoke` and `agent run` read |
|
|
188
197
|
| `--schedule "<cron>"`, `--timezone <zone>` | `flow trigger schedule`: the cron line (5 fields, or `@daily`) and the zone it is read in |
|
|
198
|
+
| `--team`, `--started`, `--mine`, `--all` | `inbox`: which bucket — the rest of what waits in your workspaces, runs you started, what is assigned to you, or every one (default: assigned and started) |
|
|
199
|
+
| `--dismissed`, `--unread` | `inbox`: what you set aside; only what you have not read |
|
|
200
|
+
| `--models <m[:effort],…>` | `eval compare`: the settings to compare — two to four tiers or model ids, each with an optional `:effort` |
|
|
201
|
+
| `--max-usd <n>` | `eval compare`: stop starting runs once the runs and judges have spent this (default 5) |
|
|
202
|
+
| `--payload <file\|-\|json>` | `flow filter`: the delivery to test — a file, `-` for stdin, or JSON inline |
|
|
189
203
|
| `--kind <k>` | `secrets set`: `file`, `ssh`, `api`, `service-account` or `m2m` — each with its own flags, in the `secrets set NAME --kind` row above |
|
|
190
204
|
| `--client <name>` | `connect`: run the consent from that OAuth client saved on the platform (`secrets clients` lists them) |
|
|
191
205
|
| `--subagent`, `--consult`, `--can-ask`, `--description`, `--isolated` | `agent link`, `agent unlink`: which list to edit, a worker's description when it has none, and (`link --subagent` only) `sandbox: own` on the worker |
|
package/docs/evals.md
CHANGED
|
@@ -35,7 +35,9 @@ satisfy. The heading is the case's name, so make it the thing being checked.
|
|
|
35
35
|
| `agent` **or** `flow` | what runs. A flow eval runs the whole flow per case |
|
|
36
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
|
-
| `trigger` | `deploy` (default) or `
|
|
38
|
+
| `trigger` | `deploy` (default), `manual`, or `runs` — grade real finished runs instead of running cases (see [Grading live runs](#grading-live-runs)) |
|
|
39
|
+
| `sample` | `trigger: runs` — the percent of finished runs graded, 0 to 100 (default 25) |
|
|
40
|
+
| `improve` | `trigger: runs` — `true` (5) or a count: once that many graded runs failed, a fix is proposed for a person to review (see [Proposed fixes](#proposed-fixes)) |
|
|
39
41
|
| `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 |
|
|
40
42
|
| `live` | an eval's runs are [test runs](runs#test-runs) — nothing outward, `state/` untouched — unless this says `true`. A flow eval that must really send to prove itself opts in; nothing opts in by accident |
|
|
41
43
|
|
|
@@ -69,6 +71,154 @@ so it counts against the account's `concurrency:` and waits its turn.
|
|
|
69
71
|
eval runs the entire flow once per case, so a three-case eval on an hour-long
|
|
70
72
|
flow is three hours and the full cost, on every deploy.
|
|
71
73
|
|
|
74
|
+
## Grading live runs
|
|
75
|
+
|
|
76
|
+
An eval tests the cases someone wrote. A grader tests the runs that actually
|
|
77
|
+
happened: the 3am desk run nobody watched, held to the same checks. It is an
|
|
78
|
+
eval with `trigger: runs` and no tasks — each `##` heading is a check, with
|
|
79
|
+
only an `expect:` list:
|
|
80
|
+
|
|
81
|
+
```markdown
|
|
82
|
+
---
|
|
83
|
+
name: digest-quality
|
|
84
|
+
flow: weekly-digest # or agent: writer — that agent's steps, in any flow
|
|
85
|
+
trigger: runs
|
|
86
|
+
sample: 25 # percent of finished runs graded
|
|
87
|
+
model: fast # the judge
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## cites its sources
|
|
91
|
+
expect:
|
|
92
|
+
- judge: every claim names the source it came from
|
|
93
|
+
|
|
94
|
+
## no placeholders
|
|
95
|
+
expect:
|
|
96
|
+
- not-contains: lorem ipsum
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
- **What it grades.** For `flow:`, every step's reply joined; for `agent:`,
|
|
100
|
+
that agent's replies, in whichever flow it ran. The judge is also given
|
|
101
|
+
the run's task, so it grades the reply against what was asked.
|
|
102
|
+
- **Which runs.** Completed ones — a crash is already an
|
|
103
|
+
[issue](runs#issues). Not test runs, not eval runs, and only those that
|
|
104
|
+
finished after the grader was first deployed: a new grader starts from
|
|
105
|
+
now, it does not grade a week of runs at once. The sample is chosen by a
|
|
106
|
+
hash of the run id, so the same run is always in or out.
|
|
107
|
+
- **When.** The platform grades a few runs every five minutes, at most four
|
|
108
|
+
per account a pass, so a backlog drains steadily rather than spending at
|
|
109
|
+
once. A paused account or workspace is not graded.
|
|
110
|
+
- **The checks.** `contains:`, `not-contains:` and `judge:`, cheap ones
|
|
111
|
+
first; the judge only runs when they pass. `file:` and `run:` are refused
|
|
112
|
+
(`foldrun check` and the deploy say so): a finished run's sandbox is gone,
|
|
113
|
+
and they would act on the platform's host instead. `matches:` is refused
|
|
114
|
+
too — a regular expression over whatever a run said, on the platform's
|
|
115
|
+
scheduler, could stall it. A `task:` in a grader does nothing and is
|
|
116
|
+
refused as well.
|
|
117
|
+
- **A judge that does not answer** (a spent usage cap, a provider outage)
|
|
118
|
+
leaves the run *not decided*, which is not a failure. It is tried again
|
|
119
|
+
after an hour, three times at most.
|
|
120
|
+
- **What it costs.** One judge call per `judge:` line per graded run — at
|
|
121
|
+
most five lines in a grader — on the account's own model key like any
|
|
122
|
+
eval judge. A paused account or workspace is not graded, by the pass or
|
|
123
|
+
by **Grade a run**. The card shows what the
|
|
124
|
+
judges cost; the runs' own cost is on the runs.
|
|
125
|
+
|
|
126
|
+
The grades are on the Evals page: each grader's card has its pass rate over
|
|
127
|
+
7 and 30 days, a bar per day, the recent failures with the check that failed
|
|
128
|
+
and the judge's reason, and **Grade a run** to grade any finished run now,
|
|
129
|
+
sampled or not (a run graded again shows its newer grade). A run's own page
|
|
130
|
+
lists every grade it got. From the CLI, `foldrun eval grades <eval> --to
|
|
131
|
+
<workspace>` (`--status fail`, `--days 7`) and `foldrun eval grade <eval>
|
|
132
|
+
--run <id> --to <workspace>`; the API is `GET …/evals/<eval>/grades` and
|
|
133
|
+
`POST …/evals/<eval>/grade`. Grades live in `evals/.results/grades/`, kept
|
|
134
|
+
90 days; a deploy never touches them.
|
|
135
|
+
|
|
136
|
+
A grader never runs as cases: not on deploy, not from Run, not from
|
|
137
|
+
`foldrun eval`.
|
|
138
|
+
|
|
139
|
+
## Proposed fixes
|
|
140
|
+
|
|
141
|
+
A failure that keeps happening should turn into a change someone can look
|
|
142
|
+
at, not sit on a card. A grader with `improve: true` (or `improve: 3` — how
|
|
143
|
+
many failures) opens a **proposal** on its own once that many graded runs
|
|
144
|
+
have failed and no proposal has covered them, or once the oldest such failure
|
|
145
|
+
is a week old:
|
|
146
|
+
|
|
147
|
+
```yaml
|
|
148
|
+
trigger: runs
|
|
149
|
+
improve: true # 5 failed grades nobody has looked at → a proposal
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
- **The assistant drafts it.** It is given a short digest of the failures —
|
|
153
|
+
at most ten, each the run, the check and the judge's reason — and reads
|
|
154
|
+
the agent, the flow, their skills and the runs themselves. It is asked for
|
|
155
|
+
the smallest change that fixes the cause, never to loosen a check, and to
|
|
156
|
+
propose nothing when the fault is not in the files (a missing secret, an
|
|
157
|
+
outage).
|
|
158
|
+
- **Only agents, flows and skills.** A proposal may change
|
|
159
|
+
`agents/<name>/agent.md`, `flows/<name>.md` and `skills/<name>/…`; anything
|
|
160
|
+
else the assistant proposes is left out, and the proposal says so.
|
|
161
|
+
- **Nothing is written until you apply it.** Apply checks that every file
|
|
162
|
+
still reads what the proposal was drawn against and can be written, then
|
|
163
|
+
writes them all, each a revision by you (so History can take it back). A
|
|
164
|
+
file someone changed since is refused — dismiss it and propose again. A
|
|
165
|
+
check error the deploy would refuse blocks Apply.
|
|
166
|
+
- **At most one open proposal per eval**, and the automatic ones at most one
|
|
167
|
+
a day per grader. A failure is *looked at* once a proposal covers it —
|
|
168
|
+
drafting, ready, applied or dismissed; a draft that failed releases its
|
|
169
|
+
failures so they count again. A draft that has not finished in half an
|
|
170
|
+
hour has failed.
|
|
171
|
+
- **What it costs**: one assistant reply, paid the way Ask AI is.
|
|
172
|
+
|
|
173
|
+
They are on the eval's card under **Proposed fixes**: what failed, the
|
|
174
|
+
assistant's reasoning, a diff per file, and Apply or Dismiss. **Propose a
|
|
175
|
+
fix** asks for one now — for a grader, from the failures no proposal has
|
|
176
|
+
covered; for an eval that runs cases, from its last result's failed cases.
|
|
177
|
+
Ready proposals are in your [inbox](runs#your-inbox). From the CLI: `foldrun
|
|
178
|
+
eval improve <eval> --to <workspace>` asks for one and waits for the draft;
|
|
179
|
+
`foldrun proposals --to <workspace>` (`--status ready`), `foldrun proposals
|
|
180
|
+
show|apply|dismiss <id> --to <workspace>`. The API: `POST
|
|
181
|
+
…/evals/<eval>/improve`, `GET …/proposals`, `GET`/`POST …/proposals/<id>`.
|
|
182
|
+
|
|
183
|
+
## Comparing models
|
|
184
|
+
|
|
185
|
+
"Could this step run on the fast model?" is answered from the cases you
|
|
186
|
+
already have. **Compare models** on an eval's card runs every case under two
|
|
187
|
+
to four settings — a model (`fast`, `default`, `max`, or a model id), an
|
|
188
|
+
effort, and for a flow eval either every step or one agent's — and puts them
|
|
189
|
+
side by side: how many passed, what the runs and their judges cost, the cost
|
|
190
|
+
and time per run, and each case under each setting, so you see where they
|
|
191
|
+
differ.
|
|
192
|
+
|
|
193
|
+
- **The model is forced on the steps**, so it wins over the agent's own
|
|
194
|
+
`model:` and the flow's defaults. "every step" forces all of a flow's
|
|
195
|
+
steps; naming an agent forces only that agent's.
|
|
196
|
+
- **Every run is a test run**, whatever the eval or the flow says about
|
|
197
|
+
`live:` — a comparison is never the reason something was sent.
|
|
198
|
+
- **The judge is the eval's own** and the same for every setting: what
|
|
199
|
+
changes is the subject, never the grader.
|
|
200
|
+
- **`repeat`** (1–5) runs each case more than once; one run each tells you
|
|
201
|
+
little where the model is not deterministic. Repeats interleave — each
|
|
202
|
+
setting once, then again — so a provider that slows down mid-way slows
|
|
203
|
+
every setting alike.
|
|
204
|
+
- **Bounded twice**: at most 60 runs (cases × settings × repeat), and a spend
|
|
205
|
+
limit (default $5, at most $100) — once the runs and judges have spent it,
|
|
206
|
+
the rest are recorded as not run and the result says it stopped early.
|
|
207
|
+
- **Time per run** is from a worker picking the run up to its end; time in
|
|
208
|
+
the queue is left out, so a busy account does not read as a slow model.
|
|
209
|
+
- **What it writes**: the comparison's own result, kept with the last 20 —
|
|
210
|
+
never the eval's latest result or its history. One comparison of an eval
|
|
211
|
+
at a time.
|
|
212
|
+
|
|
213
|
+
It runs on the platform and can take as long as its runs do; leave the page
|
|
214
|
+
and come back. From the CLI: `foldrun eval compare <eval> --models
|
|
215
|
+
fast,default,max --to <workspace>` (`--models fast:low,max:high` for
|
|
216
|
+
efforts, `--agent <name>`, `--repeat 3`, `--max-usd 10`; `--list` and `--run
|
|
217
|
+
<id>` read the kept ones). The API is `POST …/evals/<eval>/compare`, then
|
|
218
|
+
`GET …/compare?run=<id>`. A grader (`trigger: runs`) has no cases, so it
|
|
219
|
+
cannot be compared, and neither can an eval with a `run:` check — a
|
|
220
|
+
comparison repeats every check many times, and `run:` executes a command.
|
|
221
|
+
|
|
72
222
|
## Promoting a run
|
|
73
223
|
|
|
74
224
|
A run is a case you already have — the task is on record. A run that worked
|
package/docs/flows.md
CHANGED
|
@@ -106,7 +106,7 @@ begins.
|
|
|
106
106
|
| `schema:` | with `output: json` — the JSON Schema the value must satisfy: one-line JSON, a YAML block under the key, or a file under the workspace |
|
|
107
107
|
| `model:` | override the tier for this step |
|
|
108
108
|
| `effort:` | override how hard it thinks for this step |
|
|
109
|
-
| `retry:` | attempts after the first, clamped to 5. Each retry waits — 15 s, 30 s, 1 m, 2 m, 4 m, jittered — holding no sandbox while it waits; a stop ends the wait. An attempt the cluster ended for want of memory or disk (OOMKilled, Evicted) is retried one `size:` class up. An `outward` step whose check failed after its tools ran is not retried — the retry would send again; `check` and `deploy` warn on `retry:` on an outward step, because a failure mid-send is still retried |
|
|
109
|
+
| `retry:` | attempts after the first, clamped to 5. Each retry waits — 15 s, 30 s, 1 m, 2 m, 4 m, jittered — holding no sandbox while it waits; a stop ends the wait. An attempt the cluster ended for want of memory or disk (OOMKilled, Evicted) is retried one `size:` class up. A step refused because the model usage cap is reached is not retried: the window resets in hours, not within the waits. An `outward` step whose check failed after its tools ran is not retried — the retry would send again; `check` and `deploy` warn on `retry:` on an outward step, because a failure mid-send is still retried |
|
|
110
110
|
| `timeout:` | seconds, or `90s` / `15m` / `4h` / `3d` — **the only clock there is** |
|
|
111
111
|
| `max_turns:` | the most model turns before the step is stopped (1 to 500) — the third bound beside `budget:` and `timeout:` |
|
|
112
112
|
| `limits:` | this step's call limits, on one line — `limits: {web.search: 10, calls: 50}` — over the agent's, key by key. A call past one is refused, never run ([Tools](tools#limits--how-many-calls-a-step-may-make)) |
|
|
@@ -115,7 +115,7 @@ begins.
|
|
|
115
115
|
| `ask:` | the same gate carrying a question; the typed answer reaches the prompt (a plain approval with no note reaches it as "approved with no note") |
|
|
116
116
|
| `preview:` | what the gate shows: paths under `storage/` (a `workspace/storage/` prefix is accepted), comma-separated, globs allowed — `draft/*.mdx, draft/images/*.webp`; at most 12 files |
|
|
117
117
|
| `wait:` | `3d` (`s`/`m`/`h`/`d`, capped at 30 days) or `event` — the run page shows the event URL and can release it with a payload, recorded as released by you (approve with `payload`) |
|
|
118
|
-
| `on-fail:` | another agent takes the step over, with the failure as context — and, for a gated step, the person's approval and answer, which it acts within. The rescuer owes what the step owed: its `verify:`, its `output:`, and a `loop:` step's `until:` marker (a loop that runs out of cycles goes to its rescuer too). A `verify:` that accepts `BLOCKED` never fails, so it never reaches the rescuer |
|
|
118
|
+
| `on-fail:` | another agent takes the step over, with the failure as context — and, for a gated step, the person's approval and answer, which it acts within. The rescuer owes what the step owed: its `verify:`, its `output:`, and a `loop:` step's `until:` marker (a loop that runs out of cycles goes to its rescuer too). A `verify:` that accepts `BLOCKED` never fails, so it never reaches the rescuer. A step refused by the model supply itself — a spent usage window, a credential refused — is not handed to a rescuer on the same supply (the same `provider:`, or both on the workspace's): it would be refused the same way, and the issue is filed as the cap or the credential |
|
|
119
119
|
| `delegate:` | the step's agent picks who runs next, from this set |
|
|
120
120
|
|
|
121
121
|
### What is retried without a `retry:`
|
|
@@ -329,6 +329,7 @@ normal outcome, recorded as one.
|
|
|
329
329
|
| `path` | the `storage/` prefix to watch, for `trigger: storage` |
|
|
330
330
|
| `url` / `every` | what to poll and how often (default 15m), for `trigger: watch` |
|
|
331
331
|
| `signature` / `signing_secret` | `github` \| `stripe` \| `slack` \| `hmac`, and the vault entry to check it against |
|
|
332
|
+
| `filter` | for a `webhook`, `email`, `flow` or `storage` trigger: the fields a delivery must have to start a run — see [Which deliveries start a run](#which-deliveries-start-a-run) |
|
|
332
333
|
| `model` / `effort` | defaults for every step |
|
|
333
334
|
| `budget` | the most one run may spend, in USD — a number, or `unlimited`; unset is no limit |
|
|
334
335
|
| `overlap` | `skip` or `queue` when a run is already live |
|
|
@@ -398,6 +399,66 @@ A flow may not chain on itself. First sightings for `watch` record without
|
|
|
398
399
|
firing, and a `once` instant more than six hours gone at first sighting is
|
|
399
400
|
recorded and never fired.
|
|
400
401
|
|
|
402
|
+
### Which deliveries start a run
|
|
403
|
+
|
|
404
|
+
`filter:` says which deliveries are this flow's. A GitHub hook for pull
|
|
405
|
+
requests posts for drafts, bot pushes and label changes alike; without a
|
|
406
|
+
filter each one is a run whose first step only says "not this one". With
|
|
407
|
+
one, a delivery that does not match starts nothing — no sandbox, no model
|
|
408
|
+
call — and the Triggers page lists it as *did not match the filter*, with the
|
|
409
|
+
condition that held it back.
|
|
410
|
+
|
|
411
|
+
```yaml
|
|
412
|
+
trigger: webhook
|
|
413
|
+
filter:
|
|
414
|
+
action: [opened, reopened] # any of these values
|
|
415
|
+
pull_request:
|
|
416
|
+
draft: false # exactly this value
|
|
417
|
+
base:
|
|
418
|
+
ref: { starts_with: release/ } # text tests: starts_with, ends_with, contains
|
|
419
|
+
sender:
|
|
420
|
+
type: { not_in: [Bot] } # none of these
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
What it is matched against depends on the trigger:
|
|
424
|
+
|
|
425
|
+
| trigger | the fields |
|
|
426
|
+
|---|---|
|
|
427
|
+
| `webhook` | the request's JSON body — a body that is not JSON matches no filter |
|
|
428
|
+
| `email` | `from`, `to`, `subject`, `text` |
|
|
429
|
+
| `flow` | `run`, `flow`, `status`, `ending` (`completed`, `failed` or `blocked`), `verdict`, `summary` |
|
|
430
|
+
| `storage` | `by` (who wrote), `paths` (the files that landed under `path:`) |
|
|
431
|
+
|
|
432
|
+
The rules, all of them:
|
|
433
|
+
|
|
434
|
+
- Every key must match. A nested map matches the nested field.
|
|
435
|
+
- A list is the values allowed — any of them. A plain value is the one value.
|
|
436
|
+
- An operator map says it explicitly: `in`, `not_in`, `exists` (`true` or
|
|
437
|
+
`false`), and the text tests `starts_with`, `ends_with` and `contains`.
|
|
438
|
+
There are no regular expressions: a sender controls the body, and a
|
|
439
|
+
pattern it could make backtrack has no place before a run starts.
|
|
440
|
+
- Strings compare exactly and case-sensitively; numbers numerically (`1`
|
|
441
|
+
equals `1.0`; the text `"1"` does not); `true`, `false` and `null` as
|
|
442
|
+
themselves.
|
|
443
|
+
- A missing field fails a value, `in` and the text tests, and passes
|
|
444
|
+
`not_in`. `exists: false` passes only when it is missing.
|
|
445
|
+
- A list in the delivery: a nested map matches when any element does;
|
|
446
|
+
`in` when any element is allowed, `not_in` when none is.
|
|
447
|
+
- It fails closed. A filter `foldrun check` cannot read is a check error,
|
|
448
|
+
and if one reaches the platform it matches nothing — a typo holds
|
|
449
|
+
deliveries back instead of letting all of them through. At most 10 levels
|
|
450
|
+
deep, 64 conditions, 32 values in a list.
|
|
451
|
+
|
|
452
|
+
A filtered delivery counts toward nothing: not `idempotency:`, not
|
|
453
|
+
`throttle:`, not `debounce:`, not `disable_after:`. A paused account or
|
|
454
|
+
workspace is still told it is paused first. `filter:` on a `schedule`,
|
|
455
|
+
`once`, `watch` or `manual` flow does nothing, and `foldrun check` says so.
|
|
456
|
+
|
|
457
|
+
Try a delivery without sending one: the Triggers page has a **Test filter**
|
|
458
|
+
box for each filtered flow, `foldrun flow filter <flow> --payload body.json`
|
|
459
|
+
reads the local file (add `--to <workspace>` for the deployed one), and the
|
|
460
|
+
API is `POST …/flows/<flow>/filter { payload }`.
|
|
461
|
+
|
|
401
462
|
### How many of those starts are real
|
|
402
463
|
|
|
403
464
|
A trigger says what happens. These four say how much of it becomes a run,
|
package/docs/hooks.md
CHANGED
|
@@ -105,8 +105,8 @@ hook can narrow what an agent does but never widen it.
|
|
|
105
105
|
## What a hook does not see
|
|
106
106
|
|
|
107
107
|
Tools the model provider runs on its own side never pass foldrun's dispatch.
|
|
108
|
-
With `web: {search: anthropic}` (or another provider's search
|
|
109
|
-
|
|
108
|
+
With `web: {search: anthropic}` (or another provider's search) the search
|
|
109
|
+
runs at the provider, and a `tool.call` hook
|
|
110
110
|
cannot refuse or rewrite it. foldrun's own `web` tool, the default, goes
|
|
111
111
|
through hooks like every other tool. `foldrun check` warns when an agent with
|
|
112
112
|
a `tool.call` hook uses a provider's own search.
|
package/docs/network.md
CHANGED
|
@@ -110,8 +110,12 @@ Every program in the sandbox reaches the internet through the **egress
|
|
|
110
110
|
proxy**: the pod's `HTTPS_PROXY` and `HTTP_PROXY` name it, with the step's
|
|
111
111
|
own lease as the password, and `NODE_USE_ENV_PROXY=1` turns that on for Node.
|
|
112
112
|
curl, Python's `requests` and `httpx`, pip, npm, git, apt and the Agent SDK
|
|
113
|
-
all read those variables.
|
|
114
|
-
|
|
113
|
+
all read those variables. A browser your own script launches does not:
|
|
114
|
+
Chromium ignores a password written in the proxy variable, so every page
|
|
115
|
+
fails `ERR_INVALID_AUTH_CREDENTIALS`. Hand Playwright the proxy yourself —
|
|
116
|
+
read `HTTPS_PROXY` with `new URL()` and pass `proxy: { server, username,
|
|
117
|
+
password }` to `launch()` (the `web` tool's browsers already do). The proxy
|
|
118
|
+
holds the step's list and checks every connection against it:
|
|
115
119
|
|
|
116
120
|
- a site **off the list** is refused — `403`, saying which site and what to
|
|
117
121
|
add — and the refusal is a line in the run trace;
|
package/docs/runs.md
CHANGED
|
@@ -266,6 +266,34 @@ Each agent also has an **Activity** page: every step it has ever run, across
|
|
|
266
266
|
flows and direct runs, with that step's own headline. It is where "how has
|
|
267
267
|
this agent been going" is answered without opening runs one at a time.
|
|
268
268
|
|
|
269
|
+
## Your inbox
|
|
270
|
+
|
|
271
|
+
**Inbox**, top right and under View in the account's sidebar, is what waits
|
|
272
|
+
on *you*, across every workspace you may see, in one list: gates to approve,
|
|
273
|
+
questions agents are asking, open [issues](#issues), [proposed
|
|
274
|
+
fixes](evals#proposed-fixes) to review, and your own runs that went wrong.
|
|
275
|
+
|
|
276
|
+
Three tabs that never mix:
|
|
277
|
+
|
|
278
|
+
| tab | what is in it |
|
|
279
|
+
|---|---|
|
|
280
|
+
| **For me** | assigned to you: you are named in the flow's `approvers:`, you own the flow or agent (`owner:`, down to the workspace's and the account's), or you asked for the proposal |
|
|
281
|
+
| **Started by me** | runs you started: one that failed or ended BLOCKED or BAD in the last week, a gate on one (someone else must decide it — your own approval is refused), a question one is asking |
|
|
282
|
+
| **Team** | everything else waiting in your workspaces, for whoever picks it up — a gate anyone may decide, an issue nobody owns |
|
|
283
|
+
|
|
284
|
+
Nothing is kept in it: each item is read from where it lives and leaves on
|
|
285
|
+
its own when it is done there — the gate decided, the issue resolved, the
|
|
286
|
+
fix applied. A gate you may not decide (`approvers:` names others) is not in
|
|
287
|
+
yours at all. What *is* kept is what you did to your own view: an item you
|
|
288
|
+
opened is read, and **Set aside** hides one from you — it is still waiting
|
|
289
|
+
for everyone else, and the gate is not decided. An item names its occurrence,
|
|
290
|
+
so an issue that happens again after you set it aside comes back. The count
|
|
291
|
+
on the top bar is what is unread in For me and Started by me.
|
|
292
|
+
|
|
293
|
+
`foldrun inbox` prints the same (`--team`, `--all`, `--kind approval`); a key
|
|
294
|
+
reads the inbox of the person who minted it, within the key's own workspaces.
|
|
295
|
+
The API is `GET /api/me/inbox`.
|
|
296
|
+
|
|
269
297
|
## Gates
|
|
270
298
|
|
|
271
299
|
A step marked `2!` or carrying `approve: true` / `ask: <question>` parks the
|