SUITS & SANDALS

Expertise

  • Website Strategy, UX & Development
  • Sales, Marketing & Investor Communications
  • Clarifying Complex Stories
  • Embedded Creative & Digital Services
  • Product UX/UI & Design Systems

Who We Help

  • Technical B2B & Expert-Led Companies
  • Healthtech & Life Sciences
  • Growth & Repositioning Brands
  • Platforms & Digital Products
Ask
Answers about our work, services, and insights
Email a partner
  • HR TechVault Workforce Screening
  • Enterprise SoftwareAdaCore
  • InsurtechArturo
  • FintechInterchecks
  • Works
  • Insights
  • Immersive Lab
  • About Us
  • Website Strategy, UX & Development
  • Sales, Marketing & Investor Communications
  • Clarifying Complex Stories
  • Embedded Creative & Digital Services
  • Product UX/UI & Design Systems
  • Technical B2B & Expert-Led Companies
  • Healthtech & Life Sciences
  • Growth & Repositioning Brands
  • Platforms & Digital Products
Get in touch

Brooklyn, NY / Philadelphia, PA

Get in touch

Lab / Tool

The lab journal

While a feature is built, the working agent logs each decision and dead end, hooks count prompts and tokens with no model involved, and Jev sorts the record at wrap-up. A writer agent drafts the Lab Project from the journal and checks every sentence against it. Along the way the house voice moved into one file, with a gate at save time.

Published
27 September 2026
Built with
Claude Code, TypeSafe Jev, Payload CMS+3
Read
39 MIN

Context

A document for every feature

At the end of a session reviewing the sas-cms MCP server for places TypeSafe could cut agent token use, Miles asked for the findings as a document with his prompt, the model and the token usage, because that work would become a Lab Project entry.

The question

Then he asked whether an agent could do that for every feature: stay with the work across days and sessions, and record the key decisions, challenges, insights, models, token usage and prompt history. In his words:

is this something an agent would be able to do going forward if i want to begin work on a feature with the end goal of documenting the entire process for a new lab project entry in my cms including key decisions, challenges, insights, ai model and token usage used, prompt history, etc.?

He accepted the design Claude recommended in his next message, and asked for TypeSafe too:

i want this built - and i want you to also find opportunities to use /typesafe:typesafe-ai for this if relevant to save usage and improve workflow

What a published Lab Page already carried

Two Lab Pages were already published: From Webflow to Payload and Building a shader studio in Payload CMS. A later audit of the writer counted what is on them. The pages carry 7 to 8 diagrams each. The Webflow page adds 10 code listings and 14 admin screenshots with client names blurred, the shader page 4 charts and 2 bespoke figures, all of it placed by hand in sessions that predate the journal.

The problem

Three constraints

The repository is public, so nothing typed can be committed as typed. A subagent cannot watch a session: it runs in its own context, sees only what it is handed and returns one message. And the hooks run on every prompt, so they had to be instant, offline and unable to fail a session. What went wrong inside those lines follows.

The repository is public

Found while building: gh repo view reports the repository as public. A committed prompt file would publish everything Miles types, including anything pasted by accident.

What went wrong

Two bugs no test caught

The first live run captured the wrong session

The start command backfills the running session's prompts from its transcript. The first version found that transcript as the newest file in the project's transcript folder. Miles had a second session open in the same checkout, and it had written more recently, so the journal took that session's prompt and token counts.

It was caught on the first real run, by reading the session id in the output instead of trusting the summary line. Claude Code names the session in every tool call's environment as CLAUDE_CODE_SESSION_ID, so that became the source and the newest file only a fallback. The same run named the private folder with-vercel-website, because package.json still carries the template's name. The name now comes from the git origin. Unit tests had passed. Both bugs lived in the part that touches the real machine.

One session, two features

The tooling's first real use was wrong in a way no test would catch. Claude started one journal, named for the MCP feature, and logged both the MCP review and the building of the journal into it, with the whole session's tokens. Miles caught it after the push: they are different features.

The design had assumed a session belongs to one feature. Now a session row can carry a window, set with pnpm lab:journal window and honored by every recount, by the prompt capture and by the digest. The session was split at 16:33:08 UTC, the prompt where the conversation turned, so its tokens sum to the session total across the two journals instead of counting twice. Nothing detects a change of subject yet, so the skill tells the agent to ask.

typescript
1// scripts/lab-journal/lib.ts
2export type SessionRow = {
3 session: string
4 branch: string | null
5 // ...
6 /** Per model, subagents included: a subagent on another model is its own key. */
7 models: Record<string, TokenCounts>
8 /**
9 * The part of the session that belongs to this journal, when one session
10 * served two features (`pnpm lab:journal window`). Kept on every recount.
11 */
12 window?: SessionWindow
13}
14
15/** ISO times: `from` inclusive, `to` exclusive, either may be open. */
16export type SessionWindow = { from?: string; to?: string }
17
18export const inWindow = (at: string | undefined, window?: SessionWindow): boolean => {
19 if (!window?.from && !window?.to) return true
20 if (!at) return false
21 return (!window.from || at >= window.from) && (!window.to || at < window.to)
22}

Checkouts

A feature's sessions move between checkouts

Claude Code files a transcript under the path of the directory the session ran in, and the tooling looked only under the current directory and its checkout root. From a worktree, the recount and the digest would never have found the sessions that ran in the main checkout. They now look under every checkout that git worktree list names.

Conductor needed one more step. A Conductor workspace is a worktree of Conductor's own clone, so from there git worktree list never names the main checkout. Transcripts are now found by session id in any Claude Code project folder, since ids are unique. Tested in a workspace chat, Conductor ran the prompt hook but showed no SessionStart note. The guide now says to run pnpm lab:journal status rather than read a missing note as a failed hook.

How a session's transcript is found

yesfoundnot foundfoundnoSession id set?Every checkoutEvery project folderNewest fileTranscript
yesfoundnot foundfoundnoSession id set?Every checkoutEvery project folderNewest fileTranscript
Description and steps

