# Deploy an AgentVM from an agent harness

This is the durable return point for launching a temporary AgentVM from Codex, Claude Code, Cursor, `bs`, or another terminal-capable agent harness.

The bounded provisioning flow below uses a 59-minute (`3540` second) hard runtime ceiling so a default harness run stops before the next hourly provider billing boundary.

For the interactive copy/paste prompt and harness tabs, open:

https://agentvm.sh/docs

For agent-readable instructions, open:

https://agentvm.sh/SKILL.md

## 1. Pick a harness

### Codex

```bash
curl -fsSL https://chatgpt.com/codex/install.sh | sh
codex
```

### Claude Code

```bash
curl -fsSL https://claude.ai/install.sh | bash
claude
```

### Cursor

```bash
curl https://cursor.com/install -fsS | bash
agent
```

### BuilderStudio

```bash
npm install -g @wundercorp/bs
bs
```

BuilderStudio package:

https://www.npmjs.com/package/@wundercorp/bs

## 2. Install AgentVM and Tempo

AgentVM:

```bash
npm install -g @wundercorp/agentvm
```

Package:

https://www.npmjs.com/package/@wundercorp/agentvm

Tempo CLI:

```bash
curl -fsSL https://tempo.xyz/install | bash
```

When required by the current Tempo setup:

```bash
tempo add request
tempo wallet login
```

## 3. Give the harness the deployment contract

The full copy/paste contract lives at https://agentvm.sh/docs. It tells the agent to:

- inspect the current MPP discovery surface before paying
- default to `hetzner-shell`
- cap spend at `$1.00 USD` unless you explicitly authorize another amount
- create exactly one `requestId` and `idempotencyKey` and persist both before payment
- prefer a Tempo-backed MPP Session only when live discovery enables it
- otherwise use the fixed-budget Stripe Link run
- save the complete receipt with mode `0600`
- recover interrupted provisioning with the same IDs instead of making a duplicate purchase
- verify the machine over SSH and require `AGENTVM_OK`
- report safe operational metadata without printing access tokens or secret capabilities

## 4. Live MPP resources

Always let the live API override copied examples:

- Discovery: https://mpp.agentvm.sh/
- Agent instructions: https://mpp.agentvm.sh/llms.txt
- OpenAPI: https://mpp.agentvm.sh/openapi.json
- Fixed runs: https://mpp.agentvm.sh/compute/runs
- Sessions: https://mpp.agentvm.sh/compute/sessions

## 5. Protect request state

Before a paid request, create protected local state:

```bash
mkdir -p .agentvm-mpp
chmod 700 .agentvm-mpp
```

Generate one pair only when the current attempt does not already have IDs, and persist the dedicated automation key path with them:

```bash
REQUEST_ID="run-$(date +%s)-$(openssl rand -hex 4)"
IDEMPOTENCY_KEY="$(openssl rand -hex 24)"
SSH_KEY_PATH="$PWD/.agentvm-mpp/keys/$REQUEST_ID"
MAX_SPEND="2.00"
MAX_RUNTIME_SECONDS="3540"
printf 'REQUEST_ID=%q\nIDEMPOTENCY_KEY=%q\nSSH_KEY_PATH=%q\nMAX_SPEND=%q\nMAX_RUNTIME_SECONDS=%q\n' "$REQUEST_ID" "$IDEMPOTENCY_KEY" "$SSH_KEY_PATH" "$MAX_SPEND" "$MAX_RUNTIME_SECONDS" > .agentvm-mpp/request.env
chmod 600 .agentvm-mpp/request.env
```

If `.agentvm-mpp/request.env` already exists for the attempt, load it instead of creating new values. Keep `.agentvm-mpp/` out of git.

## 6. SSH key

Use a dedicated per-attempt key under `.agentvm-mpp`. Do not reuse `~/.ssh/id_ed25519`, `~/.ssh/id_rsa`, or another personal default identity: those keys may require a passphrase or interactive SSH agent access that an automated harness cannot provide.

