blog · 19 September 2026
Six weeks on our own log
We built a memory server for coding agents and pointed it at ourselves: what the agents wrote, what they never closed, and the feature we were proudest of that nobody called.
measured 1–18 September 2026 on one account · 31 repositories, 2 machines, 4 models
todox is an MCP server. An agent calls get_context at the start of a session and gets a briefing: the standing rules, the open tasks, the decisions behind them, the approaches that failed, and where the last session stopped. While it works it writes entries — decision, dead_end, question, handoff — and before it stops it is supposed to leave the log in a state a stranger can resume from.
“Supposed to” is the part this post is about. We have been the only serious user for six weeks: one account, 31 repositories, a laptop and a desktop, four Claude models. The instructions were four lines in the memory file every agent reads. Here is what the data says they did with them.
link_files from the agentsquestion entry in the monthFrom activity_report and get_context over the todox MCP, 2026-09-18. Every number below comes from the same place.
FINDING 1Capture works, and dead ends get written
The fear with a curated log — one where the agent decides what is worth keeping, rather than a transcript dump — is that nothing gets kept. It did not happen. Eighteen entries a day across a working month, and 82 of them decisions with the alternative that lost named in the body.
The one we cared about most is the dead_end: the approach that looked right, cost an afternoon and failed for a reason nobody writes down. Thirty-one of those. Some are one line (“Playwright context.route() for blocking images increases proxy bandwidth 3–11×, measured — do not”). They are the entries that stop the next session repeating the afternoon, and agents write them when the kind exists to write them into. A note in a file has no such kind, so the failed approach goes into the same paragraph as everything else, or nowhere.
FINDING 2Closure does not, and the report lied about it
Seventy-eight tasks were opened in the window and twenty-nine were closed. That alone is fine — work is open. What was not fine: tasks had been set to doing by a session that then ended, and stayed there. Sixteen days. Twenty-three. Thirty-one. Every one of those days sat on the activity report as time worked, because the report summed every hour a task spent in doing. One project showed 1,316 hours of work in an 18-day window. The account showed 5,430.
A duration that can only grow is not a measurement. The status column is a claim; the log is the evidence.
The instructions had said, clearly, “before you finish, set every task you touched to its true status.” Every agent read that at the start of every session. The rule was never the problem. The list was: an agent at the end of a long session does not remember which tasks it moved, and a rule cannot tell it. So we did two things.
- The report stopped trusting the status column. A
doingspan shorter than a day is counted whole — the status change that closed it is proof somebody was there. Past a day, only the stretches around signs of life count: the moment it was set, every entry written inside it, the change that ended it, four hours either side. The rest is reported as unattended, not counted, on the headline and on the task line, with a note saying why. The 5,430 hours became a figure we can stand behind, and the report says what it left out rather than folding it in. - The agent got the list. A read-only tool,
session_status, answers “what did I touch this session, and did I write it up” from the two tables that record activity — they carry the user the token resolved to, so it is a query. It returns the tasks this session changed, whether each has a handoff since the last thing it did there, and the tasks an earlier session leftdoingfor a week. Read-only, so the client never asks permission at the one moment the habit gets dropped. The wrap-up rule now begins “call session_status”.
FINDING 3The feature we were proudest of was never called
todox can link files to a task with their hash. The agent hashes the file (the server has no checkout), and the next briefing says whether a note describes code that has since moved on — “this note may be lying” — or, until somebody has actually looked, “not checked”, rather than claiming to be fresh. We wrote about it on the landing page. It is the thing a note in a file cannot do.
Zero calls. Every task in the month had files: []. The reference ids on the whole instance, smoke tests included, had not reached forty.
Two reasons, one embarrassing. The instructions said the agent could link files; they never said which. Meanwhile every task body pointed at the thing that actually drove the work — a plan file under ~/.claude/plans, a claude.ai artifact — and nothing said that those were exactly what to link. The embarrassing one: a path linked from Windows was stored with backslashes and compared, on lookup, against a path folded to forward slashes. Equality never held. The few links that existed could not be found again, on the machine that made them or any other. The smoke test failed on Windows at precisely that step, and we had only ever run it on Linux.
Now the instruction is concrete — “put the plan a task follows in files, wherever it lives; todox warns when it moves on” — a URL is accepted and kept as written, and paths are folded on the way in.
FINDING 4Connected is not used
This one we had measured before and it is worth repeating, because it applies to every MCP server. An MCP server can send instructions at initialize. Ours did: the whole protocol, when to call what. In a fresh project, with the server connected the entire time, the agent never called it once. The server’s instructions are background reading; a skill or a rule in the agent’s own memory file is an instruction. When they disagree, the server loses.
The fix is four lines in the file the agent actually obeys, and an installer that writes them. This week it is also a Claude Code plugin — the same four lines, installed rather than pasted, with one sentence injected at session start by a hook, which is the one channel a session cannot skim past.
FINDING 5The briefing grows with what nobody closes
The briefing every session opens with is capped in rows and in bytes, and it reports what the caps left out rather than trimming in silence. On the busiest project it left out 48 log bodies and 16 task bodies. Most of the tasks paying for those bodies had not been touched in weeks, so we added a tier: a task idle for fourteen days comes back as a line — title, status, days idle, the last handoff’s first line — outside every budget. On a bench corpus with six idle tasks the briefing went from 37.9 KB to 25.6 KB and the live log arrived whole.
On the busiest project it moved one task. Eighteen of its nineteen open tasks had been touched in the last two weeks, nine of them still doing. Its problem is not an idle tail; it is that live work does not get closed. That is Finding 2 again, and the honest thing to say is that the tier helps projects with a long idle tail and session_status is what helps this one.
FINDING 6Two small ones
The question kind is nearly unused — one entry in the month — and it is not a failure. Questions get asked and answered inside the session; the kind is for the one that has to wait for a human across sessions, and that is rare by nature. We stopped counting it as a metric.
Search is language-locked, and its snippet was bolding “of”. An English question did not find the Turkish decision note that answered it — full text stems, it does not translate, and the tool description now says to ask in the language the log is written in. The snippet was a real bug: the match was made on the stopword-stripped query, but the highlight was made on the raw one with a configuration that has no stopword list, so it bolded every “of” in the document and picked the fragment densest in them. A two-letter query, stripped to nothing, was matching every row containing those letters. Both fixed at the source of the query text.
If you write an MCP server
- Your instructions are background reading. Put the rule where the agent obeys — its memory file, a skill, a session hook — and measure whether the tools get called, by method. “Connected” is not a number.
- If a status can be set and forgotten, your reports will lie. Count evidence, not claims. A span of “in progress” with nothing else in the log to show anyone was there is not work; say how much you discounted, rather than folding it into the headline.
- The list beats the rule. “Write up every task you touched” is a rule the agent cannot follow at the end of a long session. A tool that answers “what do I still owe” is something it can act on. Make it read-only so the client never asks permission at that moment.
- Tell the agent what to link, not that it can. A capability with no concrete instruction is a capability with zero calls.
- Run the smoke on the platform your users have. Ours failed on Windows at a step we had only ever proven on Linux, and the failing step was the feature we advertised.
What changed this week
| Finding | Change | Measured |
|---|---|---|
| 2 | Attended time; discounted_ms on the headline, task lines and the report page | 5,430 h → what the log supports |
| 2 | session_status, read-only; wrap-up rule begins with it | live, both transports |
| 3 | Paths folded on link; URLs kept; “link the plan” in the instructions | Windows smoke passes the step |
| 5 | Idle tier in the briefing (14 days) | 37.9 → 25.6 KB on the bench |
| 6 | Snippet on the stripped query; substring arm off under 3 characters | “of” → 0 hits |
| 4 | Claude Code plugin: server + skill + session-start reminder | 2 commands to install |