Mantle

Operations

Running sessions and workers.

Everything here describes release 0.3.1. Behaviour of earlier releases, including the destructive stop of 0.1.4 and the Substrate 0.7.10 workers of 0.2.0, is on release history.

01 / Commands

The CLI.

--profile NAME works with every command; see profiles.

Mantle commands and their behaviour
CommandWhat it does
mantle worker up [--binaries DIR] [--no-idle-stop]Create or start the worker, install the Mantle helpers from DIR, and check Substrate’s facts.
mantle worker statusShow provider state and the worker’s observed capabilities.
mantle worker sshOpen a shell on the worker host through the provider tunnel, outside every sandbox.
mantle worker downStop the worker; its data volume and workspaces are kept.
mantle worker upgrade --check|--apply --manifest PATHCheck or apply a verified helper bundle during offline maintenance. See offline upgrades.
mantle start [MANIFEST] [--detached [--receipt PATH]]Resolve the manifest (default mantle.yaml), clone the repositories, start the agent, and attach unless --detached.
mantle attach NAMEAttach to the agent’s terminal. One attachment at a time. Ctrl+] d detaches.
mantle list [--json]List sessions recorded locally, including retained ones.
mantle status NAME [--json] [--timeout-secs N]Recorded intent, then what Substrate observes now. --session-id ID replaces the name with --json.
mantle exec NAME -- PROGRAM ARGSRun one command in the workspace under the agent’s confinement, without the agent’s credential.
mantle stop NAMEEnd the agent; keep the workspace and its private home.
mantle restart NAMEStart a fresh agent in the retained workspace.
mantle destroy NAME --yesDelete the workspace and its private home.
mantle profile add|list|showManage named configuration and state paths.
mantle doctor [--json] [--timeout-secs N]Diagnose an existing worker with read-only probes.
mantle-acceptance run|resume|report|cleanupQualify disposable sessions through the installed CLI.

Attach, exec, stop, restart and destroy accept --expected-session-id ID.

02 / Worker

One worker per configuration.

The first worker up boots the VM with cloud-init, waits for bootstrap, installs the three static helpers from --binaries (default: x86_64-unknown-linux-musl/release under $CARGO_TARGET_DIR, or under ~/.cache/b10x-target/mantle when that is unset, which is where task build-worker puts them), installs the Claude token when [claude] is configured, starts Substrate, installs pinned Codex and prints the worker’s identity and facts.

On AWS, worker up also creates a CloudWatch alarm that stops the instance after 24 five-minute periods (two hours) below 2% average CPU. --no-idle-stop leaves it out. worker down stops the VM; the data volume stays, and retained cloud storage may still incur charges.

On a worker that already runs, worker up starts services but does not restart a running older Substrate daemon, defers changed shared binaries while services are active, and refuses to replace helpers installed from a release bundle. Use offline upgrades for those.

A worker created by 0.2.0 runs Substrate 0.7.10. The 0.3.1 CLI and helpers cannot use it, and neither worker up nor an offline upgrade moves it to Substrate 0.16.1: sessions on 0.3.1 need a new worker. See upgrading from 0.2.0.

03 / Sessions

Stop keeps the workspace.

mantle stop my-project
mantle restart my-project
mantle destroy my-project --yes

Interruptions and concurrent commands

For automation, pass --expected-session-id ID with the recorded id, not just the reusable name: the CLI compares it before any remote change. Concurrent commands claim a durable generation and intent before acting. An interrupted restart observes its original admission operation instead of starting another process; run the same command again to reconcile. A missing or unknown observation leaves the command incomplete with a nonzero exit. A confirmed running or ended admission can be stopped or destroyed even when readiness failed. An interrupted first materialization stays incomplete when its termination cannot be proven; it is not reported as a retained workspace.

Records from 0.1.x

A historical STOPPED record means destroyed and cannot be restarted. Migration keeps a historical STOPPING record as a destroy intent, so retrying it may still delete the workspace. Records without a validated launch context cannot be restarted from today’s configuration. Changed worker bindings and unsupported persisted policy versions are refused. Retention does not recover a lost worker or volume.

04 / Exec results

The remote exit code is yours.

