Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

4 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Agent Forge β€” the agent that builds agents

Type a plain-English request and watch a multi-agent system design itself in real time: task analysis, agent roles, an animated workflow graph, tool selection with rationales, a memory schema, runnable Python scaffolding, and a simulated execution trace β€” all streamed live over Server-Sent Events.

Runs fully offline with an intelligent mock engine if no LLM key is set, so the demo never breaks. Drop in a provider key (Groq / Gemini / Anthropic / OpenAI) and it switches to real generation. Any LLM failure silently falls back to the mock engine.


Table of contents


Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   Next.js 14 + Tailwind            β”‚   SSE   β”‚   FastAPI (uvicorn)          β”‚
β”‚   React Flow + lucide-react        β”‚ ──────▢ β”‚   3-stage pipeline:          β”‚
β”‚   /  hero  /build  (4-pane UI)     β”‚         β”‚   PLAN β†’ CODEGEN β†’ SIMLOG    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜         β”‚     ↳ LLM provider OR mock   β”‚
                                               β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

No database. No auth. No persistent state β€” generated specs live in an in-process Python dict keyed by UUID. The frontend reads everything over a single SSE stream from POST /api/generate.


Project layout

agent-forge/
β”œβ”€β”€ backend/
β”‚   β”œβ”€β”€ main.py              FastAPI app, 5 endpoints
β”‚   β”œβ”€β”€ orchestrator.py      3-stage async pipeline; provider dispatch
β”‚   β”œβ”€β”€ mock_engine.py       Keyword-driven offline generator (demo safety net)
β”‚   β”œβ”€β”€ prompts.py           System prompts for PLAN / CODEGEN / SIMLOG
β”‚   β”œβ”€β”€ requirements.txt     Pinned Python deps
β”‚   β”œβ”€β”€ Dockerfile           python:3.11-slim image
β”‚   β”œβ”€β”€ .env.example         Template for provider keys
β”‚   └── .env                 Your local keys (gitignored)
β”œβ”€β”€ frontend/
β”‚   β”œβ”€β”€ app/
β”‚   β”‚   β”œβ”€β”€ page.tsx         Hero / prompt input
β”‚   β”‚   β”œβ”€β”€ build/page.tsx   4-pane builder (tabs Β· graph Β· code Β· terminal)
β”‚   β”‚   β”œβ”€β”€ layout.tsx       Root layout, fonts
β”‚   β”‚   └── globals.css      Forge palette, grid backdrop, scrollbars
β”‚   β”œβ”€β”€ components/
β”‚   β”‚   β”œβ”€β”€ AgentCards.tsx
β”‚   β”‚   β”œβ”€β”€ CodePanel.tsx    Typewriter-effect code viewer
β”‚   β”‚   β”œβ”€β”€ Terminal.tsx     Replays simulation log
β”‚   β”‚   β”œβ”€β”€ WorkflowGraph.tsx  React Flow with BFS-depth layout
β”‚   β”‚   β”œβ”€β”€ StageProgress.tsx
β”‚   β”‚   └── nodes/CustomNode.tsx
β”‚   β”œβ”€β”€ lib/
β”‚   β”‚   β”œβ”€β”€ api.ts           SSE reader + export URL helper
β”‚   β”‚   └── types.ts         Spec / Agent / Tool / WfNode / LogLine
β”‚   β”œβ”€β”€ tailwind.config.ts   Forge ember/cyan palette, pop & glow keyframes
β”‚   β”œβ”€β”€ next.config.js       Rewrites /api/* β†’ backend
β”‚   β”œβ”€β”€ tsconfig.json
β”‚   └── package.json
β”œβ”€β”€ run.sh                   Convenience launcher (backend + frontend)
└── README.md

Prerequisites

Component Version
Python 3.10 or newer (image uses 3.11)
Node.js 18 or newer
Docker Optional β€” only for the containerised backend

No system services (Postgres, Redis, etc.) are required.


Running the backend β€” Option A: Docker

The provided Dockerfile builds a self-contained image. From the repository root:

cd backend

# 1. Build the image
docker build -t agent-forge-backend .

# 2. Run it
docker run --rm -p 8000:8000 \
  --env-file .env \                    # optional β€” only if you have a key
  --name agent-forge-backend \
  agent-forge-backend

The container exposes port 8000 and starts uvicorn with --host 0.0.0.0 --proxy-headers. To skip the env file and run purely in mock mode, drop the --env-file flag.

Pass env vars inline if you prefer:

docker run --rm -p 8000:8000 \
  -e GROQ_API_KEY=gsk_... \
  -e MODEL=llama-3.3-70b-versatile \
  agent-forge-backend

Sanity check:

curl http://localhost:8000/api/health
# β†’ {"ok":true,"mode":"groq"}    (or "demo (offline mock)")

docker compose (optional)

If you'd rather not type long docker run lines, drop this into backend/docker-compose.yml:

services:
  backend:
    build: .
    ports:
      - "8000:8000"
    env_file:
      - .env
    restart: unless-stopped

Then docker compose up --build from backend/.


Running the backend β€” Option B: Python venv

This is the path used during local development. Hot-reloads on file changes.

cd backend

# 1. Create and activate a virtual environment
python3 -m venv .venv
source .venv/bin/activate              # Windows: .venv\Scripts\activate

# 2. Install dependencies
pip install --upgrade pip
pip install -r requirements.txt

# 3. (Optional) copy the env template and add a key
cp .env.example .env
# edit .env to add ONE of GROQ_API_KEY / GEMINI_API_KEY / ANTHROPIC_API_KEY / OPENAI_API_KEY

# 4. Run with auto-reload
uvicorn main:app --reload --port 8000

On startup you should see:

[orchestrator] python=/.../backend/.venv/bin/python
[orchestrator] PROVIDER=groq MODEL=(default)
INFO:     Uvicorn running on http://127.0.0.1:8000

If PROVIDER=None, no key was detected β€” that's fine, the mock engine handles everything.

Heads-up. python-dotenv loads .env once at import time. After editing the file, restart uvicorn (Ctrl+C β†’ uvicorn main:app --reload). --reload watches Python files, not .env.


Running the frontend

The frontend is independent of how the backend is started, as long as the backend is reachable on port 8000 (or whatever you set in NEXT_PUBLIC_API).

cd frontend
npm install
npm run dev

Open http://localhost:3000.

frontend/next.config.js proxies /api/* to http://localhost:8000 by default. To point it elsewhere (e.g. a deployed backend), export NEXT_PUBLIC_API before npm run dev:

NEXT_PUBLIC_API=https://my-backend.example.com npm run dev

Production build:

npm run build
npm run start                          # serves the prebuilt app

One-shot launcher (macOS / Linux)

For convenience, run.sh starts both processes:

./run.sh
# Backend on :8000  Β·  Frontend on :3000  Β·  Ctrl+C to stop both

It creates the venv if missing, installs Python deps, runs npm install, and launches both servers in the background. Not suitable for production.


Configuring an LLM provider

The orchestrator auto-detects exactly one provider, with this priority:

GEMINI_API_KEY  β†’  GROQ_API_KEY  β†’  ANTHROPIC_API_KEY  β†’  OPENAI_API_KEY

Set one in backend/.env:

# Pick ONE block:

# --- Groq (free tier, very fast) ---
GROQ_API_KEY=gsk_...
MODEL=llama-3.3-70b-versatile          # default; also: openai/gpt-oss-120b, etc.

# --- Google Gemini ---
# GEMINI_API_KEY=AIza...
# MODEL=gemini-2.5-flash

# --- Anthropic Claude ---
# ANTHROPIC_API_KEY=sk-ant-...
# MODEL=claude-3-5-sonnet-latest

# --- OpenAI ---
# OPENAI_API_KEY=sk-...
# MODEL=gpt-4o

If a real provider call fails for any reason (rate limit, 5xx, malformed JSON, missing key), the orchestrator silently falls back to the mock engine for that stage. The pipeline always completes.


Tech stack and libraries

Backend (backend/requirements.txt)

Library Version Why it's here
fastapi 0.115.0 HTTP framework, SSE streaming, Pydantic request validation
uvicorn[standard] 0.30.6 ASGI server with --reload, websockets, http-tools
pydantic 2.9.2 Request/response models (GenReq)
python-dotenv 1.0.1 Loads backend/.env on import
anthropic 0.39.0 Native Claude SDK (used only if ANTHROPIC_API_KEY is set)
openai 1.55.0 Native OpenAI SDK (used only if OPENAI_API_KEY is set)
google-genai latest Google's native Gemini SDK (avoids httpx proxies incompat)
groq latest Native Groq SDK (used only if GROQ_API_KEY is set)

Each provider's library is imported lazily β€” only the active provider's package is touched at runtime, so an outage in one SDK can't crash startup.

Frontend (frontend/package.json)

Package Version Why
next 14.2.5 App-router framework, dev rewrites for /api/*
react 18.3.1 UI runtime
react-dom 18.3.1 DOM renderer
reactflow 11.11.4 Workflow graph with custom nodes
lucide-react 0.395.0 Icon set (Bot, GitBranch, Github, …)
typescript 5.5.3 Type-safe components
tailwindcss 3.4.4 Utility CSS β€” see tailwind.config.ts for the forge palette
postcss 8.4.39 CSS processing pipeline
autoprefixer 10.4.19 Vendor prefixes
@types/react 18.3.3 React types
@types/node 20.14.9 Node types

Fonts (loaded via Google Fonts in layout.tsx):

  • Bricolage Grotesque β€” display headings
  • JetBrains Mono β€” body, code, terminal

API reference

All endpoints are unauthenticated, CORS-open (*), and live under /api/.

Method Path Returns Notes
GET /api/health JSON {ok, mode} β€” mode is the active provider or "demo (offline mock)"
GET /api/examples JSON List of 4 starter prompts
POST /api/generate SSE Body: {"prompt": "..."} β€” streams data: events through the 3 stages
GET /api/spec/{sid} JSON Full spec for a finished run; 404 if id unknown
GET /api/export/{sid}?format= zip|json format=zip (default) β†’ agent.py + spec.json + requirements.txt + README.md. format=json β†’ raw spec

SSE event shape

{ "stage": "plan",   "status": "running",  "data": null,           "id": "..." }
{ "stage": "plan",   "status": "complete", "data": { /* spec */ }, "id": "..." }
{ "stage": "codegen","status": "running",  "data": null,           "id": "..." }
{ "stage": "codegen","status": "complete", "data": { "code": "..." }, "id": "..." }
{ "stage": "simlog", "status": "running",  "data": null,           "id": "..." }
{ "stage": "simlog", "status": "complete", "data": { "simulation": [...] }, "id": "..." }
{ "stage": "done",   "status": "complete", "data": null,           "id": "..." }
{ "stage": "stored", "id": "..." }

