Problem
A business owner says: "I need an app so we stop losing track of customers and quotes."
Ten words, and everything that matters is missing. Who is allowed to give a discount? What happens to a quote after it is sent? Is a site a customer, or is the parent company the customer? Does the price list differ by currency? Nobody has asked, and nobody will — because the next thing that usually happens is that somebody starts writing tables.
We ran that experiment deliberately. Three agents, one brief each, same reference material, no conversation with the customer: they produced systems of 20, 16 and 7 tables. Not one of them was wrong at the level of code. They were wrong at the level of which business this is, and no amount of good engineering after that point recovers it. The one that asked questions built the system the customer recognised.
The expensive mistakes in business software are never syntax. They are a discount rule that lives in one person's head, a customer list that exists twice, a nightly job that nobody knew was required. Each one is cheap to fix while it is a sentence in a document, and expensive once it is a table with rows in it.
So the lifecycle below puts the sentences first.
The ten phases
0 FRAME what are they asking for? is there already a system here?
1 DISCOVER the module catalog -> the reference module's spec
2 UNDERSTAND roles, stories, workflows, records, shared data, rules
3 DESIGN map it onto the platform -> ERP_<MODULE>.md -> customer signs off
4 BUILD copy the platform layer, generate the business layer
5 TEST one test per story, per role, per invariant
6 PROVISION a test copy first; merge role catalogs, never replace them
7 DEPLOY git push -> CI -> registry -> CD
8 VERIFY the app reaches its own data; a real person signs in
9 OPERATE changes go back to phase 2; the next module back to phase 0
Phases 0–3 produce documents, not code. That is the whole design. Everything else on this page follows from it.
The full text an agent follows is at /modules/lifecycle.md, and it is
the same text the CLI serves as dodil://build — one source, so the two cannot drift.
Phase 0 — listen for capabilities, not labels
People say "an app", "a system", "a portal", "an ERP", "something to replace the spreadsheet". The word tells you nothing. "An app that tracks our customers, sends invoices and handles support tickets" is three modules whatever they call it.
Two questions calibrate the whole engagement, and both feed the design:
- "What do you use for this today?" — spreadsheets, an old tool, nothing. It reveals their experience and what you are replacing.
- "Shall I make the technical calls and explain them as I go, or would you like to weigh in?" — it asks about involvement, not competence. "You decide" is a fine answer.
Then recommend an order: the piece that hurts most first, the rest as a roadmap. Modular in design, incremental in delivery, shared in data, one app by default.
One decision belongs here and nowhere later: if anything on that roadmap touches money, the first module references a shared customer record rather than keeping its own. A CRM-only customer list is free today and costs a migration the day invoicing arrives.
Phase 2 — the part everyone skips
This is the only creative phase. Four outputs, all readable by the customer.
Roles, in business language
Not job titles: what each person may do and see. This becomes the permission catalog, and it is where row visibility is decided — a rule with teeth, since a record a person may not see must come back as not found, never forbidden, which would confirm it exists.
Stories, with their acceptance criteria
Three kinds, and the second is the one most methods have no place for.
A person does something:
CRM-06 · As the commercial director, I want quotes above our discount threshold routed to me, so that margin is protected without chasing approvals by email. Given a threshold of 15% and a quote with an 18% effective discount, when the rep submits it, then it waits in my queue, and the rep cannot approve it.
The system does something on its own:
CRM-10 · Every 6 hours, the system re-scores open leads against the current rules, so that the call list reflects this morning's activity rather than last week's. Given a lead that opened a pricing email an hour ago, when the run completes, then its score reflects it, and running it twice changes nothing.
The work crosses into another module:
CRM-20 · When a quote is accepted, a draft invoice appears in invoicing for the same customer and amount, so that nothing is re-keyed. Owner: the invoicing module.
The given/when/then is not decoration. It becomes the automated test in phase 5, named after the story id, which is how "is this requirement verified?" stops being a matter of opinion.
Records, with their keys
The entities in business terms first: what it means, how it is identified, what it relates to, who owns it. Keys are decided here, because DataK3 has no sequences — an entity whose identity cannot be named is an entity nobody has understood yet.
Shared data, with one writer
For every record: is it shared across modules, or owned by this one? Shared records — the customer, the product, the chart of accounts — have exactly one module that writes them; everyone else references them. A foreign key is accepted and never enforced here, so the sole-writer rule is the integrity mechanism. It has to be decided, not assumed.
Phase 3 — the design file the next agent will read
The design lands in ERP_<MODULE>.md at the root of the customer's repo: ERP_CRM.md,
ERP_INVOICING.md. One file per module, same sections every time
(template): status, people, environments, roles, stories, workflows,
records, shared data, jobs, seams, screens, what was changed from the reference, change requests,
and every decision with its date and its author.
It is named per module because another module has to find it. When invoicing arrives six weeks
later, it globs ERP_*.md and learns what a customer is, who writes it, and which sales stories
cross into finance — instead of inventing a second customer list.
Then the hard stop: the customer signs off on phases 2–3 before phase 4 starts. That is the point of writing them in language they can read.
Phase 4 — generate the business, copy the platform
The reference module supplies two different things, and confusing them is how systems get stuck:
- The business layer is generated from the design file — this customer's stages, thresholds, records and screens.
- The platform layer is copied verbatim — the database helper, the identity gate, the service account token, the Dockerfile, the workflow. These are the scars: no auth code in the app, natural keys, idempotent writes, guards on the write path, a self-check route that proves the app reached its own data.
What that looks like in practice is in the module pattern: one bucket, one sign-in, one app with a router per module, prefixed tables for what a module owns, unprefixed for what everyone shares, and — the rule that decides whether the customer can live with the result — workflow tuning lives in rows, not code. Stage names, discount thresholds, quote validity, loss reasons: if the office manager could reasonably change it, it is a row on a settings screen, not a constant we fork the module for.
Phases 5–8 — verified, not assumed
- A test per story, named for it, plus a test per role (right rows, others not found, excluded actions refused) and per invariant (totals computed not typed, re-imports idempotent, frozen records refusing edits).
- A test copy of the data first. The customer usually cannot tell which database they are changing; the agent always can, and says so in every message.
- Merge the role catalog, never replace it. Setting only your module's roles silently strips every other module's access.
- Verify what you actually built: the app opening its own bucket with its own credential, the gateway stripping forged identity headers, and a real person signing in once per role — a human step, because an agent never sets passwords.
Phase 9 — the part that decides whether they keep it
Business software is not finished; it is amended. A change request is a conversation, then autonomous work:
- Ask about the things the customer will not volunteer: what happens to the records that already exist, who is allowed to do this, what should happen automatically.
- Show the impact before doing anything — in plain words, with real counts: what changes for the team, what happens to existing records, what does not change, how it goes live, how to undo it.
- Then record the change, amend the design file first, change the code and the tests, migrate the test copy, and show it running.
- Live only on an explicit "go live", and export the affected tables before touching them.
What it produced
We ran the whole thing with a machine-tools business that had never had a system: customers in spreadsheets, quotes in inboxes, a trial of another CRM, accounting in Xero.
One conversation produced a signed-off design: three roles, sixteen stories, eleven kinds of record, four workflows, 26 dated decisions — including several the owner had never had to state before, like whether an expired quote can still be accepted.
The build that followed it: one app, prefixed tables, 36 tests named after the stories, and ten screens generated from the story list rather than invented. Three changes came in afterwards — a site-visit step before machine quotes, per-customer payment terms, a higher discount limit for VIP customers — each with questions, an impact statement with counts (158 customers, 76 draft quotes, 64 sent ones), an approval, and only then code. All three appear together in one quote screen now: a banner blocking a machine quote until the visit is recorded, the frozen payment terms, and a discount bar reading 8.00% of 10%.
Two things did not go to plan, and they are the more useful half of the report. The first attempt at a change request asked "shall I go ahead?" with the impact statement missing — approval of something the customer never saw. And a second session, resumed the next day, answered from its own memory instead of re-reading the design file and claimed a change did not exist. Both are now rules in the lifecycle: the impact statement is a message of its own, sent before the question, and every session starts by reading the design file. The method improved because the failures were visible, which is the argument for writing things down in the first place.
Nothing has gone live: the module found three consistency faults in the data platform on the way, and go-live waits for those. That, too, is the process working — they were found by a test suite that knew what each story was supposed to do.
Where to start
- Business software — the modules that exist, grouped by the part of the business they serve, each with its roles, stories and records.
- The lifecycle — the full method, phase by phase.
dodil guideanddodil://buildserve the same text. - The module pattern — how one is put together, and what belongs in rows rather than code.
- The design file template — what the customer's repo carries.
And if you are an agent reading this with the CLI installed: run dodil guide first. It is short,
and it will keep you from building a system nobody asked for.