A decision flow for finding the transcript of the session running a command. If Claude Code has put the session id in the environment, the transcript is looked for by that id, first in every checkout that git worktree list names, then in every Claude Code project folder. Only when there is no id, or the id is found nowhere, does it fall back to the newest transcript. The takeaway is that the newest file, which caused the first bug, is now the last resort.

  1. Session id set? (decision)
  2. Every checkout
  3. Every project folder
  4. Newest file
  5. Transcript (start or end)

Connections

  • Session id set? to Every checkout: yes
  • Every checkout to Transcript: found
  • Every checkout to Every project folder: not found
  • Every project folder to Transcript: found
  • Every project folder to Newest file (optional or async)
  • Session id set? to Newest file: no (optional or async)
  • Newest file to Transcript (optional or async)

The writer

The writer drafted words only

Before the writer was extended to media, an audit held its brief against the two published pages. The brief asked for one chart, tokens per session, and said the agent could not see or create images, so it would only place media ids a person handed it. That line was out of date. The agent has every tool, and Read shows it images.

A second gap sat under the first. pnpm cms:upload sent a file to CMS_UPLOAD_SERVER, else NEXT_PUBLIC_SERVER_URL, which in that workspace was localhost, while the sas-cms MCP writes to production. An uploaded id would name a local media document that the production draft could not resolve.

The figure plan

Asked what a diagram could show, Jev said yes to every entry

The figure plan adds four questions to the digest's request for each journal entry: screenshot, diagram, code listing and chart. The first wording asked whether the entry described a mechanism a diagram could show. Jev answered 0.84 to 0.97 for all 17 entries. Rewritten as a score from 0 to 3, 15 of the 17 scored 2.9 or higher. A plan that lists everything is not a plan.

TypeSafe's own limitations page for Jev 1.13 names the cause: it reads a question literally, and literally anything could be drawn. The fix asks what the entry is mainly about, and writes the near miss into the false side of the question. With that wording the diagram answers spread from 0.16 to 0.88, and five entries pass 0.5. Finding it took three full reruns of the digest at about 55,000 input tokens each.

Entries the diagram question put forward

Wording

Entries put forward, of 17

Seventeen journal entries, three wordings: whether a diagram could show the entry, the same as a score from 0 to 3, and whether the entry is mainly an explanation of a mechanism. The yes or no wordings count an answer of 0.5 or more.Source: docs/lab-journal/lab-journal/journal.md
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.

Entries the diagram question put forward
WordingEntries
Could it be drawn?17
Score of 2.9+15
A mechanism?5
typescript
1// scripts/lab-journal/digest.ts
2// The figure plan. Asked with `section` over the same state, so a figure costs no second request.
3// Jev reads a question literally, and anything "could" be drawn: asked that way it said yes to a
4// diagram for every entry. So each asks what the entry is mainly about, and its false side names
5// the near miss.
6diagram: noul('Is `entry` mainly an explanation of how a mechanism works?', {
7 true: 'Its main point is a mechanism: it names at least three components, steps or states and says what passes from one to the next, or the order they run in.',
8 false:
9 'Its main point is a choice and its reasons, a lesson, a number, or one thing that went wrong. Components or steps may be mentioned, but how they connect is not what the entry is for.',
10}),

Decisions

A skill and hooks, with one agent at the end

Nine decisions. The first four settle who writes each part of the record and where it lives. The rest decide how the writer reaches the CMS, what its copy follows and what one entry covers. Each says what it rejected and whose call it was.

Miles asked for an agent that stays with a feature across days and sessions. Claude recommended against a subagent for that part, and Miles accepted. A subagent runs in its own context, sees only what it is handed and returns one message, so it cannot watch a session, and keeping it informed would mean resending the context each time.

So the work splits three ways. A skill tells the main session what to log and when, because only that session has the context. Hooks capture prompts and token counts with no model involved. One subagent runs at the end, the lab-project-writer, where a fresh context helps: it reads the journal, not five days of conversation.

Who records what

logspromptsbrieftokensWorking sessionjournal.mdHooksJev digestWriter agentDraftDraft check
logspromptsbrieftokensWorking sessionjournal.mdHooksJev digestWriter agentDraftDraft check
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.

  1. Working session
  2. journal.md
  3. Hooks
  4. Jev digest
  5. Writer agent
  6. Draft (start or end)
  7. 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

Jev reads the record once, at wrap-up

Miles asked for TypeSafe wherever it saves usage. Claude kept Jev, TypeSafe's model, out of the hooks, because they have to be instant, offline and unable to fail a session. Jev runs once instead, in a digest step at wrap-up, and reads the raw record so the writer does not have to.

The record

Prompts stay on the machine

The journal, sessions.jsonl and meta.json are committed. Prompts go, redacted with the same redactFreeText the Ask assistant uses, to a folder under ~/.claude/lab-journals that every workspace and worktree on the machine shares.

Rejected: everything in the repository, which travels with the branch and publishes raw prompts, and a gitignored folder in the checkout, which a second Conductor workspace would not see. The cost is that prompt history does not follow the work to another machine. It was Claude's call, made without asking because it is the safe default and reversible, and flagged to Miles.

Never journal main

A review of the docs found that a journal listed on main captured every session anyone ran there, whatever feature it was for. Miles's call: main is never live, and start and resume refuse on it. Rejected: only removing main from the journals' meta.json, since the next resume on main would bring it back. The cost is that a journal cannot follow work done directly on main. Branch first.

The same change strips the block Conductor adds to a chat's first prompt, which the capture had kept. Both fixes are a string compare and a regex, with no extra git calls or model requests. Miles had asked for exactly that:

make sure we do not do anything here that causes excessive token usage or make things more costly.

typescript
1// scripts/lab-journal/lib.ts
2/**
3 * Never journaled: every session anyone runs on it would be captured, whatever
4 * feature it was for. A journal lives on its feature's branch.
5 */
6export const MAIN_BRANCH = 'main'
7
8/** The journals live on `branch`. More than one: the newest, since a branch has one feature in hand. */
9export function resolveActive(metas: JournalMeta[], branch: string | null): JournalMeta | null {
10 if (branch === null || branch === MAIN_BRANCH) return null
11 const live = metas
12 .filter((meta) => meta.status === 'active' && meta.branches.includes(branch))
13 .sort((a, b) => b.startedAt.localeCompare(a.startedAt))
14 return live[0] ?? null
15}
16
17export function cleanPromptText(raw: string): string | null {
18 // ...
19 const text = raw.replace(/<system_instruction>[\s\S]*?<\/system_instruction>/g, '').trim()
20 // ...
21}

