System design with Codex
Codex supports system design by splitting the work across its surfaces: a read-only CLI run maps the current architecture, Plan mode drafts the options, Codex cloud tests the riskiest assumptions as throwaway spikes, and parallel worktrees build competing designs against the same scenarios. The decision is recorded as an ADR and enforced by a CI rule.
This page is for the developer or tech lead who owns an architecture decision and has to defend it. The situation: every deploy of your monolith is a gamble, the team has agreed to extract the payments module into its own service, and nobody agrees on the API boundary, the data migration or what happens while both systems run. You need a decision backed by evidence by the end of the sprint, not a plan that only one person has read.
What you get from designing a service extraction with Codex
Section titled “What you get from designing a service extraction with Codex”- A six-step design loop, with the Codex surface, command and output file for each step
- Seven copy-paste prompts: the evidence map, the option comparison, two cloud spikes, the worktree bake-off, the ADR with its enforcement rule, and the design review
- A verification table that tells you which check proves each kind of design claim, and who signs off
- Recovery steps for the ways Codex-assisted design goes wrong
For the vendor-neutral method behind this page (short ADRs plus machine-checkable constraints), read architecture decisions agents can follow. This page is its Codex implementation.
Which Codex surface handles which part of a design?
Section titled “Which Codex surface handles which part of a design?”Each step produces a file you can check, so the design never lives only in a chat transcript.
| Step | Codex surface | Command | Output you keep |
|---|---|---|---|
| 1. Map the current system | CLI, read-only sandbox | codex exec -s read-only -o … | docs/architecture/payments-map.md |
| 2. Compare design options | Interactive CLI in Plan mode | /plan | docs/architecture/payments-options.md |
| 3. Test risky assumptions | Codex cloud, best-of-N | codex cloud exec --env … --attempts 2 | Spike diffs and a findings note, never merged |
| 4. Build competing designs | Managed worktrees | codex --worktree | Two prototype branches and one comparison table |
| 5. Record and enforce the decision | CLI | codex | An ADR, an AGENTS.md rule and a CI dependency rule |
| 6. Review and watch for drift | CLI (read-only), @codex review, automations | codex exec -s read-only … | Review findings and a weekly drift report |
Design work rewards more reasoning than Codex’s default effort gives it (the models hub lists the current default model and effort), so raise effort for these sessions before you consider another model. Add -c model_reasoning_effort="high" to any command on this page; the key exists in Codex CLI 0.157.1.
Map the current architecture with cited evidence
Section titled “Map the current architecture with cited evidence”A design is only as good as its picture of the current system. Run the first pass headless and read-only, so Codex can inspect everything and change nothing. Run this in your terminal from the repository root:
mkdir -p docs/architecturecodex exec -s read-only -c model_reasoning_effort="high" \ -o docs/architecture/payments-map.md \ "$(cat prompts/payments-map.md)"-s read-only blocks writes to the workspace, and -o saves Codex’s final message to the file, which the CLI itself writes. Put this prompt in prompts/payments-map.md:
A language model counts imports badly and misses dynamic wiring, so treat the dependency part of the map as a draft and compute the static graph with a tool. For a TypeScript codebase (checked on 2026-09-26 with dependency-cruiser 18.4.0):
npx dependency-cruiser@18.4.0 src --no-config --output-type metrics > docs/architecture/metrics.txtnpx dependency-cruiser@18.4.0 src --no-config --output-type mermaid -f docs/architecture/deps.mmdThe metrics file gives coupling per module (afferent, efferent and instability); the Mermaid file holds every static import edge. Then ask Codex, in a new session, to read docs/architecture/deps.mmd and list every edge the tool found that the map missed and every map edge the tool does not show. Each disagreement is either a runtime link you now know about or an error you remove. For a deeper onboarding pass on an unfamiliar repository, see understanding codebases with Codex.
Compare design options in Plan mode
Section titled “Compare design options in Plan mode”Start an interactive session with codex -c model_reasoning_effort="high" -c plan_mode_reasoning_effort="high", switch to Plan mode with /plan, and give Codex the map. Codex CLI 0.157.1 has a separate plan_mode_reasoning_effort key for Plan mode, so set both to keep the higher effort after the switch. In Plan mode the session produces a plan before any code, which is where a design discussion belongs.
The ranked list of killer assumptions is the input to the next step. An option you cannot test is an option you are taking on faith, so say that in the document.
Test the riskiest assumptions as Codex cloud spikes
Section titled “Test the riskiest assumptions as Codex cloud spikes”A spike is code written to answer one question and then thrown away. Codex cloud runs it in a container set up for your repository, so the spike does not touch your machine or your branch. ENV_ID is your cloud environment’s identifier: run codex cloud to browse environments, and see Codex cloud environments to set one up with your test database and setup script. codex cloud is marked experimental in Codex CLI 0.157.1, so check codex cloud exec --help if a flag below is rejected.
Commit docs/architecture/ and push the branch first. codex cloud exec runs against the branch on the remote (--branch, default: the current branch), not your working tree, so a spike cannot read an options file that exists only on your machine. The commands below name the branch explicitly as design/payments.
--attempts 2 asks for two independent attempts (best-of-N), which is useful here: if two attempts reach different answers to the same question, the question is not settled.
codex cloud diff prints only the files a task changed, not its final message. That is why both prompts write their findings to a file under docs/architecture/spikes/: the verdict and the evidence then appear in the diff of each attempt. When the tasks finish, compare the attempts from your terminal:
codex cloud list --env ENV_IDcodex cloud diff TASK_ID --attempt 1codex cloud diff TASK_ID --attempt 2Copy the verdicts and the evidence from those spike files into docs/architecture/payments-options.md. Do not run codex cloud apply on a spike: its job was to produce an answer, not code.
Build competing designs in parallel worktrees
Section titled “Build competing designs in parallel worktrees”When the team splits between two designs, for example synchronous REST calls against events for the transition period, build both and measure them against the same scenarios. Write the scenarios before either prototype exists; otherwise each prototype quietly picks the scenarios it wins.
-
Commit a shared scenario file,
docs/architecture/payments-scenarios.md, with 10 numbered cases: a normal charge, a refund, a duplicate webhook, the payments service down for 30 seconds, a slow database, and so on. Each case states the expected outcome. -
Start one Codex session per design, each in its own managed worktree, from two terminals:
Terminal window codex --worktree "$(cat prompts/prototype-rest.md)"codex --worktree "$(cat prompts/prototype-events.md)" -
When both finish, run the same scenario script in each worktree and fill in one comparison table.
-
Bring both results files onto the design branch, because each prototype wrote its file inside its own worktree branch. From the main checkout, on
design/payments, rungit checkout <rest-worktree-branch> -- docs/architecture/results-rest.mdandgit checkout <events-worktree-branch> -- docs/architecture/results-events.md, then commit them. This works because each prototype prompt ends by committing its results file on its worktree’s branch. The next step’s prompt reads both files.
The two prompt files differ only in the first line. Here is the REST version; replace its first line with the events design for the second file:
The comparison is now a table of scenario results rather than a whiteboard argument. See isolated Codex tasks with Git worktrees for bootstrapping and cleaning up worktrees, and multi-agent workflows when you run more than two variants.
Record the decision so later agent sessions follow it
Section titled “Record the decision so later agent sessions follow it”A decision that exists only in a document erodes the first time an agent takes a shortcut. Record it three ways: an ADR for people, a rule in AGENTS.md that Codex reads at the start of every session, and a CI check that fails when the rule is broken.
The current violations become the migration’s work list, and the rule stops new ones from appearing while the migration runs, but only once CI runs it and it can fail. The prompt sets the rule’s severity to "error", so dependency-cruiser exits non-zero on a violation; check that it did, then add this as a CI step:
npx dependency-cruiser@18.4.0 src --config .dependency-cruiser.cjsThe fitness functions guide shows the full CI job. The same guide has equivalent rules for Python and Java, and AGENTS.md optimization covers keeping that file short enough to stay useful.
Review the design and watch for drift
Section titled “Review the design and watch for drift”Before the design branch merges, get an independent review. codex review --base main gives a general review of the branch, but it cannot take a custom checklist: in Codex CLI 0.157.1, codex review accepts custom instructions only when no --base, --commit or --uncommitted is given, and --base main "…" fails with an argument error. For an architecture-specific review, run a read-only codex exec against the branch diff:
On GitHub, mention @codex review on the pull request; Codex also applies the review rules you write in AGENTS.md (OpenAI’s GitHub integration docs, checked 2026-08-28). The code review with Codex page covers setup.
For the months the migration runs, schedule a weekly drift check as a Codex automation in the desktop app or as a scheduled codex exec job in CI; scheduled Codex automations shows both. Give it the ADR and ask for a report of new imports across the boundary, new endpoints added to the monolith instead of the service, and ADR sections that no longer match the code. Let the dependency-cruiser rule do the blocking; the automation reports what a static rule cannot see.
How do you know the design is sound without reading every line?
Section titled “How do you know the design is sound without reading every line?”Nobody should read every line of two prototypes and three spikes. Check each kind of claim with the evidence that proves it:
| Claim | Evidence that proves it | Who signs off |
|---|---|---|
| “The map is complete” | Every edge cited as path:line; zero unexplained differences from the dependency-cruiser output; you spot-check three citations | The developer who owns the module |
| “The boundary holds” | Both cloud attempts return the same verdict, and the flagged reads have an agreed fix | Tech lead |
| “The migration is safe” | Row comparison output from the spike, a reproduced failure case and a written rollback per phase | Tech lead and the database owner |
| “Design A beats design B” | Both prototypes ran the same 10 committed scenarios; the results tables come from scripts/run-scenarios.sh, not from Codex’s summary | The team, in one review meeting |
| “The decision will hold” | The dependency-cruiser rule runs in CI at severity: "error" and fails the build on a deliberate test violation | Tech lead |
The last row is the one teams skip. Add a throwaway import across the boundary on a branch and confirm CI goes red before you trust the rule.
When Codex-assisted system design goes wrong
Section titled “When Codex-assisted system design goes wrong”The map misses hidden coupling. Codex finds imports and calls, but it can miss coupling through shared config values, transaction boundaries or middleware that injects dependencies at runtime. Recovery: compare the “Not visible statically” section with the tool output, then add logging at the three integration points Codex named and run the payments tests to see what really crosses the boundary.
A spike’s code ends up in production. Spikes skip error handling and security by design. Recovery: never codex cloud apply a spike; start every spike prompt with “Spike, not production code”, and rebuild the chosen design from the ADR in a fresh task.
The two attempts disagree. With --attempts 2, different verdicts mean the question depends on something the prompt did not pin down. Recovery: diff the attempts with codex cloud diff TASK_ID --attempt N, find the assumption they differ on, write it into the prompt and run it again.
The prototypes are not comparable. One prototype redefined a scenario or measured latency differently. Recovery: the scenario file is committed before the prototypes start and the prompt forbids editing it; rerun scripts/run-scenarios.sh yourself in both worktrees rather than trusting either summary.
The session loses the thread on a large monolith. Codex’s default context window (see the models hub) is finite, and a full map of a large monolith plus the options discussion can fill it. Recovery: keep each step in its own session and pass state through the files in docs/architecture/, not through the conversation.
The architecture document goes stale. A migration that takes three months drifts from the plan by month two. Recovery: the CI rule blocks new violations, the weekly drift report lists the rest, and one named person owns updating the ADR when the report finds a mismatch.