7.2 The "small modules, stitched later" pattern
Overview and links for this section of the guide.
On this page
- The core idea
- Why this pattern is powerful with LLMs
- How to choose seams (boundaries) that work
- Define contracts before implementation
- Build modules in isolation
- Stitch modules together safely
- Context management: paste less, get more
- Copy-paste prompt sequence
- Common pitfalls (and fixes)
- Where to go next
The core idea
When a task is too big for one prompt (or one context window), you win by splitting the work into small modules with explicit contracts—then integrating them.
The flow is:
- Choose seams: define module boundaries and responsibilities.
- Define contracts: inputs/outputs, error behavior, invariants.
- Build modules: implement and test each module in isolation.
- Stitch later: integrate modules with minimal glue code.
You’re using the model as a throughput engine inside boundaries you control. The seams are your architecture.
Why this pattern is powerful with LLMs
LLMs have two limitations that show up in multi-file projects:
- Context limits: they can’t hold the entire codebase at once.
- Drift: long conversations accumulate conflicting instructions and assumptions.
Small modules solve both. You can paste only the relevant module + its contract, and get high-quality output with less drift.
Instead of one 2000-line generation, you do ten 200-line generations with clear contracts. That’s faster to review, easier to test, and easier to fix.
How to choose seams (boundaries) that work
Good seams have two traits:
- they can be tested independently,
- they have a small, stable interface.
Common seam types
- Pure functions: parse/transform/validate functions with deterministic outputs.
- Adapters: “wrap this API call” or “wrap this file read” behind an interface.
- Core vs I/O: keep business logic separate from CLI/web/database layers.
- Data models: define schemas/types that everything else uses.
If a module’s job is “do the rest of the app,” that’s not a seam. That’s a dumping ground. Make seams small and specific.
Define contracts before implementation
A contract is the thing you can paste into a prompt that prevents the model from guessing.
At minimum, a contract should specify:
- function signature / inputs,
- outputs (including types / schema),
- error behavior (exceptions, error values),
- invariants (always true conditions),
- 2–5 examples.
Example contract (calculator evaluator)
Contract: calc.eval.evaluate(expression: str) -> float
Rules:
- Supports +, -, *, /, parentheses, unary minus
- Ignores whitespace
- Raises CalcSyntaxError for invalid expressions
- Raises ZeroDivisionError for division by zero
Examples:
- "2+2" => 4
- "2*(3+4)" => 14
- "2+*3" => CalcSyntaxError
You can paste a contract in 10 lines and get a correct module. That’s better than pasting 500 lines of code and hoping it infers intent.
Build modules in isolation
When implementing a module, keep the prompt narrow:
- paste only the contract and the module file(s),
- include the tests for that module,
- explicitly forbid changing other modules.
This produces tighter diffs and reduces accidental coupling.
Combine with “tests first”
The best version of this pattern is “module contract + tests first”:
- ask the model to write tests for the contract,
- confirm the tests match your intent,
- then implement the module until tests pass.
Stitch modules together safely
Integration is where systems break. Keep stitching deliberate:
- wire one module at a time,
- add one integration test per seam,
- avoid “refactor while integrating.”
The goal is to make integration errors obvious (wrong imports, wrong data shapes, wrong error handling).
If you’re calling external APIs, isolate them behind an adapter module. Then you can iterate on internals without changing the whole app.
Context management: paste less, get more
This pattern is also a context strategy:
- Keep a short “module map” (one paragraph per module).
- Paste only the module you’re changing + its direct dependencies.
- Paste tests that prove correctness.
- Don’t paste the whole repo unless you truly need cross-cutting changes.
You’re treating context like a budget, not a trash can.
Copy-paste prompt sequence
Prompt A: propose seams and contracts
We are using the “small modules, stitched later” pattern.
Goal:
[Describe the feature/app.]
Constraints:
- Language/runtime: [...]
- Dependencies: [...]
- Keep each module’s public API small
Task:
1) Propose 4–8 modules with responsibilities (one paragraph each).
2) For each module, define a contract (inputs/outputs/errors).
3) Identify which modules can be built/tested independently.
4) Stop and wait for confirmation. No code yet.
Prompt B: write tests for one module
Write tests for Module X based on this contract:
(paste contract)
Constraints:
- Do not implement Module X yet
- Do not change other modules
- Tests should be deterministic and cover edge cases
Output:
- Diff-only changes (tests only)
Prompt C: implement one module to pass tests
Implement Module X to satisfy its contract and pass the tests.
Constraints:
- Only edit Module X and any required shared types
- Keep changes minimal
- Do not introduce new dependencies
Output:
- Diff-only changes
Prompt D: stitch one module into the system
Integrate Module X into the app.
Constraints:
- Wire only the minimal path needed
- Add 1 integration test
- Do not refactor unrelated code
Output:
- Plan first, then diff-only changes
Common pitfalls (and fixes)
Pitfall: too many modules
Fix: collapse modules until you have 4–8. Too many modules create integration overhead and slow the loop.
Pitfall: contracts are vague
Fix: add examples and error behavior. “Returns a result” is not a contract. “Returns JSON matching this schema” is a contract.
Pitfall: stitching becomes a refactor
Fix: freeze internals, wire the seam, add an integration test, then refactor later.