AutoGen Multi-Agent Server on Ubuntu Mini-PC

This guide shows how to run AutoGen-based multi-agent workflows on a dedicated Ubuntu mini-PC (Beelink N100) acting as an "AI build/agent server," controlled remotely from your laptop via VS Code Remote SSH and a minimal HTTP API.

Introduction

This setup enables you to:

  • Run AutoGen agent workflows on a low-power, always-on mini-PC with Ubuntu
  • Edit code on the mini-PC directly from your laptop using VS Code Remote SSH
  • Trigger agent runs via a lightweight HTTP API without needing to SSH in
  • Easily switch between multiple repositories (workspaces) with a simple config file

The primary example workspace is best-of-ui, but the architecture is designed to support any number of repositories by adding entries to a workspace registry.

Why This Architecture?

Code lives locally on the mini-PC SSD for fast random I/O during builds and tests. Network shares (SMB/NFS) introduce latency that degrades build tool performance. VS Code Remote SSH provides a native editing experience while keeping the codebase local to the execution environment.

Prerequisites

Hardware

  • Mini-PC: Beelink MINIS 12 PRO (Intel N100, 16 GB RAM, 500 GB SSD) or similar x86 mini-PC
  • Laptop: Your primary development machine (any OS with VS Code)
  • Network: Both on the same LAN (Ethernet recommended for stability, Wi-Fi acceptable)

Software

  • Mini-PC OS: Ubuntu 24.04 LTS (or recent LTS version)
  • Laptop: VS Code with the "Remote - SSH" extension
  • Both machines: Git installed
  • Python: Python 3.10+ on the mini-PC

Accounts

  • Git hosting account (GitHub, GitLab, etc.) for your repositories
  • LLM provider API keys (OpenAI, Anthropic, etc.) as required by your AutoGen agents

Network and SSH Setup

Mini-PC Configuration

SSH into or physically access your mini-PC and run the following:

# Update system packages
sudo apt update && sudo apt upgrade -y

# Install OpenSSH server if not already installed
sudo apt install -y openssh-server

# Enable and start SSH service
sudo systemctl enable ssh
sudo systemctl start ssh

# Create a non-root developer user (if you don't have one)
sudo adduser devuser
sudo usermod -aG sudo devuser

# Set a static IP or configure DHCP reservation in your router
# Example static IP configuration (edit /etc/netplan/01-netcfg.yaml):
# network:
#   version: 2
#   ethernets:
#     eth0:
#       dhcp4: no
#       addresses: [192.168.1.100/24]
#       gateway4: 192.168.1.1
#       nameservers:
#         addresses: [8.8.8.8, 8.8.4.4]
# Then apply: sudo netplan apply

# Verify SSH is listening
sudo ss -tulpn | grep :22

Laptop SSH Key Setup

On your laptop, generate an SSH key if you don't have one:

# Generate SSH key (if needed)
ssh-keygen -t ed25519 -C "your_email@example.com"

# Copy public key to mini-PC
ssh-copy-id devuser@192.168.1.100

# Test SSH connection
ssh devuser@192.168.1.100

VS Code Remote SSH

On your laptop, open VS Code and install the Remote - SSH extension. Then configure the SSH host:

# Edit SSH config (on laptop)
# ~/.ssh/config

Host minipc-autogen
    HostName 192.168.1.100
    User devuser
    IdentityFile ~/.ssh/id_ed25519

In VS Code, press Cmd+Shift+P (Mac) or Ctrl+Shift+P (Windows/Linux), type "Remote-SSH: Connect to Host," and select minipc-autogen. You can now edit files on the mini-PC as if they were local.

Directory Layout and Workspace Concept

This setup uses a clear directory structure that supports multiple repositories without code changes:

