Writing · · 15 min read
Wanted a bigger context window. Ended up breaking through it.
I kept wanting more context because too much of my actual product work lived inside one Claude session. Somewhere along the way I realised I didn’t really need a bigger window. I needed to be able to jump out of one and continue like nothing happened.
Given the kind of work I’ve been doing lately, my hand is almost forced into keeping one Claude Code session alive for way too long.
A lot of the time it starts with brainstorming.
Then product decisions.
Then some rough implementation.
Then I render it, hate something, change the flow, find another constraint, change something else, and suddenly the same chat has gone from:
“what should this feature even look like?”
to:
“why is this button 2px taller only on mobile Safari?”
By that point the conversation isn’t just chat history anymore. It has slowly accumulated the reasoning behind the feature.
And sometimes I’m right at the end of implementation, almost ready to wrap the whole thing up, when compaction is standing behind my context window like The Undertaker waiting for the pin.
1... 2... 3...
Context gone.
So every time the context meter started creeping up, the same thought came back:
what exactly are you about to forget?
For a while my answer was obvious.
Give me a bigger context window.
If 300k isn’t enough, give me 500k.
If 500k isn’t enough, give me 1M.
Just let me keep going.
But then another little fear starts creeping in.
Every request is now carrying this enormous history around. I’m getting more attached to one particular session. Splitting work becomes harder. Starting fresh feels expensive because the new session knows nothing.
And my Indian brain also starts looking at token usage like 👁️👁️.
At some point the question flipped.
I didn’t actually want to keep one Claude session alive forever.
I wanted to remove the constraint itself.
If I:
- start a fresh Claude session
- switch to another chat
- split one workstream into multiple sessions
- come back to something weeks later
...I want it to still feel like I’m continuing the same work.
Why should I care which session I’m in at all?
I was way too loyal to a single Claude session
The long conversation had basically become project memory. So naturally I treated leaving that one Claude session like abandoning the project halfway through.
A lot of frontend/product work doesn’t start with a perfect PRD, finished Figma and architecture diagram.
I mean, just admit it.
You’ve built features like this too.
At least I have.
Sometimes you’re figuring the feature out while building the feature.
- Render something.
- See how it feels.
- Change it.
- That exposes another interaction.
- Inspect the current product.
- Find some weird API constraint.
- Change the flow.
- That changes the UI.
- Mobile ruins the entire theory you had twenty minutes ago.
- Repeat.
All of those tiny decisions start accumulating inside the conversation.
Not just what the feature is, but:
- why something ended up shaped this way
- what technically worked but felt wrong
- which interaction I explicitly rejected
- which assumption turned out to be false
- what broke when we tried the “obvious” solution
- what I asked Claude never to touch again
- what changed after seeing the thing actually rendered
Eventually the design stabilises.
At that point, absolutely write a spec.
But before that, the work is often producing the specification.
That was why I stayed so loyal to one particular Claude session.
Throwing away the conversation felt like throwing away part of the design process with it.
Compaction hits and remembers a lot. Just not always the stuff I wanted it to.
Claude compaction remembered plenty. Problem is, most of it was stuff I could’ve just fetched from the code anyway -_-
Compaction is actually pretty good.
It remembers architecture, files, functions, classes, commands, implementation details...
Stuff. Stuff. Stuff.
But the thing is:
I can find most of that again.
Forget a filename?
Search.
Forget a chunk of code?
Open the file and read it.
Forget what an API returns?
Open the client.
Forget what component owns some behaviour?
Follow the imports.
Annoying sometimes, sure.
But recoverable.
The things I got much more nervous about losing were things like:
- “We already tried that. It didn’t work.”
- “No, don’t change that.”
- “I changed this requirement.”
- “This option was explicitly rejected.”
- “This weird thing is intentional.”
- “Protect the mobile behaviour.”
- “Port this, don’t invent a new version.”
- “I approved this once, not forever.”
- “Do not put that thing back.”
Those aren’t just technical facts.
They’re decisions.
Corrections.
Rules.
Negative knowledge.
And code is weirdly bad at preserving negative knowledge.
Git can tell you what exists.
A diff can tell you what changed.
Neither can reliably tell a fresh Claude:
“This thing is absent because I explicitly asked for it to be removed. Please don’t helpfully put it back.”

