Loadout Manifest System
Loadout Manifest System
Declarative YAML manifests for composable VM provisioning. Loadouts define what tools, runtimes, AI providers, and AIWG frameworks get installed in a VM.
Quick Start
# Single-provider VM with Claude Code
./provision-vm.sh agent-01 --loadout profiles/claude-only.yaml --agentshare --start
# Two providers for cross-checking
./provision-vm.sh agent-02 --loadout profiles/dual-review.yaml --start
# Isolated security audit environment
./provision-vm.sh agent-03 --loadout profiles/security-audit.yaml --start
# Full suite - all 9 providers with the consumer framework bundle
./provision-vm.sh agent-04 --loadout profiles/full-suite.yaml --cpus 8 --memory 32G --start
Pre-Built Profiles
Per-Provider
| Profile | AI Tools | AIWG Framework | Resources |
|---|---|---|---|
| `claude-only` | Claude Code | `all` consumer bundle -> claude | 4 CPU, 8G |
| `codex-only` | Codex CLI | sdlc -> codex | 4 CPU, 8G |
| `copilot-only` | Copilot CLI | sdlc -> copilot | 4 CPU, 8G |
Collaboration
| Profile | AI Tools | AIWG Framework | Resources |
|---|---|---|---|
| `dual-review` | Claude + Codex | sdlc -> [claude, codex] | 4 CPU, 12G |
| `multi-provider` | Claude + Codex + Copilot | sdlc -> [claude, codex, copilot] | 6 CPU, 16G |
| `full-suite` | Claude Code + Aider + Codex + Copilot | `all` consumer framework bundle -> all 9 providers | 8 CPU, 32G |
Task-Focused
| Profile | Purpose | Network | AIWG Framework |
|---|---|---|---|
| `security-audit` | Forensics/security analysis | isolated | forensics + sdlc |
| `research-station` | Deep research tasks | full | research + sdlc |
| `sdlc-team` | Collaborative SDLC development | full | sdlc + ops |
| `browser-qa` | Trusted-input browser QA (carbonyl + uinput + Xorg) — VM fallback when Docker hot-plug is unavailable (carbonyl-agent#120). Provisions a private carbonyl session mount at `/home/agent/.local/share/carbonyl-agent/sessions`; verify with `scripts/validate-browser-qa.sh <vm-name>` after provision. | full | none |
| `automation-control` | Orchestrator-driven TUI control blueprint with Observer-first workflow, Codex/Claude launch wrappers, provider readiness, ops/sdlc AIWG frameworks, and credential-free inventory. | full | ops + sdlc |
Browser-QA Session Persistence
The `browser-qa` profile creates a per-VM host directory for carbonyl-agent session material:
| Host Path | VM Path | Mode | Purpose |
|---|---|---|---|
| `/var/lib/agentic-sandbox/vms/{vm}/carbonyl-sessions` | `/home/agent/.local/share/carbonyl-agent/sessions` | `0700` | Private carbonyl-agent browser session state |
The directory is mounted into the VM with the `carbonylsessions` virtiofs tag and is not deleted when a loadout is changed. Cookie/session files placed there should be mode `0600`. The sandbox does not import, prepopulate, or transform cookie material; higher-level tooling owns that workflow.
`browser-qa` declares `readiness.setup_timeout_seconds: 1200` because its setup path installs the browser automation stack and can exceed the generic 300 second profile setup budget on loaded hosts. Operators can override the setup wait for any loadout with `AGENTIC_VM_SETUP_WAIT_SECONDS` or `LOADOUT_SETUP_WAIT_SECONDS`. `vm-info.json` is written before readiness waits begin, so timeout diagnostics still include the VM IP, storage path, loadout profile, and generated SSH key path.
Backward Compatibility
| Profile | Equivalent To |
|---|---|
| `basic` | `--profile basic` |
| `agentic-dev` | `--profile agentic-dev` |
Manifest Schema
apiVersion: loadout/v1
kind: loadout # "loadout" (complete) or "layer" (composable partial)
metadata:
name: my-profile
description: What this profile does
labels:
category: per-provider # per-provider | collaboration | task-focused
extends: # Composable inheritance (depth-first, left-to-right)
- layers/base-dev.yaml
- layers/docker.yaml
- providers/claude-code.yaml
resources:
cpus: 4
memory: 8G
disk: 40G
gpu:
enabled: false
device: "0000:01:00.0" # PCI device ID for passthrough
runtime_options:
kind: vm
provider: cloud-hypervisor
required_capabilities: [instance.restore]
excluded_capabilities: [device.vfio]
launch_strategy:
mode: restore
prefer_fast_start: true
asset_ref: ch-snapshot-agentic-dev
restore_mode: copy
constraints:
allow_vfio_fast_start: false
fallback_mode: fail
network:
mode: full # isolated | allowlist | full
readiness:
setup_timeout_seconds: 300 # Optional --wait-ready setup budget
packages: # apt packages
- ripgrep
- jq
runtimes:
python:
enabled: true
method: uv # uv (default) | system
tools: [ruff, aider-chat]
node:
enabled: true
method: fnm # fnm (default) | system
version: lts
package_manager: pnpm
global_packages: [aiwg, "@openai/codex"]
go:
enabled: true
version: latest
tools: [github.com/fullstorydev/grpcurl/cmd/grpcurl@latest]
rust:
enabled: true
components: [clippy, rustfmt, rust-analyzer]
crates: [xh, websocat, hyperfine]
bun:
enabled: true
ai_tools:
claude_code:
enabled: true
channel: stable
settings: { model: claude-sonnet-4-5-20250929 }
aider:
enabled: true
config: { model: claude-3-5-sonnet-20241022, auto_commits: false }
codex:
enabled: true
config: { model: gpt-4o, approval_mode: suggest }
copilot:
enabled: true
aiwg:
enabled: true
frameworks:
- name: sdlc
providers: [claude, codex]
docker:
enabled: true
mode: rootless
startup_profile:
id: startup-codex-ci
trigger: on_instance_ready
session:
launcher: agentic-codex-automation
workdir: /workspace
credential_refs:
- id: cred_openai_platform_ci
mount: openai_api_key
readiness:
probes:
- provider: codex
kind: auth
observation:
retention_class: credentialed-short
redaction_profile: provider-secrets-v1
`startup_profile` is the target declarative autostart model tracked by #484 and ADR-028. It references credential ids only; loadout manifests must not contain provider secret values.
Runtime Options and Fast Start
`runtime_options` describes portable launch intent for management clients such as Cockpit. It does not select host-local files directly; the management API resolves `asset_ref` into an available snapshot, checkpoint, fork base, or warm pool on the chosen provider.
`POST /api/v2/admin/instances` accepts the same intent for VM provisioning. For `runtime: qemu`, `mode: cold` follows the normal provision path, while `mode: restore`, `mode: fork`, and `mode: warm_pool` delegate to the selected provider's fast-start operation and still return an async operation envelope. Non-cold launch intent requires `launch_strategy.asset_ref` and cannot be mixed with cold-provision-only fields such as `loadout`, `profile`, `image`, `agentshare`, `mounts`, `labels`, `working_dir`, `startup_profile_id`, or `ssh_key`. Cloud Hypervisor supports restore, fork, and warm-pool handoff; libvirt supports restore and warm-pool handoff. Libvirt fork is rejected because checkpoint reuse does not provide a provider-native fork operation. For libvirt warm-pool handoff, the pool's consumed checkpoint supplies the immutable VM identity; Cloud Hypervisor handoff uses the requested instance name.
Cold boot on the default VM provider:
runtime_options:
kind: vm
launch_strategy:
mode: cold
constraints:
fallback_mode: fail
Cloud Hypervisor snapshot restore:
runtime_options:
kind: vm
provider: cloud-hypervisor
required_capabilities: [instance.restore]
launch_strategy:
mode: restore
prefer_fast_start: true
asset_ref: ch-snapshot-agentic-dev
restore_mode: copy
constraints:
allow_vfio_fast_start: false
fallback_mode: fail
Cloud Hypervisor fork from a warm base:
runtime_options:
kind: vm
provider: cloud-hypervisor
required_capabilities: [instance.fork]
launch_strategy:
mode: fork
prefer_fast_start: true
asset_ref: ch-base-sdlc-team
Libvirt warm-pool handoff:
runtime_options:
kind: vm
provider: libvirt
required_capabilities: [warm_pool.manage]
launch_strategy:
mode: warm_pool
prefer_fast_start: true
asset_ref: libvirt-pool-sdlc-team
VFIO-attached VM. Fast-start capabilities must be excluded because managed VFIO VMs cannot safely use snapshot, restore, fork, or warm-pool reuse:
resources:
gpu:
enabled: true
device: "0000:01:00.0"
driver: vfio-pci
runtime_options:
kind: vm
provider: cloud-hypervisor
required_capabilities: [device.vfio]
excluded_capabilities: [instance.snapshot, instance.restore, instance.fork, warm_pool.manage]
launch_strategy:
mode: cold
constraints:
allow_vfio_fast_start: false
fallback_mode: fail
Composable Layers
Manifests can extend other manifests via `extends:`. Resolution is depth-first, left-to-right:
- Scalars: last value wins (most-specific manifest)
- String arrays: union + dedup (e.g., packages merge)
- Object arrays: concatenate (e.g., frameworks append)
- Maps: deep merge (recursive)
Available Layers
| Layer | Contents |
|---|---|
| `layers/base-minimal.yaml` | SSH, health server, UFW, qemu-guest-agent |
| `layers/base-dev.yaml` | Languages, build tools, CLI tools (extends base-minimal) |
| `layers/ai-tools.yaml` | Claude Code, Aider, Codex CLI |
| `layers/docker.yaml` | Rootless Docker with compose + buildx |
| `layers/databases.yaml` | PostgreSQL, MySQL, Redis, SQLite clients |
| `layers/observability.yaml` | strace, sysstat, iotop, nethogs |
| `layers/network-tools.yaml` | xh, grpcurl, websocat, hyperfine |
| `layers/automation-control.yaml` | Provider inventory helper, credential-aware `agentic-codex-automation` and `agentic-claude-automation` wrappers, `agentic-provider-readiness`, and control-session notes for orchestrator-driven TUI sessions |
Provider Layers
One per AIWG provider. Each declares prerequisites and AI tool config:
| Provider | AI Tool | Prerequisites |
|---|---|---|
| `providers/claude-code.yaml` | Claude Code CLI | Node.js |
| `providers/codex.yaml` | @openai/codex | Node.js |
| `providers/copilot.yaml` | GitHub Copilot CLI | Node.js |
| `providers/factory.yaml` | (framework only) | Node.js |
| `providers/cursor.yaml` | (framework only) | Node.js |
| `providers/opencode.yaml` | (framework only) | Node.js |
| `providers/warp.yaml` | (framework only) | Node.js |
| `providers/windsurf.yaml` | (framework only) | Node.js |
| `providers/openclaw.yaml` | (framework only) | Node.js |
Creating Custom Profiles
1. Create a YAML file in `images/qemu/loadouts/profiles/`:
apiVersion: loadout/v1
kind: loadout
metadata:
name: my-custom
description: Custom profile for my use case
extends:
- layers/base-dev.yaml
- layers/docker.yaml
- providers/claude-code.yaml
- providers/codex.yaml
resources:
memory: 16G
aiwg:
enabled: true
frameworks:
- name: sdlc-complete
providers: [claude-code, codex]
2. Use it:
./provision-vm.sh agent-01 --loadout profiles/my-custom.yaml --start
CLI Override Precedence
CLI flags always override manifest values:
# Manifest says 8G memory, but CLI overrides to 16G
./provision-vm.sh agent-01 --loadout profiles/claude-only.yaml --memory 16G
Network Modes
| Mode | Behavior |
|---|---|
| `full` | Unrestricted egress (default) |
| `allowlist` | DNS-filtered, HTTPS-only (requires Blocky) |
| `isolated` | Management server only, no internet |
GPU Passthrough
VMs can access host GPUs for ML inference, security testing, and other GPU workloads:
resources:
gpu:
enabled: true
device: "0000:01:00.0" # PCI device ID (lspci -nn)
driver: vfio-pci # default
Prerequisites
1. Host IOMMU enabled (`intel_iommu=on` or `amd_iommu=on` in kernel cmdline) 2. An ACS-isolated, headless GPU whose complete IOMMU group can be dedicated to the VM 3. A PCI reset interface at `/sys/bus/pci/devices/<BDF>/reset` for cross-tenant use 4. PCI device ID from `lspci -nn` (e.g., `0000:01:00.0`)
What Happens
- The loadout generator writes a `gpu-config` sidecar file
- Libvirt adds a managed `<hostdev>` PCI passthrough element
- Cloud Hypervisor claims the IOMMU group, binds every member to `vfio-pci`, resets the GPU, and
adds one `--device path=/sys/bus/pci/devices/<BDF>/` argument per member
- Cloud-init installs GPU drivers via `ubuntu-drivers install --gpgpu`
- The GPU is exclusively owned by the VM (not shared with host)
- Managed CH teardown resets the GPU, restores original host drivers, and releases the group claim
- CH GPU VMs use cold hand-outs only; snapshot/restore, fork, and warm-pool flows reject VFIO
snapshots because generic `vfio-pci` device state is not migratable
See GPU sandboxing and passthrough for the host-class recommendation, security boundary, reset policy, and hardware validation procedure.
Example
# Security audit with GPU for accelerated hash cracking
./provision-vm.sh agent-01 --loadout profiles/security-audit.yaml --start
# (edit security-audit.yaml to set resources.gpu.enabled: true and device)
AIWG Provider Matrix
| Provider | Native Features | Emulated Features |
|---|---|---|
| claude-code | cron, agent_teams, tasks, MCP | behaviors, mission_control |
| codex | (none) | all via aiwg-mc |
| copilot | (none) | all via aiwg-mc |
| factory | (none) | all via aiwg-mc |
| cursor | (none) | all via aiwg-mc |
| opencode | (none) | all via aiwg-mc |
| warp | (none) | all via aiwg-mc |
| windsurf | (none) | all via aiwg-mc |
| openclaw | MCP, behaviors | cron, tasks via aiwg-mc |
AIWG Frameworks
| Framework | Purpose |
|---|---|
| all | Reserved bundle for all consumer-facing frameworks: sdlc, ops, forensics, and research |
| aiwg-dev | Reserved contributor tooling bundle; opt in explicitly, not included in all |
| sdlc | Software development lifecycle workflows |
| ops | Operations and infrastructure workflows |
| forensics | Digital forensics and incident response workflows |
| research | Research synthesis and knowledge management workflows |
Directory Structure
images/qemu/loadouts/
schema.yaml # Manifest schema reference
resolve-manifest.sh # YAML inheritance resolver
generate-from-manifest.sh # Manifest -> cloud-init generator
layers/ # Composable base layers
providers/ # Per-AIWG-provider layers
profiles/ # Pre-built composed profiles
tests/ # Test suite
Troubleshooting
Manifest not found
# Paths are relative to images/qemu/loadouts/
./provision-vm.sh agent-01 --loadout profiles/claude-only.yaml # correct
./provision-vm.sh agent-01 --loadout /absolute/path/to/manifest.yaml # also works
Package conflicts
The resolver deduplicates string arrays. If two layers specify the same package, it appears once.
Debugging resolution
# See the fully resolved manifest
cd images/qemu/loadouts
./resolve-manifest.sh profiles/full-suite.yaml