Deployment Guide

Deployment Guide

Comprehensive deployment guide for the agentic-sandbox VM orchestration platform. This guide covers installation, configuration, and production deployment from scratch.

Table of Contents

1. Prerequisites 2. Installation 3. Host Configuration 4. Docker Runtime (Optional) 5. Base Image Setup 6. Management Server Setup 7. VM Provisioning 8. Agent Deployment 9. Monitoring Setup 10. Production Checklist 11. Verification

Prerequisites

Hardware Requirements

ComponentMinimumRecommendedNotes
CPU4 cores with KVM support16+ coresCheck: `egrep -c '(vmxsvm)' /proc/cpuinfo`
RAM16GB64GB+8GB per agent VM + 8GB for host
Disk200GB1TB+SSD strongly recommended
Network1Gbps NIC10Gbps NICFor agentshare storage performance

KVM Support Check:

# Check for hardware virtualization support
egrep -c '(vmx|svm)' /proc/cpuinfo
# Should return > 0

# Check if KVM modules are loaded
lsmod | grep kvm
# Should show kvm_intel or kvm_amd

# Check /dev/kvm exists
ls -l /dev/kvm
# Should exist with permissions for kvm or libvirt group

Software Dependencies

Ubuntu 24.04 LTS (Recommended):

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

# Install QEMU/KVM and libvirt
sudo apt install -y \
    qemu-kvm \
    libvirt-daemon-system \
    libvirt-clients \
    libvirt-daemon \
    bridge-utils \
    virt-manager \
    cpu-checker

# Verify KVM installation
sudo kvm-ok
# Should output: "KVM acceleration can be used"

# Install build tools
sudo apt install -y \
    build-essential \
    pkg-config \
    libssl-dev \
    protobuf-compiler

# Install Rust (latest stable)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source "$HOME/.cargo/env"
rustc --version  # Verify Rust 1.75+

# Install Python 3.11+ for tests
sudo apt install -y python3 python3-pip python3-venv

# Install jq for JSON processing
sudo apt install -y jq

Docker Engine (Optional Runtime):

# Install Docker Engine and Compose plugin
sudo apt install -y docker.io docker-compose-plugin

# Enable and start Docker
sudo systemctl enable --now docker

# Allow current user to run docker
sudo usermod -aG docker $USER
newgrp docker

Add User to libvirt and kvm Groups:

sudo usermod -aG libvirt,kvm $USER
newgrp libvirt
# Log out and log back in to apply group changes

Network Requirements

PortProtocolDirectionPurpose
8120TCPInboundgRPC agent connections
8121TCPInboundWebSocket streaming
8122TCPInboundHTTP dashboard and REST API
9090TCPLocalhostPrometheus metrics
9093TCPLocalhostAlertmanager
3000TCPLocalhostGrafana dashboard
9100TCPVM networkNode exporter (agent VMs)

Firewall Configuration (if using UFW):

# Allow management server ports
sudo ufw allow 8120/tcp comment 'Agentic Sandbox gRPC'
sudo ufw allow 8121/tcp comment 'Agentic Sandbox WebSocket'
sudo ufw allow 8122/tcp comment 'Agentic Sandbox HTTP'

# Prometheus/Grafana (optional - restrict to localhost)
sudo ufw allow from 127.0.0.1 to any port 9090 proto tcp
sudo ufw allow from 127.0.0.1 to any port 3000 proto tcp

sudo ufw reload

Installation

1. Clone Repository

cd ~/dev  # or your preferred development directory
git clone https://github.com/jmagly/agentic-sandbox.git
cd agentic-sandbox

2. Build Management Server

cd management
cargo build --release

# Binary will be at: target/release/agentic-mgmt
# Verify build
./target/release/agentic-mgmt --version

Build time: Approximately 45 seconds on modern hardware.

3. Build Agent Client

cd ../agent-rs
cargo build --release

# Binary will be at: target/release/agent-client
# Verify build
./target/release/agent-client --version

Build time: Approximately 30 seconds.

Rolling out agent fixes (version propagation)

