The proof
Building a calculator with Groundwork.
One real build across 20 sessions, measured call by call. Every number here comes from OpenCode’s own token accounting and links to the raw row it came from, including the ones that don’t flatter it.
DeepSeek V4.1 Flash (max thinking) in OpenCode 2.0.18 · measured 2026-09-26
In short
What it all means.
- 01Each step costs the same, however big the project gets.Every step of the build started with about 9k tokens of context, from the first piece of work to the last.see the numbers ↓
- 02Picking up where you left off is cheap.A new session knew where the project stood after reading 6.8× fewer tokens than one without Groundwork, and got it right.see the numbers ↓
- 03The honest catch.On an app this small, one plain chat would likely be cheaper. Groundwork pays off as a project grows past what one chat can hold.see the numbers ↓
Don’t take our word for it: every number links to its raw data, and every session is readable in full →
What was built
A working calculator, built and checked piece by piece.
Why it matters
This is a finished, deployed app, not a demo. The numbers below are what it actually took.
Deploying to GitHub Pages ($0.020) and exporting the transcripts ($0.061) happened in the same chats and are counted separately.measurements.md
Every card starts the same size
Each step started from the same small context.
Why it matters
Card 60 should cost about what card 6 did. A growing project doesn’t make each step slower or more expensive.
Show as a table
| Where | Context at start (tokens) |
|---|---|
| runner, message 1: /gw | 9,157 |
| runner, message 2: building a calculator | 15,490 |
| runner, message 3: /gw | 22,094 |
| runner, message 4: init, then plan | 55,407 |
| runner, message 5: commit; what’s next? | 103,158 |
| runner, message 6: /gw-approve | 122,721 |
| runner, message 7: cards left? | 130,195 |
| runner, message 8: finish phase 1 | 130,994 |
| runner, message 9: /gw-approve | 171,618 |
| runner, message 10: /gw-retro | 178,217 |
| runner, message 11: transcript export | 189,991 |
| runner, message 12: transcript export | 248,158 |
| runner, message 13: transcript export | 252,996 |
| runner, end of the chat | 280,280 |
| card 1.1 tester | 9,257 |
| card 1.1 implementer | 9,264 |
| card 1.1 reviewer | 8,851 |
| card 1.2 tester | 9,222 |
| card 1.2 implementer | 9,201 |
| card 1.2 reviewer | 8,789 |
| card 1.3 tester | 9,211 |
| card 1.3 implementer | 9,232 |
| card 1.3 reviewer | 8,826 |
| card 1.4 tester | 9,248 |
| card 1.4 implementer | 9,317 |
| card 1.4 reviewer | 8,851 |
| card 2.1 tester | 9,330 |
| card 2.1 implementer | 9,652 |
| card 2.1 reviewer | 9,001 |
| card 2.2 tester | 9,469 |
| card 2.2 implementer | 9,497 |
| card 2.2 reviewer | 8,982 |
Cost per card
Cost followed the work in the card, not the size of the project.
Why it matters
You can estimate a project’s cost by counting its cards. Screens and interface work cost more than logic.
- 1.1 Number entry$0.059
- 1.2 Operations and equals$0.045
- 1.3 Clear and error state$0.040
- 1.4 Result formatting$0.060
- 2.1 Page and keypad$0.128
- 2.2 Wire it up$0.084
Show as a table
| Card | Cost | Fresh tokens | Largest role context |
|---|---|---|---|
| 1.1 Number entry | $0.0593 | 140,272 | 74,174 |
| 1.2 Operations and equals | $0.0449 | 121,033 | 38,892 |
| 1.3 Clear and error state | $0.0395 | 108,356 | 41,030 |
| 1.4 Result formatting | $0.0599 | 151,850 | 56,751 |
| 2.1 Page and keypad | $0.1279 | 287,821 | 134,817 |
| 2.2 Wire it up | $0.0843 | 224,168 | 85,044 |
By role
- Tester · 6 sessions$0.170
- Reviewer · 6 sessions$0.149
- Implementer · 6 sessions$0.097
- Runner · 2 sessions$0.170
The runner row is the main chats’ building turns only. by role
Picking up where you left off
A new session knows where things stand.
Why it matters
Closing the laptop, switching tools or coming back next week doesn’t mean re-explaining the project, or paying to re-read it.
- tokens read, all calls
- 956,066
- model calls
- 27
- tool calls
- 50
- cost
- $0.0229
Rebuilt it from git history, and read folders outside the project.
- tokens read, all calls
- 140,682
- model calls
- 9
- tool calls
- 11
- cost
- $0.0086
Right: phase 2 built, waiting for the phase review.
The honest part
On a calculator, a plain chat is cheaper.
Why it matters
For a quick script, one chat may be all you need. For anything that takes days, the flat cost per card wins.
cards built → · tokens re-read, total ↑
- One chat stops being cheaper
- around card 9
- At card 30, one chat re-reads
- 3.4× as much
- One chat’s context per call by then
- 759kpast a 200k window
re-read ≈ m·N·S + m·h·N²/2 with S = 9k (OpenCode’s measured start) and your m and h; the plain-chat run was never measured, so its defaults are assumptions. Neither line includes the runner, which Groundwork now restarts each phase.The full derivation: What it costs, and when it pays off.
What didn’t stay flat
The main chat grew. So we changed how it works.
Why it matters
The one part that grew was found by measuring, and fixed. Starting a new chat for each phase keeps it small.
Measured
280k
tokens of context per call, by the end of phase 1’s main chat. The two main chats account for 61% of all tokens re-read (21.6M of 35.2M), and about half of that history was the runner’s own retained reasoning. The roles’ reports back were about a thousand tokens each. summary
Changed
- Short hand-offs: the card’s path, the role file and checked facts. No retelling what was agreed, no statuses to set.
- No reruns between roles: the reviewer reruns everything once.
- A fresh session each phase, suggested when a phase closes.
Why it works
9.2k
was where phase 2’s new main chat started, instead of about 190k had phase 1’s chat continued. Over its 61 calls, that’s roughly 11M tokens not re-read. Phase 2 did this by accident; now Groundwork suggests it at every phase close. summary
The lessons ledger
It caught problems in Groundwork itself.
Why it matters
The ledger catches problems in the process, not just in the code, and Groundwork got better because of it.
L-001 · Runner prompts hand over role files, not status rules
origin The runner’s prompt told the implementer to set the card to done; the implementer role says review. Both cards had to be reset.
fixed The runner no longer passes statuses at all. The role file says.
L-002 · Calls are recorded on the card by whoever makes them
origin Implementers reported their judgment calls but left the call: lines to the runner, who backfilled them.
fixed Tester and implementer write their own History and call: lines; a report isn’t enough.
The reviewer
It says what it couldn’t check.
Why it matters
You know exactly what wasn’t verified, so you know what to check yourself.
“Live browser check — attempted, not possible … verified in Node only, not verified live in a browser”
“Minor, non-blocking … a multi-character key like ‘1abc’ would be treated as a digit … Noted in my report, not sent back.”
Overhead
Groundwork adds 681 tokens to a session start.
Why it matters
Installing it costs about the length of a short paragraph at the start of each session.
| OpenCode alone, empty repo | 8,563 |
| The calculator, Groundwork removed | 8,564 |
| The calculator, with Groundwork | 9,245 |
| Groundwork’s tester subagent | 9,069 |
npx groundwork-ai doctor shows this for your own project.startup
Measure your own
Don’t take our numbers. Take yours.
Why it matters
You can check all of this on your own project instead of trusting ours.
# export each session, then summarize them
opencode session export <session-id> > main.json
node checks/session-costs.mjs main.json tester-1.1.json ...Receipts
Every session, as it ran.
Why it matters
Everything on this page can be checked against the actual conversations, word for word.
Two main chats and 18 role sessions, exported from OpenCode and left unedited. Start with ten key moments, or open any session in full.
Read the receipts →