foldrun 2.8.1 → 2.9.1
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 +1 -0
- package/docs/agents.md +70 -6
- package/docs/api.md +3 -2
- package/docs/budgets-and-billing.md +13 -6
- package/docs/four-jobs.md +2 -2
- package/docs/self-hosting.md +131 -21
- package/package.json +2 -2
- package/src/commands.mjs +40 -1
- package/src/guide.mjs +1 -1
package/bin/foldrun.mjs
CHANGED
|
@@ -178,6 +178,7 @@ const HELP = `foldrun — agents are just folders
|
|
|
178
178
|
foldrun rerun --status failed [--since 24h] [--until 2h] [--flow f] rerun every run a filter matches, from --from <n> (default 1) — lists, asks (--dry-run, --to)
|
|
179
179
|
foldrun observe where the account fails, retries and spends, per workspace (--to <workspace> in full, --since <days>, --json)
|
|
180
180
|
foldrun usage what the account consumed, and what it was charged week by week (--days <n>, --json)
|
|
181
|
+
foldrun usage compute the cores and GiB its running steps may hold together and in one step, and hold now (--json)
|
|
181
182
|
foldrun schedule every flow that fires on a clock, its cron line and the next times (--to <workspace>)
|
|
182
183
|
foldrun schedule tick fire whatever is due now — Settings' "Run scheduler now"
|
|
183
184
|
foldrun guide write the coding-agent rules: a block in AGENTS.md and CLAUDE.md importing it (--check, --print)
|
package/docs/agents.md
CHANGED
|
@@ -346,14 +346,78 @@ What holds:
|
|
|
346
346
|
| field | |
|
|
347
347
|
|---|---|
|
|
348
348
|
| `size` | `small` \| `large` (default) \| `heavy` — the sandbox reservation |
|
|
349
|
+
| `resources` | `{cpu, memory, disk}` in numbers, over the `size` class's figures one key at a time |
|
|
349
350
|
| `runtime` | language runtimes, packages and Debian system packages the agent's own scripts need |
|
|
350
351
|
|
|
351
|
-
`size` is a price
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
`
|
|
356
|
-
|
|
352
|
+
`size` is a price and a limit. Each class gives the sandbox a number of cores,
|
|
353
|
+
an amount of memory and an amount of disk, and its seconds are billed at those
|
|
354
|
+
figures:
|
|
355
|
+
|
|
356
|
+
| `size` | cores | memory | disk |
|
|
357
|
+
|---|---|---|---|
|
|
358
|
+
| `small` | 1 | 1Gi | 2Gi |
|
|
359
|
+
| `large` | 2 | 2Gi | 8Gi |
|
|
360
|
+
| `heavy` | 4 | 8Gi | 20Gi |
|
|
361
|
+
|
|
362
|
+
These are the defaults; an install can set its own
|
|
363
|
+
([environment](environment)), and the step's log says what it held.
|
|
364
|
+
|
|
365
|
+
All three are **hard ceilings**:
|
|
366
|
+
|
|
367
|
+
- **Memory:** a step over it is killed, because memory is not compressible.
|
|
368
|
+
On a single-node host, that protects every other tenant.
|
|
369
|
+
- **Cores:** CPU is capped at the class's figure, so a step cannot take the
|
|
370
|
+
whole machine from the steps beside it. When every core is busy, a bigger
|
|
371
|
+
class gets a bigger share.
|
|
372
|
+
- **Disk:** a step over it is evicted rather than the node filling up.
|
|
373
|
+
This is the step's scratch space, not the account's storage: the
|
|
374
|
+
sandbox's copy of the workspace and whatever the step writes while it
|
|
375
|
+
runs. When the step ends, the files it changed are copied back to the
|
|
376
|
+
workspace and the sandbox, scratch space included, is deleted.
|
|
377
|
+
|
|
378
|
+
Pick `small` for an agent that mostly waits on an API, `heavy` for one that
|
|
379
|
+
renders a browser or holds real data. The step's `resources:` line reports CPU
|
|
380
|
+
busy time, **throttled** time (how long the cap held it back), and peak memory.
|
|
381
|
+
A step that is throttled for much of its run wants a bigger class; one whose
|
|
382
|
+
peak memory is a fraction of the class's figure can take a smaller one.
|
|
383
|
+
|
|
384
|
+
### `resources:` — your own numbers
|
|
385
|
+
|
|
386
|
+
When no class fits, write the numbers. Each key replaces that one figure of
|
|
387
|
+
the class; the rest stay the class's:
|
|
388
|
+
|
|
389
|
+
```yaml
|
|
390
|
+
size: large
|
|
391
|
+
resources:
|
|
392
|
+
cpu: 1 # cores, 0.5 to 8, in halves
|
|
393
|
+
memory: 6Gi # 256Mi to 16Gi, in steps of 256Mi
|
|
394
|
+
disk: 30Gi # whole GiB, 1Gi to 40Gi
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
Examples:
|
|
398
|
+
|
|
399
|
+
- One core and a lot of memory, for an agent that loads a big CSV.
|
|
400
|
+
- A lot of disk, for one that renders video.
|
|
401
|
+
|
|
402
|
+
The figures are hard limits and what the step is billed at, exactly as for a
|
|
403
|
+
class. The step's header on the run page shows what it held ("1 vCPU ·
|
|
404
|
+
6 GiB"), with the class and your `resources:` in its tooltip.
|
|
405
|
+
|
|
406
|
+
The keys are bounded and stepped the way sandbox platforms sell them, so
|
|
407
|
+
steps pack onto a machine predictably. `foldrun check` reports a value
|
|
408
|
+
outside them (`config/invalid-value`); at run time that one key is dropped,
|
|
409
|
+
the class's figure is used, and the step's log says so. Sizes need a unit:
|
|
410
|
+
`4Gi` or `512Mi`, never a bare `4`, and not `4G` (Kubernetes reads `G` as
|
|
411
|
+
decimal, Docker as binary).
|
|
412
|
+
|
|
413
|
+
`resources:` may also be written inside `guardrail:` ([four jobs](four-jobs)).
|
|
414
|
+
When a retry moves a step up a class after it ran out of memory or disk, each
|
|
415
|
+
of your figures is raised to the bigger class's, so the retry never runs
|
|
416
|
+
smaller than the class it moved to.
|
|
417
|
+
|
|
418
|
+
Your account's plan may also cap what one step can hold, and how much all of
|
|
419
|
+
an account's running steps hold together
|
|
420
|
+
([budgets and billing](budgets-and-billing)).
|
|
357
421
|
|
|
358
422
|
```yaml
|
|
359
423
|
runtime:
|
package/docs/api.md
CHANGED
|
@@ -340,6 +340,7 @@ Reads return which secrets exist, never their values.
|
|
|
340
340
|
| `/api/team` | GET, POST | members with their roles and workspace scope; mint an invite — `role` (viewer \| editor \| admin, default editor), `workspaces` (names, or omit for all), `email` (bound into the invite: only that address can accept it; sent through the account's `RESEND_API_KEY` unless `send: false`). Returns `path` (the invite's relative URL), `expires`, `sent` and any `sendError` either way; the full link is in the mail |
|
|
341
341
|
| `/api/team/members/<id>` | PATCH, DELETE | change a member's role (`role`) and/or which workspaces they may open (`workspaces`: names, or null for all); remove them |
|
|
342
342
|
| `/api/team/transfer` | POST | hand the account to a member (`to`) — owner only; you become an admin |
|
|
343
|
+
| `/api/usage/compute` | GET | the account's compute: `compute` is `{ vcpu, memGiB, stepVcpu, stepMemGiB, source }` — the cores and GiB all its running steps may hold together, the most one step may hold (each `null` for no cap), and where the pool came from (`plan`, `account` — its own `compute:` in AGENTS.md, which can only lower it — `install`, or `null`); `inUse` is `{ vcpu, memGiB, steps }`, what its running steps' sandboxes hold now, from their records (a step still waiting for room holds nothing; a caller scoped to some workspaces sees theirs). A step past the pool waits for one of the account's steps to finish; one bigger than the pool or the step maximum fails at once ([agents](agents#resources-your-own-numbers)) |
|
|
343
344
|
| `/api/usage/history?days=` | GET | charged spend by flow, by agent and by workspace over time, read from the ledger rather than the run records — so it survives runs being pruned and reports what was billed rather than what was consumed. Buckets by week over three weeks, by day under. Each series carries its periods, its total, and the change between the last two COMPLETE periods, so a partial week never reads as a collapse. `unattributedUsd` is charges from before a line recorded its flow. A caller scoped to some workspaces sees only the charges in those; `days` 1–365, default 56 |
|
|
344
345
|
| `/api/account/model` | GET, PUT, DELETE | the account's own model key — what every step runs on unless an agent, workspace or the account's AGENTS.md names a `provider:`. GET: `{ set, provider, baseUrl, format, keyHint, runsOn, platformKeyAllowed }` — never the key, only its last four characters; `runsOn` is `account` (its own key), `platform` (the platform's credential, for the operator's accounts) or `none` (runs are refused until a key is set). PUT `{ provider, apiKey, baseUrl?, format? }` (needs `secrets:write`): `provider` is a preset name (anthropic, openrouter, openai, groq, … — see providers) or `custom` with an https `baseUrl` and a `format` (anthropic or openai); a Claude login token (sk-ant-oat…) is refused for any account not allowed the platform's credential. Stored as the account secrets `FOLDRUN_MODEL_PROVIDER`, `FOLDRUN_MODEL_API_KEY`, `FOLDRUN_MODEL_BASE_URL`, `FOLDRUN_MODEL_FORMAT`. DELETE removes them. Honours Idempotency-Key on PUT; audited as model.key_set / model.key_removed |
|
|
345
346
|
| `/api/account/providers` | GET, POST | GET: the model providers this account's files actually use — each one's preset name, where it is declared, the model a check would send — plus the last daily health check. POST forces one now, which spends one minimal request at each distinct endpoint, and needs the permission that runs |
|
|
@@ -358,7 +359,7 @@ Reads return which secrets exist, never their values.
|
|
|
358
359
|
| `/api/account/api` | GET, PATCH | how this account meets the API — GET `{ version: { current, pinned, effective, supported, supportMonths }, limits: { plan, windowSeconds, perCredential: { read, write }, perAccount: { read, write } }, usage }`. `usage` is the last 30 days — `{ days, versions: [{ version, requests, lastSeen }], deprecated: [{ route, requests, lastSeen, deprecatedAt, sunsetAt, link, callers: [{ credential, requests, lastSeen }] }] }`, `credential` naming a key by its label and prefix — or `null` when it could not be read; PATCH `{ version: "YYYY-MM-DD" \| null }` pins the version API-key requests without a `Foldrun-Version` header get (null: always current); the dashboard always gets current. `keys:manage`. See [api-usage](api-usage.md) |
|
|
359
360
|
| `/api/account/flags` | GET | the feature flags as they stand for this account — `{ flags: [{ name, description, owner, default, on, source, rollout }] }`, `source` being what decided it: `account` (an override for this account), `global` (the install-wide setting), `rollout` (a percentage of accounts, by a stable hash of account and flag; `rollout` is the percentage) or `default`. Read-only, every role; only the super admin changes a flag. See [Feature flags](feature-flags.md) |
|
|
360
361
|
| `/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 |
|
|
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 |
|
|
362
|
+
| `/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`, `compute` (`{ vcpu, memGiB }` — the account's own cap on what its running steps hold together, lower than the plan's; PATCH writes it as `compute: { vcpu, memory }`), `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 |
|
|
362
363
|
| `/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 |
|
|
363
364
|
| `/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 |
|
|
364
365
|
| `/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 |
|
|
@@ -427,7 +428,7 @@ X-Foldrun-Cli: 0.6.0 # the CLI released with this version (absent
|
|
|
427
428
|
|
|
428
429
|
| Route | Methods | |
|
|
429
430
|
|---|---|---|
|
|
430
|
-
| `/api/auth/signup` | POST | `email`, `password`, `account`, `invite`. Rate limited per address and per caller. With signups closed every address gets the same refusal, so the form cannot be used to discover which addresses are registered |
|
|
431
|
+
| `/api/auth/signup` | POST | `email`, `password`, `account`, `invite`, `setup`. An install with `FOLDRUN_SETUP_TOKEN` set creates its first account only with `setup` equal to it (403 otherwise); once an account exists the token does nothing. Rate limited per address and per caller. With signups closed every address gets the same refusal, so the form cannot be used to discover which addresses are registered |
|
|
431
432
|
| `/api/auth/login` | POST | `email`, `password` |
|
|
432
433
|
| `/api/auth/logout` | POST | ends the session |
|
|
433
434
|
| `/api/auth/session` | GET | who am I |
|
|
@@ -27,6 +27,7 @@ wins, and the step's error says which line set it: `budget: in the flow
|
|
|
27
27
|
file` or `budget: on the writer agent`.
|
|
28
28
|
| **runs at once** | `concurrency: 3` in the account's `AGENTS.md`, or Settings → Governance | the fourth run waits its turn in the queue — never refused, only later. Empty means the install's share (`FOLDRUN_ACCOUNT_CONCURRENCY`) or plan number (`FOLDRUN_PLAN_CONCURRENCY`, which applies only where billing is on), or no cap. Your own number can lower that, never raise it; a higher number comes with a plan |
|
|
29
29
|
| **the queue lane** | the plan's lane (`high` \| `normal` \| `low`); `priority: low` in the account's `AGENTS.md` lowers it, and `high` there counts only with a plan that gives it | when slots are short, accounts take turns, weighted by lane — high 3, normal 2, low 1: every account's first run goes before anyone's second, a fast lane takes three turns to a slow lane's one and never all of them, an account with runs going waits behind one with none, and a run that has waited ten minutes moves up a turn. A flow's own `priority:` orders runs within that. The cap above still holds |
|
|
30
|
+
| **compute at once** | the plan's cores and GiB (below); `compute: { vcpu: 4, memory: 8Gi }` in the account's `AGENTS.md`, or Settings → Governance, lowers it | what all the account's running steps may hold together — each step's cores and memory, its `size:` class's or its own `resources:` ([agents](agents#resources-your-own-numbers)). A step that would pass it waits to start, like a full node — `room: waiting to start — 6 of this account's 8 GiB is held by its running steps…` — and starts when one of the account's steps ends. A step that **alone** is bigger than the pool, or than the plan's most for one step, fails at once with that message: no step finishing would make room. Without a plan, the install's `FOLDRUN_ACCOUNT_VCPU` / `FOLDRUN_ACCOUNT_MEMORY_GIB`, or no cap. Your own number can lower it, never raise it. `foldrun usage compute` shows the pool and what is held now. Enforced on the hosted platform and any k8s install; a self-hosted Docker install caps each step at its class but not the pool |
|
|
30
31
|
| **room to start** | the install's `FOLDRUN_RUN_MEMORY_GIB` and `FOLDRUN_ACCOUNT_PODS` | a step whose sandbox would not fit, or whose account already has its most sandboxes running, waits to start — `room: waiting to start — …` on its trail — instead of failing. The wait is not billed; the run page's start-up chip shows it as `room`. A step still waiting after 30 minutes (`FOLDRUN_ROOM_WAIT_SEC`) fails, saying why |
|
|
31
32
|
|
|
32
33
|
A cap is a literal, never an expression, so it is readable off the file. The
|
|
@@ -64,12 +65,18 @@ at the platform's rates. The ledger stays in dollars underneath — every
|
|
|
64
65
|
statement, budget and refund reconciles to one column — and a credit is how
|
|
65
66
|
the money is shown and bought.
|
|
66
67
|
|
|
67
|
-
| plan | a month | credits a month | an extra credit | runs at once | workspaces | queue lane |
|
|
68
|
-
|
|
69
|
-
| **Starter** | $29 | 2,900 | 1.00¢ | 1 | 3 | standard |
|
|
70
|
-
| **Creator** | $79 | 8,800 | 0.90¢ | 2 | 10 | standard |
|
|
71
|
-
| **Pro** | $249 | 29,000 | 0.86¢ | 4 | unlimited | fast |
|
|
72
|
-
| **Scale** | $799 | 100,000 | 0.80¢ | 8 | unlimited | fast |
|
|
68
|
+
| plan | a month | credits a month | an extra credit | runs at once | compute at once | one step, at most | workspaces | queue lane |
|
|
69
|
+
|---|---|---|---|---|---|---|---|---|
|
|
70
|
+
| **Starter** | $29 | 2,900 | 1.00¢ | 1 | 4 vCPU · 8 GiB | 4 vCPU · 8 GiB | 3 | standard |
|
|
71
|
+
| **Creator** | $79 | 8,800 | 0.90¢ | 2 | 8 vCPU · 16 GiB | 4 vCPU · 8 GiB | 10 | standard |
|
|
72
|
+
| **Pro** | $249 | 29,000 | 0.86¢ | 4 | 16 vCPU · 32 GiB | 8 vCPU · 16 GiB | unlimited | fast |
|
|
73
|
+
| **Scale** | $799 | 100,000 | 0.80¢ | 8 | 32 vCPU · 64 GiB | 8 vCPU · 16 GiB | unlimited | fast |
|
|
74
|
+
|
|
75
|
+
**Compute at once** is what all of an account's running steps may hold
|
|
76
|
+
together, counted by the cores and memory each step's sandbox is capped at.
|
|
77
|
+
**One step, at most** is the largest single step the plan runs: `heavy`
|
|
78
|
+
(4 vCPU · 8 GiB) on every plan, and up to 8 vCPU · 16 GiB with `resources:`
|
|
79
|
+
on Pro and Scale.
|
|
73
80
|
|
|
74
81
|
**How long a credit lasts.** A plan's credits land when its invoice is paid,
|
|
75
82
|
and what is unused **rolls over** into the next cycle up to **twice** the
|
package/docs/four-jobs.md
CHANGED
|
@@ -37,8 +37,8 @@ top. Either way works, and the two can be mixed in one file.
|
|
|
37
37
|
|
|
38
38
|
| File | `governance:` | `guardrail:` | `observability:` | `evaluation:` |
|
|
39
39
|
|---|---|---|---|---|
|
|
40
|
-
| `AGENTS.md` | `owner`, `budget`, `priority`, `concurrency`, `deploy_block` | `limits`, `network`, `network_enforce`, `hooks` | `alerts`, `telemetry`, `notify`, `capture` | `judge` |
|
|
41
|
-
| an agent | `owner`, `budget` | `limits`, `network`, `hooks`, `disallowedTools`, `permissionMode`, `secrets`, `sandbox` | — | — |
|
|
40
|
+
| `AGENTS.md` | `owner`, `budget`, `priority`, `concurrency`, `compute`, `deploy_block` | `limits`, `network`, `network_enforce`, `hooks` | `alerts`, `telemetry`, `notify`, `capture` | `judge` |
|
|
41
|
+
| an agent | `owner`, `budget` | `limits`, `network`, `hooks`, `disallowedTools`, `permissionMode`, `secrets`, `sandbox`, `resources` | — | — |
|
|
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/self-hosting.md
CHANGED
|
@@ -23,36 +23,146 @@ A self-hosted server has no billing: nothing is charged, and spend budgets
|
|
|
23
23
|
|
|
24
24
|
## Your own server
|
|
25
25
|
|
|
26
|
-
One machine with Docker
|
|
27
|
-
|
|
28
|
-
throwaway container beside it, started through the Docker socket.
|
|
26
|
+
One machine with Docker — Linux or macOS, Intel/AMD (amd64) or ARM (arm64);
|
|
27
|
+
on Windows, inside WSL2 with Docker Desktop's WSL integration. One command:
|
|
29
28
|
|
|
30
29
|
```sh
|
|
31
|
-
|
|
32
|
-
for r in foldrun-core foldrun-platform foldrun-web foldrun-docs; do
|
|
33
|
-
git clone https://github.com/foldrun-io/$r
|
|
34
|
-
done
|
|
35
|
-
cd foldrun-web/self-hosting
|
|
36
|
-
echo "FOLDRUN_SECRET_KEY=$(openssl rand -hex 32)" > .env
|
|
37
|
-
echo "FOLDRUN_PUBLIC_URL=http://<host>:3900" >> .env # the address people open
|
|
38
|
-
docker compose up -d --build
|
|
30
|
+
curl -fsSL https://foldrun.io/install.sh | sh
|
|
39
31
|
```
|
|
40
32
|
|
|
41
|
-
|
|
42
|
-
|
|
33
|
+
It checks Docker, puts `docker-compose.yml` in `~/foldrun`, writes `.env`
|
|
34
|
+
there once (a fresh secret key, the address, the version), pulls the
|
|
35
|
+
ready-made images for your machine's chip and starts foldrun on port 3900.
|
|
36
|
+
|
|
37
|
+
**No Docker yet?** The installer offers to set it up, and asks before each
|
|
38
|
+
step (`[y/N]`):
|
|
39
|
+
|
|
40
|
+
| on | Docker missing | Docker stopped | Docker not reachable by you |
|
|
41
|
+
|---|---|---|---|
|
|
42
|
+
| **macOS** | installs Colima (a small Linux VM), `docker` and `docker-compose` with [Homebrew](https://brew.sh), then starts Colima with up to 4 CPUs and 8 GiB — enough for a `heavy` step | starts Colima, or opens Docker Desktop | — |
|
|
43
|
+
| **Linux** | runs Docker's official script ([get.docker.com](https://get.docker.com): the engine and the Compose plugin) as root, through `sudo` | `sudo systemctl start docker` | adds you to the `docker` group (root-equivalent), and uses `sudo` for the rest of that run; the group applies from your next login |
|
|
44
|
+
|
|
45
|
+
Answer after each question appears: anything typed ahead is lost while
|
|
46
|
+
Docker installs.
|
|
47
|
+
|
|
48
|
+
**What it checks first.** The install folder and every file Compose reads
|
|
49
|
+
there (`.env`, `docker-compose.yml`, `docker-compose.override.yml`) must be
|
|
50
|
+
yours alone — owned by you, writable by nobody else, no access control list,
|
|
51
|
+
no symlinks — because Docker runs what they say with root's reach. The
|
|
52
|
+
override must stand alone: no `include` or `extends`, and no `env_file` —
|
|
53
|
+
put settings in `.env` or in the override's own `environment:`. Anything
|
|
54
|
+
else it names, such as a secret's file, is your own configuration to
|
|
55
|
+
protect, as is a key written to slip past these checks.
|
|
56
|
+
|
|
57
|
+
`FOLDRUN_DATA_DIR` (a host folder instead of the data volume) holds the
|
|
58
|
+
accounts and the vault, so it must be private: the installer makes it
|
|
59
|
+
owner-only when it does not exist, refuses one that others can read or
|
|
60
|
+
write, and keeps its real path (symlinks resolved) in `.env`.
|
|
61
|
+
|
|
62
|
+
The installer sets foldrun up on the machine it runs on, and refuses a
|
|
63
|
+
remote Docker (`DOCKER_HOST` or a context pointing at another machine): run
|
|
64
|
+
it on the Docker host itself.
|
|
65
|
+
Compose is always given these files by name, so a `compose.yaml` beside them
|
|
66
|
+
or a `COMPOSE_FILE` in `.env` cannot stand in. Before starting, it checks
|
|
67
|
+
that the Docker the platform will be given (`FOLDRUN_DOCKER_SOCK`) is the
|
|
68
|
+
same daemon it installed into. With no terminal (a provisioning script), nothing is
|
|
69
|
+
installed unless you say so in advance with `FOLDRUN_INSTALL_DOCKER=yes`;
|
|
70
|
+
`FOLDRUN_INSTALL_DOCKER=no` never asks. Without Homebrew on a Mac, install
|
|
71
|
+
it or [Docker Desktop](https://docs.docker.com/desktop/setup/install/mac-install/)
|
|
72
|
+
first. Then open the signup link it prints —
|
|
73
|
+
`http://127.0.0.1:3900/signup?setup=…` (127.0.0.1, not `localhost`, which a browser may send to IPv6 where another user could be listening) — in a browser. The first account
|
|
74
|
+
becomes the owner, and only that link can create it: anyone else using the
|
|
75
|
+
same machine can reach `localhost` too, so the installer ties the owner to
|
|
76
|
+
whoever ran it (the token is `FOLDRUN_SETUP_TOKEN` in `~/foldrun/.env`, and
|
|
77
|
+
does nothing once the owner exists). With `FOLDRUN_BIND` set to one address
|
|
78
|
+
the link uses that address; from another machine, open it through the
|
|
79
|
+
tunnel the installer prints (`ssh -o ExitOnForwardFailure=yes -L …`), never
|
|
80
|
+
over plain http. Then sign the CLI in and deploy:
|
|
43
81
|
|
|
44
82
|
```sh
|
|
45
83
|
npm install -g foldrun
|
|
46
|
-
foldrun login --url http
|
|
47
|
-
foldrun deploy # from
|
|
84
|
+
foldrun login --url http://localhost:3900
|
|
85
|
+
foldrun deploy # from your account folder
|
|
48
86
|
```
|
|
49
87
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
88
|
+
To read the script before it runs (anything piped into a shell deserves it):
|
|
89
|
+
`curl -fsSL https://foldrun.io/install.sh -o install.sh`, read it, then
|
|
90
|
+
`sh install.sh`. Its source is `self-hosting/install.sh` in
|
|
91
|
+
[foldrun-web](https://github.com/foldrun-io/foldrun-web/tree/main/self-hosting).
|
|
92
|
+
|
|
93
|
+
The platform runs in one container — the dashboard, the API, the scheduler
|
|
94
|
+
and the queue — and every agent step runs in a throwaway container beside
|
|
95
|
+
it, started through the Docker socket. The images are
|
|
96
|
+
`ghcr.io/foldrun-io/platform` and `ghcr.io/foldrun-io/runner`, public, built
|
|
97
|
+
from these repositories.
|
|
98
|
+
|
|
99
|
+
### Settings
|
|
100
|
+
|
|
101
|
+
Give them to the installer as environment variables
|
|
102
|
+
(`curl -fsSL https://foldrun.io/install.sh | FOLDRUN_BIND=0.0.0.0 sh`) or
|
|
103
|
+
edit `~/foldrun/.env` and run `docker compose up -d` there.
|
|
104
|
+
|
|
105
|
+
| variable | default | what |
|
|
106
|
+
|---|---|---|
|
|
107
|
+
| `FOLDRUN_PUBLIC_URL` | `http://localhost:3900` | the address people open. CLI sign-in, approve links, password resets and shares are made with it |
|
|
108
|
+
| `FOLDRUN_BIND` | `127.0.0.1` | where the dashboard listens. The default is this machine only — reach it over an SSH tunnel or a reverse proxy. `0.0.0.0` opens it to the network: sign up straight away, because the first account becomes the owner |
|
|
109
|
+
| `FOLDRUN_VERSION` | the current `stable` | a version to pin, `vYYYY.MM.DD.N`; `stable` unpins. Give it to the installer: in `.env` by hand it holds only with `FOLDRUN_PINNED=yes` beside it, or the next installer run moves it to `stable` |
|
|
110
|
+
| `ANTHROPIC_API_KEY` | none | the install's model key. Without one, each account adds its own in Settings → Model; a step with neither is refused with what to set |
|
|
111
|
+
| `FOLDRUN_DIR` | `~/foldrun` | where the install lives (installer only) |
|
|
112
|
+
| `FOLDRUN_DOCKER_SOCK` | `/var/run/docker.sock` | the Docker socket; rootless Docker is found by the installer |
|
|
113
|
+
| `FOLDRUN_SETUP_TOKEN` | made once | the first account (the owner) is created only with this, through the printed `/signup?setup=…` link |
|
|
114
|
+
| `FOLDRUN_INSTALL_DOCKER` | ask | `yes` installs or starts Docker without asking (for a script with no terminal); `no` never offers to |
|
|
115
|
+
| `FOLDRUN_EGRESS_BIND` | `0.0.0.0` | the egress proxy's port 8090, which steps reach through `host.docker.internal`. On a server that faces a network, bind it to the Docker bridge (`172.17.0.1`); Docker-published ports skip host firewalls such as ufw |
|
|
116
|
+
| `FOLDRUN_DATA_DIR` | a named volume | a host path for the data instead |
|
|
117
|
+
|
|
118
|
+
`docker-compose.yml` lists the rest (concurrency, retention, gVisor, other
|
|
119
|
+
model endpoints). Put your own changes in `docker-compose.override.yml`
|
|
120
|
+
beside it, which Compose reads too: the installer replaces
|
|
121
|
+
`docker-compose.yml` on an update and keeps a copy of one you edited.
|
|
122
|
+
|
|
123
|
+
### Updating, versions, both kinds of chip
|
|
124
|
+
|
|
125
|
+
Run the installer again. It keeps `.env` (the secret key, your settings),
|
|
126
|
+
moves `FOLDRUN_VERSION` to the newest `stable`, pulls and restarts; your
|
|
127
|
+
data stays in its volume. With a version pinned, a re-run keeps it.
|
|
128
|
+
|
|
129
|
+
`stable` is the newest version built for both amd64 and arm64, so the same
|
|
130
|
+
command works on an Intel server and an Apple Silicon Mac. It moves forward
|
|
131
|
+
once a day at most; each version is tested on foldrun's own servers first.
|
|
132
|
+
|
|
133
|
+
### Backups
|
|
134
|
+
|
|
135
|
+
`~/foldrun/.env` (the secret key: losing it signs everyone out and voids
|
|
136
|
+
every webhook URL) and the data volume (`foldrun_foldrun-data`, or your
|
|
137
|
+
`FOLDRUN_DATA_DIR`) are the whole install. Stop with `docker compose stop`;
|
|
138
|
+
`docker compose down -v` deletes the data volume.
|
|
139
|
+
|
|
140
|
+
### By hand, or from source
|
|
141
|
+
|
|
142
|
+
The installer is a convenience. The same by hand:
|
|
143
|
+
|
|
144
|
+
```sh
|
|
145
|
+
mkdir ~/foldrun && cd ~/foldrun
|
|
146
|
+
curl -fsSLO https://raw.githubusercontent.com/foldrun-io/foldrun-web/main/self-hosting/docker-compose.yml
|
|
147
|
+
echo "FOLDRUN_SECRET_KEY=$(openssl rand -hex 32)" > .env
|
|
148
|
+
echo "FOLDRUN_PUBLIC_URL=http://localhost:3900" >> .env
|
|
149
|
+
echo "FOLDRUN_BIND=127.0.0.1" >> .env # this machine only, as the installer does
|
|
150
|
+
docker compose up -d
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
From source instead of images — the four repositories side by side, built
|
|
154
|
+
here (a few minutes and some gigabytes the first time):
|
|
155
|
+
|
|
156
|
+
```sh
|
|
157
|
+
for r in foldrun-core foldrun-platform foldrun-web foldrun-docs; do
|
|
158
|
+
git clone https://github.com/foldrun-io/$r
|
|
159
|
+
done
|
|
160
|
+
cd foldrun-web/self-hosting
|
|
161
|
+
echo "FOLDRUN_SECRET_KEY=$(openssl rand -hex 32)" > .env
|
|
162
|
+
echo "FOLDRUN_PUBLIC_URL=http://localhost:3900" >> .env
|
|
163
|
+
echo "FOLDRUN_BIND=127.0.0.1" >> .env
|
|
164
|
+
docker compose -f docker-compose.yml -f docker-compose.build.yml up -d --build
|
|
165
|
+
```
|
|
56
166
|
|
|
57
167
|
## Your machine
|
|
58
168
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "foldrun",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.9.1",
|
|
4
4
|
"description": "Agents are just folders. Write them, check them, run them — on your machine.",
|
|
5
5
|
"license": "FSL-1.1-ALv2",
|
|
6
6
|
"type": "module",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"node": ">=22"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@foldrun/core": "^2.
|
|
14
|
+
"@foldrun/core": "^2.13.0"
|
|
15
15
|
},
|
|
16
16
|
"files": [
|
|
17
17
|
"bin",
|
package/src/commands.mjs
CHANGED
|
@@ -5650,6 +5650,7 @@ function printDefaults(d) {
|
|
|
5650
5650
|
console.log(` notify ${notify}`);
|
|
5651
5651
|
console.log(` budget ${d.budget ? `$${d.budget.usd} per ${d.budget.period}` : c.dim("no cap")}`);
|
|
5652
5652
|
console.log(` concurrency ${d.concurrency ?? c.dim("the plan's number")}`);
|
|
5653
|
+
console.log(` compute ${d.compute ? `${d.compute.vcpu ?? "plan's"} vCPU · ${d.compute.memGiB ?? "plan's"} GiB ${c.dim("— at most, for all running steps")}` : c.dim("the plan's (foldrun usage compute)")}`);
|
|
5653
5654
|
console.log(` deploy_block ${d.deployBlock && d.deployBlock !== "none" ? `${d.deployBlock} ${c.dim(d.deployBlock === "secrets" ? "— a written-in key refuses a deploy" : "— any security finding refuses a deploy")}` : c.dim("none — security findings only warn")}`);
|
|
5654
5655
|
console.log(` network ${networkEnforceText(d.networkEnforce, "the install")}`);
|
|
5655
5656
|
}
|
|
@@ -5951,7 +5952,7 @@ async function accountCmd(positional, flags) {
|
|
|
5951
5952
|
if (key === "telemetry" && verb === "set") {
|
|
5952
5953
|
throw new Error("telemetry has its own verb — `foldrun account telemetry set <otlp-url> [--header name=value]`; `foldrun account clear telemetry` turns it off");
|
|
5953
5954
|
}
|
|
5954
|
-
const KEYS = ["timezone", "notify", "budget", "concurrency", "deploy_block", "network_enforce", "owner", "alerts", "judge", ...(verb === "clear" ? ["telemetry"] : [])];
|
|
5955
|
+
const KEYS = ["timezone", "notify", "budget", "concurrency", "compute", "deploy_block", "network_enforce", "owner", "alerts", "judge", ...(verb === "clear" ? ["telemetry"] : [])];
|
|
5955
5956
|
if (!KEYS.includes(key)) {
|
|
5956
5957
|
throw new Error(`which setting? \`foldrun account ${verb} <${KEYS.join("|")}> …\` — or <job>.<field>, e.g. governance.owner (\`foldrun jobs\` lists them)`);
|
|
5957
5958
|
}
|
|
@@ -5961,6 +5962,20 @@ async function accountCmd(positional, flags) {
|
|
|
5961
5962
|
patch = { [key]: null };
|
|
5962
5963
|
} else if (key === "alerts" || key === "judge") {
|
|
5963
5964
|
patch = { [key]: await mergedEntry(url, flags, key, positional.slice(2), null, view, `foldrun account set ${key}`) };
|
|
5965
|
+
} else if (key === "compute") {
|
|
5966
|
+
// `set compute vcpu 4 memory 8` (GiB) — either or both, over what is set.
|
|
5967
|
+
const pairs = positional.slice(2);
|
|
5968
|
+
const usage = "`foldrun account set compute vcpu <cores> memory <GiB>` — either or both";
|
|
5969
|
+
if (!pairs.length || pairs.length % 2) throw new Error(usage);
|
|
5970
|
+
const { defaults } = await remoteCall(url, flags, "/api/account");
|
|
5971
|
+
const next = { vcpu: defaults?.compute?.vcpu ?? null, memGiB: defaults?.compute?.memGiB ?? null };
|
|
5972
|
+
for (let i = 0; i < pairs.length; i += 2) {
|
|
5973
|
+
const [k, v] = [pairs[i], pairs[i + 1]];
|
|
5974
|
+
if (k !== "vcpu" && k !== "memory") throw new Error(`${usage} — not "${k}"`);
|
|
5975
|
+
if (!/^\d+(\.\d+)?$/.test(v)) throw new Error(`compute ${k} is a number${k === "memory" ? " of GiB" : " of cores"} — not "${v}"`);
|
|
5976
|
+
next[k === "vcpu" ? "vcpu" : "memGiB"] = Number(v);
|
|
5977
|
+
}
|
|
5978
|
+
patch = { compute: next };
|
|
5964
5979
|
} else if (key === "notify") {
|
|
5965
5980
|
// `set notify email you@example.com` / `set notify url https://…`, and
|
|
5966
5981
|
// --events to change what is notified on.
|
|
@@ -9612,6 +9627,29 @@ async function usageCmd(flags) {
|
|
|
9612
9627
|
return 0;
|
|
9613
9628
|
}
|
|
9614
9629
|
|
|
9630
|
+
/**
|
|
9631
|
+
* `foldrun usage compute` — the cores and GiB the account's running steps
|
|
9632
|
+
* may hold together and in one step, and what they hold now (Settings →
|
|
9633
|
+
* Governance, GET /api/usage/compute).
|
|
9634
|
+
*/
|
|
9635
|
+
async function usageComputeCmd(flags) {
|
|
9636
|
+
const url = platformFor(flags, "usage compute");
|
|
9637
|
+
const d = await remoteCall(url, flags, "/api/usage/compute");
|
|
9638
|
+
if (flags.json === true) {
|
|
9639
|
+
console.log(JSON.stringify(d, null, 2));
|
|
9640
|
+
return 0;
|
|
9641
|
+
}
|
|
9642
|
+
const p = d.compute ?? {};
|
|
9643
|
+
const u = d.inUse ?? { vcpu: 0, memGiB: 0, steps: 0 };
|
|
9644
|
+
const of = (used, cap, unit) => (cap == null ? `${used} ${unit} ${c.dim("(no cap)")}` : `${used} of ${cap} ${unit}`);
|
|
9645
|
+
const from = { plan: "the plan's", account: "the account's own compute:", install: "this install's" }[p.source] ?? null;
|
|
9646
|
+
console.log(`\n ${c.bold("compute")} ${c.dim(from ? `${from} — what all running steps may hold together` : "no pool set")}`);
|
|
9647
|
+
console.log(` ${of(u.vcpu, p.vcpu, "vCPU")} · ${of(u.memGiB, p.memGiB, "GiB")} ${c.dim(`held by ${u.steps} running step${u.steps === 1 ? "" : "s"}`)}`);
|
|
9648
|
+
if (p.stepVcpu != null || p.stepMemGiB != null) console.log(` ${c.dim(`one step: up to ${p.stepVcpu ?? "any"} vCPU · ${p.stepMemGiB ?? "any"} GiB`)}`);
|
|
9649
|
+
console.log(`\n ${c.dim("a step past the pool waits for one to finish · lower it: foldrun account set compute vcpu <n> memory <GiB>")}\n`);
|
|
9650
|
+
return 0;
|
|
9651
|
+
}
|
|
9652
|
+
|
|
9615
9653
|
/** `foldrun billing <verb>` — statement, wallet, details, portal; bare, the balance. */
|
|
9616
9654
|
async function billingVerbCmd(positional, flags) {
|
|
9617
9655
|
const url = platformFor(flags, "billing");
|
|
@@ -11778,6 +11816,7 @@ export async function run(command, positional, flags, workspace, layout) {
|
|
|
11778
11816
|
case "observe":
|
|
11779
11817
|
return observeCmd(flags, layout);
|
|
11780
11818
|
case "usage":
|
|
11819
|
+
if (positional[0] === "compute") return usageComputeCmd(flags);
|
|
11781
11820
|
return usageCmd(flags);
|
|
11782
11821
|
case "workspace":
|
|
11783
11822
|
if (positional[0] === "vocabulary") return vocabularyCmd(flags, layout);
|
package/src/guide.mjs
CHANGED
|
@@ -30,7 +30,7 @@ export function agentRulesBlock() {
|
|
|
30
30
|
|
|
31
31
|
# Read foldrun's docs before you edit this folder
|
|
32
32
|
|
|
33
|
-
This is a foldrun account: agents, flows, tools and knowledge as markdown, run by foldrun. Its formats — frontmatter fields, step options, \`verify:\` prefixes, which folder a file goes in — are foldrun's own and may differ from your training data. Before you write or change an agent, flow, tool or skill, read the relevant guide: \`foldrun docs\` lists them and \`foldrun docs <page>\` prints one (start with \`foldrun docs coding-agents\`). They ship with the CLI, so they match the version you are driving. Check every edit with \`foldrun check\` — offline, no model calls; it is also the security scanner (a key written into a file, hidden text, an agent that can leak), and \`foldrun check --security\` fails on what it finds. Never paste a credential into a file: \`foldrun secrets set NAME\` and write \`\${NAME}\`. For a flow's shape, \`foldrun flow show <flow>\` draws it, and \`foldrun flow add\`, \`foldrun flow rm-step\`, \`foldrun flow dup-step\` and \`foldrun agent link\` make the same edits as the dashboard canvas (patterns, sub-agents, consults, \`ask\`). An agent with \`tools: [peers]\` can message the steps running beside it in its parallel group (\`peers_message\`) and ask for what they have instead of guessing or failing (\`foldrun docs agents\`). A sub-agent that reads untrusted content (web pages, mail, uploads) gets \`sandbox: own\` in its own file: each job then runs in a sandbox of its own with its own tools, secrets and network, and only its reply and the files it wrote reach the agent that delegated (\`foldrun agent link <agent> --subagent <worker> --isolated\`). A script tool's own \`secrets: [NAMES]\` are granted with the tool; list on an agent only the secrets it uses directly. A hook (\`hooks/<name>/hook.md\` + \`run.mjs\`, turned on with \`hooks: [name]\`) is code at every tool call, step start and step end — \`foldrun hook add\`/\`test\` and \`foldrun docs hooks\`. Paths to the workspace's own folders are written \`workspace/storage/…\` and \`workspace/state/…\` — in an agent's prose, a flow's options and a script's arguments — not \`../../\`, which still works but is the older spelling. Which sites a step's sandbox may reach is \`network:\` — open for an agent that may fetch or browse the web, the trusted list plus what it declares for every other; add a site with \`foldrun agent network <agent> --add <site>\` (\`foldrun docs network\`). Bind a secret to the sites it is for (\`foldrun secrets sites NAME <site>\`) and no sandbox ever holds its value. A webhook, email, chained or storage flow says which deliveries start it with \`filter:\` (\`foldrun flow filter <flow> --payload body.json\` tries one); an eval with \`trigger: runs\` grades a \`sample:\` of real finished runs instead of running cases, \`foldrun eval compare\` runs an eval's cases under several models side by side, and \`improve: true\` turns repeated failures into a proposed fix a person applies (\`foldrun docs evals\`).
|
|
33
|
+
This is a foldrun account: agents, flows, tools and knowledge as markdown, run by foldrun. Its formats — frontmatter fields, step options, \`verify:\` prefixes, which folder a file goes in — are foldrun's own and may differ from your training data. Before you write or change an agent, flow, tool or skill, read the relevant guide: \`foldrun docs\` lists them and \`foldrun docs <page>\` prints one (start with \`foldrun docs coding-agents\`). They ship with the CLI, so they match the version you are driving. Check every edit with \`foldrun check\` — offline, no model calls; it is also the security scanner (a key written into a file, hidden text, an agent that can leak), and \`foldrun check --security\` fails on what it finds. Never paste a credential into a file: \`foldrun secrets set NAME\` and write \`\${NAME}\`. For a flow's shape, \`foldrun flow show <flow>\` draws it, and \`foldrun flow add\`, \`foldrun flow rm-step\`, \`foldrun flow dup-step\` and \`foldrun agent link\` make the same edits as the dashboard canvas (patterns, sub-agents, consults, \`ask\`). An agent with \`tools: [peers]\` can message the steps running beside it in its parallel group (\`peers_message\`) and ask for what they have instead of guessing or failing (\`foldrun docs agents\`). A sub-agent that reads untrusted content (web pages, mail, uploads) gets \`sandbox: own\` in its own file: each job then runs in a sandbox of its own with its own tools, secrets and network, and only its reply and the files it wrote reach the agent that delegated (\`foldrun agent link <agent> --subagent <worker> --isolated\`). A script tool's own \`secrets: [NAMES]\` are granted with the tool; list on an agent only the secrets it uses directly. A hook (\`hooks/<name>/hook.md\` + \`run.mjs\`, turned on with \`hooks: [name]\`) is code at every tool call, step start and step end — \`foldrun hook add\`/\`test\` and \`foldrun docs hooks\`. Paths to the workspace's own folders are written \`workspace/storage/…\` and \`workspace/state/…\` — in an agent's prose, a flow's options and a script's arguments — not \`../../\`, which still works but is the older spelling. Which sites a step's sandbox may reach is \`network:\` — open for an agent that may fetch or browse the web, the trusted list plus what it declares for every other; add a site with \`foldrun agent network <agent> --add <site>\` (\`foldrun docs network\`). Bind a secret to the sites it is for (\`foldrun secrets sites NAME <site>\`) and no sandbox ever holds its value. What a step's sandbox may use is \`size:\` (\`small\` 1 core/1Gi, \`large\`, \`heavy\` 4 cores/8Gi — hard limits and the billed figures); \`resources: {cpu, memory, disk}\` sets the numbers yourself, key by key over the class (\`foldrun docs agents\`). A webhook, email, chained or storage flow says which deliveries start it with \`filter:\` (\`foldrun flow filter <flow> --payload body.json\` tries one); an eval with \`trigger: runs\` grades a \`sample:\` of real finished runs instead of running cases, \`foldrun eval compare\` runs an eval's cases under several models side by side, and \`improve: true\` turns repeated failures into a proposed fix a person applies (\`foldrun docs evals\`).
|
|
34
34
|
|
|
35
35
|
This block is for coding tools. It is written and re-added by the foldrun CLI, and the runtime strips it, so the agents that run here never see it; the rest of this file is the account's own context, which they do read. Committing the block with your work keeps the tree clean.
|
|
36
36
|
|