Pipeline stages

The orchestrator (backend/orchestrator.py) runs three LLM calls in sequence, each in a thread (asyncio.to_thread) so the SSE stream stays responsive:

  1. PLAN β€” given the user prompt, return a JSON spec: analysis, 3–6 agents, tools with rationales, workflow graph (nodes/edges including at least one decision when applicable), and a memory schema. JSON mode is enabled (response_format=json_object for OpenAI/Groq, response_mime_type=application/json for Gemini).
  2. CODEGEN β€” given the JSON spec, emit a single agent.py file using a LangGraph-style structure (TypedDict state, one function per agent, StateGraph wiring, conditional edges for decisions, tool stubs with docstrings + TODO comments).
  3. SIMLOG β€” produce 18–30 simulated log entries (ts, agent, level, msg) showing realistic handoffs via memory keys, exactly one warning + recovery, and a final success summary.

The exact prompts are in backend/prompts.py β€” those are the product; iterate on them more than the surrounding code.


The generated zip β€” what to do with it

The zip contains four files:

agent.py            LangGraph-style scaffold (one function per agent)
spec.json           Full plan: agents, tools, workflow, memory schema
requirements.txt    Just: langgraph
README.md           Minimal "pip install … && python agent.py"

To run as-is:

unzip agent_system_<id>.zip -d my_agent && cd my_agent
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
python agent.py

