szpak

Make The Good Path Boring

A practical tutorial about using enclosure recipes to make the approved architecture shape easier for coding agents to follow than the shortcut.


Peter had turned the review comment into something more useful. The boundary could be checked, the missing seam could become a pre-edit question, and the assistant now had to report its route before touching the code. This was good progress, but one detail still bothered him. The correct path through Ledger House was clear after he explained it, but the wrong path was still easier to start.

That matters more than architects like to admit. People do not always choose the wrong structure because they disagree with the architecture. Very often they choose it because the correct structure takes six small decisions before the first useful line of code appears. A coding agent has the same problem, only faster. If the bad path is one import away, and the good path requires knowing where the application service lives, where the UI adapter goes, where the tests belong, and how the public seam should be named, the shortcut will keep looking reasonable.

So Peter stopped asking only how to prevent the wrong import. He started asking how to make the good beginning cheaper.

Act I. The Shortcut Was Faster

The assistant did not choose the wrong structure because it wanted to be clever. It chose it because the wrong structure was available. The reports screen needed customer risk data, the customer internal function already existed, and the import worked. From the assistant’s point of view, this was a normal path through the repository.

The approved path was different. Reports should own the export orchestration, customers should own risk calculation, audit should own audit access, and the UI should call report application code instead of collecting data itself. This shape was not complicated after Peter described it, but it was not obvious from an empty editor.

In practical terms, the good path required the assistant to know this before creating files:

reports/application/customer-risk-export
reports/ui/customer-risk-export
reports/tests/customer-risk-export

It also required knowing that customer risk data should come through a public customer seam, not through customers/internal. The rule prevented the bad import, but it did not yet make the first correct file appear. That is the difference between a guardrail and a road.

Peter wanted the road.

Act II. Recipes

This is where recipes become useful. A recipe is not only scaffolding. If it were only scaffolding, it would be a convenience feature and nothing more. In this series, a recipe is architectural memory turned into a repeatable starting point.

When Ledger House needs another report export, the team should not rediscover the same shape. The assistant should not invent folders. A new developer should not copy the nearest feature and hope it is the right one. The repository should offer the approved beginning.

A recipe can create the first files in the right places, with the right names, and with enough placeholders to remind the assistant where the public seams belong. It does not finish the design. It prevents the first wrong decision from being cheap.

For Peter, the report export recipe starts with a small target:

reports/application/{{ exportName }}
reports/ui/{{ exportName }}
reports/tests/{{ exportName }}

The exact file names depend on the project, but the architectural idea is stable: application code coordinates the export, UI code triggers it, tests describe the behavior, and external module data comes through public seams.

The Paved Road

Architecture often fails at the point where the approved path is more expensive than the shortcut. This is not a moral problem. It is a workflow problem. If every good feature starts with a small ceremony of folder decisions, naming decisions, test decisions, and “where does this go?” decisions, the team will eventually drift.

The paved road is the opposite. It says: if we want this shape repeated, the repository should make it easy to start.

For the assistant, this changes the route receipt. Before, the receipt only named rules and checks. Now it can also name the recipe:

Route read:
- Files: .enclosure/rules/INDEX.md, .enclosure/rules/shared/INDEX.md,
  .enclosure/rules/local/reports.md
- Reason: new report export for customer risk data.
- Owner: reports owns export orchestration.
- Public seams: customers public risk query, audit public query.
- Recipe: report-export.
- Checks to run: boundary check, report export tests.

This is a different kind of cooperation. Peter does not only tell the assistant what not to do. He gives it a known-good start.

A Small Recipe

Peter writes the recipe small on purpose. He does not try to generate the whole feature, because that would only move the guessing into a template. The recipe should create the architectural slots and leave the actual behavior for the task.

In plain language, the recipe says:

Recipe: report-export

Creates:
- reports/application/<export-name>.ts
- reports/ui/<export-name>.tsx
- reports/tests/<export-name>.test.ts

Requires:
- exportName
- public customer seam
- public audit seam

Reminder:
- UI calls reports application code.
- Reports application code calls public module seams.
- Reports must not import customers/internal/**.

This is enough. The assistant now starts with the expected structure instead of discovering it by wandering through the codebase. The reminder is not a substitute for the boundary check, and the boundary check is not a substitute for the reminder. They work together. One makes the right path visible. The other detects the wrong path if it appears anyway.

Dry Run First

Peter also makes one rule about recipes: run them in dry-run mode first. This is not because recipes are dangerous magic. It is because generated structure should be reviewed before it becomes part of the change.

The assistant should be able to say:

Recipe dry run:
- reports/application/customer-risk-export.ts
- reports/ui/customer-risk-export.tsx
- reports/tests/customer-risk-export.test.ts

Now Peter can check the shape before implementation starts. If the recipe puts UI code in the wrong place, or creates a test in a folder the team no longer uses, that is a recipe problem. Fix the recipe once, not every future pull request.

This is where recipes become more than convenience. They become a way to keep local architecture from depending on memory and review pressure.

What The Assistant Should Do

For the next export, Peter expects a sequence like this:

1. Read the route.
2. Name the owner and public seams.
3. Select the recipe.
4. Run the recipe in dry-run mode.
5. Report the created paths.
6. Implement inside those paths.
7. Run boundary checks and tests.

This sequence is slower at the beginning, but faster where it matters. The assistant does not spend time inventing structure, and Peter does not spend time explaining the same folder shape in review. The first minute becomes a small architecture agreement instead of a guess.

The important part is that the recipe does not remove judgment. If the assistant cannot name the public customer seam, the recipe should not become a way to hide that uncertainty. It should stop and ask. A paved road is useful only when it goes to the right place.

Where Recipes Help

Report exports are only one example. The same idea works anywhere the team repeats a shape and gets annoyed when people improvise it.

Good candidates are:

  • new module;
  • new feature;
  • new adapter;
  • new application service;
  • new content document;
  • new test fixture.

A recipe is especially useful when the first wrong decision is expensive. If a new adapter usually ends up in the wrong layer, make the adapter recipe. If tests are often missing because nobody remembers the fixture shape, make the fixture recipe. If new modules keep exposing internals by accident, make the module recipe create the public seam and internal folder intentionally.

Do not start by generating everything. Start with the first decision you want to stop repeating in review.

Exercise

Pick one task your team repeats. It should be concrete enough that you can name the first three files or folders before implementation starts.

Write this:

Repeated task:
Approved starting shape:
First wrong decision this prevents:
Inputs needed:
Dry-run output should show:
Checks after generation:
Stop and ask when:

Then keep the first recipe small. If the recipe prevents one real wrong decision, it is already useful. You can make it richer later, after it survives real work.

Summary

Rules tell the assistant what not to cross. Checks show when a boundary was crossed. Recipes make the approved path easier to start. The three belong together, because architecture is not only about forbidding bad paths. It is also about making good paths boring enough to repeat.

Peter does not want the assistant to be creative about folder structure every time Finance asks for another export. He wants creativity where the problem is, not where the repository already has an answer.

In the next tutorial, Peter will use the whole operating contract before a more dangerous request: not “add an export”, but “clean up this module”. That is when the assistant must prove it understands the room before it moves the walls.