Project Documentation for AI Agents: Agent Workspace RefArch in Practice
AGENTS.md + HANDOVER.md + three-layer architecture + ARID principles. Anthropic's progressive disclosure, OpenAI's agents.md, plus our own context engineering experience — all blended into a documentation standard designed for AI agents.
Every new session, it forgets who you are
It all starts with a painfully real problem.
You've been working on a project for three weeks. You know the tech stack cold, the directory structure is muscle memory, you can rattle off the test commands in your sleep. You've even developed a fixed workflow — read HANDOVER.md first, then AGENTS.md, then start coding.
Then you close the terminal, grab lunch, and come back.
The AI agent stares at you like it's never seen you before.
"Let me take a look at this project… hmm, this is a Next.js project…"
The mental model you built over three weeks? Gone in an instant.
This isn't an IQ problem with the model. It's a context problem. Every new session is a blank slate, and your project is just a pile of filenames on that slate. The agent doesn't know which files matter, doesn't know where you left off, doesn't know what landmines the project has waiting for it.
I tried a few approaches.
At first, I wrote a monstrous system prompt cramming in the project structure, tech stack, and every gotcha I could think of. Then I realized every prompt change required a new session to validate — ten minutes per iteration — and the prompt kept growing until the model spent more time reading instructions than doing actual work.
Next, I tried putting everything in the project README. But READMEs are written for humans. Docs for AI agents have different demands — they need to be structured, searchable, and ideally self-maintaining by the agent itself.
After banging my head against this for the better part of a year, I ended up with something I call Agent Workspace RefArch.
It's not some groundbreaking invention. It's Anthropic's "progressive disclosure" approach from Claude Code, mixed with OpenAI's agents.md practices, layered with the context engineering lessons our team learned the hard way — all blended into a documentation standard that AI agents can actually read.
I gave it version v1.2, because I couldn't be bothered to number the failures that came before.
From a single AGENTS.md to a three-layer architecture
The original idea was simple: drop an AGENTS.md in every project root telling the agent what this project is, how to run it, and what pitfalls to watch for.
I wrote the first version — about 80 lines. It worked okay. The agent stopped asking "what package manager does this project use?" at least.
But issues surfaced fast.
When you have 10 projects coexisting in a single workspace — each with its own AGENTS.md — you need a map at the top level telling the agent what lives here, how they depend on each other, and which one is the main project.
Meanwhile, AGENTS.md itself was growing. The longer a project runs, the more you want to cram in — tech debt, known issues, special configs… A file balloons from 80 to 200 lines, and the agent has to read the whole thing before it can start working.
There's a fundamental tension between information density and load speed.
Anthropic uses an approach in Claude Code called "progressive disclosure" — instead of dumping everything on the model at once, load on demand: give an index first, then details.
I borrowed that idea and turned it into a three-layer structure:
workspace/
├── AGENTS.md ← Layer 1: project map + dependencies
├── .workspace.yaml ← MCP / tool configuration
├── project-a/
│ ├── AGENTS.md ← Layer 2: tech stack + commands + constraints
│ ├── HANDOVER.md ← session log (80-line auto-archive)
│ ├── docs/decisions/ ← ADR
│ └── ...
└── hooks/ ← Layer 3: SessionStart/End auto-maintenance
Here's the thinking behind each layer.
Layer 1: The project map
The root AGENTS.md stays under 60 lines. It answers exactly two questions: What's in this workspace? How do they relate to each other?
# Workspace: gh.l-web
Project: Personal tech blog (Next.js 16, Static Export, bilingual CN/EN)
Dependencies: none
Sub-projects:
- gh.l-web/ → main blog project
- shared-assets/ → shared resources
Entry point: gh.l-web/
That's it. Once the agent reads this, it knows which directory to dive into — everything else can wait.
The 60-line hard limit came from experimentation. Beyond that threshold, the agent starts losing focus at the first layer.
Layer 2: Project-scoped AGENTS.md
Each sub-project's AGENTS.md is the core document, capped at 150 lines. It contains:
- Tech stack and key commands
- Directory structure and file conventions
- Coding standards and constraints
- The boundary trilemma (detailed below)
- The pitfall table (also detailed below)
150 lines is what I landed on after repeated adjustments. Too few and the agent still asks low-level questions. Too many and it starts working before finishing the read, missing critical constraints.
Layer 3: Hook-driven automation
This layer is the most important — and the most overlooked.
AGENTS.md is written by humans, but HANDOVER.md must be maintained by the agent itself.
At the end of every session, a SessionEnd hook automatically appends a summary of changes made, decisions taken, and outstanding items to HANDOVER.md, then trims it to 80 lines. When the next session starts, a SessionStart hook loads the last 10 lines of HANDOVER.md — the agent instantly knows "where I left off."
The 80-line cutoff has its own story. I tried 50 lines — too much context lost. I tried 120 lines — the agent reads slowly and tends to mistake old logs for current state. 80 lines is the sweet spot.
Why I chose ARID over DRY
Software engineers treat DRY (Don't Repeat Yourself) with near-religious reverence. When I first designed this template, I tried to follow it too — write a shared list of constraints in the root AGENTS.md, then have each sub-project reference it.
It worked terribly.
An agent is not a compiler. It doesn't automatically resolve cross-file references. When you write "see constraint #3 in the root", the agent will almost certainly not actually go read the root — it falls back to whatever "common constraints" its training data suggests, skipping your custom rule entirely.
So I accepted a basic fact: the primary reader of this document is an LLM, not a human compiler. LLMs read linearly and one-pass. They don't Ctrl+Click to follow references the way a programmer does.
This led to my core principle — ARID: Accept Repetition In Documentation.
It sounds like a step backward. It's intentional. Every sub-project's AGENTS.md can (and should) include the common constraints, even if they're already written at the root. When the LLM reads project A's AGENTS.md, it doesn't need to know project B exists; it only needs the rules for its own turf.
Repetition is not a bug. It's a feature.
Let me be clear on this, because it goes against most people's intuition.
DRY starts from "one concept, one place, change once everywhere." That's absolutely correct for code — you don't want to maintain five copies of the same logic. But in documentation, especially docs written for AI, the maintenance cost isn't "change one place vs. many" — it's "the information exists but the agent never reads it."
The cost of repetition is a few extra keystrokes when writing. The cost of not repeating is an agent missing a constraint, producing a bug, and you spending half an hour debugging. Do the math.
This doesn't mean abandoning modularity entirely. Architecture Decision Records (ADRs) — things that need global visibility — still live in docs/decisions/ and get shared. But day-to-day operational constraints, boundary rules, and pitfall reminders? Every AGENTS.md should be self-contained.
The birth of the boundary trilemma
What's the scariest thing about an AI agent executing a task?
It's not lack of capability — it's excessive initiative.
Give it a "change this CSS color" task, and it might helpfully upgrade your dependencies, refactor your component structure, and rewrite your test cases — because its training data says "good engineers improve code as they go."
But you can't blame it. You never told it where the boundaries are.
So I designed a "boundary trilemma" that goes into every AGENTS.md:
- ✅ Always: things the agent can do freely (run tests, update HANDOVER.md, fix lint)
- ⚠️ Ask: things the agent must confirm first (add dependencies, change public interfaces)
- 🚫 Never: things the agent must never do (hardcode secrets, delete tests)
This trilemma wasn't pulled out of thin air. It came from a painful lesson.
I once asked an agent to "optimize the Dockerfile." It helpfully swapped the base image from node:18-alpine to ubuntu:latest — because "the latest Ubuntu is more secure." The entire CI pipeline blew up. Was it the agent's fault? No. I never told it "don't change the base image."
After that incident I added the Never section. Then I discovered that Never alone wasn't enough — the agent would get anxious about "what can I actually do?" — so I added Always. The Ask section came later, for things that aren't absolutely forbidden but do require human confirmation.
The trilemma is now a mandatory section in every project I set up. Remove any one leg and it doesn't work.
The design thinking behind the pitfall table
Going a step beyond the boundary trilemma is the pitfall table.
Every AGENTS.md ends with a markdown table in a fixed format:
| Symptom | Cause | Fix |
|---------|-------|-----|
I chose this format deliberately.
Three columns, no more, no less. Symptom is what the agent can observe ("build fails with Module not found"). Cause is the root cause it needs to understand ("path aliases are configured in tsconfig but webpack doesn't recognize them"). Fix is the command or steps it can execute directly.
I tried four columns (adding "Prevention"), but the agent rarely looks at the prevention column proactively — it only checks the table after falling into a pit. I tried two columns (dropping "Cause"), but without understanding the causal link between symptom and fix, the agent falls into a similar-but-different pit next time.
Three columns is the sweet spot. No over-explaining, just the data — the agent learns from one lookup.
Here's a subtle point: the "Cause" column must be written in language the agent can actually use. Don't write "ESM/CJS interop issue" — the agent knows that term, but it won't connect it to the build failure. Write "webpack encounters mixed module formats during bundling because the default config only supports CJS." The more concrete, the better.
The current context-engineering-system pitfall table has 12 entries, covering everything from build to deploy. Every entry was distilled from a real incident.
What the results look like
Enough design philosophy. Let's look at the numbers.
I compared the context-engineering-system project before and after adopting this RefArch. Before the change, the Agent Workspace had no structured documentation. After, it's fully deployed.
| Metric | Before | After |
|---|---|---|
| Total skill description characters | ~15,000 | 2,322 |
| Average description length | ~190 chars | 34 chars |
| Unused skills in index | 59 | 10 archived |
Total characters dropped from 15,000 to 2,322 — an 84% reduction. Every session load, the agent reads 12,000+ fewer characters of noise.
The more important metric — one I can't quantify but I can feel — is the dramatic drop in "low-level questions." The agent used to constantly ask "what's the test command for this project?" — it doesn't anymore, because the first paragraph of AGENTS.md covers it. The agent occasionally used to modify .env files — it doesn't anymore, because the Never section is crystal clear.
This architecture has been adopted by 10+ projects. From my personal side projects (baby-harness, agent-search-mcp, crawlweaver) to multi-contributor repos (headroom, qq-group-bot), it's in use everywhere.
I didn't force it. I just shared it with my team once, and someone from the neighboring team walked over and asked, "can I get a copy of that doc template?"
Of course I gave it to them.
Leveling up: writing docs for AI is training it how to read your project
Let me zoom out for a moment.
I've come to believe that writing documentation for an AI agent is, at its core, training a reading pattern.
Every boundary you write, every pitfall you document, every constraint you spell out — you're telling the agent: "this is how this project works."
You're training its attention — which signals matter (the constraints in AGENTS.md) and which signals to ignore (the hundred thousand files in node_modules).
Traditional software documentation is written for humans. Humans have judgment — you tell a developer "don't do expensive work in the constructor," and they extrapolate to "so I shouldn't do it during initialization either." But an agent won't. You say "don't do expensive work in the constructor," and that's all it knows. You have to write "don't do it during initialization either" as a separate rule.
So writing docs for AI means accepting a fundamental truth: documentation granularity must be coarse enough that every rule can be understood and applied independently.
That granularity is much coarser than what you'd write for humans.
But here's the interesting part: once you get used to this granularity, you find it helps humans too. New team members onboarding with an AGENTS.md? They glance at it and know exactly what to do and what not to do. No more "go read the whole codebase first to get familiar."
Honestly? Docs written for AI end up being clearer for humans too.
Because I'm not writing documentation. I'm writing operating manuals for this project — just in two languages at once: plain English and tokens.
The full implementation of this RefArch lives in context-engineering-system (sorry, it's an internal project — not open-sourced yet). The template files and hook scripts are iterating in my dotfiles.
If you're also fighting AI agent amnesia, start small: drop an AGENTS.md in your project root with three lines of boundaries. No three-layer architecture, no pitfall table, not even 100 lines.
Then watch what happens to your agent's behavior.
I bet it changes.