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-Agent
header — 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.
The obvious fix is to dump everything into your instructions file (CLAUDE.md
, AGENTS.md
, whatever your tool reads). I tried. It rots fast:
.env
") with 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.
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.
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."
decisions.md
reminds me Two failure modes to avoid, both learned the hard way:
x
to y
" is in the diff. Memory is for the decisions.md
no longer reflects reality, the fix is a 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.