Engineering6 min read

Your agent knows your preferences. It just never uses them

Commit style rules never surface when the agent commits, because retrieval is keyed on the conversation and the rule shares no words with the task. The fix: tag rules to the action they govern and fetch them the moment the agent performs it.


There is a way for agent memory to fail that is worse than forgetting, and it took me a while to even recognize it as a failure. The preference is sitting right there in the store, and if I ask the agent what my commit style is, it answers correctly: single line, conventional prefix, no emojis. But the moment it actually fixes a bug and commits, the message comes out four lines long with an emoji in the subject, because knowing a rule and using it at the right moment turn out to be completely different problems.

Nothing here is technically broken, which is exactly why it hides so well. Memory lookups are driven by the conversation: embed what the user said, find similar memories. A rule about commit style shares no words and no meaning with the sentence fix the login bug, so the one moment the rule matters, right before the commit runs, is exactly the moment similarity search cannot find it.

There are two obvious ways out, and both are bad. You can pin the rule so it gets injected into every single turn, which works until you have thirty rules and every prompt drags all of them along, mostly irrelevant. Token bloat is the number one reason people rip memory systems out. Or you can put the rules in a static config file, which works until it does not: nobody updates it, it does not travel across machines, and when you change your mind the old rule just sits there.

The thing I eventually understood is that these preferences are not facts about topics at all. They are rules about actions. And rules about actions should be retrieved by the action, not by the conversation. You remember how your team writes commit messages when you are writing one, not when someone mentions login bugs. So the fix is a third way to look things up, next to the two everyone has: recall by topic for facts, a tiny set of rules that ride along on every turn, and now rules that fire on the action itself.

How it works

A rule gets a tag naming the action it governs: trigger:git-commit, trigger:pr-create. That is everything you do to store one. On the read side there is one call, recall_for_action. It looks the tag up in the index, checks who can see it the same way every other lookup does, drops anything superseded, and returns the rules newest first:

python
# a preference about HOW to commit, tagged with the action it governs
db.store(
    "commit messages: single line, conventional prefix, no emojis",
    memory_type="procedural",
    tags=["trigger:git-commit"],
)

# right before the agent runs a commit, its hook asks:
rules = db.recall_for_action("git-commit", k=6)
for r in rules:
    print(r.content)   # injected into context at the moment of the commit

The retrieval is deterministic: no embedding of the query, no similarity threshold to tune, no model call, just the action name as the key. That matters because this lookup runs at the worst possible moment to be slow or flaky, directly in front of the user's command.

The hook that asks at the right moment

The missing half is knowing when to ask, and the agent already knows: it is about to run the command. For Claude Code, setup installs a hook that looks at each command right before it runs, and when it is a commit or a PR, fetches the matching rules and drops them into context in that exact moment:

bash
npx mentedb-mcp@latest setup claude-code
# installs a PreToolUse hook matched to Bash. Before a git commit or
# gh pr create, it fetches your trigger:git-commit / trigger:pr-create
# rules and injects them as context. Anything else: silence.

Most of the engineering here is in what the hook must never do. It never blocks or approves anything, it only adds context. It has a hard time budget, and on any failure, memory down, old server, timeout, it prints nothing and the commit runs untouched. A memory system that can break your git workflow gets uninstalled the same day, so the failure mode is always silence.

The parsing turned out to be where the real bugs hide. A rule firing on the wrong command erodes trust the same way a wrong memory does, so the matcher parses the command instead of grepping it: git -c commit.gpgsign=false commit fires, because the subcommand is commit. git config commit.gpgsign false does not, because the subcommand is config, even though the word commit appears twice. echo "git commit" does not, because it is quoted text. cd repo && git commit fires, because each segment of a compound command is checked on its own, and every one of those shapes has its own test in the suite.

Corrections work the way you would hope

These rules are ordinary memories, so everything from the last post about corrections applies to them too. Tell the agent you switched from squash merges to rebase, and the old rule is superseded: it stops showing up at commit time the moment you say so, and the history is still there if you ever ask what the old rule was. Rules also come back newest first, so if two versions of a rule are still around, the later one leads.

That is the part I care about most: the rule surfaces at the moment it applies, and the version that surfaces is the one you meant.

Try it

The engine call is recall_for_action in mentedb 0.27.2, the hosted API exposes it as the get_action_rules tool, and npx mentedb-mcp@latest setup installs the hook. Tag a commit rule, ask for a commit, and watch it show up at exactly the right moment. The live demo has the rest of the memory graph to explore.

AI agentsAgent memoryPreferencesClaude Code hooksAction rulesRust