Tarang Deep Goel

Off the clock · Jul 2026

A harness that gives Claude Code a memory between sessions

Hooks, skills, handoffs and memory that let Claude Code and Codex pick up a project where the last session left off, and how it grew from a setup I used at work into a public harness.

claude-harness

Why does my agent forget everything the moment the session ends?

This was when 1M-token context windows were just coming to market, and I was running Claude Code with the biggest one I could get. Memory management wasn't really a thing yet: an agent could write notes to a file, but nothing decided what was worth keeping, checked whether it was still true or noticed it piling up. And compaction was a real problem. When a long session ran out of room, Claude Code summarised the conversation to make space, and the decisions and detail I cared about went with it. A bigger window helps inside one session, but it does nothing for the next one. Every new session started cold: it didn't know what I had decided yesterday, which branch held the half-finished work, or that another session was editing the same repo in the next tab. Working on my projects with agents made me realise how important context management is, and how important it is to keep track of what was planned, what is done and what is remaining. Most of that was living only in my head. So I started working on my harness to improve how I work across sessions.

claude-harness is what came out of it. It sits around Claude Code and Codex and has no server: it's shell scripts, some Python, Markdown files and a local search index. A session starts with a short brief of where the project was left, guards stop the agent from doing anything too dangerous while it works, every conversation gets saved, and at the end the session writes a handoff that the next one reads back.

How it works today

flowchart TD
    A["Me, in Claude Code or Codex"] --> B["Session starts"]
    B --> C["Brief: where this project was left"]
    A --> D["Every prompt"]
    D --> E["Suggest the 3 most relevant skills"]
    A --> F["Every action"]
    F --> G["Guards: secrets, risky commands, edits before planning"]
    A --> H["Every reply, and before compaction"]
    H --> I[("Archive of every conversation")]
    H --> M["Cheaper model suggests memories, I approve"]
    A --> J["End of session: handoff"]
    J --> K[("Resume board and project log")]
    K --> C
    I --> L[("Local search index")]
    K --> L
    L --> N["Recall past work"]

The way I thought about it, there were four pieces to get right: hooks, skills, handoffs and memory.

Hooks: things that always happen

A hook is a small script that Claude Code runs by itself at fixed moments: when a session starts, when I send a prompt, before and after the agent uses a tool, after every reply, and just before it compacts the conversation. The model doesn't get a say in whether a hook runs, and that's exactly why I wanted them. Anything I needed to happen every single time, I put in a hook.

The first one I cared about was the session-start brief. I wanted a new session to know what I would have told it myself: where this project was left on the resume board, what git looks like, the most useful previous session, and which other sessions are running right now. Then I added guards before every action, to block reads of secret files and destructive commands, and one that refuses code edits until the agent has worked out what kind of task it is dealing with. And since compaction kept throwing detail away, a hook exports the whole conversation after every reply and before every compaction, so nothing is lost for good.

I usually have several sessions open at once, often on the same repo, so I also had each session leave a small heartbeat file saying where it is and what it's doing. That's how a new session knows about the others, and which old ones I never saved.

Skills: playbooks the agent picks up

A skill is a written playbook for one kind of job, a Markdown file of instructions the agent loads when that job comes up. I ended up with 17: recall and vault-push to open and close a session, a family of workflow skills (work out what kind of task this is, debug in a disciplined way, check the work is actually done before saying so), plus ones for diagrams and for making text sound less machine-written.

The problem with skills is that the agent has to decide to use them, and often it doesn't. So on every prompt a hook searches the skill descriptions for the closest matches and puts the top three in front of the agent. I treat that as a nudge. For the things I wanted to happen no matter what, like planning before editing, I moved the rule into a hook.

Handoffs: a ladder of summaries

This is the part I think of as hierarchical context, and it's where the "what was planned, what is done and what is remaining" lives. I built it as four levels, each a shorter summary of the one below. At the top is a resume board with one section per project: what's in flight, what's next, what's blocked and the exact command to resume. Below it is a handoff per session with what I did, what I decided and what's still open. Below that is a project log for each repo, with a separate file for the real pivots. At the bottom are the full conversations, searchable.

Reading goes top down and stops as early as it can. A new session gets one section of the board, recalling a project adds the newest handoff and log entry in about 15 lines, and only questions like "why did we choose X" go down to the full conversations. Writing goes the other way: at the end of a session, vault-push writes the handoff, adds to the log and updates the board.

I held the ladder to one rule: every level has exactly one writer and at least one reader. A file that's written but never read is dead cost, and a file that's read but never maintained is a trap. Where several sessions touch the same file, I let a script do the editing rather than the model, so one session can't overwrite another's work.

Memory: what's true about me

Handoffs are about a project. Memory is about me: how I like to work, and facts that hold across every project. Claude Code keeps small memory files for this, but back then that was all it was: files, with nobody looking after them. I didn't want just anything landing in them. So once a session is long enough, a cheaper model reads the recent turns and suggests new memories, but they wait in a review queue until I approve them. Other hooks note when each memory was last checked, flag old or duplicate ones, and count how often each one is actually read, because I wanted to know whether memory was helping or just piling up.

Codex runs through one adapter that translates its events into the same shape, so both tools share the same scripts and the same archive. Logs, conversations and the search index all stay on my machine.

