Here's a conversation I had with my coding agent three times in one week:
Me: The DEV.to API 403s us.
Agent: Let me add aUser-Agentheader — their bot filter rejects the default one.
Me: Right. We figured that out on Monday. And Tuesday.
The agent was correct every time. It was also starting from zero every time. Monday's hard-won lesson — dev.to blocks the default user agent — evaporated the moment the session closed. Wednesday-me paid the same debugging tax as Monday-me.
This is the part of "agents write code, but they don't remember" that actually hurts. It's not that the model is dumb. Within a session it's sharp. The problem is that every session is session one. All the context you built — why a function is shaped weirdly, which approach you already tried and rejected, the username that has an underscore in one place and not another — is gone. You're not pair-programming with a senior dev. You're onboarding a brilliant amnesiac, daily.
Why "just put it in the prompt" doesn't scale
The obvious fix is to dump everything into your instructions file (CLAUDE.md, AGENTS.md, whatever your tool reads). I tried. It rots fast:
- It becomes a 600-line wall the model skims and ignores.
- It mixes stable facts ("the token lives in
.env") with episodic ones ("on the 23rd I tried X and it failed"). - Nobody prunes it, so wrong facts linger and actively mislead.
A single flat file is a junk drawer. What I actually wanted was a small, typed memory — different kinds of knowledge in different places, each with a rule for when to read and when to write.
The fix: four files and four protocols
I gave the project a docs/project_notes/ directory. Four files, each one job:
docs/project_notes/
├── bugs.md # known bugs → their solutions
├── decisions.md # why things are built the way they are (mini-ADRs)
├── key_facts.md # usernames, endpoints, file purposes, run commands
└── issues.md # work log, newest first
That's it. No database, no vector store, no embedding pipeline. Markdown the human and the model both read.
The trick isn't the files — it's wiring triggers into the instructions file so the agent knows when to consult and update them. The entire memory protocol is four lines in CLAUDE.md:
## Project Memory System
- Encountering an error → search `bugs.md` first
- Proposing an architecture change → check `decisions.md` for conflicts
- Need a username/endpoint/command → check `key_facts.md`
- Completing a phase of work → log it in `issues.md`
Now the DEV.to 403 lives in bugs.md once, as a fact, not a rediscovery:
### dev.to API returns 403 on every request
**Cause:** dev.to rejects the default HTTP-client User-Agent (bot filter).
**Fix:** send `User-Agent: Mozilla/5.0`. Applies to all /api/articles calls.
And the gotcha that bit me twice — same person, two usernames — lives in key_facts.md:
## Usernames
- GitHub: enjoykumawat (no underscore)
- DEV.to: enjoy_kumawat (with underscore)
The next time the agent reaches for a username, it reads the fact instead of guessing and getting it half-right.
The rule that makes it actually work: write-on-completion
Reading is easy. The discipline is writing. A memory system only compounds if knowledge flows back in. So the single most important protocol is the last one: when you finish a chunk of work, log it. My issues.md is append-only, newest first:
### 2026-06-23 - DEV.to publisher + 403 fix
- Status: Completed
- Built reusable stdlib publisher. Root cause of the
intermittent 403 was the default User-Agent. Fixed with
a Mozilla UA + H1-strip on the markdown body.
That one entry means future-me (and future-agent) gets the outcome and the reason for free. The work log is the difference between "we have notes" and "we have memory."
What changed in practice
- No more re-debugging. Solved problems stay solved. The 403 conversation hasn't happened a fourth time.
-
Decisions stick. When I'm tempted to re-architect something,
decisions.mdreminds me why it's that way — usually because I already tried the "better" idea and it broke. - Onboarding cost dropped to near zero. A fresh session reads four short files and is roughly as caught-up as I am.
Keep it small or it rots
Two failure modes to avoid, both learned the hard way:
-
Don't log what the code or git already says. "Renamed
xtoy" is in the diff. Memory is for the non-obvious: the why, the dead end, the gotcha. If git can answer it, don't write it. -
Prune wrong facts immediately. A stale fact is worse than no fact — the agent trusts it. When
decisions.mdno longer reflects reality, the fix is a delete, not an append.
The whole system is four markdown files and four lines of protocol. No framework. The insight isn't technical — it's that an agent's memory has to live outside the agent, in artifacts that survive the session, with explicit rules for when to read and write them.
Your agent doesn't need a bigger context window. It needs a place to write things down — and a habit of reading them back.













