
Lab / Tool
The lab journal
To document internal projects and R&D work, I built a journal to preserve the decisions behind work made with AI agents. Its first article showed what the record could capture, and what a writer still has to decide.
- Editor
- Miles Roxas
- Published
- 27 September 2026
- Built with
- Claude Code, TypeSafe Jev, Payload CMS+3
- Read
- 8 MIN
Keep the decisions while they are still clear
The idea started after a session reviewing the studio's CMS tooling. I asked for a document that kept the findings alongside my prompt, the model and its token usage. That raised a more useful question: could the same record follow every feature across days and sessions?
I wanted a Lab entry to explain why the work took its particular shape, including the options left behind and the problems that changed the plan. Reconstructing those decisions at the end would lose the context that made them worth writing about.
Record the reasons where the work happens
Claude proposed a skill for the working agent, hooks for the mechanical record, and a separate writer at the end. I accepted that split. The agent making a change has the context to log its reasons; the hooks can capture prompts and count tokens without asking a model to interpret them.
A fresh writer then gets the journal as its source. That separation matters: the finished article has to make sense to someone who was not in the session. Raw prompts stay redacted on my machine because the repository is public.
Description and steps
A flow from the build to the draft. The working session logs each decision to journal.md. Hooks, with no model, capture every prompt and every session's token counts. At wrap-up Jev's digest reads the journal and the prompts and hands a brief to the writer agent, which also reads the token counts and writes the Lab Project draft, and the draft goes through the draft check. The takeaway is that the only model watching the build is the session doing the work. Everything else is captured by code or read once at the end.
- Working session
- journal.md
- Hooks
- Jev digest
- Writer agent
- Draft (start or end)
- Draft check (start or end)
Connections
- Working session to journal.md: logs
- journal.md to Jev digest
- Hooks to Jev digest: prompts
- Jev digest to Writer agent: brief
- Hooks to Writer agent: tokens (optional or async)
- Writer agent to Draft
- Draft to Draft check
First, make the record trustworthy
The first run recorded the wrong conversation
Before a writer could trust that record, the capture had to belong to the right work. The first live run failed there. The start command chose the newest transcript file, but I had another session open in the same checkout. Its prompts and token counts went into the journal.
The session id in the output exposed the mistake. Claude changed the lookup to use the id supplied by Claude Code, leaving the newest file as a last resort. The unit tests had passed; running against the real machine revealed the assumption they had missed.
A session is not a feature
Finding the right conversation solved only part of the problem. Claude had put the CMS review and the building of the journal into one record, with the whole session's tokens. I caught the mix after the push.
The fix was a time window: close one feature's share of the session at the prompt where the subject changes, then open the next. That keeps the evidence and usage attached to the feature they describe. Someone still has to recognize the change of subject.
Description and steps
A flow for the first session of 21 September, which held two features. A window at 16:33:08 UTC, the prompt where the conversation turned, sends everything before it to the MCP review journal and everything after it to the lab journal. The takeaway is that a session no longer has to belong to one feature, and its tokens are counted once across the two journals.
- Session (start or end)
- Before split? (decision)
- CMS review (start or end)
- Journal build (start or end)
Connections
- Session to Before split?
- Before split? to CMS review: yes
- Before split? to Journal build: no
Help the writer choose and check
Give the writer a way into the record
A trustworthy record still leaves the writer with a lot to read. Claude added a digest using Jev, TypeSafe's model. It suggests where entries belong, which prompts might be worth quoting and where a figure could help. It also flags agent messages that might contain an unlogged decision.
The digest runs at wrap-up so a model request cannot interrupt prompt capture. Jev can suggest a diagram, but the writer still has to check whether drawing it would explain the decision. The first figure plan made that limit visible.
A suggestion that selected everything
Asked whether an entry described something a diagram could show, Jev selected all 17 entries. Almost anything can be drawn, so the answer did little to help the writer choose.
Claude changed the question to ask whether explaining a mechanism was the entry's main purpose. It also described the near miss: an entry can mention components while really being about a decision. That brought the selection down to five. The useful distinction was what a figure would help the reader understand.
Wording
Entries put forward, of 17
Description and data table
A horizontal bar chart of three wordings of one question over the same 17 journal entries. Asked whether a diagram could show the entry, Jev put forward all 17. As a score from 0 to 3, 15 scored 2.9 or higher. Asked whether the entry is mainly an explanation of a mechanism, 5 passed. The takeaway is that the wording, not the model, decided whether the plan was useful.
| Wording | Entries |
|---|---|
| Could it be drawn? | 17 |
| Score of 2.9+ | 15 |
| A mechanism? | 5 |
Check the claims against the source
Selection creates another responsibility: the writer must stay faithful to the evidence it keeps. The draft check first looks for numbers absent from the record. Jev then compares each sentence with the three journal entries that share the most words with it and reports support, contradiction or missing evidence.
That comparison can miss a source in a prompt or a commit. It gives the writer a list to investigate, rather than permission to treat every unflagged sentence as proven.
Description and steps
A flow for one Lab Project draft. The draft is split into sentences. Code lists every em dash and every number the record does not hold. Jev then judges each sentence against the three journal entries nearest to it, with a verdict and whether it states a checkable fact. A checkable sentence is listed when the record contradicts it, says nothing about it, or supports it with low confidence. Anything else is skipped as connective prose. What is listed goes into verify.md, which ends with the voice check. The takeaway is that code handles what is exact and Jev only what needs judgment.
- Draft (start or end)
- Sentences
- Code: dashes, numbers
- Jev: verdict
- Checkable? (decision)
- Listed
- Skipped (start or end)
- verify.md (start or end)
Connections
- Draft to Sentences
- Sentences to Code: dashes, numbers
- Code: dashes, numbers to Jev: verdict
- Jev: verdict to Checkable?
- Checkable? to Listed: yes
- Checkable? to Skipped: no
- Listed to verify.md
The first check needed a check of its own
A test draft included an em dash, invented numbers and a reversed decision. Code caught the punctuation and numbers; Jev caught the contradiction. But the invented result initially fell below the threshold for deciding whether a sentence made a checkable claim.
Lowering that threshold brought the claim into review. The test was useful because it exposed a missed error. It did not establish the check's accuracy on every future article.
Make the copy sound and read like me
Accuracy still leaves the voice to solve
A sourced sentence can still sound unlike me. I asked for the writer to follow the studio's voice, so the same pipeline gained a shared voice document, a save-time gate and a broader review with code and Jev.
The gate handles the exact rules: it refuses agent saves containing an em dash or a banned phrase. The review looks for generic, inflated or formulaic writing. Those judgments need a person to read them. The shared rules also reach other CMS collections and the Ask assistant.
Description and steps
One file, docs/editorial/voice.md, holds the house voice. A test keeps voice.ts, the same rules as code, identical to it, and one line of the rules goes into the Ask assistant's system prompt. From voice.ts the rules reach the save gate, which refuses an API key's save that holds an em dash or a banned phrase on every collection, and the check, pnpm editorial:voice, whose report also ends the draft check. The takeaway is that the voice work reaches past the lab pipeline: the gate covers the whole CMS, and the Ask assistant reads the same rules.
- voice.md (start or end)
- voice.ts
- Ask prompt
- Save gate
- Voice check
- Draft check
Connections
- voice.md to voice.ts: tested
- voice.md to Ask prompt: one line
- voice.ts to Save gate
- voice.ts to Voice check
- Voice check to Draft check

