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

ProfileAI ToolsAIWG FrameworkResources
`claude-only`Claude Code`all` consumer bundle -> claude4 CPU, 8G
`codex-only`Codex CLIsdlc -> codex4 CPU, 8G
`copilot-only`Copilot CLIsdlc -> copilot4 CPU, 8G

Collaboration

ProfileAI ToolsAIWG FrameworkResources
`dual-review`Claude + Codexsdlc -> [claude, codex]4 CPU, 12G
`multi-provider`Claude + Codex + Copilotsdlc -> [claude, codex, copilot]6 CPU, 16G
`full-suite`Claude Code + Aider + Codex + Copilot`all` consumer framework bundle -> all 9 providers8 CPU, 32G

Task-Focused

ProfilePurposeNetworkAIWG Framework
`security-audit`Forensics/security analysisisolatedforensics + sdlc
`research-station`Deep research tasksfullresearch + sdlc
`sdlc-team`Collaborative SDLC developmentfullsdlc + 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.fullnone
`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.fullops + sdlc

Browser-QA Session Persistence

The `browser-qa` profile creates a per-VM host directory for carbonyl-agent session material:

Host PathVM PathModePurpose
`/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

ProfileEquivalent 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

LayerContents
`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:

ProviderAI ToolPrerequisites
`providers/claude-code.yaml`Claude Code CLINode.js
`providers/codex.yaml`@openai/codexNode.js
`providers/copilot.yaml`GitHub Copilot CLINode.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

ModeBehavior
`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

ProviderNative FeaturesEmulated Features
claude-codecron, agent_teams, tasks, MCPbehaviors, 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
openclawMCP, behaviorscron, tasks via aiwg-mc

AIWG Frameworks

FrameworkPurpose
allReserved bundle for all consumer-facing frameworks: sdlc, ops, forensics, and research
aiwg-devReserved contributor tooling bundle; opt in explicitly, not included in all
sdlcSoftware development lifecycle workflows
opsOperations and infrastructure workflows
forensicsDigital forensics and incident response workflows
researchResearch 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