Prototype. The core ideas are solid but the implementation is early — there will be rough edges, missing cases, and better ways to do things we haven't thought of yet. If something feels wrong or could be better, open an issue. Opinions, feedback, and contributions are welcome.
Version control and coordination system for AI agents. Every code file has a paired .spec file. Agents collaborate on the spec — reaching consensus — before any code is written. The code is always the output of consensus, never the battleground.
Spec is not a git replacement — it complements git. They solve different problems:
- Git tracks code changes over time — what changed, when, and by whom
- Spec coordinates intent before code is written — what should be built and why
Commit your .spec files alongside your code — they're the source of truth for intent and should live in version control with the implementation. The session logs in .spec/sessions/ are worth committing too — they're the full audit trail of how consensus was reached.
git add src/auth.php src/auth.php.spec .spec/sessions/
git commit -m "add JWT validation to auth controller"Your .gitignore should not exclude *.spec or .spec/sessions/.
.spec files and sessions are branch-scoped — they switch when you checkout. This is correct. A feature branch has its own spec files and sessions that represent the intent for that branch.
Lessons live at ~/.spec/lessons.json — outside the repo entirely. They accumulate across all projects and branches, and persist regardless of what you checkout. This is intentional: lessons are knowledge about how your agents work, not knowledge about any particular codebase.
# Orchestrator loads work into the queue
spec queue add src/auth.php "add JWT validation with 1-hour expiry"
# Agents pull from the queue and run consensus
SPEC_AGENT_ID=alice spec next
SPEC_AGENT_ID=alice spec propose src/auth.php "add JWT validation with 1-hour expiry"
SPEC_AGENT_ID=bob spec respond src/auth.php
SPEC_AGENT_ID=alice spec agree src/auth.php
SPEC_AGENT_ID=bob spec agree src/auth.php
# Implementer builds
spec build src/auth.php
# Commit spec + implementation together
git add src/auth.php src/auth.php.spec .spec/sessions/
git commit -m "add JWT validation to auth controller"cargo install --path .This installs the spec CLI. To also install the MCP server (for AI agent coordination via MCP):
cargo install --path spec-mcpBoth binaries should be on your PATH.
spec init automatically installs skills for Codex CLI and Claude Code when it detects them (~/.codex or ~/.claude). To install or reinstall manually:
spec install-skills # auto-detect Codex or Claude Code
spec install-skills --target <dir> # install to a specific directorySkills live in skills/ in this repo and are embedded in the binary — no network access needed.
If you use Claude Code or Codex CLI, Spec can piggyback on your existing subscription:
export SPEC_PROVIDER=claudecode # uses the local `claude` CLI
# or
export SPEC_PROVIDER=codex # uses the local `codex` CLINo SPEC_API_KEY needed.
export SPEC_PROVIDER=anthropic
export SPEC_API_KEY=your-key-hereA complete run from empty project to built file. Copy-paste these commands to verify the system works. Uses SPEC_PROVIDER=claudecode (requires Claude Code installed).
# 1. Create a test project
mkdir spec-test && cd spec-test
export SPEC_PROVIDER=claudecode
spec init
# 2. Load a task into the queue
spec queue add app/Http/Controllers/HomeController.php \
"create a basic Laravel controller with __invoke(): string returning 'hello'"
spec queue list
# → [ ] task_abc123 app/Http/Controllers/HomeController.php PENDING
# 3. Agent alice claims the task and proposes
SPEC_AGENT_ID=alice spec next
# → Task: task_abc123
# → Run: SPEC_AGENT_ID=alice spec propose app/Http/Controllers/HomeController.php "..."
SPEC_AGENT_ID=alice spec propose app/Http/Controllers/HomeController.php \
"create a basic Laravel controller with __invoke(): string returning 'hello'"
# → shows proposal, prompts y/N (type y)
# → STATUS: WAITING_FOR_REPLY
# 4. Agent bob responds
spec state app/Http/Controllers/HomeController.php
# → STATUS: WAITING_FOR_REPLY
SPEC_AGENT_ID=bob spec respond app/Http/Controllers/HomeController.php
# → STATUS: WAITING_FOR_AGREE
# 5. Both agents agree — session locks
SPEC_AGENT_ID=alice spec agree app/Http/Controllers/HomeController.php
SPEC_AGENT_ID=bob spec agree app/Http/Controllers/HomeController.php
# → CONSENSUS REACHED — Session locked
spec state app/Http/Controllers/HomeController.php
# → STATUS: LOCKED
# 6. Build the implementation
spec build app/Http/Controllers/HomeController.php
# → writes app/Http/Controllers/HomeController.php
# 7. Verify the queue auto-completed the task
spec queue list
# → [✓] task_abc123 app/Http/Controllers/HomeController.php DONE
# 8. Check the full session history
spec log app/Http/Controllers/HomeController.phpIf agents disagree and the session goes STUCK, the mediator intervenes:
spec state app/Http/Controllers/HomeController.php
# → STATUS: STUCK
spec clarify app/Http/Controllers/HomeController.php
# → surfaces the contradiction; agents must now respond/concede, not re-propose
SPEC_AGENT_ID=alice spec respond app/Http/Controllers/HomeController.php
SPEC_AGENT_ID=bob spec concede app/Http/Controllers/HomeController.php
SPEC_AGENT_ID=alice spec agree app/Http/Controllers/HomeController.php
SPEC_AGENT_ID=bob spec agree app/Http/Controllers/HomeController.php
spec build app/Http/Controllers/HomeController.phpIf a session gets contaminated and you need a clean start:
spec reset app/Http/Controllers/HomeController.php
# → session cleared; propose again from scratch# 1. Initialize a project
cd your-project
spec init
# 2. Agent A proposes
export SPEC_AGENT_ID=alice
spec propose app/Http/Controllers/HomeController.php "basic controller with invoke method returning 'testing'"
# 3. Agent B responds
export SPEC_AGENT_ID=bob
spec respond app/Http/Controllers/HomeController.php
# 4. If stuck, mediator clarifies
spec clarify app/Http/Controllers/HomeController.php
# 5. Agents concede or revise
export SPEC_AGENT_ID=alice
spec concede app/Http/Controllers/HomeController.php
# 6. Both agents sign off (consensus requires at least 2 distinct agents)
export SPEC_AGENT_ID=alice
spec agree app/Http/Controllers/HomeController.php
export SPEC_AGENT_ID=bob
spec agree app/Http/Controllers/HomeController.php
# → "CONSENSUS REACHED — Session locked"
# 7. Implementer writes the code
spec build app/Http/Controllers/HomeController.php
# 8. Run tests
spec test app/Http/Controllers/HomeController.php
# 9. Release
spec release app/Http/Controllers/HomeController.php productionYou always pass the source file. Spec automatically appends .spec:
app/Http/Controllers/HomeController.php ← you reference this
app/Http/Controllers/HomeController.php.spec ← Spec manages this
The .spec file is plain human-readable markdown — the agreed description of what the code file is supposed to do. It is written by Spec when consensus is reached, not by hand.
spec init creates a .spec/ folder in your project root (like .git):
your-project/
.spec/
config.json — LLM provider and model configuration
queue.json — task queue (added by orchestrator, claimed by agents)
sessions/ — session logs per spec file (JSON)
lessons/
lessons.json — lesson graph (learned from past failures)
app/Http/Controllers/
HomeController.php — the code (produced by spec build)
HomeController.php.spec — the spec (written at consensus)
Orchestrator loads queue → Agents claim tasks (spec next) → Agent A proposes
→ Agent B responds → Mediator clarifies/reframes (if stuck)
→ Agents concede/revise → All agents agree → Session locks
→ Implementer writes the code
- A build is never produced until all agents have explicitly agreed
- Consensus requires at least 2 distinct agents — one agent cannot lock a session alone (use
--soloto bypass for single-agent workflows) - The
.specfile is only written when the session fully locks - After a mediator runs
clarifyorreframe, agents must userespondorconcede— new proposals are blocked until consensus is reached
SPEC_AGENT_ID is required for all agent commands (propose, respond, concede, agree). Without it, Spec errors with a clear message.
export SPEC_AGENT_ID=alice
spec propose app/Http/Controllers/HomeController.php "..."This is intentional. Agent identity must be stable and explicit across commands — if each invocation got a different ID (e.g. hostname:pid), the system would treat them as different agents and consensus would never be reachable.
Set it once per terminal session with export, or prefix each command:
SPEC_AGENT_ID=alice spec propose app/Http/Controllers/HomeController.php "..."
SPEC_AGENT_ID=alice spec agree app/Http/Controllers/HomeController.phpInitialize a spec project in the current directory. Creates .spec/ with config and empty session/lesson stores. Automatically installs agent skills to ~/.codex/skills/ and ~/.claude/skills/ if those tools are detected.
An agent reads the current spec, queries the lesson graph, and proposes a change with full reasoning. Pass the source file — Spec manages the .spec file automatically.
SPEC_AGENT_ID=alice spec propose app/Http/Controllers/AuthController.php "add JWT validation with 1-hour expiry"
# With explicit knowledge basis
SPEC_AGENT_ID=alice spec propose app/Http/Controllers/AuthController.php "add JWT validation" \
--knowledge "We use the firebase/php-jwt library. Tokens must expire in 3600s."When running interactively (human at a terminal), the generated proposal is printed and requires confirmation (y/N) before it's committed to the session. Automated agents pipe stdin and skip the prompt automatically.
Output ends with STATUS: WAITING_FOR_REPLY — a machine-readable line agents can check without burning LLM tokens. Poll with spec state <file>.
Blocked after mediation: if a mediator has run clarify or reframe on this session, propose is blocked. Agents must use respond or concede instead.
A second agent reads the current session, reviews the latest proposal, and responds — accepting, rejecting, or suggesting modifications.
SPEC_AGENT_ID=bob spec respond app/Http/Controllers/AuthController.phpAn agent reviews the discussion and updates or withdraws its position.
SPEC_AGENT_ID=alice spec concede app/Http/Controllers/AuthController.phpAn agent explicitly signs off on the current spec state. When all agents (minimum 2) have agreed, the session locks, the .spec file is written, and the build becomes available.
SPEC_AGENT_ID=alice spec agree app/Http/Controllers/AuthController.php
SPEC_AGENT_ID=bob spec agree app/Http/Controllers/AuthController.php
# → "CONSENSUS REACHED — Session locked"Use --solo to lock immediately without requiring a second agent:
SPEC_AGENT_ID=alice spec agree app/Http/Controllers/AuthController.php --solo
# → "SOLO AGREEMENT — Session locked"Mediator-only. Surfaces a semantic contradiction without taking a position or voting.
spec clarify app/Http/Controllers/AuthController.phpMediator-only. Reframes a stuck disagreement to help agents find common ground.
spec reframe app/Http/Controllers/AuthController.phpThe implementer reads the locked spec and writes the implementation. Errors if consensus has not been reached. The LLM determines the correct file extension from context.
spec build app/Http/Controllers/HomeController.php
# → writes HomeController.php from the agreed specRuns tests for the built file. Dispatches to the appropriate test runner based on file extension (cargo test, pytest, npm test, phpunit, etc.).
spec test app/Http/Controllers/HomeController.phpRecords a release of the build to an environment. Verifies consensus before allowing release.
spec release app/Http/Controllers/HomeController.php production
spec release app/Http/Controllers/HomeController.php stagingShows the current state of the project — all spec files, open and locked sessions, agents involved, consensus state, lesson count.
spec statusReturns a single machine-readable status line — no LLM call, no noise. Designed for agents to poll cheaply.
STATUS: WAITING_FOR_REPLY — proposal submitted, no response yet
STATUS: WAITING_FOR_AGREE — discussion active, agents need to sign off
STATUS: STUCK — competing proposals from 2+ agents, or ≥3 rounds with no agreement
STATUS: LOCKED — consensus reached, build available
STATUS: NO_SESSION — no session exists for this file
spec state app/Http/Controllers/HomeController.php
# → STATUS: WAITING_FOR_REPLYAgents can use this in a loop without burning tokens:
until spec state <file> | grep -q "WAITING_FOR_AGREE\|LOCKED"; do
sleep 10
donePretty-prints the full session history — agent IDs, message types, timestamps, proposals, and reasoning.
spec log app/Http/Controllers/HomeController.phpLists all lessons in the lesson graph with their provenance.
spec lessonsClears the session for a spec file — removes the active session and all archived versions — so a fresh proposal can be made. Use when a session is contaminated or stuck in an unrecoverable state.
spec reset app/Http/Controllers/HomeController.phpBlocks until a session reaches a target status, then exits. Designed for AI agents that need to pause until a specific state arrives. Default timeout is 30 seconds — exits with STATUS: TIMEOUT if nothing changes, so the agent can loop without hitting tool time limits.
Without a file, watches all sessions and prints the matching file when found:
spec wait STUCK # watch all sessions — exits when any goes STUCK
spec wait STUCK --timeout 25 # same, 25s timeout then STATUS: TIMEOUT
spec wait app/Http/Controllers/HomeController.php LOCKED # watch one fileThe mediator uses this to start cold without knowing which file will need attention:
# loop until stuck, act, repeat
spec wait STUCK --timeout 25
# → STATUS: STUCK app/Http/Controllers/HomeController.phpBlocks indefinitely and emits a STATUS: line whenever the session changes. Useful for human-readable monitoring in a dedicated terminal.
spec watch app/Http/Controllers/HomeController.php
# → STATUS: WAITING_FOR_REPLY
# → STATUS: STUCK
# → STATUS: LOCKEDAdd a task to the work queue. The orchestrator uses this to break a high-level request into file-level tasks with explicit dependencies.
spec queue add app/Models/User.php "add email validation — return bool, no exceptions"
spec queue add app/Http/Controllers/AuthController.php "implement login using User model" \
--after task_abc123Show all tasks and their current status. Automatically syncs task state with live sessions — a task is marked done when its session is locked and built.
spec queue list
# [ ] task_abc123 app/Models/User.php PENDING
# [B] task_def456 app/Http/Controllers/... PENDING (blocked on task_abc123)
# [✓] task_ghi789 app/Http/Controllers/... DONEManually mark a task complete. Usually automatic (tasks complete when their session locks and is built), but available for manual override.
spec queue done task_abc123Claim the next unblocked, pending task from the queue for the current agent. Checks session state before handing out work — blocked tasks (whose dependencies aren't done yet) are skipped automatically.
SPEC_AGENT_ID=alice spec next
# → Task: task_abc123
# → File: app/Models/User.php
# → Intent: add email validation...
# → Run: SPEC_AGENT_ID=alice spec propose app/Models/User.php "..."Launch Claude Code or Codex CLI as a specific spec role. Sets SPEC_PROVIDER, SPEC_ROLE, and (for agent roles) SPEC_AGENT_ID automatically, then replaces the current process with the AI tool.
spec run orchestrator --with claude # Claude Code as orchestrator (loads the queue)
spec run agent alice --with claude # Claude Code as agent "alice"
spec run agent bob --with codex # Codex as agent "bob"
spec run mediator --with claude # Claude Code as mediator
spec run implementer --with codex # Codex as implementerValid roles: agent, proposer, mediator, implementer, orchestrator.
Installs agent skill files to the target directory. Defaults to ~/.codex/skills/ or ~/.claude/skills/ (auto-detected).
spec install-skills # auto-detect
spec install-skills --target ~/.claude/skills| Variable | Required | Description |
|---|---|---|
SPEC_AGENT_ID |
Yes (for agent commands) | Stable agent identity. Must be set before propose, respond, concede, agree. |
SPEC_PROVIDER |
Yes | LLM provider. Overrides .spec/config.json. See providers table below. |
SPEC_API_KEY |
Depends | API key. Not required for claudecode or codex providers. |
SPEC_MODEL |
No | Model override (e.g. claude-opus-4-7, gpt-4o). Overrides .spec/config.json. |
SPEC_ROLE |
No | Role enforcement. Set automatically by spec run. Restricts which commands are allowed. |
| Provider | SPEC_PROVIDER value |
Key required |
|---|---|---|
| Anthropic | anthropic |
SPEC_API_KEY or ANTHROPIC_API_KEY |
| OpenAI | openai |
SPEC_API_KEY or OPENAI_API_KEY |
| Ollama (local) | ollama |
No (set OLLAMA_HOST or defaults to localhost:11434) |
| Claude Code CLI | claudecode |
No — uses your existing Claude subscription |
| Codex CLI | codex |
No — uses your existing OpenAI subscription |
When set, restricts which commands an agent can run. Set automatically by spec run.
| Role | Allowed commands |
|---|---|
agent / proposer |
propose, respond, concede, agree |
mediator |
clarify, reframe |
implementer |
build, test, release, status |
orchestrator |
queue, next, status |
SPEC_PROVIDER and SPEC_API_KEY are deliberately separate from whatever keys your app uses:
# Your app's key — Spec ignores this
export ANTHROPIC_API_KEY=sk-prod-app-key
# Spec's own keys — completely isolated
export SPEC_PROVIDER=anthropic
export SPEC_API_KEY=sk-spec-billing-key
export SPEC_MODEL=claude-sonnet-4-6 # optional.spec/config.json is created by spec init and controls defaults:
{
"llm_provider": "anthropic",
"model": "claude-sonnet-4-6",
"anthropic_api_key_env": "ANTHROPIC_API_KEY"
}Environment variables always take precedence over this file.
The cleanest way to work with multiple agents is to define a shell alias for each one in your ~/.zshrc or ~/.bashrc:
alias spec-alice="SPEC_AGENT_ID=alice spec"
alias spec-bob="SPEC_AGENT_ID=bob spec"
alias spec-carol="SPEC_AGENT_ID=carol spec"Then use them directly — no export or inline env var needed:
spec-alice propose app/Http/Controllers/HomeController.php "basic controller returning 'testing'"
spec-bob respond app/Http/Controllers/HomeController.php
spec-alice agree app/Http/Controllers/HomeController.php
spec-bob agree app/Http/Controllers/HomeController.php
spec build app/Http/Controllers/HomeController.phpCommands that don't involve an agent identity (build, clarify, reframe, status, log, state, lessons) are called as plain spec with no alias needed.
The full system runs five roles in parallel. Start the long-running roles first (orchestrator, mediator, implementer), then let agents pull work from the queue.
Tab 1 — Orchestrator (loads the queue from a request or backlog)
spec run orchestrator --with claude
# → reads codebase, populates queue with ordered tasksTab 2 — Alice (agent, pulls tasks automatically)
export SPEC_AGENT_ID=alice
spec run agent alice --with claude
# → spec next → spec propose → spec respond/agree → loopsTab 3 — Bob (agent, pulls tasks automatically)
export SPEC_AGENT_ID=bob
spec run agent bob --with claude
# → spec next → spec propose → spec respond/agree → loopsTab 4 — Mediator (starts cold, waits for any session to go STUCK)
spec run mediator --with claude
# → spec wait STUCK --timeout 25 → spec clarify/reframe → loopsTab 5 — Implementer (waits for LOCKED, then builds)
spec run implementer --with claude
# → spec wait LOCKED --timeout 25 → spec build → spec test → loopsThe agents don't need to know which files to work on — they call spec next and the queue handles assignment, respecting dependencies so no agent starts a task before its prerequisites are done.
spec-mcp is a standalone MCP server that exposes the full spec protocol over stdio. It lets AI agents (Claude Code, Codex, or any MCP-compatible client) use spec tools without shell access, and pushes notifications to subscribed agents when sessions change — no polling required.
cargo install --path spec-mcpAdd to your Claude Code MCP configuration (e.g. ~/.claude/claude_desktop_config.json):
{
"mcpServers": {
"spec": {
"command": "spec-mcp",
"args": []
}
}
}spec-mcp defaults to SPEC_PROVIDER=claudecode when no provider is set — it runs inside Claude Code, so the claude CLI is always available. To use a different provider, set it in the env block:
{
"mcpServers": {
"spec": {
"command": "spec-mcp",
"args": [],
"env": {
"SPEC_PROVIDER": "anthropic",
"SPEC_API_KEY": "sk-..."
}
}
}
}All spec commands are available as MCP tools:
| Tool | Description |
|---|---|
spec_state |
Machine-readable status — no LLM call |
spec_log |
Full session history |
spec_status |
Overall project status |
spec_propose |
Propose a spec change |
spec_respond |
Respond to a proposal |
spec_concede |
Update your position |
spec_agree |
Sign off (supports solo: true) |
spec_clarify |
Mediator: surface contradictions |
spec_reframe |
Mediator: find common ground |
spec_build |
Implementer: write code from spec |
Agent identity is passed as a tool parameter (agent_id) rather than an environment variable:
{
"tool": "spec_propose",
"arguments": {
"file": "app/Http/Controllers/HomeController.php",
"intent": "add JWT validation",
"agent_id": "alice"
}
}Sessions are exposed as subscribable MCP resources:
| URI | Content |
|---|---|
spec://session/<path> |
Session JSON for a given spec file |
spec://status |
Overall project status text |
Subscribe to a session resource to receive push notifications when it changes:
{"method": "resources/subscribe", "params": {"uri": "spec://session/app/Http/Controllers/HomeController.php"}}When another agent writes to that session, spec-mcp emits:
{"method": "notifications/resources/updated", "params": {"uri": "spec://session/..."}}The agent wakes up, calls spec_state (instant, no LLM) to see what changed, and decides its next move. No polling loop, no token burn.
Spec calls executable scripts from .spec/hooks/ at defined points in the lifecycle. This is how you plug in your own deployment pipelines, notifications, linters, or gate checks — without Spec needing to know anything about your infrastructure.
spec init creates example scripts for every hook (.example extension). To activate one:
cp .spec/hooks/pre-release.example .spec/hooks/pre-release
chmod +x .spec/hooks/pre-release| Hook | When | Aborts on failure? |
|---|---|---|
post-agree |
Session locks — consensus reached | No |
post-build |
Code file written by implementer | No |
pre-release |
Before release is recorded | Yes |
post-release |
After release is recorded | No |
Every hook receives context via environment variables:
| Variable | Available in |
|---|---|
SPEC_FILE |
All hooks |
SPEC_SESSION_ID |
All hooks |
SPEC_ENV |
pre-release, post-release |
Notify Slack when consensus is reached (post-agree)
#!/bin/sh
curl -s -X POST "$SLACK_WEBHOOK" \
-H "Content-Type: application/json" \
-d "{\"text\": \"Consensus reached on $SPEC_FILE\"}"Block production releases on Fridays (pre-release)
#!/bin/sh
if [ "$SPEC_ENV" = "production" ] && [ "$(date +%u)" -eq 5 ]; then
echo "No production releases on Fridays." >&2
exit 1
fiTrigger a deployment pipeline (post-release)
#!/bin/sh
curl -s -X POST "https://ci.example.com/deploy" \
-H "Authorization: Bearer $CI_TOKEN" \
-d "env=$SPEC_ENV&file=$SPEC_FILE"Run a linter after build (post-build)
#!/bin/sh
./vendor/bin/phpstan analyse "${SPEC_FILE%.spec}"Missing hooks are silently skipped — if .spec/hooks/post-agree doesn't exist, nothing happens.
- A build is never produced until all agents run
spec agree - Consensus requires at least 2 distinct agents — one agent cannot lock a session alone (use
--soloto bypass explicitly) SPEC_AGENT_IDmust be set for all agent commands — no implicit identity- The
.specfile is plain markdown, written only at consensus - After a mediator runs
clarifyorreframe, agents mustrespondorconcede— new proposals are blocked - The mediator never votes, never decides, never proposes
- The implementer never participates in resolution
- The orchestrator never proposes, responds, or agrees — it only queues
- Every message carries reasoning — proposals alone are not enough
- Queue tasks are auto-completed when their session locks and a build is recorded — no manual tracking needed
Rust. The spec CLI has two dependencies: serde, serde_json. The spec-mcp server adds tokio and notify. LLM calls go over raw HTTP via curl — no SDKs.
Skills are embedded in the binary at compile time — spec-agent, spec-proposer, spec-mediator, spec-implementer, and spec-orchestrator are all installed via spec install-skills.