One table in voice.md says which rule is enforced where.
Calibrate against writing I would keep
Claude tested the voice review on two Lab Projects I had approved and a deliberately generic agency draft. The first thresholds flagged some of the approved assertions as inflated, so the thresholds moved into the gap between the two sets.
Some abstract lines still appeared as generic even though they expressed a specific studio point of view. I kept those. A voice check should help me notice weak writing without sanding away the ideas that make the writing mine.
Keep the article's signposts with its copy
The page also needed headings that would help a reader follow the story. Claude initially proposed copying each beat's heading from the Lab Project into its page block. I rejected that duplication: a beat now brings its heading from the canonical record, with a page override only when needed.
Repeated headings also needed a rule. Code hides exact repeats of the section opener. Jev judges paraphrases when the page is saved, so the page can avoid saying the same thing twice.

A beat carries its own public heading on the record.

The record's beat heading prints one level under the Section's opener.
What the heading check actually added
In the test on the Webflow-to-Payload page's 48 beats, code hid 13 exact repeats and Jev identified two more paraphrases. The remaining 33 headings printed. Most of the work belonged to a simple rule; the model handled the narrower judgment the rule could not make.
The first article exposed a different problem
The first article asked too much of the reader
All of those checks still left the most important test: reading the finished article. The writer's first run on this journal produced 41 beats across 24 sections. It followed the journal closely, called me by name in the third person 28 times, and left 11 sections with no visual.
The brief had pointed to my published Lab Pages as examples and asked for every beat to appear. The writer copied their scale along with their strengths. The brief specified coverage and examples, but no reading-time limit.
The examples were over budget too
The first draft took 28 minutes by the site's prose count, before code listings were included. With code listings excluded, From Webflow to Payload took 15 minutes and the shader studio page took 14. At 200 words a minute, none fit the five-to-ten-minute article I wanted.
That made the problem concrete. Removing code from the reading-time calculation could make the estimate more useful, but the draft still needed an editorial cut.
- With code
- Prose only
Lab Page
Minutes
Description and data table
A horizontal bar chart of three Lab Pages, each read with code listings counted and with prose only. The first draft of this page took 39 minutes with code and 28 without. From Webflow to Payload took 22 and 15. The shader studio page took 15 and 14. A line marks the ten minute ceiling. The takeaway is that all three pages were over the budget, the two published pages the writer copied included.
| Lab Page | With code | Prose only |
|---|---|---|
| First draft of this page | 39 | 28 |
| From Webflow to Payload | 22 | 15 |
| Shader studio | 15 | 14 |
Set the boundaries the examples left unstated
I set three requirements: write in my first person, stay within ten minutes of prose, and give every section a visual. Claude made them part of the draft check. The first rewrite came down to seven sections and 19 beats, with a seven-minute prose count.
The shorter page still gave session capture, voice calibration and heading mechanics similar weight. It needed a clearer connection between those parts: first preserve the decisions, then help a reader understand them. The page rules did not test for that.
What the first published test taught me
How the work was shared
I set the direction, reviewed the drafts and made the calls attributed to me in the journal. Claude designed and built the tooling, logged the work and proposed decisions of its own. Jev helped sort the record and review the copy.
The chart records the build and first write-up sessions through 27 September. Cache reads dominate that volume because context is read repeatedly; they should not be compared with output tokens as if they measured the same work. These figures document usage, not time saved or the quality of the result.
- Output
- Cache write
- Cache read
Session
Tokens
Description and data table
A horizontal bar chart of ten sessions in date order, each with its output, cache write and cache read tokens. Cache reads dominate every session. In this historical snapshot, the session that ran the first write-up and the new rules, from 17:17 UTC on 27 September, read 81.6 million tokens from cache by 18:49 UTC. The longest build session, from 23:50 UTC on 21 September, read 20.8 million and wrote 154 thousand output tokens. Four short sessions on 21 September read under 3 million each. The takeaway is that cache reads, the context read again on every message, are nearly all of the volume and are not comparable to output.
| Session | Output | Cache write | Cache read |
|---|---|---|---|
| 21 Sep 16:33 | 103.2K | 158.7K | 16.4M |
| 21 Sep 17:53 | 4K | 47.9K | 867.9K |
| 21 Sep 18:05 | 1.4K | 39.5K | 256.3K |
| 21 Sep 18:11 | 23.3K | 104.2K | 2.7M |
| 21 Sep 18:32 | 71.8K | 478.1K | 11.9M |
| 21 Sep 19:28 | 2.1K | 41.9K | 215.1K |
| 21 Sep 23:50 | 153.9K | 640.2K | 20.8M |
| 27 Sep 17:00 | 1.5K | 52.6K | 534.9K |
| 27 Sep 17:03 | 11.7K | 100.9K | 1.7M |
| 27 Sep 17:17 | 68.9K | 1.2M | 81.6M |
Test the handoff, not just the parts
The same pattern appeared twice. Capture passed its unit tests and then took the wrong session on the real machine. The shorter article met the page rules and had been checked against the record, but it was still hard to follow.
Each handoff needs its own test: does the record describe the right work, and does the article help a new reader understand it? Checking the individual parts cannot answer both questions.
Tell the writer what the example is for
The brief should say what an example is for. I wanted the diagrams and screenshots on my published pages, but the writer also copied their length.
Next time I would name those qualities alongside a reading-time limit, rather than leave the writer to infer the brief from a reference page.
What I would carry into the next feature
The journal now gives a fresh writer a record of the decisions, failures and evidence behind a feature. It still depends on someone noticing when the conversation changes subject, and its private prompt history stays on the machine that captured it.
For the next write-up, I would decide the article's central question before choosing its beats or figures, then read the assembled page for the connections between sections. That is the judgment this first published test needed: enough context to follow the decisions, and enough restraint to know which ones belong in the story.
About this project
- Status
- In progress
- Built with
- Claude CodeTypeSafe JevPayload CMSTypeScriptModel Context ProtocolClaude in Chrome