That’s the stuff I actually cared about.
So I built a handoff and compared it with compaction
I stopped asking “what happened in this chat?” and started asking “what do I need to safely start a fresh Claude session and keep going?”
The handoff had a different job from compaction.
I didn’t need another meeting summary.
The actual question was:
What do I need to safely start a completely fresh Claude session and continue this work?
That pushed very different things into the handoff:
- current state
- human instructions
- decisions
- corrections
- rejected alternatives
- failed approaches and why they failed
- literal errors worth remembering
- open questions
- one-off approvals
- external actions that shouldn’t happen twice
- weird repo or runtime discoveries
- the next thing to do
- exact working locations when rediscovering them would be annoying
I initially had this very pure rule:
If it can be rediscovered, don’t store it.
Sounds smart.
Turns out rediscoverable doesn’t mean free.
Making another Claude spend five minutes rediscovering the same stupidly named entry point every session isn’t intelligence.
It’s cardio.
So the rule became:
Store things based on the cost of losing them.
And then I compared the handoff against compaction.
For the first benchmark, I did this manually.
I went through the original session myself and wrote down 46 things I would genuinely care about the next session knowing.
Not random nouns.
Actual decisions, instructions, blockers, working commands, corrections, weird discoveries, current state.
Then I verified that all 46 actually existed in the source session and checked both artifacts against them.
The result:
| Compaction | Handoff | |
|---|---|---|
| Important facts carried | 31 / 46 | 43 / 46 |
| Coverage | 67% | 93% |
That was enough to convince me there was something real here.
I later ran broader and repeated tests too, and the exact numbers move depending on how you score the artifacts.
The general pattern didn’t.
Compaction was smaller and more efficient.
The handoff preserved more of the stuff I actually wanted a fresh session to inherit.
That was great.
Unfortunately, the way I was generating the handoff was completely stupid.
My first solution was to ask Claude to self-revive
I waited until Claude was basically out of context and then asked it to do one last massive context-heavy job to save itself. This went about as well as you’d expect.
The first architecture looked roughly like this:
long engineering session
↓
near context ceiling
↓
PreCompact / session-end
↓
read transcript
inspect repo
check live state
collect failures
write handoff
audit handoff
↓
clear
↓
start againThe handoff quality was good.
The problem was that I was generating it at possibly the worst time imaginable.
A proper handoff wasn’t one model response.
It involved:
- multiple transcript passes
- thread inventory
- repo inspection
- live Git state
- checking failures
- collecting gotchas
- figuring out what was still open
- auditing the final thing
A single run could take roughly six minutes.
And because this was all happening inside the main engineering session, every tool call was dragging the giant parent context along with it.
In one run I measured roughly 325k of context per request.
So the mechanism I had built to rescue my giant context was repeatedly carrying the giant context around while trying to rescue it.
Lovely.
There was another, more fundamental problem.
I was trying to trigger this around PreCompact.
But when PreCompact fires, the session is already sitting right near the boundary.
There isn’t suddenly a magical extra pile of tokens available for my handoff.
So the session would start trying to produce this huge checkpoint...
...while still sitting at the context ceiling.
And then hit the boundary again.
Blocking compaction didn’t help either.
Blocking doesn’t free context.
Clearing does.
At one point a single rollover attempt triggered nine blocked events, six of them inside 33 seconds.
So I had effectively asked Claude to revive itself while it was already being counted out.
The architecture itself was wrong.
The dying session shouldn’t be responsible for saving itself.
So I outsourced it.
So I hired a contractual Claude
Instead of making the main Claude write its own obituary, I spawned a temporary employee whose only job was to read the session and write the handoff.
The new idea was simple.
When the handoff needed to be created, spawn a completely fresh Claude process.
The engineering session keeps doing engineering.
The contractual employee gets one job:
Read this session and produce the handoff.
That solved the headroom problem immediately.
The main session no longer needed enough spare context to perform its own rescue operation.
But now the temporary employee had a different disadvantage.
The main Claude had been living inside this conversation all day.
Its context was already there.
A lot of that prefix could benefit from cache reuse.
The fresh Claude walks in on day one like:
hello sir where documents
and has to understand the whole thing from scratch.
At smaller contexts, not terrifying.
At very large contexts, suddenly you’re paying a fresh worker to ingest an enormous session just so it can summarise it.
The cost started getting a little too spicy for my Indian brain.
More importantly, I had solved where the handoff gets written.
I hadn’t solved when.
I was still doing something weird:
1. Let a massive session happen. 2. Wait until the end. 3. Ask someone else to reconstruct everything important afterward.
Which led to a much more obvious question.
What if the handoff writer just watched the whole thing live?
Why wait 300k tokens to remember something Claude already knew ten seconds after it happened?
Information doesn’t arrive as one giant transcript.
It arrives one turn at a time.
A decision happens.
Then an error.
Then some feedback.
Then we realise an assumption was wrong.
Then I reject an approach.
Then something gets fixed.
Why wait until the very end and reconstruct all of that?
So the handoff stopped being something generated at the end.
It started becoming live workstream memory.
A cursor keeps track of how much of the session the handoff worker has already seen.
Every few turns, the worker gets:
current handoff + only the new transcript since the last cursor
and answers one question:
What changed?
Maybe:
- I gave a new instruction.
- We made a decision.
- We corrected an old assumption.
- An approach failed.
- A blocker disappeared.
- Something became the new current state.
- Nothing important happened at all.
Then it updates the handoff and advances the cursor.
It originally ran more aggressively.
Right now the cadence is around every eight turns, with whatever is left over flushed when the session reaches the boundary.
So now the boundary isn’t:
300k session → reconstruct entire life story → hope we finish
It’s closer to:
work → handoff stays mostly current → work → handoff stays mostly current → boundary → flush small residue → clear → restore → continue
The expensive archaeology mostly disappears.
And if the session dies unexpectedly?
The handoff is already there.
Maybe it’s missing the last few turns.
That’s very different from having no checkpoint at all.
This was much closer to what I actually wanted.
Then I looked at what each tiny handoff worker was carrying around.
Because apparently I enjoy finding new problems.
Turns out my tiny memory worker came with a whole moving truck
The worker’s job was basically “read this delta and update memory.” Naturally I had also given it an entire coding-agent toolbox it did not need.
Every time I spawned the handoff worker, it came with its own context too.
System instructions.
Strict rules.
Skills.
Tool definitions.
MCP tools.
All the normal scaffolding you’d expect around a coding agent.
Except this wasn’t really a coding agent anymore.
Its entire job was:
1. Read the current handoff. 2. Read the new transcript delta. 3. Decide what durable state changed. 4. Update memory.
In one measurement, tool definitions alone added roughly 25k tokens of context.
For a worker that absolutely did not need to open Chrome.
So we stripped it down.
No browser.
No giant MCP toolbox.
No Bash playground.
No reason for it to behave like a full coding agent.
The worker gets only what it actually needs:
handoff-writing / memory rules + current handoff state + new transcript delta
And instead of letting the model directly mutate the file however it feels like, it returns structured operations:
ADD discovery UPDATE current state SUPERSEDE old decision RESOLVE open question ADD human instruction
Then normal deterministic code applies those changes.
Claude decides:
What does this new information mean for memory?
The script handles:
- locking
- cursors
- validation
- atomic writes
- actual mutation
Much cleaner split.
And it gets rid of a lot of context that had absolutely nothing to do with maintaining memory.
The funny thing is that as the system evolved, the memory worker became less agentic, not more.
Turns out not every problem improves when you give Claude seventeen tools and a browser.
So what does the whole thing look like now?
Claude works normally. Every few turns, a tiny memory worker catches up the handoff. When the context finally fills up, there is almost nothing left to save: flush the last few turns, /clear, reload the handoff, keep going.
The final architecture is much less dramatic than where I started.
Main Claude session
│
│ works normally
│
├── every ~8 turns
│ ↓
│ read transcript delta
│ since last cursor
│ ↓
│ memory worker
│ ↓
│ update live handoff
│
│
▼
context approaches ~300k
│
▼
PreCompact fires
│
├── anything after last cursor?
│ │
│ ├── yes → flush remaining delta
│ └── no → nothing to write
│
▼
/clear
│
▼
/session-start <workstream>
│
├── read handoff
├── verify live repo/runtime state
└── continue workingSo most of the time, Claude is just Claude.
The memory worker quietly keeps the handoff close to current while I work.
When the context window finally runs out, we are not asking a 300k-token session to suddenly remember its entire life.
We’re only catching up whatever happened since the last memory update.
Then the context gets wiped.
A fresh Claude loads the same workstream and continues.
That’s basically the whole trick.
The context window can fill up.
The chat can disappear.
The session can change.
The work doesn’t restart.
Okay, but why not just write a spec?
If the decisions have already converged, please just write the spec. The annoying part is when building the thing is how you’re discovering what the spec even is.
This is the obvious objection.
Why carry all this evolving workstream state?
Just write a proper spec.
Then every fresh session gets the spec.
Done.
And honestly?
If the feature is already well-defined, I agree.
If I have:
- a stable PRD
- finished design
- settled architecture
- clear constraints
- tasks that decompose cleanly
then please don’t drag one giant Claude conversation around like a family heirloom.
Write the plan.
Start fresh sessions.
Delegate narrow jobs.
Persist the output.
Move on.
But sometimes you just don’t have that leverage yet.
Sometimes we’re still figuring out the feature by building it.
The UI changes the product decision.
The product decision exposes an API limitation.
The API limitation changes the flow.
The flow changes the responsive behaviour.
The rendered thing changes my mind.
A spec written halfway through that process can become outdated almost immediately.
Eventually the decisions converge.
Then the handoff can become a proper spec.
But before they converge, I want something that preserves the moving state of the work, not something pretending the work has already stopped moving.
And this ended up solving a much bigger problem than compaction.
Because if the workstream state doesn’t belong to one specific chat anymore...
I can:
- start a completely fresh Claude session
- continue work days later
- come back weeks later
- move the work to another session
- split one workstream into multiple sessions
- potentially branch different directions from the same checkpoint
The handoff isn’t just a compaction backup.
It starts becoming the thing that makes the session interchangeable.
The context window was never the session
I started this because I wanted more context. Turns out what I really wanted was confidence that I could lose all of it and still keep working.
This was the actual shift.
I used to treat these as basically the same thing:
- context
- conversation
- session
- project memory
They’re not.
Context is working memory.
It’s whatever Claude needs in its head right now.
The transcript is history.
It’s the raw record of what happened.
The repo, Git, tests, browser and runtime are live truth.
They tell me what currently exists and whether it actually works.
The handoff is workstream state.
It carries the glue the others don’t naturally preserve:
- what we decided
- what we rejected
- what already failed
- what I explicitly instructed
- what got corrected
- what is still unresolved
- what shouldn’t happen twice
- what matters right now
- where the next session should continue
Once I separated those things, the context window became a lot less precious.
More context is still useful.
Obviously.
But I no longer need the biggest possible context window just because it exists.
I can work inside a bounded context.
Persist the expensive state outside it.
Then throw the entire context away and start clean.
Which is funny.
I started all of this because I wanted a bigger context window.
I ended up building the ability to break through it.
A session can end.
A chat can change.
A context window can disappear.
The workstream doesn’t have to.
The setup
Hooks, the skill, the commands and the reports, with a README covering install and the parts that are still unsolved.
Download the kit ↓More writing
Enjoyed this one? Here’s what else I’ve been writing.
Similarity isn’t memory
The hard part about AI memory isn’t storing everything. It’s remembering the right thing at the right time.
Agents are the easy part
Agents are easy to start. The hard part is everything around them.
One person, every discipline
A few years ago, one person trying to do all of that would have sounded unrealistic. Now, with AI, it’s just… Tuesday.