This will walk the StateGraph and log [Agent Name] running for each node, then exit. Every agent body is a stub with a TODO comment β€” no actual tools are implemented. Think of it as create-next-app for multi-agent systems: the structure is laid out, you fill in the bodies.

To turn the scaffold into a working agent:

  1. pip install the real SDK for each tool listed in spec.json (e.g. slack-sdk, PyGithub, openai).
  2. Replace the TODO line in each agent function with the real API calls, writing results into state[...] using keys from spec.json's memory.schema.
  3. Put secrets (GITHUB_TOKEN, SLACK_BOT_TOKEN, …) in a .env and load with python-dotenv.
  4. For any decision-type node in spec.json, replace the linear g.add_edge(...) with g.add_conditional_edges(...) returning the next node id.
  5. Swap the dummy app.invoke({}) for an entrypoint that takes real input.

Troubleshooting

Build page is stuck on "Awaiting architecture…" forever. Open the network tab and confirm /api/generate is streaming. If the first event arrives but nothing follows, the LLM provider is likely retrying internally β€” check the uvicorn log for [PLAN LLM ERROR] / [CODEGEN LLM ERROR] lines (Gemini 503s during peak hours are common). Swap to Groq or gemini-1.5-flash, or comment the key out to use the mock.

/api/health returns mode: "demo (offline mock)" even though I set a key. Restart uvicorn after editing .env β€” python-dotenv only reads it at import.

The code panel cuts off mid-function. The codegen response hit max_tokens. Bump the value in backend/orchestrator.py (currently 8000 across providers; Groq allows up to ~32k for llama-3.3-70b-versatile).

docker build fails compiling a wheel. The image installs build-essential gcc libffi-dev libssl-dev for exactly this case. If something newer than what's pinned still fails, rebuild with --no-cache.

Frontend says "Backend unreachable". The Next.js rewrite in next.config.js proxies /api/* to http://localhost:8000. Make sure the backend is listening there, or set NEXT_PUBLIC_API before npm run dev.

Releases

Packages

Contributors

Languages