Skip to content

How to Run ML Multi-Agent Framework (Step-by-Step Guide)

This guide walks you through running the full Multi-Agent Framework locally on your machine.

We’ll go in the correct startup order:

1️⃣ MCP Servers → 2️⃣ Agents → 3️⃣ Router → 4️⃣ (Optional) Web Client (Testing)

The order matters — each layer depends on the one before it.


Before You Start: Understand the Architecture

Knowing why you're doing each step will make troubleshooting much easier.

--- config: layout: elk --- graph TB subgraph Database["Database  "] DB[("PostgreSQL Database  ")] AgentReg[mcp_servers table] MCPReg[deployed_agents table] end subgraph Components["Components  "] Agent[ML Agent] MCP[MCP Server] Router[Router] end Agent -->|register_agent| AgentReg MCP -->|register_server| MCPReg Router -->|query agents| AgentReg Router -->|discover_mcp_servers| MCPReg Agent -->|request via Router| Router Router -->|route to| MCP MCP -->|response| Router Router -->|response| Agent AgentReg -.stored in.- DB MCPReg -.stored in.- DB class DB,AgentReg,MCPReg dbStyle class Agent,MCP,Router compStyle

MCP Servers

These expose tools (DB writes, API calls, model access, etc.).

  • Agents depend on MCP servers.
  • If required MCP servers are not running, agents may fail silently or crash.
  • MCP servers register themselves in PostgreSQL.

Agents

Each agent:

  • Looks up MCP servers in PostgreSQL.
  • Registers itself in PostgreSQL.
  • Exposes an API endpoint.

Router

The Router:

  • Is the single entry point for frontend requests.
  • Reads agent metadata from PostgreSQL.
  • Routes requests deterministically using agent_key.

Optional Integrations

Some components may reference:

  • Sentry
  • Temporal
  • Langfuse
  • Kafka

These are not required for basic functionality. Missing credentials will only affect specific actions, not the core system.

Prerequisites

Python (<= 3.12)

Some dependencies (e.g., LangGraph) require Python ≤ 3.13. Asynchronous calls may not be properly managed in Python versions newer than 3.12.

If you have a newer version installed:

  • Install Python 3.12 from python.org
  • Use that version for virtual environments

Verify:

python --version

PostgreSQL

Install PostgreSQL locally.

Default expected credentials:

POSTGRES_HOST=localhost
POSTGRES_PORT=5432
POSTGRES_DB=agents_system_base
POSTGRES_USER=postgres
POSTGRES_PASSWORD=postgres

After installation, use the PgAdmin utility shipped with the database to create the database and two tables.

Note

Ensure you have the necessary administrative permissions to create a database and tables.

Create the Database

Create a database named:

agents_system_base

Create Required Tables

Create the mcp_servers and deployed_agents tables.

mcp_servers table
CREATE TABLE IF NOT EXISTS public.mcp_servers
(
    pk_id integer NOT NULL GENERATED ALWAYS AS IDENTITY,
    name varchar(100),
    url varchar(255),
    description text,
    tags text,
    is_active boolean,
    updated_at timestamp with time zone,
    CONSTRAINT unique_name UNIQUE (name)
);
deployed_agents table
CREATE TABLE IF NOT EXISTS public.deployed_agents
(
    name varchar(100),
    agent_key varchar(255),
    url varchar(255),
    capabilities text,
    keywords text,
    is_active boolean,
    updated_at timestamp with time zone,
    id integer NOT NULL GENERATED BY DEFAULT AS IDENTITY,
    CONSTRAINT deployed_agents_name_key UNIQUE (name)
);

Rust (Required for Python Dependencies)

Why you need Rust

  • Some Python packages (like cryptography, PyO3-based packages, maturin-built packages) now require Rust to compile native extensions.
  • If Rust/Cargo is not on your system PATH, pip tries a temporary install → slow and often fails.

Installation

Visual Studio Prerequisites

Install the Visual Studio's latest version (currently 2022) with minimum requirements which are the Build Tools for Visual Studio without the IDE. Select “Desktop Development with C++” in the Visual Studio installer. For more information on installation process, review the following document: MSVC prerequisites.

Why this happens

On Windows:

  • Rust defaults to the MSVC toolchain
  • That toolchain depends on Microsoft’s C++ linker
  • Fresh Windows machines don’t include it
  • VS Code does NOT provide compilers or linkers

