One person, six lanes

I build Pulse alone. There is the hosted app, a desktop app that runs a small model on your machine, an iOS app, and a few small websites. One person cannot hold all of that in his head. So most of the work goes to Claude Code sessions, and I give the word on what ships.
That works only if three things are true. Each session must know what the company knows. Each session must know what it was doing yesterday. And each change must pass checks that do not depend on the model's good intentions.
This post describes how I do those three things today: the brain, the lanes, and the checks. It also says what is not solved yet, and where the ideas came from.
The problem: summaries of summaries
A Claude Code session has a context window. When the conversation gets too long, Claude Code compacts it: it writes a summary and continues from the summary. That is useful. But a session that runs for weeks gets compacted many times. Each new summary is a summary of the last summary.
Detail goes first. Then facts go wrong. One of my sessions, after a few compactions, had a date wrong. It had mixed UTC and Tokyo time in a summary. Nothing failed. The session simply believed the wrong date, and it was sure of it.
The second problem is the opposite one. When a session gets confused, the easy fix is to start a new session. Do that ten times and you have ten sessions that each know part of a story. Nobody knows which one holds the current state of the release.
So I needed memory that does not live in a conversation, and a rule about which session does which work.
The brain

"The brain" is my name for the set of files that tell a new session what the company knows. There is an internal page for it at /hq/brain. The page shows each part, how big it is, and what is wrong with it.
In November 2026, Anthropic published The components of agent memory. It names four layers of memory and one process that works across them. I use the same names, because they fit what I had already built.
Transcripts. The raw record. Nobody writes it for a reader. Here it is 5,655 git commits in one repository, the session transcripts on my Mac, the server logs and the database logs. A session reads this layer only when a note or a doc is in doubt.
Memory notes. One file for each fact that the code cannot tell. A decision, a correction, a taste call, a limit. Each note has a "Why" line and a "How to apply" line. There are about 150 notes. A short index file, MEMORY.md, has one line for each note.
Memory representations. The wiki. The docs folder has 110 pages. One page for each system, with links between the pages. The pages in docs/areas/ are the reference: when a playbook and a doc disagree, the doc wins.
Injected context. The text that every session reads before its first step. Here that is the root CLAUDE.md and the memory index. A product folder can have its own CLAUDE.md, which loads only when the work touches that folder. There are 10 of them.
Dreaming. A pass that reads the other layers, corrects errors, merges copies and repairs links. Mine is a scheduled task on the first day of each month. For each rule, it asks one question: does this rule still carry load?

Rules need a reason and a test
A rule without a reason gets broken by the first session that finds it inconvenient. So each rule in CLAUDE.md says what went wrong. For example: "Never set measureBeforeMount on the grids (2026-09-11: it broke every stacked layout)." The date and the failure are part of the rule.
A rule that matters more gets a test. tests/unit/house-rules.test.ts holds 36 cheap source checks. Every server Supabase client must pass a no-store fetch. Nothing may call new Resend directly. Those two rules come from one incident: a framework cache served a 17-day-old database read to a daily audit. The test runs in CI, so the rule does not depend on a session reading CLAUDE.md with care.
Patterns across products
The brain page also lists 17 patterns. A pattern is a rule that two or more products learned from a real failure. "One home for every fact that two places need." "A cache nobody writes again serves its first answer forever." "Selection is code, wording is the model." Each pattern names its evidence, its source docs, and how it is checked: enforced by a test, partly checked, or written down only.
The "written down only" column is the honest one. It is the list of rules that a session can still break.
The preload is too large
The injected layer is now about 7,700 words of CLAUDE.md plus about 2,500 words of index. That is roughly 10,000 words before the first tool call. On 8 November, CLAUDE.md was about 6,600 words. It grows because every incident adds a line.
Anthropic's post warns about this: long instruction files reduce how well the model follows them. I know that the fix is to move detail down into the docs and keep a pointer. I have not done it yet. The brain page lists it as an open gap.
Lanes

