A month ago I wrote about my living PLAN.md getting fat and the skill I built so I’d never have to compact it by hand again. 28,357 words down to 8,332. I was pleased with myself. I wrote a whole section called “It’s a Process” about how this is maintenance, not a one-time fix, and then I went right back to not thinking about it.
Two weeks ago I was working on the supplement wiki project and typed something at Claude that I’d been thinking for a while: the tasks file is huge.
It was 758 lines. About 686 of them were work that was already finished. And every one of those lines gets handed to the agent at the start of every session whether anyone is reading it or not.
And the skill I’d built for exactly this problem had no idea what to do with it, because I had only ever taught it about the other file. The living-plan skill, which I built to keep a project’s PLAN.md and TASKS.md small enough to actually load every session, knew how to cool a bloated PLAN.md. Point it at a bloated TASKS.md and it would happily apply the wrong medicine, because I had never noticed these are two different diseases. (The skill used to be called plan-rebalance. It isn’t anymore, and this post is part of why.)
- Two Files, Two Completely Different Ways to Get Fat
- The Tell: A File That Contradicts Itself
- The Eviction Rule (and Why It Can’t Be Automatic)
- The Two Carve-Outs, or How I Nearly Threw Away the Good Part
- Move It With sed, Not With Your Hands
- What 758 Lines Looks Like at 100
- Fix the File, Then Fix the Rule That Let It Happen
- One More Thing Before the Confession
- To Be Honest
- What’s Next
Two Files, Two Completely Different Ways to Get Fat
If you haven’t read the first post, the setup is: one PLAN.md holds decisions, open questions, and a session log, and one TASKS.md holds the current batch of committed work that an implementer agent actually executes. Planner writes the first one. Executor writes the second. The skill that owns both docs is open source now, in my agent-skills repo.
PLAN.md bloats because decisions accumulate. Every architectural call gets appended, forever, and the fix is what I call cooling: figure out what nothing active references anymore, move the body out of the main doc into docs/, leave a one-line pointer behind. Reference plus a foundational flag, not recency, because an old load-bearing decision is rarely mentioned and would get wrongly archived by a recency test.
TASKS.md bloats for a reason that looks almost like the opposite. The discipline was working. The rule in my AGENTS.md said: flip each piece’s checkbox as it completes, and when the whole queue is done, collapse it to one summary block under a stable [tag]. Both of those happened. The file was full of neat, correctly collapsed, correctly tagged blocks of finished work.
They just never left.
Check, collapse, evict. I had built two stages of a three-stage rule and called it a discipline. A collapse-only rule doesn’t stop a file from growing without bound. It just makes the growth tidy.
So the skill got a new route instead of a shared one, and the detect step now measures both files:
# size proxy for both docs (NOT live context %, which the agent can't read reliably)
wc -w PLAN.md TASKS.md 2>/dev/null
# how much of the task doc is already finished?
grep -c '^\s*-\s*\[x\]' TASKS.md 2>/dev/null; grep -c '^\s*-\s*\[\]' TASKS.md 2>/dev/null
That second line is the actual change in thinking. For PLAN.md the nudge is a word count, defaulting to 15,000. For TASKS.md a word count tells you nothing useful, because a task file can be legitimately long during a big batch. The signal is a ratio : when finished material is over roughly 60% of the file, say so out loud. “TASKS.md is 758 lines, about 90% of it completed work” is a sentence that justifies surgery. “TASKS.md is 9,000 words” isn’t.
The Tell: A File That Contradicts Itself
Here’s the diagnostic step I’d never have thought to write down if I hadn’t hit it: look for the file arguing with itself.
The strongest evidence a doc has stopped being read is that it contains two statements that can’t both be true. In this file, the deferred-work queue listed a research pass as upcoming. The status header, about thirty lines further up, said that same pass had completed two months earlier.
Those entries don’t get archived. They get deleted. Archiving a lie just relocates the lie somewhere I’m even less likely to read it.
The Eviction Rule (and Why It Can’t Be Automatic)
A collapsed block leaves TASKS.md when both of these hold:
- Every task in the block is checked off. If one task in it is still open, the block can’t move as a unit. Split it: the finished tasks move out, the open one gets promoted to its own live item.
- Nothing still open points at it. An open item that says “see the FIX-6 notes” pins those notes in place until it’s either resolved or rewritten to point at the archive.
The obvious next thought is to fire this automatically when a queue drains. Don’t. Two reasons, and the second one took me a minute to see.
Condition 2 is a judgment call. “Does anything still open depend on this” is a question about intent, and it lives in the planning thread, not in a grep.
And the implementer agent must never do this while it’s working, because it would be editing the file that holds its own run state. TASKS.md is where the executor logs which piece it’s on and whether it stopped clean or blocked. That’s the recovery record for a killed session. An agent that relocates half of that file mid-run is sawing the branch it’s standing on.
What never leaves, no matter how big the file gets: open checkboxes, the open-items list, the deferred and pre-launch queues, the active queue and its run-state note, and the status header.
The Two Carve-Outs, or How I Nearly Threw Away the Good Part
“Move the finished stuff to an archive” sounds complete. It isn’t, because some of what’s buried in a finished block isn’t history at all. It’s operational knowledge that the next pass needs, and dropping it into an archive nobody opens is a silent loss. It looks tidy. It’s data destruction with good manners.
Two things get lifted out before anything moves.
Carve-out 1: reusable lessons. The test is one question. Would the next person doing a similar pass need this? If yes, it’s a lesson, not history. Out of those done blocks came 112 lines of stuff like the project’s canonical three-part definition of done, and the blunt note that Amazon is dead as a prose source. None of that is a record of what happened. All of it is what to do next time. It went to docs/references/lessons.md.
Carve-out 2: artifact inventory. I found that the collapsed done blocks were the only place recording which backfill scripts existed and what each one did. That’s not a task log, that’s documentation that had accidentally been filed under history. It became a 19-row table in pipeline/README.md, next to the code, where somebody might actually find it.
Everything else moves verbatim.
Move It With sed, Not With Your Hands
Halfway through this run, I confidently concluded that pipeline/README.md didn’t exist. It existed. It was right there. A cd inside a Bash call persists , and an earlier cd into a subdirectory was silently redirecting every later relative-path check I ran. That’s twenty minutes I’m not getting back. The workflow now says to use absolute paths or re-anchor with cd <root> && on every command, and I’ve since hit the same thing in two other sessions, so apparently I needed it written down.
That detour aside, the actual move: 732 lines had to relocate. There is a strong temptation to have the agent read the old file and write a nice clean new one.
Don’t do that, for two reasons.
The first is fidelity. That material was already collapsed once. Summarizing it again strips detail the author (me, earlier, with more context on the problem) deliberately chose to keep. Re-summarizing a summary is how a project quietly forgets things.
The second is cost. Retyping 700 lines means those 700 lines go through the model twice, in and out, for a job that is fundamentally cat. That’s a pile of context burned to accomplish nothing but a move.
So: back the file up first, then assemble the archive by piping line ranges with sed -n 'A,Bp'. Verify the seams afterward with a heading grep on the result rather than reading the whole thing back.
What 758 Lines Looks Like at 100
What survived: the status header, the blocked design pass with its visual-pass items and a note about a CSS stopgap, five open items, the deferred and post-launch queues, and an eight-row Done index. That index is a table with one row per [tag], what it was, its dates, and a link into the archive. Eight rows replacing about 686 lines.
The part I didn’t expect: the sweep found work. Reading the file carefully enough to decide what could leave surfaced two open items that had never been tracked anywhere. A sub-piece of the symptom work that was quietly incomplete, and 74 noisy relationship edges in the graph that I’d noted in passing and then lost in the pile. Both got promoted to live open items instead of being archived with the block they were buried in.
Which is its own argument for doing this. A file too big to re-read isn’t just heavy. It’s actively hiding things from you.
Fix the File, Then Fix the Rule That Let It Happen
The bloat wasn’t drift. It wasn’t the agent misbehaving. It was a rule with a missing stage. Process Rule 3 in AGENTS.md said check, then collapse, and stopped. The agent followed it perfectly. Fixing only the file guarantees you’ll be doing this again in six weeks with more lines.
So the rule got rewritten to state all three stages explicitly, with the eviction gate spelled out, both carve-outs named with their destination files, and an explicit “what never leaves” list. Then the skill got the new route and the workflows/evict-tasks.md file, the docs index got the two new files, and the run went into the ledger with its dates and line counts so the next run doesn’t re-derive any of this.
If you rebalance a doc without changing the rule that let it bloat, you haven’t built a system. You’ve signed up for a recurring chore.
One More Thing Before the Confession
Eight lines of skill, and it has nothing to do with task files. It’s about the other doc. I’m putting it here because the lesson underneath it is the same one this whole post is about: what an archive promises.
Cooling a decision uses reference, not recency: if nothing active points at it, the body moves to docs/decisions/ and a one-line pointer stays behind. There was already one marker that overrides this, 🔒 foundational, for load-bearing decisions that are rarely referenced but must never leave.
Then I hit a case the model didn’t cover. A decision can be genuinely cold by reference, nothing points at it, and still describe work that is decided but not built yet. Cooling its body is correct. Cooling it silently is not, because the decisions index is the thing I skim, and an index that reads as “here are the settled decisions” quietly implies they’re all done.
So there’s a second marker now. 🔨 on the pointer heading means decided-but-unbuilt, which makes the decisions index double as a pending-build list. 🔨 still cools, unlike 🔒. It just flags status, and it comes off when the work ships. The line that settled it, from the ledger: “if there’s a designator that says they’re unbuilt, that’s enough.”
The general lesson is the same one the task file taught me. When you archive something, ask what the archive is promising. The decisions index was promising “decided” and I was reading it as “done.” The task file was promising “managed” and it meant “stored.”
To Be Honest
I have run this exactly once. One project, one file, 758 lines to 100. That’s a real result and it is not a validated heuristic. The 60% ratio threshold is a number I picked because 90% was so obviously past the line that anything below it seemed safe to start at.
And here’s the genuinely embarrassing part.
content-tools-v2 is the project whose 28,357-word PLAN.md kicked off this entire series. Its TASKS.md is sitting at 1,056 lines and 10,938 words as I write this. It has finished blocks from session 35, a halted block whose opening note says “do not pick this up,” and a shelved block marked superseded by decision D27, which has been sitting there since before I wrote the eviction workflow. Its PLAN.md is back up to 16,408 words , which is over my own 15,000-word nudge threshold. I compacted that file to 8,332 words in June and to 4,700 on July 1st.
So I built the workflow on a different project, it cut 87% off a real file, and I have not run it on the repo that most obviously needs it.
What’s Next
Run it on content-tools-v2, obviously, and find out whether a task file shaped by a different kind of project breaks the rule I just wrote. A halted arc and a superseded shelf are exactly the ambiguous cases the two-condition gate was supposed to handle, and I don’t fully trust it until it’s survived something it wasn’t designed against.
After that, the thesis post I keep threatening to write. Planning is the only thing in the stack worth a boutique model, everything downstream is keystrokes you can buy in bulk, and the plan is only cheap to enforce if the docs holding it stay small enough to read. Which, it turns out, the docs do not do on their own.





Top comments (0)