/srv/
├── projects/              # Git repositories (workspaces)
│   ├── best-of-ui/       # Primary example workspace
│   ├── another-repo/     # Additional workspace
│   └── ...
└── autogen/              # AutoGen code and API
    ├── .venv/            # Python virtual environment
    ├── workspaces.yaml   # Workspace registry
    ├── api_server.py     # FastAPI HTTP server
    ├── agents.py         # AutoGen agent definitions
    ├── .env              # API keys and secrets
    ├── logs/             # Per-workspace job logs
    │   ├── best-of-ui/
    │   │   └── job_001.log
    │   └── another-repo/
    └── runs/             # Optional per-job artifacts
        └── best-of-ui/
Workspace Definition

A workspace is a logical name that maps directly to /srv/projects/<workspace>/. The HTTP API accepts a workspace name in each request, allowing you to switch repositories simply by changing the workspace parameter—no code changes required.

Preparing the Mini-PC

System Dependencies

SSH into the mini-PC (or use VS Code terminal) and install required packages:

# Install Python and build tools
sudo apt install -y python3 python3-venv python3-pip git build-essential

# Verify Python version (3.10+ required)
python3 --version

Create Directory Structure

# Create base directories
sudo mkdir -p /srv/projects /srv/autogen
sudo chown -R devuser:devuser /srv/projects /srv/autogen

Clone Primary Repository

# Clone best-of-ui as the first workspace
git clone https://github.com/yourusername/best-of-ui.git /srv/projects/best-of-ui

# Verify clone
ls -la /srv/projects/best-of-ui
Why Local Storage?

Keeping code on the mini-PC SSD ensures fast random I/O for npm/pip installs, test runners, and build tools. Network shares add 10–100ms latency per file operation, which compounds rapidly during builds with thousands of file reads.

Creating the AutoGen Environment

Virtual Environment Setup

# Navigate to AutoGen directory
cd /srv/autogen

# Create virtual environment
python3 -m venv .venv

# Activate virtual environment
source .venv/bin/activate

# Upgrade pip
pip install --upgrade pip

Install AutoGen and Dependencies

# Install AutoGen packages
pip install -U "autogen-agentchat>=0.4.0"
pip install "autogen-ext[openai]>=0.4.0"

# Install FastAPI and server dependencies
pip install "fastapi>=0.104.0" "uvicorn[standard]>=0.24.0"

# Install additional utilities
pip install python-dotenv pyyaml gitpython

Configure Secrets

Create a .env file in /srv/autogen/ with your API keys:

# /srv/autogen/.env
OPENAI_API_KEY=sk-proj-your-key-here
OPENAI_MODEL=gpt-4o
ANTHROPIC_API_KEY=your-anthropic-key-here

Make sure this file is not committed to Git:

echo ".env" >> /srv/autogen/.gitignore

Workspace Registry and Configuration

The workspace registry is a YAML file that maps workspace names to repository paths and repo-specific commands. This design allows the AutoGen agents and HTTP API to operate on any repository without hardcoded paths.

Create workspaces.yaml

# /srv/autogen/workspaces.yaml
workspaces:
  best-of-ui:
    path: /srv/projects/best-of-ui
    language: javascript
    test_command: "npm test"
    build_command: "npm run build"
    install_command: "npm install"
    description: "UI component library with design system"

  another-repo:
    path: /srv/projects/another-repo
    language: python
    test_command: "pytest"
    build_command: ""
    install_command: "pip install -r requirements.txt"
    description: "Python data processing pipeline"
Registry Design

Each workspace entry defines the file system path and repo-specific commands (test, build, install). AutoGen agents can read this config at runtime to execute the correct commands for each workspace. Adding a new repository requires only a config entry and a git clone—no code changes.

Defining the AutoGen Multi-Agent Workflow

Create a Python module that defines your AutoGen agents and orchestration logic, parameterized by workspace.

agents.py

# /srv/autogen/agents.py
import os
import yaml
from pathlib import Path
from dotenv import load_dotenv
from autogen_agentchat.agents import AssistantAgent
from autogen_agentchat.teams import RoundRobinGroupChat
from autogen_ext.models import OpenAIChatCompletionClient