Media

Screenshots from Miles's own Chrome

Miles's call: the writer takes its screenshots through the Claude in Chrome extension, in the Chrome window he is already signed into. For admin shots he signs in first and the agent moves around the admin. A reference site uses the same browser.

Rejected: a Playwright capture script with its own admin login, which needs a stored password or a saved login state on every machine, and fixture data unless it points at production. The extension shares the browser's login state, and its screenshot tool can save the image to disk, so the file goes straight to pnpm cms:upload. The cost is that the writer needs a session started with claude --chrome and a visible Chrome window.

The scripts and the MCP agree on one site

An upload has to land on the site the MCP drafts on, or its media id names nothing there, or another image. The first fix was scripts/cms-target.ts, one place that resolves the key and the site for every agent script. It refuses a local site unless --local is passed, and the upload prints the site it went to. Rejected: asking the MCP server which site it is, which a script cannot see, and defaulting to production, since agent uploads land public-approved and a contributor testing locally would publish by accident. Miles confirmed production as the target.

That version still made a person copy the MCP key and the production URL into .env. Miles pointed out that the sas-cms MCP is already configured for the whole machine. The scripts now read the site and the key from the sas-cms entry in ~/.claude.json, so they cannot disagree with the MCP about which database they are in. Environment variables still win, for CI, Codex and Cursor.

typescript
1// scripts/cms-target.ts
2export function cmsTarget(options: { local: boolean }): CmsTarget | string {
3 const configured = fromClaudeConfig()
4 const key =
5 (process.env.CMS_MCP_API_KEY ?? process.env.SAS_CMS_MCP_KEY)?.trim() || configured?.key
6 const explicit = process.env.CMS_UPLOAD_SERVER?.trim()
7 const server = (
8 explicit ||
9 configured?.server ||
10 process.env.NEXT_PUBLIC_SERVER_URL?.trim()
11 )?.replace(/\/$/, '')
12 // ...
13 if (isLocal(server) && !options.local) {
14 return `the target is ${server}, a local database. The ${MCP_SERVER} MCP server writes to the site in docs/mcp.md, so an id from here would not exist there. Set CMS_UPLOAD_SERVER to that site, or pass --local if your MCP client points at this dev server.`
15 }
16 return { key, server }
17}

Voice

The house voice, in three layers

Miles asked that every piece of text the writer produces follow the editorial guide's voice, tone and style, with TypeSafe wherever a judgment fits. He noted the guide was current on voice and stale on composition: its page structure describes service pages built with an older block set.

Three layers, each catching what the one before cannot. The rules move to one file, docs/editorial/voice.md, and a test keeps the code's phrase lists identical to it. A Payload plugin refuses, by path, any save from an API key that holds an em dash or a banned phrase. A check reads the rest, with code for what is exact and Jev for what is a judgment. Only the gate can promise anything. The other two report.

Rejected: rewriting the guide, because the ask was copy that follows it. A Claude review of each draft was rejected too, since it costs what the draft cost and judges with the writer's own bias. People editing in the admin are not gated, because a quotation or a client's own words may hold a phrase the doc bans.

One voice file, read in four places

testedone linevoice.mdvoice.tsAsk promptSave gateVoice checkDraft check
testedone linevoice.mdvoice.tsAsk promptSave gateVoice checkDraft check
The save gate runs on every collection and refuses. The voice check, pnpm editorial:voice, reports, and the draft check ends with the same report. The Ask prompt carries the rules in one line.
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.

  1. voice.md (start or end)
  2. voice.ts
  3. Ask prompt
  4. Save gate
  5. Voice check
  6. 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 Lab Project, the voice work included

When the voice work began, the journal noted that Miles had opened it as a continuation of this feature, that the save-time plugin and the Ask prompt reach beyond the journal, and that pnpm lab:journal window could split it off if it became its own Lab Project. On 2026-09-27, before this write-up, Miles settled it:

scope is one lab project no splitting.

The case for one entry: the voice check is the writer's quality gate. The cost is that this entry has to say where else the voice work reaches. The save-time gate sits on every collection in the CMS. And the same rules, cut to one line, sit in the Ask assistant's system prompt, so what a visitor reads there follows them too.

typescript
1// src/features/editorial/voice.ts
2export const VOICE_PROMPT_LINE = `Plain, specific, composed: short sentences of varied length, no hype, no superlatives, no em dashes (use a comma, colon or period). Never these words: ${AVOID_PHRASES.slice(0, 10).join(', ')}. Never a "not just X, it is Y" or "not only X but also Y" frame.`
3
4// src/features/ask/prompts.ts
5// The house voice (docs/editorial/voice.md), in the one line a system prompt has room for.
6const VOICE = `Speak as the studio ("we") in a warm, direct, plain voice. ${VOICE_PROMPT_LINE}`

Headings

Beat headings come from the record

A Story beats block printed a heading only when the page set one, by design. Miles's call reversed that design note and the agent's recommendation, which was to write a page heading on each block that needed one, taken from the record by hand. In his words:

incorrect it should fallback to heading only overrides can "override" it. story beat headings would need to follow proper heading hierarchy with correct heading type set. use /typesafe:typesafe-ai to classify this automatically in the future.

The rejected option put the same words in two places and left 64 blocks on the two Lab Pages to fill in by hand each time a page is composed. The chosen rule has a cost of its own. The Prose heading usually prints the first beat's heading already, so something has to decide when a beat heading only restates the heading above it, and at what level a beat sits. That judgment went to Jev.

Lab Project 3 in the CMS admin with story beat 23 of the Approach section open: Key custom-copy-falls-through, Label Custom copy falls through, Heading Page copy wins, canonical copy falls through, and the beat's body below.

A beat carries its own public heading on the record.

Lab Page 2 in the CMS admin with a Story beats block open: Source approach, Story content set to Story beat, Story beat Custom copy falls through, and an empty Heading field whose help text says to leave it empty to print the record's heading.

On the page, the heading field only overrides the record.

Build

Four hooks, and what each costs

