foldrun 0.6.0 → 0.8.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 +98 -16
- package/bin/foldrun.mjs +333 -46
- package/docs/agents.md +483 -0
- package/docs/api-usage.md +156 -0
- package/docs/api.md +435 -0
- package/docs/backups-and-restore.md +171 -0
- package/docs/budgets-and-billing.md +362 -0
- package/docs/cli.md +449 -0
- package/docs/coding-agents.md +224 -0
- package/docs/deploying.md +77 -0
- package/docs/evals.md +90 -0
- package/docs/faq.md +53 -0
- package/docs/flows.md +670 -0
- package/docs/getting-started.md +142 -0
- package/docs/glossary.md +78 -0
- package/docs/harness.md +128 -0
- package/docs/how-it-works.md +255 -0
- package/docs/introduction.md +47 -0
- package/docs/knowledge-and-memory.md +85 -0
- package/docs/notifications.md +320 -0
- package/docs/providers.md +241 -0
- package/docs/runs.md +238 -0
- package/docs/scheduling.md +103 -0
- package/docs/secrets.md +163 -0
- package/docs/security.md +167 -0
- package/docs/self-hosting.md +65 -0
- package/docs/status.md +73 -0
- package/docs/storage-and-sharing.md +118 -0
- package/docs/team-and-access.md +146 -0
- package/docs/tools.md +1810 -0
- package/docs/troubleshooting.md +155 -0
- package/docs/versions.md +79 -0
- package/docs/workspaces.md +160 -0
- package/package.json +9 -5
- package/src/commands.mjs +6487 -260
- package/src/guide.mjs +172 -0
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# Backups and restore
|
|
2
|
+
|
|
3
|
+
Two different things get called "a backup" here, and they bring back
|
|
4
|
+
different things:
|
|
5
|
+
|
|
6
|
+
| | What it brings back | Who does it | How far back |
|
|
7
|
+
|---|---|---|---|
|
|
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
|
+
| **The nightly snapshot** | everything else: runs, `state/`, storage's index, secrets, people, keys, the ledger, the audit log | the platform team, when you ask | the snapshots kept (below) |
|
|
10
|
+
|
|
11
|
+
You do not need to switch either on.
|
|
12
|
+
|
|
13
|
+
## What the nightly snapshot holds
|
|
14
|
+
|
|
15
|
+
On the hosted platform today (a single server), a job runs
|
|
16
|
+
**nightly at 03:30, server time (Sydney)** and archives every account
|
|
17
|
+
together:
|
|
18
|
+
|
|
19
|
+
- the whole data volume: every workspace's files — source, `memory/`,
|
|
20
|
+
`state/`, run records — each workspace's git history, the account's
|
|
21
|
+
encrypted secrets (the vault) and the storage index;
|
|
22
|
+
- the database (`pg_dump`): people, API keys, sessions, the ledger and every
|
|
23
|
+
balance, the queue, the audit log;
|
|
24
|
+
- the server's key file.
|
|
25
|
+
|
|
26
|
+
**Encrypted before it leaves the server.** Every file the job writes — the
|
|
27
|
+
archive (with the database dump inside it), the list of what it holds, and
|
|
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.
|
|
55
|
+
|
|
56
|
+
### What it does not hold
|
|
57
|
+
|
|
58
|
+
- **Storage file bytes.** On the hosted platform the files in a workspace's
|
|
59
|
+
Storage live in object storage (R2), not on the data volume; the snapshot
|
|
60
|
+
has the index of what exists, not the bytes. *Unverified:* whether a
|
|
61
|
+
storage file deleted by mistake can still be recovered from object
|
|
62
|
+
storage. Ask, and say when it was deleted.
|
|
63
|
+
- Anything that only existed inside a sandbox while a step ran.
|
|
64
|
+
- What model providers and connected services hold on their side.
|
|
65
|
+
|
|
66
|
+
A self-hosted install backs up however its operator set it up; this page
|
|
67
|
+
describes the hosted platform. `foldrun-infra/dev/backup.sh` is the job, if
|
|
68
|
+
you want the same for yours: it needs `age` and `FOLDRUN_BACKUP_RECIPIENT`
|
|
69
|
+
(your age public key) and will not run without them.
|
|
70
|
+
|
|
71
|
+
## Seeing your snapshots
|
|
72
|
+
|
|
73
|
+
Settings → **Backups** shows how often, how many are kept, when the last one
|
|
74
|
+
was taken and whether it was verified, copied off the server and
|
|
75
|
+
**encrypted**, and each snapshot that holds your account, newest first —
|
|
76
|
+
each one marked encrypted or not (the ones from before October 2026 are
|
|
77
|
+
not).
|
|
78
|
+
|
|
79
|
+
```sh
|
|
80
|
+
foldrun backups # the same, in the terminal
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
```http
|
|
84
|
+
GET /api/account/backups
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Each archive the job verifies is recorded with the accounts it holds; an
|
|
88
|
+
account sees only the archives it is in, and its own name as the only entry
|
|
89
|
+
in `accounts`. Each record says `encrypted` and
|
|
90
|
+
names the key it was encrypted to by fingerprint (`recipient`,
|
|
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.
|
|
95
|
+
|
|
96
|
+
## Restoring a workspace yourself
|
|
97
|
+
|
|
98
|
+
Every change to a workspace — a dashboard save, a deploy, a `git push` — is
|
|
99
|
+
a commit in its own git repository. Restoring puts the workspace's source
|
|
100
|
+
back as it was at one of those commits:
|
|
101
|
+
|
|
102
|
+
```sh
|
|
103
|
+
foldrun restore sales --to 2026-09-28T14:00 --dry-run # what would change, with the diff
|
|
104
|
+
foldrun restore sales --to 2026-09-28T14:00 # asks you to type the name, then does it
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
`--to` takes a commit (or the start of one), a branch or tag, a date or time
|
|
108
|
+
(the last commit at or before it — a time without a zone is read in the
|
|
109
|
+
server's timezone, so add `Z` or an offset to be exact), or an age: `90m`, `12h`, `3d`, `2w`.
|
|
110
|
+
|
|
111
|
+
- **Preview first.** The dry run lists every file that would change, come
|
|
112
|
+
back, or go, with a line diff. Nothing is written.
|
|
113
|
+
- **Confirm by name.** The restore itself needs the workspace's name typed
|
|
114
|
+
back (`confirm` in the API). The CLI sends the removals you were shown, and
|
|
115
|
+
if anything else would now be removed — a file added in between — the
|
|
116
|
+
restore is refused and you are asked to look again.
|
|
117
|
+
- **It is undoable.** A restore is a new commit whose files are the old
|
|
118
|
+
ones; history says "restored to abc1234". To undo it, restore to the commit
|
|
119
|
+
it answered as `from`.
|
|
120
|
+
- **It touches only the source.** Runs, `state/`, storage, secrets and the
|
|
121
|
+
memory agents wrote are what a deploy keeps, and a restore is a deploy:
|
|
122
|
+
they are left as they are.
|
|
123
|
+
- **It waits for runs.** While a run is in flight the restore is refused, as
|
|
124
|
+
a deploy is — the run would otherwise finish on agents its first step never
|
|
125
|
+
saw.
|
|
126
|
+
- It needs the editor role or above, and is written to the audit log as
|
|
127
|
+
`workspace.restored`.
|
|
128
|
+
|
|
129
|
+
```http
|
|
130
|
+
POST /api/workspaces/<ws>/restore
|
|
131
|
+
{ "to": "3d", "dryRun": true }
|
|
132
|
+
{ "to": "<sha from the dry run>", "confirm": "<ws>", "expectRemoved": [ … ] }
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
The workspace's History page lists every commit, and its Repository page can
|
|
136
|
+
deploy any one of them — the same operation, by commit instead of by time.
|
|
137
|
+
|
|
138
|
+
## Asking for a restore from a snapshot
|
|
139
|
+
|
|
140
|
+
Run records, `state/`, storage and the account's records (people, keys,
|
|
141
|
+
balances) come back only from a nightly snapshot. That restore replaces a
|
|
142
|
+
volume and a database every account shares, and the key that opens the
|
|
143
|
+
sealed half of the archive is deliberately not on the server, so it is done
|
|
144
|
+
by the platform team rather than by a button.
|
|
145
|
+
|
|
146
|
+
Settings → Backups → **Request a restore**: what (run records, `state/`,
|
|
147
|
+
storage, or everything), which workspace (or all), the time you want back,
|
|
148
|
+
optionally the snapshot, and a note. The request is recorded, mailed to the
|
|
149
|
+
platform team and written to the audit log (`backup.restore_requested`); the
|
|
150
|
+
page lists your earlier requests. Asking needs the admin role and access to
|
|
151
|
+
every workspace.
|
|
152
|
+
|
|
153
|
+
```sh
|
|
154
|
+
foldrun backups request --what runs --at "2026-09-28 14:00" --to sales --note "a run record was deleted"
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
```http
|
|
158
|
+
POST /api/account/backups/requests
|
|
159
|
+
{ "what": "runs", "workspace": "sales", "target": "2026-09-28 14:00", "note": "…" }
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Nothing is restored automatically. The team restores the archive somewhere
|
|
163
|
+
else first and copies back only your account's paths, so a restore for you
|
|
164
|
+
does not roll back anyone else.
|
|
165
|
+
|
|
166
|
+
## On AWS
|
|
167
|
+
|
|
168
|
+
The AWS production design (`foldrun-infra/prod`) backs up differently —
|
|
169
|
+
database point-in-time recovery, nightly EFS snapshots kept 35 days, both
|
|
170
|
+
copied to a second region. It has not been built yet; this page will change
|
|
171
|
+
when it is.
|
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
# Budgets and billing
|
|
2
|
+
|
|
3
|
+
Money on this platform is a cap at three levels and a meter underneath. The
|
|
4
|
+
caps are yours to set and the thing to set first: an agent with a tool loop
|
|
5
|
+
and no ceiling can spend in an hour what a month was meant to.
|
|
6
|
+
|
|
7
|
+
## Four caps
|
|
8
|
+
|
|
9
|
+
| level | where | what it does when reached |
|
|
10
|
+
|---|---|---|
|
|
11
|
+
| **an agent, per run** | `budget: 2` in the agent's `agent.md` | the most that agent may spend in one run, across every step it takes in it — its step stops itself mid-turn at what is left, and a later step of its own in the same run is refused before it starts. Its steps running at once share what is left, as a group shares the run's: the copies of a fan-out split the cap, they are not each handed the whole of it |
|
|
12
|
+
| **a run** | `budget: 6` in a flow's frontmatter | a hard cap: between groups the next group does not start; within a group each step gets an equal share of the remainder and stops itself mid-turn when it is spent — the run cannot end over the cap |
|
|
13
|
+
| **a workspace, over time** | `budget: 60` in the workspace's `AGENTS.md`, or Settings | new runs refuse until the window turns |
|
|
14
|
+
| **the account, over time** | `budget: 500` in the account's `AGENTS.md`, or Settings | the same, for everything |
|
|
15
|
+
|
|
16
|
+
**Unset means no limit.** No file, no key, `budget: unlimited` — all the
|
|
17
|
+
same: the platform invents no cap on its own. The two caps over time take
|
|
18
|
+
a period: `budget: 60` alone is per month, as it always was; `budget: 60/day`
|
|
19
|
+
and `budget: 60/week` say so. The window is measured in the account's own
|
|
20
|
+
calendar (`timezone:` in `AGENTS.md`, nearest wins), so a daily cap resets
|
|
21
|
+
at midnight where the customer is, not at midnight UTC. The two per-run
|
|
22
|
+
caps take no period — an agent has no calendar, and writing one there is a
|
|
23
|
+
mistake the editor names rather than a cap it quietly keeps.
|
|
24
|
+
|
|
25
|
+
When an agent's cap and the flow's both apply to one step, the tighter one
|
|
26
|
+
wins, and the step's error says which line set it: `budget: in the flow
|
|
27
|
+
file` or `budget: on the writer agent`.
|
|
28
|
+
| **runs at once** | `concurrency: 3` in the account's `AGENTS.md`, or Settings → Defaults | the fourth run waits its turn in the queue — never refused, only later. Empty means the install's plan number (`FOLDRUN_PLAN_CONCURRENCY`, which applies only where billing is on), or no cap |
|
|
29
|
+
| **the queue lane** | `priority: high` in the account's `AGENTS.md` (`high` \| `normal` \| `low`) | its runs are claimed before other accounts' normal ones when slots are short; a flow's own `priority:` adds to it. The cap above still holds — a lane jumps the queue, it does not hold the fleet |
|
|
30
|
+
|
|
31
|
+
A cap is a literal, never an expression, so it is readable off the file. The
|
|
32
|
+
run cap is hard: a step that reaches its share of the remainder is stopped
|
|
33
|
+
mid-turn, so a runaway costs at most the cap, and the step's own error says
|
|
34
|
+
how much of its ceiling it reached.
|
|
35
|
+
|
|
36
|
+
**What counts as spent is the whole bill**, tokens and the sandbox seconds
|
|
37
|
+
behind them. This matters more than it sounds: a step that calls no model —
|
|
38
|
+
a script looping on a page that never loads, a browser waiting for a selector
|
|
39
|
+
that never appears — spends nothing in tokens and would once have been capped
|
|
40
|
+
by nothing at all, while the sandbox billed by the second. The platform sets
|
|
41
|
+
no clock of its own by design, so the cap is the only backstop there is, and
|
|
42
|
+
it counts everything.
|
|
43
|
+
|
|
44
|
+
The account's cap and a workspace's are checked separately and both apply:
|
|
45
|
+
the workspace one stops that desk, the account one stops everything.
|
|
46
|
+
|
|
47
|
+
**A cap over time warns before it refuses.** At 80% and again at 95% of a
|
|
48
|
+
workspace's `budget:`, the workspace's notification destination is told what
|
|
49
|
+
has been spent and what happens at the cap. Each mark is sent once per
|
|
50
|
+
window — today, this week, this month, whichever the cap is over — and
|
|
51
|
+
resets when the cap does. Without it the way a customer learns about the
|
|
52
|
+
cap is a desk failing to start on a Tuesday.
|
|
53
|
+
|
|
54
|
+
Size the run cap from the run records, at two or three times the median — real
|
|
55
|
+
work never trips it, a runaway is stopped where it went wrong, and the
|
|
56
|
+
failure notification says why. The dashboard's Usage page shows spend by
|
|
57
|
+
workspace and by meter.
|
|
58
|
+
|
|
59
|
+
## Plans and credits
|
|
60
|
+
|
|
61
|
+
Usage is sold in **credits: one credit is one cent** of what a step consumes
|
|
62
|
+
at the platform's rates. The ledger stays in dollars underneath — every
|
|
63
|
+
statement, budget and refund reconciles to one column — and a credit is how
|
|
64
|
+
the money is shown and bought.
|
|
65
|
+
|
|
66
|
+
| plan | a month | credits a month | an extra credit | runs at once | workspaces | queue lane |
|
|
67
|
+
|---|---|---|---|---|---|---|
|
|
68
|
+
| **Starter** | $29 | 2,900 | 1.00¢ | 1 | 3 | standard |
|
|
69
|
+
| **Creator** | $79 | 8,800 | 0.90¢ | 2 | 10 | standard |
|
|
70
|
+
| **Pro** | $249 | 29,000 | 0.86¢ | 4 | unlimited | fast |
|
|
71
|
+
| **Scale** | $799 | 100,000 | 0.80¢ | 8 | unlimited | fast |
|
|
72
|
+
|
|
73
|
+
**How long a credit lasts.** A plan's credits land when its invoice is paid,
|
|
74
|
+
and what is unused **rolls over** into the next cycle up to **twice** the
|
|
75
|
+
plan's monthly quota — so a balance of plan credits reaches at most three
|
|
76
|
+
times it: this cycle's, plus at most two carried. Anything above the cap
|
|
77
|
+
expires, and the statement line says how much rolled and how much did not.
|
|
78
|
+
The carry is lost on a **cancellation** or a **downgrade**, because the
|
|
79
|
+
rollover is a courtesy of the subscription continuing at its level; moving
|
|
80
|
+
up keeps it. Credits **bought** on top (Balance → Add credit) are separate:
|
|
81
|
+
rollover does not touch them and they last **twelve months** from the day
|
|
82
|
+
they were bought (`FOLDRUN_CREDIT_TTL_MONTHS`; `0` turns expiry off).
|
|
83
|
+
Whoever changes that number should know that prepaid credit sold to
|
|
84
|
+
Australian consumers can attract gift-card rules, which are three years.
|
|
85
|
+
|
|
86
|
+
Which credit a run spends is the order that favours the customer: the
|
|
87
|
+
plan's granted credits first, then bought credit oldest-first — so the
|
|
88
|
+
credit closest to expiring is always the credit being used. Spend draws on the cycle's credits first, then
|
|
89
|
+
on the balance. A new account starts with **300 trial credits**, no card
|
|
90
|
+
asked. Without a plan, credit costs 1¢ each, one run at a time, standard
|
|
91
|
+
lane — a plan is cheaper from the first month you would have spent its fee
|
|
92
|
+
anyway. Changing plan happens at once, prorated by Stripe, and the new
|
|
93
|
+
bundle lands with the invoice; ending one ends it with the cycle.
|
|
94
|
+
|
|
95
|
+
**Seats are unlimited** on every plan: agents work, people supervise, and a
|
|
96
|
+
per-seat fee would tax the approver the safety story depends on.
|
|
97
|
+
|
|
98
|
+
**Bring your own model key** and tokens cost no credits at all — the model
|
|
99
|
+
is billed to your own provider account — so a plan then buys sandbox time
|
|
100
|
+
and the platform, which is most of what a BYOK customer's credits go on.
|
|
101
|
+
|
|
102
|
+
**Going into minus.** Ordinarily an empty balance starts no run. The
|
|
103
|
+
platform can allow an account to run below zero — to a floor, or with none —
|
|
104
|
+
for a customer on invoice terms. It shows on that account's Balance page
|
|
105
|
+
and on the audit; it is a term, never a surprise.
|
|
106
|
+
|
|
107
|
+
**Charges an account does not pay.** The platform can waive any of the three
|
|
108
|
+
meters for one account: model tokens, sandbox time, network. It is set per
|
|
109
|
+
account from the admin console, never as an install-wide rate, because a
|
|
110
|
+
rate turned off globally would quietly stop charging every customer. The
|
|
111
|
+
meters still run and the Usage page still shows everything measured; what
|
|
112
|
+
changes is the bill. Every run still writes a ledger line — at zero when
|
|
113
|
+
everything is waived — naming which legs were waived, so a statement that
|
|
114
|
+
totals nothing has a reason printed on it rather than looking like a fault.
|
|
115
|
+
It is for the accounts the platform runs itself, and for pilots and
|
|
116
|
+
partners whose compute is absorbed by arrangement.
|
|
117
|
+
|
|
118
|
+
## What a credit buys
|
|
119
|
+
|
|
120
|
+
Three meters, at the platform's rates. Every charge is recorded in **two
|
|
121
|
+
halves** — what the models cost and what the platform under them cost — and
|
|
122
|
+
the Usage page, the statement and its CSV all show them apart. They are
|
|
123
|
+
different questions: a customer on their own model key pays no model half
|
|
124
|
+
at all, and one whose bill moved needs to know which half moved. The halves
|
|
125
|
+
always sum to the charge; where a minimum-run fee applies it lands on the
|
|
126
|
+
platform half, never on a model number a customer can check against their
|
|
127
|
+
provider's own invoice.
|
|
128
|
+
|
|
129
|
+
| meter | what it counts |
|
|
130
|
+
|---|---|
|
|
131
|
+
| **tokens** | model usage at cost, times a margin. **Bring your own key and the margin does not apply** — a BYOK step is billed nothing for tokens |
|
|
132
|
+
| **compute** | sandbox seconds: what a step reserved (cores and memory, while it ran) and what it actually burned |
|
|
133
|
+
| **storage and network** | GB-months in the file store, accrued daily; GB the sandbox moved, both directions |
|
|
134
|
+
|
|
135
|
+
There is deliberately **no per-step fee**. Steps are sandbox-seconds, so a
|
|
136
|
+
step fee would charge compute twice and punish a well-factored flow for
|
|
137
|
+
having more, smaller steps. The Usage page shows every table in credits at
|
|
138
|
+
today's rates beside the meters, so a customer can read what a run will
|
|
139
|
+
cost from what the last one consumed.
|
|
140
|
+
|
|
141
|
+
## What each desk costs
|
|
142
|
+
|
|
143
|
+
The Usage page's per-workspace tables are rolled up from the run records,
|
|
144
|
+
which is right for step counts and pod seconds and wrong for money over
|
|
145
|
+
time: run records are pruned, so a desk's cost history disappears exactly
|
|
146
|
+
when it becomes interesting, and a record holds what a run consumed rather
|
|
147
|
+
than what it was charged.
|
|
148
|
+
|
|
149
|
+
So every charge also carries the flow it was, and that charge split across
|
|
150
|
+
the agents that did the work — in proportion to what each step actually
|
|
151
|
+
consumed, normalised so the shares add up to the charge. The fixed parts of
|
|
152
|
+
a bill are spread the same way, which is the ordinary treatment and the only
|
|
153
|
+
one where a column of shares equals the invoice.
|
|
154
|
+
|
|
155
|
+
The Usage page shows both, by week, with the change between the last two
|
|
156
|
+
**complete** periods so a partial week never reads as a collapse. Charges
|
|
157
|
+
written before attribution existed appear as a remainder rather than being
|
|
158
|
+
quietly dropped: a breakdown that does not add up is worse than one that
|
|
159
|
+
admits what it cannot place. `GET /api/usage/history?days=` is the same
|
|
160
|
+
thing as JSON.
|
|
161
|
+
|
|
162
|
+
## The Billing section
|
|
163
|
+
|
|
164
|
+
Money is one subject, and the dashboard treats it as one section under the
|
|
165
|
+
avatar, with eight tabs:
|
|
166
|
+
|
|
167
|
+
| tab | what it is |
|
|
168
|
+
|---|---|
|
|
169
|
+
| **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 |
|
|
170
|
+
| **Plan** | the plan you are on, this cycle's credits and when they reset, and the plans you could move to |
|
|
171
|
+
| **Usage** | what was consumed, from the run records, and what each flow and agent cost by week from the ledger |
|
|
172
|
+
| **Statement** | the ledger as a statement: grouped by day, human labels, month-to-date, filters, and a CSV for the month |
|
|
173
|
+
| **Invoices** | a tax invoice for every card payment — the plan's fee and each top-up — hosted by Stripe, and a statement for every month |
|
|
174
|
+
| **Payment** | the saved card and the auto top-up rule, with defaults derived from the burn |
|
|
175
|
+
| **Details** | who the invoices are made out to: legal name, ABN, address and the invoice email |
|
|
176
|
+
| **Spend limits** | the account's cap and each workspace's — over a day, a week or a month — as bars with the warning marks drawn where they fire, editable in place |
|
|
177
|
+
|
|
178
|
+
The balance also sits in the header of every page, tinted by runway, and a
|
|
179
|
+
banner appears across the dashboard when the balance is under a week's cover
|
|
180
|
+
or empty, carrying the one button that fixes it. A refused run says why at
|
|
181
|
+
the button that was clicked, with a link to add credit when money was the
|
|
182
|
+
reason. The old `/dashboard/wallet` address redirects here.
|
|
183
|
+
|
|
184
|
+
Adding credit is the owner's to do. Anyone else sees why the button is not
|
|
185
|
+
there, and who to ask, rather than a button that fails.
|
|
186
|
+
|
|
187
|
+
**Tax invoices.** Every payment is invoiced to the account's one Stripe
|
|
188
|
+
customer, made on the first payment and carrying what Details holds — the
|
|
189
|
+
legal name, the invoice email, the address, and the ABN as the customer's
|
|
190
|
+
tax id (checked against the ABN rule before it is kept). A top-up's
|
|
191
|
+
checkout asks Stripe for a numbered invoice, not just a receipt, so a
|
|
192
|
+
business can claim the GST on credits as well as on the plan. With
|
|
193
|
+
`STRIPE_TAX=1` (Stripe Tax enabled on the Stripe account), checkout works
|
|
194
|
+
the tax out from the address, collects the address and a tax id if they
|
|
195
|
+
are missing, and writes them back to the customer. Saving Details updates
|
|
196
|
+
the customer at Stripe at once; without Stripe, or when Stripe refuses,
|
|
197
|
+
they are kept and sent with the next save or payment.
|
|
198
|
+
|
|
199
|
+
**Manage in Stripe.** The Payment, Plan, Invoices and Details tabs carry a
|
|
200
|
+
button into Stripe's Billing Portal — the card, every invoice as a PDF, the
|
|
201
|
+
plan — for the owner. Its look and what it allows (cancelling, switching
|
|
202
|
+
plan) are set once at Stripe, under Settings → Billing → Customer portal.
|
|
203
|
+
|
|
204
|
+
**When a plan payment fails.** Stripe retries the card on its own
|
|
205
|
+
schedule. On the first failure of an invoice the owner is emailed once —
|
|
206
|
+
the amount, Stripe's page to pay that invoice, and the way to update the
|
|
207
|
+
card — and a grace period starts (`FOLDRUN_DUNNING_GRACE_DAYS`, default 7).
|
|
208
|
+
Runs go on meanwhile, and an amber banner across the dashboard says until
|
|
209
|
+
when. If the invoice is still unpaid when the grace ends, the hourly sweep
|
|
210
|
+
suspends the account for new runs with the reason **payment overdue**, and
|
|
211
|
+
one more email says so; the banner turns red. Runs in flight finish, people
|
|
212
|
+
can still sign in, nothing is deleted. The moment `invoice.paid` arrives
|
|
213
|
+
for it — or `invoice.voided`, `invoice.marked_uncollectible`, or Stripe
|
|
214
|
+
cancelling the subscription — the suspension is lifted, and a payment
|
|
215
|
+
gets a thank-you email. Every step is once per invoice, however often
|
|
216
|
+
Stripe retries or redelivers; a second invoice failing during the grace
|
|
217
|
+
does not restart it. Dunning lifts only a suspension it placed: an account
|
|
218
|
+
the super admin suspended for another reason stays suspended. The super
|
|
219
|
+
admin sees where it stands on the customer's overview.
|
|
220
|
+
|
|
221
|
+
**Your data, and leaving.** Settings → Your data gives the owner one JSON
|
|
222
|
+
file with the account's workspaces, members, an index of every run and the
|
|
223
|
+
whole ledger (`GET /api/account/export`). The same place holds **Close
|
|
224
|
+
account**: type the account's name and it is suspended at once — no new
|
|
225
|
+
runs, schedules stop firing — while the platform team settles the last
|
|
226
|
+
invoice and any refund. Nothing is deleted by the request itself; people can
|
|
227
|
+
still sign in and take the copy.
|
|
228
|
+
|
|
229
|
+
## The admin console's money levers
|
|
230
|
+
|
|
231
|
+
Each customer (account) in the super admin console has a Billing tab with
|
|
232
|
+
every lever that changes what the customer pays, each behind a confirmation
|
|
233
|
+
that names the amount and the account: **credit or debit** with a reason on
|
|
234
|
+
the ledger (the console sends an idempotency key, so a double click is one
|
|
235
|
+
line), **extend the trial** (more trial credits as a grant, with whatever
|
|
236
|
+
trial credit is left carried into it; not for an account on a plan), the
|
|
237
|
+
**plan** (grant a cycle by hand, end it, and the lane and limits that ride
|
|
238
|
+
with it), **going into minus**, and **charges this account does not pay**.
|
|
239
|
+
The overview beside it holds the contact on record (name, email, phone,
|
|
240
|
+
company, ABN), notes, suspension, and the customer's Stripe customer and
|
|
241
|
+
subscription with links into Stripe's own dashboard — test or live, read
|
|
242
|
+
from the install's key.
|
|
243
|
+
|
|
244
|
+
Revenue has a CSV per month for the accountant (`GET /api/admin/export`):
|
|
245
|
+
one row per customer and a total, charges by leg, fees, top-ups, grants,
|
|
246
|
+
expiries, refunds, disputes, adjustments and net, with Stripe's own figures
|
|
247
|
+
for the month beside them when Stripe is connected.
|
|
248
|
+
|
|
249
|
+
## The wallet
|
|
250
|
+
|
|
251
|
+
Usage is settled from a credit balance. Add credit by card, once or with
|
|
252
|
+
auto top-up; set a low-balance email so the empty wallet is never a
|
|
253
|
+
surprise. With billing on, an empty balance refuses *new* runs — runs in
|
|
254
|
+
flight finish. Every run is charged exactly once; the ledger is a database
|
|
255
|
+
table with that as a constraint, not a promise.
|
|
256
|
+
|
|
257
|
+
## How the money is kept straight
|
|
258
|
+
|
|
259
|
+
The bookkeeping rules behind the numbers, for whoever has to answer "why is
|
|
260
|
+
my balance this".
|
|
261
|
+
|
|
262
|
+
**One grant per plan cycle.** A cycle is the subscription, the plan and the
|
|
263
|
+
end of the period it pays for, and that is the grant's key. Stripe tells us
|
|
264
|
+
about a paid cycle several ways — the checkout returning, the checkout
|
|
265
|
+
webhook, `invoice.paid`, `customer.subscription.updated` — and in no fixed
|
|
266
|
+
order; whichever arrives first grants and the rest find it done. A plan
|
|
267
|
+
change inside a period is a new cycle on the new plan (the prorated invoice
|
|
268
|
+
and the updated event are the same cycle, one grant); a renewal is a new
|
|
269
|
+
period. A change inside a period grants the new plan's bundle **pro-rated
|
|
270
|
+
by the share of the period left**, the way Stripe prorates the charge; a
|
|
271
|
+
renewal grants it whole. Going back to a plan already granted this period
|
|
272
|
+
switches the plan and its gates back but grants nothing again. Downgrades
|
|
273
|
+
still lose the carry.
|
|
274
|
+
|
|
275
|
+
**The low-balance guard runs every hour**, and right after a run whose
|
|
276
|
+
charge takes the balance across the warning line. Per crossing of the line
|
|
277
|
+
it makes at most **one** auto top-up attempt and sends at most **one**
|
|
278
|
+
email; a declined card is not retried every hour — the email goes instead —
|
|
279
|
+
and both re-arm once the balance is back above the line.
|
|
280
|
+
|
|
281
|
+
**Tax.** A top-up credits the price before tax: with Stripe Tax on, the
|
|
282
|
+
Checkout price is tax-inclusive, so a $50 top-up costs $50 and credits $50.
|
|
283
|
+
An auto top-up is a bare charge with no Stripe Tax and no tax invoice.
|
|
284
|
+
|
|
285
|
+
**Refunds.** A refunded top-up takes off the credits it added — the charge's
|
|
286
|
+
own amount caps it, scaled to the pre-tax share when the charge carried tax — and the balance is **not** floored at zero: credit
|
|
287
|
+
refunded after it was spent leaves the account in the red, and admission
|
|
288
|
+
refuses the next run until it is topped up. A refunded plan fee does not
|
|
289
|
+
touch the wallet: the credits it bought were a grant, and they end with the
|
|
290
|
+
cycle.
|
|
291
|
+
|
|
292
|
+
**Chargebacks.** The disputed amount comes off when the dispute opens (the
|
|
293
|
+
card network already has the money). The account is found through the
|
|
294
|
+
disputed charge — its metadata, its payment's, then the saved customer. Won
|
|
295
|
+
(or an inquiry closed without a chargeback): the same amount goes back on,
|
|
296
|
+
once. Lost: it stays off. As with refunds, a dispute on a plan fee does not
|
|
297
|
+
touch the wallet; it is logged and left to the super admin.
|
|
298
|
+
|
|
299
|
+
**Every line says why it exists.** Each ledger line carries a reason —
|
|
300
|
+
usage, pod, fee, checkout, auto-topup, manual, operator, refund, dispute,
|
|
301
|
+
dispute-won, plan, trial, grant-expired, credits-expired — and lines written
|
|
302
|
+
before the field have theirs derived from their key and note. Statements
|
|
303
|
+
label lines by it. **Burn**, the 30-day spend and month-to-date count usage
|
|
304
|
+
and the daily fees only — not expiries, refunds, chargebacks or an
|
|
305
|
+
operator's debit. The admin console's **charged** figure is usage (runs and
|
|
306
|
+
account pod sessions) in the customer list and the money report alike;
|
|
307
|
+
**paid in** is top-ups paid plus plan fees from the invoices Stripe said
|
|
308
|
+
were paid; **held in wallets** is credit bought and unspent — granted
|
|
309
|
+
credit is shown apart, because nobody paid for it by the dollar.
|
|
310
|
+
|
|
311
|
+
**Hand-placed plans renew.** A plan the super admin grants runs a month and
|
|
312
|
+
is renewed by the hourly sweep at its period's end, with the same grant and
|
|
313
|
+
rollover rules. Granting a plan to an account Stripe is billing is refused
|
|
314
|
+
(end it first, or change it at Stripe); ending a Stripe-billed plan cancels
|
|
315
|
+
the subscription at Stripe first, at once, and changes nothing if Stripe
|
|
316
|
+
refuses.
|
|
317
|
+
|
|
318
|
+
**An operator's credit or debit is written once** per idempotency key the
|
|
319
|
+
console sends, and audited once.
|
|
320
|
+
|
|
321
|
+
**Stripe.** Every call pins its API version (`2025-03-31.basil`;
|
|
322
|
+
`STRIPE_API_VERSION` overrides) — create the webhook endpoint on the same
|
|
323
|
+
version — and charges in `FOLDRUN_CURRENCY` (default `usd`), which is also
|
|
324
|
+
the unit of every balance. Set the currency before any money is on the
|
|
325
|
+
books; Stripe will not mix currencies on one customer. The webhook endpoint
|
|
326
|
+
is `<public url>/api/billing/stripe`, sending exactly:
|
|
327
|
+
|
|
328
|
+
| event | what it does |
|
|
329
|
+
|---|---|
|
|
330
|
+
| `checkout.session.completed` | a top-up credited, or a plan's first cycle granted |
|
|
331
|
+
| `invoice.paid` | a plan cycle granted: signup, renewal, or a change; an overdue invoice settled |
|
|
332
|
+
| `invoice.payment_failed` | the plan goes past due; dunning starts |
|
|
333
|
+
| `invoice.voided` | an overdue invoice cancelled; dunning ends |
|
|
334
|
+
| `invoice.marked_uncollectible` | an overdue invoice written off; dunning ends |
|
|
335
|
+
| `customer.subscription.updated` | a plan swap, or a status change |
|
|
336
|
+
| `customer.subscription.deleted` | the plan it paid for ends; its dunning ends |
|
|
337
|
+
| `payment_intent.succeeded` | an auto top-up credited |
|
|
338
|
+
| `payment_intent.payment_failed` | an auto top-up refused; the warning goes |
|
|
339
|
+
| `charge.refunded` | a refunded top-up's credits come off |
|
|
340
|
+
| `charge.dispute.created` | a chargeback's amount comes off |
|
|
341
|
+
| `charge.dispute.closed` | won: it goes back on; lost: it stays off |
|
|
342
|
+
|
|
343
|
+
Every delivery is logged — the event, the account, what was done or the
|
|
344
|
+
error, and a hash of the payload (never the payload, which carries names
|
|
345
|
+
and addresses). The super admin lists the log at `GET
|
|
346
|
+
/api/admin/stripe-events` and replays one event with `POST
|
|
347
|
+
/api/admin/stripe-events/<id>/replay`, which re-fetches it from Stripe and
|
|
348
|
+
runs it through the same handler; every write it makes is keyed once, so a
|
|
349
|
+
replay of an event that was handled changes nothing.
|
|
350
|
+
|
|
351
|
+
## Plan gates
|
|
352
|
+
|
|
353
|
+
Two more limits come with the plan rather than the wallet: how many
|
|
354
|
+
workspaces an account may create (beyond it, creation refuses with `402`),
|
|
355
|
+
and how many runs may execute in parallel (beyond it, runs **wait** — they
|
|
356
|
+
are never refused, because a queued run is work that will happen and a
|
|
357
|
+
refused one is work that will not).
|
|
358
|
+
|
|
359
|
+
## On your machine
|
|
360
|
+
|
|
361
|
+
None of this meters. `budget:` still caps a run and every step's cost is on
|
|
362
|
+
its record — the model bill is yours, paid to your provider directly.
|