load_dotenv()

def load_workspace_config():
    """Load workspace registry from YAML."""
    config_path = Path("/srv/autogen/workspaces.yaml")
    with open(config_path) as f:
        return yaml.safe_load(f)

def get_workspace_info(workspace_name):
    """Get workspace configuration by name."""
    config = load_workspace_config()
    if workspace_name not in config["workspaces"]:
        raise ValueError(f"Workspace '{workspace_name}' not found in registry")
    return config["workspaces"][workspace_name]

async def run_autogen_workflow(workspace: str, prompt: str, branch: str = "main"):
    """
    Run AutoGen multi-agent workflow on a specific workspace.

    Args:
        workspace: Workspace name from workspaces.yaml
        prompt: User task description for agents
        branch: Git branch to work on

    Returns:
        Workflow result and metadata
    """
    # Load workspace config
    ws_config = get_workspace_info(workspace)
    repo_path = Path(ws_config["path"])

    # Verify workspace exists
    if not repo_path.exists():
        raise FileNotFoundError(f"Workspace path does not exist: {repo_path}")

    # Initialize model client
    model_client = OpenAIChatCompletionClient(
        model=os.getenv("OPENAI_MODEL", "gpt-4o"),
        api_key=os.getenv("OPENAI_API_KEY")
    )

    # Define agents with workspace context
    architect = AssistantAgent(
        name="Architect",
        model_client=model_client,
        system_message=f"""You are a software architect analyzing code in the {workspace} repository.
        Repository path: {repo_path}
        Language: {ws_config['language']}
        Task: Review the codebase and propose architectural improvements."""
    )

    senior_dev = AssistantAgent(
        name="SeniorDev",
        model_client=model_client,
        system_message=f"""You are a senior developer implementing changes in {workspace}.
        You have access to:
        - Test command: {ws_config.get('test_command', 'N/A')}
        - Build command: {ws_config.get('build_command', 'N/A')}
        Working directory: {repo_path}
        Task: Implement the proposed changes with high code quality."""
    )

    reviewer = AssistantAgent(
        name="Reviewer",
        model_client=model_client,
        system_message=f"""You are a code reviewer for {workspace}.
        Review all changes for:
        - Code quality and style
        - Test coverage
        - Performance implications
        Repository: {repo_path}"""
    )

    # Create team chat
    team = RoundRobinGroupChat([architect, senior_dev, reviewer])

    # Change to workspace directory
    os.chdir(repo_path)

    # Run the workflow
    result = await team.run(task=prompt)

    return {
        "workspace": workspace,
        "result": result,
        "repo_path": str(repo_path),
        "branch": branch
    }
Agent Parameterization

This design loads workspace metadata from workspaces.yaml and injects it into agent system messages and working directories. The same agent code works for any workspace—only the configuration changes.

HTTP API with Workspace Parameter

Build a FastAPI server that exposes workspace-aware endpoints for triggering AutoGen runs and querying status.

api_server.py

# /srv/autogen/api_server.py
import asyncio
import uuid
from datetime import datetime
from pathlib import Path
from typing import Optional
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
import yaml

from agents import run_autogen_workflow, load_workspace_config

app = FastAPI(title="AutoGen Workspace API")

# In-memory job store (use Redis/DB for production)
jobs = {}

class RunRequest(BaseModel):
    workspace: str
    prompt: str
    branch: str = "main"

class JobStatus(BaseModel):
    job_id: str
    workspace: str
    status: str  # pending, running, completed, failed
    created_at: str
    completed_at: Optional[str] = None
    error: Optional[str] = None

@app.get("/")
def root():
    return {"service": "AutoGen Workspace API", "version": "1.0"}