Built in the order below, in seven sessions between 16:33 UTC on 2026-09-21 and 01:56 UTC the next morning: the journal tooling first, run on its own build, then the writer's figures, screenshots and draft check, then the voice layers and record beat headings. Two pull requests carried it to main.

Four Claude Code hooks are registered in .claude/settings.json, SessionStart, UserPromptSubmit, Stop and SessionEnd, and each runs scripts/lab-journal/hook.ts. Between them they capture each prompt, redacted, and each session's token counts, with no model involved.

Measured on this machine, one run each: a trivial TypeScript file starts in 52 ms under Node's native type stripping and 164 ms under tsx, and the real prompt hook took 163 ms end to end. tsx was kept. It matches every other script in the repository, so the hook reuses Ask's redaction instead of a copy. The prompt and Stop hooks run async, so those 163 ms never delay a reply. Only SessionStart is waited for, once per session.

What a hook costs to start

Run

Milliseconds

One run each with time, on Node 22.20, on the machine the journal was built on. Native means Node's own type stripping. The prompt hook is the real UserPromptSubmit hook.Source: docs/lab-journal/lab-journal/journal.md
Description and data table

A horizontal bar chart of three timed runs. A trivial TypeScript file starts in 52 milliseconds under Node's native type stripping and in 164 under tsx. The real prompt hook, under tsx, took 163 milliseconds end to end. The takeaway is that tsx costs about three times as much to start, which does not matter here because the prompt hook runs async.

What a hook costs to start
RunStart to exit
Native, trivial file52
tsx, trivial file164
tsx, prompt hook163

Two of the four entries in .claude/settings.json. Stop and SessionEnd run the same script.

json
1{
2 "hooks": {
3 "SessionStart": [
4 {
5 "hooks": [
6 {
7 "type": "command",
8 "command": "\"${CLAUDE_PROJECT_DIR}/node_modules/.bin/tsx\" \"${CLAUDE_PROJECT_DIR}/scripts/lab-journal/hook.ts\"",
9 "timeout": 10
10 }
11 ]
12 }
13 ],
14 "UserPromptSubmit": [
15 {
16 "hooks": [
17 {
18 "type": "command",
19 "command": "\"${CLAUDE_PROJECT_DIR}/node_modules/.bin/tsx\" \"${CLAUDE_PROJECT_DIR}/scripts/lab-journal/hook.ts\"",
20 "timeout": 10,
21 "async": true
22 }
23 ]
24 }
25 ]
26 }
27}

First version

Built, then run on its own build

By 16:47 UTC the first version existed: scripts/lab-journal with its library, hook, command line and digest, ten unit tests, the lab-journal skill, the lab-project-writer agent and the four hooks. Every hook event was exercised by hand with real payload shapes, and start, log, status and the digest ran for real against the session that built them.

The milestone also listed what was not verified. The hooks had not fired from Claude Code itself, which needed a new session. The writer had never run. The digest thresholds were untuned defaults.

bash
1# .agents/skills/lab-journal/SKILL.md
2pnpm lab:journal start <slug> --title "<Title>" # new journal, live on the current branch
3pnpm lab:journal log --kind <kind> --title "<T>" # append an entry, body on stdin (below)
4pnpm lab:journal resume [slug] # live again, and on this branch too
5pnpm lab:journal status # entries, sessions, token totals
6pnpm lab:journal pause [slug] # stop capturing (the journal live on this branch by default)
7pnpm lab:journal sync [session-id] # recount sessions from their transcripts; an id adds one the hooks missed
8pnpm lab:journal window --from <iso> --to <iso> # this journal's share of the running session
9pnpm lab:journal shot <file> --what "<what>" # file a screenshot with the journal, outside the repository
10pnpm lab:journal wrap [slug] # the feature is done
11pnpm lab:journal:digest [slug] # Jev reads the raw record into a brief and a figure plan
12pnpm lab:journal:verify [slug] --project <id> # code and Jev hold the Lab Project draft against the record

A guide the team can follow

Miles asked for a guide he and the team could follow in order, ending with the feature as a Lab Project and a new Lab Page. It is docs/lab-journal/README.md, beside the journals: setup per machine, the order of a feature, the eight finishing steps from final milestone to publish and wrap, team rules, a symptom table and a checklist. The skill stays the agent's contract, and the guide points at it.

Writing the steps down exposed two gaps. Nothing could capture a session the hooks had missed, so pnpm lab:journal sync now takes a session id. The writer said nothing about images. One claim in the first draft was also wrong: a wrapped journal can be resumed, so wrapping stops capture instead of ending it for good.

Wrap-up

The digest and its figure plan

The digest, scripts/lab-journal/digest.ts, reads the raw record once and asks Jev about each item on its own. A prompt gets three questions: what it was doing, how much of the story it tells, and whether it must not be published. A journal entry gets its story section, and in the same request whether it could carry a screenshot, a diagram, a code listing or a chart. For each agent message of 280 characters or more, four questions ask whether it reads as a decision, a problem, a measurement or a lesson, so an unlogged moment can be found without rereading transcripts.

Raw probabilities are cached, the thresholds live in one constant, and an item already judged is never sent again. The writer starts from what comes out: the prompts worth quoting and the ones held back, each entry's story section, a figure plan, and candidate moments to check against the journal.

What the digest asks, item by item

Digest scriptJevdigest.mdEach prompt: what, how much, sensitive?Quote it, or hold it backEach entry: section, four figures?Section and figure planEach long message: a missed moment?Decision, problem, measurement, lessonWrite the brief
DigestscriptJevdigest.mdEach prompt: what, how much, sensitive?Quote it, or hold it backEach entry: section, four figures?Section and figure planEach long message: a missed moment?Decision, problem, measurement, lessonWrite the brief
Description and messages

A sequence between the digest script, Jev and the private digest file. For each prompt the digest asks Jev what it was doing, how much of the story it tells and whether it is sensitive, and learns whether to offer it for quoting or hold it back. For each journal entry it asks for the story section and the four figure questions, and gets a section and a place in the figure plan. For each long agent message it asks whether it reads as a moment nobody logged, and gets a decision, problem, measurement or lesson score. Then it writes the brief to digest.md, outside the repository. The takeaway is that every item is judged on its own, in one pass, before the writer starts.

  1. Digest script to Jev: Each prompt: what, how much, sensitive?
  2. Jev to Digest script: Quote it, or hold it back (reply)
  3. Digest script to Jev: Each entry: section, four figures?
  4. Jev to Digest script: Section and figure plan (reply)
  5. Digest script to Jev: Each long message: a missed moment?
  6. Jev to Digest script: Decision, problem, measurement, lesson (reply)
  7. Digest script to digest.md: Write the brief