```bash
source .agentvm-mpp/request.env
if test -z "${MAX_RUNTIME_SECONDS:-}"; then
  if test -s .agentvm-mpp/provision.json || test -s .agentvm-mpp/session-recovery.json; then
    echo 'Existing paid/recovery state predates the 59-minute runtime ceiling; stop rather than changing an authorized request.' >&2
    exit 1
  fi
  MAX_RUNTIME_SECONDS="3540"
  printf 'MAX_RUNTIME_SECONDS=%q\n' "$MAX_RUNTIME_SECONDS" >> .agentvm-mpp/request.env
  chmod 600 .agentvm-mpp/request.env
fi
if test -z "${SSH_KEY_PATH:-}"; then
  if test -s .agentvm-mpp/provision.json; then
    echo 'Existing AgentVM response predates persisted SSH key state; do not create a replacement key or repurchase.' >&2
    exit 1
  fi
  SSH_KEY_PATH="$PWD/.agentvm-mpp/keys/$REQUEST_ID"
  printf 'SSH_KEY_PATH=%q\n' "$SSH_KEY_PATH" >> .agentvm-mpp/request.env
  chmod 600 .agentvm-mpp/request.env
fi
mkdir -p "$(dirname "$SSH_KEY_PATH")"
chmod 700 "$(dirname "$SSH_KEY_PATH")"
test -f "$SSH_KEY_PATH" || ssh-keygen -q -t ed25519 -N '' -f "$SSH_KEY_PATH" -C "agentvm-mpp-$REQUEST_ID"
test -f "$SSH_KEY_PATH.pub" || { echo 'AgentVM automation SSH public key is missing'; exit 1; }
chmod 600 "$SSH_KEY_PATH"
chmod 644 "$SSH_KEY_PATH.pub"
PUBLIC_KEY="$(cat "$SSH_KEY_PATH.pub")"
```

Run key creation before payment. The per-attempt path is persisted with the request IDs and the key is generated with no passphrase, so the harness can use the same private key later without human input. If creation fails, do not purchase the VM. Only the public key should be sent to AgentVM.

If an older request state has no `SSH_KEY_PATH` and a paid response already exists, do not generate a replacement identity or repurchase. The existing VM must be accessed with the original private key matching the public key submitted at purchase time.

## 7. Tempo session when enabled

Inspect the session discovery endpoint first. Only use Tempo when the endpoint reports that MPP Sessions are enabled and Tempo is an active settlement method.

The request should follow the current published schema and set a hard maximum spend no larger than your authorized amount. Preserve the exact JSON in the `x-agentvm-request-payload` fallback header so a paid retry that loses its body can still be verified. A typical command shape is:

```bash
source .agentvm-mpp/request.env
MAX_SPEND="${MAX_SPEND:-2.00}"
MAX_RUNTIME_SECONDS="${MAX_RUNTIME_SECONDS:-3540}"
test "$MAX_RUNTIME_SECONDS" = "3540" || { echo 'This bounded flow requires a 59-minute maximum runtime.' >&2; exit 1; }
PUBLIC_KEY="$(cat "$SSH_KEY_PATH.pub")"
SESSION_REQUEST_JSON="$(jq -c -n \
  --arg requestId "$REQUEST_ID" \
  --arg idempotencyKey "$IDEMPOTENCY_KEY" \
  --arg publicKey "$PUBLIC_KEY" \
  --arg maximumSpend "$MAX_SPEND" \
  --argjson maximumRuntimeSeconds "$MAX_RUNTIME_SECONDS" \
  '{requestId:$requestId,idempotencyKey:$idempotencyKey,publicKey:$publicKey,maximumSpend:$maximumSpend,maximumRuntimeSeconds:$maximumRuntimeSeconds}')"
REQUEST_PAYLOAD_B64="$(printf '%s' "$SESSION_REQUEST_JSON" | base64 | tr -d '\r\n')"
umask 077
tempo request \
  --max-spend "$MAX_SPEND" \
  -X POST \
  -H "x-agentvm-request-payload: $REQUEST_PAYLOAD_B64" \
  --json "$SESSION_REQUEST_JSON" \
  -o .agentvm-mpp/provision.json \
  'https://mpp.agentvm.sh/compute/sessions/hetzner-shell'
chmod 600 .agentvm-mpp/provision.json
```

## 8. Fixed-budget fallback

When sessions are unavailable, use the fixed-budget MPP run. AgentVM's CLI distribution includes Stripe Link support; authenticate if needed:

```bash
link-cli auth status
link-cli auth login
```

The live MPP instructions publish the current request shape. A `$1.00` shell run is expected to use:

```bash
source .agentvm-mpp/request.env
MAX_RUNTIME_SECONDS="${MAX_RUNTIME_SECONDS:-3540}"
test "$MAX_RUNTIME_SECONDS" = "3540" || { echo 'This bounded flow requires a 59-minute maximum runtime.' >&2; exit 1; }
test -n "${SSH_KEY_PATH:-}" || { echo 'Missing persisted AgentVM SSH key path'; exit 1; }
PUBLIC_KEY="$(cat "$SSH_KEY_PATH.pub")"

umask 077
LINK_RESULT_PATH=".agentvm-mpp/link-result.json"
link-cli mpp pay -X POST 'https://mpp.agentvm.sh/compute/runs/hetzner-shell' \
  -H 'content-type: application/json' \
  -d "$(jq -n \
    --arg requestId "$REQUEST_ID" \
    --arg idempotencyKey "$IDEMPOTENCY_KEY" \
    --arg budget '2.00' \
    --arg publicKey "$PUBLIC_KEY" \
    --argjson maximumRuntimeSeconds "$MAX_RUNTIME_SECONDS" \
    '{requestId:$requestId,idempotencyKey:$idempotencyKey,budget:$budget,publicKey:$publicKey,maximumRuntimeSeconds:$maximumRuntimeSeconds}')" \
  --context 'Purchase one predefined AgentVM shell box for development/testing with a hard 59-minute runtime ceiling. Maximum authorized spend is $2.00. The VM is temporary and automatically expires.' \
  --format json > "$LINK_RESULT_PATH"
chmod 600 "$LINK_RESULT_PATH"
jq -e -r '.body' "$LINK_RESULT_PATH" > .agentvm-mpp/provision.json
chmod 600 .agentvm-mpp/provision.json
jq -e . .agentvm-mpp/provision.json >/dev/null
rm -f "$LINK_RESULT_PATH"
```

Treat the paid MPP result as secret-bearing. Keep Link CLI structured output redirected to the protected intermediate file, extract only the merchant `body` into `.agentvm-mpp/provision.json`, and never print either file. If the installed Link CLI schema differs, inspect its schema/help before making the paid request rather than discovering the shape with secret-bearing terminal output.

## 9. Safe receipt and verification

Print a redacted operational summary:

```bash
agentvm mpp receipt .agentvm-mpp/provision.json
```

Then SSH into the returned machine with the exact persisted key and non-interactive identity selection:

```bash
SSH_HOST="$(jq -r '.vm.ssh.host // .vm.ipv4 // .vm.ipv6 // empty' .agentvm-mpp/provision.json)"
SSH_USER="$(jq -r '.vm.ssh.user // "user"' .agentvm-mpp/provision.json)"
SSH_PORT="$(jq -r '.vm.ssh.port // 22' .agentvm-mpp/provision.json)"
test -n "$SSH_HOST" || { echo 'AgentVM SSH host is missing'; exit 1; }
ssh -o BatchMode=yes -o IdentitiesOnly=yes -o StrictHostKeyChecking=accept-new -i "$SSH_KEY_PATH" -p "$SSH_PORT" "$SSH_USER@$SSH_HOST"
```

Run remotely:

```bash
set -e
hostname
whoami
uname -a
cat /etc/os-release
df -h /
free -h || true
if command -v cloud-init >/dev/null 2>&1; then sudo cloud-init status --wait || true; fi
for attempt in $(seq 1 90); do
  if sudo test -f /var/lib/agentvm-wundercorp-tools-ready; then break; fi
  if sudo test -f /var/lib/agentvm-wundercorp-tools-failed; then sudo systemctl restart agentvm-wundercorp-tools.service || true; fi
  sleep 5
done
agentvm-tools-status
for command_name in node npm python3 argent agentvm om doku codex claude bs; do command -v "$command_name" >/dev/null; done
echo AGENTVM_OK
```

The expected success marker is:

```text
AGENTVM_OK
```

## 10. If provisioning is interrupted

Do not buy another VM just because the terminal lost the response or provisioning is still pending.

Reload `.agentvm-mpp/request.env` and retry/recover with the exact same `REQUEST_ID` and `IDEMPOTENCY_KEY` using the live MPP status or recovery instructions. The IDs are the duplicate-purchase guardrail.

## 11. What to keep

Keep `.agentvm-mpp/provision.json` protected with mode `0600` until the VM has expired and you no longer need the receipt.

A safe handoff should include:

- payment mode
- VM profile and provider
- region and capacity when returned
- amount authorized or paid
- creation and expiration time
- SSH host, user, and port
- HTTPS endpoint when returned
- `AGENTVM_OK` result
- local receipt path

Do not paste private SSH keys, bearer tokens, MPP Session tokens, wallet credentials, or secret capabilities into tickets, chat, commits, or logs.