@app.get("/workspaces")
def list_workspaces():
    """List all available workspaces."""
    config = load_workspace_config()
    return {
        "workspaces": {
            name: {
                "path": ws["path"],
                "language": ws["language"],
                "description": ws.get("description", "")
            }
            for name, ws in config["workspaces"].items()
        }
    }

@app.post("/run")
async def run_job(request: RunRequest):
    """
    Trigger an AutoGen workflow on a specific workspace.
    Returns job_id for status tracking.
    """
    # Validate workspace exists
    config = load_workspace_config()
    if request.workspace not in config["workspaces"]:
        raise HTTPException(status_code=404, detail=f"Workspace '{request.workspace}' not found")

    # Generate job ID
    job_id = str(uuid.uuid4())[:8]

    # Create log directory
    log_dir = Path(f"/srv/autogen/logs/{request.workspace}")
    log_dir.mkdir(parents=True, exist_ok=True)
    log_file = log_dir / f"{job_id}.log"

    # Initialize job status
    jobs[job_id] = {
        "job_id": job_id,
        "workspace": request.workspace,
        "prompt": request.prompt,
        "branch": request.branch,
        "status": "running",
        "created_at": datetime.utcnow().isoformat(),
        "log_file": str(log_file)
    }

    # Run workflow in background (for async version, use background tasks)
    try:
        result = await run_autogen_workflow(
            workspace=request.workspace,
            prompt=request.prompt,
            branch=request.branch
        )

        # Write log
        with open(log_file, "w") as f:
            f.write(f"Job ID: {job_id}\n")
            f.write(f"Workspace: {request.workspace}\n")
            f.write(f"Prompt: {request.prompt}\n")
            f.write(f"Result: {result}\n")

        jobs[job_id]["status"] = "completed"
        jobs[job_id]["completed_at"] = datetime.utcnow().isoformat()
        jobs[job_id]["result"] = result

    except Exception as e:
        jobs[job_id]["status"] = "failed"
        jobs[job_id]["error"] = str(e)
        jobs[job_id]["completed_at"] = datetime.utcnow().isoformat()

        with open(log_file, "w") as f:
            f.write(f"Job ID: {job_id}\n")
            f.write(f"Error: {str(e)}\n")

    return {"job_id": job_id, "status": "running"}

@app.get("/status/{job_id}")
def get_status(job_id: str):
    """Get status of a specific job."""
    if job_id not in jobs:
        raise HTTPException(status_code=404, detail="Job not found")

    job = jobs[job_id]
    return JobStatus(
        job_id=job["job_id"],
        workspace=job["workspace"],
        status=job["status"],
        created_at=job["created_at"],
        completed_at=job.get("completed_at"),
        error=job.get("error")
    )

@app.get("/logs/{job_id}")
def get_logs(job_id: str):
    """Retrieve logs for a specific job."""
    if job_id not in jobs:
        raise HTTPException(status_code=404, detail="Job not found")

    log_file = Path(jobs[job_id]["log_file"])
    if not log_file.exists():
        raise HTTPException(status_code=404, detail="Log file not found")

    with open(log_file) as f:
        return {"job_id": job_id, "logs": f.read()}

Running the API Server

# Activate venv
cd /srv/autogen
source .venv/bin/activate

# Run FastAPI with Uvicorn
uvicorn api_server:app --host 0.0.0.0 --port 8000 --reload

# For production, use a process manager (see operational notes)

The API is now accessible at http://192.168.1.100:8000 from your laptop.

Using the System from the Laptop

Editing Code

Use VS Code Remote SSH to open any workspace directory and edit as if it were local:

  1. In VS Code, connect to minipc-autogen (Remote SSH)
  2. Open folder: /srv/projects/best-of-ui
  3. Edit files, commit changes, and push to Git as normal

Triggering Agent Runs

From your laptop terminal, use curl to interact with the API:

# List available workspaces
curl http://192.168.1.100:8000/workspaces