Checking the draft

A draft check held against the journal

pnpm lab:journal:verify holds a Lab Project draft against the journal. Code goes first, because code is exact: it lists every em dash and every number in the draft that the record does not hold. Then Jev takes each sentence with the three journal entries that share the most words with it and answers supports, contradicts or says nothing, following TypeSafe's citation check cookbook. A second question over the same state asks whether the sentence states a checkable fact at all, so a line of connective prose is not reported as unsupported.

The idea came from Miles asking whether TypeSafe could make the writer's media work cheaper and better. Jev 1.13 takes text only, so it cannot look at a screenshot, and that check stays with Claude. What it can do is plan the figures before the writer starts and hold the finished draft to the record.

How a draft sentence is checked

yesnoDraftSentencesCode: dashes, numbersJev: verdictCheckable?ListedSkippedverify.md
yesnoDraftSentencesCode: dashes, numbersJev: verdictCheckable?ListedSkippedverify.md
Checkable means a score of 0.25 or more, and low confidence means under 0.6. Both thresholds are untuned defaults.
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.

  1. Draft (start or end)
  2. Sentences
  3. Code: dashes, numbers
  4. Jev: verdict
  5. Checkable? (decision)
  6. Listed
  7. Skipped (start or end)
  8. 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
typescript
1// scripts/lab-journal/verify.ts
2const QUESTIONS = {
3 verdict: choice(
4 'How does `record`, notes kept while a software feature was built, relate to `claim`, a sentence from an article about that feature?',
5 {
6 supports:
7 'The record states what the claim states, in the same or other words. Every specific in the claim, such as a name, a number, a cause or an order of events, is in the record.',
8 contradicts:
9 'The record states something that cannot be true if the claim is: a different number, a different cause, a different choice, or the opposite outcome.',
10 says_nothing:
11 'The record is about the same feature but does not state what the claim states, or states only part of it and the claim adds a specific the record lacks.',
12 },
13 ),
14 checkable: noul('Does `claim` state a fact that notes from the build could confirm or deny?', {
15 true: 'It says what was built, chosen, rejected, measured, broken or learned, or what existed before.',
16 false:
17 'It is a heading, a question, a transition between passages, or a general opinion with no specific in it.',
18 }),
19} satisfies Questions

The writer, rewritten for figures and screenshots

By 19:56 UTC the writer could plan figures, take screenshots and check its own draft. The digest writes a figure plan. pnpm lab:journal shot files a screenshot in the journal's private media folder while the feature is built. The writer's brief was rewritten around a setup check, the figure bar of the two published pages, screenshots through Miles's Chrome, and a verify run and a preview check before it reports. The capture steps went into the article-authoring skill, so any article can use them.

The Chrome capture itself was not verified. The Conductor session that built it had no browser tools, and whether a subagent gets them was an open question.

Voice

One file for the voice

docs/editorial/voice.md now holds the house voice: how it should feel, sentences, punctuation, the language to favor and to avoid, the contrast frames, the flattened claims, and a table of what is checked where. The editorial guide's voice section points at it, and a note at the guide's top marks its composition sections as the service-page system that predates Sections, Story beats and Lab Pages.

src/features/editorial/voice.ts holds the same rules as code. Its test reads voice.md and fails if the two lists differ, so the two cannot drift.

The What is checked table in docs/editorial/voice.md on GitHub: each voice rule, where it is checked (at save for MCP writes, in pnpm editorial:voice, or in pnpm lab:journal:verify) and how, by code or by Jev, with the note that only API key saves are refused.

One table in voice.md says which rule is enforced where.

A gate at save time

src/plugins/house-style adds a beforeChange hook to every collection. A save from an API key that holds an em dash or a banned phrase is refused, every problem in one error with its path, the way the figures plugin refuses a bad spec. Autosave and team members in the admin are exempt. Nine unit tests cover it, and the MCP server instructions mention it in the room their 2048 character limit leaves.

When it shipped, the refusal was covered by those unit tests only. The workspace had no dev server of its own running to try it against.

typescript
1// src/plugins/house-style/index.ts
2const isApiKeyUser = (req: PayloadRequest): boolean =>
3 Boolean(req.user) && req.user?.collection !== 'users'
4
5const isAutosave = (req: PayloadRequest): boolean => req.query?.autosave === 'true'
6
7export const refuseOffVoiceCopy: CollectionBeforeChangeHook = ({ collection, data, req }) => {
8 if (!isApiKeyUser(req) || isAutosave(req)) return data
9 const errors: FieldError[] = copyStrings(data).flatMap(({ path, text }) => {
10 const findings = gateFindings(text)
11 return findings.length ? [{ message: describe(findings), path }] : []
12 })
13 if (errors.length)
14 throw new ValidationError({
15 collection: collection.slug,
16 errors: errors.map(({ message, path }) => ({
17 label: `${path} (${message})`,
18 message,
19 path,
20 })),
21 req,
22 })
23 return data
24}
25
26/** Attaches the gate to every collection: every one of them holds copy somewhere. */
27export const houseStylePlugin = (): Plugin => (config) => ({
28 ...config,
29 collections: config.collections?.map((collection) => ({
30 ...collection,
31 hooks: {
32 ...collection.hooks,
33 beforeChange: [...(collection.hooks?.beforeChange ?? []), refuseOffVoiceCopy],
34 },
35 })),
36})

Voice check

Jev reads for what code cannot

The check, pnpm editorial:voice, runs on any Lab Project, any document by collection and id, a file or a string, and pnpm lab:journal:verify ends with the same Voice section. Code lints what is exact: banned phrases, contrast frames, semicolons, paragraph length. Jev scores each paragraph on the guide's own terms: specific or generic, composed or inflated, human or formulaic, and whether it closes on a punchline. For headings, Jev names each one's grammatical form and code flags a run of one form.

