coding-os — Meta-Project Architecture
P: Definitive description of what coding-os is (a meta-project / factory), its three concentric layers, the consumer model, and the dogfood contract that makes the meta-repo a first-class instance of itself. R: Anyone editing
src/core/,src/cli/,src/adapters/, orsrc/templates/. Anyone onboarding to the codebase. Anyone deciding whether a change is DNA, mRNA, or phenotype. S: Stack-specific docs (docs/adapters/<id>.md, per-stack rules insrc/templates/<stack>/rules/) which describe ONE layer at a time. N: AGENTS.md, docs/engineering/graph_os-queries.md, docs/engineering/graph-hallucination-cures.md, docs/governance/critical-rules.md
Nav: Section Index | Docs Index
TL;DR
- coding-os is not a library an application imports.
- coding-os is a factory that emits projects shaped like itself —
identical scaffold (
.coding-os/, hooks, MCP server, AGENTS.md), swap the stack (django, nextjs, …) and the agent (claude, codex) per consumer. - The repo is also an instance of what it produces —
CLAUDE.mdis a symlink toAGENTS.md,.claude/hooks/*symlinks back intosrc/core/hooks/. This is "P5 Dogfood" in the principles list. - The first-class meta-stack (
src/templates/meta/) is what binds the meta-repo to the same skill / dimension / enforcement contract every consumer project gets. Without it, the meta-repo is the only un-instrumented consumer.
Project layout (Python src-layout)
Top-level shape — Python src-layout, industry standard (matches pip, black, flask, requests, pydantic):
coding-os/
├── src/ ← importable code (P5 Dogfood: scaffold convention)
│ ├── cli/ Factory entrypoint
│ ├── core/ DNA (thinking_os, graph_os, board_os, web, scheduled, hooks, skills, rules, docs, schemas, commands)
│ ├── adapters/ mRNA (claude, codex)
│ ├── templates/ Phenotype scaffolds (django, fastapi, go, go-fiber, nextjs, react-native, meta, python)
│ └── scripts/ Maintenance tooling (regen, capture-golden, audit)
├── tests/ Test suite at root — NOT shipped in wheel (Python convention)
├── docs/ Meta-repo development docs
│ └── _meta/ Navigation + open questions (foundation-map.md, questions.md)
├── .coding-os/ Runtime state (gitignored — session, DB, presence, logs)
├── pyproject.toml package-dir maps every importable top-level package into src/
├── Makefile
├── AGENTS.md (+ CLAUDE.md symlink)
└── README.md
Why tests/ is at root, not under src/:
Python src-layout convention. Tests are not shipped in the wheel
(pip install coding-os only fetches src/). Keeping them at root
also matches every major Python project (pip, black, flask, requests,
pydantic) and lets pytest discover them without sys.path tweaks.
The scaffold (what consumer projects from cos init receive) does
not have a top-level tests/ directory — consumer test files live
co-located inside their stack root (e.g. src/backend/tests/). The
meta-repo's top-level tests/ is the factory's own test suite, not a
deviation from scaffold convention.
Three concentric layers
Layer 1 ── core/ ─ DNA (agent-agnostic, stack-agnostic)
Layer 2 ── src/adapters/<agent>/ ─ mRNA (per-agent translation)
Layer 3 ── src/templates/<stack>/ ─ phenotype (per-stack overlay)
│
▼ cli/ (the factory — `cos init`, `cos update`, `cos sync-all`)
Layer 4 ── consumer project ─ organism (e.g. acme-shop using django + claude)
Layer 1 — src/core/ (DNA)
Agent-agnostic, stack-agnostic kernel. Anything here propagates to
every consumer project the moment a make regen-* is run (or
immediately, via live symlinks for hooks/skills/rules).
| Module | Purpose |
|---|---|
src/core/thinking_os/ |
MCP server (FastMCP). Hippocampus — memory + learning + metrics. Roles, formula composer, dispatcher. |
src/core/graph_os/ |
Knowledge graph. Corpus callosum — SQLite backend, 22 cos_graph_* tools, per-language extractors (py · ts/tsx · go · sh · php · yaml · json · toml) + framework contracts (fastapi/django/fiber/gin/next.js/laravel/wordpress/whmcs). |
src/core/board_os/ |
Scrumban planner. Prefrontal cortex — task lifecycle, swimlane sync, TASK-NNN-*.md ↔ DB. |
src/core/hooks/ |
60+ shell hooks. Single registry (registry.yaml). |
src/core/skills/ |
Markdown skill contracts (graph-explorer, codebase-explorer, thinking_os, …). |
src/core/rules/ |
Always-active rules (memory.md, thinking_os.md, test-discipline.md). |
src/core/web/ |
FastAPI Hub (port 9188) + React SPA. |
src/core/scripts/ |
Make-time helpers; never run from hooks. |
src/core/schemas/ |
JSON-schema definitions for stack.yaml / doctor-config / etc. |
Layer 2 — src/adapters/<agent>/ (mRNA)
Per-agent translation: how the kernel surfaces inside one specific AI coding agent's runtime.
| Adapter | What it ships |
|---|---|
src/adapters/claude/ |
settings.template.json rendered from src/core/hooks/registry.yaml, sdk_dispatcher.py for real Claude-Code-Sub-Agent dispatch, install.sh to wire .claude/, .mcp.json. |
src/adapters/codex/ |
hooks.template.json (smaller — Codex CLI fires fewer matchers), ensure_codex_mcp.py, enable_codex_hooks.py. |
Each src/adapters/<id>/adapter.yaml declares hook_capabilities — the
renderer skips registry entries whose {event, matcher} pair isn't in
the list. Adapter parity is bounded by the runtime, not by adapter
design.
Layer 3 — src/templates/<stack>/ (phenotype)
Per-stack overlay: skills + rules + dimension-map + scaffold for one language/framework.
| Template | Stack |
|---|---|
src/templates/_base/ |
Base scaffold (Makefile, fragments, .coding-os.yaml.template). Always installed. |
src/templates/django/ |
Django + DRF + PostgreSQL backend. |
src/templates/nextjs/ |
Next.js + React + Tailwind frontend. |
src/templates/fastapi/ |
FastAPI + Pydantic backend. |
src/templates/go/ + src/templates/go-fiber/ |
Go backends. |
src/templates/react-native/ |
RN mobile. |
src/templates/meta/ ⭐ |
The meta-stack itself — auto-loads graph-explorer + clean-code + thinking_os for src/core/**, src/cli/**, src/adapters/** edits. Closes the dogfood loop. |
A consumer project picks one or more of these via
cos init --template <stack> (repeatable) or a named --preset.
The aggregator merges all listed stacks into the AGENTS.md / .coding-os.yaml.
The fourth axis — subsystem modules (which kernel surfaces ship)
The three layers answer whose agent and which stack. A fourth, orthogonal
axis answers how much kernel a project wants:
src/core/subsystems.yaml declares the module
registry — kernel (always on) plus docs, tasks, graph, memory,
cognition, observability, hub-extras, cicd — each owning a set of hooks,
MCP tools, skills, commands, and rules. Disabling a module gates its tools
(module_disabled), self-skips its hooks, and unlinks its skills/commands/rules;
safety-category hooks are never disableable regardless of module.
Named profiles in the same file curate that surface, each listing what it turns off:
| Profile | Disables | Fits |
|---|---|---|
lite |
everything optional | kernel only — discipline + safety with a near-zero MCP surface (MCP-averse adopters) |
core |
memory, cognition, observability, cicd |
kernel + docs + tasks + graph + hub-extras |
standard (default) |
cognition, cicd |
the balanced default |
full |
— | every subsystem |
Selection happens at create (cos init --profile <name> and/or repeatable
--disable-module <id>, mirrored by the Hub Composer's module chips) and stays
adjustable afterwards via cos module enable|disable or Hub Config → Modules;
per-project state lives in .coding-os/subsystems-state.json.
The two flags are UNIONED, never merged — a profile plus
--disable-module can only ever remove more; --enable-module <id>
(repeatable) is the escape that keeps a module on against a profile, pulling its
depends_on closure with it (e.g. --profile lite --enable-module graph =
kernel + graph only). That asymmetry is why the Hub Composer pins the widest
profile and sends the exact chip state (see
hub-architecture.md). Both entrypoints
close the requested set over its dependents first
(cli.subsystems.close_over_dependents), because the state writer refuses an
unclosed set instead of cascading.
The meta-stack — closing the dogfood loop
For a long time the meta-repo was the only consumer with no stack:
src/templates/django/→ firespython-django + clean-codeskill onsrc/backend/**/*.py.src/templates/nextjs/→ firesnextjs-react + clean-code + frontend-designonsrc/frontend/**/*.tsx.- meta-repo edits to
src/core/**/*.py,src/cli/**/*.py→ NOTHING — no skill auto-load, no dimension routing, no graph-explorer nudge.
src/templates/meta/ fixes that. It declares:
dimensions:for MCP-tool authoring, graph extractors, hook authoring, adapter authoring, template authoring, CLI commands, hub routes, board, cognition, rule SSOT regen.skill_enforcement:mapping the meta-repo file globs tograph-explorer + clean-code + thinking_os.rules/meta-engineering.md— pre-edit moves (graph context, rename plan, dim doctor, doc anchor, task marker).
The aggregator picks src/templates/meta/stack.yaml up automatically (no
filter — regen_rules.py walks every src/templates/<id>/stack.yaml).
After make regen-rules lands, src/core/rules/dimension-registry.md and
src/core/rules/skill-enforcement.md carry meta entries, and
enforce-skill.sh fires Skill graph-explorer whenever the agent
edits src/core/**/*.py.
Propagation matrix — when a meta-repo edit reaches consumer
| Edit | Reaches consumer via | Latency |
|---|---|---|
src/core/hooks/*.sh |
Live symlink — no rebuild | Immediate |
src/core/thinking_os/** |
MCP server restart on consumer | One restart |
src/core/rules/*.md, src/core/skills/** |
Live symlink + cos update to refresh adapter glue |
cos update |
src/adapters/<agent>/** |
bash src/adapters/<agent>/install.sh re-renders |
Manual |
src/templates/<stack>/** |
cos update + make manifest-regen |
Manual |
src/cli/** |
New cos invocations pick up changes |
Immediate after uv tool install --editable . |
Layer routing — where to put a change
Decision table:
| Change | Layer | Path |
|---|---|---|
| New MCP tool | core | src/core/thinking_os/tools/*.py |
New cos_graph_* capability |
core | src/core/graph_os/tools/graph.py + extractor |
| New hook | core | src/core/hooks/<name>.sh + register in registry.yaml |
| New skill | core | src/core/skills/<name>/SKILL.md |
| Always-active rule | core | src/core/rules/<name>.md (or template + regen) |
| Agent-only behaviour | adapter | src/adapters/<id>/... |
| Stack-only behaviour | template | src/templates/<stack>/... |
| Meta-repo behaviour | meta-template | src/templates/meta/... |
| Web UI / Hub change | src/core/web | src/core/web/{routes,ui}/... |
When ambiguous: ask "would a consumer using a different agent / stack
benefit?" — if yes, it's src/core/. If only one agent, it's src/adapters/.
If only one stack, it's src/templates/<stack>/.
P-principles (one-liner index)
| # | Principle |
|---|---|
| P1 | SSOT-first |
| P2 | Agent-agnostic — never hardcode .claude/ in src/core/ |
| P3 | Minimal-context (3–10 files/task) |
| P4 | Diff-first |
| P5 | Dogfood — the meta-repo is also an instance |
| P6 | Log-everything via make |
| P7 | No-guessing — log unknowns to docs/_meta/questions.md |
| P8 | Adapter-SDK autonomy — never import an adapter SDK from src/core/** |
Anti-patterns to avoid
- Editing
src/core/rules/dimension-registry.mdorskill-enforcement.mddirectly. Both are regen targets — editsrc/templates/<stack>/stack.yamland runmake regen-rulesinstead. - Running
cos initinside the meta-repo. Would scaffold a duplicate. The meta-repo is bootstrapped, not initialised. - Adding a hardcoded
.claude/path tosrc/core/. P2 violation. Use$COS_AGENT_DIR. The audit hookblock-hardcoded-literals.shcatches most cases, but not every shell file. - Bypassing
src/core/hooks/registry.yaml. A hook script with no registry entry won't be rendered into any adapter.
Project lifecycle commands (cli/)
The factory CLI manages a consumer project across its whole life. All four reuse one scaffold/sync core — they differ only in entry conditions:
| Command | When | Behaviour |
|---|---|---|
cos init |
greenfield — empty dir or new --name |
Full scaffold: .coding-os/, adapters, AGENTS.md, stack docs. |
cos adopt |
brownfield — existing repo, no .coding-os |
Overlays coding-os in place without touching user code: detects stacks from build markers (pyproject.toml/package.json/go.mod/…) and records them, then runs the init scaffold with name/project_dir/force unset so no pre-existing file is overwritten. An already-adopted repo pivots to the idempotent sync path (same as a bare re-cos init). |
cos update |
adopted repo, new meta-repo version | Re-links missing adapter components + refreshes config (_sync_missing). |
cos materialize |
severing the meta-repo umbilical | Copies every coding-os symlink into a real file so the project is self-contained — keeps coding-os, just stops tracking the source. cos materialize-file <path> does one file. |
cos eject |
leaving coding-os | Removes the overlay (symlinks + .coding-os/ + generated AGENTS.md/.coding-os.yaml) and deregisters the project, keeping your code + docs byte-identical. The inverse of adopt. |
Stack auto-detection maps a build marker to its base language (not a stack
id — Rule 11), then resolves that language to its plain stack via the registry
(plain_stack_by_language); a marker with no matching stack is skipped, never
guessed.
Where to read next
| Question | Doc |
|---|---|
| What's the agent loop? | AGENTS.md § Core Loop |
| All Critical Rules? | docs/governance/critical-rules.md |
| MCP error envelope? | docs/engineering/mcp-error-envelope.md |
| Graph layer how-to? | docs/engineering/graph_os-queries.md |
| Graph hallucinations cured? | docs/engineering/graph-hallucination-cures.md |
| Hub UI internals? | docs/engineering/hub-architecture.md |
| Adapter parity contract? | docs/engineering/adapter-parity.md |
| Claude Code adapter? | docs/adapters/claude-sdk.md |