# Trigger an AutoGen run on best-of-ui
curl -X POST http://192.168.1.100:8000/run \
  -H "Content-Type: application/json" \
  -d '{
    "workspace": "best-of-ui",
    "prompt": "Refactor the authentication module to use JWT tokens",
    "branch": "feature/jwt-auth"
  }'

# Response: {"job_id": "a3b2c1d4", "status": "running"}

# Check job status
curl http://192.168.1.100:8000/status/a3b2c1d4

# Retrieve logs
curl http://192.168.1.100:8000/logs/a3b2c1d4

CLI Helper Script (Optional)

Create a simple Python CLI on your laptop for easier interaction:

# run_agent.py (on laptop)
import requests
import sys
import time

API_URL = "http://192.168.1.100:8000"

def trigger_run(workspace, prompt, branch="main"):
    response = requests.post(f"{API_URL}/run", json={
        "workspace": workspace,
        "prompt": prompt,
        "branch": branch
    })
    response.raise_for_status()
    return response.json()["job_id"]

def poll_status(job_id):
    while True:
        response = requests.get(f"{API_URL}/status/{job_id}")
        status = response.json()
        print(f"Status: {status['status']}")

        if status["status"] in ["completed", "failed"]:
            return status

        time.sleep(5)

if __name__ == "__main__":
    workspace = input("Workspace: ")
    prompt = input("Task prompt: ")
    branch = input("Branch (default: main): ") or "main"

    print(f"Starting job on {workspace}...")
    job_id = trigger_run(workspace, prompt, branch)
    print(f"Job ID: {job_id}")

    final_status = poll_status(job_id)
    print(f"Final status: {final_status}")

Run with: python run_agent.py

Adding a New Repository

The workspace registry design makes adding new repositories trivial—no code changes required.

Step-by-Step Process

1. Clone the Repository

# SSH into mini-PC or use VS Code terminal
ssh devuser@192.168.1.100
cd /srv/projects

# Clone new repo
git clone https://github.com/yourusername/new-project.git new-project

2. Add Workspace Entry

Edit /srv/autogen/workspaces.yaml and add a new entry:

workspaces:
  best-of-ui:
    path: /srv/projects/best-of-ui
    language: javascript
    test_command: "npm test"
    build_command: "npm run build"
    install_command: "npm install"

  new-project:
    path: /srv/projects/new-project
    language: python
    test_command: "pytest tests/"
    build_command: "python setup.py build"
    install_command: "pip install -e ."
    description: "New Python project for data analysis"

3. Restart API Server (if needed)

If the API server caches the config, restart it:

# Find and kill existing process
pkill -f api_server

# Restart
cd /srv/autogen
source .venv/bin/activate
uvicorn api_server:app --host 0.0.0.0 --port 8000

4. Trigger Run on New Workspace

# From laptop
curl -X POST http://192.168.1.100:8000/run \
  -H "Content-Type: application/json" \
  -d '{
    "workspace": "new-project",
    "prompt": "Analyze the codebase and suggest improvements"
  }'
Zero-Code Workspace Addition

Adding a new repository requires only:

  • git clone into /srv/projects/
  • New entry in workspaces.yaml
  • Optional API server restart

No changes to agents.py or api_server.py are needed.

Operational Notes

Running API Server as a Service

For persistent operation, use systemd to manage the API server:

# Create systemd service file
sudo nano /etc/systemd/system/autogen-api.service
[Unit]
Description=AutoGen Workspace API
After=network.target

[Service]
Type=simple
User=devuser
WorkingDirectory=/srv/autogen
Environment="PATH=/srv/autogen/.venv/bin"
ExecStart=/srv/autogen/.venv/bin/uvicorn api_server:app --host 0.0.0.0 --port 8000
Restart=on-failure
RestartSec=10

[Install]
WantedBy=multi-user.target
# Enable and start service
sudo systemctl daemon-reload
sudo systemctl enable autogen-api
sudo systemctl start autogen-api