So until Build Tools are installed, Rust cannot finish compiling packages like:

  • cryptography
  • writeable
  • pyo3-based libs
  • maturin packages

Install Rust

  • Go to Rustup Installer page.
  • Download and run the installer (choose default installation).

Verify installation:

rustc --version
cargo --version

Create Cache Folder for Dependencies

Make a new empty folder that will be used as pip cache. For example, C:\cache\pip. The cache accelerates the installation of Python dependencies for Agent projects.

Collect Required Credentials

PostgreSQL

Default expected credentials:

POSTGRES_HOST=localhost
POSTGRES_PORT=5432
POSTGRES_DB=agents_system_base
POSTGRES_USER=postgres
POSTGRES_PASSWORD=postgres

PocketBase

POCKETBASE_URL=...
POCKETBASE_SERVICE_EMAIL=...
POCKETBASE_SERVICE_PASSWORD=...

AWS Bedrock

AWS_ACCESS_KEY_ID=...
AWS_SECRET_ACCESS_KEY=...
AWS_REGION=us-east-1

If you don’t have credentials, request them from your helpdesk.


Clone the Repositories

Clone the following repos:

MCP Servers:

  • mcp.storycraft.python
  • mcp.projectstructure.python
  • mcp.generationtasks.python
  • mcp.modelsinformation.python

Agents:

  • imagegenerationagent.python
  • projectagent.python
  • storycraftagent.python
  • videogenerationagent.python

Router:

  • routerservice.python

Optional Web/Test Client:

  • agentswebclient.python

You can create an empty folder (for example, ML) and clone all necessary git repositories to that folder:

echo MCP Servers
git clone git@git.icvr.io:icvr/xavier/ml/multi-agent-system/mcp-servers/mcp.storycraft.python.git
git clone git@git.icvr.io:icvr/xavier/ml/multi-agent-system/mcp-servers/mcp.projectstructure.python.git
git clone git@git.icvr.io:icvr/xavier/ml/multi-agent-system/mcp-servers/mcp.generationtasks.python.git
git clone git@git.icvr.io:icvr/xavier/ml/multi-agent-system/mcp-servers/mcp.modelsinformation.python.git
echo Agents
git clone git@git.icvr.io:icvr/xavier/ml/multi-agent-system/agents/imagegenerationagent.python.git
git clone git@git.icvr.io:icvr/xavier/ml/multi-agent-system/agents/projectagent.python.git
git clone git@git.icvr.io:icvr/xavier/ml/multi-agent-system/agents/storycraftagent.python.git
git clone git@git.icvr.io:icvr/xavier/ml/multi-agent-system/agents/videogenerationagent.python.git
echo Router
git clone git@git.icvr.io:icvr/xavier/ml/multi-agent-system/routerservice.python.git
echo Web Client
git clone git@git.icvr.io:icvr/xavier/ml/multi-agent-system/prototypes/agentswebclient.python.git

All repositories use the Develop branch by default.

Tip

You can run VS Code, open the ML folder and within the VSCode IDE open each repo folder in the integrated terminal to start servers, agents and router.

Step 1 — Start MCP Servers

If unsure which agent depends on which MCP server — just run all four.

For each MCP server do the following:

Open Repo Folder

Open terminal inside the repo directory.


Configure Environment

copy .env.example .env

Fill in:

  • PostgreSQL credentials
  • PocketBase credentials
  • AWS credentials

Create Virtual Environment

python -m venv .venv
.venv\Scripts\activate
pip install -r requirements.txt
pip install "psycopg[binary]"

The psycopg[binary] ensures PostgreSQL native bindings are installed.


Start Server

python scripts/start_server.py

You should see:

INFO:     Application startup complete.
INFO:     Uvicorn running on http://localhost:80XX (Press CTRL+C to quit)

Verify Registration

Open PostgreSQL database (you can use the PgAdmin utility) and examine the mcp_servers table.

You should see one row per running MCP server.

Registered MCP Servers

If not:

  • Check DB credentials
  • Check server logs

Step 2 — Start Agents

Repeat the following actions for each agent repository.


Configure .env

copy .env.example .env

Fill required credentials.


Install Dependencies

py -m venv .venv
.venv\Scripts\activate
python -m pip install --upgrade pip setuptools wheel
pip install --upgrade --cache-dir C:\cache\pip psycopg[binary] -r requirements.txt

Start Agent

python main.py

Expected:

INFO:     Application startup complete.
INFO:     Uvicorn running on http://localhost:80XX (Press CTRL+C to quit)

