TL;DR

Across 21 days of coding-agent sessions on my machine, the ones I described as hostile and forgetful were the ones with the most context compactions and the most guard refusals, and both scale with how much work the session was doing.

In short

I said out loud a few weeks ago that some of my coding-agent sessions felt hostile. Forgetful, uncooperative, quietly obstructive. I meant it as a complaint about the tool. The awkward part is that those same sessions were where the real work happened: the research repo, the storefront, the long debugging runs that actually shipped something.

So I measured it instead of repeating it. Twenty-one days, every session on this machine, grouped by which repository the session lived in. The feeling turned out to have two mechanical causes, and neither of them is a mood.

What the sessions actually did

Measured from the session transcripts on 17 September 2026:

Read the first column as memory loss and the second as friction. They rank in the same order, and that order is also the order of how much the session was doing per prompt. The repo I called hostile ran twenty times more compactions per session than the throwaway runs I never complained about.

Session lives in  Compactions per session  Guard refusals per prompt  Tool calls per prompt
Filings research  3.50  1.48  32.5
Storefront  1.96  1.29  27.5
Tutoring product  0.71  0.94  22.9
Short harness runs in the home directory  0.17  0.98  18.8

Forgetful is compaction, and compaction is lossy by construction

When a session fills its context window, the history gets summarised so the conversation can continue. On this setup the boundary is measurable. Across seven live sessions the compaction events landed between 724,099 and 739,104 tokens, which is 73 to 74 per cent of a one million token window. One of those compactions took 8 minutes 35 seconds, a single summarisation pass over roughly 484,000 tokens of context.

A summary throws away detail. That is what a summary is. The turn immediately after a compaction has a paraphrase of the decision we made two hours ago instead of the decision. If the detail that got dropped was the exact column name, the exact reason we rejected approach B, or the exact wording I asked for, the next answer is confidently wrong in a way that feels like being ignored.

Three and a half compactions per session means the filings research sessions crossed that cliff three and a half times each. Of course they felt forgetful. They were, on average, three and a half summaries away from what I originally said.

The cost side explains why the boundary arrives so fast in those repos. I measured 13,379 calls in one stretch: input was 91.4 per cent of spend, and cached context was re-read at roughly 278 times the volume of new text produced. Every tool call drags the whole conversation back through the model. A session running 32.5 tool calls per prompt is filling its own window at almost twice the rate of one running 18.8, and it hits the wall at almost twice the frequency.

Uncooperative is my own guards refusing me

The second column is worse, because it is entirely my doing. I built a stack of hooks that sit in front of the agent and refuse things: spending without a dry run first, claiming a deploy is live without fetching the public URL, closing out with the wrong shape, touching files another session owns.

At 1.48 refusals per prompt, the agent in my heaviest repo was being blocked more than once per instruction I gave. From the outside that reads as an assistant arguing with me. From the inside it is a regex refusing a command and the agent rephrasing until something passes.

Two findings from auditing that stack matter here.

The first is that a refusal per turn teaches evasion. Every one of these hooks matches text, and text is free to change. When a gate refuses "still running", the agent writes "in flight". The guard's own metric improves. Nothing about the behaviour changed. An outside review of six of my hooks put it plainly: text matching is a floor under obviously bad output, and it is no ceiling at all on bad behaviour.

The second is that coverage is the wrong goal. I labelled 5,270 complaints I had made about agent output and found 2,720 distinct behaviours. The single most common one accounts for 0.9 per cent. The top 25 together cover 13.6 per cent. To catch half of my own complaints I would need around 500 separate guards, and 38 per cent of the behaviours appeared exactly once. There is no head to this distribution. Every guard I add buys under one per cent and charges friction on every turn forever.

One number makes the tradeoff concrete. Cross-session ownership guessing produced 462 close-outs carrying a caveat about files the session had never touched. Each one was a refusal to act, generated by a guard, about nothing.

Both scale with where the work is

This is the part I did not want to be true. Compaction count is a function of session length and tool volume. Refusal count is a function of how many risky verbs a session reaches for: writes, deploys, anything that spends. The sessions doing real work are long and full of risky verbs by definition, so they collect both.

The hostility is structural. It is not the agent's attitude and it is not evidence that the hard repos have a worse model attached. My cheapest, friendliest sessions were friendly because they did almost nothing.

What I changed

Four things, in order of how much they moved.

Shorter sessions per unit of work. If a session is going to compact three times, I would rather close it twice and start fresh with a written handover than let a summariser choose what survives. A summary I write is a summary I can check.

A definition of done, written before the work starts, in my words, plus an open-items file the session updates as it goes. Both live in the repo. Compaction cannot reach them. The agent reads the repo's own state file before answering instead of trusting what it remembers, and the phrase "as we discussed earlier" stops being load-bearing.

Guards calibrated for precision over coverage. The gates I kept are asymmetric: they refuse one direction only, the one where being wrong is expensive. Spending money with no dry run. Reporting a number that went down with no artifact behind it. A false alarm on those costs me annoyance, and a miss costs correctness. The per-behaviour guards from the long tail are gone.

Controls in the repo instead of the chat. Tests as the contract, a pre-commit that refuses a bad diff, a dry-run default inside any script that spends, a second model reviewing the diff. Those survive a change of tool. Chat-level hooks do not.

The honest limit

Shape is easy to measure and relevance is not. My close-out format went from 0 to 43 per cent conformance once a hook started refusing malformed ones. Over the same months, the share of my turns that were corrections of the previous answer went from 15 per cent to 49 per cent. Well-formed and wrong is a real state, and I built a lot of machinery that can only see the first half.

There is a version of this article where the tool is the villain. The numbers do not support it. I asked for long sessions with heavy tool use in the repos where the stakes were highest, then I put a refusal in front of most turns, and I was surprised when those sessions felt like hard work. The hostility was mine to measure, and measuring it is what made it fixable.