Jev cannot read taste. The check finds the faults the guide names, not whether a line is good.

The strict pass on the published pages

With the check built, Miles asked for a strict pass on the published Lab Pages. The check's lists were read in full. Then every passage of Lab Projects 2 and 3 and Lab Pages 1 and 2 was read against voice.md by hand, because Jev lists what the doc names and a pass has to read the rest.

Nine lines changed, as drafts. One used a banned verb with an ending the gate's first pattern had missed, so the gate now catches a banned single word in any form, with a test. Others were a ranking with no particular behind it, a stock phrase used twice, a rhetorical setup and a caption that repeated a line the page already carried. The lines Jev had read as generic stayed. Each was an abstract line the studio means, such as "The website used to be the thing we owned. Now it is the first thing we do with what we own."

typescript
1// src/features/editorial/voice.ts
2/** "unlock" also as unlocks, unlocked, unlocking; "elevate" also as elevates, elevated, elevating. */
3const inflected = (phrase: string): string => {
4 const literal = escapeRegExp(phrase).replace(/'/g, "['’]")
5 if (!/^[a-z]+$/.test(phrase)) return literal
6 return phrase.endsWith('e')
7 ? `${literal.slice(0, -1)}(?:e|es|ed|ing)`
8 : `${literal}(?:s|es|ed|ing)?`
9}
10
11const PHRASE_PATTERNS = AVOID_PHRASES.map((phrase) => ({
12 phrase,
13 // Word-bounded, and a single word is caught in every inflection: "unlocks
14 // composable content areas" is the marketing verb the doc bans, whatever its
15 // ending. Found in approved copy on 2026-09-22, past the first version of this
16 // pattern, which stopped at the bare word.
17 pattern: new RegExp(`(?<![\\w-])${inflected(phrase)}(?![\\w-])`, 'gi'),
18}))

Headings

Headings judged when the page is saved

At render, src/blocks/shared/story-headings.ts finds the Section's first Prose heading and its level. Each beat prints its record heading one level under it, is hidden when it repeats the opener exactly, and gives way to the block's own heading when the page overrides it. Nothing at render calls a model.

What code cannot read goes to Jev when the page is saved. A plugin on the collections that offer the block, Lab Pages and Work Pages, asks two questions about each beat that code has not settled: does its heading only restate the opener, and does it open a subsection or a short passage. A subsection sits one level under the opener, a passage two. The answers are stored on the block with a hash of what was judged, and the renderer trusts one only while its hash matches. Autosave is exempt, and a failure leaves the beat to the code rule, so a save never fails for it.

The stored answers needed one new json column on the Story beats tables of both collections and their version tables, so the change waited on a migration, Miles's call.

How a beat heading is settled

yesnocurrentnoexactnoBeat headingOverride?OverrideJev's answer?Jev's callRepeat?HiddenPrinted
yesnocurrentnoexactnoBeat headingOverride?OverrideJev's answer?Jev's callRepeat?HiddenPrinted
Run at render for each beat with a heading on its record. Jev's answer is stored when the page is saved, with a hash of what it read, and counts only while the hash matches. Repeat means an exact repeat of the Section's opener. A printed heading sits one level under it.
Description and steps

A decision flow run at render time for each beat that has a heading on its record. If the page sets its own heading, that override prints. Otherwise, if the answer Jev stored at the last save still matches what it read, the heading prints or hides at the level Jev chose. If there is no matching answer, code hides a heading that exactly repeats the Section's opener and prints any other one level under it. The takeaway is that nothing at render calls a model: Jev judges once, when the page is saved, and its answer is used only while it is still current.

  1. Beat heading (start or end)
  2. Override? (decision)
  3. Override (start or end)
  4. Jev's answer? (decision)
  5. Jev's call (start or end)
  6. Repeat? (decision)
  7. Hidden (start or end)
  8. Printed (start or end)

Connections

  • Beat heading to Override?
  • Override? to Override: yes
  • Override? to Jev's answer?: no
  • Jev's answer? to Jev's call: current
  • Jev's answer? to Repeat?: no
  • Repeat? to Hidden: exact
  • Repeat? to Printed: no
typescript
1// src/plugins/story-headings/index.ts
2export const STORY_HEADING_THRESHOLDS = {
3 /** `restates` at or above which the beat's heading is hidden under the opener. */
4 restates: 0.6,
5} as const
6
7const QUESTIONS = {
8 restates: noul(
9 'Does `beat_heading` only restate `section_heading`, the heading printed directly above it on the page?',
10 {
11 true: 'It names the same idea in the same or other words, so a reader would see one heading twice.',
12 false:
13 'It names a different step, a part, a consequence or a narrower point than the heading above, so it adds something.',
14 },
15 ),
16 weight: choice(
17 'Under `section_heading`, what does `beat_heading` open? `beat_opening` is the start of its copy.',
18 {
19 subsection:
20 'A distinct step or part of the idea above: a stage, a mechanism, a component, something that could carry several paragraphs of its own.',
21 passage:
22 'A single point inside the idea above: one paragraph, an aside, an example, a detail.',
23 },
24 ),
25} satisfies Questions
Lab Page 2 on the live site: the eyebrow Payload CMS, the Section opener Enter Payload with its standfirst, then the beat heading Why Payload printed from the record one level down, and the start of the beat.

The record's beat heading prints one level under the Section's opener.

Shipping

Two pull requests, two merge commits

PR 18 went to main at 23:08 UTC on 2026-09-21, after Miles started a new Conductor workspace and found none of the work in it, since a workspace starts from main. His call was to merge then and keep working after. It went in as a merge commit, not a squash, so the branch and main share history. GitHub's merge call answered with a server error twice and then an empty body, and the merge had gone through anyway. The pull request's state was the thing to check.

PR 19 followed at 01:56 UTC with the voice layers, the strict pass and the record beat headings, again as a merge commit. The schema check found the one additive column, heading_auto. A migration with exactly that change was already in the tree when pnpm migrate:create ran, the command added nothing, and pnpm check:migrations and the drift check both passed against it.

Working with AI

How the work was split