Verify Registration

Open PostgreSQL database (you can use the PgAdmin utility) and examine the deployed_agents table.

Each agent should register itself automatically.

Registered Agents

If missing:

  • Check DB config
  • Confirm MCP servers are running

Step 3 — Start the Router

The Router must start after agents.


Configure Environment

copy .env.example .env

Fill PostgreSQL credentials.


Install & Run

python -m venv .venv
.venv\Scripts\activate
pip install -r requirements.txt
pip install "psycopg[binary]"
python main.py

Confirm Agent Discovery

On startup, router logs should say it found agents.

If it doesn't:

  • Check deployed_agents table
  • Verify DB credentials

Step 4 — Run the Web Test Client (Optional)

Start the agentswebclient.python project

It uses the same pattern: create venv, install, run the provided scripts. The .env file is not used in that project, and you do not have to specify any settings to run it properly.

Run the following commands:

python -m venv .venv
.venv\Scripts\activate
pip install -r requirements.txt

Prepare a payload (IDs + routing fields)

The example_payloads folder contains sample JSON files for different scenarios.

The payload format described includes:

  • user_id (from marketplace / PocketBase)
  • project_id
  • conversation_id …and some constant fields that don’t change at the moment.

Routing fields:

  • agents_mode is “deterministic”
  • agent_key selects which agent gets the request
storycraft.json
{
    "message": "hi",
    "user_id": "05a62227-d340-7447-8314-194b6595a76a",
    "project_id": "qo1aog8hrihsuvu",
    "conversation_id": "5s9admp8wyvaeb8",
    "context": "sct_bible",
    "agents_mode": "deterministic",
    "agent_key": "storycraft",
    "stream_format": "ndjson"
}

You should edit the existing payload (or copy it to create a new one), and specify the project_id and conversation_id.

Where to get IDs

  • Project ID: created by the frontend flow; you can fetch it from the Pocketbase Projects collection (the text shows getting an existing project and copying its ID).
  • Conversation ID: the frontend auto-creates conversations; you pick the one for the agent/page you’re testing and copy its ID.

Project ID in Pocketbase

Conversation ID in AI Portal

Run Interactive script

Use the script chat_interactive_json.py that accepts a payload; you (re)start it after changing IDs so it connects to the proper conversation:

python src\chat_interactive_json.py example_payloads\storycraft.json

The dialog may proceed like this:

(.venv) C:\ML\agentswebclient.python>python src\chat_interactive_json.py example_payloads\storycraft.json

============================================================
AgentRouter - Interactive Chat
============================================================
Commands:
  /quit or q - Exit chat
  /info - Show session information
  /payload - Show current payload
  /raw - Toggle raw JSON display
============================================================


============================================================
Session Information
============================================================
URL: http://localhost:8000
User ID: xxxx-xxx-xxxxxx-xxxx-xxxxxxxxxx
Project ID: qo1aog8hrihsuvu
Card ID: N/A
Context: sct_bible
Agent Mode: deterministic
Agent Key: storycraft
Stream Format: ndjson
============================================================

You: hi!
Agent: Hey there! 👋

You're working on **"Beaver Battle"** - your story about intelligent beavers defending their dam against an evil force.

You've got your Story Bible all set up, and now you're ready to bring this story to life! Would you like to:

- **Generate the script** - Create the full screenplay with scenes and action
- **Brainstorm variations** - Explore different creative directions for your beaver story
- **Generate everything** - Create script, characters, and environments all at once

