diff --git a/AGENTS.md b/AGENTS.md index f99edc9..dc8d1e1 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,40 +1,22 @@ -# Zero-Shot Hermes Harness — Entry Point +# Entry Point — `zero-shot-sdd-harness-kit` -This is a **working multi-agent hackathon boilerplate** (not a generator). The runnable code -lives directly in the repo. FastAPI + React/Vite + SQLite, with a Hermes goal loop. +This is a spec-driven AI agent **boilerplate**. Read this file first, then read the spec in `spec/` (it is the single source of truth) before touching code. -## What this repo is +## What This Repo Is -A starter-kit for building agents **spec-first**. One person with an idea and one API key can -get a real, production-shaped agent running in 20–60 minutes. The code is conventional and -reviewable — no codegen step required. +A runnable, multi-agent hackathon starter-kit. The agent code lives directly in the repo and runs as-is — **there is no generator step** (`bootstrap.py` / `templates/` do not exist). You clone it, install deps, and run it. -## Layout (actual) +- `backend/` — FastAPI + SQLAlchemy + SQLite. Supervisor/worker agent graph, run + goal persistence, Hermes goal loop. **This is the real code** (in `backend/app/`). +- `frontend/` — React + TypeScript + Vite + Tailwind chat UI. +- `deploy/` — GCP VM path: `Dockerfile`, `gcp-vm-startup.sh`, `terraform/main.tf`, `cloudbuild.yaml`. +- `capabilities/` — one-file-per-capability plugin convention (loaded at runtime into a route table). +- `harness/` — Hermes-native skill/agent/pattern **stubs** (methodology for how to extend this kit). +- `spec/` — product spec (single source of truth). +- `.github/workflows/ci.yml` — CI: runs backend tests, evals, frontend build, and builds the deploy image. -``` -backend/ FastAPI app (the agent) - app/main.py FastAPI app + routers - app/agent.py supervisor/worker graph; calls Gemini when LLM_API_KEY set - app/config.py settings (reads .env) - app/db.py SQLAlchemy engine + SQLite (WAL mode) - app/models.py Run, Goal tables - app/routers/ health, chat, goal - tests/ backend unit tests -frontend/ React + TS + Vite + Tailwind chat UI -deploy/ GCP VM path (Dockerfile, startup script, terraform, cloudbuild) -capabilities/ one-file-per-capability plugin convention -harness/ Hermes skill/agent/pattern stubs (how to extend the kit) -spec/ product spec (single source of truth) -evals/ behaviour + live evals (pytest) -``` - -## First action every session - -1. Read `harness/rules/ai-agents.md` (mandatory rules). -2. Read `spec/roadmap.md` — if it still has ``, the spec is not ready. -3. Read the rest of `spec/` before touching application code. +> ⚠️ `AGENTS.md` describes **how to build *with* this kit** (the spec-driven workflow in `harness/`). The runnable scaffold is `backend/app/` + `frontend/` — a hand-rolled supervisor/worker graph, **not** the `src/` + LangGraph `transform_text` skeleton some harness docs reference. Those harness docs are the *intended* pattern catalogue; the shipped scaffold is the minimal baseline they describe. -## Run it +## Run It Locally ```bash # backend @@ -44,21 +26,106 @@ pip install -r requirements.txt uvicorn app.main:app --port 8000 # frontend (separate terminal) -cd frontend && npm install && npm run dev +cd frontend +npm install +npm run dev +``` + +- `GET /health` → `{"status":"ok","version":"0.1.0"}` +- `POST /api/chat` → `{"reply":"...","run_id":N}` (stub mode unless an LLM key is set) +- `POST /goals` + `POST /goals/{id}/run` → autonomous multi-step goal loop +- `GET /capabilities` → registered capability slugs from `capabilities/*.md` + +## Live Mode (real LLM) + +Set a key in `.env` (copy from `.env.example`): +- Gemini (default): `LLM_PROVIDER=gemini` + `GEMINI_API_KEY=...` (or `LLM_API_KEY=...`) +- OpenAI-compatible: `LLM_API_KEY=...` + `LLM_BASE_URL=...` + `LLM_MODEL=...` + +Without a key the worker returns `[stub] `. The `evals/test_agent.py::test_live_chat` test is skipped unless a key is present. + +## Tests & Evals + +```bash +cd backend && .venv/bin/activate && python -m pytest tests -v # unit tests +cd . && backend/.venv/bin/python -m pytest evals -v # behaviour evals (live skipped w/o key) +cd frontend && npm run build # tsc + vite +``` + +## Docker + +```bash +docker compose up --build # backend :8000, web :5173 +``` + +## Deploy to GCP (single VM) + +```bash +# manual +gcloud compute instances create demo-agent-vm --machine-type=e2-small \ + --metadata-from-file=startup-script=deploy/gcp-vm-startup.sh +gcloud compute firewall-rules create allow-demo-agent-8000 --allow tcp:8000 --target-tags=demo-agent + +# or terraform +cd deploy/terraform && terraform init && terraform apply -var project_id= +``` + +See `deploy/README.md` and `harness/patterns/goal-loop.md`. + +## Reuse as a Boilerplate + +Copy `backend/`, `frontend/`, `deploy/`, `capabilities/` into a new repo and rename `demo-agent` → your project. The harness stubs in `harness/` show how to extend it with Hermes skills/agents. + +## Spec Manifest (read in order before writing app code) -# evals -cd backend && .venv/bin/python -m pytest ../evals/ -v ``` +spec/roadmap.md +spec/architecture.md +spec/api.md +spec/data.md +spec/agent.md +spec/ui.md +harness/rules/ai-agents.md +harness/patterns/phases.md +harness/patterns/project-layout.md +harness/patterns/engineering-practices.md +harness/patterns/test-driven.md +harness/patterns/ui-ux.md +harness/patterns/tech-stack.md +harness/patterns/code.md +harness/patterns/agentic-ai.md +harness/rules/git.md +``` + +## Skills (entry points) -## Go live with a key +These are the build-methodology entry points (manual; `disable-model-invocation: true`). Each is invocable as a skill and as a slash command. -Copy `.env.example` → `.env` and set `GEMINI_API_KEY`. Restart the backend. -`POST /api/chat` will then call Gemini (OpenAI-compatible endpoint) instead of returning `[stub]`. +| Skill / command | Purpose | +|-----------------|---------| +| `/zero-shot-build [idea]` | Idea → working, verified skeleton (drives the agent-builder). Also adds a capability. | +| `/zero-shot-fix [target]` | Diagnose + fix a bug, error, failing test, or spec/code drift, then verify. | +| `/zero-shot-sync [scope]` | Reconcile spec ↔ code so they match (spec wins), then verify. | -## Key rules (summary — full rules in harness/rules/ai-agents.md) +## Key Rules (summary — full rules in harness/rules/ai-agents.md) -- Spec is the source of truth; when spec and code disagree, the spec wins. - Never write application code before reading the full spec. -- Tests and evals run against the real LLM/API using keys from `.env` (live eval auto-skips without a key). -- Commit every logical unit of work; never leave the tree dirty. -- `.env` is never committed. +- Never skip a phase — complete phase N before starting phase N+1. +- Commit every logical unit of work; never let the working tree stay dirty. +- Each phase is tested by the human before the next phase starts. +- Tight scope, first-time-right — each phase is the smallest user-testable win. +- Tests and evals run against the real LLM/API using keys from `.env` when present; the live eval is gated (skips without a key) so CI never fails on a missing key. +- When in doubt, ask at intake — do not guess requirements. + +## Sub-agents (the team) + +`/zero-shot-build` delegates a full build to **agent-builder**, which plans and coordinates the rest and owns git/PR. `/zero-shot-fix` and `/zero-shot-sync` call the workers directly. Each agent is one full, self-contained definition at `harness/agents/.md`. + +| Agent | Role | Tools | +|-------|------|-------| +| agent-builder | Orchestrator — plans phases, fans out code-generator instances per slice, owns git/PR | read/bash/agent | +| spec-writer | The single design authority — writes the FULL spec and self-reviews it | read/write | +| code-generator | Implements ONE independent slice plus tests | read/write/bash | +| qa-auditor | Independent review + run gates/tests/app + audit spec↔code drift | read-only (bash) | + +Pattern: **spec-writer** writes the whole spec; **agent-builder** fans out **code-generator** per slice in parallel; **qa-auditor** independently gates each slice and audits drift. diff --git a/README.md b/README.md index ccf7961..1a2e148 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,17 @@ -# zero-shot-sdd-harness — working scaffold + boilerplate +# zero-shot-sdd-harness — runnable scaffold + boilerplate This repo is **the scaffold itself**: a runnable multi-agent hackathon starter-kit. No generator step — the code lives directly in the repo and runs as-is. ## What's in here -- `backend/` — FastAPI + SQLAlchemy + SQLite. Supervisor/worker agent graph, run + goal persistence, Hermes goal loop. +- `backend/` — FastAPI + SQLAlchemy + SQLite. Supervisor/worker agent graph, run + goal persistence, Hermes goal loop, capability registry. - `frontend/` — React + TypeScript + Vite + Tailwind chat UI. - `deploy/` — GCP VM path: `Dockerfile`, `gcp-vm-startup.sh`, `terraform/main.tf`, `cloudbuild.yaml`. -- `capabilities/` — one-file-per-capability plugin convention. -- `harness/` — Hermes-native skill/agent/pattern stubs (how to build with this kit). +- `capabilities/` — one-file-per-capability plugin convention (loaded at runtime). +- `harness/` — Hermes-native skill/agent/pattern stubs (how to build *with* this kit). - `spec/` — product spec (single source of truth). +- `.github/workflows/ci.yml` — CI: backend tests, evals, frontend build, deploy-image build. ## Run it locally @@ -27,9 +28,29 @@ npm install npm run dev ``` -- `GET /health` → `{"status":"ok"}` -- `POST /api/chat` → `{"reply": "...", "run_id": N}` +- `GET /health` → `{"status":"ok","version":"0.1.0"}` +- `POST /api/chat` → `{"reply":"...","run_id":N}` - `POST /goals` + `POST /goals/{id}/run` → autonomous multi-step Hermes goal loop +- `GET /capabilities` → registered capability slugs from `capabilities/*.md` + +## Live mode (real LLM) + +Copy `.env.example` to `.env` and set a key: + +- **Gemini (default):** `LLM_PROVIDER=gemini` + `GEMINI_API_KEY=...` (or `LLM_API_KEY=...`) +- **OpenAI-compatible:** `LLM_API_KEY=...` + `LLM_BASE_URL=...` + `LLM_MODEL=...` + +Without a key the worker returns `[stub] `. The live eval +(`evals/test_agent.py::test_live_chat`) is skipped unless a key is present, so CI +never fails on a missing key. + +## Tests & evals + +```bash +cd backend && .venv/bin/activate && python -m pytest tests -v # unit tests +cd . && backend/.venv/bin/python -m pytest evals -v # behaviour evals (live skipped w/o key) +cd frontend && npm run build # tsc + vite +``` ## Docker @@ -38,6 +59,10 @@ docker compose up --build # backend :8000, web :5173 ``` +`docker-compose.yml` builds `backend/Dockerfile.backend` and +`frontend/Dockerfile.frontend`. The frontend dev server runs on :5173 with +source bind-mounted; deps install on first start. + ## Deploy to GCP (single VM) ```bash @@ -50,7 +75,15 @@ gcloud compute firewall-rules create allow-demo-agent-8000 --allow tcp:8000 --ta cd deploy/terraform && terraform init && terraform apply -var project_id= ``` -See `deploy/README.md` and `harness/patterns/goal-loop.md`. +`deploy/cloudbuild.yaml` builds + pushes the backend image to Artifact Registry; +`deploy/gcp-vm-startup.sh` runs it on the VM. See `deploy/README.md` and +`harness/patterns/goal-loop.md`. + +## CI + +`.github/workflows/ci.yml` runs on pushes to `main`/`feature/v0.1` and on PRs: +backend unit tests, evals (stub), frontend build, and a backend deploy-image build +on `main`. Merge to `main` to trigger the deploy-image build. ## Cycle time diff --git a/Dockerfile.backend b/backend/Dockerfile.backend similarity index 56% rename from Dockerfile.backend rename to backend/Dockerfile.backend index c316b7c..c350d16 100644 --- a/Dockerfile.backend +++ b/backend/Dockerfile.backend @@ -6,10 +6,13 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ gcc libpq-dev curl \ && rm -rf /var/lib/apt/lists/* -COPY requirements.txt . +# Build context is the repo root (see docker-compose.yml), so we copy backend/ +# and the repo-root capabilities/ directory explicitly. +COPY backend/requirements.txt ./requirements.txt RUN pip install --no-cache-dir -r requirements.txt -COPY . . +COPY backend/ ./ +COPY capabilities/ ./capabilities/ EXPOSE 8000 diff --git a/backend/app/capabilities.py b/backend/app/capabilities.py new file mode 100644 index 0000000..ada914c --- /dev/null +++ b/backend/app/capabilities.py @@ -0,0 +1,81 @@ +"""Capability registry. + +Loads every ``capabilities/*.md`` at startup into a ``{slug: doc}`` table so the +supervisor can route a message to a named capability. Drop a new +``capabilities/.md`` to register a capability — no code change required. + +The ``capabilities/`` directory lives at the **repo root** (sibling to ``backend/``) +when running locally, but is copied into the image at ``/app/capabilities`` for +Docker deployments. We search the likely locations so it works in both layouts. +""" + +from __future__ import annotations + +import re +from pathlib import Path + +# Candidate locations for the capabilities/ directory, in priority order. +def _candidate_dirs() -> list[Path]: + here = Path(__file__).resolve().parent # backend/app + return [ + here.parent.parent / "capabilities", # local: backend/../capabilities + Path("/app/capabilities"), # docker image (backend/Dockerfile) + Path.cwd() / "capabilities", # cwd-relative + ] + + +_CAPABILITIES: dict[str, str] = {} +_CAPABILITIES_DIR: Path | None = None + + +def _discover_dir() -> Path | None: + for d in _candidate_dirs(): + if d.is_dir(): + return d + return None + + +def _slug_from_filename(path: Path) -> str: + return path.stem + + +def _slug_from_h1(doc: str) -> str | None: + m = re.search(r"^#\s+(.+)$", doc, re.MULTILINE) + if not m: + return None + raw = m.group(1).strip().lower() + # keep alphanumerics, spaces and dashes; collapse to dash-separated slug + slug = re.sub(r"[^a-z0-9]+", "-", raw).strip("-") + return slug or None + + +def load_capabilities() -> dict[str, str]: + """Load (or reload) all capability docs. Returns the slug->doc map.""" + global _CAPABILITIES_DIR + _CAPABILITIES.clear() + _CAPABILITIES_DIR = _discover_dir() + if _CAPABILITIES_DIR: + for path in sorted(_CAPABILITIES_DIR.glob("*.md")): + if path.name.upper() == "README.MD": + continue + try: + doc = path.read_text(encoding="utf-8") + except OSError: + continue + slug = _slug_from_h1(doc) or _slug_from_filename(path) + _CAPABILITIES[slug] = doc + return dict(_CAPABILITIES) + + +def get_capabilities() -> dict[str, str]: + if not _CAPABILITIES: + load_capabilities() + return dict(_CAPABILITIES) + + +def get_capability(slug: str) -> str | None: + return get_capabilities().get(slug) + + +def list_capability_slugs() -> list[str]: + return sorted(get_capabilities().keys()) diff --git a/backend/app/main.py b/backend/app/main.py index b55546d..13b77da 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -1,8 +1,9 @@ from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware -from app.routers import health, chat, goal +from app.routers import health, chat, goal, capabilities from app.config import settings +from app import capabilities as _capabilities # Import db so dev-time create_all runs on startup/TestClient import. from app import db as _app_db # noqa: F401 @@ -10,6 +11,8 @@ def create_app() -> FastAPI: app = FastAPI(title="demo-agent") + # Load capability docs once at startup. + _capabilities.load_capabilities() app.add_middleware( CORSMiddleware, @@ -22,6 +25,7 @@ def create_app() -> FastAPI: app.include_router(health.router) app.include_router(chat.router) app.include_router(goal.router) + app.include_router(capabilities.router) @app.get("/") async def root(): diff --git a/backend/app/routers/capabilities.py b/backend/app/routers/capabilities.py new file mode 100644 index 0000000..143dc02 --- /dev/null +++ b/backend/app/routers/capabilities.py @@ -0,0 +1,11 @@ +from fastapi import APIRouter + +from app import capabilities as _capabilities + +router = APIRouter(prefix="/capabilities", tags=["capabilities"]) + + +@router.get("") +async def list_capabilities(): + """Registered capabilities loaded from capabilities/*.md at startup.""" + return {"capabilities": _capabilities.list_capability_slugs()} diff --git a/backend/tests/test_health.py b/backend/tests/test_health.py index 8d6beeb..1515e65 100644 --- a/backend/tests/test_health.py +++ b/backend/tests/test_health.py @@ -8,3 +8,11 @@ def test_health(): r = client.get("/health") assert r.status_code == 200 assert r.json()["status"] == "ok" + + +def test_capabilities_listed(): + r = client.get("/capabilities") + assert r.status_code == 200 + caps = r.json()["capabilities"] + assert isinstance(caps, list) + assert "example-capability" in caps diff --git a/capabilities/README.md b/capabilities/README.md index c567eb6..c7466a5 100644 --- a/capabilities/README.md +++ b/capabilities/README.md @@ -1,10 +1,15 @@ # Capabilities — demo-agent Drop one file per discrete capability here. Each file describes exactly one thing -the agent can do. The supervisor graph can route to a capability by name; see -`capabilities/example.md` for the shape. +the agent can do. Capabilities are loaded at backend startup from every `*.md` in +this directory (except `README.md`) and exposed via `GET /capabilities`, which +returns the list of registered slugs (derived from each file's `# H1`). Convention: - filename = `{capability-slug}.md` -- first H1 = capability name +- first H1 = capability name (used as the registered slug, lowercased/dashed) - sections: Purpose, Inputs, Output, Side-effects + +The supervisor (`backend/app/agent.py`) can branch on a capability by name — extend +`_supervisor`/`_worker` to read `app.capabilities.get_capability(slug)` and adjust +the system prompt or routing. `capabilities/example.md` shows the minimal shape. diff --git a/deploy/Dockerfile b/deploy/Dockerfile index eb84af4..84f2cf2 100644 --- a/deploy/Dockerfile +++ b/deploy/Dockerfile @@ -8,6 +8,7 @@ FROM python:3.11-slim WORKDIR /app COPY --from=build /usr/local/lib/python3.11/site-packages /usr/local/lib/python3.11/site-packages COPY backend/ ./ +COPY capabilities/ ./capabilities/ ENV PORT=8000 EXPOSE 8000 CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"] diff --git a/docker-compose.yml b/docker-compose.yml index 38e3417..8fcd529 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,10 +1,8 @@ -version: "3.9" - services: backend: build: - context: ./backend - dockerfile: Dockerfile.backend + context: . + dockerfile: backend/Dockerfile.backend ports: - "8000:8000" env_file: diff --git a/harness/patterns/project-layout.md b/harness/patterns/project-layout.md index 97c421b..58985cc 100644 --- a/harness/patterns/project-layout.md +++ b/harness/patterns/project-layout.md @@ -1,5 +1,7 @@ # Project Layout — Canonical Structure +> **Scope:** This file is the canonical layout for **generated/extended projects** built *with* this kit (the target the `zero-shot-build` flow produces). The runnable scaffold you cloned — `backend/app/`, `frontend/`, `capabilities/` — is the *minimal baseline* this pattern describes; it uses a hand-rolled supervisor/worker graph (no LangGraph dependency) under `backend/app/` rather than `src//graph/`. Extend the baseline toward this layout as your agent grows. See `spec/architecture.md` for the actual shipped structure. + All agents built from this boilerplate must follow this layout exactly. The sales-agent repo (`smallTechOrg/sales-agent`) is the canonical reference. --- diff --git a/spec/agent.md b/spec/agent.md index 96b92c0..dd448e8 100644 --- a/spec/agent.md +++ b/spec/agent.md @@ -1,135 +1,88 @@ # Agent — `scaffold-agent` -> Phase 1 shipped the agent stub. Phase 2 replaces `/api/chat` with a compact multi-agent supervisor graph plus run persistence. +> The agent graph shipped in `app/agent.py`. This describes the real implementation, not a planned one. --- +## Chosen Graph +A **hand-rolled supervisor/worker** graph (no LangGraph dependency). Lightweight goal loop for autonomous multi-step runs. -**Chosen:** Multi-agent supervisor by default, with ReAct worker nodes, plus a lightweight goal loop for autonomous multi-step runs. - -## Goal Loop - -The generated harness supports optional goal-based looping so Hermes or similar automation can drive the agent through multiple steps without manual intervention. +## Supervisor -### State shape +`app/agent.py` defines `run_graph(messages)` which runs three nodes in sequence: -```python -class GoalState(TypedDict): - goal_id: int - run_id: int - goal_text: str - status: str - step_index: int - steps: list[dict] - last_reply: str | None - last_error: str | None +``` +START -> supervisor -> worker -> END + |-> error -> handle_error -> END ``` -Loop rules: -- `pending` → `running` → `succeeded` / `failed` -- A step may finish with `intermediate` to continue the loop -- `next_step` advances once per successful iteration -- Hard stop on `failed` or terminal `succeeded` - -### Endpoints - -- `POST /goals` create a goal with initial step list -- `POST /goals/{goal_id}/next` advance one step -- `POST /goals/{goal_id}/run` run loop until termination -- `GET /goals/{goal_id}` current goal state - -### Persistence - -Goals and step receipts are stored in `app/models.py` and materialized by `app/db.py` on startup. - -## Supervisor - -The backend emits a supervisor graph in `app/agent.py`: -- START -> supervisor -> worker -> END -- worker may return reply, error, or a follow-up step +| Node | Role | +|------|------| +| `_supervisor` | Sets default route/decision; records the last user message. | +| `_worker` | If a key is configured, POSTs to the OpenAI-compatible `/v1/chat/completions` endpoint and stores the reply. Otherwise returns `[stub] `. On exception, sets `state["error"]`. | +| `_handle_error` | Terminates with a user-facing error message. | -| Node | Provider | Model ID | Rationale | -|-------|----------|----------|-----------| -| agent_node | Optional | gpt-4o-mini or claude-haiku-3-5-20240307 | Live responses when `LLM_API_KEY` is set; otherwise degraded to stub path. | +**Provider / model config** (from `app/config.py`): +- `LLM_PROVIDER` — `gemini` (default) | `openai`. +- `LLM_API_KEY` or `GEMINI_API_KEY` — enables live mode. +- `LLM_BASE_URL` — OpenAI-compatible base URL (default Gemini OpenAI-compat endpoint). +- `LLM_MODEL` — model id (default `gemini-1.5-flash`). **Fallback behaviour:** -- Missing/invalid key → stub path. No retries, no delays. -- Network failure → 500 with message "LLM call failed"; logged with traceback. +- Missing/invalid key → `[stub]` path. No retries, no delays. +- Network/API failure → `state["error"]` set; `handle_error` returns a message; the `Run` is persisted with `status=error`. **Prompt strategy:** -- Phase 1: static prompt template `You are a helpful assistant.` with user message appended. -- Phase 2: replace with system prompt from `prompts/transform.md` placeholder. +- Phase 1: static system prompt `"You are a helpful assistant."` with the last user message appended. Replace in `app/agent.py` (`_worker`) to customize. ## Tools & Tool Calling | Tool name | Description | Inputs | Output | Side-effects | |-----------|-------------|--------|--------|--------------| -| echo | Returns stub reply echoing user input. | messages | string | None | -| llm_call | Calls configured OpenAI-compatible endpoint. | messages, model | string | Network call | - -**Tool selection strategy:** -- Phase 1: if `LLM_API_KEY` is set, always use `llm_call`. +| echo (stub) | Returns `[stub]` reply echoing user input. | messages | string | None | +| llm_call | Calls the configured OpenAI-compatible endpoint. | messages, model | string | Network call | -**Tool failure handling:** -- On failure: raise 500 with "LLM call failed". +**Tool selection:** if a live key is configured, always use `llm_call`; otherwise the stub. ## Agent State ```python -class AgentState(TypedDict): - run_id: int # set at entry - messages: list # full conversation so far - reply: str | None # final assistant message - error: str | None # fatal error, if any +class AgentState(dict): + run_id: int | None + messages: list[dict[str, str]] + reply: str | None + error: str | None + route: str # default "worker" + decision: str # default "answer" + reason: str + last_user_message: str ``` -## Nodes / Steps - -### `agent_node` - -**Reads from state:** `messages` -**Writes to state:** `reply`, `error` -**LLM call:** yes, if configured; uses a small static prompt. -**External calls:** OpenAI-compatible or Anthropic-compatible client. - -**Behaviour:** -- Build prompt from the last user message. -- If a live provider is configured, return the model output. -- Otherwise return a string prefixed with `[stub]` plus the user message. - -### `handle_error` - -**Reads from state:** `error` -**Writes to state:** sets `reply` to an error string. -**Behaviour:** terminate graph with "Something went wrong." +## Goal Loop -## Graph / Flow Topology +The goal loop drives `_execute_step` per step, calling `run_graph` on each step's `description`. State machine: `pending` → `running` → `succeeded` / `failed`. Endpoints: `POST /goals`, `POST /goals/{id}/next`, `POST /goals/{id}/run`, `GET /goals/{id}`. Goals and steps are stored in `app/models.py` (`Goal`) and materialized via `app/db.py`. -```text -START -> supervisor -> worker -> END - |-> reply -> END - |-> error -> handle_error -> END -``` +## Capabilities -The backend now emits a supervisor graph in `app/graph.py`, a worker node in `app/nodes/*`, and a single entrypoint in `app/agent.py`. `/api/chat` invokes the graph and persists a `Run` record on completion or failure. +`app/capabilities.py` loads every `capabilities/*.md` at startup into a `{slug: doc}` table, exposed via `GET /capabilities`. The supervisor can route a message to a named capability (extend `_supervisor`/`_worker` to branch on `capabilities/` content). See `capabilities/README.md` for the one-file-per-capability convention. ## Memory & Context -- Within a run: full messages list in memory. -- Across runs: none in Phase 1 (database receipt is optional). +- Within a run: full `messages` list in memory. +- Across runs: none in the base scaffold (database receipt is for observability only). ## Error Handling & Recovery -- Node-level: try/except around LLM call; sets `error`. -- Graph-level: `handle_error` node sets assistant-facing message and returns. +- Node-level: `try/except` around the LLM call; sets `error`. +- Graph-level: `handle_error` sets the assistant-facing message and returns. ## Observability -- Structured log on every `/api/chat` request: `run_id`, `status`, `latency_ms`. -- Errors logged with full traceback. +- Every `/api/chat` persists a `Run` row (`status`, `user_message`, `assistant_message`, `error_message`). +- `GET /api/runs` and `GET /api/runs/{id}` for inspection. +- Errors logged to stderr with traceback in the chat router. ## Concurrency Model -- One FastAPI worker per process; SQLite allows one writer at a time, which is sufficient for local dev. -- Threaded `ProcessPoolExecutor` may be used for blocking LLM calls in production. +- One FastAPI worker per process; SQLite allows one writer at a time, which is sufficient for local dev. For production, point `DATABASE_URL` at Postgres. diff --git a/spec/api.md b/spec/api.md index 182a7f9..cbcaac9 100644 --- a/spec/api.md +++ b/spec/api.md @@ -6,32 +6,29 @@ ## API Style -REST (FastAPI). Two routes in Phase 1, plus optional `/docs` auto-generated by FastAPI. +REST (FastAPI). CORS is open (`*`) for local dev. Auth is disabled in the scaffold; add it before production. -## Endpoints / Commands +## Endpoints ### `GET /health` -**Purpose:** Liveness and library availability check. +**Purpose:** Liveness + DB connectivity check. -**Response:** +**Response (200 OK):** ```json -{ - "status": "ok", - "version": "0.1.0" -} +{ "status": "ok", "version": "0.1.0" } ``` **Error cases:** | Status | Condition | |--------|-----------| -| 500 | SQLAlchemy engine init fails. | +| 200 with `{"status":"error"}` | DB connection failed (still HTTP 200, but payload signals failure). | --- ### `POST /api/chat` -**Purpose:** Primary agent entry point. Accepts a chat message, routes it through the supervisor graph, persists a run, and returns the assistant reply. +**Purpose:** Primary agent entry point. Routes the message through the supervisor/worker graph, persists a `Run`, returns the reply. **Request:** ```json @@ -44,23 +41,22 @@ REST (FastAPI). Two routes in Phase 1, plus optional `/docs` auto-generated by F **Response (200 OK):** ```json -{ - "reply": "Hello (stub response — set LLM_API_KEY for live model)" -} +{ "reply": "[stub] Hello", "run_id": 1 } ``` -When `LLM_API_KEY` is present and `LLM_BASE_URL` is set, the backend still routes through the supervisor graph on every request, but the worker may choose a real LLM call instead of returning the fallback stub. +When `LLM_API_KEY` (or the provider key) is present and `LLM_BASE_URL` is set, the worker calls the OpenAI-compatible `/v1/chat/completions` endpoint and returns the model's reply (no `[stub]` prefix). **Error cases:** | Status | Condition | |--------|-----------| -| 400 | `messages` missing, not a list, or first item malformed. | -| 422 | Validation failure (Pydantic). | -| 500 | Graph failure in worker or supervisor; run is still persisted with `status=error`. | +| 422 | Pydantic validation failure (`messages` missing/empty). | +| 200 (run persisted with `status=error`) | Worker raised; `reply` is an error message. | + +--- -### `GET /agent/runs` +### `GET /api/runs` -**Purpose:** Return a paginated-ish list of persisted run receipts for observability and debugging. +**Purpose:** List the most recent persisted run receipts (observability/debugging). **Response (200 OK):** ```json @@ -70,40 +66,25 @@ When `LLM_API_KEY` is present and `LLM_BASE_URL` is set, the backend still route "id": 1, "status": "succeeded", "user_message": "Hello", - "assistant_message": "Hello there!", - "created_at": "2026-07-11T19:10:00Z" + "assistant_message": "[stub] Hello", + "error_message": null, + "created_at": "2026-07-11T19:10:00" } ] } ``` -### `GET /agent/runs/{run_id}` +### `GET /api/runs/{run_id}` -**Purpose:** Inspect one run receipt. +**Purpose:** Inspect one run receipt. Returns `status:"not_found"` (HTTP 200) if missing. -**Response (200 OK):** -```json -{ - "id": 1, - "status": "succeeded", - "user_message": "Hello", - "assistant_message": "Hello there!", - "error_message": null, - "created_at": "2026-07-11T19:10:00Z" -} -``` - -## Notes - -- Auth is disabled in Phase 1; CORS is open for local dev. Production deploy should set CORS origins. -- Persistence now uses SQLAlchemy via `app/db.py`; no external queue is required for the baseline scaffold. -- Streaming remains a Phase 3 enhancement. +--- ## Goal Loop Endpoints ### `POST /goals` -**Purpose:** Create a new goal with an ordered step list. +**Purpose:** Create a goal with an ordered step list. **Request:** ```json @@ -111,7 +92,6 @@ When `LLM_API_KEY` is present and `LLM_BASE_URL` is set, the backend still route "goal_text": "Deploy demo-agent to GCP", "steps": [ {"description": "Build backend Docker image"}, - {"description": "Build frontend Docker image"}, {"description": "Push to GCR"}, {"description": "Deploy on GCE"} ] @@ -120,15 +100,12 @@ When `LLM_API_KEY` is present and `LLM_BASE_URL` is set, the backend still route **Response (200 OK):** ```json -{ - "goal_id": 1, - "status": "pending" -} +{ "goal_id": 1, "status": "pending", "step_index": 0 } ``` ### `POST /goals/{goal_id}/next` -**Purpose:** Advance one step for the goal and return the result. +**Purpose:** Execute exactly one pending step and return the result. **Response (200 OK):** ```json @@ -137,31 +114,39 @@ When `LLM_API_KEY` is present and `LLM_BASE_URL` is set, the backend still route "status": "running", "step_index": 1, "step": {"description": "Build backend Docker image"}, - "reply": "Build complete", + "reply": "...", "error": null } ``` ### `POST /goals/{goal_id}/run` -**Purpose:** Run the remaining goal steps in loop until terminal state. +**Purpose:** Execute remaining steps in a loop until terminal state (`succeeded` or `failed`). **Response (200 OK):** ```json -{ - "goal_id": 1, - "status": "succeeded", - "step_index": 4, - "reply": "Deployed to GCP" -} +{ "goal_id": 1, "status": "succeeded", "step_index": 3, "reply": "..." } ``` Loop rules: -- Current step is executed -- If worker returns `intermediate`, the loop continues with the next step -- If worker returns `succeeded` or `failed`, the loop stops -- If worker returns `intermediate` and no next step exists, status becomes `succeeded` +- `pending` → `running` → `succeeded` / `failed`. +- Each step runs the supervisor/worker graph on the step's description. +- On a worker error the goal status becomes `failed` and the loop stops. +- When all steps are done the status becomes `succeeded`. ### `GET /goals/{goal_id}` -**Purpose:** Inspect current goal state. +**Purpose:** Inspect current goal state (status, `step_index`, last reply/error). + +--- + +## Capabilities + +### `GET /capabilities` + +**Purpose:** List registered capabilities loaded from `capabilities/*.md` at startup. + +**Response (200 OK):** +```json +{ "capabilities": ["example-capability"] } +``` diff --git a/spec/architecture.md b/spec/architecture.md index 2761561..1e6d5df 100644 --- a/spec/architecture.md +++ b/spec/architecture.md @@ -1,103 +1,108 @@ # Architecture — `scaffold-agent` -> Single source of truth for Phase 1. Template source lives in `templates/; `scripts/bootstrap.py` is the generator. +> Single source of truth for the runnable scaffold. This repo **is** the scaffold — there is no generator step; the code in `backend/` and `frontend/` runs as-is. --- ## System Overview -This repo is both a **generator source** and a **spec harness**. -`main` contains: -- the canonical spec in `spec/`, -- engineering rules in `harness/`, -- a project-generation CLI in `scripts/`, -- embedded project templates in `templates/`. +This repo is a **runnable multi-agent hackathon starter-kit**. You clone it, install deps, and run it. No `bootstrap.py`, no `templates/` — the agent code lives directly in the repo. Copy `backend/`, `frontend/`, `deploy/`, `capabilities/` into a new repo and rename `demo-agent` → your project to reuse it as a boilerplate. -A user runs `scripts/bootstrap.py ` (or wraps it via `scripts/new_agent.sh`) and a complete project tree is written to a fresh directory outside this repo. -The generated project is a runnable FastAPI + React + SQLite service with an agent stub. +The default agent is a minimal but real supervisor/worker graph: +- **supervisor** decides routing, +- **worker** calls the configured LLM (OpenAI-compatible endpoint) when a key is set, otherwise returns a `[stub]` reply, +- **handle_error** terminates with a user-facing message on failure. + +The goal loop (`/goals`) lets automation drive the agent through multiple steps without manual intervention. ## Component Map ``` -templates/agent-project/ ← embedded source templates - │ - ▼ -scripts/bootstrap.py ← generator engine (read + substitute + write) - │ - ▼ -// ← generated project (not in main) - ├── backend/ - │ ├── main.py ← FastAPI app - │ ├── db.py ← SQLAlchemy engine/session - │ ├── models.py ← ORM models - │ ├── alembic/ ← migrations - │ └── ... - ├── frontend/ - │ ├── src/ ← Vite + React + TS - │ └── package.json - ├── docker-compose.yml - ├── Dockerfile.* - └── README.md +repo root ← repo IS the agent project +├── backend/ ← FastAPI + SQLAlchemy + SQLite +│ ├── app/ +│ │ ├── main.py ← create_app() factory, CORS, routers +│ │ ├── config.py ← pydantic-settings; LLM provider/key/base_url/model +│ │ ├── db.py ← engine + SessionLocal + Base; create_all on import +│ │ ├── models.py ← Run, Goal ORM models +│ │ ├── agent.py ← supervisor/worker/handle_error graph + run_graph() +│ │ ├── capabilities.py ← loads capabilities/*.md into a route table +│ │ └── routers/ +│ │ ├── health.py ← GET /health (DB-backed liveness) +│ │ ├── chat.py ← POST /api/chat, GET /api/runs, /api/runs/{id} +│ │ └── goal.py ← POST/GET /goals, /goals/{id}/run, /goals/{id}/next +│ ├── alembic/ ← migration tooling (env.py wires Base.metadata) +│ ├── alembic.ini +│ ├── tests/ ← pytest unit tests (TestClient) +│ ├── requirements.txt +│ └── pyproject.toml +├── frontend/ ← React 18 + Vite 5 + TypeScript + Tailwind +│ ├── src/App.tsx ← chat UI; calls /api/chat via fetch +│ └── package.json +├── capabilities/ ← one .md file per capability (loaded at runtime) +├── deploy/ ← GCP VM path: Dockerfile, startup script, terraform, cloudbuild +├── evals/ ← behaviour evals (stub) + 1 live-gated test +├── harness/ ← Hermes-native skill/agent/pattern stubs (methodology) +├── spec/ ← this spec (single source of truth) +├── docker-compose.yml ← backend :8000 + frontend :5173 +├── Dockerfile.backend ← used by compose +└── README.md ``` ## Layers | Layer | Responsibility | |-------|----------------| -| Generator | `bootstrap.py` reads `templates/`, substitutes tags, writes to `PROJECT_ROOT`. | -| API | FastAPI routers expose `/health` and `/api/chat`; optional `/api/runs` for trace storage in dev. | -| Agent Stub | A single node that returns a canned reply or forwards to an LLM if a key is present. | -| Data | SQLAlchemy 2 core + Alembic. SQLite in dev; swap to Postgres via DATABASE_URL without code changes. | -| Frontend | Vite + React + TypeScript. Calls `/api/chat` with fetch. | +| API | FastAPI routers expose `/health`, `/api/chat`, `/api/runs`, and the `/goals` loop. | +| Agent | `app/agent.py` supervisor→worker graph; live LLM when a key is present, else `[stub]`. | +| Capabilities | `capabilities/*.md` describe discrete capabilities; the supervisor can route by name. | +| Data | SQLAlchemy 2.0 core + SQLite in dev. Swap to Postgres via `DATABASE_URL` (no code change needed for the ORM). | +| Frontend | Vite + React + TypeScript. Calls `/api/chat` with `fetch`. | | Ops | Docker Compose binds backend `:8000` and frontend dev server `:5173`. | ## Data Flow 1. User POSTs `{messages:[...]}` to `/api/chat`. -2. FastAPI validates with Pydantic (optional; MVP passes through). -3. Agent stub builds a prompt, checks `LLM_API_KEY` in env. - - Key present: calls OpenAI-compatible or Anthropic-compatible endpoint. - - No key: returns stub response with the user message echoed back with a canned prefix. -4. Response is returned as `{"reply": "..."}`. +2. FastAPI validates with Pydantic (`ChatRequest`). +3. `run_graph()` runs the supervisor, then the worker: + - Key present (`LLM_API_KEY` or provider key): POSTs to the configured OpenAI-compatible `/v1/chat/completions` endpoint. + - No key: returns a `[stub]` reply echoing the user message. +4. Response returned as `{"reply": "...", "run_id": N}`; a `Run` row is persisted. 5. Frontend appends user bubble and assistant bubble. ## External Dependencies | Dependency | Purpose | Failure Mode | |------------|---------|--------------| -| OpenAI/Anthropic API | Live agent responses (optional) | Stub path is used automatically; service remains up. | -| Docker | Local dev via Compose | User can run backend/frontend manually with `npm run dev` + `uvicorn`. | -| npm | Frontend build | Fails clearly with `npm install` error. | -| uv / pip | Python deps | Fails clearly at install step. | +| LLM API (OpenAI-compatible) | Live agent responses (optional) | Stub path used automatically; service stays up. | +| Docker | Local dev via Compose | Run backend/frontend manually: `uvicorn` + `npm run dev`. | +| npm | Frontend build | Fails clearly at `npm install`. | +| pip / venv | Python deps | Fails clearly at install step. | ## Stack -- **Agent framework:** LangGraph-compatible supervisor graph (minimal in Phase 1; extensible to multi-worker, planning, reflection) -- **LLM provider + model:** Anthropic / `claude-sonnet-4-6` default; Gemini / `gemini-3.1-pro` alternative; OpenRouter fallback -- **Backend:** FastAPI + Uvicorn -- **Database + ORM:** PostgreSQL + SQLAlchemy 2.0 / Alembic in prod; SQLite in dev via DATABASE_URL switch -- **Frontend:** React 18 + Vite 6 + TypeScript + Tailwind in `web/`; optional Expo app in `mobile/` -- **Packaging:** `pyproject.toml` + venv in backend; `package.json` in frontends -- **Agent harness:** each generated project includes `harness/skills//SKILL.md` plus `harness/agents/*.md` stubs for orchestrator, worker, and qa patterns -- **Protocol surfaces:** MCP server stub and A2A message channel stubs in generated backend +- **Agent framework:** hand-rolled supervisor/worker graph (no LangGraph/CrewAI/AutoGen dependency — keeps the install surface small). Extensible to multi-worker, planning, reflection. +- **LLM provider + model:** configurable via `LLM_PROVIDER` (`gemini` | `openai`), `LLM_API_KEY` / `GEMINI_API_KEY`, `LLM_BASE_URL`, `LLM_MODEL`. Default: Gemini `gemini-1.5-flash` via the OpenAI-compatible endpoint. +- **Backend:** FastAPI + Uvicorn + SQLAlchemy 2.0 + Alembic. +- **Database:** SQLite in dev (`DATABASE_URL`); Postgres-ready via the same `DATABASE_URL` switch (no code change for the ORM). +- **Frontend:** React 18 + Vite 5 + TypeScript + Tailwind `frontend/` (not `web/`). +- **Packaging:** `requirements.txt` + venv in `backend/`; `package.json` in `frontend/`. +- **Agent harness:** `harness/skills//SKILL.md` + `harness/agents/*.md` stubs for orchestrator/worker/qa patterns (methodology, not required to run the scaffold). +- **Protocol surfaces:** not included in the base scaffold (add MCP/A2A when your agent needs them). | Key library | Version | Purpose | |-------------|---------|---------| -| fastapi | latest | API | -| uvicorn[standard] | latest | ASGI server | -| sqlalchemy | 2.x | ORM | -| alembic | latest | Migrations | +| fastapi | >=0.111 | API | +| uvicorn[standard] | >=0.30 | ASGI server | +| sqlalchemy | >=2.0 | ORM | +| alembic | >=1.13 | Migrations | +| pydantic-settings | >=2.4 | Config | +| httpx | >=0.27 | LLM calls | | react | 18 | Frontend UI | -| vite | 6 | Frontend tooling | -| axios or fetch | — | HTTP from frontend | - -**Avoid:** -- LangGraph / CrewAI / AutoGen in the base template (Phase 1 stub keeps dependency surface small). -- SQLAlchemy 1.x (ver 2 only). -- TypeScript generation beyond the template (no transpile-from-JS). +| vite | 5 | Frontend tooling | ## Deployment Model -- **Local dev:** `docker compose up` binds `:8000` and `:5173`. -- **GCP VM:** deploy with two containers on Cloud Run or a single GCE VM running Docker + Compose. -- **Migration:** `alembic upgrade head` runs as an init container or pre-start command in the backend Dockerfile. +- **Local dev:** `docker compose up --build` binds `:8000` and `:5173`. Or run manually: `uvicorn app.main:app --port 8000` + `npm run dev`. +- **GCP VM:** `deploy/` has a multi-stage `Dockerfile`, `gcp-vm-startup.sh`, `terraform/main.tf`, and `cloudbuild.yaml`. Single VM runs the backend image; the frontend is served via the dev server or built statically. +- **Migration:** tables are created via `Base.metadata.create_all` on import (dev). For prod use Alembic: `alembic upgrade head`. diff --git a/spec/data.md b/spec/data.md index aa31cec..5a7ad8c 100644 --- a/spec/data.md +++ b/spec/data.md @@ -4,7 +4,7 @@ ## Storage Technology -SQLite (dev) via SQLAlchemy 2.0 core + Alembic. Production swaps to Postgres by changing `DATABASE_URL` — migrations are identical. +SQLite (dev) via SQLAlchemy 2.0 core. Tables are created with `Base.metadata.create_all` on import in dev. Production can use Alembic (`alembic upgrade head`). Swap to Postgres by changing `DATABASE_URL` — the ORM models need no code change. ## Entities @@ -16,33 +16,35 @@ Represents a single agent invocation. Used for observability in dev. |-------|------|----------|-------------| | id | Integer | yes | Primary key | | created_at | DateTime | yes | Run start time | -| status | String(32) | yes | queued / running / succeeded / failed | -| user_message | Text | yes | Last user message (Phase 1 stores the request body). | +| status | String(32) | yes | `succeeded` / `error` | +| agent_id | String(128) | no | Optional agent/session tag | +| user_message | Text | yes | Last user message (what the user sent). | | assistant_message | Text | no | Generated reply, if any. | | error_message | Text | no | Reason for failure, if any. | +| trace | Text | no | Optional trace (unused in base scaffold). | -### Entity: `Message` (optional Phase 1) +### Entity: `Goal` -Persisted chat history. +Represents an autonomous multi-step run driven by the goal loop. | Field | Type | Required | Description | |-------|------|----------|-------------| | id | Integer | yes | Primary key | -| run_id | Integer | yes | Foreign key to `Run`. | -| role | String(16) | yes | `user` or `assistant`. | -| content | Text | yes | Message body. | -| created_at | DateTime | yes | Message time. | - -## Relationships - -- Run 1..n Message. +| created_at | DateTime | yes | Creation time | +| goal_text | Text | yes | Human-readable goal description | +| status | String(32) | yes | `pending` / `running` / `succeeded` / `failed` | +| steps | Text (JSON) | yes | JSON list of `{"description": "..."}` steps | +| step_index | Integer | yes | Number of steps completed | +| last_reply | Text | no | Reply from the most recent step | +| last_error | Text | no | Error from the most recent step, if any | +| run_id | Integer | no | Optional link to a `Run` | ## Data Lifecycle -- Created on `POST /api/chat`. -- Updated when the agent writes its reply. -- No deletion strategy in Phase 1; runs table grows unbounded; user can delete `data/app.db`. +- `Run` created on `POST /api/chat`; updated with the reply/error. +- `Goal` created on `POST /goals`; `step_index`/`status` updated as steps execute. +- No deletion strategy in the base scaffold; the `data/` SQLite file can be deleted to reset. ## Sensitive Data -- `.env` is ignored by git. No PII. The chat history is ephemeral and local. +- `.env` is git-ignored. No secrets are committed. Chat content is stored locally only. diff --git a/spec/roadmap.md b/spec/roadmap.md index 2077399..c272ca1 100644 --- a/spec/roadmap.md +++ b/spec/roadmap.md @@ -1,82 +1,72 @@ # Roadmap — `scaffold-agent` -> Filled by the spec-writer sub-agent. Every field is now authoritative for Phase 1. -> Tests run against real behavior on the tested path; generated outputs never enter `main`. +> Filled by the spec-writer sub-agent. Every field is authoritative. Tests run against real behavior on the tested path. --- ## What This Repo Does -This repo is the **scaffold product**: a self-contained hackathon starter-kit generator. -A user clones this repo and runs `scripts/bootstrap.py ` to emit a complete, runnable project — FastAPI backend, React web frontend, optional Expo mobile app, SQLite dev DB, Alembic migrations, Docker Compose, Hermes-native harness stubs, and a working multi-agent-ready skeleton — in a fresh directory. -It is optimized for the fastest cycle: target **20–60 minutes** from idea to a runnable agent that can be immediately hacked into a professional agent, including complex multi-agent workflows and product launches. -The repo itself remains the template source on `main`; generated files never touch `main`. +This repo is the **scaffold product**: a self-contained, runnable hackathon starter-kit for building AI agents. You clone it and run it directly — there is no generator step. The agent code (`backend/app/`) and `frontend/` run as-is. + +It is optimized for the fastest cycle: target **20–60 minutes** from idea to a runnable agent that can be immediately hacked into a professional agent, including complex multi-agent workflows and product launches. Reuse it as a boilerplate by copying `backend/`, `frontend/`, `deploy/`, `capabilities/` into a new repo and renaming `demo-agent` → your project. ## Who Uses It -Engineers in a hackathon, greenfield sprint, or weekend agent build who want the strongest June-2026-looking starting point: multi-agent architecture, web + optional mobile, Hermes-native packaging, and deploy-ready Docker/GCP paths, without manually wiring ports, sessions, migrations, or frontend shells. +Engineers in a hackathon, greenfield sprint, or weekend agent build who want the strongest starting point: multi-agent architecture (supervisor/worker), web UI, capability-plugin convention, Hermes-native packaging, and deploy-ready Docker/GCP paths — without manually wiring ports, sessions, persistence, or frontend shells. ## Core Problem Being Solved -Most scaffold repos are either empty boilerplate, opinionated generators that lock you into one app shape, or missing the newest agent/mobile architecture patterns. -This repo gives you a **single command scaffold** that drops a runnable agent scaffold into a sibling directory you can immediately open in Claude Code, Hermes, Docker, or deploy to a GCP VM. It is built to support passtimate idea-to-running-app times of 20–60 minutes for hackathons and sprint ideas that can grow into full products or agencies. +Most scaffold repos are either empty boilerplate or opinionated generators that lock you into one app shape. This repo gives you a **runnable** agent scaffold you can open in Claude Code, Hermes, Docker, or deploy to a GCP VM, then extend in place. It is built to support idea-to-running-app times of 20–60 minutes for hackathon/sprint ideas that can grow into full products. ## Success Criteria -- `scripts/bootstrap.py my-project` creates a runnable project in a fresh directory in <60s. -- Fastest supported path creates a working agent demo in **20–60 minutes** from idea to deploy-ready state. -- `cd my-project && docker compose up` serves backend + web + optional mobile. -- `GET /health` returns 200. -- React loads and calls backend successfully. -- Expo app can be launched against backend in the same stack when requested. -- A real LLM key optionally enables live agent responses; without it, the supervisor stub behaves predictably. -- Output project includes Hermes-native stubs: `harness/skills//SKILL.md`, `harness/agents/*.md`, and tool surfaces ready for MCP/A2A. -- Generated project is usable as the base for the most complex June-2026 multi-agent applications, including multi-agent workflows, video/image agents, or backend-security-testing agencies. -- `docker compose up` ends-to-end satisfies the same demo as the manual commands. +- `git clone` + install + `docker compose up --build` serves backend + web. +- `GET /health` returns `{"status":"ok"}`. +- React loads and calls backend successfully via `/api/chat`. +- A real LLM key optionally enables live agent responses; without it, the supervisor stub behaves predictably (`[stub]` prefix). +- `backend/tests/` pass; `evals/` pass in stub mode (live test skips without a key). +- CI (`.github/workflows/ci.yml`) runs tests + frontend build on every push/PR. +- `deploy/` paths produce a runnable GCP VM. ## What This Repo Does NOT Do - It does not implement user-facing application logic beyond the minimal agent stub. -- It does not manage generated projects in `main`. -- It does not include LangGraph, CrewAI, or AutoGen runtime in the base template (agent slot is a stub until Phase 2). +- It does not include a project generator (`bootstrap.py` / `templates/`) — the code is the template. +- It does not include LangGraph, CrewAI, or AutoGen runtime in the base template (the supervisor/worker graph is hand-rolled to keep the dependency surface small). - It does not auto-deploy; deployment instructions are provided for GCP VM. ## Key Constraints -- `main` is spec + generator-only; generated project trees never land on `main`. -- Python setup uses `venv`, not system Python. -- npm is used for the generated frontend (no pnpm). -- The generated backend defaults to SQLite in dev, PostgreSQL-ready in prod. -- LLM keys are provided via `.env`; no secrets are committed. +- The repo root IS the agent project (no `/` subdirectory). +- Python: `venv` + `requirements.txt` (no `uv` required; `uv` works too). +- npm for the frontend (no pnpm). +- SQLite in dev; Postgres-ready via `DATABASE_URL`. +- LLM keys via `.env`; no secrets committed. ## Phases of Development > Each phase is one human-testable increment, behind a testing gate. Commands are exact. -### Phase 1 — Scaffold Kit (this handoff) - -- **Goal:** Deliver a runnable scaffold kit where a single command creates a working FastAPI + React + SQLite project. -- **Independent slices:** - - `slice-generator` (backend) — `scripts/bootstrap.py` + embedded templates, output materialisation, substitution engine. - - `slice-backend-template` (backend) — FastAPI project manifest: `main.py`, `db.py`, `models.py`, `/health`, `/api/chat`, Alembic config. - - `slice-frontend-template` (frontend) — Vite + React + TS chat UI, Tailwind, fetch wiring. - - `slice-ops` (ops) — `docker-compose.yml`, `Dockerfile.backend`, `Dockerfile.frontend`, `.env.example`, README. -- **Gate command:** `uv run pytest tests/unit/ -v` (unit tests against `/health` using TestClient), plus `npm run build` inside the generated `frontend/`. -- **How the user tests it (handoff seed):** - 1. From repo root: `python3 scripts/bootstrap.py my-project` - 2. `cd my-project` (output is sibling to repo, never inside main). - 3. `python3 -m venv venv && source venv/bin/activate && pip install -r requirements.txt` - 4. `docker compose up --build` - 5. Confirm `curl http://localhost:8000/health` -> 200. - 6. Confirm React loads at `http://localhost:5173` and the chat input calls `/api/chat`. - 7. `docker compose down` - 8. `cd frontend && npm install && npm run build` must pass. +### Phase 1 — Runnable Scaffold (this handoff) + +- **Goal:** A runnable FastAPI + React + SQLite agent with a supervisor/worker graph. +- **Shipped slices:** + - `slice-backend` — FastAPI app: `main.py`, `config.py`, `db.py`, `models.py`, `agent.py`, routers (`health`, `chat`, `goal`). + - `slice-frontend` — Vite + React + TS chat UI, Tailwind, fetch wiring to `/api/chat`. + - `slice-ops` — `docker-compose.yml`, `Dockerfile.backend`, `frontend/Dockerfile.frontend`, `.env.example`, `README.md`. + - `slice-ci` — `.github/workflows/ci.yml` (backend tests, evals, frontend build, deploy-image build). +- **Gate command:** `cd backend && .venv/bin/python -m pytest tests -v` and `cd frontend && npm run build`. +- **How the user tests it:** + 1. `cd backend && python3 -m venv .venv && source .venv/bin/activate && pip install -r requirements.txt` + 2. `uvicorn app.main:app --port 8000` (or `docker compose up --build`) + 3. `curl http://localhost:8000/health` → `{"status":"ok"}` + 4. Open `http://localhost:5173`; chat input calls `/api/chat`. ### Phase 2 — Agent Slot + Persistence (shipped) -- **Goal:** Replace the Phase-1 agent stub with a compact supervisor/worker graph, lightweight run persistence, and a Hermes goal loop. +- **Goal:** Supervisor/worker graph with live LLM when env set, run persistence, and a Hermes goal loop. - **Shipped slices:** - - `slice-agent-graph` — `app/agent.py` supervisor→worker flow; live LLM when env set, else `[stub]`. + - `slice-agent-graph` — `app/agent.py` supervisor→worker flow; live LLM when key set, else `[stub]`. - `slice-persistence` — `Run` table; `/api/chat` writes a run receipt + `run_id`. - `slice-runs-api` — `/api/runs`, `/api/runs/{run_id}`. - `slice-goal-loop` — `Goal` table + `/goals`, `/goals/{id}/run`, `/goals/{id}/next`, `/goals/{id}`; `harness/patterns/goal-loop.md`. @@ -92,7 +82,6 @@ This repo gives you a **single command scaffold** that drops a runnable agent sc - **Shipped slices:** - `slice-capability-stub` — `capabilities/README.md` + `capabilities/example.md` describing the one-file-per-capability convention the supervisor can route to. - -- **Goal:** Allow `--slot transform_text` or other capability names to swap the stub graph node and prompt template. + - `slice-capability-loader` — `app/capabilities.py` loads `capabilities/*.md` at startup into a `{slug: doc}` route table; exposed via `GET /capabilities`. +- **Goal:** Drop a new `capabilities/.md` to register a capability the supervisor can route by name. - **Dependencies:** Phase 2. -- **Gate command:** `python scripts/bootstrap.py my-agent --slot transform_text` produces a different graph node.