Miles set the direction and made the calls the journal marks as his: build it, use TypeSafe, never journal main, screenshots from his own Chrome, merge PR 18 early, headings from the record, one Lab Project. Claude designed and built the tooling, wrote the docs, logged the journal entries, and made the calls the journal marks as Claude's, such as keeping prompts out of the repository. Jev judged the record and the drafts. No model ran in the hooks.

Seven Claude Code sessions built it, between 16:33 UTC on 2026-09-21 and 01:56 UTC the next morning. Two models shared them. The model claude-fable-5-1 carried the three long sessions: 2,668 input, 318,339 output, 1,191,464 cache write and 47,836,354 cache read tokens. The other, claude-opus-5, ran four short sessions and part of a long one, for 152 input, 41,453 output, 319,049 cache write and 5,330,506 cache read. Cache reads are most of both totals, and they do not compare with output.

The write-up runs on claude-opus-5-5 in further sessions on 2026-09-27, counted in the same file and not in these sums. Jev's runs are logged apart, in jev.jsonl. The journal puts the first digest at 17 requests and 13,556 input tokens, each of three figure-plan reruns at about 55,000 input tokens, and the planted draft check at 5 requests and 6,052. Four of those rows were restored by hand after a careless git checkout dropped them, with approximate times. The voice calibration and the heading judgments ran outside that file, and their costs are in their own entries.

Who did what

MilesClaudeHooksJevA prompt, or a call to makeSave prompt, redactedLog the entryCount tokens on StopDigest the recordSections, quotes, figure planCheck each draft sentenceSupports, contradicts, says nothingDrafts, never publishedReview, then publish
MilesClaudeHooksJevA prompt, or a call to makeSave prompt, redactedLog the entryCount tokens on StopDigest the recordSections, quotes, figure planCheck each draft sentenceSupports, contradicts, says nothingDrafts, never publishedReview, then publish
Description and messages

A sequence between Miles, Claude, the hooks and Jev. Miles sends a prompt or makes a call. The hooks save the prompt, redacted, and Claude logs the journal entry. When a turn stops, the hooks count its tokens. At wrap-up Claude asks Jev to digest the record and gets back story sections, quotable prompts and a figure plan, then asks Jev to check each draft sentence and gets supports, contradicts or says nothing. Claude hands Miles drafts that are never published, and Miles reviews and publishes. The takeaway is that the only model watching the build is the one doing the work, and a person publishes.

  1. Miles to Claude: A prompt, or a call to make
  2. Hooks, to itself: Save prompt, redacted
  3. Claude, to itself: Log the entry
  4. Hooks, to itself: Count tokens on Stop
  5. Claude to Jev: Digest the record
  6. Jev to Claude: Sections, quotes, figure plan (reply)
  7. Claude to Jev: Check each draft sentence
  8. Jev to Claude: Supports, contradicts, says nothing (reply)
  9. Claude to Miles: Drafts, never published
  10. Miles, to itself: Review, then publish

Tokens per session

  • Output
  • Cache write
  • Cache read

Session

Tokens

Seven sessions built the feature on 21 September. The two on 27 September prepared the write-up. The session running the writer is left out while it is still counting. Input tokens are too small to draw at this scale and are in the file.Source: docs/lab-journal/lab-journal/sessions.jsonl
Description and data table

A horizontal bar chart of nine finished sessions in date order, each with its output, cache write and cache read tokens. Cache reads dominate every session. The longest build session, starting 23:50 UTC on 21 September, read 20.8 million tokens from cache and wrote 154 thousand output tokens. The first, at 16:33, read 16.4 million and wrote 103 thousand. Three short sessions on 21 September read under a 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.

Tokens per session
SessionOutputCache writeCache read
21 Sep 16:33103.2K158.7K16.4M
21 Sep 17:534K47.9K867.9K
21 Sep 18:051.4K39.5K256.3K
21 Sep 18:1123.3K104.2K2.7M
21 Sep 18:3271.8K478.1K11.9M
21 Sep 19:282.1K41.9K215.1K
21 Sep 23:50153.9K640.2K20.8M
27 Sep 17:001.5K52.6K534.9K
27 Sep 17:0311.7K100.9K1.7M

Outcome

What was measured

What was measured while it was built, and how. Each number is a smoke test on one journal, two approved projects or one page, and none is an accuracy figure.

The first digest, on this journal

The first digest ran on the record of the session that built it: 4 prompts, 10 journal entries and 3 agent messages long enough to judge. It made 17 Jev requests on jev-1.13.0, 13,556 input tokens, in 1.2 seconds of wall clock, from the script's own output. It offered 3 of the 4 prompts for quoting, held none back, placed all 10 entries in a story section, and flagged 3 agent messages as candidate moments, all already logged.

What the same pass would cost Claude was not measured: one session is too small to show it. The digest behind this entry judged 37 entries in 61 requests and 57,759 input tokens.

The draft check on a planted draft

The first test of pnpm lab:journal:verify was a five sentence draft with three planted faults: an em dash in the title, an invented result of 83 percent across 412 sessions, and a reversed decision claiming every prompt was committed to the repository. Code caught the dash and both numbers. Jev marked the reversed decision as contradicted at confidence 1.00 and the three true sentences as supported at 0.81 to 0.99, for 5 requests and 6,052 input tokens.

The invented result came back as says nothing at 1.00, and was hidden at first. The second question, whether the sentence states a checkable fact, scored it 0.47 against a gate of 0.5. The gate is now 0.25. One planted test is not an accuracy figure, and the thresholds stay untuned until a real write-up has been through it.

typescript
1// scripts/lab-journal/verify.ts
2const VERIFY_THRESHOLDS = {
3 /**
4 * `checkable` at or above which a sentence is held to the record at all. Low on purpose: an
5 * invented result ("cut review time by 83 percent") scored 0.47, and a missed invention costs
6 * more than a transition listed by mistake.
7 */
8 checkable: 0.25,
9 /** Verdict confidence below which a supported sentence is still listed for a person. */
10 confidence: 0.6,
11} as const

Calibration

The voice check against approved copy

pnpm editorial:voice ran on 2026-09-21 against copy Miles had approved, Lab Project 3 with 70 paragraphs and Lab Project 2 with 48, and a planted four paragraph agency draft written to break every rule in voice.md. Code found no em dash and no banned phrase in any approved copy field, and refused the planted draft at every paragraph.

