The problem AGENTS.md is solving
If you've used Claude Code, Cursor agent mode, or Devin for more than an hour, you've noticed a pattern: the agent is great at one turn and forgets everything before it. It re-reads the same files. It re-asks the same architectural questions. It "decides" to use kebab-case routes, you correct it, it writes them your way for the rest of the session, and tomorrow you're back at square one.
The naive fix is "use a longer context window." That doesn't actually work. Even with a million-token window, every fresh conversation starts with no awareness of past decisions. The agent is technically capable of reading prior chats; it just doesn't, because nothing in its prompt tells it to.
The fix is to externalize the things you want remembered into files the agent reads every turn. In VibeKit the anchor file is AGENTS.md, and it sits at the root of every app's workspace.
This post is about what's actually in that file, why each piece is there, and the small number of architectural decisions that make persistent agent memory work in production.
What AGENTS.md is, concretely
It's a Markdown file of 8,000 to 10,000 characters. It sits at the root of every VibeKit-hosted app's workspace and is committed to its GitHub repo (every workspace gets a real repo when it's created), alongside package.json and README.md. The gateway injects it into the agent's context on every turn, so the agent never has to be told to go read it.
Here's an excerpt from the actual template that ships with every new VibeKit app, rendered by renderAgentsMd (src/services/agent-templates.ts) at provision time and again every time the backend boots (lightly trimmed):
# Agent guide
App: **my-dashboard** at https://my-dashboard.vibekit.bot
Repo: user/my-dashboard
## NEVER (breaks the product)
- **NEVER point the user at localhost / `npm start`**: only the live URL
above. They have no terminal.
- **Deploy ONLY when the user's message this turn asks (deploy/publish/
make-it-live) or reports the app broken.**
The full file is under 50 (long) lines and covers four things:
- Identity: what the app is called, its live URL, its repo
- Defaults: when to use tools vs. just reply, when to deploy, how to close a turn
- Mechanics: how the app must listen, its memory limit, the stack to default to (the deploy and API details live in
TOOLS.md, which the agent reads on demand) - Anti-patterns: common mistakes that look reasonable but break the system
That fourth bucket is the most underrated. Most "system prompt" content focuses on what to do; AGENTS.md spends roughly equal real estate on what not to do, with explanations. Examples I've ended up writing the hard way:
- Sandbox rejects chmod/sudo/docker by design: Edit/Write directly; a Write
error is never a perms bug.
- App MUST listen on process.env.PORT, host 0.0.0.0.
- Default Express + vanilla HTML/CSS/JS: React/Vite/Next break unless asked.
- NEVER say "fixed"/"works"/"verified" on a 2xx alone: curl the live URL
and grep YOUR change in what comes back.
Each one of those bullets exists because an agent run failed on it once, the user asked "why did the agent give up?", and the answer was something like "it interpreted a sandbox-by-design block as a real permission problem." Without AGENTS.md, that lesson dies with the conversation. With it, it generalizes: every agent on every app starts from the corrected baseline.
Why this isn't just a system prompt
The instinct most engineers have when they hear "file the agent reads every turn" is: "isn't that just a system prompt?" There's overlap, but they're not the same thing, and the differences are what make AGENTS.md work as a persistence mechanism.
A system prompt:
- Lives in your application code or your model-provider config
- Is the same for every user of the application
- Changes only when you redeploy
- Is not visible to the user
- Is not editable by the agent itself
AGENTS.md:
- Lives in the app's workspace, next to the code, and is committed to the app's GitHub repo
- Is per-app, rendered with the app's name, live URL and repo, and with its state (an app that hasn't shipped its first build gets extra first-turn rules)
- Changes when the platform re-renders it: on every backend boot and on every rename
- Is locked: read-only and owned by the platform, so the agent can't rewrite its own safety rules
- Points the agent at the file it does write:
MEMORY.md, whose seed tells it to "Update this file with important decisions, architecture choices, and lessons"
The key property is the split. AGENTS.md is the contract the platform pushes down; MEMORY.md is the notebook the agent writes back to. When an agent solves a tricky bug, it logs the lesson in MEMORY.md. The next agent run, days later, reads that lesson and doesn't repeat the bug.
For this to work the files need to be:
- Checked into git: survives container restarts, redeploys, even cancellation
- Per-app, not global: different apps have different conventions; mixing them poisons context
- Bounded in size: too large and you blow the model's context window every turn
Number 3 is the boring constraint that ends up driving design. The gateway injects AGENTS.md with a hard cap of 10,000 characters; anything past it is cut. In May the cap was 5,000, and you'd see the warning in the gateway logs: workspace bootstrap file AGENTS.md is 9726 chars (limit 5000); truncating in injected context. Today a render test fails the deploy if the worst-case template crosses its budget. So longer-form notes go into MEMORY.md (a sibling file the agent owns and writes to freely), and reference material lives in TOOLS.md and PLATFORM.md, which the agent reads only when it needs them.
The three-file pattern: AGENTS.md, MEMORY.md, STATUS.md
In production, persistent memory in VibeKit is actually three files plus a log folder, not one file. Each has a different access pattern:
| File | Owner | Read frequency | Write frequency | Size budget |
|---|---|---|---|---|
AGENTS.md |
Platform (re-rendered on boot and rename) | Every turn | Never by the agent | Under 10K characters |
MEMORY.md |
Agent | When user asks for "real work" | When agent learns something | Unbounded, agent-managed |
STATUS.md |
Platform (live updates) | When user asks about app health | When you message the agent, at most every 5 minutes | ~2KB |
memory/YYYY-MM-DD.md |
Agent | Today's | Whenever something happens | Daily rotation |
This separation matters because the read pattern is different per file. AGENTS.md is read by every agent run, even trivial replies, so it has to be small and stable. MEMORY.md is only read on real work tasks, so it can be much larger. STATUS.md is only relevant when the agent is investigating live behavior. Daily logs in memory/ are mostly write-only: they exist so that "what did I do last Tuesday" is answerable.
Around them sit files that carry instructions rather than memory: TOOLS.md and PLATFORM.md (locked reference docs) and SOUL.md, IDENTITY.md and USER.md (personality and the person's preferences, which the user or the agent can rewrite).
If you tried to do this with one big file, you'd hit one of two failure modes:
- All in AGENTS.md: the file balloons past the context budget. Agent now spends its entire context on memory, has none left for the actual task.
- All in MEMORY.md: the agent never reads it on trivial turns (greetings, status checks), so identity context evaporates.
Splitting by access pattern is what makes the system stable. The reason most "agent memory" implementations don't work is they pick one file and try to make it serve both jobs.
How the agent is told to read it
The hard part of "agent reads file every turn" isn't the file. It's the instruction that the agent reads the rest without being asked. This is brittle: most LLMs will ignore instructions to do work they don't see immediate value in.
What worked in the May 2026 template:
## Setup (every session)
\`\`\`bash
source .vibekit-env # loads VIBEKIT_API_URL, VIBEKIT_API_KEY, ...
source .env # app env vars
\`\`\`
When the user asks you to do real work, also read: STATUS.md (live
health/logs), MEMORY.md (your accumulated knowledge), memory/YYYY-MM-DD.md
(today's log). For greetings or trivial messages, skip and answer from context.
Two things about this:
The setup was concrete, not aspirational. "Read AGENTS.md" doesn't work, because the agent's already in a context that includes AGENTS.md. The instruction was to
source .vibekit-env, which has a verifiable side effect (env vars are now defined). Concrete actions stick; abstract reminders don't.The "skip for trivial messages" carve-out is load-bearing. Without it, the agent reads MEMORY.md on every "hi" and burns 30+ seconds (and tokens) on a non-task. The carve-out is what keeps voice-call latency bearable: a "yes I'm here" reply doesn't have to traverse the whole memory layer.
Both survive in the current template in shorter form: the agent's API credentials are already in its shell when a turn starts, and the carve-out is one line in Style, "hi"/"thanks" → text only.
Empirically, the carve-out cuts tool-call counts on trivial turns by close to an order of magnitude. Without it, the agent will dutifully run git status, cat package.json, and read STATUS.md before answering "hey are you there", every single time. A "yes I'm here" reply that takes 8 seconds because it's traversing the whole memory layer feels broken to a user, even though the agent is technically doing what its instructions told it to.
The "be resourceful, check the file" failure mode
There's a specific anti-pattern AGENTS.md is designed to prevent, and it shows up across nearly every agent platform: the default-to-resourceful-investigation loop.
The agent gets a prompt like "make the hero copy shorter." The reasonable instinct, and the instinct most agent system prompts encourage, is to:
- Read the current homepage HTML
- Read the deployed version to confirm what's live
- Check git log for recent changes
- Look at the README to understand the project
- Then edit the hero
That's five tool calls before any work. For "shorter hero copy" (a 30-token text edit) you've burned 50,000 input tokens just gathering context, half a minute of latency, and a few cents of API spend.
AGENTS.md addresses this directly:
- "hi"/"thanks" → text only. ≤3 tool calls/turn default (builds excepted).
- Turn 1: Don't Read/ls to "understand" first; then edit ONLY the
file(s) the change touches.
The framing matters: it's not "be lazy," it's "be appropriately resourced for the task at hand." Most coding agents are tuned by their providers to look thorough, because thoroughness reads as competence in benchmarks. In production, with a real user waiting and real tokens metered, thoroughness without judgment is just expensive.
What goes in AGENTS.md vs MEMORY.md vs the system prompt
This is the question I get most often when I describe the architecture: where does each kind of context belong? Some heuristics that have held up:
Goes in the system prompt (gateway-level, identical for every agent run on every app):
- General coding agent personality and behavior
- Tool-use formatting (how to invoke Bash, Edit, Write)
- Output structure expectations (markdown, plain text, JSON)
- Universal safety rules ("never expose API keys")
Goes in AGENTS.md (per-app, identical across turns within an app):
- App identity (name, live URL, repo)
- When to deploy, and what counts as proof that something works
- Constraints unique to this hosting environment (512MB RAM, 1GB on Pro, sandbox limits)
- Anti-patterns that have bitten this platform's users specifically
Goes in MEMORY.md (per-app, agent-edited, read on demand):
- Architectural decisions specific to this app ("we use Drizzle, not Prisma")
- Bug fixes that should not be re-introduced
- User preferences this user has stated ("commit messages should be lowercase")
- In-progress work spanning multiple sessions
Goes in conversation history (per-conversation):
- The actual back-and-forth of the current task
The line between AGENTS.md and MEMORY.md is the most subtle. The principle: AGENTS.md is what the platform knows; MEMORY.md is what the agent and user have learned. AGENTS.md is re-rendered whenever the platform's template or the app's identity changes: on every backend boot and every rename. MEMORY.md is seeded once at provision, edited by the agent from then on, and never overwritten by the platform.
When a user renames an app, the platform regenerates AGENTS.md to reflect the new name and subdomain; see the rename handler in src/routes/hosting.ts. The handler explicitly only writes AGENTS.md; MEMORY.md isn't touched because the architectural decisions captured there are still valid. This split is what makes it safe for the platform to mutate AGENTS.md without losing the agent's accumulated knowledge.
What I'd build differently if starting over
Three things I'd revisit:
1. The cap is still an absolute number. It started at 5,000 characters, a back-of-envelope estimate of how much context to spend on per-turn boilerplate. It silently cut rules off the end of the file twice, so in July we raised it to 10,000; the template later crossed that too, which is why a test now holds the budget. A smarter cap would be percentage-of-context-budget, not absolute characters.
2. AGENTS.md wasn't versioned. When I wrote this, a template update reached only newly-provisioned apps; existing apps stayed on whatever version they were created with until a rename. That one is fixed: the backend re-renders every app's AGENTS.md from the current template on boot, so a new rule reaches every app without anyone opting in. What's still missing is a changelog, so you can see what changed and when.
3. The "trivial vs work" judgment is in the agent's hands. Right now the agent decides whether a turn is a greeting (no tools) or a work request (read MEMORY.md). When it gets that judgment wrong, interpreting a real request as a greeting, the user sees a confidently wrong answer with no investigation. A platform-level classifier ("is this a real task?") that ran before the agent saw the message would give better worst-case behavior.
Each of these is a real tradeoff, not a clear win. The current design ships and works for the vast majority of turns. But if you're building something similar, those are the spots I'd push on first.
Closing
The takeaway isn't "every agent platform should have an AGENTS.md file." It's that persistent memory in agents is an architecture problem, not a model capability. You can't fix it by upgrading to a larger context window. You can't fix it by giving the model better instructions in the system prompt. The thing that fixes it is structuring memory across multiple files with different access patterns, and giving both the platform and the agent ownership over different parts of that memory graph.
If your agent forgets things between sessions, the bug isn't in the model. It's in the absence of a place for the model to remember things to.
If you want to see how this works in production, VibeKit gives every workspace its own agent with this architecture. Free with BYOK (bring your own Claude or OpenAI key): your AI charges go straight to the provider. The persistent agent page has the architecture summary; this post is the longer-form why.
For more on what the agent keeps and what it deliberately forgets, read What persistent agents actually remember. New posts go out on @609.sol on X.