Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
151 changes: 109 additions & 42 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -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 `<!-- FILL IN -->`, 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
Expand All @@ -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] <message>`. 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=<YOUR_GCP_PROJECT>
```

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/<name>.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.
47 changes: 40 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -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] <message>`. 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

Expand All @@ -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
Expand All @@ -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=<YOUR_GCP_PROJECT>
```

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

Expand Down
7 changes: 5 additions & 2 deletions Dockerfile.backend → backend/Dockerfile.backend
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
81 changes: 81 additions & 0 deletions backend/app/capabilities.py
Original file line number Diff line number Diff line change
@@ -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/<slug>.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())
6 changes: 5 additions & 1 deletion backend/app/main.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
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


def create_app() -> FastAPI:
app = FastAPI(title="demo-agent")
# Load capability docs once at startup.
_capabilities.load_capabilities()

app.add_middleware(
CORSMiddleware,
Expand All @@ -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():
Expand Down
11 changes: 11 additions & 0 deletions backend/app/routers/capabilities.py
Original file line number Diff line number Diff line change
@@ -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()}
Loading
Loading