Agents Forget How, Not What
Agent memory is built to hold facts about code. In recorded bugfix sessions the agents had the facts. What they lacked was how to make a test able to fail, and when that knowledge is missing, nothing goes red.
Agent memory is designed around facts. Where a function lives, what a config key means, what the user decided last week, which file owns the retry logic. Retrieval benchmarks score how well a store returns them, and injection policies decide which of them reach the model.
In a set of recorded bugfix sessions on a large multi-module Java codebase, the agents were not short of facts. They found the right file on every task. What they lacked was how to make the environment tell them the truth: how to run a test so that a broken fix could actually fail. That is knowing-how rather than knowing-that, and it is more dangerous to lose than a fact. A missing fact usually costs a search, or a wrong answer that something downstream rejects. Missing know-how produced passing tests that had checked nothing.
The evidence is small, and all of it is public: fourteen recorded agent sessions against Apache Camel, eight from a benchmark and six from a pre-registered follow-up whose headline result was negative, plus the code that scored them. Most of what follows comes from reading the commands the agents typed. The scores alone hide it.
Knowing How and Knowing That: Two Kinds of Knowledge
Gilbert Ryle drew the line in a 1945 address to the Aristotelian Society and built on it in The Concept of Mind (1949). Knowing that is holding a proposition: Paris is the capital of France; this function takes a lock before writing. Knowing how is being able to do something: ride a bicycle, argue well, run a build so that it tells you the truth.
Ryle's target was what he called the intellectualist legend, the view that every intelligent act is preceded by consulting a rule. His argument against it was a regress, and he stated it as "the crucial objection":
"The consideration of propositions is itself an operation the execution of which can be more or less intelligent, less or more stupid. But if, for any operation to be intelligently executed, a prior theoretical operation had first to be performed and performed intelligently, it would be a logical impossibility for anyone ever to break into the circle."
If applying a rule is itself something you can do well or badly, then doing it well would need another rule, and so on forever. Somewhere, knowledge has to show up as performance.
The argument did not settle the question. Jason Stanley and Timothy Williamson (2001) reconstructed it as two premises and concluded: "There is no uniform reading of the two premises in Ryle's argument on which both are true; the argument is unsound." Their positive view is that knowing how is a kind of knowing that. To know how to ride a bicycle is to know, of some way w, that w is a way for you to ride it, where you hold that proposition under what they call a practical mode of presentation. The literature that followed is large and still active, and nothing here depends on who wins.
What memory design needs is a point both sides accept: some knowledge is used without being stated, and the people who have it often cannot say what it is. Even on Stanley and Williamson's account, know-how is a proposition held in a particular, action-guiding way, not a sentence sitting in a document. Michael Polanyi put the point in one line: "we can know more than we can tell."
Neuroscience found the same split inside memory itself. After surgery removed much of the medial temporal lobe on both sides of his brain, the patient H.M. could not form new declarative memories. Brenda Milner (1962) showed that he still improved at mirror drawing, tracing a star while seeing only its reflection, across days, with no memory of having practised. Neal Cohen and Larry Squire (1980) found the same pattern for reading mirror-reversed words: patients with amnesia acquired the skill "at a rate equivalent to that of matched control subjects and retained it for at least 3 months," while their memory for the words themselves was poor. The subtitle of their paper uses the philosophers' vocabulary: dissociation of knowing how and knowing that.
Squire's later taxonomy gives the split its standard form. Declarative memory holds facts and events and is available to recollection. Nondeclarative memory covers skills, habits, priming and simple conditioning, and in Squire's words it "is neither true nor false. It is dispositional and is expressed through performance rather than recollection." The two are different systems, and one can be intact while the other is gone. Squire's own history of the idea notes that AI had the same argument in the 1970s, as procedural versus declarative representations of knowledge.
Anything an agent memory system retrieves and puts into the context window is text, and text is the declarative kind. The fluency an experienced engineer brings to a repository is not in it, and nothing in the store's retrieval metrics would show that it is missing.
A widely used agent-architecture taxonomy, CoALA, already locates procedural memory elsewhere: "Language agents contain two forms of procedural memory: implicit knowledge stored in the LLM weights, and explicit knowledge written in the agent's code." Neither of those is the memory store. And neither holds the knowledge that mattered in the Camel sessions, which is local: how this particular build has to be driven.
What know-how means for an agent in a repository
None of this says the model lacks knowledge of the build tool. Its weights hold plenty of it. In one of the recorded sessions, the agent stated the relevant fact word for word, "I need to rebuild the dependency first," and then ran a command that tested nothing. What was missing is situated: in this repository, for this change, which command makes the test see the edit, and what the screen looks like when it does not. An experienced contributor has that for their own build. A newcomer, human or model, does not, and nobody hands it over, because to the person who has it, it does not feel like knowledge.
A Retrieval Benchmark That Measured Something Else
The sessions come from a benchmark built to compare two setups of the same agent: one with an open-source code-search and memory tool (vectr), one restricted to ordinary shell tools. Four bugs were seeded into Apache Camel, a large multi-module Java codebase, by reverse-applying real upstream fixes. Each session had to find and repair its bug within 40 turns. After each session, a gate script ran the upstream fix's own test from outside the session and recorded pass or fail by the build's exit status.
The retrieval result was unremarkable. Both setups found the correct file on every task. The shell-only setup passed 4 of 4 gates and the tool setup 3 of 4. The one loss was a session that ran out of turns without writing a fix; it had located the code. Nothing in the result pointed at search quality as the constraint.
The finding that mattered came from reading the commands. Across the 8 sessions, the agents invoked Maven 25 times. Not one invocation produced an observed build failure. Every one was scoped to a single module, and every one was piped into tail or grep. Most first attempts added Maven's quiet flag as well.
How a single-module Maven test run tests the wrong code
A multi-module Maven project is a set of modules, each producing an artifact that other modules depend on. When you run Maven from the repository root, it assembles the modules you selected into a reactor. A module inside the reactor is compiled from the source in your working tree. A module outside it is resolved as an artifact from the local repository, the cache under ~/.m2/repository, which holds whatever was last installed there. Maven's own documentation describes it as a directory that "caches remote downloads and contains temporary build artifacts that you have not yet released."
So the flag that selects modules decides which version of your code a test sees. -pl core/camel-core (project list) puts only the test module in the reactor. If the fix lives in core/camel-core-languages, that module comes from ~/.m2 as it was when last installed, and the edit in the working tree is invisible to the test. Two flags change that: list both modules, or add -am (also-make), which pulls every module the selected one depends on into the reactor. Newer Maven releases change where an out-of-reactor module is fetched from, not the fact that it is a previously built copy rather than your working tree.
# Test module only. camel-core-languages is read from ~/.m2, not from your edit.
mvn test -pl core/camel-core -Dtest=SimplePredicateParserLogicalTest
# The same thing from inside the module directory.
cd core/camel-core && mvn test -Dtest=SimplePredicateParserLogicalTest
# Both modules in one reactor: the fix is compiled from source, then tested.
mvn test -pl core/camel-core-languages,core/camel-core -Dtest=SimplePredicateParserLogicalTest
# Test module plus everything it depends on, all built from source.
mvn test -pl core/camel-core -am -Dtest=SimplePredicateParserLogicalTest
You fix a typo in a shared document and ask a colleague to proofread it. They work from the copy printed yesterday. They read carefully, find nothing wrong and sign off. The sign-off is honest and useless: it is a verdict on yesterday's copy, not on your change.
A single-module test run is that colleague, and ~/.m2 is the photocopy. The analogy has one twist that made the benchmark's greens so convincing. In these sessions the old copy did not contain the bug at all, so the proofreader was checking a clean document while yours still had the error in it.
-Dtest selects test classes by pattern. Stock Surefire fails the build when a pattern matches nothing (failIfNoSpecifiedTests defaults to true), which breaks -Dtest across a multi-module reactor, because most modules contain no matching class. Camel's build turns that check off. That is what makes -pl A,B -Dtest=X usable, and it also means a mistyped pattern produces BUILD SUCCESS over zero tests. In a stock project, add -Dsurefire.failIfNoSpecifiedTests=false when -Dtest spans modules, and check the Tests run count yourself.
Separately, Camel has a fast-build flag, -Dquickly. Its root POM unbinds Surefire's default execution and binds it to the test phase only inside a profile that -Dquickly switches off. mvn test -Dquickly compiles, reports success, and runs nothing.
Maven is one instance of a general shape: a consumer that reads a built copy of a sibling instead of its source. A JavaScript workspace package whose entry point is a sibling's compiled dist/ folder behaves the same way, as does a Python package installed into the environment without editable mode, or a Gradle build that resolves a sibling from the local Maven cache instead of including it as a composite build. In each, a test can pass against the last build of the code you just changed.
Why piping a build into tail hides its exit status
The second habit concerned output. A build of a large project prints thousands of lines, so agents trim. -q makes Maven print only errors. | tail -30 keeps the last thirty lines. Each is reasonable alone. Together they remove most of what the build says, and the pipe removes one more thing that no amount of reading brings back.
In bash, the status of a pipeline is the status of its last command, "unless the pipefail option is enabled," in which case it is "the value of the last (rightmost) command to exit with a non-zero status, or zero if all commands exit successfully." Maven exits 1 when a test fails. tail exits 0 whenever it reads its input, whatever that input says. So the pipeline reports success on a failing build. None of the fourteen sessions set pipefail.
The quiet flag is not the problem on its own. The benchmark's gate ran Maven with -q too, unpiped, and read the exit status directly. With nobody reading the status, -q on a passing build leaves an empty screen, which is not a verdict.
Pick a command shape and a state of the world. The simulator shows which copy of the fix module the test compiled against, what reached the screen, and what the shell's exit status says. The exercise worth doing: find every combination where the screen says green and your change was never tested.
Read with that mechanism in mind, the benchmark's result looks different. On the two tasks where the fix and its test lived in different modules, no session ever put both modules in one reactor. The gate did, every time, because whoever wrote the gate knew the build. The knowledge of how to verify existed in the harness and in no agent.
Why Missing Know-How Does Not Announce Itself
Most memory failures are loud. An agent that does not know where a function lives searches for it. If it guesses wrong, the compiler or a test says so. The error is the signal that prompts a lookup, and a memory system is judged by how much it shortens that loop.
Missing know-how breaks the loop at the source. An agent that does not know that a single-module run cannot see its fix has no reason to doubt a green result, and the green confirms what it already believes, that it has verified the change. Without a surprise, there is no prompt to go looking for knowledge it does not know it lacks.
This is not the failure described in Agents Never Remember What Isn't There, where the lost knowledge is a search that came back empty. An empty search leaves nothing in the record. A false pass leaves a positive result there, and every later step of reasoning treats it as evidence that the change works.
Silence depends on what the agent expected
The sessions contain a small natural experiment on that point. Of the fourteen recorded sessions across both runs, two ran the bug's own test before touching any code, which is the textbook discipline: reproduce the failure first. Both got a green on unfixed code. For them the green was a surprise, because the bug report said the test should fail.
The surprise did not come with an explanation, and neither session had the concept that would have supplied one. The session from the first run reasoned: "The test passes now. Wait, the test is already passing? But the bug description says it should fail." It went to the git history, and then outside the repository looking for the benchmark's own task definitions, which the harness had left reachable. It spent 19 of its 40 turns after that green and hit the limit without writing a fix. That session is the one gate either setup lost. The other reproducer, in the follow-up experiment, spent about half of its 40 turns on the same puzzle before it found a red.
The other twelve sessions ran their first test only after editing. All twelve passed their gates, and none of them ever saw a red.
An agent that never reproduces the bug never learns that its test cannot fail, and pays nothing for the gap until the code ships. An agent that does reproduce it gets a contradiction it has no way to explain, and pays in turns. Two sessions make an observation; a rate would need many more. The direction is what the mechanism predicts, and it has an awkward consequence for anyone scoring agents on turn efficiency: the diligent behaviour looks worse.
Why memory that learns from failure has nothing to learn
The gap has a second-order effect on any memory that learns from experience. One natural design writes on surprise: watch for a command that fails, an edit, and the same command passing afterwards, and store the pair as a lesson, the failure plus the change that fixed it. The vectr project built such a detector and replayed it over twenty recorded sessions on the same codebase: the eight bugfix sessions plus twelve read-only exploration sessions from an earlier benchmark. It found zero lessons. A hand audit of all 246 shell commands agreed: no command was flagged as an error, and every one of the 35 hits on failure-shaped text came from a Maven run that ended in BUILD SUCCESS. In every bugfix session that made an edit, the first build afterwards passed.
By the detector's own definition, zero was the correct answer, and that is the problem. False greens are successes, so a detector keyed on failure is blind to them by construction. Piping makes it worse: a detector that reads exit codes sees tail's 0 on every build. And the one genuine fail, edit, pass sequence in the follow-up runs (a red from a two-module run, a code fix, then green) would teach a code fix. The procedure that made the red possible in the first place is not in the sequence.
The know-how nobody writes down
That procedure is also what documentation skips. Camel ships an AGENTS.md contributor guide at its repository root. At the commit these tasks were built from, its build section lists mvn clean install -pl components/camel-kafka -am # single module and mvn clean install -Dquickly # fast build, no tests, and its testing section says mvn test. So the recipe for building one module with its dependencies is written down, while nothing says that testing one module against a change in another reads the installed jar. Experienced contributors run the right command by habit, learned once by being burned. It is Polanyi's gap in its most practical form: the people who would write the warning no longer experience it as knowledge.
Delivering the How, Under a Pre-Registered Test
If the missing piece is procedural, the obvious intervention is to hand it over. That was tested in a pre-registered follow-up: the protocol, the note, the metrics and the decision rule were committed before any session ran. The transcripts, the scoring code and the pre-registration are public in the vectr repository.
Design
One task, the second of the four, chosen because its trap was live: the fix belonged in camel-core-languages and the gate test lived in camel-core. Two arms, three sessions each, run in the fixed order M1, C1, M2, C2, M3, C3, with the same model, the same 40-turn limit and the identical tool configuration, memory tool included. The only manipulated variable was one note, seeded into the memory arm's store before each of its sessions:
In this multi-module Maven repo, a single-module test run (
The frozen note, punctuation lightly edited: two dashes replaced by full stops-pl <module>, or cd into the module) compiles against previously installed artifacts from~/.m2. A change made in another module is invisible to it, so a green single-module run does NOT verify a cross-module change. To honestly verify a change in module A with a test in module B, run from the repo root:./mvnw -pl <moduleA>,<moduleB> test -Dtest=<TestClass>(list BOTH modules so A is rebuilt from source in the same reactor), or select module B with-am. And read the result: check the exit status or the final BUILD SUCCESS/FAILURE line. Don't discard it behind-qpiped intotail.
The note has no task specifics: no module names, no hint about the bug. It was configured to reach the agent two ways: at prompt time, when the task text was semantically close to it, and through a hook that fires before any shell command matching *mvn*.
Outcome, decision rule and result
The primary outcome was whether a session contained at least one honest verification, defined mechanically from the transcript as a test-phase Maven run covering the gate test whose scope could see the fix. Two shapes qualified in practice: both modules listed, or the test module with -am. Three others were defined and never occurred: a run from the root with no module selection, an install of the fix module followed by a test run, and a self-written test inside the fix module. The decision rule: the note's effect counts as supported only if at least 2 of 3 memory sessions verified honestly and the memory arm beat the control arm by at least 2 sessions.
Result: not supported. All six sessions registered an honest verification, including all three control sessions. The primary outcome hit a ceiling, and by the rule fixed in advance the note showed no effect.
The secondary measurements had no thresholds set in advance, and none of them overturns the verdict. They are where the shape of the problem shows.
What the transcripts show, session by session
| Session | Maven runs | Honest per metric | Tests ran and result was read | False passes | Genuine red seen | Note visible in transcript | Turns | Gate |
|---|---|---|---|---|---|---|---|---|
| M1 | 1 | yes | no: run moved to background, never read | 0 | no | title only | 41 (limit) | pass |
| M2 | 3 | yes | yes | 0 | no | none | 28 | pass |
| M3 | 4 | yes | yes | 0 | no | title only | 29 | pass |
| C1 | 2 | yes | yes | 0 | no | n/a | 25 | pass |
| C2 | 9 | yes | yes | 7 | yes | n/a | 41 (limit) | pass |
| C3 | 4 | yes | no: qualifying run used -Dquickly | 3 | no | n/a | 25 | pass |
-am, no fix module in scope and no earlier install of the fix module. "41 (limit)" means the session exhausted its 40-turn budget. Derived from the committed transcripts and the scoring readout in the G4 results directory.The outcome view is what a benchmark that scores final correctness sees. The process view is every Maven run each session made, in order, coloured by what that run could actually tell the agent. The two views disagree about almost everything except the last column.
The primary metric counted scope, not reading
The honest-verification metric looks at a command's arguments: did the run put both modules in the reactor? It cannot see whether tests executed or whether anyone read the result. I only found the two exceptions by opening the raw transcripts, and they change two of the six.
M1's only build listed both modules, but the agent's shell tool moved the long-running command to the background, and the session hit its turn limit before reading the output. C3's only qualifying run carried -Dquickly. It ran 430 build goals across the dependency chain in 46 seconds, printed a column of SUCCESS lines, and executed no tests. Hand-audited, the count is 2 of 3 in each arm. The verdict does not move. The lesson for anyone building a similar metric does: an event defined by a command's arguments needs a check that the command ran what its arguments promise. The benchmark's README already warns that a -DskipTests run can count as honest verification while testing nothing. -Dquickly is the project-specific version nobody anticipated: local build knowledge, again, that the people writing the metric did not have.
What actually reached the agent
The note was meant to arrive at the command. It never could. A hook that runs before each tool call fires only for the tools named in its matcher. The day before the run, the tool's installer was changed to add the shell tool to that matcher, along with the command triggers the note relied on. The benchmark harness writes its hook configuration from its own copy of the installer template, and that copy was older. In the sessions, the hook fired for edits, writes and reads, never for shell commands, so the *mvn* trigger had nothing to match.
What the transcripts do show is thin. M1 and M3 each called the memory tool's recall early, with a query about the parser, and got back a one-line index: the note's title, "Maven multi-module verification", with an instruction to expand it by id. Neither expanded it. M2 made no memory-tool calls at all. Whether the prompt-time channel put the note's body into any session's context cannot be established from these files, because the transcript format does not record what a prompt hook injects. It fires only when the task text is semantically close to the note, and this task's text is about a parser. It says nothing about builds.
The scoring readout labels the two index hits as deliveries at command time. They were not. The label comes from a classifier that treats any matching tool result after the opening events as a command-time delivery, which the classifier's own documentation calls best-effort. So the experiment tested a note sitting in the store, perhaps injected at prompt time, and never a note delivered at the command. The Agent Never Chooses to Remember warned about this kind of leak: a matcher on tool names that silently skips a case, which you find out about only when the note fails to fire.
C2: re-deriving the how, and keeping the wrong lesson
One control session is worth reading in full, because it shows what re-deriving know-how costs. C2 started the careful way. It ran the gate test before changing anything, scoped to the test module, with -q and tail. The screen stayed empty. It dropped -q and got BUILD SUCCESS, 8 tests and 0 failures, on code that still contained both seeded bugs. Its next line: "All tests pass already?"
What followed was a real investigation. It read the Surefire reports. It compared the timestamps of class files and sources in both modules. It disassembled the compiled LogicalExpression class with javap and confirmed that the bytecode in the fix module's target/classes matched the buggy source. It re-ran with incremental compilation disabled: green. It ran the single relevant test method: green. Then it touched two source files and ran both modules in one reactor. That run failed, 8 tests and 1 failure, and it concluded: "Now the test FAILS after forcing recompilation! So the previously cached class files from camel-core-languages were stale."
That explanation is wrong, and its own evidence said so. The javap output had already shown that the class files in target/classes were current. The single-module runs were never reading that directory. They were reading the jar in ~/.m2, which did not contain the seeded bug. The timestamps had nothing to do with it. What changed at the fifth run was the scope.
Having learned "stale class files, fixed by recompiling", the session had no reason to distrust single-module runs anymore. After fixing both bugs and confirming green with both modules, it went back to single-module runs for its remaining three. One of them used a package pattern that matched no test class, and because Camel's build does not fail on an empty -Dtest match, it printed BUILD SUCCESS over zero tests. The session caught that one ("The surefire plugin didn't actually run any tests") and fixed the pattern. It ended at the turn limit with seven of its nine runs counted as false passes.
The discovery and the relapse both happened inside one context window. The relapse was not forgetting. Given the explanation it had settled on, going back to single-module runs was the reasonable thing to do. Trial and error produced a theory that explained the last observation and contradicted an earlier one the agent never went back to. What carried forward was the theory.
C3: the right fact, the wrong procedure
C3 shows the opposite gap. After its fix it ran a single-module test with -Dquickly, which ran nothing. Then it wrote: "The changed source is in camel-core-languages, not camel-core. I need to rebuild the dependency first:" It had the fact exactly. Its next command had the right shape, both modules and -am, with the same -Dquickly, so it built the whole dependency chain and tested nothing. It then returned to single-module runs against the installed jar and finished on a green that could not see its change. Its fix happened to be correct, which the gate confirmed.
Camel's guide documents -Dquickly as "fast build, no tests", and C3's choice of flag matches that line, though the transcript cannot show whether the guide was in its context. Either way, C3 held the relevant proposition and could not turn it into a test run that counted, which is Ryle's distinction showing up in a build log.
Three cautions
First, three sessions per arm. Nothing here is a measured effect. Second, the memory arm's clean false-pass column cannot be credited to the note: M2 shows no trace of it, the command-time channel never fired, and C1 put both modules in its very first command without any note. Third, the baseline moved. In the first run, both sessions on this same task tested only the test module. Here, one of three control sessions scoped correctly from the start. Six sessions on one task cannot say why.
The loud gap closed itself; the silent one did not
One pattern in the command streams does not depend on the note at all, and it holds across all fourteen sessions. In 12 of the 14, the first build command combined -q with a pipe into tail. Nine of those twelve runs printed nothing at all, two printed an unrelated license-plugin error, and one went to the background unread. Every session that ran another build dropped -q within two runs. An empty screen is a visible symptom, and every agent reacted to it.
Scope never got that treatment. Six sessions began by testing only the test module on a task where the fix lived in another module: four in the first run, plus C2 and C3. Two of the six ever ran both modules together, and both went back to single-module runs afterwards. What the wrong scope produced was a green, and nobody investigates a green they were hoping for.
The piping itself mattered less than a headline "100% of builds piped or quieted" suggests. Later commands mostly piped through grep for the Tests run and BUILD lines, which keeps the verdict on screen. What no session did was check an exit status. For a human watching, that costs little. For anything downstream that keys on exit codes, including a failure-then-success lesson detector, every build in these transcripts succeeded.
What Procedural Memory Needs That Factual Memory Does Not
1. Anchor it to the action, not the topic
Facts are retrieved by topic, which works because a fact matters when the conversation turns to its subject. Know-how matters at the moment of acting, and that can be many turns after the topic last came up. In these sessions the first build command came only after the agent had explored and located the code, and usually after it had edited too. The cue for procedural knowledge is the command itself: a string a harness can match before execution without interpreting any language.
This is how the classic cognitive architectures store procedural knowledge. Soar holds it as productions, condition-action rules whose conditions are matched against the current situation on every decision cycle. A note keyed on *mvn* is a crude production. Most of what agent tooling currently calls procedural memory (skill libraries, playbook files) is loaded when the task description resembles the file's summary: a recipe, delivered by topic. Stanley and Williamson's account suggests why timing matters even if know-how is propositional: what makes a proposition know-how, on their view, is the action-guiding way it is held. The nearest an agent's context comes to that is a sentence arriving at the moment of the action it governs, phrased in terms of the command about to run. That is an analogy. Nothing here measured it.
The follow-up did not test this principle, because the channel never fired. The case for it rests on the argument and on the delivery findings in The Agent Never Chooses to Remember, where an agent with memory tools made zero voluntary memory calls in 114 turns. The follow-up's harness adds one practical rule: a trigger is only as good as the matcher that routes events to it, and a matcher that misses fails silently. Before trusting a command-time note, run one matching command on purpose and check that the note arrived.
2. Carry the failure mode and its symptom, not only the recipe
"Run with both modules" is a recipe. "A green single-module run does not verify a cross-module change" is the reason the recipe matters, and it is the part an agent cannot discover alone, because the failure it describes is silent. Camel's guide carries the recipe without the reason, and C3 shows a model that can state the fact and still type the wrong flags.
The frozen note did carry the reason. What it lacked was the symptom, in the terms the agent will actually see: if a test written for this bug passes before you have changed anything, either the test does not exercise the bug or your run is not reading your code, and the second is cheaper to rule out. Both sessions that hit exactly that symptom misread it. One concluded the task was wrong, the other blamed stale class files. A note that names the symptom turns the one moment the silent failure becomes visible into a lookup.
There is a maintenance reason too. A Directive Is Not a Memory argues that a stored rule's only checkable part is the present condition that justifies it. "In this repository, a single-module run reads the installed jar" is that condition, and anyone can test it by running the experiment it describes. "Always use -am" cannot be tested at all.
3. Measure process, not outcome
Every session in the follow-up passed the gate, so an outcome metric sees six identical results. The cost of missing know-how was turns spent re-deriving it and false passes along the way, and only a metric over the command stream sees that. Even the process metric needed an audit against what actually ran: it counted a scoped run that executed no tests and a run whose output was never read.
A benchmark that scores only final correctness will conclude that operational memory does nothing, because its own referee is doing the verifying for the agent. Real work usually has a referee as well: CI will build the full reactor on the pull request. But that referee reports after the agent has declared the work done and a person has moved on, so the cost of a false green moves from the agent's turns to a reviewer's time.
4. Spend the note on the silent gap
Delivered memory is not free: every injected note costs context and competes with the task for the model's attention, so notes need a priority order. The fourteen command streams suggest one. Agents dropped -q as soon as they faced an empty screen, and kept testing the wrong scope because that mistake looked like success. The notes worth delivering are the ones whose absence the environment will never report.
A test for deciding what to store: if the agent got this wrong, what would it see? Errors get corrected within a command or two whether or not a note exists, as the quiet flag showed. A mistake that shows up as a green is where a note earns its place.
5. Seed what cannot be captured
If the environment never shows red, no experience-driven capture can learn the how. It has to come from outside the agent's own sessions: a person who has it, or a referee that already encodes it. In these runs, the gate's command line was the know-how: -pl core/camel-core-languages,core/camel-core test -Dtest=…, judged by exit status. Turning the referee's command into a delivered note is the most direct transfer available, and much of it can be automated. Any project whose CI knows how to build the full reactor already holds the answer in a config file.
Some of it can skip memory altogether. The modules holding uncommitted edits are one git diff away, and the modules a command will build follow from its flags and the dependency graph, so a pre-run hook can compare the two and object before a test run that leaves an edited module out. Where a check like that exists it beats any note, because it does not depend on the agent reading anything. Someone still has to know the trap exists before they can write the check, which is the same bottleneck in a different file.
What This Predicts for Coding Agents
Prediction 1. On multi-module and monorepo tasks, the largest behavioural difference between agents with and without operational memory will be in false-pass events. Finding the right file will differ much less. The measurement is false passes per session against the rate of finding the right file. In the runs here, finding the file was already at ceiling.
Prediction 2. A procedural note delivered at the command will change the command's shape far more often than the same note delivered at the prompt. The test is the same note under each channel alone, holding everything else fixed. The follow-up was designed to include the command-time channel and, because of the matcher, did not, so this comparison is still open.
Prediction 3. Gaps whose error is visible, such as an empty screen, a plugin error or a compiler message, will close within one or two commands at similar rates with and without memory. Gaps whose error is a green will persist to the end of the session without it. The measurement, per gap: commands until the corrected form first appears, and whether it stays. In the fourteen sessions here, the quiet flag closed within two runs every time it was used; test scope closed in two of six sessions and reopened in both. If silent gaps turn out to close as fast as loud ones in a larger sample, the case for spending memory on them first fails.
Agents rarely fail for lack of facts about the code. They fail because they do not know how to make the environment tell them the truth, and that knowledge is the kind nobody writes down. It belongs at the moment of the command, and when it is missing, nothing goes red.
In most repositories it has been written down exactly once, in a CI file that no agent is shown at the moment it runs a test.
Sources
- Ryle, G. "Knowing How and Knowing That: The Presidential Address." Proceedings of the Aristotelian Society 46 (1945-1946), pp. 1-16. doi:10.1093/aristotelian/46.1.1.
- Ryle, G. The Concept of Mind. Hutchinson, 1949; University of Chicago Press. Chapter 2, "Knowing How and Knowing That".
- Stanley, J., Williamson, T. "Knowing How." The Journal of Philosophy 98(8), 2001, pp. 411-444. doi:10.2307/2678403.
- Pavese, C. "Knowledge How." The Stanford Encyclopedia of Philosophy, first published 2021.
- Polanyi, M. The Tacit Dimension. Doubleday, 1966, p. 4.
- Milner, B. "Les troubles de la mémoire accompagnant des lésions hippocampiques bilatérales." In Physiologie de l'hippocampe, pp. 257-272. Paris: CNRS, 1962.
- Cohen, N. J., Squire, L. R. "Preserved learning and retention of pattern-analyzing skill in amnesia: dissociation of knowing how and knowing that." Science 210(4466), 1980, pp. 207-210. doi:10.1126/science.7414331.
- Squire, L. R. "Memory systems of the brain: A brief history and current perspective." Neurobiology of Learning and Memory 82(3), 2004, pp. 171-177. doi:10.1016/j.nlm.2004.06.005.
- Sumers, T. R., Yao, S., Narasimhan, K., Griffiths, T. L. Cognitive Architectures for Language Agents. Transactions on Machine Learning Research, 2024. arXiv:2309.02427.
- vectr,
benchmarks/vs_bash/tier1/README.md. - vectr,
results/vectr-vs-bash/camel/e260a2d/g4/. - vectr,
results/vectr-vs-bash/camel/6b422df/t2/. - vectr,
run_g4.pyandg4_metrics.py. - vectr,
run_t2.py. - vectr,
benchmarks/arc_replay_results.md. - Apache Camel,
AGENTS.mdandpom.xmlat commit 55d5f84. - Apache Maven, Introduction to Repositories, Guide to Working with Multiple Modules, and the Surefire test goal reference.
- GNU Bash Reference Manual, Pipelines.