What sounds good to you?
  🔧 Tool: send_suggestions(actions=[{'label': 'Generate Script', 'type': 'message', 'value': 'generate the script'}, {'label': 'Brainstorm Ideas', 'type': 'message', 'value': 'help me brainstorm'}, {'label': 'Generate Everything', 'type': 'message', 'value': 'generate everything'}])

  ⏳ Thinking about next steps...
  ✅ Result: Command(update={'messages': [ToolMessage(content='Sent 3 suggestion buttons to user.', tool_call_id=...
Agent:
You: No, thanks
Agent: No problem! Is there something specific you'd like to work on with your Beaver Battle story, or would you prefer to explore something different? I'm here to help however you'd like!
You: 

Final Checklist

Confirm:

  • All 4 MCP servers running
  • mcp_servers table populated
  • Agents running
  • deployed_agents table populated
  • Router running and discovered agents
  • Test client successfully sends payload

Addendum - Python Script

launch_framework.py
#!/usr/bin/env python3
"""
launch_framework.py — Orchestrate:
MCP servers -> Agents -> Router 

Commands:
  python launch_framework.py clone
  python launch_framework.py prep
  python launch_framework.py start
  python launch_framework.py status
  python launch_framework.py logs
  python launch_framework.py stop
"""

from __future__ import annotations

import argparse
import os
import platform
import shutil
import signal
import subprocess
import sys
import time
from pathlib import Path
from typing import Dict, List


# --------------------------------------------------
# CONFIGURATION (edit only if folder names change)
# --------------------------------------------------

MCP_REPOS = [
    "mcp.storycraft.python",
    "mcp.projectstructure.python",
    "mcp.generationtasks.python",
    "mcp.modelsinformation.python",
]

AGENT_REPOS = [
    "imagegenerationagent.python",
    "projectagent.python",
    "storycraftagent.python",
    "videogenerationagent.python",
]

ROUTER_REPO = "routerservice.python"
CLIENT_REPO = "agentswebclient.python"

# ---- Git clone URLs ----
CLONE_MAP = {
    "mcp.storycraft.python":
        "git@git.icvr.io:icvr/xavier/ml/multi-agent-system/mcp-servers/mcp.storycraft.python.git",
    "mcp.projectstructure.python":
        "git@git.icvr.io:icvr/xavier/ml/multi-agent-system/mcp-servers/mcp.projectstructure.python.git",
    "mcp.generationtasks.python":
        "git@git.icvr.io:icvr/xavier/ml/multi-agent-system/mcp-servers/mcp.generationtasks.python.git",
    "mcp.modelsinformation.python":
        "git@git.icvr.io:icvr/xavier/ml/multi-agent-system/mcp-servers/mcp.modelsinformation.python.git",

    "imagegenerationagent.python":
        "git@git.icvr.io:icvr/xavier/ml/multi-agent-system/agents/imagegenerationagent.python.git",
    "projectagent.python":
        "git@git.icvr.io:icvr/xavier/ml/multi-agent-system/agents/projectagent.python.git",
    "storycraftagent.python":
        "git@git.icvr.io:icvr/xavier/ml/multi-agent-system/agents/storycraftagent.python.git",
    "videogenerationagent.python":
        "git@git.icvr.io:icvr/xavier/ml/multi-agent-system/agents/videogenerationagent.python.git",

    "routerservice.python":
        "git@git.icvr.io:icvr/xavier/ml/multi-agent-system/routerservice.python.git",
    "agentswebclient.python":
        "git@git.icvr.io:icvr/xavier/ml/multi-agent-system/prototypes/agentswebclient.python.git",
}

MCP_ENTRY = Path("scripts") / "start_server.py"
AGENT_ENTRY = Path("main.py")
ROUTER_ENTRY = Path("main.py")
CLIENT_ENTRY = Path("main.py")

RUN_DIRNAME = ".run"
LOGS_DIRNAME = "logs"
PIDS_DIRNAME = "pids"


# --------------------------------------------------
# Helpers
# --------------------------------------------------

def is_windows():
    return platform.system().lower().startswith("win")


def venv_python(repo_dir: Path):
    if is_windows():
        return repo_dir / ".venv" / "Scripts" / "python.exe"
    return repo_dir / ".venv" / "bin" / "python"


def ensure_dirs(root: Path):
    logs = root / RUN_DIRNAME / LOGS_DIRNAME
    pids = root / RUN_DIRNAME / PIDS_DIRNAME
    logs.mkdir(parents=True, exist_ok=True)
    pids.mkdir(parents=True, exist_ok=True)
    return logs, pids


def ensure_git():
    try:
        subprocess.check_call(["git", "--version"], stdout=subprocess.DEVNULL)
    except Exception:
        raise RuntimeError("git not found on PATH. Install Git first.")


# --------------------------------------------------
# Clone logic
# --------------------------------------------------

def clone_repos(root: Path):
    ensure_git()

    repos = MCP_REPOS + AGENT_REPOS + [ROUTER_REPO] + CLIENT_REPO

    for repo in repos:
        target = root / repo
        if target.exists():
            print(f" exists: {repo}")
            continue

        if repo not in CLONE_MAP:
            raise RuntimeError(f"No clone URL configured for {repo}")

        print(f"⬇️  cloning: {repo}")
        subprocess.check_call(["git", "clone", CLONE_MAP[repo], str(target)])

    print(" clone complete.")


# --------------------------------------------------
# Environment / venv / deps
# --------------------------------------------------

def copy_env(repo_dir: Path):
    env = repo_dir / ".env"
    example = repo_dir / ".env.example"
    if not env.exists() and example.exists():
        shutil.copyfile(example, env)
        print(f" created .env in {repo_dir.name} (fill secrets before start)")


def ensure_venv(repo_dir: Path, python_cmd: str):
    venv_dir = repo_dir / ".venv"
    if venv_dir.exists():
        return
    print(f" creating venv: {repo_dir.name}")
    subprocess.check_call([python_cmd, "-m", "venv", ".venv"], cwd=repo_dir)


def ensure_deps(repo_dir: Path):
    req = repo_dir / "requirements.txt"
    if not req.exists():
        return
    py = venv_python(repo_dir)
    print(f" installing deps: {repo_dir.name}")
    subprocess.check_call([str(py), "-m", "pip", "install", "--upgrade", "pip"], cwd=repo_dir)
    subprocess.check_call([str(py), "-m", "pip", "install", "psycopg[binary]", "-r", "requirements.txt"], cwd=repo_dir)


# --------------------------------------------------
# Service control
# --------------------------------------------------

def start_service(root: Path, logs: Path, pids: Path, name: str, repo: str, entry: Path):
    repo_dir = root / repo
    py = venv_python(repo_dir)

    if not py.exists():
        raise RuntimeError(f"{repo} missing venv. Run prep first.")

    log_file = logs / f"{name}.log"
    pid_file = pids / f"{name}.pid"

    print(f"  starting {name}")

    log = open(log_file, "ab")

    env = os.environ.copy()
    env["PYTHONUTF8"] = "1"

    proc = subprocess.Popen(
        [str(py), "-X", "utf8", str(entry)],
        cwd=repo_dir,
        stdout=log,
        stderr=subprocess.STDOUT,
        env=env,
    )

    pid_file.write_text(str(proc.pid))
    print(f"   PID={proc.pid}")

def strip_unicode(s: str) -> str:
    return s.encode("ascii", "ignore").decode("ascii")

def stop_all(root: Path):
    pids_dir = root / RUN_DIRNAME / PIDS_DIRNAME
    if not pids_dir.exists():
        return

    for pid_file in pids_dir.glob("*.pid"):
        pid = int(pid_file.read_text())
        print(f" stopping {pid_file.stem}")
        try:
            os.kill(pid, signal.SIGTERM)
        except Exception:
            pass
        pid_file.unlink()

    print(" stopped.")


# --------------------------------------------------
# Main
# --------------------------------------------------

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("command", choices=["clone", "prep", "start", "stop", "status"])
    parser.add_argument("--python", default="py" if is_windows() else "python3")
    args = parser.parse_args()

    root = Path(__file__).resolve().parent
    logs, pids = ensure_dirs(root)

    if args.command == "clone":
        clone_repos(root)
        return

    if args.command in ["prep"]:
        clone_repos(root)

        repos = MCP_REPOS + AGENT_REPOS + [ROUTER_REPO]

        for repo in repos:
            repo_dir = root / repo
            copy_env(repo_dir)
            ensure_venv(repo_dir, args.python)
            ensure_deps(repo_dir)

        if args.command == "prep":
            print(" prep complete.")
            return

    if args.command == "start":
        for i, r in enumerate(MCP_REPOS):
            start_service(root, logs, pids, f"mcp-{i+1}", r, MCP_ENTRY)

        time.sleep(2)

        for i, r in enumerate(AGENT_REPOS):
            start_service(root, logs, pids, f"agent-{i+1}", r, AGENT_ENTRY)

        time.sleep(2)

        start_service(root, logs, pids, "router", ROUTER_REPO, ROUTER_ENTRY)

        print(" all services started.")
        return

    if args.command == "stop":
        stop_all(root)
        return

    if args.command == "status":
        for pid_file in (root / RUN_DIRNAME / PIDS_DIRNAME).glob("*.pid"):
            pid = int(pid_file.read_text())
            try:
                os.kill(pid, 0)
                print(f" {pid_file.stem} (PID={pid})")
            except OSError:
                print(f" {pid_file.stem} not running")


if __name__ == "__main__":
    main()

Usage

# clone only
python launch_framework.py clone

# clone + prep
python launch_framework.py prep

# full start
python launch_framework.py start

# status
python launch_framework.py status

# stop
python launch_framework.py stop