The agent binary runs inside each VM — either baked into the base image at provision time or deployed to a running VM. A change to agent behaviour (for example the control-channel keepalive of #633 or the state-preserving reconnect of #634/#637) only takes effect on VMs that are actually running the rebuilt binary. It does not reach existing VMs automatically:

  • Running VM: `./scripts/deploy-agent.sh <vm-name>` rebuilds (if needed) and

redeploys the binary, then restarts the agent service. Running tmux/work sessions are preserved by the new agent, but the old agent is what handled the reconnect that triggered the redeploy.

  • New VMs: reprovision (`./scripts/reprovision-vm.sh <vm>`) or bake a fresh

base image so newly provisioned VMs ship the fixed binary.

Caveat: a VM provisioned from a **pre-v2026.7.7 image still runs the old

kill-on-reconnect agent** until its binary is redeployed or the VM is

reprovisioned. Likewise, transport changes from the same range (vsock,

static-IP) apply only to newly provisioned VMs — they are image/cloud-init

properties, not hot-deployable like the binary.

4. Build CLI (Optional)

cd ../cli
cargo build --release

# Binary will be at: target/release/agentic-sandbox
./target/release/agentic-sandbox --help

5. Verify Installation

cd ..
tree -L 2 -I target

# Expected structure:
# .
# ├── management/         (Management server)
# ├── agent-rs/          (Agent client)
# ├── cli/               (CLI tool)
# ├── proto/             (gRPC definitions)
# ├── images/qemu/       (VM provisioning)
# ├── scripts/           (Utilities)
# ├── docs/              (Documentation)
# └── tests/             (E2E tests)

Host Configuration

1. Directory Structure Setup

Create the required directories for VM storage and shared filesystems:

# VM storage directory
sudo mkdir -p /var/lib/agentic-sandbox/vms
sudo mkdir -p /var/lib/agentic-sandbox/secrets

# Agentshare storage (virtiofs)
sudo mkdir -p /srv/agentshare/global
sudo mkdir -p /srv/agentshare/inbox
sudo mkdir -p /srv/agentshare/tasks

# Base images storage
sudo mkdir -p /mnt/ops/base-images

# Verify directory structure
tree -L 2 /var/lib/agentic-sandbox /srv/agentshare

2. Set Permissions

# VM storage (accessible by libvirt-qemu user)
sudo chown -R libvirt-qemu:kvm /var/lib/agentic-sandbox/vms
sudo chmod 755 /var/lib/agentic-sandbox/vms

# Secrets directory (readable by management server)
sudo chown $USER:$USER /var/lib/agentic-sandbox/secrets
sudo chmod 755 /var/lib/agentic-sandbox/secrets

# Agentshare storage
sudo chown -R $USER:$USER /srv/agentshare
sudo chmod 755 /srv/agentshare

# Global directory (read-only for VMs)
sudo chmod 755 /srv/agentshare/global

# Base images (readable by libvirt)
sudo chown $USER:libvirt-qemu /mnt/ops/base-images
sudo chmod 755 /mnt/ops/base-images

Docker Runtime (Optional)

Use Docker containers as a parallel runtime to VMs for faster iteration. The runtime choice should be a minor user-facing detail.

# Launch a hardened docker sandbox
./scripts/sandbox-launch.sh --runtime docker --image agent-claude --name agent-docker-01

# Or run via compose
docker compose -f runtimes/docker/docker-compose.yml up -d

3. Configure libvirt Network

Verify the default libvirt network is configured:

# Check if 'default' network exists
virsh net-list --all

# Expected output:
# Name      State    Autostart   Persistent
# --------------------------------------------
# default   active   yes         yes

# If not active, start it
virsh net-start default
virsh net-autostart default

# Verify network configuration
virsh net-dumpxml default

# Expected: 192.168.122.0/24 network with DHCP

Custom Network Configuration (Optional):

If you need to customize the network range:

# Define custom network
cat > /tmp/agentic-network.xml <<EOF
<network>
  <name>agentic</name>
  <forward mode='nat'/>
  <bridge name='virbr1' stp='on' delay='0'/>
  <ip address='192.168.122.1' netmask='255.255.255.0'>
    <dhcp>
      <range start='192.168.122.201' end='192.168.122.254'/>
    </dhcp>
  </ip>
</network>
EOF

virsh net-define /tmp/agentic-network.xml
virsh net-start agentic
virsh net-autostart agentic

4. Configure Storage Pools

# Define VM storage pool
virsh pool-define-as agentic-vms \
  dir \
  --target /var/lib/agentic-sandbox/vms

virsh pool-start agentic-vms
virsh pool-autostart agentic-vms

# Verify storage pool
virsh pool-list --all

5. Configure Host Networking (for virtiofs)

Ensure the host can be reached from VMs at the standard gateway address:

# Check libvirt bridge IP
ip addr show virbr0

# Should show 192.168.122.1
# If using custom network, adjust accordingly

Base Image Setup

1. Download Ubuntu 24.04 Cloud Image

cd /mnt/ops/base-images

# Download Ubuntu 24.04 LTS cloud image
wget https://cloud-images.ubuntu.com/releases/24.04/release/ubuntu-24.04-server-cloudimg-amd64.img

# Rename to match provisioning script expectations
mv ubuntu-24.04-server-cloudimg-amd64.img ubuntu-server-24.04-agent.qcow2

# Verify image
qemu-img info ubuntu-server-24.04-agent.qcow2

Expected output:

image: ubuntu-server-24.04-agent.qcow2
file format: qcow2
virtual size: 2.2 GiB
disk size: 600 MiB

2. Pre-configure Base Image (Optional)

For faster provisioning, you can pre-install common packages:

# Install virt-customize (part of libguestfs-tools)
sudo apt install -y libguestfs-tools

# Pre-install common packages
sudo virt-customize -a ubuntu-server-24.04-agent.qcow2 \
  --install qemu-guest-agent,cloud-init \
  --run-command 'systemctl enable qemu-guest-agent' \
  --run-command 'cloud-init clean'

# Create a snapshot for safety
cp ubuntu-server-24.04-agent.qcow2 ubuntu-server-24.04-agent-pristine.qcow2

3. Verify Cloud-init

Ensure the base image supports cloud-init:

sudo virt-customize -a ubuntu-server-24.04-agent.qcow2 \
  --run-command 'cloud-init --version' \
  --dry-run
# Should output cloud-init version

4. Set Base Image Permissions

sudo chmod 644 /mnt/ops/base-images/ubuntu-server-24.04-agent.qcow2
sudo chown libvirt-qemu:kvm /mnt/ops/base-images/ubuntu-server-24.04-agent.qcow2

Management Server Setup

Configuration Options

The management server can be configured via environment variables.

Development Configuration (`.run/dev.env`):

cd ~/dev/agentic-sandbox/management
mkdir -p .run

cat > .run/dev.env <<EOF
# Management Server Development Configuration

# Listen address. Default/recommended is loopback; gRPC uses this port,
# WebSocket uses +1, HTTP uses +2.
LISTEN_ADDR=127.0.0.1:8120

# Secrets directory (bootstrap tokens and local mTLS CA material)
SECRETS_DIR=/var/lib/agentic-sandbox/secrets
# vsock (same-host VM transport, ADR-023) is enabled by default when
# /dev/vhost-vsock exists: listener on 8120, CID map file defaulting to
# $VM_STORAGE_DIR/.vsock-cid-registry (#633). Opt out or override:
# AGENTIC_GRPC_VSOCK_PORT=0
# AGENTIC_GRPC_VSOCK_CID_MAP_FILE=/var/lib/agentic-sandbox/vms/.vsock-cid-registry

# Heartbeat timeout (seconds before marking agent disconnected)
HEARTBEAT_TIMEOUT=90

# Logging configuration
RUST_LOG=info
LOG_FORMAT=pretty  # pretty, json, compact

# Metrics
METRICS_ENABLED=true
EOF

Production Configuration:

For production, use a systemd service with environment file:

# Create production environment file
sudo mkdir -p /etc/agentic-sandbox

sudo tee /etc/agentic-sandbox/management.env <<EOF
LISTEN_ADDR=127.0.0.1:8120
SECRETS_DIR=/var/lib/agentic-sandbox/secrets
# vsock (same-host VM transport, ADR-023) is enabled by default when
# /dev/vhost-vsock exists: listener on 8120, CID map file defaulting to
# $VM_STORAGE_DIR/.vsock-cid-registry (#633). Opt out or override:
# AGENTIC_GRPC_VSOCK_PORT=0
# AGENTIC_GRPC_VSOCK_CID_MAP_FILE=/var/lib/agentic-sandbox/vms/.vsock-cid-registry
HEARTBEAT_TIMEOUT=90
RUST_LOG=info
LOG_FORMAT=json
METRICS_ENABLED=true

# Optional: connect to an aiwg serve instance
# AIWG_SERVE_ENDPOINT=http://aiwg-serve-host:7337
# AIWG_SERVE_NAME=prod-sandbox
EOF

sudo chmod 600 /etc/agentic-sandbox/management.env

Management Transport Security

The management listeners are local-only by default. `LISTEN_ADDR` controls the plaintext gRPC listener; the legacy WebSocket and HTTP ports derive from the same bind IP at `+1` and `+2`. Binding plaintext management TCP to a non-loopback address is rejected at startup unless the operator explicitly sets `AGENTIC_ALLOW_PLAINTEXT_TCP=1`.

For remote access, prefer one of these patterns:

  • Keep `LISTEN_ADDR=127.0.0.1:8120` and expose the dashboard through a local

SSH tunnel, reverse proxy, or UDS-authenticated admin path.

  • Configure the HTTP/admin TLS listener with:
AIWG_TLS_CERT=/etc/agentic-sandbox/tls/server.crt
AIWG_TLS_KEY=/etc/agentic-sandbox/tls/server.key
AIWG_TLS_CLIENT_AUTH=required
AIWG_TLS_CLIENT_CA=/etc/agentic-sandbox/tls/operator-ca.crt
AIWG_TLS_LISTEN=127.0.0.1:8122
AIWG_MTLS_ADMIN_ALLOWLIST=admin.operator.example
  • Configure gRPC UDS/vsock/mTLS side channels for non-loopback agent

connectivity instead of exposing bearer-token plaintext on `virbr0`.

`AGENTIC_ALLOW_PLAINTEXT_TCP=1` is a break-glass compatibility override. It keeps the old non-loopback plaintext behavior and should not be used where untrusted guests can sniff host networking.

Gateway-Mediated SSH (ADR-029)

Managed-profile SSH access goes through the gateway, not direct `ssh agent@<ip>`. The gateway issues short-lived, principal-scoped OpenSSH certificate leases (`POST /api/v2/gateway/ssh/leases`, operator-authenticated) and proxies the SSH byte stream through a point-to-point connector. The lease API never persists private keys or certificate bodies; see the "Gateway SSH Certificate Leases" section of `docs/API.md` for the request/response contract.

Both halves are opt-in via environment variables on the management process:

# --- Certificate authority (lease signing + guest trust) ---
# OpenSSH CA private key used to sign submitted public keys at lease issuance.
# When unset, leases are metadata-only (no certificate is returned).
AGENTIC_GATEWAY_SSH_CA_KEY=/etc/agentic-sandbox/ssh-ca/ca_ed25519
# CA public key pushed into guests at provision time (TrustedUserCAKeys). If
# unset, the matching <CA_KEY>.pub is used.
AGENTIC_GATEWAY_SSH_CA_PUBLIC_KEY_HOST_PATH=/etc/agentic-sandbox/ssh-ca/ca_ed25519.pub
# Guest user whose AuthorizedPrincipalsFile is provisioned (default: agent).
AGENTIC_GATEWAY_SSH_AUTHORIZED_USER=agent
# Accepted certificate principals (comma/space separated; default: the user).
AGENTIC_GATEWAY_SSH_AUTHORIZED_PRINCIPALS=agent

# --- Point-to-point connector (byte-stream proxy) ---
# Enable the connector listener (loopback recommended). Unset = disabled.
AGENTIC_GATEWAY_SSH_LISTEN=127.0.0.1:8124
# Instance -> runtime SSH endpoint map.
AGENTIC_GATEWAY_SSH_TARGETS=agent-01=127.0.0.1:2222,agent-02=127.0.0.1:2223
# actor=instance routing allowlist (either side may be * for break-glass).
[email protected]=agent-01

Operators normally use `sandboxctl ssh <instance>` (and `sandboxctl ssh-config`), which request a lease and wrap the connector in an OpenSSH `ProxyCommand`; client defaults are `AGENTIC_GATEWAY_SSH_CONNECT` (connector address) and `AGENTIC_GATEWAY_SSH_ACTOR` (prelude actor). The private CA key is never written into cloud-init user-data. Direct runtime SSH remains a dev/break-glass bypass (`AGENTIC_ENABLE_DIRECT_RUNTIME_SSH=1`) and is omitted from the managed `agentic-dev` profile by default.

AIWG Integration (Optional)

Agentic Sandbox can register with an aiwg serve instance to join the AIWG operator dashboard. This is entirely optional — all sandbox features work without it.

When to configure AIWG integration:

  • You are running AIWG and want fleet visibility of your sandboxes
  • You want HITL requests to appear in the aiwg serve dashboard
  • You are using AIWG Mission Control to delegate tasks to agentic-sandbox VMs

Configuration:

# Add to .run/dev.env (development) or /etc/agentic-sandbox/management.env (production)
AIWG_SERVE_ENDPOINT=http://localhost:7337   # URL of your aiwg serve instance
AIWG_SERVE_NAME=my-sandbox                 # Display name in the dashboard

Start `aiwg serve` first (from a project with AIWG installed):

aiwg serve          # starts on http://localhost:7337 by default

The management server will register automatically on startup and reconnect if the connection drops. Server startup is never blocked — if aiwg serve is unreachable the sandbox operates normally and retries registration every 5 seconds in the background.

Verify integration:

# aiwg serve should show the sandbox in its registry
curl http://localhost:7337/api/sandboxes

# Management server logs will show:
# INFO Registered with aiwg serve at http://localhost:7337
# INFO aiwg serve WS connected: ws://localhost:7337/ws/sandbox/...

Running in Development Mode

cd ~/dev/agentic-sandbox/management

# Start server (builds if needed)
./dev.sh

# Server will start on:
# - gRPC:      localhost:8120
# - WebSocket: localhost:8121
# - HTTP:      localhost:8122

# View logs
./dev.sh logs

# Restart server
./dev.sh restart

# Stop server
./dev.sh stop

Verification:

# Check if server is running
curl http://localhost:8122/api/v1/health

# Expected output:
# {"status":"healthy","uptime_seconds":42}

# View dashboard
xdg-open http://localhost:8122  # or open in browser

Running in Production (systemd)

1. Create systemd service file:

sudo tee /etc/systemd/system/agentic-mgmt.service <<EOF
[Unit]
Description=Agentic Sandbox Management Server
After=network-online.target
Wants=network-online.target
Documentation=https://github.com/jmagly/agentic-sandbox

[Service]
Type=notify
NotifyAccess=main
User=$USER
Group=$USER
WorkingDirectory=$HOME/dev/agentic-sandbox/management
ExecStart=$HOME/dev/agentic-sandbox/management/target/release/agentic-mgmt
Restart=always
RestartSec=5
WatchdogSec=30
WatchdogSignal=SIGABRT
KillMode=mixed
LimitNOFILE=1048576
EnvironmentFile=/etc/agentic-sandbox/management.env

# Security hardening
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=read-only
ReadWritePaths=/var/lib/agentic-sandbox/secrets

# Logging
StandardOutput=journal
StandardError=journal
SyslogIdentifier=agentic-mgmt

[Install]
WantedBy=multi-user.target
EOF

2. Enable and start service:

sudo systemctl daemon-reload
sudo systemctl enable agentic-mgmt.service
sudo systemctl start agentic-mgmt.service

# Check status
sudo systemctl status agentic-mgmt.service

# View logs
sudo journalctl -u agentic-mgmt.service -f

The management binary supports systemd readiness and watchdog notifications. `READY=1` is sent after the process binds the gRPC listener and launches the HTTP/WebSocket startup tasks. When `WatchdogSec=30` is set, the process pings systemd at half of `WATCHDOG_USEC`; if the Tokio runtime stops scheduling that task, systemd marks the service failed and applies the restart policy. Confirm the installed supervision and descriptor ceiling with:

systemctl show agentic-mgmt -p Type -p WatchdogUSec -p KillMode -p LimitNOFILE
pid=$(systemctl show agentic-mgmt -p MainPID --value)
grep 'Max open files' /proc/$pid/limits

3. Verify service is accessible:

# Health check
curl http://localhost:8122/api/v1/health

# List agents (should be empty initially)
curl http://localhost:8122/api/v1/agents | jq .

Service Ports

PortProtocolEndpointPurpose
8120gRPC`localhost:8120`Agent client connections (bidirectional streaming)
8121WebSocket`ws://localhost:8121`Real-time UI updates (metrics, terminal streams)
8122HTTP`http://localhost:8122`Dashboard, REST API, metrics endpoint

When `AIWG_TLS_CERT` and `AIWG_TLS_KEY` are set, the HTTP/admin listener serves HTTPS on `AIWG_TLS_LISTEN` or the normal HTTP address. The plaintext HTTP listener is not bound in that mode.

VM Provisioning

Basic VM Provisioning

Provision a minimal VM with default settings:

cd ~/dev/agentic-sandbox

# Provision with defaults (4 CPUs, 8GB RAM, 40GB disk)
./images/qemu/provision-vm.sh agent-01 --start

# Provision and wait for bootstrap/readiness checks
./images/qemu/provision-vm.sh agent-02 --start --wait

Output:

[INFO] Generating secure transport material for agent-01
[INFO] Generating SSH key pair for agent-01
[INFO] Allocating IP: 192.168.122.201
[INFO] Creating overlay disk from ubuntu-24.04
[INFO] Generating cloud-init configuration
[INFO] Defining VM in libvirt
[INFO] Starting VM agent-01
[OK] VM agent-01 provisioned successfully
     IP: 192.168.122.201
     Direct SSH (dev/break-glass): ssh [email protected]
     Agent transport: mTLS bootstrap enrollment

Direct runtime SSH bypasses gateway policy and audit guarantees. Treat direct `ssh agent@...` commands in this document as dev/break-glass diagnostics. The managed-profile direction is gateway-mediated SSH per `ADR-029` and the `SSH gateway rollout plan`.

Provisioning with agentic-dev Profile

The `agentic-dev` profile includes a full development environment:

./images/qemu/provision-vm.sh agent-01 \
  --profile agentic-dev \
  --agentshare \
  --start \
  --wait-ready

Included in agentic-dev:

  • Languages: Python (uv), Node.js (fnm), Go, Rust
  • AI Tools: Claude Code, Aider, GitHub Copilot CLI
  • CLI Tools: ripgrep, fd, bat, eza, delta, jq, xh, grpcurl
  • Build Tools: cmake, ninja, meson, GCC
  • Containers: Docker (rootless) with compose and buildx
  • Databases: PostgreSQL, MySQL, Redis, SQLite clients

Profile Selection

ProfileUse CaseProvisioning TimeDisk Usage
`basic`Minimal utilities plus dev/break-glass direct SSH1-2 minutes~2GB
`agentic-dev`Full development environment5-10 minutes~8GB

Resource Allocation

Adjust resources based on workload:

# High-performance single VM
./images/qemu/provision-vm.sh agent-01 \
  --cpus 8 \
  --memory 16G \
  --disk 100G \
  --profile agentic-dev \
  --agentshare \
  --start

# Multiple concurrent VMs (resource-efficient)
./images/qemu/provision-vm.sh agent-01 --cpus 2 --memory 4G --disk 20G
./images/qemu/provision-vm.sh agent-02 --cpus 2 --memory 4G --disk 20G
./images/qemu/provision-vm.sh agent-03 --cpus 2 --memory 4G --disk 20G

Resource Guidelines:

ScenarioCPUsMemoryDiskConcurrent VMs
Single high-perf816G100G1
Default (2-4 VMs)48G40G2-4
High-density24G20G8+

Agentshare Storage

Enable shared storage with `--agentshare`:

./images/qemu/provision-vm.sh agent-01 \
  --profile agentic-dev \
  --agentshare \
  --start

Mounts inside VM:

/mnt/global  → ~/global   (read-only shared resources)
/mnt/inbox   → ~/inbox    (read-write per-agent workspace)

Verify agentshare inside VM using a dev/break-glass direct SSH session:

ssh [email protected]

# Check mounts
ls -la ~/global ~/inbox

# Test write access
echo "test" > ~/inbox/test.txt
cat ~/inbox/test.txt

Network Modes

Control VM network access:

# Full network access (default)
./images/qemu/provision-vm.sh agent-01 --network-mode full

# Isolated (management server only)
./images/qemu/provision-vm.sh agent-01 --network-mode isolated

# Allowlist (DNS-filtered HTTPS only)
./images/qemu/provision-vm.sh agent-01 --network-mode allowlist
ModeManagement ServerInternetDNSUse Case
`full`YesYesYesDevelopment, unrestricted tasks
`isolated`YesNoLimitedHigh-security, offline tasks
`allowlist`YesHTTPS onlyFilteredProduction, controlled egress

Static IP Allocation

VMs are automatically assigned static IPs based on their name:

VM NameIP Address
agent-01192.168.122.201
agent-02192.168.122.202
agent-03192.168.122.203
......
agent-54192.168.122.254

Manual IP assignment:

./images/qemu/provision-vm.sh agent-custom \
  --ip 192.168.122.220 \
  --start

Verify VM Provisioning

# List all VMs
virsh list --all

# Check VM status
virsh domstate agent-01

# Get VM IP
virsh domifaddr agent-01

# Dev/break-glass direct SSH to VM
ssh [email protected]

# Inside VM, check agent client
sudo systemctl status agentic-agent

# Check agent logs
sudo journalctl -u agentic-agent -f

Agent Deployment

Agent deployment happens automatically during VM provisioning. Packaged `agentic-mgmt` launches provide the exact sibling `agent-client` binary to the provisioner through `AGENT_CLIENT_SOURCE_BIN`; development checkouts fall back to `agent-rs/target/release/agent-client`. An explicit `AGENT_CLIENT_SOURCE_BIN=/absolute/path/to/agent-client` overrides either source and is validated before deployment.

To update agents after code changes:

Deploy Agent to Single VM

cd ~/dev/agentic-sandbox

# Deploy with normal logging
./scripts/deploy-agent.sh agent-01

# Deploy with debug logging
./scripts/deploy-agent.sh agent-01 --debug

# Force rebuild agent binary
./scripts/deploy-agent.sh agent-01 --rebuild

Output:

[deploy] Deploying to agent-01 (192.168.122.201)
[deploy] Building agent binary...
[deploy] Waiting for SSH...
[deploy] Checking secure transport configuration...
[deploy] Secure transport env found; legacy AGENT_SECRET is absent
[deploy] Copying agent binary...
[deploy] Configuring agent service (log_level=info)...
[deploy] Verifying deployment...
[deploy] SUCCESS: Agent deployed and running on agent-01

Feb 07 12:34:56 agent-01 agentic-agent[1234]: INFO Connected to management server
Feb 07 12:34:57 agent-01 agentic-agent[1234]: INFO Heartbeat sent

Deploy to All Running VMs

# Full rebuild and deploy to all VMs
./scripts/dev-deploy-all.sh

# With debug logging
./scripts/dev-deploy-all.sh --debug

This script: 1. Rebuilds management server 2. Restarts management server 3. Rebuilds agent client 4. Deploys agent to all running VMs 5. Verifies all agents reconnect

Verify Agent Connectivity

# Check agent status via management server API
curl http://localhost:8122/api/v1/agents | jq .

# Expected output:
# [
#   {
#     "agent_id": "agent-01",
#     "status": "ready",
#     "connected_at": "2026-02-07T12:34:56Z",
#     "last_heartbeat": "2026-02-07T12:35:26Z",
#     "capabilities": ["exec", "file_transfer", "pty"]
#   }
# ]

# Dev/break-glass: check agent inside VM
ssh [email protected] 'sudo systemctl status agentic-agent'

Agent Configuration

Agent configuration is stored in `/etc/agentic-sandbox/agent.env` on each VM:

# Inside VM
sudo cat /etc/agentic-sandbox/agent.env

Example:

AGENT_ID=agent-01
AGENT_TRANSPORT=auto
AGENT_GRPC_TLS_CA=/etc/agentic-sandbox/grpc-mtls/ca.pem
AGENT_GRPC_TLS_CERT=/etc/agentic-sandbox/grpc-mtls/agent.pem
AGENT_GRPC_TLS_KEY=/etc/agentic-sandbox/grpc-mtls/agent-key.pem
MANAGEMENT_SERVER=192.168.122.1:8120
HEARTBEAT_INTERVAL=30
RUST_LOG=info

Security:

  • `agent.env` and mTLS private key files are root-owned with mode 600.
  • The agent authenticates through UDS, vsock, or mTLS transport identity.
  • Bootstrap enrollment tokens are one-time use and are not the long-lived agent credential.

Troubleshooting Agent Connection

Agent not connecting:

# Check management server is running
curl http://localhost:8122/api/v1/health

# Dev/break-glass: check agent service status
ssh [email protected] 'sudo systemctl status agentic-agent'

# Dev/break-glass: check agent logs
ssh [email protected] 'sudo journalctl -u agentic-agent -n 50'

# Dev/break-glass: verify secure transport configuration on the VM
ssh [email protected] 'sudo grep "AGENT_TRANSPORT\|AGENT_GRPC_TLS_" /etc/agentic-sandbox/agent.env'

Agent disconnecting:

# Check network connectivity
ssh [email protected] 'ping -c 3 192.168.122.1'

# Check firewall rules
sudo ufw status

# Increase heartbeat timeout in management server
# Edit /etc/agentic-sandbox/management.env
HEARTBEAT_TIMEOUT=120  # Increase from 90 to 120 seconds
sudo systemctl restart agentic-mgmt

Monitoring Setup

Prometheus Installation

# Install Prometheus and Alertmanager
sudo apt update
sudo apt install -y prometheus prometheus-alertmanager

# Enable services
sudo systemctl enable prometheus alertmanager
sudo systemctl start prometheus alertmanager

# Verify installation
prometheus --version
amtool --version

Deploy Observability Stack

cd ~/dev/agentic-sandbox/scripts/prometheus

# Deploy full stack (Prometheus + Alertmanager + Grafana)
sudo ./deploy.sh

This script will: 1. Install Prometheus, Alertmanager, and Grafana 2. Deploy configuration files 3. Configure alert rules 4. Start all services 5. Prompt for Slack and PagerDuty configuration

Configure Alertmanager

Edit Alertmanager configuration:

sudo nano /etc/alertmanager/alertmanager.yml

Add Slack webhook URL:

receivers:
  - name: 'slack-alerts'
    slack_configs:
      - api_url: 'https://hooks.slack.com/services/YOUR/WEBHOOK/URL'
        channel: '#alerts'
        title: 'Agentic Sandbox Alert'
        text: '{{ range .Alerts }}{{ .Annotations.description }}{{ end }}'

Add PagerDuty service key:

  - name: 'pagerduty-critical'
    pagerduty_configs:
      - service_key: 'YOUR_PAGERDUTY_SERVICE_KEY'
        description: '{{ .CommonAnnotations.summary }}'

Restart Alertmanager:

sudo systemctl restart alertmanager

Configure Grafana

1. Access Grafana:

Open http://localhost:3000 in browser.

  • Default credentials: admin/admin
  • Change password on first login

2. Add Prometheus data source:

1. Go to ConfigurationData SourcesAdd data source 2. Select Prometheus 3. Set URL: `http://localhost:9090` 4. Click Save & Test

3. Import dashboards:

The prometheus directory includes pre-built dashboard JSON files. Import them via:

1. DashboardsImport 2. Upload JSON file or paste JSON 3. Select Prometheus data source 4. Click Import

Enable node_exporter on Agent VMs

Node exporter is automatically installed with the `agentic-dev` profile. For existing VMs:

# SSH to agent VM
ssh [email protected]

# Install node_exporter
sudo apt install -y prometheus-node-exporter

# Create textfile collector directory
sudo mkdir -p /var/lib/prometheus/node-exporter
sudo chown agent:agent /var/lib/prometheus/node-exporter

# Restart node_exporter
sudo systemctl restart prometheus-node-exporter

# Verify metrics endpoint
curl http://localhost:9100/metrics | head -20

Verify Monitoring

Check Prometheus targets:

# Via API
curl http://localhost:9090/api/v1/targets | jq '.data.activeTargets[] | {job: .labels.job, health: .health}'

# Via web UI
xdg-open http://localhost:9090/targets

Expected targets:

JobTargetStatus
management-serverlocalhost:8122UP
agent-vms192.168.122.201:9100UP
agent-vms192.168.122.202:9100UP

Check metrics are being collected:

# Query agent count
curl -G http://localhost:9090/api/v1/query \
  --data-urlencode 'query=agentic_agents_connected' | jq .

# Query command execution rate
curl -G http://localhost:9090/api/v1/query \
  --data-urlencode 'query=rate(agentic_commands_total[5m])' | jq .

Access Monitoring Dashboards

ServiceURLPurpose
Prometheushttp://localhost:9090Metrics query and exploration
Alertmanagerhttp://localhost:9093Alert management and silencing
Grafanahttp://localhost:3000Visualization dashboards
Management Dashboardhttp://localhost:8122Live agent status and terminal

Production Checklist

Security Hardening

  • [ ] Firewall configured - Only expose necessary ports
  • [ ] SSH key authentication - Disable password authentication
  • [ ] TLS certificates - Use HTTPS for web dashboard (behind reverse proxy)
  • [ ] Transport credential rotation - Rotate mTLS/CA/bootstrap material per policy; legacy agent shared secrets are retired
  • [ ] Audit logging - Enable audit logs for all agent actions
  • [ ] Resource quotas - Set CPU, memory, and disk quotas per VM
  • [ ] Network isolation - Use `isolated` or `allowlist` network modes
  • [ ] Docker rootless - Ensure containers run without root privileges

Enable audit logging:

# Edit management.env
sudo nano /etc/agentic-sandbox/management.env

# Add:
AUDIT_LOG_ENABLED=true
AUDIT_LOG_PATH=/var/log/agentic-sandbox/audit.log

# Create log directory
sudo mkdir -p /var/log/agentic-sandbox
sudo chown $USER:$USER /var/log/agentic-sandbox

# Restart management server
sudo systemctl restart agentic-mgmt

Backup Configuration

Critical files to backup:

# Backup script
sudo tee /usr/local/bin/backup-agentic-sandbox.sh <<'EOF'
#!/bin/bash
BACKUP_DIR="/var/backups/agentic-sandbox/$(date +%Y%m%d-%H%M%S)"
mkdir -p "$BACKUP_DIR"

# Secrets (encrypted)
tar -czf "$BACKUP_DIR/secrets.tar.gz.gpg" \
  --transform 's|^|secrets/|' \
  -C /var/lib/agentic-sandbox/secrets .
gpg --symmetric "$BACKUP_DIR/secrets.tar.gz"

# VM definitions
for vm in $(virsh list --all --name | grep agent-); do
  virsh dumpxml "$vm" > "$BACKUP_DIR/${vm}.xml"
done

# Management server config
cp /etc/agentic-sandbox/management.env "$BACKUP_DIR/"

# Prometheus data (optional - large)
# rsync -a /var/lib/prometheus/data/ "$BACKUP_DIR/prometheus/"

echo "Backup completed: $BACKUP_DIR"
EOF

sudo chmod +x /usr/local/bin/backup-agentic-sandbox.sh

# Schedule daily backups
sudo tee /etc/cron.daily/agentic-backup <<EOF
#!/bin/bash
/usr/local/bin/backup-agentic-sandbox.sh >> /var/log/agentic-backup.log 2>&1
EOF
sudo chmod +x /etc/cron.daily/agentic-backup

Log Rotation

# Configure logrotate
sudo tee /etc/logrotate.d/agentic-sandbox <<EOF
/var/log/agentic-sandbox/*.log {
    daily
    rotate 30
    compress
    delaycompress
    notifempty
    create 0644 $USER $USER
    sharedscripts
    postrotate
        systemctl reload agentic-mgmt > /dev/null 2>&1 || true
    endscript
}
EOF

Resource Limits

Set system-wide resource limits:

# Edit provisioning defaults
nano ~/dev/agentic-sandbox/images/qemu/provision-vm.sh

# Adjust DEFAULT_* values:
DEFAULT_CPUS="2"        # Reduce for high-density
DEFAULT_MEMORY="4G"     # Reduce for high-density
DEFAULT_DISK="20G"      # Reduce for ephemeral tasks

# Set per-VM limits during provisioning
./images/qemu/provision-vm.sh agent-01 \
  --mem-limit 3800M \
  --cpu-quota 180 \
  --io-read-limit 300M \
  --io-write-limit 100M \
  --disk-quota 10G

Monitoring Alerts

Review and customize alert thresholds:

sudo nano /etc/prometheus/rules/agentic-sandbox.yml

# Adjust alert thresholds based on your SLOs
# Example: Reduce CPU threshold for production
- alert: AgentHighCPU
  expr: agent_cpu_usage > 70  # Changed from 80
  for: 5m  # Changed from 10m

Capacity Planning

Monitor resource usage to plan capacity:

# Check current resource usage
virsh domstats --state-running

# Prometheus query for average CPU usage
curl -G http://localhost:9090/api/v1/query \
  --data-urlencode 'query=avg(agent_cpu_usage)'

# Prometheus query for memory usage
curl -G http://localhost:9090/api/v1/query \
  --data-urlencode 'query=avg(agent_memory_usage_percent)'

# Estimate max concurrent VMs
# Available RAM / Average VM RAM = Max VMs
# Example: 64GB host RAM / 8GB per VM = 8 concurrent VMs

Verification

1. Health Checks

# Management server health
curl http://localhost:8122/api/v1/health
# Expected: {"status":"healthy","uptime_seconds":123}

# Management server readiness
curl http://localhost:8122/readyz
# Expected: {"status":"ready","agents_connected":3}

# Prometheus targets
curl http://localhost:9090/api/v1/targets | jq '.data.activeTargets[] | {job, health}'
# Expected: All targets with health="up"

2. Agent Connectivity

# List connected agents
curl http://localhost:8122/api/v1/agents | jq .

# Expected output (for 3 VMs):
# [
#   {"agent_id": "agent-01", "status": "ready", ...},
#   {"agent_id": "agent-02", "status": "ready", ...},
#   {"agent_id": "agent-03", "status": "ready", ...}
# ]

# Check agent heartbeats
for i in {1..3}; do
  echo "Checking agent-0$i..."
  ssh [email protected]$i 'sudo journalctl -u agentic-agent -n 5 --no-pager | grep -i heartbeat'
done

3. Test Command Execution

# Execute command on agent
curl -X POST http://localhost:8122/api/v1/agents/agent-01/exec \
  -H "Content-Type: application/json" \
  -d '{
    "command": "echo Hello from agent-01 && uname -a"
  }' | jq .

# Expected: Command output with exit code 0

4. Test Terminal Session

# Open dashboard
xdg-open http://localhost:8122

# Click on agent-01
# Click "Terminal" button
# Type commands in terminal

# Verify output appears in real-time

5. Test Agentshare Storage

# On host: Create test file in global
echo "Shared resource" > /srv/agentshare/global/test.txt

# In VM via dev/break-glass direct SSH: verify read access
ssh [email protected] 'cat ~/global/test.txt'
# Expected: "Shared resource"

# In VM via dev/break-glass direct SSH: test write to inbox
ssh [email protected] 'echo "Agent output" > ~/inbox/output.txt'

# On host: Verify file appears
cat /srv/agentshare/inbox/agent-01/output.txt
# Expected: "Agent output"

6. Submit Test Task

# Submit a simple task
curl -X POST http://localhost:8122/api/v1/tasks \
  -H "Content-Type: application/json" \
  -d '{
    "manifest": {
      "version": "1",
      "kind": "Task",
      "metadata": {"id": "", "name": "Hello World smoke"},
      "repository": {"url": "https://github.com/example/repo.git", "branch": "main"},
      "claude": {
        "prompt": "Create a Python script that prints Hello World",
        "model": "claude-sonnet-4-5-20250929"
      },
      "lifecycle": {"timeout": "5m"}
    }
  }' | jq .

# Get task ID from response
TASK_ID="<task-id-from-response>"

# Check task status
curl http://localhost:8122/api/v1/tasks/$TASK_ID | jq .

# Stream task logs
curl http://localhost:8122/api/v1/tasks/$TASK_ID/logs

# Wait for task completion, then check artifacts
curl http://localhost:8122/api/v1/tasks/$TASK_ID/artifacts | jq .

7. Verify Monitoring

# Check Prometheus metrics
curl http://localhost:8122/metrics | grep agentic_agents_connected
# Expected: agentic_agents_connected 3

# Check alert rules are loaded
curl http://localhost:9090/api/v1/rules | jq '.data.groups[] | .name'

# Verify Grafana dashboards
# Open http://localhost:3000
# Navigate to Dashboards → Browse
# Verify "Agent Fleet Overview" dashboard loads

8. Full Integration Test

Run the E2E test suite:

cd ~/dev/agentic-sandbox
./scripts/run-e2e-tests.sh

# Expected: All tests pass

9. Verify VM Lifecycle

# Stop VM
virsh shutdown agent-01

# Verify agent disconnects
curl http://localhost:8122/api/v1/agents | jq '.[] | select(.agent_id=="agent-01")'
# Expected: status="disconnected" or agent not in list

# Start VM
virsh start agent-01

# Wait 30 seconds for agent to reconnect
sleep 30

# Verify agent reconnects
curl http://localhost:8122/api/v1/agents | jq '.[] | select(.agent_id=="agent-01")'
# Expected: status="ready"

10. Production Readiness Checklist

Final checklist before production deployment:

  • [ ] All services start automatically on boot
  • [ ] Health endpoints return 200 OK
  • [ ] At least 3 agent VMs provisioned and connected
  • [ ] Commands execute successfully on agents
  • [ ] Terminal sessions work in dashboard
  • [ ] Agentshare storage is accessible (read global, write inbox)
  • [ ] Prometheus is scraping all targets
  • [ ] Alertmanager is configured with notification channels
  • [ ] Grafana dashboards load and display data
  • [ ] Backup script runs successfully
  • [ ] Log rotation configured
  • [ ] Firewall rules tested
  • [ ] Audit logging enabled
  • [ ] Documentation reviewed and updated
  • [ ] Runbooks created for alert responses

Troubleshooting Common Issues

Management Server Won't Start

# Check logs
sudo journalctl -u agentic-mgmt -n 50

# Common issues:
# 1. Port already in use
sudo lsof -i :8120
# Kill conflicting process or change LISTEN_ADDR

# 2. Secrets directory not readable
ls -la /var/lib/agentic-sandbox/secrets
sudo chmod 755 /var/lib/agentic-sandbox/secrets

# 3. Missing environment file
ls -la /etc/agentic-sandbox/management.env

VM Won't Start

# Check libvirt logs
sudo journalctl -u libvirtd -n 50

# Check VM definition
virsh dumpxml agent-01

# Common issues:
# 1. Base image not found
ls -la /mnt/ops/base-images/

# 2. Overlay disk not found
ls -la /var/lib/agentic-sandbox/vms/agent-01/

# 3. Network not active
virsh net-list --all
virsh net-start default

Agent Won't Connect

See Agent Deployment - Troubleshooting Agent Connection.

Prometheus Not Scraping Targets

# Check Prometheus logs
sudo journalctl -u prometheus -n 50

# Verify config syntax
promtool check config /etc/prometheus/prometheus.yml

# Test target connectivity
curl http://localhost:8122/metrics  # Management server
curl http://192.168.122.201:9100/metrics  # Agent VM

# Reload Prometheus config
sudo systemctl reload prometheus

Next Steps

After successful deployment:

1. Read Operations Guide: See `docs/OPERATIONS.md` for day-to-day operations 2. Review API Documentation: See `docs/API.md` for complete API reference 3. Set Up CI/CD: Automate agent deployment with your CI/CD pipeline 4. Configure Backups: Set up automated backups for critical data 5. Create Runbooks: Document procedures for common operational tasks 6. Train Team: Ensure team members understand system architecture and operations

References

  • Architecture: `docs/ARCHITECTURE.md`
  • API Reference: `docs/API.md`
  • Observability: `docs/OBSERVABILITY_DESIGN.md`
  • Session Management: `docs/SESSION_RECONCILIATION.md`
  • Build Guide: `BUILD.md`
  • Project README: `README.md`

Deployment Guide Version: 1.0 Last Updated: 2026-02-07 Maintained By: Agentic Sandbox Team