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.
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/
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
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"
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
}
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:
- In VS Code, connect to
minipc-autogen(Remote SSH) - Open folder:
/srv/projects/best-of-ui - 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"
}'
Adding a new repository requires only:
git cloneinto/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:
htoporglances
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.yamlmatches actual location - Check:
ls -la /srv/projects/<workspace>
LLM API Failures
- Symptom: "AuthenticationError" or "RateLimitError"
- Fix: Verify
.envfile 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 pythonshould 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/projectsand/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.