mantle exec exits with the remote exit code (0–255) and maps the INT, TERM and KILL signals to 130, 143 and 137. A refused, missing, contradictory or indeterminate result exits nonzero. After a valid Substrate response is decoded, stdout and stderr keep their bytes and the status line follows on stderr. A malformed response fails without printing raw output. Networked Cargo builds through exec need the proxy set explicitly; see session environment.

05 / Profiles

Separate named environments.

mantle profile add personal --config "$HOME/.config/mantle/personal.toml" \
  --state-dir "$HOME/.local/state/mantle-personal"
mantle profile list
mantle profile show personal
mantle --profile personal list
MANTLE_PROFILE=personal mantle worker status

06 / Doctor

Diagnose an existing worker.

mantle --profile personal doctor
mantle --profile personal doctor --json --timeout-secs 5

Checks, in order: selection, configuration, local state, the recorded worker and placement, the provider, strict SSH, the Substrate service and socket, installed Mantle binary versions with live Substrate version and wire compatibility, and the common confinement facts. Each reports healthy, failed or skipped. A failure skips the checks that depend on it and exits nonzero. JSON stays structured even when selection or configuration fails. Reports give fixed advice and omit raw provider, SSH and configuration contents. A healthy worker does not prove agent authentication.

Doctor needs an existing SSH key and pinned known host. It never creates keys, accepts new hosts, runs an agent credential command, initializes or migrates the database, changes records or restarts services. SQLite opens read-only and query-only and sees committed WAL data. Your provider’s normal authentication plugins may run. Bounds: each external probe 10 seconds by default (1–120), with timed-out process groups and tunnel descendants killed; configuration input 1 MiB; database and sidecar files 64 MiB; SQLite busy and query handling 250 ms.

07 / Automation

Metadata and receipts.

mantle --profile personal list --json
mantle --profile personal status my-project --json --timeout-secs 10
mantle --profile personal status --session-id SESSION_ID --json

Use the JSON forms in scripts; human status may print launcher diagnostics and is not a stable format. list --json includes ended records and does not contact the worker. status --json separates the recorded identity and state from bounded Substrate observations, including unavailable or refused ones; a lookup by session id also finds destroyed records. In both, a command session’s record carries argv, its declared argv; Claude Code and Codex records have no argv key. Neither fetches terminal output. The recorded authentication method is metadata, not proof of login.

To own what you create, start with mantle start MANIFEST --detached --receipt PATH. The receipt is written only after a successful start and holds the immutable session id and the profile binding. PATH must not exist; put it in a private directory (Mantle does not check the directory). Persist it before acting on the session: a name lookup cannot replace a lost receipt. An interrupted start may have created resources without writing a receipt; treat that as unresolved rather than claiming or destroying the name.

08 / Acceptance runs

Repeatable qualification through the installed CLI.

mantle-acceptance ships in the GNU bundle. It runs the same cases for Claude Code and Codex against a disposable session it creates, on a worker and profile you already configured. It does not provision workers or adopt existing sessions.

mkdir -m 0700 "$HOME/.cache/mantle-acceptance-run"
mantle-acceptance run --real --mantle "$HOME/.local/mantle/bin/mantle" \
  --profile personal --agent codex --manifest examples/codex.yaml \
  --checkpoint "$HOME/.cache/mantle-acceptance-run/run.json"

For Claude Code use --agent claude-code with a Claude manifest and a profile whose [claude] source is configured. Add --release-manifest /absolute/path/manifest.json to bind the run to a verified bundle: the manifest, both sibling archives and the CLI’s digest must match, otherwise the run is refused. Without it, the source identity is recorded as unknown; a version string or install directory name is not proof of source.

Login, a model and tool turn and the visual check happen in your terminal through the attach command the runner prints. Use it unchanged: it runs the run’s private copy of the CLI with the resolved configuration and state paths pinned. Then submit only what you observed:

mantle-acceptance resume --real \
  --checkpoint "$HOME/.cache/mantle-acceptance-run/run.json" \
  --expected-run-id RUN_ID --expected-exec-id EXEC_ID \
  --attest login --attest model-tool --attest visual
mantle-acceptance report --checkpoint "$HOME/.cache/mantle-acceptance-run/run.json"
mantle-acceptance cleanup --real --checkpoint "$HOME/.cache/mantle-acceptance-run/run.json"

