S.O.F.T. C.O.D.E.
The 8 Layers of Soft Code Architecture — A Reference for Agents and Humans
SOFT = Core
Pure Python. No I/O. No frameworks. All business logic lives here. Every function either validates, decides, or sequences — never touches the outside world.
CODE = Shell
Transport, I/O, governance. HTTP routes, file operations, presenter mapping, and mechanical enforcement. Thin wrappers that call Core through the Contract wall.
SOFT — Core Layers (pure, no side effects)
| Layer | Files | Guiding Question | Hard Rule |
| S |
Shapes |
domain/models.py, domain/specs.py, schema_checklist.md |
What does the data look like? |
Frozen dataclasses only. No methods with side effects. All constants, thresholds, and vocabulary here. |
| O |
Operations |
application/decision_logic.py, input_validation.py, output_mapping.py |
What decisions does the tool make? |
Pure functions: data in → data out. No open(), no os, no requests. Every if lives here. |
| F |
Flow |
application/orchestrator.py (+ optional workflow.py) |
What order do operations run in? |
Zero branching. No if/for/while/try/with/match. ≤60 lines. A recipe card, not a chef. |
| T |
Terms |
contracts.py |
How do other systems call this tool? |
ToolInput, ToolOutput, run(). Versioned. The only import adapters may touch. |
CODE — Shell Layers (I/O, transport, governance)
| Layer | Files | Guiding Question | Hard Rule |
| C |
Channel |
adapters/flask/<tool>/app.py |
How does the request arrive? |
HTTP routes only. Calls core exclusively via contracts.run(). No business logic. |
| O |
Outward |
adapters/flask/<tool>/presenter.py |
How is the result shaped for the user? |
Maps HTTP params → contract payload, and contract result → template context. No domain rules. |
| D |
Disk |
adapters/flask/<tool>/io.py |
Where do side effects happen? |
File reads, file writes, downloads, in-memory caching. The only place side effects live. |
| E |
Enforce |
scripts/preflight.py, tools_registry.json, DECISIONS.md |
How do we know it’s correct? |
Mechanical validation. No discretion. Preflight passes or it doesn’t ship. |
Data Flow Diagram
USER REQUEST
↓
┌───────────└
│ Channel │ app.py — receives HTTP, extracts files/params
┐───────────┘
↓
┌───────────└
│ Outward │ presenter.py — builds contract payload from HTTP
┐───────────┘
↓
┌───────────└
│ Terms │ contracts.py — ToolInput → run() → ToolOutput
┐───────────┘
↓
──── THE WALL ───── nothing below does I/O
↓
┌───────────└
│ Flow │ orchestrator.py — calls operations in sequence
┐───────────┘
↓
┌───────────└
│ Operat’s │ decision_logic / input_validation / output_mapping
┐───────────┘
↓
┌───────────└
│ Shapes │ models.py (dataclasses) + specs.py (constants)
┐───────────┘
↓
──── THE WALL ───── back through to the shell
↓
┌───────────└
│ Outward │ presenter.py — formats result for template
┐───────────┘
↓
┌───────────└
│ Disk │ io.py — writes files, stores sessions
┐───────────┘
↓
┌───────────└
│ Channel │ app.py — returns HTTP response
┐───────────┘
↓
USER RESPONSE
The “Which Layer?” Decision Tree
When you’re about to write code, ask:
- Is it a dataclass or a constant? S — Shapes
- Does it contain
if/for/while business logic? O — Operations
- Does it sequence function calls with no branching? F — Flow
- Is it
ToolInput/ToolOutput/run()? T — Terms
- Does it handle HTTP requests/responses? C — Channel
- Does it convert between HTTP and domain? O — Outward
- Does it touch the filesystem or network? D — Disk
- Does it validate architecture rules? E — Enforce
Why the Contract (Terms) Is the Load-Bearing Seam
- Adapters never import from
application/ — they only see contracts.run()
- Core never knows about Flask — it only receives a
dict payload and returns a dict result
- Tool composition = one tool’s contract calling another tool’s contract. No shared internal state, no coupling
- Swappability = swap Flask for FastAPI, CLI, or a test harness — core doesn’t change at all
- Speed = the agent builds the entire SOFT half (shapes → operations → flow → terms) without ever thinking about HTTP, templates, or files. Pure logic with zero context-switching.
Build Order Maps to the Acronym
| Step | Layer | What You Produce |
| 1. Decision list | E — Enforce | DECISIONS.md |
| 2. Data shapes | S — Shapes | models.py, specs.py, contracts.py, examples |
| 3. Decision functions | O — Operations | decision_logic.py, input_validation.py, output_mapping.py + tests |
| 4. Orchestrator | F — Flow | orchestrator.py |
| 5. Adapters | C + O + D | app.py, presenter.py, io.py |
| 6. Verification | E — Enforce | preflight passes, registry updated |
Prompt-Ready Reminder
Paste this into CLAUDE.md or a system prompt to keep the agent on-track:
S — Shapes: domain/models.py, specs.py (dataclasses, constants)
O — Operations: application/decision_logic.py, input_validation.py,
output_mapping.py (pure functions, all branching here)
F — Flow: application/orchestrator.py (zero branching, ≤60 lines)
T — Terms: contracts.py (ToolInput/ToolOutput/run — the API wall)
——— THE WALL: nothing above does I/O ———
C — Channel: adapters/app.py (HTTP routes, calls contracts.run only)
O — Outward: adapters/presenter.py (HTTP ↔ domain mapping)
D — Disk: adapters/io.py (all side effects here and only here)
E — Enforce: scripts/preflight.py (mechanical rule checking)