foldrun 0.9.0 → 1.1.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 +33 -11
- package/docs/agents.md +3 -1
- package/docs/api-usage.md +16 -2
- package/docs/api.md +30 -5
- package/docs/assistant.md +139 -0
- package/docs/backups-and-restore.md +35 -66
- package/docs/budgets-and-billing.md +1 -1
- package/docs/cli.md +23 -9
- package/docs/flows.md +1 -1
- package/docs/hooks.md +139 -0
- package/docs/network.md +158 -0
- package/docs/notifications.md +7 -1
- package/docs/secrets.md +54 -0
- package/docs/security.md +28 -7
- package/docs/tools.md +36 -5
- package/docs/versions.md +39 -2
- package/package.json +3 -3
- package/src/commands.mjs +904 -21
- package/src/guide.mjs +1 -1
- package/src/terminal.mjs +85 -0
package/bin/foldrun.mjs
CHANGED
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
// foldrun eval new <name> a new eval in a deployed workspace
|
|
39
39
|
// foldrun agent run <name> run one agent once on a platform, no flow
|
|
40
40
|
// foldrun tool test <name> exercise one tool alone — no model, no run
|
|
41
|
+
// foldrun hook <verb> ls / add / test / rm — your own code at every tool call and step
|
|
41
42
|
// foldrun deploy [dir] push this account — or one workspace — into an installation
|
|
42
43
|
// foldrun pull bring the platform's account down into this folder
|
|
43
44
|
// foldrun status what differs here from what is deployed — --platform [--history]: is the platform up
|
|
@@ -64,6 +65,11 @@ import { fileURLToPath } from "node:url";
|
|
|
64
65
|
import fs from "node:fs";
|
|
65
66
|
import path from "node:path";
|
|
66
67
|
import process from "node:process";
|
|
68
|
+
import { guardTerminal } from "../src/terminal.mjs";
|
|
69
|
+
|
|
70
|
+
// Before anything prints: a platform's text never writes escape sequences to
|
|
71
|
+
// a person's terminal (src/terminal.mjs). Pipes and files are untouched.
|
|
72
|
+
guardTerminal();
|
|
67
73
|
|
|
68
74
|
const HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
69
75
|
|
|
@@ -101,6 +107,7 @@ const HELP = `foldrun — agents are just folders
|
|
|
101
107
|
foldrun agent run <name> run one agent once on the platform (--task "…", --to, --wait, --test)
|
|
102
108
|
foldrun agent link <agent> add to its team, in its frontmatter: --subagent <worker>, --consult <agent> or --can-ask (--description for a worker with none)
|
|
103
109
|
foldrun agent unlink <agent> take one away: --subagent <name>, --consult <name> or --can-ask
|
|
110
|
+
foldrun agent network <agent> the sites its sandbox may reach; set the level (open, trusted, none, inherit) and --add / --remove <site> (repeatable)
|
|
104
111
|
foldrun agent import <workspace>/<agent> copy an agent you already have in another workspace into this one — its agent.md, own skills and scripts, not its memory (--as <name>, --workspace <name>; --to <ws> on the platform)
|
|
105
112
|
foldrun agent import --list what could be imported into a deployed workspace: every other workspace you can read, and its agents (--to, --json)
|
|
106
113
|
foldrun flow add <flow> <pattern> one canvas block as a markdown edit: chain, parallel, router, fan-out, loop, approval, ask, wait, rescue, subflow — diff shown, checked, --dry-run
|
|
@@ -116,7 +123,13 @@ const HELP = `foldrun — agents are just folders
|
|
|
116
123
|
foldrun flow copy-step <flow> --step <n|agent>[,<n>…] the steps' markdown on stdout, as the canvas's Copy
|
|
117
124
|
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
|
|
118
125
|
foldrun flow draft "<what it should do>" Draft with AI on the platform (--to; --flow <name> redrafts one): shows the files and the check, saves on yes (--yes, --dry-run)
|
|
126
|
+
foldrun assistant ["<message>"] the dashboard's assistant (also: foldrun ai): answers from the foldrun docs and the deployed workspace's files, writes agents, flows, tools and skills as diffs with the check, saves on yes (--to, --yes, --dry-run, --json); every reply is kept — --conversation <id> carries one on; no message in a terminal is a conversation (/save, /files, /drop, /new, /quit)
|
|
127
|
+
foldrun assistant history your kept conversations, newest first (--q <text> searches them, --workspace, --json); show <id> prints one, rm <id> deletes it
|
|
119
128
|
foldrun tool test <name> exercise one tool alone, no model and no run (key=value args, --to)
|
|
129
|
+
foldrun hook ls the hooks in this workspace and the account library: on, tool, description — invalid ones with the reason (--json); --to <ws>: the DEPLOYED workspace's, who turns each on, and what they denied, rewrote or failed lately (--runs <n>, --all)
|
|
130
|
+
foldrun hook add <name> --on <event> a hook folder: hook.md and a run.mjs that allows everything, the contract in its comments (--tool <pattern>, --library, --to)
|
|
131
|
+
foldrun hook test <name> run one hook here on a sample event, as a step would: its answer and how long it took (--event '<json>'|@file; --to <workspace>: the deployed one, on the platform)
|
|
132
|
+
foldrun hook rm <name> delete a hook folder — says which agents still turn it on, asks (--yes, --library)
|
|
120
133
|
foldrun extract [dir] move single-file script tools into folders (tool.md + run.*)
|
|
121
134
|
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
|
|
122
135
|
foldrun eval [name] run one eval, or all of them (--to <workspace>: the deployed evals, run on the platform)
|
|
@@ -171,10 +184,11 @@ const HELP = `foldrun — agents are just folders
|
|
|
171
184
|
foldrun history [path] every change to a deployed workspace, newest first (--id <revision> for its diff, --limit, --to)
|
|
172
185
|
foldrun history restore <path> --id <revision> put one file back as that revision left it, as a new revision — diff, asks (--yes, --dry-run, --to)
|
|
173
186
|
foldrun repo ls a workspace's branches and tags — also diff <branch>, deploy <ref>, merge <branch> (both ask first), rm-branch <branch>, mirror <git-url|off>, mirror-now (--to)
|
|
174
|
-
foldrun restore <ws> --to <commit|time|
|
|
187
|
+
foldrun restore <ws> --to <commit|time|age> put a workspace's source back as it was then — shows the change, asks for the name typed back (--dry-run: the diff only, --yes)
|
|
175
188
|
foldrun backups how the account is backed up (encrypted?), when it last was, the snapshots it is in — also request --what runs|state|storage|everything --at "<when>" [--to <ws>] [--note]
|
|
176
189
|
foldrun backups requests every restore asked of the platform team, and where each stands (--json)
|
|
177
|
-
foldrun secrets set NAME store a secret (prompted, never echoed) — also ls, rm, status; --kind file|ssh|api|service-account|m2m for the other shapes
|
|
190
|
+
foldrun secrets set NAME store a secret (prompted, never echoed) — also ls, rm, status; --kind file|ssh|api|service-account|m2m for the other shapes; --site <domain> (repeatable) binds it as it is stored
|
|
191
|
+
foldrun secrets sites NAME [site…] bind a secret to sites: the egress proxy fills it for those alone and it never enters a sandbox; no sites lists them, --clear unbinds
|
|
178
192
|
foldrun secrets clients the OAuth clients saved on the platform — also add <name> (--provider or --authorize-url/--token-url, --client-id, --scopes), rm <name>
|
|
179
193
|
foldrun connect NAME OAuth sign-in from the terminal, stored as an auto-refreshing secret — --client <saved> runs it from a saved client through the platform
|
|
180
194
|
foldrun deploy [dir] push the whole account, or deploy <workspace> for one of them
|
|
@@ -188,7 +202,7 @@ const HELP = `foldrun — agents are just folders
|
|
|
188
202
|
foldrun workspaces show <name> what is deployed in one: its agents, flows and how each fires, its files by folder (--json)
|
|
189
203
|
foldrun workspaces new <name> --platform make it on the platform: blank (an AGENTS.md), or --starter for the example researcher, writer and flow
|
|
190
204
|
foldrun workspaces demo make the demo pipeline workspace on the platform — "Try the demo pipeline"
|
|
191
|
-
foldrun library the account's shared library on the platform: ls [skills|tools|scripts|knowledge|memory], cat <kind>/<path>, put <kind>/<path> (--file, else stdin), rm <kind>/<path> (asks)
|
|
205
|
+
foldrun library the account's shared library on the platform: ls [skills|tools|hooks|scripts|knowledge|memory], cat <kind>/<path>, put <kind>/<path> (--file, else stdin), rm <kind>/<path> (asks)
|
|
192
206
|
foldrun library new <kind>/<name> a new one from the platform's template, as the Library page's New (--template <transport or gallery entry>, --language)
|
|
193
207
|
foldrun find <words> search the account as the dashboard's ⌘K does: workspaces, agents, flows, tools, skills, knowledge, memory, runs (--json)
|
|
194
208
|
foldrun invoke <flow> start a flow on a running platform (--to <workspace>; --once <key> so a retry never starts a second run; --tag <t> repeatable)
|
|
@@ -196,8 +210,8 @@ const HELP = `foldrun — agents are just folders
|
|
|
196
210
|
foldrun source new <kind> <name> the dashboard's New for any document — tools, knowledge, memory, skills, scripts (agents, flows, evals too): the platform writes the template (--agent <a> inside one agent, --template, --language, --to)
|
|
197
211
|
foldrun open [page] the dashboard for this workspace, in the browser
|
|
198
212
|
foldrun api spec the platform's OpenAPI 3.1 document (GET /api/openapi.json) — stdout, or --out <file>
|
|
199
|
-
foldrun api version the API version this account's requests get, the versions served, the rate limits — also pin <YYYY-MM-DD>, unpin (admin; --json)
|
|
200
|
-
foldrun changelog the platform's release notes, newest first: features, fixes, docs (--limit <n>, --json)
|
|
213
|
+
foldrun api version the API version this account's requests get, the versions served, the rate limits, your last 30 days by version and any deprecated route you still call — also pin <YYYY-MM-DD>, unpin (admin; --json)
|
|
214
|
+
foldrun changelog the platform's release notes, newest first: features, fixes, docs — and the API's versions, breaking changes and deprecations (--limit <n>, --json)
|
|
201
215
|
foldrun version this CLI's version, its core's, and the platform's: release, API version, component shas — warns when the platform ships a newer CLI (--json)
|
|
202
216
|
|
|
203
217
|
Signing in
|
|
@@ -278,18 +292,26 @@ Options
|
|
|
278
292
|
--step <n> approve, reject: decide only that step; default is every step that is waiting
|
|
279
293
|
--payload <json|text|@f> approve: release a step waiting on wait: event with this body, recorded as you
|
|
280
294
|
--note "<text>" approve, reject: guidance the agent reads — or the reason for a refusal; billing credit: the ledger line's note
|
|
281
|
-
--yes approve, stop, rerun (bulk), flow rotate-hook, flow draft, history restore, restore <ws>, import, billing plan, repo, workspace set name, and every rm/revoke/unshare: skip the confirmation, deliberately (required with no terminal)
|
|
282
|
-
--json report: the raw run record instead of the report; observe, usage, api version, changelog, find, preferences, workspace vocabulary, flags, workspaces, workspaces show, agent ls, flow ls, agent import --list, backups requests, team: the raw document; version: {cli, core, platform}
|
|
295
|
+
--yes approve, stop, rerun (bulk), flow rotate-hook, flow draft, assistant, history restore, restore <ws>, import, billing plan, repo, workspace set name, and every rm/revoke/unshare: skip the confirmation, deliberately (required with no terminal)
|
|
296
|
+
--json report: the raw run record instead of the report; observe, usage, api version, changelog, find, preferences, workspace vocabulary, flags, workspaces, workspaces show, agent ls, flow ls, agent import --list, backups requests, team, hook ls: the raw document; version: {cli, core, platform}
|
|
283
297
|
--events <a,b> account set notify: failed, awaiting-approval, completed
|
|
284
298
|
--failed webhooks deliveries: only the ones that gave up
|
|
285
299
|
--limit <n> runs, billing, history, changelog: how many rows
|
|
286
300
|
--value "<text>" secrets set: skip the prompt (careful with shell history)
|
|
301
|
+
--site <domain> secrets set, secrets sites: a site the secret is bound to — the egress proxy fills it for those alone (repeatable, or a,b)
|
|
302
|
+
--clear secrets sites: unbind it
|
|
303
|
+
--add <site> agent network: add a site to its network: list (repeatable)
|
|
304
|
+
--remove <site> agent network: take a site off its network: list (repeatable)
|
|
287
305
|
--out <file> api spec: where to write the OpenAPI document (default: stdout)
|
|
288
306
|
--file <path> source put, library put, flow paste: the local file to send (default: stdin); secrets set --kind file|service-account: the file it holds; storage get, report get, report live, account export, export, billing statement: where to write it (- for stdout)
|
|
289
307
|
--message "<why>" source put: recorded on the file's revision
|
|
290
308
|
--account secrets: account scope instead of the workspace's
|
|
291
309
|
--wait invoke, agent run: hold on and print the result
|
|
292
310
|
--path <p> tool test: the path an http tool should probe, appended to its base:
|
|
311
|
+
--on <event> hook add: what it listens for — tool.call, tool.result, step.start or step.end
|
|
312
|
+
--tool <pattern> hook add: which tools a tool.call or tool.result hook sees — Bash, Write|Edit, mcp__* (default all)
|
|
313
|
+
--library hook add, hook rm: in the account library (every workspace), not this workspace
|
|
314
|
+
--event <json|@file> hook test: the event the hook reads on stdin (default: a sample for its on:)
|
|
293
315
|
--watch invoke: follow the run's trace here as it happens
|
|
294
316
|
--print open: print the URL only
|
|
295
317
|
--from <n> invoke, flow run, rerun: start at step n; earlier steps are recorded as skipped (a bulk rerun defaults to 1)
|
|
@@ -305,7 +327,7 @@ Options
|
|
|
305
327
|
--grace <span> keys rotate: keep the old key working for 1h or 24h while you swap the new one in (default 0: revoked now)
|
|
306
328
|
|
|
307
329
|
Platform options (deploy, invoke, secrets, logs, keys)
|
|
308
|
-
--to <workspace> workspace on the platform (deploy default: folder name); flow add/show, agent link: edit or show the deployed copy instead of this folder; restore <ws>: the commit, date or age to put it back to (3d, 2026-09-28T14:00)
|
|
330
|
+
--to <workspace> workspace on the platform (deploy default: folder name); flow add/show, agent link: edit or show the deployed copy instead of this folder; hook test: run the deployed hook on the platform; hook ls: list the deployed workspace's hooks and what they did lately; restore <ws>: the commit, date or age to put it back to (3d, 2026-09-28T14:00)
|
|
309
331
|
--tenant <name> account to deploy into (default: default, local only)
|
|
310
332
|
--data <dir> the installation's data directory
|
|
311
333
|
--url <url> a running platform (or FOLDRUN_URL, or where you last signed in)
|
|
@@ -317,7 +339,7 @@ Platform options (deploy, invoke, secrets, logs, keys)
|
|
|
317
339
|
--local deploy: into the installation on this machine, even when signed in
|
|
318
340
|
--commit <sha> deploy: record which commit this is
|
|
319
341
|
--overwrite import: replace files the workspace already has with different text (refused otherwise)
|
|
320
|
-
--dry-run deploy: check and report, change nothing; flow add, flow set, flow trigger, flow move-step, flow paste, flow draft, history restore, agent link, agent unlink: show the diff, write nothing; restore <ws>: the diff only, restore nothing; import: the preview only, write nothing; stop, rerun (bulk): list what matches, touch nothing
|
|
342
|
+
--dry-run deploy: check and report, change nothing; flow add, flow set, flow trigger, flow move-step, flow paste, flow draft, assistant, history restore, agent link, agent unlink: show the diff, write nothing; restore <ws>: the diff only, restore nothing; import: the preview only, write nothing; stop, rerun (bulk): list what matches, touch nothing
|
|
321
343
|
--no-runtimes deploy: do not wait for the workspace's environments to be built
|
|
322
344
|
--force deploy: deploy even while runs are in flight; pull, storage get: overwrite local files
|
|
323
345
|
--yes deploy: allow deleting files this folder no longer has (asked otherwise; required with no terminal)
|
|
@@ -338,9 +360,9 @@ if (!command || command === "--help" || command === "-h") {
|
|
|
338
360
|
// `--value` as the account's argument and stored an empty secret; `--force
|
|
339
361
|
// ./dir` swallowed the directory. A flag followed by another flag is also
|
|
340
362
|
// boolean, so an unlisted switch at least does not eat its neighbour.
|
|
341
|
-
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"]);
|
|
363
|
+
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"]);
|
|
342
364
|
// Flags said more than once collect into a list: `--tag a --tag b`.
|
|
343
|
-
const REPEATABLE = new Set(["tag", "expect", "header"]);
|
|
365
|
+
const REPEATABLE = new Set(["tag", "expect", "header", "site", "add", "remove"]);
|
|
344
366
|
// `--wait` is a switch everywhere but `flow add <flow> wait`, where it takes
|
|
345
367
|
// a span: `--wait 30m`, `--wait event`. Only a value that reads as one is
|
|
346
368
|
// taken, so `invoke x --wait` and friends are as they were.
|
package/docs/agents.md
CHANGED
|
@@ -60,10 +60,12 @@ the point of the week; a step that weighs two arguments and picks one is not.
|
|
|
60
60
|
| `agents` | colleagues it may consult mid-run — an answer, no tools |
|
|
61
61
|
| `subagents` | colleagues it may delegate a whole job to — own context, own tools, never more than its own ([below](#delegating-to-a-sub-agent)) |
|
|
62
62
|
| `scripts` | programs in `scripts/`, each becoming a callable tool |
|
|
63
|
+
| `hooks` | your own code at every tool call, step start and step end — refuse, rewrite, add context — the same on every model ([Hooks](hooks)). Also in a workspace's or the account's `AGENTS.md` for every agent under it |
|
|
63
64
|
| `apis` | an HTTP API declared inline, as one tool |
|
|
64
65
|
| `mcpServers` | an MCP server declared inline |
|
|
65
66
|
| `secrets` | vault entries its tools may use — names only, never values |
|
|
66
67
|
| `limits` | the most calls one step may make — per tool, per web action, or in all: `limits: {web.search: 40, crm: 20, calls: 300}`. A call past a limit is refused, never run ([Tools](tools#limits--how-many-calls-a-step-may-make)) |
|
|
68
|
+
| `network` | which sites its sandbox may reach: `open`, `trusted` or `none`, or a list of sites added to the trusted list — `network: [api.hubapi.com]`. Unset, an agent that may fetch or browse the web is `open` and every other `trusted`. Also in a workspace's or the account's `AGENTS.md` ([Network](network)) |
|
|
67
69
|
| `permissionMode` | `plan` makes the run read-only, whatever else was granted |
|
|
68
70
|
| `web` | the `web` tool's settings: `actions:` — which of its eight it may use (all when absent) — and who does each, `web: {search: brave, fetch: jina, browse: browserbase, crawl: firecrawl}`, with the key named in the vault. Unset is foldrun's own. `search:` also takes a block of the own engine's settings — engines, categories, safesearch, plugins ([Tools](tools#tuning-the-accounts-own-engine)); `browse:` a block of the browser's ([below](#webbrowse--which-browser-and-how-it-presents-itself)), and `browse:` alone cascades from `AGENTS.md`. The providers and their keys: [Tools](tools#reaching-the-web). A per-action key outside the block is not read — `check` errors on it |
|
|
69
71
|
|
|
@@ -476,7 +478,7 @@ take, like `eval` or `download`; a misspelt one is refused by `check`).
|
|
|
476
478
|
Beside them: `boundaries: true` marks the page's words in every reply,
|
|
477
479
|
`init:` runs workspace scripts before each page's own, `extensions:` loads
|
|
478
480
|
unpacked Chrome extensions, `webgpu: true`, `ignore_https_errors: true`, and
|
|
479
|
-
`state_key:` names a secret that encrypts saved logins; `video: true`
|
|
481
|
+
`state_key:` names a secret that encrypts saved logins; `state:` names a saved login every call uses (beside `cookies:` it needs `state_key:`, and the secret then seeds it once — see tools.md); `video: true`
|
|
480
482
|
records every call for the run page to play, and `live_view: false` stops
|
|
481
483
|
the page being shown live there. See
|
|
482
484
|
[What this agent's browser may do](tools#what-this-agents-browser-may-do).
|
package/docs/api-usage.md
CHANGED
|
@@ -50,8 +50,22 @@ The CLI sends the version it was built for on every call.
|
|
|
50
50
|
replaces it is released. A pin to a retired version is answered as the
|
|
51
51
|
current version, never refused.
|
|
52
52
|
- Every breaking change is announced in **What's new** (the dashboard's
|
|
53
|
-
changelog,
|
|
54
|
-
|
|
53
|
+
changelog, `GET /api/changelog` and `foldrun changelog`) on the day it
|
|
54
|
+
ships: the version that brought it and what changed, and every deprecated
|
|
55
|
+
route with the date it stops answering.
|
|
56
|
+
- **Checked before it ships.** Every change to the platform is compared with
|
|
57
|
+
the version that is deployed. A route or documented field taken away, a
|
|
58
|
+
type changed, or a request field made required cannot ship unless it comes
|
|
59
|
+
as a new dated version, with the old shape kept for older pins. A route
|
|
60
|
+
can only be removed once its deprecation has passed its sunset date.
|
|
61
|
+
- **You are told if it affects you.** Every request is counted by the
|
|
62
|
+
version that answered it, and a call to a deprecated route by the key or
|
|
63
|
+
person that made it. **Settings → API** and `foldrun api version` show the
|
|
64
|
+
last 30 days. An account that called a deprecated route in the last 30
|
|
65
|
+
days is emailed when it is first seen doing so, and again 90, 30 and 7
|
|
66
|
+
days before the sunset — one email per step, naming the keys that still
|
|
67
|
+
call it. This is required mail: it cannot be switched off, because it is
|
|
68
|
+
the only warning before something you rely on stops.
|
|
55
69
|
- A route on its way out answers with
|
|
56
70
|
[`Deprecation`](https://www.rfc-editor.org/rfc/rfc9745) (`@<unix time>` it
|
|
57
71
|
was deprecated) and, once the date is fixed,
|
package/docs/api.md
CHANGED
|
@@ -65,7 +65,7 @@ api workspaces/$WS/flows/publish/run -X POST # start a flow
|
|
|
65
65
|
|
|
66
66
|
| Route | Methods | |
|
|
67
67
|
|---|---|---|
|
|
68
|
-
| `/api/workspaces` | GET, POST | list; create (`name`, plus one of: `template: true` — blank, just an AGENTS.md, what the dashboard sends; `starter: true` — the example researcher, writer and publish flow; `demo: true`; `files`) |
|
|
68
|
+
| `/api/workspaces` | GET, POST | list; create (`name`, plus one of: `template: true` — blank, just an AGENTS.md, what the dashboard sends; `starter: true` — the example researcher, writer and publish flow; `demo: true`; `files`). A POST to a name that exists deploys over it; add `ifAbsent: true` to create only — a name that exists is then `409` and nothing changes, decided atomically (the assistant's Create workspace and `foldrun assistant` send it) |
|
|
69
69
|
| `/api/workspaces/<ws>` | GET, PATCH, DELETE | read; rename or re-describe (`name`, `description`), or set its defaults (`timezone`, `budget`, `notify` — null clears one, edited in AGENTS.md in place); delete |
|
|
70
70
|
| `/api/workspaces/<ws>/vocabulary` | GET | which document kinds this workspace admits — its `docs` list is what `[[ ]]` resolves: knowledge, memory, `state/` and `storage/` files, plus the folders themselves |
|
|
71
71
|
|
|
@@ -145,12 +145,35 @@ Left out, `test` is decided by the platform:
|
|
|
145
145
|
a preview workspace's runs are tests unless the flow says `live: true`;
|
|
146
146
|
everywhere else a run is live.
|
|
147
147
|
|
|
148
|
+
## Assistant
|
|
149
|
+
|
|
150
|
+
| Route | Methods | |
|
|
151
|
+
|---|---|---|
|
|
152
|
+
| `/api/workspaces/<ws>/assistant` | POST | one reply from the assistant, which answers from the foldrun docs and this workspace's files only and writes agents, flows, tools and skills: `{ messages: [{ role: "user"\|"assistant", content }], files?: [{ path, content }], page?, conversation? }` — the conversation so far (the last message the person's, at most 8,000 characters), the files it proposed that are not saved yet, and the dashboard page being looked at. Returns `{ ok, reply, files: [{ path, content, before, sha256 }], issues: [{ where, message, level }], sources: [{ doc, section, url }], supply, workspace, usage, chargedUsd }` — `files` is every unsaved proposal as it now stands, checked; `workspace` is `{ name, purpose }` when it proposes a new workspace (free and kebab-case when proposed; create it with `POST /api/workspaces { name, template: true }`, then talk to the assistant there), else `null`; `usage` is `{ inputTokens, outputTokens, costUsd }` across every model call of the reply; `chargedUsd` what the account was charged for it on the platform's models (the same price as a run's tokens), `null` on its own provider; `conversation` is the id it was kept under — send it back as `conversation` to carry on (without one, a new conversation starts). **Writes nothing**; save with the source PUT (`ifMatch` the `sha256`, `null` for a new file). With `Accept: text/event-stream` it streams `status` (what it is reading), `text` (the reply as it is written), then `done` with the same body, or `error`. Needs `workspace:write`; 60 replies an hour per person or key, and on the platform's models 300 a day per account (`429`); at most 200 messages of 60,000 characters each (`413`). Closing the stream does not stop the reply: it finishes (at most $2, 30 tool calls, five minutes) and is charged; a Chat-Completions `provider:` is `422`. On the platform's models the balance is checked first (`402`) and each reply is one ledger line, flow `ai-assistant` |
|
|
153
|
+
| `/api/assistant/conversations?workspace=&q=&limit=` | GET | the asker's kept assistant conversations — a person's own, or a key's own — newest first, across the workspaces they may see: `{ conversations: [{ id, workspace, title, updatedAt, messages, unsaved }] }`. `q` searches the titles and every message; `workspace` narrows to one; `limit` 1–200 (50) |
|
|
154
|
+
| `/api/assistant/conversations/<id>` | GET, DELETE | one kept conversation, whole: `{ id, workspace, title, messages: [{ role, content, meta? }], files, createdAt, updatedAt }` (`meta` is what the dashboard draws under a reply: sources, tokens, charge, a proposed workspace); DELETE removes it. Anyone else's id is `404`, the same as none |
|
|
155
|
+
|
|
156
|
+
It may propose only `flows/<name>.md`, `agents/<name>/agent.md`,
|
|
157
|
+
`skills/<name>/SKILL.md` (and files under the skill's `scripts/`,
|
|
158
|
+
`references/` or `assets/`), and `tools/<name>/tool.md` with its program, or
|
|
159
|
+
`tools/<name>.md`. It reads the workspace's source and the account library,
|
|
160
|
+
never `state/`, `storage/`, runs or secrets. A proposal is checked with the
|
|
161
|
+
deploy's checks and the flow lint; a frontmatter key the docs never name, and
|
|
162
|
+
a value shaped like a credential, are errors.
|
|
163
|
+
|
|
164
|
+
A reply is not computed by the web: it is queued, and the platform's
|
|
165
|
+
assistant workers answer it — a pool of their own that scales with the
|
|
166
|
+
queue, so a burst of questions never slows the dashboard or takes a run's
|
|
167
|
+
turn. At most three replies of one account run at once. Each reply is kept
|
|
168
|
+
as a conversation private to whoever asked; the caller still sends the
|
|
169
|
+
history with each message.
|
|
170
|
+
|
|
148
171
|
## Agents and evals
|
|
149
172
|
|
|
150
173
|
| Route | Methods | |
|
|
151
174
|
|---|---|---|
|
|
152
175
|
| `/api/workspaces/<ws>/agents` | GET, POST | list; create (`name`) |
|
|
153
|
-
| `/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", value } }
|
|
176
|
+
| `/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", value } }`, 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` |
|
|
154
177
|
| `/api/workspaces/<ws>/agents/<agent>/run` | POST | run one agent alone (`task`; `test: true` for a test run) |
|
|
155
178
|
| `/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 |
|
|
156
179
|
| `/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 |
|
|
@@ -158,6 +181,8 @@ everywhere else a run is live.
|
|
|
158
181
|
| `/api/workspaces/<ws>/evals` | GET, POST | list; create (`name`) |
|
|
159
182
|
| `/api/workspaces/<ws>/evals/<eval>/run` | POST | run one eval |
|
|
160
183
|
| `/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
|
+
| `/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
|
+
| `/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) |
|
|
161
186
|
|
|
162
187
|
Running an agent alone is the fastest way to test a prompt change without
|
|
163
188
|
paying for the steps around it.
|
|
@@ -291,7 +316,7 @@ its header and requires a URL it can GET for itself.
|
|
|
291
316
|
|
|
292
317
|
| Route | Methods | |
|
|
293
318
|
|---|---|---|
|
|
294
|
-
| `/api/secrets?workspace=` | GET, PUT, DELETE | the vault: list which secrets exist (never a value); set one (`name` plus one shape, below); delete one (`name`). `workspace` names a workspace's own store; unsaid is the account's, which a key scoped to some workspaces may not touch. PUT is idempotent |
|
|
319
|
+
| `/api/secrets?workspace=` | GET, PUT, PATCH, DELETE | the vault: list which secrets exist (never a value), each with the `sites` it is bound to; set one (`name` plus one shape, below, and optionally `sites`); bind one to sites with PATCH `{ name, sites }` — `sites` a list like `["api.example.com", "*.example.com"]`, `[]` unbinds, the value untouched, 404 for a secret that is not there, 400 for a site with a scheme, path or port and for a file, ssh or api secret ([bound to sites](secrets#bound-to-sites)); delete one (`name`). `workspace` names a workspace's own store; unsaid is the account's, which a key scoped to some workspaces may not touch. PUT is idempotent; PATCH and a PUT with `sites` are audited as `secret.sites` / `secret.set` |
|
|
295
320
|
|
|
296
321
|
`PUT` takes `name` plus exactly one shape: `value` (a string), `file` (a PEM,
|
|
297
322
|
a cert), `oauth2`, `service_account`, `ssh`, or `api`. They go to the vault;
|
|
@@ -317,7 +342,7 @@ Reads return which secrets exist, never their values.
|
|
|
317
342
|
| `/api/account/close` | POST | the owner asks to close the account — in person, never a key, with `confirm` set to the account's name and an optional `reason`. Not a delete: the account is suspended at once (runs in flight finish, sign-in and the export still work), `closeRequested` is set on its admin state, and the super admin is mailed to settle the last invoice and any refund. Returns `{ ok, suspended, notified }`; 409 when already asked. Hosted installs only |
|
|
318
343
|
| `/api/account/backups` | GET | what is backed up for this account: `policy` (`schedule`, `keepLocal`, `keepOffsiteDays`, `encrypted`, `covers`, `excludes`), `last`, `backups` (each nightly archive that holds this account, newest first: `id`, `at`, `bytes` (the encrypted object), `sha256` (the archive inside the encryption — what a decrypt must reproduce), `kinds` — files, database, keys — `verified`, `offsite`, `keepDays`, `encrypted` (sealed with age before it left the server; false for archives taken before October 2026), `recipient` (the key's fingerprint, `sha256:<16 hex>`, or null)), `requests` (earlier restore requests) and `workspaces` (`name`, `head`, `history`, `lastChange`) — what a restore can reach. See [Backups and restore](backups-and-restore.md) |
|
|
319
344
|
| `/api/account/backups/requests` | GET, POST | ask the platform team to restore what an account cannot restore itself. POST `{ what, target, workspace?, backupId?, note? }` (`what` is runs, state, storage or everything) records the request, mails the operator and audits it as `backup.restore_requested`; answers 201 `{ ok, request }` with `notified` saying whether the mail went. Restores nothing. `workspace:manage`, and a member scoped to some workspaces must name one. GET lists this account's requests |
|
|
320
|
-
| `/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 } } }
|
|
345
|
+
| `/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 requests without a `Foldrun-Version` header get (null: always current). `keys:manage`. See [api-usage](api-usage.md) |
|
|
321
346
|
| `/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) |
|
|
322
347
|
| `/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 |
|
|
323
348
|
| `/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 }`); PATCH sets the keys sent, null clears one, and edits the file in place |
|
|
@@ -374,7 +399,7 @@ What a platform runs, and what changed. See [Versions and releases](versions).
|
|
|
374
399
|
| Route | Methods | |
|
|
375
400
|
|---|---|---|
|
|
376
401
|
| `/api/version` | GET | open, no key. `{ version, released_at, build, api, components: { core, platform, web, docs, infra, cli }, packages: { core, cli }, browsers, browser_versions }` — `version` is `vYYYY.MM.DD.N` (`dev` on an install the deploy did not build); `components` are short commit shas (`cli` is the CLI commit released alongside); `packages.cli` is that CLI's npm version; `api` is the API contract's date version; `browsers` is the engines the runner image was built with (its `/opt/browser/engines.json`, read by the deploy — see [Engines](tools#engines)), `null` when the deploy did not say; `browser_versions` is their versions where the manifest has a dotted one (`{ chromium: "153.0.8010.12", chrome: "154.0.8037.58", lightpanda: "0.4.1" }`), which `foldrun check` compares with a pinned `user_agent`, `null` when none. `Cache-Control: no-store` |
|
|
377
|
-
| `/api/changelog?limit=` | GET | `{ version, releases: [{ version, released_at, previous, components, notes }] }
|
|
402
|
+
| `/api/changelog?limit=` | GET | `{ version, api, releases: [{ version, released_at, previous, components, notes }] }`. `api` is the API's own changelog: `{ current, supportMonths, versions: [{ version, changes }], deprecations: [{ route, deprecatedAt, sunsetAt, link }] }` — every version a caller may pin, newest first, with the breaking changes it introduced, and the routes on their way out. `releases` newest first — 10 by default, at most 50. `notes` is `{ features, fixes, docs, other }`, each a list of `{ repo, sha, subject }` grouped from commit subjects, or `null` when the deploy could not read what changed. Needs a key or a session |
|
|
378
403
|
|
|
379
404
|
Every `/api` response carries the version in its headers, refusals included,
|
|
380
405
|
so a client learns what it is talking to without a second request:
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# Assistant
|
|
2
|
+
|
|
3
|
+
The assistant is a panel on every page of the dashboard. Ask it how
|
|
4
|
+
foldrun works, or describe what you want built. It writes agents, flows,
|
|
5
|
+
tools and skills for a workspace and shows each one as a diff, checked, for
|
|
6
|
+
you to save.
|
|
7
|
+
|
|
8
|
+
Open it with **Ask AI** at the bottom right. **Draft with AI** on the Flows
|
|
9
|
+
page opens it too, with "Draft a flow that" ready to finish.
|
|
10
|
+
|
|
11
|
+
Inside a workspace it works in that workspace. Anywhere else, such as the
|
|
12
|
+
account home, Billing or the docs, it works in the one chosen in its header;
|
|
13
|
+
your browser remembers the choice. Each workspace keeps its own conversation.
|
|
14
|
+
|
|
15
|
+
## What it knows
|
|
16
|
+
|
|
17
|
+
Only two things:
|
|
18
|
+
|
|
19
|
+
- **foldrun's docs**, these pages. It searches them and reads the sections
|
|
20
|
+
it needs before it answers, and it cites what it used as
|
|
21
|
+
`[doc › section]`, which links to the heading. Under each reply,
|
|
22
|
+
**Read N doc sections** lists them.
|
|
23
|
+
- **This workspace's files**: its `AGENTS.md`, agents, flows, skills,
|
|
24
|
+
tools, hooks, knowledge, evals and scripts, plus the account library and
|
|
25
|
+
the account's `AGENTS.md`. It never reads `state/`, `storage/`, runs or
|
|
26
|
+
secrets.
|
|
27
|
+
|
|
28
|
+
It has no web access, no shell and no other tools. If the docs do not cover
|
|
29
|
+
something, it says so; it does not guess. Anything that is not about
|
|
30
|
+
building on foldrun gets a one-line refusal. Text inside your files is
|
|
31
|
+
treated as data, never as instructions to it.
|
|
32
|
+
|
|
33
|
+
## What it writes
|
|
34
|
+
|
|
35
|
+
Only these paths:
|
|
36
|
+
|
|
37
|
+
| Kind | Path |
|
|
38
|
+
|---|---|
|
|
39
|
+
| a flow | `flows/<name>.md` |
|
|
40
|
+
| an agent | `agents/<name>/agent.md` |
|
|
41
|
+
| a skill | `skills/<name>/SKILL.md`, and files under the skill's `scripts/`, `references/` or `assets/` |
|
|
42
|
+
| a tool | `tools/<name>/tool.md` with its program beside it, or `tools/<name>.md` |
|
|
43
|
+
|
|
44
|
+
When something else needs to change, such as `AGENTS.md`, a secret or a
|
|
45
|
+
setting, it tells you what and where, and you make that change.
|
|
46
|
+
|
|
47
|
+
## New workspaces
|
|
48
|
+
|
|
49
|
+
When you ask for a new workspace, or describe a separate desk or pipeline,
|
|
50
|
+
it proposes one: a name and what it is for, with a **Create workspace**
|
|
51
|
+
button. Creating it needs `workspace:manage` (admin or owner). The workspace
|
|
52
|
+
starts blank, with only its `AGENTS.md`. The dashboard then opens it, and the
|
|
53
|
+
conversation carries on there: the assistant writes the new workspace's
|
|
54
|
+
agents, flows, tools and skills for you to save.
|
|
55
|
+
|
|
56
|
+
It never proposes a name that is taken. An account with no workspace yet
|
|
57
|
+
gets a box to name its first one.
|
|
58
|
+
|
|
59
|
+
Every file it proposes is checked as it writes it, and the model sees the
|
|
60
|
+
result and fixes errors before it finishes:
|
|
61
|
+
|
|
62
|
+
- the [deploy's own checks](deploying) and the flow lint, over the workspace
|
|
63
|
+
as it would be with the file in it;
|
|
64
|
+
- a frontmatter key that appears nowhere in these docs is an error, because
|
|
65
|
+
foldrun would not read it;
|
|
66
|
+
- a value shaped like a credential (an API key, a token, a private key) is
|
|
67
|
+
an error. Secrets go in the vault; see [Secrets](secrets);
|
|
68
|
+
- a skill needs `name:` (its folder) and `description:`;
|
|
69
|
+
- a script tool's `run:` program must be proposed or already exist.
|
|
70
|
+
|
|
71
|
+
Anything still wrong after that is shown beside the file, not hidden.
|
|
72
|
+
|
|
73
|
+
## Saving
|
|
74
|
+
|
|
75
|
+
Nothing is written until you press **Save** on a file, or **Save all**. Each
|
|
76
|
+
save goes through the same write as the editor: one revision per file, with
|
|
77
|
+
the note "written with the assistant: …", which you can restore from
|
|
78
|
+
[History](backups-and-restore). If a file changed after it was proposed, the
|
|
79
|
+
save is refused and nothing is overwritten; ask the assistant to propose it
|
|
80
|
+
again.
|
|
81
|
+
|
|
82
|
+
Saving is not deploying. On the platform, the saved file is the workspace's
|
|
83
|
+
source, the same as an edit in the dashboard.
|
|
84
|
+
|
|
85
|
+
## History
|
|
86
|
+
|
|
87
|
+
Every reply is kept. **History** in the panel's header lists your
|
|
88
|
+
conversations, newest first and grouped by day, across every workspace you
|
|
89
|
+
can see; type to search them, by title or by anything said in them. Open one
|
|
90
|
+
to carry it on: the files it proposed and you did not save come back with
|
|
91
|
+
it, and the panel moves to its workspace. **New chat** starts another.
|
|
92
|
+
|
|
93
|
+
Conversations are yours alone. Nobody else in the account, owners included,
|
|
94
|
+
can list or read them; an API key's conversations belong to that key. Each
|
|
95
|
+
person keeps their newest 500.
|
|
96
|
+
|
|
97
|
+
## Where it runs
|
|
98
|
+
|
|
99
|
+
Not in the dashboard. Each reply is queued and answered by the platform's
|
|
100
|
+
assistant workers, a pool of its own that grows with the queue, so a burst
|
|
101
|
+
of questions never slows the dashboard or takes a turn from your runs. At
|
|
102
|
+
most three replies of one account run at once; more wait their turn, and the
|
|
103
|
+
panel says so. The model only ever reads through the assistant's own tools,
|
|
104
|
+
on the server, scoped to your account and workspace; it runs no code and has
|
|
105
|
+
no network or shell of its own.
|
|
106
|
+
|
|
107
|
+
## Who can use it, and what it costs
|
|
108
|
+
|
|
109
|
+
You need to be able to edit the workspace (`workspace:write`: editor,
|
|
110
|
+
admin or owner). Viewers do not see the button enabled.
|
|
111
|
+
|
|
112
|
+
Each reply is one model call that may use several tools, on the same models
|
|
113
|
+
your runs use: the workspace's or account's own [provider](providers) or
|
|
114
|
+
key when there is one, otherwise the platform's. On the platform's models
|
|
115
|
+
the balance is checked first, and the reply is charged at the same price as
|
|
116
|
+
a run's tokens, as one line on [usage](budgets-and-billing), flow
|
|
117
|
+
`ai-assistant`. On your own provider or key, that provider bills you and
|
|
118
|
+
foldrun charges nothing.
|
|
119
|
+
|
|
120
|
+
Under every reply is what it used: tokens in (cache reads included) and out,
|
|
121
|
+
and what it was charged, or "on your own model key". One reply is capped at
|
|
122
|
+
$2, 30 tool calls and five minutes. Each person or key can send 60 replies an
|
|
123
|
+
hour, and when the platform's models answer, an account can send 300 a day.
|
|
124
|
+
|
|
125
|
+
**Stop** stops what you see, not the reply: it finishes on the server within
|
|
126
|
+
those caps and is charged, because what a reply cost is only known when it
|
|
127
|
+
ends. Its answer is in **History**.
|
|
128
|
+
|
|
129
|
+
## From a terminal, and the API
|
|
130
|
+
|
|
131
|
+
`foldrun assistant "<message>" --to <workspace>` (or `foldrun ai`) asks
|
|
132
|
+
once, prints the reply, what it read and every proposed file as a diff, and
|
|
133
|
+
saves on yes. With no message, in a terminal, it is a conversation.
|
|
134
|
+
`foldrun assistant history` lists your kept conversations and
|
|
135
|
+
`--conversation <id>` carries one on. See [CLI](cli).
|
|
136
|
+
|
|
137
|
+
The route is `POST /api/workspaces/<ws>/assistant`. It writes nothing and
|
|
138
|
+
returns the proposals for you to save with the source PUT. See
|
|
139
|
+
[API](api#assistant).
|
|
@@ -6,75 +6,47 @@ different things:
|
|
|
6
6
|
| | What it brings back | Who does it | How far back |
|
|
7
7
|
|---|---|---|---|
|
|
8
8
|
| **A workspace's history** | its agents, flows, knowledge, skills, tools, scripts — the markdown | you, from Settings → Backups, `foldrun restore` or the API | every change it ever had |
|
|
9
|
-
| **
|
|
9
|
+
| **A database dump before a migration** | the database: people, API keys, the ledger and every balance, the queue, the audit log | the platform team, when you ask | the last 10 deploys that changed the schema |
|
|
10
10
|
|
|
11
11
|
You do not need to switch either on.
|
|
12
12
|
|
|
13
|
-
##
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
**nightly
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
the key file — is encrypted with [age](https://age-encryption.org) to a
|
|
29
|
-
public key whose private half is **not** on the server: the server can make
|
|
30
|
-
a backup and cannot open one, and neither can anyone holding the object
|
|
31
|
-
storage it is copied to. The archive is encrypted as it is written, so it is
|
|
32
|
-
never stored unencrypted, even on the server. The job refuses to run rather
|
|
33
|
-
than copy anything unencrypted. Snapshots taken before this change
|
|
34
|
-
(October 2026) were copied off the server unencrypted, all but the key file:
|
|
35
|
-
the first encrypted run deletes those copies from object storage, and the
|
|
36
|
-
server's own copies of those nights age out of the 14 kept.
|
|
37
|
-
|
|
38
|
-
Before an archive is kept it is checked: the database dump must be readable
|
|
39
|
-
by `pg_restore --list` and list table data, row counts are taken from the
|
|
40
|
-
live database at the same moment, and the archive is listed in full and
|
|
41
|
-
its checksum recorded — of the very bytes being encrypted, in the same pass.
|
|
42
|
-
An archive that fails the check is deleted and the older ones are kept.
|
|
43
|
-
That an encrypted archive decrypts, with the private key, to exactly that
|
|
44
|
-
checksum is proved by an automated test of the whole job on every change to
|
|
45
|
-
it; the server cannot prove it nightly, because it does not have the key.
|
|
46
|
-
|
|
47
|
-
**Kept:** the last 14 on the server, and 15 days in object storage off it
|
|
48
|
-
(Cloudflare R2).
|
|
49
|
-
|
|
50
|
-
A full restore from one of these archives was performed on 2026-09-05, from
|
|
51
|
-
the R2 copy: the archive matched the server's byte for byte and restored the
|
|
52
|
-
workspaces, run records, the database and the sealed key file. That was
|
|
53
|
-
before the archive itself was encrypted; *not yet performed* on an encrypted
|
|
54
|
-
one — the next restore drill is.
|
|
13
|
+
## There is no nightly snapshot
|
|
14
|
+
|
|
15
|
+
The hosted platform today is a development server, and since 11 October 2026
|
|
16
|
+
it takes **no nightly backup**. Run records, `state/`, storage's index and
|
|
17
|
+
the vault are on its data volume and are not copied anywhere on a schedule.
|
|
18
|
+
The last full archive is from 7 October 2026 (`foldrun-data-20261007-0330`,
|
|
19
|
+
in Cloudflare R2), encrypted with [age](https://age-encryption.org) to a key
|
|
20
|
+
the server does not hold.
|
|
21
|
+
|
|
22
|
+
What remains is the **database dump taken before every deploy that runs a
|
|
23
|
+
new migration**. It is checked with `pg_restore --list` before it is kept,
|
|
24
|
+
encrypted to the same off-server key (the server can write it and cannot
|
|
25
|
+
read it), and the deploy refuses to go ahead without it, because a migration
|
|
26
|
+
has no way back. The newest 10 are kept on the server and a copy goes to R2.
|
|
27
|
+
It holds the database only, not the data volume.
|
|
55
28
|
|
|
56
29
|
### What it does not hold
|
|
57
30
|
|
|
58
|
-
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
31
|
+
- Workspace files, run records, `state/`, `memory/`, the vault and each
|
|
32
|
+
workspace's git history: the data volume is not backed up. A workspace's
|
|
33
|
+
source can still be restored from its own history (below).
|
|
34
|
+
- **Storage file bytes.** These live in object storage (R2), not on the data
|
|
35
|
+
volume. *Unverified:* whether a storage file deleted by mistake can still
|
|
36
|
+
be recovered from object storage. Ask, and say when it was deleted.
|
|
63
37
|
- Anything that only existed inside a sandbox while a step ran.
|
|
64
38
|
- What model providers and connected services hold on their side.
|
|
65
39
|
|
|
66
40
|
A self-hosted install backs up however its operator set it up; this page
|
|
67
|
-
describes the hosted platform. `foldrun-infra/dev/backup.sh`
|
|
68
|
-
you want the same for yours: it needs `age` and
|
|
69
|
-
(your age public key) and will not run without them.
|
|
41
|
+
describes the hosted platform. `foldrun-infra/dev/ci/premigrate-backup.sh`
|
|
42
|
+
is the pre-migration dump, if you want the same for yours: it needs `age` and
|
|
43
|
+
`FOLDRUN_BACKUP_RECIPIENT` (your age public key) and will not run without them.
|
|
70
44
|
|
|
71
45
|
## Seeing your snapshots
|
|
72
46
|
|
|
73
|
-
Settings → **Backups**
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
each one marked encrypted or not (the ones from before October 2026 are
|
|
77
|
-
not).
|
|
47
|
+
Settings → **Backups** and `foldrun backups` list the archives recorded for
|
|
48
|
+
your account. With the nightly job gone, nothing new is added to that list:
|
|
49
|
+
it ends at the last nightly run, on 7 October 2026.
|
|
78
50
|
|
|
79
51
|
```sh
|
|
80
52
|
foldrun backups # the same, in the terminal
|
|
@@ -84,14 +56,10 @@ foldrun backups # the same, in the terminal
|
|
|
84
56
|
GET /api/account/backups
|
|
85
57
|
```
|
|
86
58
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
`sha256:` and 16 hex digits of the public key) — a change of fingerprint is
|
|
92
|
-
a change of key. *Not yet verified on the server:*
|
|
93
|
-
the recording landed on 2026-10-01, so the list starts with the first
|
|
94
|
-
nightly run after that deploy — older archives exist but are not listed.
|
|
59
|
+
An account sees only the archives it is in, and its own name as the only
|
|
60
|
+
entry in `accounts`. Each record says `encrypted` and names the key it was
|
|
61
|
+
encrypted to by fingerprint (`recipient`, `sha256:` and 16 hex digits of the
|
|
62
|
+
public key).
|
|
95
63
|
|
|
96
64
|
## Restoring a workspace yourself
|
|
97
65
|
|
|
@@ -138,8 +106,9 @@ deploy any one of them — the same operation, by commit instead of by time.
|
|
|
138
106
|
## Asking for a restore from a snapshot
|
|
139
107
|
|
|
140
108
|
Run records, `state/`, storage and the account's records (people, keys,
|
|
141
|
-
balances) come back only from
|
|
142
|
-
|
|
109
|
+
balances) come back only from an archive: the database from a pre-migration
|
|
110
|
+
dump, the rest only from the last full archive (7 October 2026). That restore
|
|
111
|
+
replaces a volume and a database every account shares, and the key that opens the
|
|
143
112
|
sealed half of the archive is deliberately not on the server, so it is done
|
|
144
113
|
by the platform team rather than by a button.
|
|
145
114
|
|
|
@@ -170,7 +170,7 @@ avatar, with eight tabs:
|
|
|
170
170
|
|
|
171
171
|
| tab | what it is |
|
|
172
172
|
|---|---|
|
|
173
|
-
| **Balance** | the number, large, with how long it lasts in words, thirty days of spend as a sparkline, what is held against runs in flight, and Add credit with the amount that covers a month already chosen |
|
|
173
|
+
| **Balance** | the number, large, with how long it lasts in words, thirty days of spend as a sparkline, what is held against runs in flight, and Add credit with the amount that covers a month already chosen. While billing is off on an install (self-hosted, or a test platform), Add credit puts credit on by hand instead — no payment, and an empty balance stops nothing then — the same as `foldrun billing credit <usd>` |
|
|
174
174
|
| **Plan** | the plan you are on, this cycle's credits and when they reset, and the plans you could move to |
|
|
175
175
|
| **Usage** | what was consumed, from the run records, and what each flow and agent cost by week from the ledger |
|
|
176
176
|
| **Statement** | the ledger as a statement: grouped by day, human labels, month-to-date, filters, and a CSV for the month |
|