A lane is one kind of work. Each lane has one long-lived Claude Code session and one state file. I have six lanes:
- •Pulse Hosted: the web app, billing, Plaid, iOS.
- •Pulse Local: the desktop app, its evaluations, its releases.
- •Sites: the small websites.
- •Ops and side objects: the server, Cloudflare, scheduled jobs.
- •Fundraise and credits: investors and cloud credit programs.
- •Brain and CI: the brain page, CI, the checks, the sessions themselves.
The rule is simple. Never start a second session for a lane that has one. And never let a lane's memory live only in its conversation.
The state file
Each lane has a Markdown file in ~/.claude/lanes/pulse-browser/. It is outside git: the session ids are local to my machine, and the content changes every day. Every state file has the same four sections:
# Lane: Brain and CI
Last updated: 2026-11-10 21:40 JST
## Now
- Nothing in progress. Everything is merged to main.
## Waiting for Nizar
- GitHub Pro (about $4/month) would allow server-side protection of main.
## Decisions to keep
- 2026-11-10: every change reaches main through a pull request.
## Where things are
- Hooks: scripts/ci/*-hook.mjs; wiring in .claude/settings.json.
"Now" is what is in progress. "Waiting for Nizar" is what needs my word. "Decisions to keep" is what was decided and must not be argued again. "Where things are" is paths, so the next session does not search.
The session updates this file at the end of each piece of work, not at the end of the day. A lane is only as good as its file.
Clear in place

When a lane's context gets long, the session does not wait for another compaction. It updates its state file. Then it clears its own context, in place. The sidebar row stays the same, and so does the name. The conversation is gone, but the card on the frame stays.
The next message starts with lane: brain-ci. A hook sees the prefix and tells the model to read the state file first. The session starts again with a clean context and a current, short, curated memory.
The rules for when to clear are on the brain page:
- •After one or two compactions. Each one is a summary of a summary.
- •When the session repeats old facts, or asks about things already decided.
- •When a piece of work is done and the next task is not related.
And when not to clear: while work is in progress and not yet in the file, or while a build, a dev server or a helper agent still runs.
The lane guard

A rule that only I remember is a rule that breaks at midnight. So a hook enforces it. scripts/ci/lane-guard-hook.mjs is a Claude Code UserPromptSubmit hook. It runs before a prompt reaches the model.
It reads a registry, lanes.json. Each lane has a slug, a session id and a list of keywords:
{
"slug": "brain-ci",
"name": "Brain and CI",
"sessionId": "local_bf71849c-…",
"keywords": ["hq/brain", "ci workflow", "ci-ok", "lint ratchet", "deploy.sh", "tla+", "lane guard"]
}
When the first message of a NEW session matches a lane that already has a session, the hook stops it. It shows the user this (shortened):
New session stopped: this looks like Brain and CI work (matched: ci-ok, lint ratchet).
That lane already has a session: "Side tasks: Brain and CI". Continue there,
so its context and state stay in one place.
To start a new session anyway, send it again with "new:" at the start.
If this IS the Brain and CI session after a clear, start with "lane: brain-ci".
A few details make it usable:
- •It looks only at the first message of a session. Later messages always pass.
- •When two lanes match equally, it lets the message through. A wrong block costs more than a missed one.
- •A lane with no session yet does not block. The new session may become that lane.
- •Scheduled task runs pass.
- •It matches keywords in code. There is no model call, so it costs nothing and takes milliseconds.
- •It fails open. If the hook itself has an error, the message goes through.
How work moves between lanes
Lanes talk to each other. This week the Brain and CI lane found that the internal pages load a watchlist they never show. That is not its work. So it sent a message to the Pulse Hosted session, with the files and lines. That lane now owns the fix.
A task that truly fits no lane gets its own session. It starts with new:, ends with a pull request, and the app archives it when the pull request closes.
What a session may do
Every session works under one written list of permissions. It has three groups.
Without my word: read and search files. Run tests and builds. Edit files in its own branch. Commit, push its branch and open a pull request. Start helper agents. Read production logs. Update its own state file and clear itself.
Needs my word: merge to main. Deploy. Change or delete a production user's data. Change a database schema. Send an email. Spend money. Archive a session. One word is for one action.
Never, even with my word: weaken a formal model or a test to make it pass. Raise the lint baseline. Push to main directly. Show made-up data as real. Build on production by hand. Enter a password.
The first group is wide on purpose. A session that must ask before each commit is a session that I must watch. The second group is short, and each item is something you cannot take back.
The checks

The model can be careful. The model can also be wrong and sure of itself. So the checks are scripts, and they run whether or not the session remembers them. Think of them as sieves: each one catches a different size of mistake.
One CI workflow
.github/workflows/ci.yml is the only CI workflow. It runs on every push and every pull request:
- •the TypeScript typecheck, for both Next.js apps;
- •the ESLint ratchet;
- •the unit, API guard and house-rule tests;
- •the Python tests;
- •the TLA+ model checks;
- •a check that the anonymous database key cannot read private rows.
The last job, ci-ok, passes only when every other job passed. It is the one check that matters downstream: the deploy script waits for it.
No job needs a secret. A test must pass with no credentials, so the tests mock the payment and database clients. That rule keeps CI free and keeps production keys out of it.
The lint ratchet
When lint entered CI, the repository had 1,772 lint errors in 343 files. A rule that says "lint must pass" would be red forever. A red check that is always red gets ignored.
So scripts/ci/lint-ratchet.mjs freezes the errors per file and per rule in a baseline file. CI fails when a count goes up, or when a new file has an error. When a count goes down, CI passes and asks for a baseline update. The floor can only rise. The rule for sessions is in the "never" group: never raise the baseline to pass.
Formal models
Some state machines cost money or privacy when they go wrong: billing, the Plaid bank connections, advisor briefs and consent, and device accounts with dormant-account deletion. For those four areas there are TLA+ models in verification/.
A test checks the cases somebody wrote. The TLC model checker checks every order of steps in a small model. There are 13 specifications and 64 "mutants". A mutant turns off one guard in the model, and it must break a rule. If a mutant passes, the check can no longer catch that bug.
Each area has a skill, verify-billing and so on, that lists the files it covers. Before a session commits a change to one of the covered paths (34 files and 13 folders), it runs the skill. The skill must print only ok. And the rule is never to weaken a model to make it pass: if the code changed on purpose, the model changes in the same commit.
The models also found real bugs in the current code. Those live in verification/<area>/findings/, outside the gate, and each one is also an item in the backlog.
The second reader

The session that wrote a change is the worst reviewer of it. It knows what it meant, so it reads what it meant.
For the same covered paths, a commit needs a second reader: a fresh agent, with no memory of the work, reads the staged diff. Then the commit message gets a Second-Reader: line. scripts/ci/second-reader-hook.mjs is a PreToolUse hook on Bash. It stops a git commit of a covered file when that line is not there.
I want to be clear about its limits. The line is a statement by the session, not a proof that the read happened. The hook checks agent sessions only: a commit I type in a terminal does not go through it. And it fails open. It is a speed bump with a sign on it, and that is what it is meant to be.
Pull requests only
Every change reaches main through a pull request. CI runs on the pull request before main sees the change. GitHub branch protection would enforce this on the server, but on my plan a private repository needs a paid tier for it.
So scripts/ci/main-push-guard-hook.mjs is the free half. It stops an agent's git push that writes to main: git push origin main, HEAD:main, refs/heads/main, and a bare git push while the current branch is main. When I want one direct push, the command must carry the comment # main-push: Nizar's word.
The deploy
Pushing to main deploys nothing. scripts/deploy.sh is the only way to deploy, and it runs on the server.
- •It stops if a deploy freeze file exists. I set one before every launch.
- •It waits for
ci-okon the exact commit. If CI failed, it refuses. - •It builds beside the live build, at low priority, while the live build keeps serving.
- •It boots the new build on a standby port and needs a 200 from it, then runs a smoke list.
- •It swaps. During the restart, nginx sends traffic to the standby.
- •It checks health, and rolls back to the previous build if the check fails.
Each step exists because of an outage. Two sessions once built at the same time on the live directory, and the site answered 500 for minutes. Now a lock serializes deploys.
Watches have an end date
Some work is waiting: a reply to an email, a change on a club's website. A scheduled task can watch for it. But a watch with no end date runs forever and nobody reads it. One of my watches made 143 sessions in 29 days, about five a day.
Now every watch has a cadence, an end date and the cheapest check that works. A run that finds nothing archives itself. A run that finds something stays open until I read it. The brain page lists every watch with its end date.
Is this new?
Mostly no. I looked, and most of the parts have a clear source.
- •State outside the context window. Anthropic's Effective harnesses for long-running agents (November 2025) uses a progress file and git history so that each new session can see what earlier sessions did. It also says that compaction alone is not enough. That is the state file, for one workstream.
- •Notes and fresh contexts. Effective context engineering for AI agents (September 2025) describes compaction, structured note-taking and sub-agents with clean contexts.
- •Saving the plan before the context runs out. How we built our multi-agent research system (June 2025): the lead agent saves its plan to memory, and near the limit it starts fresh agents with a handoff.
- •The memory layers. The components of agent memory (November 2026), as described above. Before that, MemGPT (2023) moved data between a small context and external storage, and Letta's sleep-time compute (2025) let background agents reorganize memory, much like dreaming.
- •Hooks as enforcement. The Claude Code memory docs say to use a hook, not memory, for anything that must be enforced. The hooks reference documents prompt blocking and Bash blocking. The best practices page says to use
/clearbetween unrelated tasks, and to use a reviewer that has not seen how the code was written. - •Long-lived roles. Steve Yegge's Gas Town gives agents named roles with persistent identity, git-backed state and handoffs. claude-code-handoff uses hooks to keep one session-state file current and reload it.
What I did not find is the combination: a fixed registry of long-lived sessions, one for each kind of work, each tied to one curated state file with fixed sections, and a hook that refuses a duplicate session at its first prompt and names the session that owns the work. Gas Town assigns work to an agent with a command. Nothing I found refuses the new session. Clearing in place, in the same session, is a small variant of the usual handoff to a new one.
That is a narrow claim. Lanes are mostly an arrangement of known parts. I think the arrangement is the useful part.
What is not solved
- •Two lanes, one working tree. The registry says which session does what. It does not lock files. Two lanes can still edit the same checkout, so each session stages its files by name and never with
git add -A. - •The preload grows. About 10,000 words before the first step, and growing with each incident.
- •The hooks fail open, and they only see agents. That is on purpose, but it means they are reminders with teeth, not walls.
- •No judge for visual work. CI can tell me that a page builds. It cannot tell me that it is ugly. I am still the only judge of a screen.
- •The second reader is a statement. Nothing proves the read happened.
What I would keep if I started again
Write state to a file before you need it. A session's memory is a guess after the second compaction.
Give each kind of work one owner, and make a script enforce it. My own discipline lasted about a day.
Put the reason next to every rule. A rule with a date and an incident survives. A bare "never" gets argued away.
Let the checks be dull. A lint ratchet, a model checker and a deploy lock do not have opinions, and that is why they work.
The model does the work. The files remember it. The scripts check it. I give the word.
Sources
- •Lance Martin, Anthropic, The components of agent memory, 6 November 2026.
- •Justin Young, Anthropic, Effective harnesses for long-running agents, 26 November 2025.
- •Anthropic, Effective context engineering for AI agents, 29 September 2025.
- •Anthropic, How we built our multi-agent research system, 13 June 2025.
- •Anthropic, Managing context on the Claude Developer Platform, 29 September 2025.
- •Claude Code docs: Best practices, How Claude remembers your project, Hooks reference.
- •Packer et al., MemGPT: Towards LLMs as Operating Systems, 2023.
- •Letta, Sleep-time Compute, 2025.
- •Steve Yegge, Gas Town and beads.
- •claude-code-handoff.
Illustrations: Pulse, made with the OpenAI image API in the house style.