The Agent Should Not Have to Remember

A Web Developer based in Belgium, specialized in PHP & JS development
A few days ago, I wrote about how my agentic development workflow mostly emerged from actual problems rather than from a carefully designed set of principles. Since then, some of those ideas have become much more concrete.
I still do not think there is a universal setup for coding agents. What I have now is simply the result of repeatedly watching agents work on a real project, noticing where things become awkward, and trying to move that friction somewhere more appropriate. Sometimes that means changing an instruction. Increasingly, though, it means changing the environment.
Here are a few things that have been working particularly well for me.
A worktree is a workspace, not a task
Git worktrees looked like an obvious solution as soon as I started running several agents concurrently. And they are. But my first mental model was wrong.
Initially, I treated worktrees as disposable environments attached to issues:
one issue → one branch → one worktree
It sounds wonderfully clean, until you actually use it. A useful development workspace is rarely just a Git checkout: it needs dependencies, local configuration, databases, generated files, perhaps frontend assets, browser-test configuration and various bits of state.
Creating all of that for every issue is possible, but it becomes surprisingly expensive operationally. You also accumulate worktrees very quickly. Some issues are tiny, some are abandoned, some turn into follow-ups, and some agents finish one task and immediately pick up another. The elegant one-worktree-per-issue model soon becomes mostly lifecycle management.
What works better for me now is:
one persistent worktree per agent, disposable branches per issue
The agent gets a stable workspace. When it starts new work, that workspace is reset to the current main branch and a new issue branch is created inside it. The worktree represents capacity: a place where one concurrent worker can safely operate. The branch represents work.
That distinction also changes what the tooling needs to do. Creating a workspace can be relatively expensive, so it should set things up properly. Resetting it between tasks should be cheap and predictable. Removing it should clean up the resources owned by that workspace.
There are already tools such as wt that wrap Git worktrees and let you attach initialization and teardown commands to them. I think that lifecycle idea is more important than the Git convenience itself, because Git is actually the easy part.
The interesting question is whether two agents can really work independently. If every worktree points to the same development database, they are not independent. If every browser suite assumes port 8000, they are not independent. If an agent starts a local server and another agent helpfully kills it during its own test cleanup, they are definitely not independent.
Few things make a coding agent more perplexed than a test suite that suddenly stops working because another coding agent just killed its process.
So each workspace now owns its environment as much as reasonably possible: its databases, local configuration, runtime processes and dynamically allocated ports. Once you think about worktrees that way, you quickly end up writing tooling around them.
Which leads to the more general lesson.
Small project tools have become incredibly cheap
In my previous article I argued that rules should become executable whenever possible. I think I would push that further now: with agents, small internal tools have become cheap enough that not building them is often the more expensive option.
Agents are going to write scripts anyway. Ask one to investigate a database problem and it may create a temporary shell script. Ask another to inspect translations and it may produce a Python script. Ask another to debug browser tests and suddenly there is a JavaScript file somewhere that starts a server, waits for a port and captures a screenshot.
Do this for a while and the project's scratch area starts looking like a digital landfill.
Many of these scripts are legitimately disposable; exploration is part of the work. But when the same operational problem appears twice, I increasingly prefer turning the solution into an actual project tool.
Worktree creation is a good example. I could document the commands needed to create a worktree, copy environment files, create databases, install dependencies and initialize everything correctly. Or the project can expose one command that does it. The second option is better documentation because it is executable, testable, discoverable and much harder to misunderstand.
This does not mean building a framework for every shell command. The internal tool can be extremely boring: a Bash script, a Make target, a tiny PHP command. The important part is giving common operations a stable interface.
I have started thinking of the repository itself as something that has a developer API. There is an intended way to:
create and reset a workspace;
run the fast checks;
launch browser tests safely;
inspect whether a workspace is healthy;
format and validate the code before it is submitted.
Once these operations become explicit, the instructions become much smaller. Instead of teaching every agent how the machinery works internally, I can tell it which control to operate.
That is useful for humans too, but for agents it has another advantage: it creates a place where the project can enforce its own assumptions. The worktree command knows that databases must be isolated. The browser runner can choose a free port. The formatter can modify files before the read-only checks run. The test launcher can know which services it actually needs.
The agent no longer needs to remember all of this. The tool remembers.
Generic skills are starting points, not project knowledge
The same distinction has become important to me with agent skills.
Generic skills can be extremely useful. A good skill for a framework or library can teach an agent its vocabulary, point it towards the right documentation, explain common patterns and warn about easy mistakes. This is already a considerable improvement over asking every agent to rediscover the same library from scratch.
But I increasingly see generic skills as starter material rather than finished instructions.
A generic skill cannot know which parts of a library your application actually uses. It does not know which import strategy you chose, which extension points you prefer, which features you deliberately avoid, which wrappers already exist in your application, or which conventions your team has settled on.
Those choices are often more important during day-to-day implementation than knowing everything the underlying library can theoretically do.
This became quite concrete recently with my own Actual CSS project (no shame in self-promotion...).
Actual CSS now ships a starter agent skill. The idea is not that consuming applications should blindly install it and consider the problem solved. It provides the framework knowledge, useful references and tools, while explicitly leaving room for a local PROJECT.md containing application-specific choices.
I like that model because there are really two different kinds of knowledge involved.
The library knows things such as its components, enhancement system, hooks, supported classes and import paths. The application knows which of those things it actually uses, how they are composed, which local conventions apply and what an agent should do when several valid framework approaches exist.
Documentation describes possibilities. A project skill describes choices.
In practice, I think a useful workflow is to take a generic skill and let the agents help adapt it as the project evolves. If agents repeatedly need to inspect the same files before using a library correctly, that is probably useful material for the project skill. If they repeatedly choose a technically valid but locally inappropriate pattern, that decision should probably be recorded there as well.
This is another reason I do not want every detail in one giant AGENTS.md. Framework-specific knowledge belongs close to the framework skill. Project-specific decisions can extend it. The agent only needs that context when the task actually touches that area.
There is a nice side effect here: producing these skills is cheap too. An agent can inspect the library, its documentation and the way your project currently uses it, then produce a first project-specific version for review. You do not need to manually write an exhaustive mini-manual before the skill becomes useful.
I suspect library authors can help here by shipping deliberately adaptable starter skills rather than pretending they can provide the perfect universal prompt. That is the approach I am trying with Actual CSS: give agents a reliable map of the framework, then encourage consuming projects to add their own local map on top.
The goal is not to teach the agent everything.
It is to reduce how much it has to infer.
Everything has a budget, even instructions
While I was trying to make my agents follow instructions more reliably, I managed to create so many instructions that they became less reliable.
My AGENTS.md grew with every incident. Each new rule had a reasonable explanation: do not do this because it caused a problem; prefer this because the other approach caused unnecessary work; run this check before that one; use this command except in this situation.
At some point I noticed something slightly absurd: some agents were effectively only seeing or using part of the instructions. Too many rules had killed the rules.
That made me think about agentic workflows in terms of budgets. Instructions have a budget. Context has a budget. Tests have a budget. Validation has a budget. Startup time has a budget. None of these necessarily has a strict technical limit, but they all have a point after which adding more makes the system worse rather than better.
The interesting thing is that these costs tend to grow silently. A single additional instruction costs almost nothing. A single additional test costs almost nothing. An extra two-second timeout seems harmless. A password hashing test taking a fraction of a second does not look particularly concerning.
Then suddenly your supposedly fast suite takes several times longer than it used to, nobody knows why or even noticed.
I hit a particularly silly version of this with tests doing unnecessarily expensive password hashing. The individual tests were perfectly legitimate. The accumulated cost was not.
Flaky browser tests produce another version of the same problem. A test fails occasionally, so someone increases a timeout. It still fails sometimes, so the timeout increases again (hello Playwright). Eventually the flaky test is still flaky, except now everyone pays several extra seconds whenever it fails. A timeout did not fix the synchronization problem; it merely increased the budget allocated to hiding it.
This is another place where tooling helps. Measure the test suite. Report slow tests. Keep a clearly fast development loop. Make sure the tooling knows when a supposedly cheap command stops being cheap. Keep the primary agent instructions small enough that the important things remain important, and move details into focused documentation or skills when they are useful but not universally relevant.
A budget does not have to be a strict threshold. Sometimes simply making the cost visible is enough. Things that grow without feedback tend to grow too much, and agents just make that happen faster.
I am becoming less interested in spec-driven development
This one surprised me a little.
I like the idea of spec-driven development. In theory, it seems particularly well suited to agents: write a detailed specification, give it to the agent, let the implementation follow. And for some things, I still think that works extremely well.
Protocols need precision. Public APIs need contracts. Persistence invariants should be explicit. If two systems exchange data, ambiguity is generally not a feature.
But for product development, I am finding detailed implementation specifications less useful than I expected. They have an unfortunate tendency to become pseudo-code.
You start with a product requirement and gradually describe the classes, methods, components, state transitions and data structures that should implement it. Eventually I look at the document and wonder why I did not simply write the code.
Worse, a very detailed spec can accidentally freeze decisions that the implementation would have resolved more naturally. The agent now has two jobs: solve the problem and remain faithful to a speculative implementation written before the problem was actually solved.
I am increasingly getting more value from use cases and scenarios.
A use case can be written in ordinary language. It describes what someone is trying to achieve, gives examples, explains important constraints and awkward situations, and can remain deliberately incomplete.
That incompleteness is useful.
Imagine a scheduling application. A detailed specification might start defining services, repositories, commands and database structures. A use case can instead say:
A company may allow customers to book a particular service, but only during certain periods. Some customers may have additional restrictions. Staff should see the complete resulting availability, while customers should only see slots they are actually allowed to book.
That is not enough information to mechanically generate the implementation. Good. It contains the thing I actually care about: the behaviour and the reason the feature exists.
The implementation still has to make decisions, and those decisions can be reviewed in code, where they become concrete. This has made me reconsider what the word "specification" is useful for.
For me, the valuable briefing is often not the document that describes exactly how the software should be built. It is the document that makes it difficult to build the wrong thing.
The code will ultimately contain far more implementation detail than any useful specification should. Tests can capture important observable behaviour. Architecture rules can protect boundaries. Database constraints can enforce invariants. A use case preserves something different: intent.
And intent is the part that is easiest to lose when an agent can produce a technically excellent implementation very quickly.
Make the correct path the easy path
All of these things seem related now.
I stopped creating a worktree for every issue because the lifecycle cost told me that I had chosen the wrong abstraction. I started adding small project tools because repeatedly explaining operational knowledge was less reliable than encoding it. I started adapting generic skills because framework knowledge becomes much more useful once it includes the choices made by the actual project. I started watching budgets because rules and tests can quietly become counterproductive when nothing pushes back on their growth. And I started preferring scenarios over pseudo-code specifications because I want the human input to preserve intent.
None of these decisions came from an agentic-development methodology. They came from things being annoying.
That still seems like a surprisingly good design process.
The more autonomous I want an agent to be, the less I want its success to depend on memory. I do not want it to remember that this particular browser test needs another port when a second worker is running; I want the browser tooling to allocate one. I do not want it to remember six commands required to prepare a workspace; I want one workspace command. I do not want it to rediscover how this particular application uses a framework every time; I want the relevant project skill to tell it. I do not want it to remember that a certain architectural dependency is forbidden; I want the architecture check to reject it.
And I do not want a fifty-page implementation specification telling it exactly what code to write. I want a clear explanation of what the user is trying to accomplish, enough constraints to avoid solving the wrong problem, and a project that gives rapid feedback while the implementation takes shape.
Perhaps that is the direction my workflow is moving in: less memory, less ceremony, and more feedback built directly into the environment.
Not because agents need some completely different way of developing software. Quite the opposite: they are simply very effective at showing us every place where our projects still depend on somebody remembering how things are supposed to work.