09 / Offline upgrades

Replace worker helpers during maintenance.

For administrators. Use a matching 0.3.1 CLI and a verified 0.3.1 bundle. The worker must already run the Substrate the CLI pins (0.16.1 for release 0.3.1, 0.7.10 for 0.2.0); this command does not migrate Substrate, so it cannot take a 0.2.0 worker to 0.3.1.

mantle worker upgrade replaces the three Mantle helpers and keeps worker data, configuration, Claude, Codex’s generation link and the Substrate daemon. A different Substrate version or revision is refused. It does not drain sessions and never stops, masks, unmasks, restarts or signals a service. --check also works against an older installed helper: it uses bounded read-only SSH observations and uploads nothing.

1. Prepare the worker

Take an exclusive maintenance window. Save wanted work and retire every execution. Keep other administrators and older Mantle clients away for the whole window: they do not take the new installation lock. Through mantle worker ssh:

sudo systemctl stop substrate.service mantle-egress.service
sudo install -d -m 0700 /var/lib/mantle/maintenance/units
sudo mv --no-clobber /etc/systemd/system/substrate.service /var/lib/mantle/maintenance/units/substrate.service
sudo mv --no-clobber /etc/systemd/system/mantle-egress.service /var/lib/mantle/maintenance/units/mantle-egress.service
sudo systemctl mask substrate.service mantle-egress.service
sudo systemctl daemon-reload

Run these only on first entry: inspect saved originals that already exist and never overwrite them. The originals must keep the rendered unit bytes, root:root, mode 0644 and one hard link. A plain mask cannot replace the regular unit files Mantle installs in /etc/systemd/system, and a runtime mask alone does not override them; that is why they are moved. The checker refuses missing originals, unsupported overrides, ineffective masks, queued jobs, nonzero service PIDs, populated cgroups and unknown observations; it inspects the cgroup subtree even when ControlGroup is empty.

2. Check and apply from the laptop

mantle worker upgrade --check --manifest /absolute/path/to/manifest.json
mantle worker upgrade --apply --manifest /absolute/path/to/manifest.json

The check report separates installed versions, the selected source and digests, Substrate compatibility and the maintenance prerequisites. Equal version strings alone never count as current. Apply repeats the observations under /opt/mantle/install.lock, stages a complete sibling of /opt/mantle/bin and swaps the two directories in one atomic exchange; a filesystem without exchange support is refused, and there is no file-by-file fallback. Each transferred file is at most 64 MiB and keeps its verified digest. Codex installation and helper reconciliation take the same host lock first.

3. Read the result

4. Leave maintenance

sudo systemctl unmask substrate.service mantle-egress.service
sudo systemctl unmask --runtime substrate.service mantle-egress.service
sudo mv --no-clobber /var/lib/mantle/maintenance/units/substrate.service /etc/systemd/system/substrate.service
sudo mv --no-clobber /var/lib/mantle/maintenance/units/mantle-egress.service /etc/systemd/system/mantle-egress.service
sudo systemctl daemon-reload
sudo systemctl start mantle-egress.service substrate.service

Check the restored unit files before starting, then run mantle doctor from the laptop. Installed versions do not prove the running daemon’s identity or that an agent can authenticate.

The upgrade transaction is verified by package tests and native scenarios on controlled filesystems. No release record shows it applied to a live worker.

10 / Troubleshooting

Start with the observation.

The worker is not ready

Run mantle worker status or mantle doctor. For a new worker, mantle worker up finishes bootstrap. For a worker with sessions, read worker before changing services. The CLI checks that bootstrap completed and Substrate is active before connecting.

FAILED_CAPABILITY: the worker lacks …

Substrate on the worker does not report a fact the session needs. The message names it. Mantle does not start the agent without it.

A session exists, but attach fails

mantle list shows recorded intent; mantle status NAME shows what Substrate observes. If the agent has ended, attach does not restart it: use mantle restart NAME.

A repository cannot be fetched

Check that the URL is HTTPS, the ref exists, and the host is on the allowlist. There is no Git credential broker.

A resource says NOT enforced

The CLI reports the worker’s actual facts and does not turn a request into a claim. CPU is never a bandwidth limit; storage needs the worker’s quota fact.