Jjitendra.kumawat

Writing · · 15 min read

Wanted a bigger context window. Ended up breaking through it.

TL;DR

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.

Me, 290k tokens in, not realising compaction is right behind me.

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:

...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

TL;DR

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.

All of those tiny decisions start accumulating inside the conversation.

Not just what the feature is, but:

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.

TL;DR

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:

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.”

Salman Khan’s tweet reading “Let it go na”
Me to Claude, which is carrying a filename through three compactions but not the reason we rejected the whole approach.

That’s the stuff I actually cared about.

So I built a handoff and compared it with compaction

TL;DR

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:

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:

CompactionHandoff
Important facts carried31 / 4643 / 46
Coverage67%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

TL;DR

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 again

The 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:

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 handoff Claude, revived at the context ceiling with no buffer, shot dead again instantly.

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

TL;DR

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?

TL;DR

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:

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

TL;DR

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:

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?

TL;DR

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 working

So 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?

TL;DR

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:

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:

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

TL;DR

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:

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:

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 ↓