ADR-025: The agentic-OS root layer is a separate tool over a multirepo, consuming each project's descriptor
- Status: Accepted
- Date: 2026-06-26
- Implements: spike #479 (evaluate a root-level agentic_os layer above project_init)
- Relates to: ADR-001 (scaffolder design / no LLM), ADR-017 (per-surface generator / capabilities), ADR-020 (memory backend is à-la-carte), ADR-024 (memory-tier model, #478), #498 (cross-project memory descriptor), #495 (tier-3 RAG — parked)
- Scope guard: this ADR decides shape and boundary only. It authorizes no implementation — see "Explicit do-NOT-build boundary" below.
Context
project_init is a per-project scaffolder: it drops a .agents/ layout into one
repo and exits (ADR-001 — deterministic file-ops, no long-running service, no LLM
calls, stay small). A recurring question (#479) is whether a root-level
"agentic-OS" layer should sit above it for cross-project concerns: global
memory, an MCP/tool registry, a project index, and — the concrete trigger —
tier-3 RAG, which ADR-024 §4 positions as worthwhile only at multi-project /
monorepo scale and therefore cannot justify living inside any single scaffold.
"Agentic OS" is two different things and the distinction decides feasibility:
- (a) infrastructure OS — scheduling, isolation, a runtime, observability. Multi-year, not on the table.
- (b) personal OS / "local mission control" — context + memory + tool aggregation across the projects you already have. Achievable in 2026.
This ADR is about (b). It answers #479's four questions: where the capability
lives, the minimum viable root layer, collision with project_init's
constraints, and the aggregation contract.
Decision
1. A separate root tool — not a feature inside project_init
The cross-project layer is its own tool, distinct from project_init, that
calls project_init to bootstrap repos and reads what each scaffold exposes.
Rationale, tied to existing constraints:
- A root orchestrator is intrinsically stateful and long-running (a project
registry, a refreshable index, possibly a daemon/MCP).
project_init's charter (ADR-001) forbids exactly that. Folding the root layer in would break the charter that makes the scaffolder safe to reason about. - The dependency direction must stay one-way: scaffolder produces, orchestrator consumes. A scaffolder that knew about a root registry would couple every scaffold to an orchestrator that most projects will never run.
- It keeps the blast radius small: the orchestrator can fail, change fast, or be abandoned without touching the deterministic scaffolder or any scaffolded repo.
project_init's only obligation toward the root layer is to make every child
introspectable by a stable contract (§4) — which is already most-built (#498).
2. A multirepo + registry, not a monorepo, and never branches-as-projects
The root layer operates over N independent repos, each carrying its own
.agents/ descriptor; the orchestrator keeps a registry (a list of project roots)
and indexes across them.
- Monorepo (per-subtree scaffold) is supported but not required: if a user
keeps projects as subtrees,
project_initscaffolds eachpackages/*/.agents/and the orchestrator walks subtrees. Same descriptor contract; different walk. - Branches-as-projects is rejected. A git branch is a temporal version of one tree, not a multiplexer for N projects. Using branches to hold parallel projects breaks history, CI, and the descriptor's one-root assumption.
The contract in §4 is identical for the multirepo and monorepo shapes — only the discovery walk differs (registry of roots vs. subtree glob). That is the point: the orchestrator integrates natively via the descriptor, not via bespoke per-project hooks.
3. Minimum viable root layer (what (b) is, concretely)
The smallest useful orchestrator is three read-only capabilities over the registry — no writes back into child repos, no runtime:
- Project registry — a list of project roots (multirepo) or a subtree glob
(monorepo). Hand-maintained or discovered by globbing for
.agents/config.yaml. - Memory aggregation — read each project's
.agents/memory/MEMORY.md(the stable anchor, ADR-024) into a global view; optionally a global~/.agents/memory/that links back to per-project facts. - Tool/MCP inventory — union of each project's
.agents/CAPABILITIES.md(ADR-017) so "which projects expose which MCP/skill" is answerable centrally.
Tier-3 RAG (#495), when built, is the fourth capability and lives here: one
cross-corpus index over the aggregated memory/vault/code of all registered
projects, reading the rag_endpoint descriptor field to know where (if anywhere)
a per-project engine already exists.
4. The aggregation contract: the descriptor each child already exposes
The orchestrator reads, per project, the memory descriptor standardized in
498 / ADR-024 — no new per-project artifact is required:
- Anchor (invariant across tiers):
.agents/config.yamlmemory:block withtier,stack,memory_path, and (tier-gated)vault_path,graph_path,rag_endpoint; plus.agents/CAPABILITIES.mdas the human-readable mirror. Field availability: tiers 0–2 (tier/stack/memory_path/vault_path/graph_path) ship today via #498; the tier-3rag_endpointfield is added by the seam PR (#505/#506) and is only present onobsidian-graphify-ragscaffolds — a tier-0–2 child simply omits it, which the degrade-by-tier read already handles. This ADR documents the target contract; it does not assume every child exposesrag_endpointbefore its scaffold opts into tier 3. - Degrade-by-tier: a cross-project skill reads
memory.tierand picks its retrieval path —tier ≥ 3→ query the RAG endpoint;≥ 2→ query the graph;≥ 0→ grepmemory_path. The anchors never move between tiers; higher tiers only add surfaces, so a reader written against tier 0 keeps working at tier 3. - Discovery: glob for
.agents/config.yaml; its presence + thememory:block is the registration breadcrumb. No callback, no daemon, no write-back.
This is why §1's "separate tool" is cheap to start: the contract is data the scaffolder already emits, and §3's MVP is three readers over it.
5. Collision check against project_init's constraints
- No long-running service / no LLM (ADR-001): preserved. Those constraints bind the scaffolder; the orchestrator is a different tool and may be stateful and may call an LLM (e.g. RAG). The boundary is the repo, not the ecosystem.
- Stay small: preserved.
project_initgains nothing here beyond finishing the descriptor (#498). The orchestrator's weight lives in its own codebase. - Deterministic scaffolds: preserved. Reading a descriptor is non-mutating; the orchestrator never writes back into a scaffolded repo as part of this shape.
Explicit do-NOT-build boundary
This ADR authorizes no code. In particular, do not, on the strength of this decision alone:
- build the orchestrator tool, a registry format, a daemon, or an MCP server;
- build tier-3 RAG or pick its engine (that is #495, decided by a hands-on test);
- add any write-back, callback, or registration hook into scaffolded projects — discovery is pull-only via the descriptor;
- add cross-project features inside
project_init(violates §1); - touch the (a) infrastructure-OS surface (scheduling/isolation/runtime).
The only project_init-side follow-up this unblocks is finishing #498 (the
rag_endpoint field — shipped with the tier-3 seam, #505 — plus a one-page
"contract a root project reads" doc). The orchestrator itself is a separate
future project, started only on an explicit, separate decision.
Consequences
-
498 is unblocked: its schema is now pinned (the
degrade-by-tier read contract) and its dependency on this spike is resolved.memory:descriptor + the -
495 (RAG) gains a home: it is an orchestrator capability over the aggregated
corpus, not a per-project engine — consistent with ADR-024 §4's positioning. project_initkeeps its charter intact; the cross-project ambition is recorded as a separate tool with a do-not-build boundary, not scope-crept into the scaffolder.- A companion design note (
docs/development/agentic-os-root-layer.md) sketches the layer and the aggregation contract for when the build is greenlit.