At the first thresholds Jev listed approved paragraphs too. The approved passages it read as inflated were the confident assertions the doc asks for, at 0.60 to 0.78, against 0.87 to 0.94 for the planted hype. Approved copy read formulaic at 0.68 at most, the planted draft from 0.79. So the thresholds moved into the gaps: inflated 0.8, formulaic 0.75, generic 0.5.

After the move Lab Project 3 lists 7 generic passages and nothing else, Lab Project 2 lists nothing, and the planted draft still lists 3 of 4 as generic and 4 of 4 on the other two. The runs cost 119 requests and 76,854 input tokens for Lab Project 3, 71 and 47,517 for Lab Project 2, and 8 and 4,987 for the planted draft.

What Jev listed at the first thresholds

  • Lab Project 3
  • Lab Project 2
  • Planted draft

Share of paragraphs listed

Listed as

Lab Projects 2 and 3 are approved, published copy. Before the thresholds moved Jev listed 10, 1 and 3 of Lab Project 3's 70 paragraphs, 2, 3 and 3 of Lab Project 2's 48, and all 4 of the planted draft's on each.Source: docs/lab-journal/lab-journal/journal.md
Description and data table

A grouped bar chart of the share of paragraphs Jev listed at its first thresholds, for two approved Lab Projects and a planted agency draft. The planted draft was listed at every paragraph as generic, inflated and formulaic. Approved copy was listed far less: 14 percent of Lab Project 3 and 4 percent of Lab Project 2 as generic, and between 1 and 6 percent as inflated or formulaic. The takeaway is that approved and planted copy scored apart, and the thresholds were moved into that gap.

What Jev listed at the first thresholds
Listed asLab Project 3Lab Project 2Planted draft
Generic14.3%4.2%100%
Inflated1.4%6.3%100%
Formulaic4.3%6.3%100%

Headings

Beat headings on Lab Page 2

The heading rule and the save-time judgment were run over Lab Page 2, From Webflow to Payload, on 2026-09-22: 24 Sections and 48 beats. Of those, 33 beat headings print and 15 are hidden. Code settled 13, all exact repeats of the Section's Prose heading and all the first beat of their Section. Jev was asked about the other 35, one request each.

It differed from the code rule on two, both paraphrases code cannot see, and both right: "Every case study in the same template" under "One template for every case study", and "A Content Hub and a website" under "Two layers: a Content Hub and a website". Every other beat read 0.05 to 0.29 against a threshold of 0.6. On level, Jev called 22 beats a subsection and 13 a passage: the passages are the short single-idea beats, the subsections the multi-paragraph steps. One page is not an accuracy figure.

How 48 beat headings on Lab Page 2 were settled

Outcome

Beat headings

The heading rule and the save-time judgment run over Lab Page 2 on 22 September. Code hides exact repeats of the Section's opener. Jev was asked about the other 35 beats and hid two paraphrases.Source: docs/lab-journal/lab-journal/journal.md
Description and data table

A horizontal bar chart of the 48 beats on Lab Page 2. 33 headings print. 13 are hidden by code as exact repeats of the Section's opener, and 2 more are hidden by Jev as paraphrases. The takeaway is that code settles most of the hidden headings, and Jev adds the two paraphrases code cannot see.

How 48 beat headings on Lab Page 2 were settled
OutcomeBeats
Printed33
Hidden by code13
Hidden by Jev2

The strict pass, checked line by line

The nine edits were saved as drafts: six beats on Lab Project 3, one beat and the medium summary on Lab Project 2, and one caption on Lab Page 2. A dump of every passage before and after showed exactly those lines changed and nothing else: 236 lines on Lab Project 3, 140 on Lab Project 2 and 164 on the page, the same counts before and after.

On the rerun nothing was refused and no passage read as inflated or formulaic. Jev was asked only about the changed passages, 6, 2 and 1 requests, because its cache is keyed on the passage text. By 2026-09-27 Miles had published all three.

Learnings

What we took from it

Five lessons, each from a moment logged above, and what the build left open.

The bugs lived where the tests did not reach

Unit tests passed before the first live run, and it still took the wrong session and named the private folder after the template. Both bugs lived in the part that touches the real machine: two sessions open in one checkout, a package name nobody had changed. The mixed journal was the same kind of fault, one no test would catch, found by a person reading what the tool had written.

Read the state, not the summary

The wrong session was caught by reading the session id in the output instead of trusting the summary line. The first merge answered with two server errors and an empty body, and had succeeded all the same. In both cases the useful check was the thing itself: the id in the output, the state of the pull request.

Ask Jev what an entry is mainly about

Jev reads a question literally. Asked whether a diagram could show an entry, it said yes to every one, because anything could be drawn. Asked what the entry is mainly about, with the near miss written into the false side, the answers spread and five entries passed.

Jev lists, a person decides

Against approved copy, Jev's generic list held abstract thesis sentences the studio means. It cannot tell a studio's own idea stated plainly from agency copy, so that list is one to read, not one to cut. The strict pass kept every one of those lines and changed nine others, found by reading every passage by hand.

Only the gate promises anything

The voice rules reach an agent in the server instructions, in the skills and in the check. Only the save-time gate can promise anything, and only for what is exact, an em dash or a banned phrase. The other two layers report. Even the exact part needed care: a banned verb with an ending slipped past the gate's first pattern, in copy that had already been approved.

What is still open

The figure plan's threshold of 0.5 and the draft check's 0.25 and 0.6 are untuned defaults, waiting on a real write-up. Nothing detects that a session has changed subject. Prompt history does not follow the work to another machine, so the digest and the writer should run where most of the work happened. Lab Page 1 was last saved before the heading hook shipped, so only the exact-repeat rule applies there until it is saved once. When the record closed, the lab-project-writer had not had its first real run.

About this project

Status
In progress
Built with
Claude CodeTypeSafe JevPayload CMSTypeScriptModel Context ProtocolClaude in Chrome

Ready to start?

Let’s make it make sense.

Request an estimateGet in touch

Want to go deeper?

Ask about our work, process, capabilities, or what working together could look like.

S&S

Ask Suits & Sandals

Online

Email a partner
Privacy PolicyTerms and Conditions