Methodology

Agents need context, not access

Agentic development keeps pushing teams into monorepos, so that an agent can see the service next door. One documentation repository, mounted into every code repository as a git submodule, hands it the same context without the same reach.

The trade a monorepo makes

An agent reads what is on disk. Put four surfaces in one working copy and it finally has the context — and write access to all four.

So when the screen fails because the endpoint returns the wrong shape, changing the endpoint is the shortest path to green. It will take it. Same incentive that gets a failing test edited instead of the code, with a wider radius.

Figure 1

One repositorythe agentone taskwrites anywhereacme/api/web/mobile/admin/One repository per surfacethe agentthe same taskwritesacme-web/src/shared-docs/the agent works hereno pathacme-api/acme-mobile/acme-admin/not in the working copy
  • writes to any of them
  • writes to one of them
  • cannot reach it at all
Both rows give the agent the whole product. Only one of them also gives it the whole product to write to.

I want the context. I do not want the reach.

One repository holds the documents

Every surface keeps its own repository. One more joins them and holds no code: shared-docs — specifications, decisions as numbered ADRs, the task queue, a progress log per surface, changelogs. It is mounted into each code repository as a submodule, at the same path in all of them.

A session — a person and an agent together — writes there. A dispatched agent only reads.

Figure 2

a sessiona person and an agent, writingshared-docsone repository, no codespec/adr/tasks/progress/changelog/agents/mounted as a submodule — the same path in eachacme-api/shared-docs/@ 9f2c1abacme-web/shared-docs/@ 9f2c1abacme-mobile/shared-docs/@ 9f2c1abacme-admin/shared-docs/@ 9f2c1abthe agent works here
  • written by a person and an agent together
  • read-only wherever it is mounted
The identical path is load-bearing, not tidiness. shared-docs/adr/0003.md names the same file from the API and from the mobile application, so a link written once resolves everywhere.

The loop

Figure 3

shared-docsspec · acceptance criteria · progresswritten, committed, pusheda person's handacme-webthe pointer moved to that commitsubmodule update --remotedispatchedthe agent's turnreads shared-docs/ · writes src/one working copy, nothing elsethe resultshared-docsprogress · changelog · the next taskwhat it finished, written downand round again
  • a person and an agent, together
  • detent, on its own
  • the agent, alone

The boundary holds mechanically rather than by instruction. acme-api/ is not in the working copy: an instruction not to touch something is a request, and an absent directory is not. The blast radius of a bad run is one repository — which is also the unit you revert, review and grant access to.

The friction: a pointer is a pin

A submodule points at one commit and stays there until somebody moves it. That is right for the thing submodules were built for — vendoring a library — and backwards here: shared-docs is not a dependency of the web client, it is the same product written down, and the whole point of it is that it is current.

Figure 4

tipshared-docs3a1f0c27bd94e1c04a8f6e21b57d9f2c1abacme-api1 commit behindacme-web3 commits behindacme-mobile4 commits behindacme-admin2 commits behindtime →
One push, N stale pointers. Nobody did anything wrong in any of those four repositories — that is the problem. No diff to review, no check that fails.

So after every write to the documents: git submodule update --remote in each repository, then commit the moved pointer. I did that by hand for months, across four repositories and then seven. Skip it once and an agent builds against Tuesday's specification — correctly, and it passes every check you have.

Doing half of it is worse. The update without the commit leaves the superproject reporting M shared-docs: a dirty tree holding nobody's work, which every check that refuses to run in one now refuses.

What detent automates

detent runs coding agents against a queue of tasks and lets nothing finish until the repository's own checks pass. Four moments in a run belong to the submodules; between them it does not touch them at all.

Figure 5

the task startstasks/todo/0042.mdthe context is refreshedsubmodule update --remotelinks checkedagainst that revisionthe agent's turnreads, then writesthe work is donethe pointer is verifiedbranch -r --contains HEADon a remote branchon nonecommitted with the taskthe code, and the revision it readthe task pausesand names the submodulenothing was committedthe pointer is put backno dirty tree left behind1234
  • lets the commit through
  • stops and asks a person
  • the run's own steps
A submodule you want none of this for goes in neverCommit: then it is not fetched, not moved and not committed, and the agent is told to leave it alone.

Two of the four I would not have got right by hand. The commit records what was read — the moved pointer goes into the task's own commit, so the history answers "which revision was this built against" instead of somebody's memory. And it is verified before it is recorded — a commit claiming the agent read revision X when it read Y is worse than no record at all, because it looks like one. Written up as ADR 0012.

The trade, line by line

Figure 6

A monorepo

One repository per surface

context

A monorepoevery surface, by reading its code

One repository per surfaceevery surface, by reading its documents

write reach

A monorepoevery surface

One repository per surfacethe one repository the task names

a change across two surfaces

A monorepoone commit

One repository per surfacetwo runs, and a document between them

the interface between two surfaces

A monorepoinferred from source

One repository per surfacean artefact somebody signed off on

what it costs you

A monorepoa bad run can reach anything

One repository per surfaceyou have to write the documents

None of it is free. The context layer is not generated: if it is not written, there is nothing to read and the agent goes back to inferring. And a document is checked by somebody reading it, not by a compiler. I would argue for writing the specification first anyway — it is still work.

Where it has run

  • Yuk Markaz 24 — a freight logistics platform: four applications to production in four months, built in parallel over this layer.
  • QMed — a platform for private clinics: seven repositories over one API, one documentation layer, thirty-three ADRs.
  • detent — the tool, open source.
detent on GitHubdetent.kazimvakhobov.com

Back to the home page