Use when: user wants to select architecture patterns, compile a spec, iterate on constraints/NFRs, audit why patterns were selected/rejected, or finalise an architecture for implementation. Not when: no repeatable decisions needed, or constraints/NFRs are not yet known (gather those first).
A deterministic compiler that selects architecture design patterns from a canonical YAML spec. No LLM, no hidden defaults — same input always produces the same output. All architectural logic lives in pattern files; the compiler is intentionally simple.
skills/compiling-architecture/SKILL.md = how to compile and finalise architecture
skills/implementing-architecture/SKILL.md = how to implement an already-approved architecture
When to Use
You have a project spec (constraints, targets for nfr/operating_model/cost) and need to select appropriate architecture patterns
You want reproducible, reviewable architecture decisions (same spec → same patterns, every time)
You are iterating progressively on a spec — starting minimal and adding constraints over time
You need to audit why specific patterns were selected or rejected
You want cost feasibility analysis
You have an existing prototype or codebase and need to compile, validate, or re-approve the architecture it should converge to
Implementation or refactoring uncovered architecture drift, and you need to update the spec and recompile before coding continues
When NOT to Use
You need generative or creative architecture suggestions (use an LLM directly)
You are designing a one-off system with no need for repeatability
The constraints, targets for nfr/operating_model/cost aren't supported by schemas (check schemas/ first)
You want patterns that account for business logic or domain-specific rules not expressible in the spec schema (check schemas/ first)
Do not treat an existing prototype or codebase as architectural authority by default. Existing code is evidence about current reality, not approval of future architecture.
Do not use this skill to retroactively bless accidental prototype choices without making them explicit in the spec and obtaining approval.
Session-Start Checklist
Run these checks before writing any spec file, compiling anything, or creating any architecture artifact.
Verify the compiler repo is installed in a stable location
Use a persistent path, not /tmp/. The canonical examples in this skill assume one of:
Codex: ~/.codex/arch-compiler
Claude Code: ~/.claude/arch-compiler
Verify one of them exists:
bash
ls ~/.codex/arch-compiler/tools/archcompiler.py
ls ~/.claude/arch-compiler/tools/archcompiler.py
If neither exists, install the repo to a stable path before continuing. Do not clone to /tmp/ for normal use.
Confirm the application repo location with the user
All spec files and approved architecture artifacts belong in the application repo, not in the compiler repo.
Pattern-level approval is not the same as provider-level approval.
Treat the architecture as still provisional if any of these remain unresolved:
concrete cloud/runtime target
OIDC/auth provider or enforcement boundary
database/storage/queue provider choice
AI provider or model class
retention/deletion mechanism for nfr.data.retention_days
message transport and delivery semantics behind async_messaging
If those choices are deferred, say so explicitly to the human before finalising. Once they become concrete later, return to this skill, update the spec, recompile, diff the pattern set, and obtain fresh approval before implementation continues.
For brownfield systems, an existing prototype may reveal provider/runtime/boundary choices that are missing from the approved or in-progress spec. Treat those as inputs that must be made explicit in the spec review loop — not as automatically approved architecture. If the prototype exposes architecture drift, return here, update the spec, recompile, diff the result, and obtain approval before implementation continues.
Artifacts Generated
File
When written
Description
compiled-spec.yaml
always (with -o)
Full merged spec with defaults applied and assumptions section; valid re-input
selected-patterns.yaml
always (with -o)
Patterns that passed all filters, with match scores and honored rules
rejected-patterns.yaml
-v flag only
Patterns that were filtered out, with reason and filter phase
compiled-spec-<timestamp>.yaml
-t flag
Same files with UTC timestamp suffix (e.g. compiled-spec-2026-03-17T19:36:31Z.yaml)
stdout
always
Compiled spec printed to stdout; inline pattern comments in -v mode
Exit code 0 on success, 1 on validation error or unsatisfiable NFR constraints. Advisory warnings (warn_nfr, [high] cost) do not change the exit code — they are informational only.
When the spec is rejected (exit 1), the compiler prints a 💡 Suggestions block listing exactly which fields to change and what values are valid — read it before retrying.
Before Writing Any Spec — App Repo Setup
MUST-DO before writing a single line of spec or running the compiler:
Ask the user where the application repo lives. The spec file, compiled artifacts, and all architecture outputs belong in the application repo — not in the pattern compiler repo. If no app repo exists yet, ask the user to create one (or create a new directory) and confirm the path before proceeding.
Ensure git is initialised in the app repo. All architecture artifacts must be version-controlled. If the app repo has no git history, run git init and make an initial commit before writing any files.
Write the spec file into the app repo, not into the compiler working directory. Use a name like <project-name>-spec.yaml at the root of the app repo.
All compiled output goes into the app repo — not into a compiled_output/ folder inside the compiler repo.
Why this matters: Architecture artifacts are the permanent record for the application. Placing them in the compiler repo creates confusion about which repo is authoritative, and git-ignored docs/architecture/ folders in the compiler repo are silently untracked.
Authoring an Input Spec
When writing a spec on behalf of a user, you MUST:
Read schemas/canonical-schema.yaml first — this is the authoritative contract for every field, allowed value, and constraint. Do not guess field names or structure.
Read config/defaults.yaml — fields omitted from the spec are filled from here; knowing defaults prevents spec over-specification.
Consult test-specs/ — comprehensive examples covering edge cases, compliance requirements, and platform combinations. Use these as reference, not templates to copy blindly.
For brownfield systems, inspect the existing codebase to extract actual providers, runtime assumptions, storage/auth boundaries, and feature signals. Use that information to inform the spec, but do not treat the prototype stack as approved architecture until it is explicit in the spec and reviewed by the human.
If the user hasn't specified constraints.cloud, constraints.language, or constraints.platform, ask before proceeding. These three fields drive the majority of pattern selection — defaulting them silently produces a spec that doesn't reflect the user's system. Same applies to any NFR target the user cares about (availability, latency, compliance).
After writing functional.summary, cross-check implied feature flags and confirm with the user before compiling. Users rarely know the flag names — infer from the description:
If the summary mentions…
Ask about…
Calling an AI/ML model (GPT, Claude, embeddings, vision, etc.)
ai_inference: true
Message queues, events, pub/sub, Kafka, SQS
async_messaging: true
Real-time updates, WebSockets, live feeds
real_time_streaming: true
Semantic search, similarity search, embeddings
vector_search: true
Storing documents or blobs with flexible schema
document_store: true
Session cache, Redis, low-latency key lookups
key_value_store: true
Multiple tenants, tenant isolation, per-customer data
multi_tenancy: true
Scheduled jobs, bulk processing, nightly runs
batch_processing: true
Ask about each implied flag explicitly — do not silently leave implied flags at their false default.
Spec authoring checklist:
Every field used exists in schemas/canonical-schema.yaml
constraints.cloud, constraints.language, constraints.platform confirmed with user (not assumed)
NFR targets reflect actual user requirements, not defaults
Feature flags cross-checked against functional.summary — implied flags confirmed with user, not silently defaulted to false
Save spec to a user-specified file (e.g. <project-name>-spec.yaml) before compiling
Example spec:
yaml
project:
name: My Service
domain: ecommerce
functional:
summary: REST API for product catalogue
constraints:
cloud: azure # aws | azure | gcp | agnostic
language: python
platform: api # api | web | mobile | data | cli
nfr:
availability:
target: 0.999
latency:
p95Milliseconds: 100
p99Milliseconds: 200
security:
auth: jwt
All missing fields are filled from config/defaults.yaml and recorded in assumptions.
After Compilation
Once the compiler succeeds (exit 0), present results to the user by:
Summarising selected patterns — read selected-patterns.yaml and list pattern names grouped by category, with a one-line description of what each provides. Ask explicitly: "Are there any patterns here you don't want, or patterns you expected that are missing?" Do not move forward until the human has reviewed the full pattern list — patterns are not surfaced again in the pre-approval gate, so this is the human's primary chance to challenge the selection.
Diffing against previous compile (on recompile only) — if this is not the first compilation, compare the new selected-patterns.yaml against the previous one and present the diff before anything else: which patterns were added, which were removed, and the compiler's reason for each change. Do this before presenting cost results. Silent pattern churn between compilations is a common source of surprises.
If you find yourself presenting cost or pattern summaries before running a diff, stop — you skipped this step. Run:
Highlighting assumptions — call out any fields in compiled-spec.yaml under assumptions that the compiler defaulted on the user's behalf; these represent decisions the user should confirm
Surfacing advisories — if the compiler printed warn_nfr warnings, explain what they mean and what the user can do (e.g. add a throughput NFR to resolve an under-utilisation warning)
Resolving cost ceiling breaches before finalising — if the compiler printed [high] cost warnings, do NOT proceed to finalisation without explicit human sign-off. Present the breach clearly and ask the human to choose a resolution path. See below.
Offering next steps — ask if the user wants to tighten any constraints, enable feature flags, adjust any defaulted assumptions, or re-compile
Do not just dump the raw YAML at the user.
Handling cost ceiling breaches
A [high] cost warning means the compiled architecture exceeds a declared ceiling. Do not finalise until the human explicitly acknowledges and resolves it. Present the breach using this structure:
Cost ceiling exceeded — confirmation required before finalising
Ceiling
Actual
Overage
Monthly OpEx
$X
$Y
+$Z/mo
One-time CapEx
$X
$Y
+$Z
Pattern infrastructure costs(for reference only — costs come from each pattern's cost.provenance field and were estimated by LLM at authoring time, not sourced from live pricing. Check cost.provenance.source in each pattern JSON for the estimate date. Validate against current provider pricing before treating as accurate):
pattern-a — $X/mo
pattern-b — $Y/mo
Operating model costs(requires your close attention — these often dominate real TCO):
Please confirm each input is accurate for your team:
ops_team_size: <value> — number of engineers dedicated to operating this system. Default is 0, which produces $0 ops cost and understates real TCO if your team has dedicated ops engineers.
single_resource_monthly_ops_usd: <value> — fully-loaded monthly cost per ops engineer (salary + benefits + overhead). Default is $10,000.
on_call: <value> — whether the team is on-call. true applies a 1.5× multiplier reflecting SRE on-call overhead.
If any of these don't match your team's reality, update operating_model in the spec and recompile before finalising.
How would you like to proceed?
Update operating_model — correct the ops team inputs and recompile for an accurate cost picture
Raise the ceiling — update cost.ceilings in the spec to reflect the actual cost and recompile
Remove specific patterns — tell me which patterns are not required; I'll remove them and recompile
Proceed anyway — acknowledge the breach and approve as-is; the ceiling remains in the spec as a documented aspiration
Wait for the human's explicit choice. Do not guess which option they want and do not proceed to finalisation on your own.
Promoting assumptions to formal spec fields
When a user wants to adjust a defaulted value, or before finalising (see below):
Find the field under assumptions — e.g. assumptions.nfr.latency.p95Milliseconds: 500
Add it at the corresponding top-level path with the confirmed value — e.g. nfr.latency.p95Milliseconds: 100
Remove the entry from assumptions
Recompile — the compiler will respect the explicit value and not re-default it
The path mapping is direct: assumptions.<section>.<field> → <section>.<field>.
MUST-DO when promoting assumptions.patterns.<id>.*: Before copying any pattern config value into the top-level patterns: section, cross-check each value against the spec's explicit choices:
Does the defaultConfig name a specific provider (e.g. model_provider: openai, provider: supabase)? Cross-check against constraints.saas-providers and constraints.cloud. If there is a conflict — e.g. model_provider: openai but saas-providers: [anthropic] — flag it and ask the human for the correct value before promoting.
Does the defaultConfig name a language-specific framework or tool? Cross-check against constraints.language.
Do not silently promote a defaultConfig value that contradicts a spec-level constraint. The defaultConfig is a registry default, not a user decision — it must be validated against what the user actually specified before it becomes part of the authoritative architecture.
MUST-DO before final approval when provider-specific variants are selected: inspect the promoted pattern config as a system, not field-by-field. Look for contradictions such as:
cloud-specific architecture pattern defaults that imply a different auth or database provider than the human approved
generic pattern config and provider-specific variant config disagreeing on timeout, persistence, or transport semantics
a provider default changing the architecture from "agnostic" to provider-bound without that being reflected in constraints.cloud or constraints.saas-providers
If you find a contradiction, stop and resolve it in the spec before finalising. Do not bury the conflict in the approval header.
Pre-Approval Validation Gate
Before finalising, you MUST present the compiled spec's core sections to the human for explicit sign-off. Do not skip this step even if the human said "looks good" to the pattern list — the pattern list is not the architecture contract. The sections below are.
Present the following sections from compiled-spec.yaml in readable form and ask the human to confirm each:
text
constraints:
cloud: <value> # is this the right target environment?
language: <value>
platform: <value>
saas-providers: [...] # are these all the external services you intend to use?
features: # are these the right feature flags — anything missing or incorrectly enabled?
ai_inference: <value>
caching: <value>
...
nfr:
availability:
target: <value> # does this reflect your actual uptime requirement?
latency:
p95Milliseconds: <value>
p99Milliseconds: <value>
data:
pii: <value> # does this app store or process personally identifiable information?
retention_days: <value>
compliance: # are these compliance requirements accurate for your jurisdiction?
gdpr: <value>
ccpa: <value>
hipaa: <value>
sox: <value>
operating_model:
ops_team_size: <value> # how many engineers will operate this?
on_call: <value> # will someone be paged if this goes down?
deploy_freq: <value> # how often will you deploy?
cost:
ceilings:
monthly_operational_usd: <value> # is this a real constraint or a placeholder?
one_time_setup_usd: <value>
preferences:
prefer_saas_first: <value>
Then ask: "Do all of these reflect your actual system requirements? Any corrections before I finalise?"
Wait for explicit confirmation. Do not proceed to finalisation until the human confirms (or makes corrections and confirms the updated values). If corrections are made, update the spec and recompile before finalising.
Why this gate exists: The compiler promotes defaults into these sections — values the human never explicitly typed. A pattern list review does not surface these defaults. This gate ensures the human is signing off on the actual architecture contract, not just the pattern names.
MUST-DO before asking for approval: call out any architecture-binding decisions that are still open. If the user is treating them as "implementation details" but they would change providers, runtimes, message semantics, auth boundaries, retention handling, or accepted data-processing risk, tell them these are still architecture decisions and must be resolved before final approval.
Finalising as Authoritative Architecture
When the user approves the compiled spec as the definitive architectural record:
Promote all assumptions to top-level fields and remove the assumptions section — the approved architecture must have no assumptions block. Every field must be explicit so implementing agents have a single place to look with no ambiguity about what was decided vs. defaulted.
For each field under assumptions.*: move it to its top-level path, confirm the value with the human if needed, then delete the assumptions entry. Pattern configs under assumptions.patterns.<id>.* move to patterns.<id>.*. Recompile once to verify the spec is still valid with all fields explicit.
Verify before proceeding: After writing architecture.yaml, run:
bash
grep -c "^assumptions:" architecture.yaml
If the result is not 0, the assumptions block is still present — stop and complete the promotion before continuing.
Copy the finalised artifacts to the application repo and commit them there.
The pattern compiler repo (docs/architecture/ here) is a staging workspace — it is git-ignored and not a permanent record. The permanent home for the approved architecture is the application repo that will be built from it.
Copy these three files into the application repo:
text
<app-repo>/docs/architecture/architecture.yaml # rename from compiled-spec.yaml
<app-repo>/docs/architecture/selected-patterns.yaml # copy as-is
<app-repo>/docs/architecture/patterns/<id>.json # copy each selected pattern's full JSON
If the user has not told you where their application repo is, ask before proceeding — do not commit into the pattern compiler repo.
Copy the full pattern JSON for every pattern in selected-patterns.yaml — not a summary. The full JSON contains everything an implementing agent needs: description, defaultConfig, configSchema (with trade-off explanations), provides, and requires. Implementing agents will not have access to the pattern registry and will rely solely on these copied files.
Verify before proceeding: Pattern count in selected-patterns.yaml must match pattern files copied:
bash
grep -c "^- id:" <app-repo>/docs/architecture/selected-patterns.yaml
ls <app-repo>/docs/architecture/patterns/*.json | wc -l
If the counts differ, find and copy the missing pattern JSONs before continuing.
If git -C <app-repo> log --oneline -1 does not show the approved architecture commit, stop and fix that before proceeding to implementation. Approved architecture artifacts must be versioned.
Add an approval header to architecture.yaml so any agent reading the repo knows this is finalised and not a work-in-progress:
yaml
# STATUS: APPROVED
# Approved by: <name>
# Date: <date>
# Do not modify without re-running the compiler and obtaining fresh approval.
project:
...
If the result is not 1, the header is missing — add it before continuing.
Tell future agents how to consume it — when asking agents to implement features or make technical decisions, point them explicitly to these three files:
docs/architecture/architecture.yaml — binding constraints (cloud, language, platform, NFR targets, compliance). Do not deviate without human approval.
docs/architecture/selected-patterns.yaml — the approved pattern set. Implementation decisions should align with these patterns.
docs/architecture/patterns/<id>.json — full pattern JSON for each selected pattern. Read the relevant file before implementing a pattern; defaultConfig and configSchema contain the key implementation decisions and trade-offs.
Re-compilation invalidates approval — if the spec is recompiled (e.g. to add a new constraint), the output replaces the approved files and requires fresh human sign-off before agents treat it as authoritative again. The old approval header must not survive into the new architecture.yaml.
If this returns a match before the human has re-approved the new output, remove the header immediately — a stale approval header is worse than no header because it misleads implementing agents into treating unreviewed output as authoritative.
Hand off to implementing agents — the approved docs/architecture/ folder is the input contract for any implementation workflow. Use skills/implementing-architecture/SKILL.md if available, or provide the three artifact paths directly: architecture.yaml (binding constraints), selected-patterns.yaml (approved pattern set), patterns/<id>.json (full pattern detail per pattern).
Running the Compiler
bash
# Choose the canonical stable install path for your environment:
# Codex: ~/.codex/arch-compiler
# Claude: ~/.claude/arch-compiler
#
# Examples below use the Codex path; substitute ~/.claude/arch-compiler if needed.
# See all options
python3 ~/.codex/arch-compiler/tools/archcompiler.py --help
# Run shared workflow preflight before compiling
python3 ~/.codex/arch-compiler/tools/archcompiler_preflight.py --app-repo <app-repo> --mode compile
# Install dependencies
python3 -m pip install -r ~/.codex/arch-compiler/tools/requirements.txt
# Compile to stdout
python3 ~/.codex/arch-compiler/tools/archcompiler.py my-spec.yaml
# Compile + write artifact files (output directory must exist before running)
mkdir -p compiled_output/
python3 ~/.codex/arch-compiler/tools/archcompiler.py my-spec.yaml -o compiled_output/
# Verbose mode — annotates each spec field with triggered patterns; primarily useful for human review.
# For agents, selected-patterns.yaml and rejected-patterns.yaml are more actionable.
python3 ~/.codex/arch-compiler/tools/archcompiler.py my-spec.yaml -v # stdout only
python3 ~/.codex/arch-compiler/tools/archcompiler.py my-spec.yaml -o compiled_output/ -v # also writes rejected-patterns.yaml
# Add UTC timestamp to output filenames
python3 ~/.codex/arch-compiler/tools/archcompiler.py my-spec.yaml -o compiled_output/ -v -t
# Include coding-level patterns (GoF, DI, test strategies — excluded by default)
python3 ~/.codex/arch-compiler/tools/archcompiler.py my-spec.yaml --include-coding-patterns
Run commands against a canonical stable install path. Relative examples like python3 tools/archcompiler.py ... assume you are already in the compiler repo; agents often are not. Prefer a persistent install path so the compiler is discoverable across sessions without re-cloning.
Without operating_model, ops team cost defaults to zero — understating real TCO.
Disallowed patterns (explicitly exclude patterns that would otherwise be selected):
yaml
disallowed-patterns:
- ops-low-cost-observability # too complex for project scope
- ops-slo-error-budgets
Excluded patterns appear in rejected-patterns.yaml with phase: phase_2_5_disallowed_patterns.
A warning is emitted for any ID not found in the registry (typo protection).
Audit Tools
bash
# Audit pattern metadata quality (descriptions, costs, NFR rules)
python3 tools/audit_patterns.py
# Audit NFR/constraint rule paths (catch stale JSON pointer references)
python3 tools/audit_nfr_logic.py
# Audit conflict symmetry (if A conflicts B, B must conflict A)
python3 tools/audit_asymmetric_conflicts.py
See docs/tools.md for full tool reference.
Adding or Editing Patterns
What agents MAY and MAY NOT do
Action
Agent allowed?
Read existing patterns in patterns/
✅ Yes
Read schemas/pattern-schema.yaml and schemas/canonical-schema.yaml
✅ Yes
Author a new pattern file
✅ Yes — but see approval workflow below
Edit an existing pattern file
❌ No — human-only
Edit schemas/pattern-schema.yaml
❌ No — human-only
Edit schemas/canonical-schema.yaml
❌ No — human-only
Edit config/defaults.yaml
❌ No — human-only
Place a new pattern file directly into patterns/
❌ No — requires human review first
Before authoring a new pattern — search existing patterns first
MUST-DO before proposing to create any new pattern:
Search patterns/ for existing candidates — use the capability name or concept as a search term. A pattern covering the need may already exist under a different name.
Check rejected-patterns.yaml — if a candidate appears there, read its rejection reason. A rejected pattern is often one spec field change away from selection (e.g. enabling audit_logging: true to unlock secrets-vault, or adding a saas-provider to unlock a provider-specific variant).
Present the spec-change option to the human first — "Pattern X already exists but was rejected because field Y is false in your spec. Setting it to true would select it. Would you like to do that instead of authoring a new pattern?"
Only proceed to author a new pattern if no existing pattern covers the need and no spec change would unlock one.
Authoring a new pattern (agent workflow)
Hard gate: A user asking you to "author a new pattern" does not by itself authorize placing it in patterns/. Default to staging-only unless the human explicitly says to make the pattern live in patterns/.
When a user asks you to create a new pattern:
Read schemas/pattern-schema.yaml — every field, type, and required property
Read schemas/capability-vocabulary.yaml — check whether every provides / requires capability you plan to use already exists as a canonical name or alias
Read 2–3 similar existing patterns in patterns/ as structural reference
If you introduce a new capability name or alias, update schemas/capability-vocabulary.yaml as part of the same change so the registry vocabulary stays aligned with the new pattern
Write the new pattern file to a human-designated staging location outside patterns/ — e.g. staging/patterns/<id>.json — never directly into patterns/
Verify before continuing:
bash
ls staging/patterns/<id>.json # must exist
ls patterns/<id>.json # must NOT exist
If the file is in patterns/, move it back to the staging location before proceeding — an unapproved pattern in patterns/ is immediately live.
Self-review against the schema and vocabulary — audit_patterns.py only scans patterns/ and will not validate staged files. Instead, manually verify the staged file against schemas/pattern-schema.yaml and schemas/capability-vocabulary.yaml: check all required fields are present, ID matches filename, capability names are canonical (or intentionally added to the vocabulary), rules use valid JSON pointer paths, and conflict declarations are bidirectional.
Verify bidirectional conflicts: For every pattern ID listed in your new pattern's conflict declarations, check that the other pattern also declares yours:
bash
grep -l "<your-new-pattern-id>" patterns/*.json
Any file returned must contain your new pattern ID in its own conflict list — if not, flag it for the human to fix before approving.
Present the file to the human for review — explicitly state it is not yet active and awaits approval
Human moves the file into patterns/ after review, then runs python3 tools/audit_patterns.py and python3 -m pytest tests/ -q to confirm it passes — this is the only step an agent must not perform unilaterally
Do not skip the staging step. A pattern placed directly into patterns/ is immediately picked up by the compiler and all tests. Unapproved patterns can silently change compiled output for all specs.
Key pattern authoring rules
Pattern IDs must match filename (cache-aside.json → id: "cache-aside")
provides / requires capability names must align with schemas/capability-vocabulary.yaml; if you add a new canonical capability or alias, update the vocabulary in the same change
supports_constraints and supports_nfr rules use AND logic — all must match for pattern to be selected
Conflict declarations must be bidirectional — if A conflicts B, B must also declare A
Sibling variant patterns (e.g. cloud-specific variants) must each conflict with all their siblings
Never encode logic as pattern ID string matching — all logic goes in pattern metadata fields
Common Mistakes
Mistake
Fix
Writing spec fields not in schemas/canonical-schema.yaml
Only use fields that already exist in the schema — agents cannot modify schemas/canonical-schema.yaml to add new ones
Compiling without -o and expecting artifact files
Without -o, output goes to stdout only — no files are written
Output directory doesn't exist when using -o
The compiler silently writes nothing; pre-create the directory before running
Treating compiler warnings (warn_nfr, [high] cost) as errors
They are advisories; exit code is still 0 unless the spec is invalid or unsatisfiable