A self-running software factory. Ticket in, deployed web app out.
Overview
The full SDLC — spec, architecture, code, review, tests, deploy — run by Claude Code agents, with a human approving at three gates. It greenfields web apps from idea to production: describe what you want, and agents build, test, and deploy it from scratch, each app in its own GitHub repo (scaffolded Next.js + TypeScript + Tailwind + Jest). Large tickets are decomposed into subtasks, built in dependency order, then reviewed and tested in parallel. Watch it all live in Mission Control. Brownfield support (existing codebases, new features, bug fixes) is next. Each agent is a Claude Code session running inside Docker. It reads the full memory file, follows its skill instructions, appends its output, and moves on. No agent-to-agent chatter — the memory file is the only shared state. We tried to figure out the smallest set of building blocks that turns a ticket into a deployed app. Every factory needs these — the specific tools are up to you: Swap any of these out.
README
Minimum Viable Factory
Ticket in, deployed web app out. The full SDLC — spec, architecture, code, review, tests, deploy — run by Claude Code agents, with a human approving at three gates. It greenfields web apps from idea to production: describe what you want, and agents build, test, and deploy it from scratch, each app in its own GitHub repo (scaffolded Next.js + TypeScript + Tailwind + Jest). Large tickets are decomposed into subtasks, built in dependency order, then reviewed and tested in parallel. Watch it all live in Mission Control. Brownfield support (existing codebases, new features, bug fixes) is next.
~700 lines of Python across 16 modules. 6 skills. 5 MCPs. You can read every file in one sitting.
Contents
- How it works
- The 11 primitives
- Quickstart
- Observability — Mission Control · Linear · LangSmith
- Project layout
- Tests
- License
How it works
Ticket created in Linear
|
Webhook fires --> orchestrator/api.py (verifies Linear signature)
|
Provision infra: GitHub repo + Vercel + Supabase + Next.js/Jest scaffold
| (blocks the ticket if the repo/scaffold didn't come up)
|
Create 6 stage sub-issues in Linear (one per agent)
|
PM Agent writes spec --> memory file
| 🟢 Spec sub-issue checked off
|
[GATE 1] 🟡 Waiting: "Move to In Arch to approve."
| 🟢 Approved (or 🔴 Blocked)
|
Architect Agent writes technical plan + subtasks
| 🟢 Architecture sub-issue checked off
|
[GATE 2] 🟡 Waiting: "Move to In Dev to approve."
| 🟢 Approved (or 🔴 Blocked)
|
Decompose: parse subtasks from architecture
|
N × Dev Agents run in sequence (one per subtask, same branch — git-safe)
| 🟢 Progress posted per subtask
|
Single PR opened with all changes
| 🟢 Implementation sub-issue checked off
|
Review Agent + Test Agent run in parallel
| 🟢 Code Review + Tests sub-issues checked off
|
[GATE 3] 🟡 Waiting: "Move to In Deploy to approve."
| 🟢 Approved (or 🔴 Blocked)
|
Deploy Agent ships to Vercel + Supabase
| 🟢 Deploy sub-issue checked off
|
🟢 Done — final summary posted with repo link + deploy URL
Each agent is a Claude Code session running inside Docker. It reads the full memory file, follows its skill instructions, appends its output, and moves on. No agent-to-agent chatter — the memory file is the only shared state.
↑ Back to top
The 11 primitives
We tried to figure out the smallest set of building blocks that turns a ticket into a deployed app. Every factory needs these — the specific tools are up to you:
| # | Primitive | What It Does | This Factory Uses |
|---|---|---|---|
| 1 | Record | Where work gets tracked | Linear (project: “software factory”) |
| 2 | Memory | How agents share context | memory/ — one markdown file per ticket, append-only |
| 3 | Orchestrator | What decides who runs next | LangGraph state machine in orchestrator/ |
| 4 | Execution Env | Where agents actually run | Docker container |
| 5 | Agent Runtime | The brain behind each agent | Claude Code via claude-agent-sdk |
| 6 | Integration Layer | How agents talk to external tools | 5 MCPs: Linear, GitHub, Vercel, Supabase, Slack |
| 7 | Quality Gates | Where humans stay in the loop | LangGraph interrupt() + Slack notifications |
| 8 | Delivery Target | Where the app gets deployed | Vercel (frontend) + Supabase (database via Vercel Marketplace) |
| 9 | Observability | How you see what’s happening | Mission Control dashboard + LangSmith traces + Linear sub-issue tracking |
| 10 | Skills | What each agent knows how to do | .claude/skills/ — 6 markdown files |
| 11 | Identity & Secrets | How agents authenticate | .env file mounted into Docker |
Swap any of these out. Use Jira instead of Linear. Deploy to Railway instead of Vercel. The primitives are the pattern. The tools are interchangeable.
↑ Back to top
Quickstart
What you need
- Docker
- ngrok (or any tunnel to expose port 8000)
- API keys for Anthropic, Linear, GitHub, Vercel, Supabase, Slack
- LangSmith (optional, for tracing)
1. Clone and add your keys
git clone https://github.com/ashtilawat/minimum-viable-factory.git
cd minimum-viable-factory
cp .env.example .env
Fill in .env:
ANTHROPIC_API_KEY=sk-ant-...
LINEAR_API_KEY=lin_api_...
LINEAR_WEBHOOK_SECRET=...
GITHUB_TOKEN=ghp_...
GITHUB_ORG=your-org-or-username
VERCEL_TOKEN=...
SUPABASE_TOKEN=...
SLACK_TOKEN=xoxb-...
SLACK_WEBHOOK_URL=https://hooks.slack.com/services/...
LANGCHAIN_API_KEY=lsv2_... # optional
LANGCHAIN_PROJECT=your-project-name # optional
LANGCHAIN_TRACING_V2=true # optional
2. Set up Linear
Create these workflow states in your team settings (exact names matter):
Backlog --> In Spec --> In Arch --> In Dev --> In QA --> In Deploy --> Done --> Blocked
Create a project called software factory — all factory issues will live here.
Turn off all Pull request automations — the orchestrator handles state transitions.
3. Connect the webhook
ngrok http 8000
In Linear: Settings > API > Webhooks > New webhook
- URL:
https://your-ngrok-url.ngrok-free.app/webhook/linear - Resource types: Issues only
Copy the signing secret to LINEAR_WEBHOOK_SECRET in .env.
4. Set up Slack
Create an app at api.slack.com/apps:
- Bot scopes:
chat:write,channels:read - Install to workspace, grab the bot token (
xoxb-...) forSLACK_TOKEN - Enable Incoming Webhooks, add one to your channel for
SLACK_WEBHOOK_URL - Invite the bot:
/invite @YourAppName
5. Start the factory
docker compose build
docker compose up
Check it’s up:
curl http://localhost:8000/health
# {"status":"ok"}
Then open Mission Control at http://localhost:8000 — a read-only live dashboard of every run.
6. Create a ticket and watch it run
Write a Linear ticket describing what you want, and move it to In Spec. The factory provisions the app’s infrastructure, then runs the pipeline in the flow above — you approve (or reject) at the three gates by moving the ticket to the next state (In Arch, In Dev, In Deploy). Every step is mirrored to the Linear issue, and you can watch it live in Mission Control.
Run it locally (without Docker)
Docker is the recommended path (it bundles Node, the Claude Code CLI, and the MCP servers). But you can also run the orchestrator directly for development:
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
# Node 20+ is required for the agent sessions (claude-agent-sdk shells out to
# the Claude Code CLI) and the npx-based MCP servers:
npm install -g @anthropic-ai/claude-code
# API keys from your .env are read automatically (config.py calls load_dotenv):
uvicorn orchestrator:app --host 0.0.0.0 --port 8000 --reload
WORKSPACE_DIR defaults to /app/workspace (the container path). For local runs, override it to a writable directory — e.g. export WORKSPACE_DIR=./workspace — or just use Docker. Everything else (memory/, audit/, .claude/skills/) is read relative to the working directory, so run from the repo root. Point your Linear webhook at your tunnel (ngrok http 8000) exactly as above.
↑ Back to top
Observability
Three ways to see what’s happening — pick your altitude.
Mission Control
Open http://localhost:8000 while the factory is running. A read-only live ops console — a different lens than Linear’s board, built entirely from what the factory already writes to disk (audit/*.log + memory/*.md), so it makes no external calls and can’t affect a run.
- Live feed — the audit stream in real time (Server-Sent Events): agents starting/finishing, subtask fan-out, gates, blocks, deploys.
- Action needed — tickets waiting at a gate or blocked, each with the exact Linear state to move to. (Approvals still happen in Linear/Slack — the dashboard is read-only.)
- Throughput — shipped today, in-flight, blocked, average cycle time.
- Fleet — every ticket with its six-stage progress; click a row for a drawer that renders the full memory file (spec → architecture → PR → review → tests → deploy).
Endpoints: GET / (page), /api/state, /api/ticket/{id}, /api/events (SSE).
Linear as a dashboard
Every pipeline event is mirrored to the parent ticket, so the issue itself becomes a full record of the journey: a stage checklist at start, 🟡/🟢 comments as each agent starts and finishes (with output excerpts), gate prompts and approvals, per-subtask progress, errors and timeouts, and a final summary with the repo and deploy URL. Six sub-issues — one per stage — are checked off as the pipeline advances.
LangSmith tracing
Every external call is a nested span under the pipeline run: Linear GraphQL calls and the sub-issue lifecycle, Slack posts, memory reads and writes, each agent session and parallel subtask, gate decisions, and pipeline start/resume.
↑ Back to top
Project layout
orchestrator/
__init__.py # Lazily exposes the FastAPI app
config.py # Env vars, paths, timeouts
state.py # LangGraph state schema + pipeline trigger states
audit.py # Append-only audit logging
memory.py # Memory file init + append-under-header (lock-guarded)
linear.py # Linear GraphQL API + sub-issue lifecycle
slack.py # Slack webhook posts
agent_runner.py # Core agent runner (claude-agent-sdk), per-agent timeout
graph.py # LangGraph DAG construction
pipeline.py # Pipeline start/resume + infra provisioning/validation
api.py # FastAPI endpoints (webhook signature verification)
dashboard.py # Read-only Mission Control (state/events from disk)
static/
dashboard.html # Mission Control single-page UI
nodes/
__init__.py # Re-exports all node functions
agents.py # PM, Architect, Review, Test, Deploy nodes
dev.py # Decompose + sequential dev execution
gates.py # Human approval gates (interrupt/resume)
terminal.py # Done and blocked handlers
tests/
test_memory.py # append_memory data-loss regression
test_parse_subtasks.py # architecture -> subtask parsing
test_graph.py # graph wiring + routing functions
test_dashboard.py # dashboard parsing / status / throughput
memory/
_template.md # Bootstrapped for each new ticket
LIN-xxx.md # One file per ticket, append-only
.claude/
CLAUDE.md # Master context for all agent sessions
settings.json # MCP server configuration
skills/
spec-writing/SKILL.md # How to write a spec
architecture/SKILL.md # How to plan implementation
coding/SKILL.md # How to write code and open a PR
code-review/SKILL.md # How to review a PR
test-writing/SKILL.md # How to write and run tests
deploy-checklist/SKILL.md # How to deploy and verify
audit/
YYYY-MM-DD.log # Every factory event, append-only
workspace/
LIN-xxx/ # Cloned app repo per ticket (gitignored)
Dockerfile
docker-compose.yml
↑ Back to top
Tests
The orchestrator has a small offline test suite — it runs without any API keys or external services (agent sessions fall back to stubs when claude-agent-sdk is absent):
pip install -r requirements.txt pytest
pytest -q
Covered: the append-only memory (repeated writes to one section must all persist), the architecture → subtask parser, the graph wiring / routing, and the Mission Control dashboard (memory/audit parsing, gate + blocked detection, throughput).
↑ Back to top
License
MIT
Recommended Tools
Try a different keyword or remove a filter.
Install
npx skillfish add ashtilawat/minimum-viable-factory