A small multi-agent system, kept minimal so it stays easy to learn.
Four LLM-powered agents — Researcher, Writer, Coder, Reviewer — composed into a sequential pipeline, exposed over a FastAPI HTTP surface, with a React + Vite + Tailwind frontend that lets you run a pipeline and watch each agent's reply appear.
Designed as a learning artifact: every concept fits in one screen, every file is reviewable in one sitting, and the package follows a strict style guide (frozen value objects, Protocol-based dependency injection, no work at import time, modern Python type syntax, full mypy strict).
┌─ Frontend (React 19 · Vite · Tailwind v4) ─────────────────────────────┐
│ Dashboard · Run Pipeline · Agents │
└────────────────────┬───────────────────────────────────────────────────┘
│ HTTP/JSON (Vite proxies /api → :8000)
┌────────────────────▼───────────────────────────────────────────────────┐
│ FastAPI app (src/multi_agent/app.py) │
│ GET /health GET /agents POST /pipelines/run │
└────────────────────┬───────────────────────────────────────────────────┘
│
┌────────────────────▼───────────────────────────────────────────────────┐
│ pipeline.py · agent.py · messages.py · llm.py │
│ run_pipeline() │
│ ├─ AGENT_REGISTRY → Researcher · Writer · Coder · Reviewer │
│ └─ each agent.process() ──► LLMClient │
│ ├─ OpenAIClient (real) │
│ └─ StubLLMClient (no API key) │
└────────────────────────────────────────────────────────────────────────┘
Five small files under src/multi_agent/:
| File | Responsibility | LOC |
|---|---|---|
messages.py |
AgentMessage frozen dataclass |
24 |
llm.py |
LLMClient Protocol + OpenAI + Stub |
58 |
agent.py |
BaseAgent + 4 agents + AGENT_REGISTRY |
95 |
pipeline.py |
run_pipeline(user_input, names, llm) |
37 |
app.py |
FastAPI app, all 3 routes inline | 110 |
Total: 324 lines of source + 144 lines of tests.
# Backend
uv sync --extra dev
uv run pytest # 14 tests
uv run uvicorn multi_agent.app:create_app --factory --reload # http://127.0.0.1:8000
# Frontend
cd frontend
bun install
bun run dev # http://localhost:5173The frontend's Vite dev server proxies /api/* to 127.0.0.1:8000, so just open http://localhost:5173 and run a pipeline.
| Env var | Default | Notes |
|---|---|---|
OPENAI_API_KEY |
(unset) | If unset, the app falls back to StubLLMClient and stays fully runnable with no external calls. |
OPENAI_MODEL |
gpt-5.4-nano |
Any chat-completions model your key supports. |
Copy .env.example to .env to get started.
GET /health
GET /agents
POST /pipelines/run { "user_input": "...", "agents": ["researcher", "writer"] }Example:
curl -s -X POST http://localhost:8000/pipelines/run \
-H 'Content-Type: application/json' \
-d '{"user_input":"Outline a REST endpoint.","agents":["researcher","writer","coder","reviewer"]}'# Backend
ruff format --check .
ruff check .
mypy --strict src tests
pytest
# Frontend
bun run lint
bun run typecheck
bun run test
bun run buildThe whole point of the registry pattern: extension is mechanical.
- Add a class to
src/multi_agent/agent.py:class CriticAgent(BaseAgent): name = "critic" description = "Pokes holes in everything." system_prompt = "You are the Critic. Identify weak spots in the conversation."
- Add it to
AGENT_REGISTRYin the same file. - Add a test.
No edits to routes, services, factory, or workflow code — there is none.
MIT