Skip to content

anmolg1997/Multi-Agent-AI-Framework

Repository files navigation

Multi-Agent AI Framework logo

Multi-Agent AI Framework

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.

Python 3.12+ React 18 Google ADK FastAPI Docker CI License: MIT PRs Welcome


Table of contents


Why this exists

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.


What makes this different

  • Deep delegation pattern — Root coordinator composes planner, coder, reviewer, and executor via Google ADK AgentTool, mirroring production “orchestrator + specialists” setups.
  • YAML-first agents & workflows — Personas and DAG-style compositions live in version-controlled YAML; PUT /agents/{name}/config can 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.

High-level architecture

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
Loading

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.


Low-level architecture

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
Loading

main.py wires Redis, SQLAlchemy, ToolRegistry, AgentFactory, WorkflowFactory, EventBus, TaskService, and (when enabled) A2A agent cards on application state.


Why we built it this way

We bet on Google ADK because the runtime matches how we want to compose agents

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.”

Tools speak through a protocol so FastAPI stays thin

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.

YAML agents exist so prompts can ride the same PR train as your product

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 + Postgres split hot path from durable recall

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.

A2A is HTTP JSON on purpose

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.

The event bus keeps WebSockets out of TaskService

Normalized task events publish on EventBus; the WebSocket route subscribes. Swap in Kafka or Redis pub/sub later without rewriting the task loop.


What you can build

  1. Automated code review pipeline — Run the code_review sequential workflow (coder → reviewer) on each pull-request summary or diff text; extend with conditional routing when you add branch keywords in conditional workflows.
  2. Research assistant — Use the research parallel workflow so planner and coder explore complementary angles, then consolidate in a follow-up coordinator task.
  3. Multi-step task execution — Post a task to the coordinator with a rich prompt; sub-agents delegate through AgentTool while the UI subscribes to /ws/tasks/{id}/stream.
  4. Agent-as-a-service via A2A — Enable A2A_ENABLED, publish agent cards from /a2a/cards, and let remote systems enqueue work with POST /a2a/agents/{agent_name}/message (returns task_id for 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.


Up and running in 5 minutes

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).

1. Clone and copy environment

git clone https://github.com/anmolg1997/Multi-Agent-AI-Framework.git
cd Multi-Agent-AI-Framework
cp env.example .env

You should see a fresh .env next to env.example. Edit it: set OPENAI_API_KEY, REDIS_URL, DATABASE_URL, and optionally A2A_ENABLED.

2. Raise Redis and Postgres

docker compose up -d redis postgres

Typical Compose output ends with containers Started for redis and postgres.

3. Install dependencies

make setup

Expect a Python venv under backend/.venv, resolved requirements, and npm install finishing in frontend/ without errors.

4. Run API + UI together

make dev

5. Prove the loop with health and a task

make health
# or:
curl -s http://localhost:8000/health/live

Example 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.

Full stack in Docker

docker compose up --build

Services: 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.


How the code is organized

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

Configuring everything

Settings merge in this order: defaults in Pydantic modelsconfig.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.

agentsconfig_dir, max_iterations, default_model for personas on disk.

workflowsconfig_dir for *.yaml workflow definitions.

toolsenabled list filters which tool packs attach to agents; sandbox sets timeout; plugins_dir loads extra Python plugins.

memorysession.backend + ttl; long_term.backend; artifacts.backend (local or s3) and artifacts.path.

a2aenabled, port (surfaced via A2A_ENABLED / A2A_PORT); agents list reserved for future filtering.

observabilitytracing, 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.


The API at a glance

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.


Frontend

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.


Wiki

Extended documentation lives in wiki/Home.md:


Want to help?

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.


Part of the AI Engineering Portfolio

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

License

This project is licensed under the MIT License — see LICENSE.


Author

Anmol Jaiswalgithub.amrom.workers.dev/anmolg1997

About

Production multi-agent orchestration with Google ADK — coordinator, planner, coder, reviewer agents, YAML-driven personas, A2A protocol, FastAPI + React dashboard

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors