foldrun 2.5.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/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/four-jobs.md CHANGED
@@ -38,7 +38,7 @@ top. Either way works, and the two can be mixed in one file.
38
38
  | File | `governance:` | `guardrail:` | `observability:` | `evaluation:` |
39
39
  |---|---|---|---|---|
40
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` | — | — |
41
+ | an agent | `owner`, `budget` | `limits`, `network`, `hooks`, `disallowedTools`, `permissionMode`, `secrets`, `sandbox` | — | — |
42
42
  | a flow | `owner`, `approvers`, `approve_within`, `budget`, `priority` | `throttle`, `debounce`, `overlap`, `disable_after`, `live` | `issues`, `sla` | — |
43
43
 
44
44
  A field that does none of the four, such as `name`, `model`, `tools`,
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, or
109
- `fetch: anthropic`) the search runs at the provider, and a `tool.call` hook
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. The proxy holds the step's list and checks every
114
- connection against it:
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
package/docs/secrets.md CHANGED
@@ -201,7 +201,14 @@ the result as an `oauth2` secret; the platform refreshes it. An agent names
201
201
  it in `secrets:` like any other.
202
202
 
203
203
  You bring the OAuth app: register it once with the provider (its client id
204
- and secret, and the redirect URL the form shows), save it, and connect. Every
204
+ and secret, and the redirect URL the form shows), save it, and connect. The
205
+ form asks for the provider's own authorize and token URLs — foldrun keeps no
206
+ list of providers — and **Extra authorize parameters**, one `name=value` per
207
+ line, for whatever else the provider's docs say its authorize URL takes:
208
+ Google issues a refresh token only with `access_type=offline` and
209
+ `prompt=consent`, and without one the connection stops when the first access
210
+ token expires. The flow's own parameters (`client_id`, `redirect_uri`,
211
+ `state`, `scope`, PKCE) cannot be set there. Every
205
212
  later connection can use the same saved app — choose it under **App** and
206
213
  type the scopes this connection needs, as the provider spells them. Each
207
214
  connection keeps its own scopes, so one app serves any number of APIs from
package/docs/tools.md CHANGED
@@ -594,7 +594,7 @@ web:
594
594
  | `parallel` | extract | full-page markdown; handles JavaScript pages and PDFs | up to 20 | `PARALLEL_API_KEY` |
595
595
  | `zyte` | **unblocker** | the page rendered in a real browser behind a proxy pool; pays per success | 1 | `ZYTE_API_KEY_BASIC` |
596
596
  | `scrapingbee` | **unblocker** | the page rendered in a real browser (`render_js`) behind a proxy pool; credits per call | 1 | `SCRAPINGBEE_API_KEY` |
597
- | `anthropic` | model provider | a small model's reading of the page, on Anthropic's servers | 1 | — |
597
+ | `anthropic` | ours | accepted, and answered by foldrun's own fetch: Claude Code's `WebFetch` it used to hand over to fails its domain check behind the egress proxy | 1, or up to 20 with `urls=` | — |
598
598
 
599
599
  A provider answers `markdown`, `text` and `html`. The modes only a raw read
600
600
  can answer — `meta`, `links`, `raw` — are always read directly, and the tool
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "foldrun",
3
- "version": "2.5.0",
3
+ "version": "2.7.0",
4
4
  "description": "Agents are just folders. Write them, check them, run them — on your machine.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "type": "module",
@@ -11,7 +11,7 @@
11
11
  "node": ">=22"
12
12
  },
13
13
  "dependencies": {
14
- "@foldrun/core": "^2.7.0"
14
+ "@foldrun/core": "^2.11.0"
15
15
  },
16
16
  "files": [
17
17
  "bin",
@@ -21,7 +21,7 @@
21
21
  ],
22
22
  "scripts": {
23
23
  "e2e": "FOLDRUN_E2E=1 FOLDRUN_DATA=$(mktemp -d /tmp/foldrun-e2e-XXXXXX) node --test --test-timeout=300000 tests/e2e.test.ts",
24
- "test": "FOLDRUN_DATA=$(mktemp -d /tmp/foldrun-test-XXXXXX) node --test tests/cli-account.test.ts tests/model-credential.test.ts tests/cli-approvals.test.ts tests/cli-issues.test.ts tests/cli-capture.test.ts tests/cli-stop.test.ts tests/cli-account-settings.test.ts tests/cli-schedule.test.ts tests/cli-billing.test.ts tests/cli-storage.test.ts tests/cli-scaffold.test.ts tests/cli-secrets.test.ts tests/cli-site-login.test.ts tests/cli-check-library.test.ts tests/cli-check-timezone.test.ts tests/cli-check-frontmatter.test.ts tests/cli-agent-import.test.ts tests/cli-check-subagents.test.ts tests/cli-connect.test.ts tests/cli-accounts.test.ts tests/cli-remote.test.ts tests/cli-stream.test.ts tests/cli-doctor.test.ts tests/cli-tool-test.test.ts tests/cli-agent-run.test.ts tests/cli-check-remote.test.ts tests/cli-shares.test.ts tests/cli-triggers.test.ts tests/cli-providers.test.ts tests/cli-rerun-put.test.ts tests/cli-guide.test.ts tests/cli-runtimes.test.ts tests/cli-deploy-safety.test.ts tests/cli-operator.test.ts tests/cli-flow-patterns.test.ts tests/cli-flow-rm-step.test.ts tests/cli-flow-dup-step.test.ts tests/cli-flow-write-stale.test.ts tests/cli-agent-link-stale.test.ts tests/cli-parity.test.ts tests/cli-invoke-inputs.test.ts tests/cli-audit-keys.test.ts tests/cli-version.test.ts tests/cli-check-web-warnings.test.ts tests/cli-report-image.test.ts tests/cli-report-cache.test.ts tests/cli-check-workspace-paths.test.ts tests/cli-parity-dashboard.test.ts tests/cli-api-standards.test.ts tests/cli-backups-onboarding.test.ts tests/cli-check-limits.test.ts tests/cli-gallery.test.ts tests/cli-status-platform.test.ts tests/cli-webhooks-notifications.test.ts tests/cli-flags.test.ts tests/cli-api-coverage.test.ts tests/cli-hooks.test.ts tests/cli-release.test.ts tests/cli-terminal.test.ts tests/cli-network.test.ts tests/cli-assistant.test.ts tests/cli-check-scan.test.ts tests/cli-published.test.ts tests/cli-init-target.test.ts",
24
+ "test": "FOLDRUN_DATA=$(mktemp -d /tmp/foldrun-test-XXXXXX) node --test tests/cli-account.test.ts tests/model-credential.test.ts tests/cli-approvals.test.ts tests/cli-issues.test.ts tests/cli-capture.test.ts tests/cli-stop.test.ts tests/cli-account-settings.test.ts tests/cli-schedule.test.ts tests/cli-billing.test.ts tests/cli-storage.test.ts tests/cli-scaffold.test.ts tests/cli-secrets.test.ts tests/cli-site-login.test.ts tests/cli-check-library.test.ts tests/cli-check-timezone.test.ts tests/cli-check-frontmatter.test.ts tests/cli-agent-import.test.ts tests/cli-check-subagents.test.ts tests/cli-connect.test.ts tests/cli-accounts.test.ts tests/cli-remote.test.ts tests/cli-stream.test.ts tests/cli-doctor.test.ts tests/cli-tool-test.test.ts tests/cli-agent-run.test.ts tests/cli-check-remote.test.ts tests/cli-shares.test.ts tests/cli-triggers.test.ts tests/cli-providers.test.ts tests/cli-rerun-put.test.ts tests/cli-guide.test.ts tests/cli-runtimes.test.ts tests/cli-deploy-safety.test.ts tests/cli-operator.test.ts tests/cli-flow-patterns.test.ts tests/cli-flow-rm-step.test.ts tests/cli-flow-dup-step.test.ts tests/cli-flow-write-stale.test.ts tests/cli-agent-link-stale.test.ts tests/cli-agent-link-isolated.test.ts tests/cli-parity.test.ts tests/cli-invoke-inputs.test.ts tests/cli-audit-keys.test.ts tests/cli-version.test.ts tests/cli-check-web-warnings.test.ts tests/cli-report-image.test.ts tests/cli-report-cache.test.ts tests/cli-check-workspace-paths.test.ts tests/cli-parity-dashboard.test.ts tests/cli-api-standards.test.ts tests/cli-backups-onboarding.test.ts tests/cli-check-limits.test.ts tests/cli-gallery.test.ts tests/cli-status-platform.test.ts tests/cli-webhooks-notifications.test.ts tests/cli-flags.test.ts tests/cli-api-coverage.test.ts tests/cli-hooks.test.ts tests/cli-release.test.ts tests/cli-terminal.test.ts tests/cli-network.test.ts tests/cli-assistant.test.ts tests/cli-check-scan.test.ts tests/cli-published.test.ts tests/cli-init-target.test.ts tests/cli-flow-filter.test.ts tests/cli-eval-grades.test.ts tests/cli-eval-compare.test.ts tests/cli-proposals.test.ts tests/cli-inbox.test.ts",
25
25
  "release": "node scripts/release.mjs",
26
26
  "typecheck": "tsc -p tsconfig.json",
27
27
  "docs:sync": "node scripts/sync-docs.mjs",
@@ -47,5 +47,8 @@
47
47
  "@types/node": "^26.6.4",
48
48
  "gray-matter": "^4.0.3",
49
49
  "typescript": "^6.0.3"
50
+ },
51
+ "overrides": {
52
+ "zod": "4.6.7"
50
53
  }
51
54
  }