One agent is a chatbot. Five agents with a coordinator? That’s a system.
Production-grade multi-agent orchestration with Google ADK, FastAPI, and optional Agent-to-Agent (A2A) HTTP APIs — YAML personas, observable tasks, and a React operator console.
- Why this exists
- What makes this different
- High-level architecture
- Low-level architecture
- Why we built it this way
- What you can build
- Up and running in 5 minutes
- How the code is organized
- Configuring everything
- The API at a glance
- Frontend
- Wiki
- Want to help?
- License
- Author
You have already stitched together “call model A, then model B” in a notebook. That works until you need repeatable orchestration: versioned personas, bounded tools, durable-ish task history, and a single HTTP surface your other services can trust.
Multi-Agent AI Framework is an open-source reference stack for running multiple LLM agents under Google’s Agent Development Kit (ADK) with a FastAPI control plane and a React 18 operator console. You define agents as YAML, register tools as Python (ToolProtocol + ToolRegistry), and choose between async tasks (WebSocket-subscribable) or batch workflows (synchronous HTTP with full event traces).
Redis holds ephemeral task and session keys. PostgreSQL stores long-term memory rows. The filesystem artifact store keeps binary and text blobs with path traversal guards. If you flip A2A_ENABLED=true, you also get HTTP ingress and JSON agent cards so another team can enqueue work without importing your codebase.
Note
Who tends to reach for this: platform engineers wiring ADK into an internal portal, applied researchers comparing planner–coder–reviewer loops, and product teams prototyping agent-as-a-service behind stable HTTP boundaries.
- Deep delegation pattern — Root
coordinatorcomposes planner, coder, reviewer, and executor via Google ADKAgentTool, mirroring production “orchestrator + specialists” setups. - YAML-first agents & workflows — Personas and DAG-style compositions live in version-controlled YAML;
PUT /agents/{name}/configcan persist agent overrides back to disk. - Observable async tasks — Tasks stream ADK events through an in-process event bus to WebSocket clients while Redis holds durable task records.
- Tooling you can extend — Built-in filesystem, web search, sandboxed code execution, and shell tools plus dynamic webhook tools and Python plugins under
tools/plugins/. - Cross-service agents — Optional A2A routes expose message ingress and JSON agent cards for discovery when
A2A_ENABLED=true.
This is the story at 30,000 feet: browser, gateway, ADK runtime, data stores, and optional A2A consumers.
flowchart LR
classDef primary fill:#0ea5e9,stroke:#0284c7,color:#fff
classDef secondary fill:#06b6d4,stroke:#0891b2,color:#fff
classDef accent fill:#f0f9ff,stroke:#0ea5e9,color:#0c4a6e
classDef store fill:#0f172a,stroke:#0ea5e9,color:#7dd3fc
subgraph FE["Frontend (React 18 + Vite)"]
UI[Control Center]:::accent
CHAT[Agent Chat]:::accent
DAG[Workflow DAG]:::accent
end
subgraph GW["FastAPI Gateway"]
REST[REST: tasks · agents · workflows · tools]:::primary
WS[WebSocket: task stream]:::primary
A2A_OPT["A2A (optional)"]:::secondary
end
subgraph ORCH["ADK orchestration"]
COORD[Coordinator LlmAgent]:::primary
SUB[Sub-agents via AgentTool]:::secondary
WF[Workflow roots: Sequential / Parallel / Loop / Conditional]:::secondary
end
subgraph POOL["Agent pool"]
P[Planner]:::accent
C[Coder]:::accent
R[Reviewer]:::accent
E[Executor]:::accent
end
subgraph EXT["Tools · A2A · Memory"]
TR[ToolRegistry + plugins]:::primary
A2A_PEER[Remote A2A peers]:::accent
RD[(Redis)]:::store
PG[(PostgreSQL)]:::store
ART[Artifact store]:::store
end
UI --> REST
CHAT --> REST
DAG --> REST
UI --> WS
REST --> COORD
REST --> WF
COORD --> SUB
SUB --> POOL
WF --> POOL
POOL --> TR
REST --> A2A_OPT
A2A_OPT --> COORD
A2A_PEER -.->|A2AClient| A2A_OPT
REST --> RD
REST --> PG
REST --> ART
The coordinator path is the default multi-agent experience: one root LlmAgent exposes specialists wrapped as AgentTool. Workflows bypass that root and materialize ADK SequentialAgent, ParallelAgent, LoopAgent, or a router LlmAgent for conditional graphs.
Same system, but with the Python package layout you will actually grep.
flowchart TB
classDef primary fill:#0ea5e9,stroke:#0284c7,color:#fff
classDef secondary fill:#06b6d4,stroke:#0891b2,color:#fff
classDef accent fill:#f0f9ff,stroke:#0ea5e9,color:#0c4a6e
classDef store fill:#0f172a,stroke:#0ea5e9,color:#7dd3fc
subgraph API["app.api.routes"]
T[tasks.py]:::primary
A[agents.py]:::primary
W[workflows.py]:::primary
TL[tools.py]:::primary
H[health.py]:::secondary
WS_R[ws.py]:::secondary
A2A_R[a2a.py]:::secondary
end
subgraph SVC["app.services"]
TS[task_service.py]:::primary
WR[workflow_runner.py]:::primary
end
subgraph AG["app.agents"]
AF[AgentFactory]:::primary
CO[coordinator.py]:::secondary
PL[planner.py · coder.py · reviewer.py · executor.py]:::accent
end
subgraph WF["app.workflows"]
WF_F[WorkflowFactory]:::primary
SEQ[sequential.py]:::accent
PAR[parallel.py]:::accent
LOOP[loop.py]:::accent
COND[conditional.py]:::accent
end
subgraph TOOLS["app.tools"]
REG[ToolRegistry]:::primary
FS[filesystem]:::secondary
WSCH[web_search]:::secondary
CE[code_execution]:::secondary
SH[shell]:::secondary
PLG[plugins/*.py]:::accent
end
subgraph MEM["app.memory"]
SS[session.py SessionStore]:::primary
LT[long_term.py]:::primary
ART_M[artifacts.py]:::secondary
CTX[context.py]:::accent
end
subgraph OBS["app.observability"]
EB[event_bus.py]:::primary
MET[metrics.py]:::secondary
TRA[tracer.py]:::secondary
end
RD_EXT[(Redis client)]:::store
PG_EXT[(SQLAlchemy async)]:::store
T --> TS
WS_R --> EB
TS --> EB
TS --> CO
TS --> AF
WR --> WF_F
WR --> AF
AF --> REG
CO --> AF
PL --> AF
REG --> FS & WSCH & CE & SH & PLG
TS --> RD_EXT
LT --> PG_EXT
main.py wires Redis, SQLAlchemy, ToolRegistry, AgentFactory, WorkflowFactory, EventBus, TaskService, and (when enabled) A2A agent cards on application state.
You get first-class LlmAgent, AgentTool, and composites (SequentialAgent, ParallelAgent, LoopAgent) plus InMemoryRunner streaming. That lines up with a coordinator that delegates and workflows that compile to ADK trees — without you maintaining a parallel graph engine in this repo.
Note
We still considered LangGraph for explicit state machines and checkpointing; heavier bridging cost for this codebase. CrewAI and AutoGen optimize different interaction shapes than “orchestrator + tools-as-agents.”
ToolProtocol plus ToolRegistry keeps tool authors decoupled from HTTP. You register builders, resolve names at agent build time, and can add webhook tools at runtime via POST /tools/register.
Tip
If you are writing tests, stub a builder on the registry instead of mocking half the filesystem stack.
Operators edit personas without redeploying Python. Restart reloads files; PUT /agents/{name}/config merges and writes YAML under agents.config_dir.
Important
API merges update disk and in-memory config; a full process restart is how you guarantee every worker picked up the same file state in multi-worker deployments.
Redis: task JSON, cancellation flags, optional SessionStore KV with TTL, SCAN for dashboards. Postgres: MemoryEntry rows for cross-session recall. Mixing them would either amplify writes on the hot path or weaken durability for long-term memory.
GET /a2a/cards and POST /a2a/agents/{agent_name}/message give other teams a language-agnostic contract. A2AClient + A2AConnectionConfig covers the outbound story.
Warning
Webhook tools POST to URLs you supply — treat them like outbound integrations: trusted networks or egress allowlists only.
Normalized task events publish on EventBus; the WebSocket route subscribes. Swap in Kafka or Redis pub/sub later without rewriting the task loop.
- Automated code review pipeline — Run the
code_reviewsequential workflow (coder → reviewer) on each pull-request summary or diff text; extend with conditional routing when you add branch keywords inconditionalworkflows. - Research assistant — Use the
researchparallel workflow so planner and coder explore complementary angles, then consolidate in a follow-up coordinator task. - Multi-step task execution — Post a task to the coordinator with a rich prompt; sub-agents delegate through
AgentToolwhile the UI subscribes to/ws/tasks/{id}/stream. - Agent-as-a-service via A2A — Enable
A2A_ENABLED, publish agent cards from/a2a/cards, and let remote systems enqueue work withPOST /a2a/agents/{agent_name}/message(returnstask_idfor polling or streaming).
Tip
Tighten tools.enabled in production when you only want read-only filesystem or no shell — the global allowlist is your safety rail.
You will need Python 3.12+, Node.js 20+, uv (recommended for the Makefile-driven venv), Redis and PostgreSQL (local or Docker), and LLM credentials (e.g. OPENAI_API_KEY when LLM_PROVIDER=openai).
git clone https://github.com/anmolg1997/Multi-Agent-AI-Framework.git
cd Multi-Agent-AI-Framework
cp env.example .envYou should see a fresh .env next to env.example. Edit it: set OPENAI_API_KEY, REDIS_URL, DATABASE_URL, and optionally A2A_ENABLED.
docker compose up -d redis postgresTypical Compose output ends with containers Started for redis and postgres.
make setupExpect a Python venv under backend/.venv, resolved requirements, and npm install finishing in frontend/ without errors.
make dev- API: http://localhost:8000 — OpenAPI at
/docs - UI: http://localhost:5173
make health
# or:
curl -s http://localhost:8000/health/liveExample liveness response:
{ "status": "ok" }Create a task:
curl -s -X POST http://localhost:8000/tasks \
-H "Content-Type: application/json" \
-d '{"prompt":"Outline a minimal FastAPI health module.","agent_name":"coordinator"}'Example creation response:
{"task_id":"<uuid>"}Execute a workflow synchronously:
curl -s -X POST http://localhost:8000/workflows/execute \
-H "Content-Type: application/json" \
-d '{"name":"code_review","prompt":"Review this snippet: def add(a,b): return a+b"}'You get a WorkflowResult JSON object with workflow, events, and result_text.
docker compose up --buildServices: PostgreSQL on 5432, Redis on 6379, backend on 8000, frontend on 5173 (container nginx on port 80 mapped to host 5173 per docker-compose.yml).
Important
Skim config.yaml after first boot: agents.config_dir, workflows.config_dir, tools.enabled, sandbox timeout, and observability defaults all live there.
Multi-Agent-AI-Framework/
├── README.md # This file
├── LICENSE # MIT
├── Makefile # setup, dev, test, lint, docker helpers
├── docker-compose.yml # postgres, redis, backend, frontend
├── config.yaml # Declarative defaults merged with env
├── env.example # Environment variable template
├── docs/
│ └── assets/
│ └── logo.svg # Repository logo (vector)
├── wiki/ # Deep-dive documentation (Markdown)
├── backend/
│ ├── Dockerfile
│ ├── requirements.txt
│ └── app/
│ ├── main.py # FastAPI app, lifespan wiring
│ ├── core/ # Settings, LLM helpers, logging, exceptions
│ ├── agents/ # AgentFactory, YAML configs, coordinator, builders
│ ├── workflows/ # WorkflowFactory + sequential/parallel/loop/conditional
│ ├── tools/ # ToolRegistry, builtins, plugins/
│ ├── memory/ # Redis session store, Postgres memory, artifacts, context
│ ├── protocols/ # A2A HTTP router, client, agent cards
│ ├── observability/ # Event bus, metrics, tracing hooks
│ ├── services/ # TaskService, WorkflowRunner
│ └── api/ # Routes and FastAPI dependencies
└── frontend/
├── Dockerfile
├── package.json
├── vite.config.ts
└── src/
├── App.tsx # Shell: nav, theme, section routing
├── components/ # AgentChat, WorkflowDAG, TaskHistory, etc.
├── services/api.ts # Axios client + WebSocket URL helper
└── store/ # Zustand UI state
Settings merge in this order: defaults in Pydantic models → config.yaml (loaded at import for nested sections) → environment variables (.env / shell) → runtime API updates for agent YAML via PUT /agents/{name}/config (persists to disk; in-memory until you reason about worker reload).
llm — Default provider hints in YAML; LLM_PROVIDER, LLM_MODEL, LLM_TEMPERATURE, LLM_MAX_TOKENS, LLM_STREAMING override at runtime.
agents — config_dir, max_iterations, default_model for personas on disk.
workflows — config_dir for *.yaml workflow definitions.
tools — enabled list filters which tool packs attach to agents; sandbox sets timeout; plugins_dir loads extra Python plugins.
memory — session.backend + ttl; long_term.backend; artifacts.backend (local or s3) and artifacts.path.
a2a — enabled, port (surfaced via A2A_ENABLED / A2A_PORT); agents list reserved for future filtering.
observability — tracing, metrics, log_level, log_format (JSON structured logs via structlog).
Provider API keys: OPENAI_API_KEY, ANTHROPIC_API_KEY, GOOGLE_API_KEY as required by your model string (see resolve_adk_model in app/agents/base.py for OpenAI/Anthropic/Ollama prefixes).
Caution
Never commit real API keys into YAML or git. Keep secrets in the environment.
| Method | Path | Description |
|---|---|---|
GET |
/health/live |
Liveness probe |
GET |
/health/ready |
Redis ping + PostgreSQL SELECT 1 |
POST |
/tasks |
Create async task; body: prompt, optional agent_name, session_id |
GET |
/tasks |
List recent tasks (Redis scan, default limit=50) |
GET |
/tasks/{task_id} |
Task status, result, error, embedded events |
DELETE |
/tasks/{task_id} |
Cancel task (sets cancel event + status) |
GET |
/agents |
List AgentConfig records |
GET |
/agents/{name} |
Single agent config |
PUT |
/agents/{name}/config |
Merge-update agent; persists YAML under agents config dir |
GET |
/workflows |
List workflow definitions |
GET |
/workflows/{name} |
Single workflow config |
POST |
/workflows/execute |
Run workflow synchronously; returns WorkflowResult |
GET |
/tools |
List registered tool builder names |
POST |
/tools/register |
Register webhook-backed tool (name, description, webhook_url) |
GET |
/a2a/cards |
List agent cards (when A2A router mounted) |
POST |
/a2a/agents/{agent_name}/message |
Enqueue task via A2A; returns task_id |
WS |
/ws/tasks/{task_id}/stream |
JSON stream: heartbeat, status, agent_event payloads |
FastAPI validation errors return JSON with a detail field (string or list of objects). Successful task creation returns {"task_id":"<uuid>"}.
Deep dives: wiki/API-Reference.md.
The Control Center is a Vite + React 18 + Tailwind single-page app built for operators, not end-user chat fluff.
| Area | Component | Role |
|---|---|---|
| Agents | AgentChat |
Submit prompts, pick agent_name, open WebSocket stream per task |
| Agents | ExecutionTimeline |
Visualize event timeline for the active task |
| Workflows | WorkflowDAG |
React Flow graph with demo execution states per workflow |
| Tasks | TaskHistory |
Browse and inspect historical task records |
| Tools | inline list | Registered tool names from GET /tools |
| Settings | AgentConfigurator |
Edit agent fields through PUT /agents/{name}/config |
Set VITE_API_URL for production API base path; VITE_WS_URL overrides WebSocket origin (defaults to current host with ws: / wss:).
Screenshots: drop PNGs under docs/assets/screenshots/ (for example agents-chat.png, workflows-dag.png) and link them from your docs site or release notes. The UI ships with a dark gradient shell and collapsible sidebar navigation.
Extended documentation lives in wiki/Home.md:
- Architecture — diagrams, data flow, component matrix
- Why we built it this way — ADK, protocols, persistence, A2A
- Agents — YAML schema, built-ins, custom agents
- Workflows — types, built-ins, extension patterns
- Tools — registry, builtins, plugins, safety
- A2A Protocol — cards, server routes,
A2AClient - Memory and State — Redis, Postgres, artifacts
- Configuration — full reference
- API Reference — payloads and WebSocket frames
- Getting Started — tutorial path
- Use Cases — end-to-end recipes
- Contributing — style and PR workflow
Fork the repository, create a feature branch, and open a pull request against main. Run make lint and make test before submitting. Prefer small, focused changes that match existing patterns (ToolProtocol, register_agent_builder, workflow YAML). Full conventions: wiki/Contributing.md.
This repo is one piece of a larger production AI toolkit. Each project stands alone, but they're designed to work together.
| Project | What it does |
|---|---|
| Enterprise-RAG-System | Hybrid search RAG with guardrails, reranking, and Langfuse observability |
| NL2SQL-Engine | Natural language to SQL with self-correction and multi-dialect support |
| SLM-From-Scratch | Build language models from scratch — tokenizer, transformer, training, alignment |
| LLM-Finetuning-Toolkit | LoRA/QLoRA fine-tuning with pluggable backends, YAML recipes, MLflow |
| Multi-LoRA-Serve | Multi-adapter inference gateway — one base model, many LoRA adapters per request |
| LoRA-Factory | Adapter lifecycle — train, evaluate, merge (TIES/DARE), version, and publish LoRAs |
| Domain-Adaptive-LLM | Domain specialization for medical, legal, finance, code with safety guardrails |
This project is licensed under the MIT License — see LICENSE.
Anmol Jaiswal — github.com/anmolg1997