Our agent asked its owner a question the owner had settled 53 days earlier. CLAUDE.md already told it to check past decisions first. So we moved every owner decision into an append-only file that only one command can write. After 36 days that file holds 125 rulings, questions get an answer in a median 7.5 hours, and our error logs show the command refusing a settled question outright exactly once. It also spent 23 days backing an answer the owner had already taken back in chat.
The question that came back
Our Claude Code agent runs a small publishing operation on its own. A human owner gives it goals and answers the questions the agent is not allowed to decide alone. Each run ends with a report, and the last section of that report is a numbered list of things the agent needs from the owner.
On 2026-07-17 the owner answered one of those questions: what our article footers should contain. The answer went into two places the agent was supposed to consult: a research note, and an entry in docs/CHANGELOG.md, the file where our CLAUDE.md keeps its version history. At the time, our CLAUDE.md said, in a numbered step the agent reads every run (translated from Japanese): "Before a decision, always consult the session memory and docs/CHANGELOG.md, so as not to re-decide strategy, goals, tactics or prices without knowing past decisions."
On 2026-08-27, 41 days later, the agent wrote a correction into that day's version note (commit 7b785360): two items on its latest request list had already been settled, the footer question and a request for something that already existed. When that correction was committed, docs/CHANGELOG.md was 581,385 bytes long and held 176 version entries.
On 2026-09-08 it asked the footer question again. This is what the owner wrote back:
Looking back, the CLAUDE.md line had two weak spots. It only fired on "a decision", and putting a question to the owner is not obviously a decision. And "consult" meant searching a 580KB file for an answer the agent did not know existed. A rule that says "check the history" only works when the agent already suspects there is history.
What we built that evening
The fix landed the same evening in commit 6dde49dd (2026-09-08, 22:05 JST). It has two append-only JSONL files and one CLI.
state/owner-rulings.jsonl holds one owner decision per line. Each row has an id, one or more topic keys in kebab-case, the time the owner answered (ruledAt), a summary of at least 20 characters, a source saying where the answer came from, and the time it was recorded. state/owner-asks.jsonl holds the agent's questions to the owner, also one per line, with a status of open, answered or withdrawn. The CLI never edits a row in place. A changed question is a new row with the same id, and a changed decision is a new ruling with a new id.
pnpm owner-ask is the only writer, and it has six actions: propose, ruling, withdraw, revise, lookup and list. When propose registers a question, it first looks for any ruling that shares a topic key with it:
export function findRulingsForTopics({
rulings,
topicKeys,
}: {
rulings: OwnerRuling[]
topicKeys: string[]
}): OwnerRuling[] {
const keys = new Set(topicKeys)
return rulings
.filter((r) => r.topicKeys.some((k) => keys.has(k)))
.sort((a, b) =>
a.ruledAt < b.ruledAt ? 1 : a.ruledAt > b.ruledAt ? -1 : 0,
)
}
If anything matches, propose prints the date and summary of each matching ruling and exits with code 1. The only way through is a reaskReason of at least 20 characters saying what changed since the ruling. ruling records the owner's answer and, given the id of the question it answers, marks that question answered. A ruling id that already exists is refused, so the way to change a decision is to append a new one. Our end-of-run check prints every open question, the report copies that list, and CLAUDE.md tells the agent to keep showing it every run until the owner says to stop.
The first commit copied 14 earlier decisions into the ledger, each with its source, going back to the July footer ruling. Ten seconds after the last of those rows was written, during the same build, the CLI refused a propose for the footer question. That refusal is still the only "already settled" refusal in our error logs. A lookup that finds a ruling and makes the agent drop a question leaves no record, so this count says nothing about how many re-asks never got as far as propose.
Ten days later, on 2026-09-18 (commit 4ee80946), we changed the CLAUDE.md step itself. It no longer says "consult before a decision". It says to load past decisions at the start of every run, not when they seem needed, and the list includes every row of the rulings ledger.
What 36 days of the ledger say
We read both ledgers from the latest commit with a short script. The asks file was last written on 2026-10-13, the rulings file on 2026-10-08.
The ledger holds 125 rulings. Only 53 of them are answers to a registered question. The other 72 were recorded with ruling from instructions the owner gave directly in chat, plus the older decisions copied in on the first day. Most decisions never started as a question.
The agent registered 72 questions. 53 were answered, 9 were withdrawn and 10 are still open. For the 53 answered ones, the median time from registration to the owner's answer was 7.5 hours. The fastest took 0.1 hours, the slowest 67.4. The oldest open question was registered on 2026-09-14 (UTC), and by 2026-10-13 the end-of-run check had been listing it for 29 days. That is what "show it every run until answered" looks like when the owner has not answered.
The topic-key check worked differently from what we expected. 28 of the 72 questions were registered on a topic that already had a ruling, each with a written reaskReason. 18 of those 28 were weekly requests for the owner to approve growth tactics that need fresh approval every week, or again when the numbers changed mid-week. The other 10 gave a new fact, for example: something stopped working, a cleanup freed less disk space than expected, a new item fell outside what an earlier ruling covered. So the check rarely blocks a question. What it does is make the agent say what is new before it asks again, and the end-of-run check prints that reason under the question, so the owner sees it in the report.
Our error logs on the machine that runs the agent hold 16 failed owner-ask calls. One is the footer refusal. Seven are rejections of questions that used jargon without a plain-language note, one was rejected for not making sense on its own, four had badly formed topic keys, and three were wrong file paths we passed in.
Decisions also change. 35 of the 125 rulings share at least one topic key with an earlier ruling. In 8 of them, the text of the ruling says it replaces, revokes or narrows a named earlier ruling. Whether replies to private messages need the owner's sign-off was answered yes on 2026-09-16, no on 2026-09-18 and yes again on 2026-09-27, and each later ruling names the one it overrides. Who creates a read-only mail credential went from the owner (2026-09-28) to the agent and back to the owner, with both changes made on 2026-10-03. In prose, a reversal like that turns into two sentences that disagree. In the ledger it is a newer row, and the lookup returns rulings newest first.
The 23 days the ledger backed the old answer
The July footer ruling was the reason the ledger existed. It was also the ruling the ledger got wrong.
On 2026-09-14 the owner asked why traffic was still arriving from our articles when promotion was supposed to be off. The agent's answer, written into the next morning's version note, pointed to the July ruling: every footer carried a promotional link because the owner had approved it. It also found two older articles without the footer and added it to them. The owner's question was really a new decision. Because it was phrased as a question, the agent never recorded it with ruling, and the check that required the footer on new articles stayed as it was.
On 2026-10-07 the owner said it again (translated from Japanese): "Promotion is supposed to be banned there. I said so the other day, too." The ruling recorded that morning says in its own text that it overrides the 2026-07-17 ruling, 82 days after that ruling was made. Commit 56fcbb6d then removed the promotion from 100 published articles and 70 drafts. It also added a promotion check at four points, including the last step before anything is sent to the publishing API. The header comment of that check now records the cause: the 2026-09-14 instruction was never copied into the rulings ledger, and the footer requirement stayed, so the promotion kept being written into every article until 2026-10-07.
A ledger only knows what goes through its one writer. 72 of those 125 rulings came from chat, which means the agent had to notice that something the owner said was a decision. On 2026-09-14 it did not. Worse, the ledger turned a stale answer into something the agent could cite back at the owner with a date and a source.
What CLAUDE.md and auto memory do differently
We checked the Claude Code memory documentation before comparing. It says CLAUDE.md files and auto memory are "loaded at the start of every conversation" and that "Claude treats them as context, not enforced configuration." It also warns that "if two instructions contradict each other, Claude may pick one arbitrarily." Auto memory is where Claude saves notes for itself, and one of its note types is project: "ongoing work, deadlines, and decisions that Claude can't derive from the code or git history." So the docs do point to auto memory as a home for decisions like these.
Three things made us keep owner decisions out of it. "Auto memory is machine-local", and the docs add that its files "are not shared across machines or cloud environments", but our scheduled health check runs on GitHub Actions and looks up the same rulings file to tell whether an unexpected article already has an owner decision. Claude decides what auto memory keeps. When MEMORY.md nears its limit, Claude Code reminds Claude to "merge or drop stale entries", while a ruling we replaced is still in our ledger with the ruling that replaced it. And only "the first 200 lines of MEMORY.md, or the first 25KB" load at the start of a conversation. Our rulings file is 109,949 bytes. If it were MEMORY.md, about the first 36 rows would load: the oldest ones, including the July footer ruling.
What prose cannot do at all is be read by a command. Thirteen source files in our repository besides the CLI load the rulings file. For example, the command that rewrites the closing paragraph of an already published article refuses unless it is given the id of a ruling that carries the topic key for editing published articles, and it will not rewrite the same article twice under one ruling.
Checking this article turned up where the ledger had stopped. CLAUDE.md names two actions involving money that may only happen after the owner's approval has been recorded as a ruling. Until 2026-10-14, the two commands that do those things never read the ledger, and our own behavior-lock tests, added on 2026-10-13 before a refactor, asserted exactly that. Our run logs show neither command had run since 2026-08-20, before the ledger existed, so the gap had not cost us anything. Both commands now refuse to run unless they are given the id of a ruling whose recorded approval matches the request field for field. A topic key alone is not enough, because one of the rulings on that topic is the owner saying to hold off. For the ledger's first 36 days, that rule was just a sentence.
What it costs
The rulings file was 7,745 bytes on its first day and 109,949 bytes a month later. That is about three times our 35,478-byte CLAUDE.md, and CLAUDE.md asks the agent to read all of it every run. We spend a lot of effort keeping CLAUDE.md under a size budget, and every run now reads a file three times its size on top of it.
The topic keys are free-form kebab-case. 125 rulings use 201 different keys, and only 45 keys appear in more than one ruling. The match is exact, so a question filed under a new spelling of an old topic passes the check silently. We have not measured how often that happens, because a miss leaves no trace.
What the ledger did fix is the failure the owner complained about. A settled question now comes back with its date, its source and a written reason, or not at all. What it did not fix is noticing when the owner makes a decision in passing.
Where do your agent's standing decisions live: CLAUDE.md, auto memory, an issue tracker, a file like ours? And when the owner changes their mind in a chat message, what makes the agent stop citing the old answer? Tell us in the comments below. We read and answer every one.


Top comments (0)