# Check status
sudo systemctl status autogen-api

# View logs
sudo journalctl -u autogen-api -f

Managing Concurrency

The Intel N100 is a low-power quad-core CPU. To avoid resource contention:

  • Limit concurrent jobs to 1–2 at a time
  • Implement a job queue (e.g., with Celery + Redis) for production use
  • Monitor CPU/memory usage: htop or glances

Log Management

# View logs for a specific workspace
ls -lh /srv/autogen/logs/best-of-ui/

# Clean old logs (example: delete logs older than 30 days)
find /srv/autogen/logs -name "*.log" -mtime +30 -delete

Updating Dependencies

# Activate venv
cd /srv/autogen
source .venv/bin/activate

# Update AutoGen packages
pip install --upgrade autogen-agentchat autogen-ext

# Update all dependencies
pip list --outdated
pip install --upgrade 

Troubleshooting

VS Code Remote SSH Connection Issues

  • Symptom: "Could not establish connection to host"
  • Fix: Verify SSH works from terminal: ssh devuser@192.168.1.100
  • Check SSH config in ~/.ssh/config
  • Ensure SSH key is added: ssh-add -l

Workspace Not Found Error

  • Symptom: HTTP 404 when calling /run
  • Fix: Verify workspace name matches exactly in workspaces.yaml
  • Check for typos in workspace name
  • Restart API server after config changes

Path Mismatch

  • Symptom: "FileNotFoundError: Workspace path does not exist"
  • Fix: Verify the path in workspaces.yaml matches actual location
  • Check: ls -la /srv/projects/<workspace>

LLM API Failures

  • Symptom: "AuthenticationError" or "RateLimitError"
  • Fix: Verify .env file has correct API keys
  • Check API key permissions and quotas
  • Test: curl https://api.openai.com/v1/models -H "Authorization: Bearer $OPENAI_API_KEY"

Python Virtual Environment Issues

  • Symptom: "ModuleNotFoundError" for autogen packages
  • Fix: Ensure venv is activated: source /srv/autogen/.venv/bin/activate
  • Verify correct Python: which python should show /srv/autogen/.venv/bin/python
  • Reinstall dependencies: pip install -r requirements.txt

API Server Not Responding

  • Symptom: Connection refused on port 8000
  • Fix: Check if server is running: sudo systemctl status autogen-api
  • Verify port is listening: sudo ss -tulpn | grep 8000
  • Check firewall rules: sudo ufw status

Job Hangs or Times Out

  • Symptom: Job status stuck in "running"
  • Fix: Check logs: cat /srv/autogen/logs/<workspace>/<job_id>.log
  • Monitor resource usage: htop
  • Reduce complexity of agent tasks or add timeouts

Summary

This guide establishes a flexible, multi-workspace AutoGen architecture on a low-power Ubuntu mini-PC. The key design decisions are:

  • Code on mini-PC SSD: Fast local I/O for builds and tests, edited remotely via VS Code Remote SSH
  • HTTP API: Trigger AutoGen workflows without SSH, using a simple REST interface
  • Workspace registry: Add new repositories by cloning and updating a config file—no code changes required
  • Parameterized agents: AutoGen agents load workspace metadata at runtime, making them repo-agnostic

Starting with best-of-ui as the primary workspace, this system scales to dozens of repositories with minimal overhead. The architecture separates infrastructure (API, agents) from data (repos), allowing clean addition of workspaces as your projects grow.

For production use, consider adding:

  • Job queue (Celery + Redis) for async processing
  • Database for job metadata (SQLite or PostgreSQL)
  • Authentication on API endpoints
  • Monitoring and alerting (Prometheus, Grafana)
  • Automated backups of /srv/projects and /srv/autogen

This setup provides a stable foundation for AutoGen-driven development on dedicated hardware, with clear paths to scale both compute resources and repository coverage.