On the first day of a multi-day piece of work, the assistant knows every rule I gave it. By day three the thread is long, and the rules from that first morning sit somewhere in the middle of it. The middle of a long context is a bad place for anything you need a model to remember.
So I stopped treating the chat as the memory. I built cockpit, a local-first command system that runs inside Claude Code, and published a generalized copy as opencockpit (MIT license, set up as a template repository). Every claim about how the system works can be checked against that repo; the numbers and settings under "Lessons from daily use" come from my own copy.
Why long threads and naive retrieval fall apart
Two published results match what I kept running into. Lost in the Middle (Liu et al., TACL) found that models do best when the relevant information sits at the beginning or end of the input, and that performance degrades significantly when it sits in the middle of a long context, even for models built for long contexts. Chroma's Context Rot report tested 18 models and found performance consistently degrading as input length grows, even on simple tasks.
A multi-day thread is a long context that keeps growing, and the decisions that matter most are usually the old ones in the middle.
Naive retrieval doesn't fix this. Similarity search returns what looks like the query, not what is current. A decision and the later decision that replaced it look almost the same to an embedding, so retrieval can hand back the stale one with full confidence.
There is a quieter failure too: ledger bloat. If every change is appended to a log the model has to read, the log grows with every session, and the model has to work out which of many entries still holds. That is the same long-context problem, rebuilt by hand.
What cockpit does instead
Four habits, all visible in the repo.
Current state lives in small files, overwritten in place. RUNWAY, the domain that ships my website, keeps its state in data/runway/state.json, a single file the script rewrites whole:
def save_state(st): STATE.write_text(json.dumps(st, indent=1, ensure_ascii=False) + "\n", encoding="utf-8", newline="\n") History lives in git, not in the context window. Every command that changes files ends with one commit and a push. When I need to know what changed last week, I read a git log, not an old thread.
Where I keep an append-only log, a script can fold it. The ideas backlog is an event log on purpose, because I want every version of an idea. The model doesn't read that raw log; scripts/ideas.py derives the current state from it. The format, from scripts/schemas.md:
{"ts": "...", "idea_id": "i-0001", "event": "edit", "data": {"set": {"gist": ""}, "note": ""}} {"ts": "...", "idea_id": "i-0001", "event": "status", "data": {"from": "new", "to": "developing", "conversation": "<folder>", "urls": [], "note": ""}} Skills stay small and load on demand. Each domain is one skill file. Claude Code keeps a short description of each skill in context and loads the full instructions only when a command needs them.
State contracts: the files the model must obey
There is no single spec file. The contract is spread over a few files, each with one job:
-
CLAUDE.md: the router, the folder contract, the global rules and the git rules. -
COMMANDS.md: every command name. A skill change updates it in the same commit. -
.claude/skills/<domain>/SKILL.md: each command step by step, including how it ends and what it commits. -
scripts/schemas.md: the format of every data file. - State files such as
data/runway/state.jsonanddata/fourthwall/state.json. - The folder contract:
inbox/is the drop zone,raw/keeps originals and is never deleted,data/holds structured truth that only skills write, andout/holds renders a script can always regenerate.
The rule I lean on most is the fourth one in CLAUDE.md:
4. Code does exact work. Arithmetic, dates, sorting, deadlines, hashes, rendering, and validation are done by scripts/. Never compute these in your head. Run the script and report its output. Every command takes the same path:
you: chat, files, phone photos -> CLAUDE.md routes the request, applies the global rules -> a skill the command's steps, from start to its end condition -> scripts/ every number, date, hash, render and check -> data/ structured records, validated after every edit -> git one commit and push per command The model makes the judgment calls. The harness does everything that must be exact.
Domains and boundaries
Every domain except HELP has its own skill, its own folder under data/, its own section in scripts/schemas.md, and a script for its exact work. Among them:
- EXTROVERT interviews me about an idea and edits the transcript into posts in my own voice. It also keeps the ideas backlog and versioned profile copy.
- RUNWAY ships content and code to my own site repo: a preview branch first, live only after I approve it, then live tests against production.
- FOURTHWALL documents the system itself and exports the public copy.
- CHASSIS is an example domain: vehicle maintenance from the owner's manual and service slips, tracked against odometer readings.
- HELP prints the command sheet.
The boundaries that matter most are enforced by code, not by the model's good intentions. A Claude Code hook runs the validator after every edit, from .claude/settings.json:
"PostToolUse": [ { "matcher": "Edit|Write|MultiEdit", "hooks": [ { "type": "command", "command": "python \"$CLAUDE_PROJECT_DIR\"/scripts/validate.py --hook" } ] } ] A disclosure lint per audience (internal, resume, public) gates every resume render and every site publish, and a voice lint (banned phrases, emoji, British spellings, em dashes) runs on every post before I see it. FOURTHWALL's export runs five numbered leak checks before a public copy can ship, and any finding blocks it. The last two, from scripts/fourthwall.py:
report["4 deny list"] = deny report["5 em dashes"] = dash Posts that make claims about my work can go to a claim checker before they go out: a read-only subagent with only the Read, Grep and Glob tools that labels every claim supported, stretched or unsupported against my own records.
To be precise about one word: this is not a multi-agent system. CLAUDE.md says that "agent" here means a division of duties, not a separate process. There is one Claude Code session plus one read-only subagent. There is no telemetry either. The record is git.
Lessons from daily use
Latency comes from the model, not the harness. The validator runs after every single edit, and on my own data it takes about 0.4 seconds (365 ms, the median of five runs, Python startup included). I never notice it. What costs time is the model reading, so the cheapest speedup is giving it less to read.
Keep the context small on purpose. Scripts print short summaries and the model reports them. A status command prints a few lines a script computed, not the files behind them. A skill loads only when its command runs.
Deterministic checks beat careful prompting. opencockpit ships 66 synthetic tests in one file. By default, every RUNWAY deploy waits for production and then runs the site's live tests against it, and a test that passed only on its retry still counts as passed, but the report names it as flaky. When a rule matters, it becomes a check that fails loudly.
The human stays at the gates that can't be undone. Nothing reaches my live site until I approve its preview. By default, a public copy of cockpit ships only after I've seen the diff and the leak report and said yes. I've since recorded a standing rule in my own copy (auto_publish): when an export is clean and its own tests pass, Claude approves and publishes it without asking and tells me afterwards. The publish script still refuses anything but a clean, approved export. And the permission rules deny the plain forms of force push, hard reset and global config change.
Try it
Create your own private repo from the template, then:
git clone <your private repo> cockpit cd cockpit pip install -r requirements.txt python tests/test_suite.py claude It ships with empty data and a fictional example user. Copy examples/persona/data/ into data/ and run /cockpit to try every command before adding your own records. The whole thing is about 4,000 lines of Python, scripts and tests together, small enough to read before you trust it.
The repo is github.com/aalishan001/opencockpit. If you build a domain of your own, I'd like to see it.

