An agent-and-skills orchestration system built only from Markdown instructions, a SQLite database, and the host's native subagent primitive. No application framework. No runtime service. No harness adapter. No Git plumbing.
This repository is the first build of the design described in the implementation plan handed off under the same name.
Most AI agent systems collapse under their own weight when work gets long, risky, or reviewable. The same failure modes keep showing up:
- State lives in the conversation. When the context window fills, or the process restarts, you lose track of what was done.
- Effects happen without audit. File edits, API calls, message sends happen ad hoc. There is no durable record of what changed, who decided it, or whether it actually landed.
- The implementer is the verifier. The agent that wrote the patch is usually also the agent that says the patch is good.
- There is no retry budget. Loops either run forever or stop arbitrarily. Neither is acceptable for serious work.
- There is no escalation story. When the agent is stuck, the only option is "try again" — there is no structured way to ask a human, request a stronger model, or narrow scope.
- There is no separation of powers. The agent that investigates also writes files, also approves decisions, also marks tasks done. There is no way to constrain what a sub-task is allowed to do.
- Vendor lock-in. Most orchestrators wrap a specific AI harness, a specific runtime, a specific model API. Switching any of them means rewriting the orchestrator.
This system is an attempt to solve those failure modes with the smallest possible substrate.
A coordinator agent owns the outcome. It delegates bounded reasoning to host-native subagents through a small, fixed set of skills. The durable state — tasks, attempts, escalations, decisions, effects, audit events — lives in a SQLite database. The only piece of deterministic logic is the SQL itself: schema, transition tables, triggers, and canonical transaction recipes.
Operator
│
▼
Coordinator agent ← agents/coordinator.md
│ owns the task, chooses specialists, delegates,
│ interprets results, retries, escalates,
│ applies effects, communicates with the operator
│
├── orchestration skills ← skills/orchestrate-work/SKILL.md,
│ plus nine narrower skills
│
├── SQLite state ledger ← schema/ + queries/
│
├── host-native subagents ← agents/investigator.md,
│ agents/planner.md,
│ agents/implementer.md,
│ agents/reviewer.md
│
└── coordinator tools ← file edit, shell, messaging,
ticket APIs
The portable part of the system is everything in this repository. The non-portable part — how the host invokes a subagent — stays host-native. The boundary is the schema and the agent and skill catalogues.
The repository makes a sharp distinction between what an agent is and what a procedure does:
agents/<name>.mddeclares an agent's identity, system prompt, tool binding, capability set, required skill, output contract.skills/<name>/SKILL.mdis the procedure the agent follows.
The host wires them together. Adding a new agent type means one
file in agents/ and one in skills/. Adding a new procedure
means one skill file and an entry in the relevant agent's
"required reading" section.
These are not implementation gaps — they are design choices. The implementation plan forbids each of these.
- No orchestration application — no Go / Python / TypeScript / Rust service sitting between the coordinator and SQLite.
- No harness adapter — the coordinator calls the host's native subagent primitive directly.
- No MCP orchestration server — no extra protocol layer.
- No Git, no branches, no worktrees, no commits, no merges — the implementer returns a patch as data; the coordinator applies it to the current file.
- No per-agent workspaces, leases, or fence tokens — there is one coordinator, one database, one filesystem.
- No automatic retry of uncertain effects — an effect that
the coordinator cannot verify stays in
reconciliation-requireduntil a human decides. - No overclaiming model use — a request for a stronger model is recorded as such; only what the host actually used is recorded as observed.
- No auto-promotion of skills or agents — promotion to a
shared skill or a promoted dynamic agent is operator-authorised.
The coordinator can propose via
queries/propose-skill.sqlorqueries/propose-agent.sql, which opens adecisionsrow; the operator decides; the coordinator then runs the matching promote or reject recipe. The promotion recipes refuse to run without the operator's decision. - No dynamic principals — the coordinator can propose new
bounded-subagent archetypes at runtime (recorded in the
agentstable, promoted only with operator authority), but it can never create a new coordinator or any principal, and dynamic agents are never user-invocable. Static agents inagents/<name>.mdremain the source of truth for the four built-in archetypes.
These are the rules the schema enforces and the skills protect:
- Only the coordinator writes SQLite.
- Only the coordinator applies external effects.
- A subagent may request escalation; only the coordinator decides it.
- A subagent may request capabilities; only the coordinator grants them.
- Granted capabilities are the intersection of root policy, task policy, and the specialist's request — not whatever the subagent asks for.
- Every subagent invocation has a durable attempt record.
- Every retry creates a new attempt. Terminal attempts are immutable.
- Every task mutation uses an expected version. Drift fails the recipe atomically.
- Reviewers never implement or repair. Failed review creates a separate implementer attempt.
- Task-local specialists do not mutate shared durable instructions. They are pinned to a task id and version, with skill versions and content hashes.
- Destructive effects and shared-skill/agent promotion require operator authority.
- The database alone is sufficient to reconstruct orchestration state after restart.
Nothing to install. You need sqlite3 (system version is fine)
and a directory to drop a database file.
git clone <repo-url> orchestrator
cd orchestrator
DB=/tmp/orch_demo.db
rm -f "$DB"
# 1. Build the database from the canonical schema.
for f in schema/*.sql; do sqlite3 "$DB" < "$f"; done
# 2. Run the end-to-end success path (Section 26, success demo).
sqlite3 "$DB" < tests/e2e-success.sql
# 3. Inspect the outcome.
sqlite3 "$DB" "SELECT task_id, status, version FROM tasks;"
# task-success | completed | 7
sqlite3 "$DB" "SELECT attempt_id, status FROM attempts;"
# attempt-1 | succeeded
# attempt-reviewer | succeeded
sqlite3 "$DB" "SELECT effect_id, status FROM effects;"
# effect-1 | observed
sqlite3 "$DB" "SELECT COUNT(*) FROM events;"
# 26That is the system working end-to-end: a task was created, an implementer attempt ran, an effect was proposed / approved / prepared / applied / observed, an independent reviewer attempt ran, and the task completed. Twenty-six events are recorded in the audit trail.
DB=/tmp/orch_fail.db
rm -f "$DB"
for f in schema/*.sql; do sqlite3 "$DB" < "$f"; done
sqlite3 "$DB" < tests/e2e-failure.sql
sqlite3 "$DB" "SELECT task_id, status FROM tasks WHERE task_id='task-fail';"
# task-fail | failed
sqlite3 "$DB" "SELECT attempt_id, failure_code FROM attempts WHERE task_id='task-fail';"
# attempt-fail-1 | model-policy-violation
# attempt-fail-2 | unsupported-by-host
# attempt-fail-3 | unsupported-by-host
sqlite3 "$DB" "SELECT status FROM decisions WHERE task_id='task-fail';"
# resolvedThe child asked for a frontier model. The task ceiling was
strong-reasoning. The coordinator rejected the escalation,
retried, hit the host's unsupported-by-host ceiling, exhausted
the attempt budget, surfaced a decision to the operator, and the
operator answered. Three failed attempts, no automatic retry past
the budget.
DB=/tmp/orch_recover.db
rm -f "$DB"
for f in schema/*.sql; do sqlite3 "$DB" < "$f"; done
sqlite3 "$DB" < tests/e2e-recovery.sql
sqlite3 "$DB" "SELECT session_id, status FROM coordinator_sessions;"
# session-1 | interrupted
# session-2 | active
sqlite3 "$DB" "SELECT effect_id, status FROM effects WHERE effect_id='eff-rec';"
# eff-rec | observedA coordinator started, applied an effect halfway (left it in
applying), and "crashed". The next coordinator session marked
the old session interrupted, moved the effect to
reconciliation-required, and the operator resolved it. No
automatic retry of the uncertain effect.
orchestrator/
├── README.md this file
│
├── agents/ agent definitions (5 files)
│ ├── coordinator.md system prompt + tools + caps + entry skill
│ ├── investigator.md read-only evidence gathering
│ ├── planner.md ordered approach + conceptual effects
│ ├── implementer.md effect proposals as data
│ └── reviewer.md independent verification only
│
├── schema/ canonical SQLite schema (8 files)
│ ├── 001_initial.sql tables + indexes
│ ├── 002_views.sql read-only projections
│ ├── 003_triggers.sql transition + immutability +
│ │ audit triggers
│ ├── 004_seed_transitions.sql allowed_*_transitions rows
│ ├── 005_recipe_guards.sql recipe_check sentinel
│ ├── 006_dynamic_skills.sql skills table + transition
│ │ guards for propose/promote/reject
│ ├── 007_denial_reason.sql per-capability denial reasons
│ └── 008_dynamic_agents.sql agents table + transition
│ guards for propose/promote/reject
│
├── queries/ 41 transaction recipes
│ ├── bootstrap-root-policy.sql start here
│ ├── start-coordinator-session.sql
│ ├── create-task.sql
│ ├── start-attempt.sql capability intersection
│ ├── submit-attempt-result.sql terminal transition
│ ├── register / approve /
│ │ prepare / record-effect-* effect lifecycle
│ ├── propose-skill.sql coordinator drafts a skill
│ ├── promote-skill.sql operator-authorised promotion
│ ├── reject-skill.sql operator-authorised rejection
│ ├── propose-agent.sql coordinator drafts a bounded agent
│ ├── promote-agent.sql operator-authorised promotion
│ ├── reject-agent.sql operator-authorised rejection
│ ├── complete-task.sql
│ ├── task-history.sql reconstruct chronology
│ └── ... see file listing
│
├── schemas/ JSON Schemas (6 files)
│ ├── task-contract.schema.json
│ ├── specialist-spec.schema.json
│ ├── attempt-result.schema.json
│ ├── escalation-request.schema.json
│ ├── effect-proposal.schema.json
│ └── reviewer-result.schema.json
│
├── skills/ Markdown procedures (14 files)
│ ├── orchestrate-work/ coordinator entry point
│ ├── contract-task/ produce a task contract
│ ├── decompose-task/ split into child contracts
│ ├── select-specialist/ build a task-local specialist
│ ├── delegate-agent/ invoke a subagent
│ ├── evaluate-result/ independent checks on a result
│ ├── route-outcome/ pick the next durable action
│ ├── apply-effect/ coordinator-only effect lifecycle
│ ├── verify-result/ spawn an independent reviewer
│ ├── reconcile-state/ startup recovery
│ ├── investigator/ archetype
│ ├── planner/ archetype
│ ├── implementer/ archetype
│ └── reviewer/ archetype
│
├── docs/ longer-form documentation
│ ├── architecture.md
│ ├── state-model.md
│ ├── operating-guide.md
│ └── limitations.md
│
├── evals/ eval catalogue
│ ├── scenarios.yaml 25 scenarios from the plan
│ ├── scoring.md two-track scoring rubric
│ ├── fixtures/ sample contracts, specs, results
│ └── expected/ recorded verdicts
│
└── tests/ end-to-end SQL exercises
├── e2e-success.sql Section 26 success demo
├── e2e-failure.sql Section 26 failure demo
├── e2e-recovery.sql restart + reconciliation demo
├── e2e-skill-proposal.sql skill propose / promote / reject flow
├── e2e-agent-proposal.sql agent propose / promote / reject flow
└── README.md
When the coordinator is running, every step is visible because every step is a row. Here is roughly what a session looks like, stripped of detail:
session-1 opened 2026-07-21T15:00:01Z
task-success created contract: objective + criteria
task-success active bound to attempt-1
attempt-1 started model=standard, caps=read+propose+submit
escalation-1 requested type=stronger-model, model=strong-reasoning
escalation-1 approved reason: within ceiling, evidence supports
attempt-1 succeeded observed_model=host-model-standard-v1
effect-1 proposed file-change, risk=low
effect-1 approved by coordinator
effect-1 applying prepared
effect-1 applied via coordinator mutation tool
effect-1 observed after_sha256 captured
attempt-reviewer started model=strong-reasoning (escalated)
attempt-reviewer succeeded verdict=pass
task-success completed via coordinator
session-1 closed no work remaining
The same chronology is recoverable from a closed database with one
query: task-history.sql.
A host needs three things:
- The ability to instantiate the coordinator agent defined at
agents/coordinator.md(system prompt, tools, capabilities). - The ability to instantiate archetype agents (
agents/,investigator.md,planner.md,implementer.md,reviewer.md) when the coordinator delegates. - A SQLite execution capability (the system's
sqlite3CLI, an MCP SQL tool, or any equivalent).
The coordinator follows skills/orchestrate-work/SKILL.md and
composes the other nine coordinator skills. Subagents are
invoked through skills/delegate-agent/SKILL.md with a
specialist spec produced by skills/select-specialist/SKILL.md
and a result contract enforced by
schemas/attempt-result.schema.json.
The boundary is sharp:
agents/<name>.mdsays what the agent is.skills/<name>/SKILL.mdsays what the agent does.queries/*.sqlsays how state changes.schemas/*.jsonsays what the contracts look like.
There is no wrapper. The host reads these files and instantiates accordingly.
Yes — you can ask the coordinator for a new skill in conversation.
The coordinator holds the skill.propose and skill.promote
capabilities, but promotion is operator-authorised. The flow:
- You say: "create a skill for postgres migrations."
- The coordinator drafts the Markdown body, computes its
sha256, and runs
queries/propose-skill.sql. A row appears inskillswith statusproposed, and adecisionsrow of typeapprove-skill-promotionopens. The decision'srequest_jsoncarries the full content for you to review. - You review the content (read it from the database or via the
coordinator's view of it). You answer the decision:
{"decision": "promote", "reason": "..."}or{"decision": "reject", "reason": "..."}. - The coordinator runs
queries/promote-skill.sqlorqueries/reject-skill.sql. The recipe refuses to run unless the linked decision resolved with the matching choice — so the coordinator cannot promote skills without you. - A promoted skill is part of the durable record. Its
content_hashis what future specialist specs reference. To revise, the coordinator proposes a new version.
Skills can be shared or task-local. A shared skill is
available to any task. A task-local skill is bound to a specific
task_id and only usable for that task.
Yes — with the same operator-authorised flow as skills. The
coordinator holds the agent.propose and agent.promote
capabilities, and promotion is operator-authorised:
- You say: "create a specialist for SQL migrations."
- The coordinator drafts the agent content (same shape as
agents/<name>.md: identity, system prompt, tool binding, capability ceiling, output contract), picks thebase_archetypewhose accountability profile it inherits, computes the sha256, and runsqueries/propose-agent.sql. A row appears inagentswith statusproposed, and adecisionsrow of typeapprove-agent-promotionopens. - You review the content and answer the decision:
{"decision": "promote", "reason": "..."}or{"decision": "reject", "reason": "..."}. - The coordinator runs
queries/promote-agent.sqlorqueries/reject-agent.sql. The recipes refuse to run without your decision — the coordinator cannot promote agents alone.
Hard constraints, enforced at the data layer:
- A dynamic agent is always a
bounded-subagent— never a coordinator, never a principal, never user-invocable. It is reachable only through a specialist spec on an attempt. - Specialist specs pin it by
name,version,content_hash;select-specialistfails on any row that is notpromoted. - Its
base_archetype(investigator, planner, implementer, or reviewer) drives the capability subtractions — a dynamic agent can never holdeffect.apply,task.complete,agent.delegate,skill.promote, oragent.promote. - Promoted agents are immutable; revisions are new versions.
No host restart is needed: the coordinator builds every child
prompt dynamically at delegation time, so a promoted agent is just
prompt content read from SQLite instead of disk. Static agents in
agents/<name>.md remain the source of truth for the four
built-in archetypes; adding a new built-in archetype is still an
operator-write-then-restart operation.
README.md— the binding commitments.agents/coordinator.md— what the coordinator agent is.docs/architecture.md— the four components and why there is no fifth.docs/state-model.md— every table, every transition, every trigger.docs/operating-guide.md— what the coordinator does, step by step.docs/limitations.md— what the system deliberately does not guarantee.
For a quick read, README.md and docs/architecture.md
are enough. For implementation work, start with docs/state-model.md
and queries/.
First build. The required first demonstrations (success path,
failure path, recovery path from Section 26) all pass end-to-end
against sqlite3 3.51.0. The 25 eval scenarios are catalogued
in evals/scenarios.yaml; the DB-track portion is verified by
the e2e tests, the host-track portion is documented for hosts to
implement.
No broader behaviour should be added until the host-side integration is exercised on a real subagent primitive.