Harness-agnostic
SDLC runs today inside Claude Code’s plugin mechanism. That’s a convenient bootstrap, not the destination. This page is the constraint that keeps the distinction honest.
The principle
Section titled “The principle”The op registry is the system. The CLI is one adapter over it. The data (markdown + frontmatter on disk) and the deterministic ops are portable: they don’t depend on Claude Code, or on any single door, to be meaningful.
A future MCP server, HTTP service, or other agent harness operates on the same registry. Claude remains a front door — not the front door.
One op, many doors
Section titled “One op, many doors”Every deterministic operation is declared once in the registry as a
defineOp descriptor and projected into each door. The contract is
restated nowhere: add a conforming module and the op appears in every
adapter with no hand-wiring (D-0007-deterministic-op-substrate §2/§5).
solutions/ontological/lib/registry.ts one defineOp descriptor path · input · output · handler │ ┌───────────────┼───────────────┐ ▼ ▼ ▼ CLI (sdlc) MCP HTTP shipped derived derived segments tool name = route = verbatim underscore-join /<seg>/<seg>[/<seg>] method by verb classAdapter projection is a pure function of the path
(D-H7FS-op-substrate-surface §5):
| Door | Projection | Example |
|---|---|---|
| CLI | path segments verbatim; interior segments are description-only group subcommands | sdlc quality baseline capture |
| MCP | tool name = underscore-join, intra-segment hyphens kept; a forward map, uniqueness enforced by project-check | quality_baseline_capture, gate_worktree-scope |
| HTTP | /<seg>/<seg>[/<seg>]; method by verb class (create/update/start → POST; get-schema/list/scan → GET) | POST /quality/baseline/capture |
The CLI is the one shipped door (solutions/ontological/cli/registry_adapter.ts); it
builds a recursive command trie from the paths and dispatches each leaf
through invokeOp. MCP and HTTP are bound to the rules above — tool names
and routes are derived, never hand-assigned — but building them is later
work, not part of the full sweep.
What the constraint demands
Section titled “What the constraint demands”Three things have to stay true.
1. The data layer is just files
Section titled “1. The data layer is just files”Tasks, milestones, decisions, standards, and the rest are plain markdown
with YAML frontmatter under docs/planning/<plural>/. No database, no
service, no Claude-specific format. Anything that can read a directory of
markdown can read the data. The moment the canonical state lives in a
proprietary store, the system is welded to whatever wrote it.
2. Ops carry no ambient context
Section titled “2. Ops carry no ambient context”A handler runs against an injected ctx — { projectRoot, dryRun, io, git, gh } — and reads no ambient cwd or env (OpCtx in
solutions/ontological/lib/registry.ts). That DI seam is exactly what makes an op
callable off-process: an MCP or HTTP caller has no working directory to
inherit. The same seam makes ops testable with fake io/git/gh, and
lets --dry-run and --output be applied once by the adapter rather than
re-coded per verb.
3. Long-running work has a lifecycle, not a request/response
Section titled “3. Long-running work has a lifecycle, not a request/response”A server or a heartbeat loop never returns a single result object, so it
can’t be a plain op — its handler would hang invokeOp’s
await-then-validate. defineService is the registry sibling for these:
same path-keyed index and help surface, but a lifecycle
(start/stop?/list?) dispatched outside the output-parse pipeline
(defineService in solutions/ontological/lib/registry.ts, §3). Lifecycle failures map
to the SERVICE_ERROR exit tier — already-running, stale pidfile,
port-in-use. The dashboard and the lease heartbeat loop are the two
services shipped today.
defineService({ path, lifecycle }) │ ┌───────────────────┼───────────────────┐ ▼ ▼ ▼ start stop? list? may never returns releases the reports running (server / loop) pidfile/port instances │ └─ dispatched OUTSIDE invokeOp's await-then-output.safeParse (a non-returning loop would hang it) — exit via SERVICE_ERRORWhat “harness-agnostic” does NOT mean
Section titled “What “harness-agnostic” does NOT mean”It does not mean the LLM is interchangeable with a non-LLM today. The
parts of the workflow that genuinely need judgment — readiness
verification, backlog triage, semantic doc-drift — are LLM-shaped right
now, and live in solutions/ontological/skills/, outside the substrate. A pure-machine
door reaches every op; it does not reach the skills that compose them
with judgment.
The point is narrower and already true: the data and the deterministic ops don’t require the LLM. One registry, projected into many doors, means a capability is reachable identically from a shell, CI, a Claude session, or a non-Claude agent — with no per-adapter contract drift.
See also
Section titled “See also”- Deterministic-first — the output contract every door projects.
- Roadmap — where the MCP/HTTP doors stand and what’s next.