| Layer | Technology |
|---|---|
| Frontend | React + Vite + TypeScript |
| UI Components | shadcn/ui + Tailwind CSS |
| Backend | FastAPI (Python 3.11+) |
| Database | SQLite + Alembic migrations |
| Background Jobs | In-process SQLiteWorker (ThreadPoolExecutor) |
| AI Executors | Claude Code CLI or Cursor Agent CLI |
- Node.js 18+ (with npm)
- Python 3.11+
No external services (Redis, etc.) required — everything runs in-process.
draft/
├── frontend/ # React + Vite + TypeScript + shadcn
│ ├── src/
│ │ ├── components/ # UI components (KanbanBoard, TicketDetailPanel, etc.)
│ │ ├── services/ # API client
│ │ └── types/ # TypeScript types
│ └── package.json
├── backend/ # FastAPI application
│ ├── app/
│ │ ├── models/ # SQLAlchemy models (Ticket, Job, Board, etc.)
│ │ ├── routers/ # API route handlers
│ │ ├── services/ # Business logic (planner, executor, worker, etc.)
│ │ ├── middleware/ # Idempotency, rate limiting
│ │ └── main.py # FastAPI app entry point
│ ├── alembic/ # Database migrations
│ ├── tests/ # pytest test suite
│ └── requirements.txt
├── draft.yaml # Executor, verification, and planner config
├── Makefile # Developer scripts
├── run.py # Unified launcher (backend + frontend)
└── README.md
npx draft-boardThat's it. This single command handles everything: checks prerequisites, sets up a Python virtual environment, installs dependencies, runs database migrations, and starts both the backend and frontend servers.
Once running:
- Frontend: http://localhost:5173
- Backend API: http://localhost:8000
- API Docs: http://localhost:8000/docs
Press Ctrl+C to stop all services gracefully.
If you prefer manual control or are contributing to Draft:
make setup # Install all dependencies (backend venv + frontend npm)
make db-migrate # Run database migrations
make run # Start backend + frontendOr run services separately in two terminals:
make dev-backend # Terminal 1: FastAPI at http://localhost:8000
make dev-frontend # Terminal 2: Vite at http://localhost:5173Draft uses git worktrees to provide isolated workspaces for each ticket. This enables safe parallel execution of multiple tickets without interference.
- When a job (execute/verify) runs for a ticket, a git worktree is created at
.draft/worktrees/{ticket_id}/ - Each worktree gets a dedicated branch:
goal/{goal_id}/ticket/{ticket_id} - All execution and verification runs in the isolated worktree directory
- Logs are written to
{worktree_path}/.draft/logs/{job_id}.log - When a ticket reaches a terminal state (done/abandoned), its worktree is automatically cleaned up
Set these environment variables in backend/.env:
# Path to the git repository (default: project root)
GIT_REPO_PATH=/path/to/your/repo
# Base branch for worktree creation (default: main, fallback: master)
BASE_BRANCH=main- The project must be a git repository
- The configured base branch must exist
If the directory is not a git repository, jobs will run without workspace isolation and log a warning.
Draft includes a verification pipeline that runs configurable commands to verify ticket implementations.
Create or edit draft.yaml at the repository root:
verify_config:
commands:
- "pytest tests/"
- "npm run lint"- When a verify job runs, it loads commands from
draft.yaml - Each command executes in the ticket's isolated worktree directory
- Commands run sequentially; verification stops on first failure
- Evidence is captured for each command:
- stdout and stderr are saved to files
- Exit codes are recorded
- All evidence is linked to the ticket and job
Based on verification outcome:
- All commands succeed: ticket moves to
needs_humanfor review - Any command fails: ticket moves to
blockedwith failure details
Evidence is displayed in the ticket detail drawer in the UI:
- Expand each command to view stdout/stderr
- Green checkmarks indicate successful commands
- Red X marks indicate failures with exit codes
| Method | Endpoint | Description |
|---|---|---|
| GET | /tickets/{id}/evidence |
List all evidence for a ticket |
| GET | /evidence/{id}/stdout |
Get stdout content (plain text) |
| GET | /evidence/{id}/stderr |
Get stderr content (plain text) |
Draft uses an in-process SQLiteWorker (ThreadPoolExecutor + SQLite job queue) to run background jobs. No external services like Redis or Celery are required.
# 1. Create a goal
curl -X POST http://localhost:8000/goals \
-H "Content-Type: application/json" \
-d '{"title": "Test Goal", "description": "A test goal"}'
# Returns: {"id": "<goal_id>", ...}
# 2. Create a ticket
curl -X POST http://localhost:8000/tickets \
-H "Content-Type: application/json" \
-d '{"goal_id": "<goal_id>", "title": "Test Ticket"}'
# Returns: {"id": "<ticket_id>", ...}
# 3. Enqueue an execute job
curl -X POST http://localhost:8000/tickets/<ticket_id>/run
# Returns: {"id": "<job_id>", "status": "queued", ...}
# 4. Check job status (wait a moment for the worker to process)
curl http://localhost:8000/jobs/<job_id>
# Returns: {"id": "<job_id>", "status": "succeeded", "logs": "...", ...}
# 5. Get raw logs
curl http://localhost:8000/jobs/<job_id>/logs
# Returns plain text log output
# 6. List all jobs for a ticket
curl http://localhost:8000/tickets/<ticket_id>/jobs
# Returns: {"jobs": [...], "total": 1}
# 7. Enqueue a verify job
curl -X POST http://localhost:8000/tickets/<ticket_id>/verify
# Returns: {"id": "<job_id>", "status": "queued", ...}
# 8. Cancel a running job (best-effort)
curl -X POST http://localhost:8000/jobs/<job_id>/cancel
# Returns: {"id": "<job_id>", "status": "canceled", "message": "..."}| Command | Description |
|---|---|
make setup |
Install all dependencies (backend venv + frontend npm) |
make run |
Start backend + frontend (2 processes) |
make dev-backend |
Run FastAPI server with hot reload |
make dev-frontend |
Run Vite dev server with HMR |
make db-migrate |
Run Alembic database migrations |
make lint |
Run linters (ruff + ESLint) |
make format |
Format code (ruff + Prettier) |
make generate-types |
Generate TypeScript types from OpenAPI spec |
make clean |
Remove build artifacts |
Run make help for a full list of commands.
Copy backend/.env.example to backend/.env:
cp backend/.env.example backend/.envVariables:
APP_ENV- Environment (development/production)DATABASE_URL- SQLite database pathFRONTEND_URL- Frontend URL for CORSGIT_REPO_PATH- Git repository root path for workspace isolation (optional, defaults to project root)BASE_BRANCH- Base branch for creating worktree branches (optional, defaults tomain, falls back tomaster)
Copy frontend/.env.example to frontend/.env:
cp frontend/.env.example frontend/.envVariables:
VITE_BACKEND_URL- Backend API URL
| Method | Endpoint | Description |
|---|---|---|
| GET | /health |
Health check |
| GET | /version |
App version info |
| Goals | ||
| POST | /goals |
Create a new goal |
| GET | /goals |
List all goals |
| GET | /goals/{id} |
Get goal by ID |
| Tickets | ||
| POST | /tickets |
Create a new ticket |
| GET | /tickets/{id} |
Get ticket by ID |
| POST | /tickets/{id}/transition |
Transition ticket state |
| GET | /tickets/{id}/events |
Get ticket event history |
| POST | /tickets/{id}/run |
Enqueue execute job |
| POST | /tickets/{id}/verify |
Enqueue verify job |
| GET | /tickets/{id}/jobs |
List jobs for ticket |
| GET | /tickets/{id}/evidence |
Get verification evidence |
| Jobs | ||
| GET | /jobs/{id} |
Get job details with logs |
| GET | /jobs/{id}/logs |
Get raw job logs |
| POST | /jobs/{id}/cancel |
Cancel a job |
| Evidence | ||
| GET | /evidence/{id}/stdout |
Get evidence stdout content |
| GET | /evidence/{id}/stderr |
Get evidence stderr content |
| Board | ||
| GET | /board |
Get kanban board view |
- Linter/Formatter: Ruff (configured in
backend/pyproject.toml) - Run:
make lint-backend/make format-backend
- Linter: ESLint
- Formatter: Prettier
- Run:
make lint-frontend/make format-frontend
See CONTRIBUTING.md for guidelines on how to contribute.
Business Source License 1.1 (BSL 1.1). See LICENSE for details.
Free for non-commercial use including personal projects, education, and evaluation. Commercial use requires a separate license. Converts to Apache 2.0 on 2030-02-26.



