foldrun 0.7.2 → 0.9.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/README.md +10 -7
- package/bin/foldrun.mjs +74 -32
- package/docs/agents.md +64 -4
- package/docs/api-usage.md +4 -3
- package/docs/api.md +29 -14
- package/docs/budgets-and-billing.md +4 -0
- package/docs/cli.md +66 -25
- package/docs/coding-agents.md +9 -3
- package/docs/deploying.md +5 -1
- package/docs/faq.md +5 -2
- package/docs/feature-flags.md +63 -0
- package/docs/flows.md +24 -5
- package/docs/getting-started.md +1 -0
- package/docs/providers.md +28 -0
- package/docs/runs.md +8 -3
- package/docs/tools.md +16 -1
- package/docs/versions.md +25 -0
- package/docs/workspaces.md +87 -0
- package/package.json +4 -4
- package/src/commands.mjs +786 -34
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Feature flags
|
|
2
|
+
|
|
3
|
+
Some changes ship dark: the code is deployed, and whether an account gets
|
|
4
|
+
the new behaviour is a decision made afterwards, by the platform team, one
|
|
5
|
+
account or a share of accounts at a time. That decision is a feature flag.
|
|
6
|
+
|
|
7
|
+
An account cannot change its flags. It can see them: **Settings → Feature
|
|
8
|
+
flags** in the dashboard, `foldrun flags` in a terminal, or
|
|
9
|
+
`GET /api/account/flags`. Each flag says whether it is on for this account
|
|
10
|
+
and why.
|
|
11
|
+
|
|
12
|
+
## What decides a flag
|
|
13
|
+
|
|
14
|
+
The flags themselves are code — a name, what it changes, who owns it, a
|
|
15
|
+
default, and sometimes a rollout percentage. A name the code does not
|
|
16
|
+
register is not a flag; asking for one is a 404. What the platform's super
|
|
17
|
+
admin sets on top is checked in this order, and the first that has an
|
|
18
|
+
answer wins:
|
|
19
|
+
|
|
20
|
+
| | source | |
|
|
21
|
+
|---|---|---|
|
|
22
|
+
| 1 | `account` | this account's own override — on or off whatever else says |
|
|
23
|
+
| 2 | `global` | the install-wide setting — on or off for every account without an override |
|
|
24
|
+
| 3 | `rollout` | a percentage of accounts. Each account falls in a fixed bucket from 0 to 99, a hash of the account and the flag's names, so the same account gets the same answer on every server and every day, and raising the percentage only adds accounts — nobody who had it loses it |
|
|
25
|
+
| 4 | `default` | what the code says when nothing else does |
|
|
26
|
+
|
|
27
|
+
A change is seen at once on the server that made it, and on every other
|
|
28
|
+
within ten seconds.
|
|
29
|
+
|
|
30
|
+
## The example
|
|
31
|
+
|
|
32
|
+
`example-flag` is registered, off by default, and read by nothing: turning
|
|
33
|
+
it on changes no behaviour. It is there to try the console, the API and the
|
|
34
|
+
CLI against without risk.
|
|
35
|
+
|
|
36
|
+
```sh
|
|
37
|
+
foldrun flags # every flag, on or off for this account, and why
|
|
38
|
+
foldrun flags --json
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
```json
|
|
42
|
+
{ "flags": [ { "name": "example-flag", "description": "The registered example. …",
|
|
43
|
+
"owner": "platform", "default": false, "on": false,
|
|
44
|
+
"source": "default", "rollout": null } ] }
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Running the platform yourself
|
|
48
|
+
|
|
49
|
+
On a hosted install the super admin changes flags at **/admin → Flags**:
|
|
50
|
+
the install-wide on/off and rollout, and an override per account. The same
|
|
51
|
+
is `PUT`/`DELETE /api/admin/flags/<flag>` and
|
|
52
|
+
`/api/admin/flags/<flag>/accounts/<account>` (see the API reference's
|
|
53
|
+
super admin section). Every change is on the platform's audit as
|
|
54
|
+
`operator.flag.global`, `operator.flag.global_cleared`,
|
|
55
|
+
`operator.flag.account` or `operator.flag.account_cleared`, with who made
|
|
56
|
+
it and what it was before. The settings are rows in Postgres
|
|
57
|
+
(`feature_flags`), or `<data>/.feature-flags.json` without a database; an
|
|
58
|
+
account's overrides go when the account is deleted, and their audit stays.
|
|
59
|
+
|
|
60
|
+
A flag is added by a line in `foldrun-platform/src/flags.ts` and asked with
|
|
61
|
+
`flagOn(account, "<name>")`. Retire it by deleting the line and the code
|
|
62
|
+
that asks, once it is on for everyone; a row left behind for a name the
|
|
63
|
+
code no longer registers is ignored.
|
package/docs/flows.md
CHANGED
|
@@ -109,12 +109,31 @@ begins.
|
|
|
109
109
|
| `limits:` | this step's call limits, on one line — `limits: {web.search: 10, calls: 50}` — over the agent's, key by key. A call past one is refused, never run ([Tools](tools#limits--how-many-calls-a-step-may-make)) |
|
|
110
110
|
| `verify:` | a shell command, or an assertion (`contains:`, `matches:`, `file:`, `judge:`). The shell runs in the agent's folder and gets the step's final turn in the file `$FOLDRUN_REPLY_FILE`; `workspace/…` reaches the workspace root there, and in `file: workspace/storage/x.md`; `node workspace/tools/x/check.mjs` runs as its own main module, so a check it makes counts ([Agents](agents#where-files-live-from-the-agents-point-of-view)). The check runs on what is left of the step's `timeout:` (at least 30 s of it); with no `timeout:` it has no clock either. A stop ends it |
|
|
111
111
|
| `approve:` | park until a person releases it |
|
|
112
|
-
| `ask:` | the same gate carrying a question; the typed answer reaches the prompt |
|
|
112
|
+
| `ask:` | the same gate carrying a question; the typed answer reaches the prompt (a plain approval with no note reaches it as "approved with no note") |
|
|
113
113
|
| `preview:` | what the gate shows: paths under `storage/` (a `workspace/storage/` prefix is accepted), comma-separated, globs allowed — `draft/*.mdx, draft/images/*.webp`; at most 12 files |
|
|
114
|
-
| `wait:` | `3d` (`s`/`m`/`h`/`d`, capped at 30 days) or `event` |
|
|
115
|
-
| `on-fail:` | another agent takes the step over, with the failure as context |
|
|
114
|
+
| `wait:` | `3d` (`s`/`m`/`h`/`d`, capped at 30 days) or `event` — the run page shows the event URL and can release it with a payload, recorded as released by you (approve with `payload`) |
|
|
115
|
+
| `on-fail:` | another agent takes the step over, with the failure as context. The rescuer owes what the step owed: its `verify:` and its `output:` |
|
|
116
116
|
| `delegate:` | the step's agent picks who runs next, from this set |
|
|
117
117
|
|
|
118
|
+
### What is retried without a `retry:`
|
|
119
|
+
|
|
120
|
+
Some failures are the platform's or the network's, not the step's, and are
|
|
121
|
+
tried again whoever wrote the flow:
|
|
122
|
+
|
|
123
|
+
| what went wrong | what happens |
|
|
124
|
+
|---|---|
|
|
125
|
+
| the step's model connection was cut (the worker or the egress proxy restarted) | the step runs again, up to twice |
|
|
126
|
+
| the model wrote a tool call out as text instead of making it, so nothing ran | the step runs again once, told what went wrong |
|
|
127
|
+
| a connected account's token (`oauth2` secret) could not be refreshed for a network blip, a 5xx or a 429 | tried three times, about 1 s and 3 s apart; a refusal (revoked, wrong client) is not retried and mails the reconnect link |
|
|
128
|
+
| a `web` provider timed out or answered busy | tried three times, about 2 s and 6 s apart (see the web tool) |
|
|
129
|
+
|
|
130
|
+
None of the step-level ones runs again once a tool that can act outside the
|
|
131
|
+
workspace has run in that attempt: the post may already be up. When a step
|
|
132
|
+
still fails, the run says so — `notify:` sends `failed` (and `blocked`, for a
|
|
133
|
+
run that finished but refused), and the run page's **Re-run from here**,
|
|
134
|
+
`foldrun rerun <run-id> --from <n>` or `POST …/runs/<id>/rerun` start it
|
|
135
|
+
again once the cause is fixed.
|
|
136
|
+
|
|
118
137
|
### Routing
|
|
119
138
|
|
|
120
139
|
```markdown
|
|
@@ -272,10 +291,10 @@ decided by a number belongs in a command that can be tested:
|
|
|
272
291
|
`contains:`, `not-contains:` and `matches:` test the step's **conclusion** —
|
|
273
292
|
its final reply, the same text the run's headline is read from — so a step
|
|
274
293
|
that narrated between tool calls and then opened its answer with `BAD — …`
|
|
275
|
-
passes `matches: ^BAD\b`. `judge:` grades the
|
|
294
|
+
passes `matches: ^BAD\b`. `judge:` grades the same conclusion as "the reply", with everything the step wrote before it shown alongside as context, so a claim about the work can still be checked.
|
|
276
295
|
|
|
277
296
|
The command sees what the step's scripts saw — its secrets, `FOLDRUN_RUN_ID`,
|
|
278
|
-
`FOLDRUN_AGENT`, `FOLDRUN_WORKSPACE`, `FOLDRUN_DATE`, `TZ` — so a marker a step leaves on disk can
|
|
297
|
+
`FOLDRUN_AGENT`, `FOLDRUN_WORKSPACE`, `FOLDRUN_DATE`, `TZ`, and `FOLDRUN_API_URL` (this install's own API, when it has a public address) — so a marker a step leaves on disk can
|
|
279
298
|
be checked to name *this* run. That matters: a run's copy-back never
|
|
280
299
|
propagates a deletion, so a marker from an earlier run rides into every later
|
|
281
300
|
sandbox. `judge:` is a toolless fast-tier grading call; the other four cost
|
package/docs/getting-started.md
CHANGED
|
@@ -8,6 +8,7 @@ files are the program — so the loop is edit, check, run, deploy.
|
|
|
8
8
|
```sh
|
|
9
9
|
npm install -g foldrun
|
|
10
10
|
export ANTHROPIC_API_KEY=sk-ant-... # an API key from console.anthropic.com
|
|
11
|
+
# or: export CLAUDE_CODE_OAUTH_TOKEN=... from `claude setup-token` (see the CLI reference)
|
|
11
12
|
|
|
12
13
|
foldrun init my-account
|
|
13
14
|
cd my-account
|
package/docs/providers.md
CHANGED
|
@@ -1,5 +1,31 @@
|
|
|
1
1
|
# Providers, by name
|
|
2
2
|
|
|
3
|
+
## Your account's own model key
|
|
4
|
+
|
|
5
|
+
The simplest way to bring a key: **Settings → Model provider**, or
|
|
6
|
+
`foldrun account model set <provider> --key <api key>`, or
|
|
7
|
+
`PUT /api/account/model`. Pick a name from the table below (or `custom`
|
|
8
|
+
with a base URL and a format), paste an **API key**, and every step in
|
|
9
|
+
every workspace runs on it. No `provider:` block is needed; one written in
|
|
10
|
+
an agent, a workspace or the account's AGENTS.md still wins over it.
|
|
11
|
+
|
|
12
|
+
The key is stored in the account's vault, encrypted under the account's own
|
|
13
|
+
key, as `FOLDRUN_MODEL_PROVIDER`, `FOLDRUN_MODEL_API_KEY` and, for a custom
|
|
14
|
+
endpoint, `FOLDRUN_MODEL_BASE_URL` and `FOLDRUN_MODEL_FORMAT`. The dashboard
|
|
15
|
+
and the API show only its last four characters.
|
|
16
|
+
|
|
17
|
+
**On a hosted platform every account brings its own key.** The platform's
|
|
18
|
+
own model credential is for the operator's accounts
|
|
19
|
+
(`FOLDRUN_PLATFORM_KEY_ACCOUNTS`); a step in any other account with no key
|
|
20
|
+
of its own is refused before it starts — "no model key: …" on the run — and
|
|
21
|
+
an eval's judge or "draft with AI" says the same. A **Claude login token**
|
|
22
|
+
(`sk-ant-oat…`, from `claude setup-token`) is refused as a customer's key:
|
|
23
|
+
Anthropic's terms do not let a third-party product run its customers on
|
|
24
|
+
claude.ai logins. Use an API key from the provider.
|
|
25
|
+
|
|
26
|
+
Tokens on your own key are billed by your provider, not by the platform
|
|
27
|
+
([budgets and billing](budgets-and-billing.md)).
|
|
28
|
+
|
|
3
29
|
What `provider: { name: … }` resolves to. Mirrors `foldrun-core/src/providers.ts`,
|
|
4
30
|
checked against each provider's own documentation on 2026-09-02 and again on
|
|
5
31
|
2026-09-06; the table below is what the runtime believes, and
|
|
@@ -178,6 +204,8 @@ what would be checked; `POST` forces one, for the minute after rotating a
|
|
|
178
204
|
key. From the terminal, `foldrun account providers` prints the same table
|
|
179
205
|
(✓, ✗ with the verdict, or · for never checked) and `--check` forces one;
|
|
180
206
|
a dead key is a non-zero exit, so a deploy script can refuse to continue.
|
|
207
|
+
In the dashboard, Settings → Model provider shows the same check per
|
|
208
|
+
provider, with **Check now** (editor and above, since it spends a request).
|
|
181
209
|
|
|
182
210
|
## When a provider says no, the run says why
|
|
183
211
|
|
package/docs/runs.md
CHANGED
|
@@ -87,7 +87,11 @@ run until a person decides. Three doors open it, and the trace says which:
|
|
|
87
87
|
- **the emailed link** — the notification carries approve and reject links
|
|
88
88
|
when the install has a public URL;
|
|
89
89
|
- **an external event** — a `wait: event` step is released by a POST to the
|
|
90
|
-
run's event URL, and what was posted reaches the next step.
|
|
90
|
+
run's event URL, and what was posted reaches the next step. A person can
|
|
91
|
+
release it the same way from the run page (or `approve` with a `payload`):
|
|
92
|
+
the payload reaches the step just the same, and the trace names them, not
|
|
93
|
+
"an external event". The approvers and self-approval rules below are for
|
|
94
|
+
gates that ask a person; a `wait: event` release asks for `run:start`.
|
|
91
95
|
|
|
92
96
|
The answer typed at an `ask:` gate reaches the next step's prompt, so "GO,
|
|
93
97
|
but skip the third one" is an instruction the publisher reads.
|
|
@@ -132,7 +136,7 @@ What the platform does on a test run, in the order a step meets it:
|
|
|
132
136
|
| the egress proxy | any `POST`, `PUT`, `PATCH` or `DELETE` to a host that is not a model provider or a read-only service is **refused** with `403` and a JSON body `{ "test_mode": true, "would_have": { method, host, path, to, body } }`. A Resend `POST /emails` is instead **redirected**: the recipients become `delivered@resend.dev` (Resend's sink), the subject is prefixed `[TEST] `, and the call goes through — so a desk still gets a real message id. Reads (`GET`, `HEAD`, `OPTIONS`) pass. A host with no granted secret is refused as always |
|
|
133
137
|
| the sandbox | secrets whose name says they can send — `RESEND_API_KEY`, `TWILIO_*`, `GETREACH_API_KEY`, `GITHUB_TOKEN`, `MONDAY_API_TOKEN`, `OI_CRM_API_KEY`, `CLOUDFLARE_*`, `LINKEDIN_*`, `MEDIUM_COOKIES`, `TIKTOK_*` — are **withheld**: the env var reads `TEST_MODE_WITHHELD`, so a script that never checked fails at the provider with an auth error rather than sending. A step granting a script tool marked `outward: true` gets no real secret at all; one granting a tool marked `test_mode: allow` gets them all. The sandbox also has `FOLDRUN_TEST_MODE=1` and `FOLDRUN_RUN_TEST=1`, for scripts that branch on it. The http tools still go through the proxy, where the table above applies |
|
|
134
138
|
| the browser | the step is handed no account [browser pod](tools#where-the-browser-runs): the tool launches Chromium inside the run pod, which the run namespace's policy denies the internet, so a `web action=browse` call fails closed rather than fetching the world through a pod the policy does not select |
|
|
135
|
-
| the write-back | `state/` and `storage/` go into the sandbox as they are; what the step wrote there comes out under `runs/<id>/test-writes/state/…` and `…/storage/…`, and the workspace's copies are untouched. Nothing is harvested to the file store and nothing under `storage/public/` is published |
|
|
139
|
+
| the write-back | `state/` and `storage/` go into the sandbox as they are; what the step wrote there comes out under `runs/<id>/test-writes/state/…` and `…/storage/…`, and the workspace's copies are untouched. Each later step of the run gets those writes laid over its copy, times kept, so a flow that hands work on through `storage/` is tested as it runs live — the step's log says how many files it sees. Nothing is harvested to the file store and nothing under `storage/public/` is published |
|
|
136
140
|
| the record | `test: true`; the summary opens with `[test]`; a **TEST** badge beside the status in the runs list and on the run page; every refusal, redirect, diversion and withheld secret is an event carrying an `effect`, and the run page's **Test run** panel lists them — "would have written state/sends.md, +3 lines" |
|
|
137
141
|
| the rest | gates still park, and the approve page says it is a test run; notifications go out only for `failed`; a `trigger: flow` chained on a test run starts as a test run |
|
|
138
142
|
|
|
@@ -155,7 +159,8 @@ flow, a time window, or an explicit list of ids. It is the tool for the
|
|
|
155
159
|
morning when one broken thing failed seventy runs. A call naming no filter is
|
|
156
160
|
refused rather than treated as everything, `dryRun: true` shows what would be
|
|
157
161
|
touched, and each run is its own outcome so one that cannot be stopped does
|
|
158
|
-
not abort the rest.
|
|
162
|
+
not abort the rest. On the runs pages, tick runs and choose **Stop** or
|
|
163
|
+
**Rerun**: the bar makes this call, once per workspace in the selection.
|
|
159
164
|
|
|
160
165
|
**Re-run from a group** (`from: N`) starts a new run at group N with the
|
|
161
166
|
earlier groups recorded as skipped, not invented — the step reads what earlier
|
package/docs/tools.md
CHANGED
|
@@ -54,7 +54,7 @@ headers:
|
|
|
54
54
|
|
|
55
55
|
| field | |
|
|
56
56
|
|---|---|
|
|
57
|
-
| `base` | the URL every call is relative to |
|
|
57
|
+
| `base` | the URL every call is relative to. `${FOLDRUN_API_URL}` is this install's own API (`base: ${FOLDRUN_API_URL}`), so a tool that reads the account's runs never names a host; a base naming it on an install that has no public address is reported unavailable |
|
|
58
58
|
| `methods` | the verb allowlist — a tool that declares none is read-only |
|
|
59
59
|
| `headers` | `${SECRET}` placeholders, resolved host-side at call time |
|
|
60
60
|
| `query` | query parameters appended to every call — for an API that carries its key in the query string; a `${SECRET}` is resolved host-side |
|
|
@@ -63,6 +63,13 @@ headers:
|
|
|
63
63
|
| `openapi` | a URL or file; turns one generic tool into typed ones, one per operation |
|
|
64
64
|
| `operations` | only these operations, by `operationId` or `METHOD /path`; the generic tool is withheld. It fails closed: if the document does not load or none of them resolve, the API has no tools that run, never the generic one. Keep an allowlisted document in the workspace, so a vendor renaming an operation cannot empty it |
|
|
65
65
|
|
|
66
|
+
**The body is the manual.** Everything under a `tool.md`'s frontmatter goes
|
|
67
|
+
to the model with the tool, after `description:` — how to call it, what it
|
|
68
|
+
refuses, what its output means, examples of a call (up to 6,000 characters;
|
|
69
|
+
a longer body is cut there and says so). A single-file tool's fenced program
|
|
70
|
+
is code, not guidance, and stays out. This holds for script and HTTP tools
|
|
71
|
+
alike, workspace or library.
|
|
72
|
+
|
|
66
73
|
The model never sees a credential — the host substitutes it as the request
|
|
67
74
|
goes out. The method allowlist protects against the agent; the key's own scopes
|
|
68
75
|
protect against everyone else, so pair a read-only tool with a read-only key.
|
|
@@ -221,6 +228,14 @@ the agent — which is already the model — to answer from; `extract` by
|
|
|
221
228
|
`schema=` or `prompt=`, which needs a model to read the page, needs a
|
|
222
229
|
provider.
|
|
223
230
|
|
|
231
|
+
Granting `web` grants **Read** with it: several actions write what they
|
|
232
|
+
found to a file and say so (a screenshot, a crawl, a page saved whole), and
|
|
233
|
+
an agent told to read a file it could not open was refused. `disallowedTools:
|
|
234
|
+
[Read]` takes it away again. A provider that times out or answers busy (429,
|
|
235
|
+
5xx) is asked twice more, about 2 s and 6 s apart, before the action reports
|
|
236
|
+
it unreachable — a fan-out of many steps asking one provider at once is the
|
|
237
|
+
usual cause.
|
|
238
|
+
|
|
224
239
|
```yaml
|
|
225
240
|
tools: [web]
|
|
226
241
|
web:
|
package/docs/versions.md
CHANGED
|
@@ -25,6 +25,31 @@ dashboard and the API), `docs` and `infra` — recorded when it was deployed.
|
|
|
25
25
|
The `foldrun` CLI and `@foldrun/core` on npm are packages a person publishes,
|
|
26
26
|
and keep ordinary semver (`0.6.0`).
|
|
27
27
|
|
|
28
|
+
## Rollback and the database
|
|
29
|
+
|
|
30
|
+
A rollback redeploys older code, but not an older database: the schema stays
|
|
31
|
+
where the newest release left it, because migrations only run forwards. So
|
|
32
|
+
every migration has to work with the code of the release before it — the
|
|
33
|
+
expand/contract rule. A release only expands: a new table, a nullable column,
|
|
34
|
+
a column with a `DEFAULT`, an index. Taking something away that released code
|
|
35
|
+
still uses — dropping or renaming a table or column, changing a column's
|
|
36
|
+
type, making a column `NOT NULL` with no default, dropping a constraint or a
|
|
37
|
+
default, `TRUNCATE`, a `DELETE` with no `WHERE` — is the contract half, and
|
|
38
|
+
ships a release later, once no deployed version reads it. A migration that
|
|
39
|
+
has been released is never edited; the next change is a new file.
|
|
40
|
+
|
|
41
|
+
The platform enforces this. `scripts/migration-guard.mjs` in
|
|
42
|
+
`foldrun-platform` reads the migrations added since the deployed version (in
|
|
43
|
+
CI, since the commit a push or pull request started from) and refuses those
|
|
44
|
+
statements, in CI and in the pre-push hook. A deliberate contract step says so
|
|
45
|
+
on the line above the statement, with a reason, and is reported rather than
|
|
46
|
+
refused:
|
|
47
|
+
|
|
48
|
+
```sql
|
|
49
|
+
-- migration-guard: contract api_keys.label unread since v2026.09.30.1
|
|
50
|
+
ALTER TABLE "api_keys" DROP COLUMN "label";
|
|
51
|
+
```
|
|
52
|
+
|
|
28
53
|
## Where to see it
|
|
29
54
|
|
|
30
55
|
| Where | What |
|
package/docs/workspaces.md
CHANGED
|
@@ -54,6 +54,85 @@ pulled, only edited in Settings.
|
|
|
54
54
|
A single folder with `agents/` and `flows/` at its root is still a workspace
|
|
55
55
|
and still works everywhere; `foldrun init --flat` makes one.
|
|
56
56
|
|
|
57
|
+
## A new workspace starts blank
|
|
58
|
+
|
|
59
|
+
**Create workspace** on the dashboard, `POST /api/workspaces` with
|
|
60
|
+
`template: true`, and `foldrun new <name>` all make a workspace with nothing in
|
|
61
|
+
it to delete: an `AGENTS.md` (plus, on a laptop, the `.gitignore` that keeps
|
|
62
|
+
its secret key out of git and a `CLAUDE.md` for your coding tool). No example
|
|
63
|
+
agents, no flow. `foldrun new <name> --starter` and `starter: true` still make
|
|
64
|
+
the example researcher, writer and `publish` flow; `foldrun init` makes it for
|
|
65
|
+
an account's first workspace, which the getting-started guide runs.
|
|
66
|
+
|
|
67
|
+
A workspace with no agents yet deploys; `check` and the deploy say so as a
|
|
68
|
+
warning. A flow that names an agent the workspace lacks is still an error.
|
|
69
|
+
|
|
70
|
+
### Importing an agent you already have
|
|
71
|
+
|
|
72
|
+
The agents worth having in a new workspace are often ones already written in
|
|
73
|
+
another: **Import agent** on the Agents page (and in the empty state of a blank
|
|
74
|
+
workspace) lists every agent in the account's other workspaces you can read,
|
|
75
|
+
and copies the one you pick. From a terminal, `foldrun agent import
|
|
76
|
+
<workspace>/<agent>` does it between workspaces of an account folder, or on the
|
|
77
|
+
platform with `--to <workspace>`; the route is `POST
|
|
78
|
+
/api/workspaces/<ws>/agents/import` with `{ from, agent, as? }`.
|
|
79
|
+
|
|
80
|
+
What is copied is what the agent **is**: `agent.md` and its own `skills/`,
|
|
81
|
+
`scripts/` and `knowledge/`. What it **accumulated** stays where it was learned:
|
|
82
|
+
`memory/`, `outputs/`, `state/` and session files. A name already taken is
|
|
83
|
+
refused — import it under another name with `--as` / `as`, which rewrites its
|
|
84
|
+
`name:`. The import then says what the copy uses that this workspace lacks —
|
|
85
|
+
tools, skills, scripts, secrets — without refusing: set the secret, import the
|
|
86
|
+
tool, and it runs. The copy is independent; editing one does not change the
|
|
87
|
+
other.
|
|
88
|
+
|
|
89
|
+
### Export and import
|
|
90
|
+
|
|
91
|
+
Agent import works inside one account. To move a desk to another account or
|
|
92
|
+
installation — or to hand someone a flow — export it as a `.zip`:
|
|
93
|
+
|
|
94
|
+
- **A workspace:** Settings → **Export and import** → **Export workspace**.
|
|
95
|
+
- **A flow:** **Export** on its card on the Flows page. The package holds the
|
|
96
|
+
flow, every flow it runs as a step, every agent those steps name (with
|
|
97
|
+
`on-fail:`, `delegate:` and the colleagues those agents consult or delegate to),
|
|
98
|
+
and the workspace tools, skills and scripts they grant — including the
|
|
99
|
+
script a single-file tool's `run:` names.
|
|
100
|
+
- **An agent:** **Export** on its page: its folder (what agent import copies)
|
|
101
|
+
and the workspace tools, skills and scripts it grants. Colleagues it consults
|
|
102
|
+
are named, not carried.
|
|
103
|
+
|
|
104
|
+
A package is authored source only: `AGENTS.md` and the `agents/`, `flows/`,
|
|
105
|
+
`evals/`, `knowledge/`, `skills/`, `tools/` and `scripts/` folders. It never holds
|
|
106
|
+
`memory/`, `state/`, `outputs/`, storage, runs, `.env` files or a secret's
|
|
107
|
+
value. The secrets it uses travel by **name**, so the importer knows what to
|
|
108
|
+
set. An agent with no `skills:` line gets every skill in scope at run time, so
|
|
109
|
+
its package carries every workspace skill.
|
|
110
|
+
|
|
111
|
+
Anything else in a zip — memory, state, a `.gitignore` or `CLAUDE.md` beside a
|
|
112
|
+
folder you zipped yourself — is **not imported**, and the preview lists it.
|
|
113
|
+
|
|
114
|
+
To import, use **Import .zip** on the Flows or Agents page or in Settings, or
|
|
115
|
+
**Add new → Import workspace** on the home page. You pick the file and first
|
|
116
|
+
see a preview, with nothing written yet:
|
|
117
|
+
|
|
118
|
+
- what it adds;
|
|
119
|
+
- what is already identical;
|
|
120
|
+
- which files it would **replace** (only with a tick);
|
|
121
|
+
- what it needs that this account lacks: secrets, library tools and skills,
|
|
122
|
+
scripts, agents and flows it names.
|
|
123
|
+
|
|
124
|
+
An import never deletes anything, and it is all or nothing: a file it cannot
|
|
125
|
+
write (a folder already in its place, a link on the way) refuses the whole
|
|
126
|
+
import before anything is written. It lands as one revision, so History can
|
|
127
|
+
restore what it replaced. A workspace package imported under a new name makes
|
|
128
|
+
that workspace. A `.zip` of a workspace folder made by hand (a Mac's
|
|
129
|
+
Compress included) imports the same way.
|
|
130
|
+
|
|
131
|
+
From a terminal: `foldrun export [workspace] [--flow <name> | --agent <name>]`
|
|
132
|
+
and `foldrun import <file.zip> --to <workspace> [--dry-run] [--overwrite]`.
|
|
133
|
+
The routes are `GET /api/workspaces/<ws>/export` and
|
|
134
|
+
`POST /api/workspaces/<ws>/import` (`?dryRun=1`, `?overwrite=1`).
|
|
135
|
+
|
|
57
136
|
## `AGENTS.md` — context and defaults
|
|
58
137
|
|
|
59
138
|
The body is prose every agent in the workspace sees before its own prompt.
|
|
@@ -147,6 +226,14 @@ memory an agent wrote that your push does not mention. Those belong to the
|
|
|
147
226
|
platform, not to git, and a deploy that reverted what an agent learned would
|
|
148
227
|
make every run a little dumber.
|
|
149
228
|
|
|
229
|
+
The agent engine's own files are not workspace content either. Each step
|
|
230
|
+
runs in a sandbox on the Claude Agent SDK — the engine, whichever model
|
|
231
|
+
answers — and the engine writes its settings and session transcripts in the
|
|
232
|
+
agent's folder there (`agents/<name>/.claude.json`, `agents/<name>/.claude/`).
|
|
233
|
+
They stay in the sandbox: never copied back, never copied in. (Your own
|
|
234
|
+
`.claude/agents/*.md` at the workspace root is different — a Claude Code
|
|
235
|
+
subagent you wrote, deployed as an agent.)
|
|
236
|
+
|
|
150
237
|
A push while a run is in flight is accepted and applied when the run
|
|
151
238
|
finishes; git tells you so at push time. A run parked at an approval gate
|
|
152
239
|
counts as in flight. See [Deploying](deploying).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "foldrun",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "Agents are just folders. Write them, check them, run them — on your machine.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"node": ">=22"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@foldrun/core": "^0.
|
|
14
|
+
"@foldrun/core": "^0.7.0"
|
|
15
15
|
},
|
|
16
16
|
"files": [
|
|
17
17
|
"bin",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
],
|
|
22
22
|
"scripts": {
|
|
23
23
|
"e2e": "FOLDRUN_E2E=1 FOLDRUN_DATA=$(mktemp -d /tmp/foldrun-e2e-XXXXXX) node --test --test-timeout=300000 tests/e2e.test.ts",
|
|
24
|
-
"test": "FOLDRUN_DATA=$(mktemp -d /tmp/foldrun-test-XXXXXX) node --test tests/cli-account.test.ts tests/cli-approvals.test.ts tests/cli-stop.test.ts tests/cli-account-settings.test.ts tests/cli-schedule.test.ts tests/cli-billing.test.ts tests/cli-storage.test.ts tests/cli-scaffold.test.ts tests/cli-secrets.test.ts tests/cli-site-login.test.ts tests/cli-check-library.test.ts tests/cli-check-timezone.test.ts tests/cli-check-subagents.test.ts tests/cli-connect.test.ts tests/cli-accounts.test.ts tests/cli-remote.test.ts tests/cli-stream.test.ts tests/cli-doctor.test.ts tests/cli-tool-test.test.ts tests/cli-agent-run.test.ts tests/cli-check-remote.test.ts tests/cli-shares.test.ts tests/cli-triggers.test.ts tests/cli-providers.test.ts tests/cli-rerun-put.test.ts tests/cli-guide.test.ts tests/cli-runtimes.test.ts tests/cli-deploy-safety.test.ts tests/cli-operator.test.ts tests/cli-flow-patterns.test.ts tests/cli-flow-rm-step.test.ts tests/cli-flow-dup-step.test.ts tests/cli-flow-write-stale.test.ts tests/cli-agent-link-stale.test.ts tests/cli-parity.test.ts tests/cli-invoke-inputs.test.ts tests/cli-audit-keys.test.ts tests/cli-version.test.ts tests/cli-check-web-warnings.test.ts tests/cli-report-image.test.ts tests/cli-check-workspace-paths.test.ts tests/cli-parity-dashboard.test.ts tests/cli-api-standards.test.ts tests/cli-backups-onboarding.test.ts tests/cli-check-limits.test.ts tests/cli-gallery.test.ts tests/cli-status-platform.test.ts tests/cli-webhooks-notifications.test.ts",
|
|
24
|
+
"test": "FOLDRUN_DATA=$(mktemp -d /tmp/foldrun-test-XXXXXX) node --test tests/cli-account.test.ts tests/model-credential.test.ts tests/cli-approvals.test.ts tests/cli-stop.test.ts tests/cli-account-settings.test.ts tests/cli-schedule.test.ts tests/cli-billing.test.ts tests/cli-storage.test.ts tests/cli-scaffold.test.ts tests/cli-secrets.test.ts tests/cli-site-login.test.ts tests/cli-check-library.test.ts tests/cli-check-timezone.test.ts tests/cli-check-frontmatter.test.ts tests/cli-agent-import.test.ts tests/cli-check-subagents.test.ts tests/cli-connect.test.ts tests/cli-accounts.test.ts tests/cli-remote.test.ts tests/cli-stream.test.ts tests/cli-doctor.test.ts tests/cli-tool-test.test.ts tests/cli-agent-run.test.ts tests/cli-check-remote.test.ts tests/cli-shares.test.ts tests/cli-triggers.test.ts tests/cli-providers.test.ts tests/cli-rerun-put.test.ts tests/cli-guide.test.ts tests/cli-runtimes.test.ts tests/cli-deploy-safety.test.ts tests/cli-operator.test.ts tests/cli-flow-patterns.test.ts tests/cli-flow-rm-step.test.ts tests/cli-flow-dup-step.test.ts tests/cli-flow-write-stale.test.ts tests/cli-agent-link-stale.test.ts tests/cli-parity.test.ts tests/cli-invoke-inputs.test.ts tests/cli-audit-keys.test.ts tests/cli-version.test.ts tests/cli-check-web-warnings.test.ts tests/cli-report-image.test.ts tests/cli-check-workspace-paths.test.ts tests/cli-parity-dashboard.test.ts tests/cli-api-standards.test.ts tests/cli-backups-onboarding.test.ts tests/cli-check-limits.test.ts tests/cli-gallery.test.ts tests/cli-status-platform.test.ts tests/cli-webhooks-notifications.test.ts tests/cli-flags.test.ts tests/cli-api-coverage.test.ts",
|
|
25
25
|
"release": "node scripts/release.mjs",
|
|
26
26
|
"typecheck": "tsc -p tsconfig.json",
|
|
27
27
|
"docs:sync": "node scripts/sync-docs.mjs",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"access": "public"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
|
-
"@types/node": "^
|
|
50
|
+
"@types/node": "^26.6.4",
|
|
51
51
|
"gray-matter": "^4.0.3",
|
|
52
52
|
"typescript": "^5.9.3"
|
|
53
53
|
}
|