Lessons learned for coding agents: what to write down
Your coding agent fixes a bug, finds a trap, makes a call. The next session starts without any of it, unless someone wrote it down. A lesson is that note. Done well, it gives the agent the memory of a colleague who was there.
This page shows what a useful lesson contains, three real ones from our own work, and how a lesson finds its way back into a session. The format works in a plain Markdown file too.
Five parts of a useful lesson
- The symptom, in search words. Write it the way you would describe it before you knew the fix: "deploy hangs", "build green, page dead". Later, someone searches with exactly those words.
- The decisive fact first. The number, the address, the command, the file. Put it into the first 100 characters. Briefings and injected notes cut lessons short, and the story fits behind the fact.
- What did not work. A dead end costs as much the second time. One line saves the next session from it.
- The files. They tie the lesson to the code. An agent about to edit that file can find it.
- One command that checks it. A test, a grep, a curl. The next session can confirm in seconds that the lesson still holds.
Keep one fact per lesson. When a lesson turns out wrong, fix that lesson and note why. Two lessons that contradict each other leave the agent guessing.
The format
These are the fields cachly's learn_from_attempts tool writes. Each one answers a question the next session will ask:
- topic
- A short slug: area, colon, keyword. One topic per fact.
- outcome, severity
- Did it work? How much does it hurt: critical, major or minor.
- what_worked
- The fix. The decisive fact goes into the first 100 characters.
- what_failed
- The dead ends, so the next session skips them.
- context
- The symptom in the words someone types while still stuck, plus the error message.
- file_paths, commands
- Where it happened, and the command that shows the fix holds.
- tags
- Words for filtering.
Three real lessons
All three come from our own work. We translated them, shortened them and removed project names.
1A docs summary invented a field name
The symptom sits in the context, in the words you would search with. The failed path warns the next session off the same shortcut.
{
"topic": "research:fetch-summary-invents-doc-fields",
"outcome": "success",
"severity": "major",
"what_worked": "LOAD THE DOCS RAW: curl -sL https://code.claude.com/docs/en/hooks.md, then grep. The summary called the UserPromptSubmit stdin field \"user_input\"; the reference says \"prompt\".",
"what_failed": "Taking field names from a fetch tool's summary of a long page. The summary invented field names and examples, including a matcher for an event that has none. A hook built on it silently does nothing.",
"context": "Symptom: the docs summary contradicts our own code, which reads payload.prompt. Hook gets no prompt, field missing, undefined.",
"file_paths": [
"docs/hooks/userpromptsubmit.tsx"
],
"commands": [
"curl -sL https://code.claude.com/docs/en/hooks.md -o hooks.md"
],
"tags": [
"docs",
"claude-code-hooks",
"research"
]
}2The image build broke while the tests stayed green
The first version told the story first. The fix started at character 300, after the cut. The rewrite puts the fix first and keeps the story in the other fields.
{
"topic": "docker:build-broke-on-test-imports",
"outcome": "success",
"severity": "major",
"what_worked": "BUILD WITH tsconfig.build.json THAT EXCLUDES src/**/*.test.ts, and run tsc -p tsconfig.build.json in the Dockerfile. Tests that import ../../app/src break the image build: the image copies only server/src.",
"what_failed": "Trusting npm test and tsc --noEmit. Both stayed green for 24 hours. Only docker build compiles inside the image, and it ran only at deploy.",
"context": "Symptom: deploy fails with TS2307 Cannot find module, while tests and type check pass locally.",
"file_paths": [
"server/tsconfig.build.json",
"server/Dockerfile"
],
"commands": [
"docker build -q -t api:check ."
],
"tags": [
"docker",
"typescript",
"ci"
]
}3A green build hid a dead server
The error text in the context is what someone pastes into a search. The command lets the next session check every file in seconds.
{
"topic": "nextjs:use-server-exports-only-async-functions",
"outcome": "success",
"severity": "critical",
"what_worked": "A \"use server\" FILE MAY EXPORT ONLY ASYNC FUNCTIONS. Move constants and objects into a plain module and import them from there.",
"what_failed": "Trusting next build. With an exported const object the build passed, and every page that loaded the file failed at runtime. Only the end-to-end tests caught it.",
"context": "Next.js 16. Build green, E2E red. Server error: A \"use server\" file can only export async functions, found object.",
"file_paths": [
"lib/actions/tasks.ts",
"lib/tasks/columns.ts"
],
"commands": [
"grep -rlE '^\"use server\"' lib app | xargs grep -nE '^export (const|let|var|class) '"
],
"tags": [
"nextjs",
"server-actions",
"build"
]
}Lesson 2, before the rewrite
The Docker lesson was first stored with the story up front. This is what a session saw of it before each prompt:
- [major] docker:build-broke-on-test-imports: The server's Docker build was broken for 24 hours and no check caught it: two tests import from ../../app/src, but the iβ¦The same lesson after the rewrite:
- [major] docker:build-broke-on-test-imports: BUILD WITH tsconfig.build.json THAT EXCLUDES src/**/*.test.ts, and run tsc -p tsconfig.build.json in the Dockerfile. Tesβ¦The first line tells you something broke. The second tells you what to do.
How a lesson comes back
A lesson helps only when it shows up at the right moment.
- Before each prompt. A UserPromptSubmit hook searches your lessons for words from the prompt. It puts the best matches next to the prompt, before Claude reads it. cachly's hook adds up to three lessons, one line each: severity, topic and the first 120 characters of what worked.
- At session start. A SessionStart hook briefs the new session on what matters in this project.
- On request. The agent searches when it is stuck, with the words of the error in front of it.
The hook searches by words. A lesson that never names its symptom is hard to find for someone who only knows the symptom. That is why part 1 matters as much as part 2.
Without a tool: LESSONS.md
You can start today with a file in your repository. Use the same five parts, one heading per fact:
# Lessons
One entry per fact. Newest on top. Fix an entry when it turns out wrong.
## docker: build broke on test imports (major)
- Fact: Build with tsconfig.build.json that excludes src/**/*.test.ts.
- Symptom: deploy fails with TS2307 "Cannot find module"; tests pass locally.
- Did not work: trusting npm test and tsc --noEmit.
- Files: server/tsconfig.build.json, server/Dockerfile
- Check: docker build -q -t api:check .
## nextjs: "use server" exports only async functions (critical)
- Fact: Move constants and objects out of "use server" files.
- Symptom: build green, every page using the file fails at runtime.
- Did not work: trusting next build.
- Files: lib/actions/*.ts
- Check: grep -nE '^export (const|let|var|class) ' lib/actions/*.tsPoint your agent to it from CLAUDE.md or your editor's rules file. Or load the matching entries before each prompt with a hook: the UserPromptSubmit guide has a working one that reads a notes file.
The file works as long as someone keeps it up to date. The weak spot is the moment right after a fix: the agent moves on, and nobody writes the lesson down.
With cachly
cachly turns those moments into experience without the extra step. Your assistant stores a lesson with learn_from_attempts. When a turn ends with an answer that reports a fix, with words like "fixed" or "root cause", cachly's Stop hook records it on its own. It also learns from your git history.
- The tool asks for the decisive fact in the first 100 characters, and for the symptom in search words.
- Updating a lesson needs a one-line reason, like a commit message. The history stays readable.
- The hooks bring the right lessons back before each prompt.
- One developer's lesson reaches the whole team.
/plugin marketplace add cachly-dev/cachly-mcp
/plugin install cachly-brain@cachlyLessons live in your own instance on servers in the EU. What leaves your machine, and where it goes.
Sources
- tools.ts (the fields of
learn_from_attemptsand what each one asks for) - einblendung.ts (how the hook picks and shortens lessons)
- ambient-cli.ts (the Stop hook that records a fix)
More: Claude Code hooks Β· AI memory in three layers Β· All docs