How I got here

1. The harness I used at work

The repo only starts in July 2026, and its first commit isn't new work. It's a copy of the harness I was already running at work, where we had also made the skill layers and hook layers. The session-start brief already read a resume board with one section per project, the conversation export was there, and so were the guard hooks. There were 12 skills, two of which only made sense at work because they ran product analysis on our analytics stack. Next to all of it sat a big notes vault built for a product manager's life: PRDs, OKRs, one-on-ones, meeting notes.

TODO(tarang): what came before the repo? The site dates the start to April 2026; how did the first hooks and skills come about?

It also uploaded its logs to a shared Google Drive folder, because several machines at work ran it and the maintainer needed to see whether each one had installed cleanly.

It also carried a few scars. Indexing had once added about 47 seconds to every turn, so it moved to a background job at session start. An early learning detector had been switched off because nobody read its queue. And the keyword skill router existed because the skills were barely being used, a problem I'd keep coming back to. So the shape was already there: hooks for each moment, a skill layer I was struggling to get used, and boards and handoffs for continuity. What it wasn't was portable.

2. Taking work out

The same day I took the work out: the two analytics skills, the keyword router, and the work-specific bits in the hooks. The bigger change was where knowledge lived. Before, a session could only save into the central work vault, and only if the repo was mapped to a project there. After, any git repo is a project, and its handoffs and log live inside the repo, next to the code they describe. That was the first real pivot.

TODO(tarang): why in-repo: decoupling from the work vault, or wanting the history to travel with the code?

I also merged skills that overlapped and two loggers that recorded the same thing, which left eight skills.

3. The memory loop

The old learning detector had failed, so I wanted to try again with a model. After a long enough session (12 turns or more), a hook sends a digest of the recent conversation, along with the memories I already have, to GLM-4.7 and asks for suggestions. They go to a review queue and I approve or dismiss each one.

TODO(tarang): why a model-based memory writer, and why GLM-4.7?

Then I stopped adding machinery. I could capture memories, but I couldn't answer whether any of them were any good, and I realised more capture wouldn't fix that. So I added the measuring instead: how often each memory is read, when it was last checked, a health report and a review that only proposes changes. The first number wasn't flattering: of nearly 200 memory files, 2% were on the new format.

Around the same time I looked at what happens when compaction hits mid-session. The end-of-session save captured state, but compaction only saved the raw conversation. I considered having the model write a summary first, and chose a plain snapshot instead (branch, last commit, uncommitted files and my last few prompts), because compaction can fire several times a session, the model took over 3 seconds with an occasional long tail, and the full conversation was already saved anyway. It's an index of where I was, not a summary.

This is also when the harness started writing handoffs about itself.

4. Going public

The biggest change after the import was the public fork, and it touched three layers at once.

TODO(tarang): why go public, and why then?

The workflow skills, which had lived outside the repo, moved in, and I added ones for diagrams, coding discipline and writing, which made 17. More importantly, two hooks started enforcing them. One refuses code edits until the agent has triaged the task that day, which its own comments call the single highest-value fix for GLM not using its skills. The other runs the project's tests in the background after edits. The instructions already told the agent to triage first; the hook turned that into a hard check.

TODO(tarang): the gate and the skill matcher both mention GLM under-using skills. Were you running Claude Code with GLM as the main model? A sentence would explain a lot.

The ladder of summaries shipped too. It dates from June, before the repo existed, but this is when it went into the template, with a small script that edits the resume board so the model never does surgery on a shared file. And I deleted the Drive upload outright. It made sense for a fleet of work machines, but for something public I wanted no data leaving the machine.

5. Matching skills by meaning

The keyword router had gone with the work skills, so I put a semantic matcher in its place: search only the skill descriptions with the prompt, and suggest the top three. Two days later every prompt started timing out. The cause was the new hook searching a 1.2 GB index that was 85% not yet processed. I first blamed a Homebrew upgrade and spent about six hours chasing rollbacks before proving the search tool ships its own engine. The fix was a rebuild: a tighter timeout and a way to pause the hook during long indexing runs, a newer version of the search tool, a different embedding model after the first replacement deadlocked, and dropping a big collection of newsletter articles, which took the index from 1.2 GB to under 500 MB.

6. Codex

Then I added Codex. One adapter takes Codex's events, translates them into the shape Claude Code uses and runs the same scripts, and Codex conversations go into the same archive, which by then held 2,000+ Claude sessions for it to share. The idea was to add Codex without moving away from Claude Code.

TODO(tarang): why Codex, and why then?

7. An optional central vault

Moving knowledge into each repo had a cost: on a machine where I work across many repos there was no single board. So I added an option to point everything at one central vault, the same way the work setup had its board, while each project keeps its own name. Without it, nothing changes. So the knowledge layer went full circle, with a choice at the end: a central vault, then one per repo, then one per repo with an optional central one. In October I rewrote the README, and that's the system at the top of this post.

Looking back, the lesson that kept coming up was to measure before adding more. Skills that barely got used, a learning queue nobody read and a memory pile at 2% on its own format all taught me the same thing. The pieces that stuck are the ones with exactly one writer and someone who actually reads what gets written.

TODO(tarang): has this changed how you work day to day?