How Corey is built, and why it is mostly markdown
Table of Contents
Corey is an agent OS. It runs the recurring work across four products, keeps this site current, and hands me the decisions that need a person.
There is no orchestration framework. No vector database. No bespoke runtime. Almost all of it is plain files in git, and that is a deliberate choice rather than a shortcut.
This is the architecture and the reasoning.
The shape
Six parts. Every request walks them in order.
Route. Work lands somewhere before anything happens to it. The system decides what kind of task this is - a one-line fix, a multi-step build, an unclear requirement, a bug - and each answer implies a different path. The cost of classifying wrong is small. The cost of not classifying is an hour spent building the wrong thing well.
Role. The agent that picks it up has a seat with a boundary: a defined scope and a defined tool list. Not one agent with every tool. The failure mode of a god-agent is not that it cannot do the job, it is that it picks the wrong tool from too many similar ones.
Skill. A procedure encoded once, loaded when relevant. The content is judgement, not documentation - the things you would tell a new hire on their first day that are written nowhere.
Memory. Three tiers of plain markdown, split by lifespan: durable facts, an append-only dated log, disposable working state.
Tool. MCP to reach live systems. Not a chatbot answering questions about software - software acting on it.
Gate. Anything public, anything involving money, anything irreversible stops and waits for me.
Why almost no framework
The obvious question: why not use one of the orchestration frameworks?
Because the problems I actually hit were never the ones a framework solves.
Frameworks give you routing, state management, memory abstractions and tool registration. Useful-sounding. But a year of running this in production says the real failure modes are: the agent could not see what it needed to, the tool surface misled it, the instructions contradicted each other, and memory rotted. No framework fixes any of those. They are design problems in your own content.
What a framework does add is a layer you have to debug through. When something goes wrong you are now asking whether it was the model, your code, or the abstraction between them. With plain files and a harness, there is one fewer place for the answer to hide.
I am not claiming frameworks are wrong. I am claiming that for a system one person maintains, the abstraction cost exceeded the benefit, and I would make the same call again.
Why memory is markdown in git
This is the choice people push back on hardest, so it is worth being precise.
The instinct is a vector database. For most agent systems that is the wrong tool, and the reason is not performance.
Retrieval quality is rarely the bottleneck. Knowing what is true is. When an agent acts on something wrong, you need to open the memory, read it as a human, find the wrong line, and fix it. Markdown in git gives you exactly that: greppable, diffable, reviewable in a pull request, restorable to any point in time.
An embedding store gives you fuzzy recall and almost no ability to audit what the system believes. You can inspect what came back for a query. You cannot easily read the whole corpus and spot the contradiction.
Use vectors when the corpus is genuinely large and unstructured - thousands of support tickets, a document archive. For an operating system’s own memory, measured in hundreds of facts, plain files win on every axis that matters.
The agent memory guide has the three-tier split in detail, including the rule that costs people the most: corrections overwrite in the durable tier, they do not append. Appending “update: pricing is now X” beneath the old number means the next reader gets both and has to guess.
Why the gate is in the system, not the prompt
Everything else here is an engineering preference. This one is not negotiable, and it is the part I would keep if I had to throw the rest away.
Three categories always wait for a person: anything published externally, anything that moves money, anything irreversible. Everything else ships unattended - and that clause matters as much as the rule, because a policy that does not clearly release the other ninety percent is just a slow manual process wearing a policy’s clothes.
The unifying property is reversibility, not risk. A wrong database write is correctable in a minute. A wrong email to two thousand people is not. Sort by what you can undo.
And it has to be enforced by the system. “Always ask before sending” in a prompt is a preference - it holds until the context grows long, until it conflicts with another instruction, or until someone phrases a request as prior authorisation. If your safety property can be removed by a sufficiently confident sentence in the conversation, it is not a safety property.
The pattern is a two-step tool pair: prepare does all the work and returns a
complete summary plus a short-lived token with nothing yet performed; execute
takes that token and refuses unless a human approved it out of band. The agent
works autonomously right up to the boundary. The human sees a finished thing
and makes one binary decision.
That is the correct division of labour. The expensive part is the work; the part needing judgement is the release.
Engine and overlay
One structural decision worth explaining, because it is what made Corey a product rather than my personal setup.
The system splits into an engine - the generic core, with zero facts about any specific business - and an overlay, which is one business’s facts, voice and guardrails.
That split is not a refactor you finish. It is an invariant you enforce, with a build gate that fails if a tenant-specific fact appears in the engine. I learned that the hard way: the first attempt at separating them was a string-rename, and the gate immediately found identifiers the manual sweep had missed. A find-and-replace makes a thing look generic. Only a check keeps it that way.
What it actually does
The unglamorous answer, which is the honest one: the recurring work.
Drafting and revising. Opening pull requests. Monitoring deploys and CI, and filing what it cannot fix. Keeping memory current. Running scheduled checks overnight so the morning starts with the state of things rather than a to-do list. Reaching the real tools - repositories, databases, payments, analytics - over MCP rather than reporting on them.
It also built and maintains this website, which is documented in detail on the colophon, including what it does unattended and what waits for me.
Roughly: the work that used to need a hire became a command. The work that needs a person still needs a person, and the gate is what keeps the line honest.
What I would change
I would put the gate in on day one. I retrofitted it, and retrofitting a control is much harder than starting with one.
I would have measured context earlier. I ran for months with a session payload roughly twice what it needed to be, because imports above a lazy-loading router quietly defeated the router. I only found it by measuring, and the fix took an afternoon.
I would not have split the memory files as eagerly. A clean conceptual seam is not a clean loading seam, and I have the measurements showing a split that cost more than it saved.
The shortest version
Route work to a bounded role. Encode judgement as skills. Persist memory as plain files split by lifespan. Reach real systems over MCP. Gate anything you cannot undo, in the system rather than the prompt.
The rest is detail, and the detail is in the guides.