Skip to content

Operator Guide: The tp Host CLI ​

Audience: Federated and standalone registry operators (self-hosted), and frame operators (--frame) Prerequisite: A running operator stack and its .env.operator, or a frame stack directory Last Updated: 2026-09-14


1. What It Is ​

Your operator bundle ships a single-binary day-2 operations CLI alongside the compose files -- no Python, no dependencies, linux/amd64 and linux/arm64 builds. It automates the routine of running a registry from the host: health checks, log tailing, restarts, license management, agent SPIRE enrollment, backups, restores, and bundle updates.

Two entry points, one command surface:

  • tp -- the umbrella binary. Day-2 ops live under tp operator <command>; it also exposes tp mcp (an MCP server mode, section 8) and reserved tp developer / tp admin personas (currently stubs).
  • tp-operator -- the standalone alias kept for compatibility. tp operator status and tp-operator status route to the same code.

Install whichever matches your architecture:

bash
case "$(uname -m)" in
  x86_64|amd64)  install -m 0755 tp-operator       /usr/local/bin/tp-operator ;;
  aarch64|arm64) install -m 0755 tp-operator-arm64 /usr/local/bin/tp-operator ;;
esac
tp-operator version

The CLI is day-2 only. First-run bootstrap is still ./operator-setup.sh -- the CLI assumes an .env.operator already exists.


2. Auth Model ​

One primitive, one secret. The CLI reads FEDERATION_LICENSE_KEY from your .env.operator and presents it as X-Federation-License: tp_fed_... on every call to your parent frame. No developer JWT, no password prompts, no shared internal secrets.

The only exception is the bundle command group (section 7), which manages agent bundles on the registry API and therefore authenticates with a developer API key (--api-key).


3. Local Stack Operations ​

CommandWhat it does
status [--detail]Aggregate stack health snapshot (compose ps + a green/yellow/red verdict). --detail adds start time and restart counts.
health [--strict]Active HTTP probe of your parent frame's /health. --strict also probes the federation and EventStore endpoints.
logs <service> [--tail N] [-f] [--since ...]Tail one service's logs. Services: registry, teg-layer, db, teg-db, redis, pgbouncer, nginx-federation, spire-agent, cert-writer.
restart [<service>] [--apply-env-changes] [--no-wait]Restart one service, or the whole stack if omitted. --apply-env-changes force-recreates so edited env vars take effect.
start / stopBring the stack up (docker compose up -d) / down (docker compose down).
doctorFull operator-side diagnostic -- Docker daemon, free disk, services running, license present, parent-frame reachability. Paste its output into support tickets. With --frame, the read-only frame checks of section 6a.
validate-config [--proposed PATH]Pre-flight schema and coupling checks on .env.operator -- validate a proposed file before you apply it.
versionPrint CLI + schema versions.

4. Federation Operations ​

CommandWhat it does
peersFederation network stats and per-peer status from your parent frame.
syncTrigger a federation sync cycle now (calls /federation/sync) instead of waiting for the background worker.
license showPrint the local license summary from .env.operator.
license verifyProbe the parent frame -- is your federation license still active?
license rotate --reason <reason>Self-serve license key rotation. Reasons: leak_suspected, scheduled_rotation, post_incident. The CLI updates .env.operator with the new key.

5. Agent mTLS (spire) ​

Manage your agents' SPIRE entries (the IRONHAND agent-identity surface) without touching the SPIRE server directly:

CommandWhat it does
spire listList this operator's enrolled agents.
spire enroll <agent-did> [--parent-fingerprint ...]Register a SPIRE entry for an agent (defaults to OPERATOR_SPIRE_AGENT_FINGERPRINT from .env.operator).
spire revoke <agent-did>Delete an agent's SPIRE entry.

6. Disaster Recovery & Updates ​

CommandWhat it does
backup [--out PATH]Atomic backup: pg_dump of the registry and TEG databases + configs + a manifest, in one archive. That is a federated operator's shape, whose ledger lives at its parent frame: a standalone stack's own event store database (es-db) is not in this archive, so dump it separately. With --frame, see section 6a.
restore <archive>Atomic restore from a backup archive, with safety nets: a pre-restore snapshot first, and single-operator only (cross-operator restore requires an explicit --force-cross-operator). It restores .env.operator and the configs along with the databases; --rebuild-volumes and --skip-env-restore are accepted but currently have no effect. With --frame, see section 6a.
update [--check-only|--apply] [--to-version X] [--allow-major] [--no-auto-rollback]Check for (default) or apply a new operator bundle. Applies run a post-apply health check and auto-roll back on failure unless disabled.

Destructive commands ask for confirmation; pass the global --yes (before the command) in scripts.

6a. Frames (--frame <stack dir>) ​

--frame points a command at a frame instead of an operator: a stack directory created by the frame standup tooling, holding .env.frame-test, docker-compose.frame-test.yml and the standup's run record. A directory missing any of the three is refused with a sentence naming what is missing. It is a global flag, so it goes before the command: tp-operator --frame /opt/frames/my-frame doctor.

backup writes one archive with mode 600, because it carries the frame's secrets:

  • the stack directory (backup and temporary files skipped);
  • three pg_dump -Fc dumps: the registry, the TEG and the event store, which is the frame's ledger;
  • the two named volumes that hold the frame's identity: the registry's signing key and the SPIRE server's data (a new CA would be a new trust root for every federated peer);
  • MANIFEST.json, with a SHA-256 for every entry and each database's migration head.

A frame whose env names fewer than three databases, or whose compose names neither volume, is refused rather than half backed up; one missing volume is backed up with a warning naming the state that is not in the archive. --include-volumes is implied for frames.

restore <archive> replaces the frame's three databases and two volumes, and refuses before touching anything when the archive does not fit:

  1. verifies every SHA-256 in the manifest, and that the archive is a frame backup;
  2. refuses an archive whose trust domain is not this frame's, unless --force-cross-frame;
  3. refuses a frame whose standup declared it production, unless --i-mean-production;
  4. takes a pre-restore snapshot of the frame as it is;
  5. stops the registry, TEG, event store, pgbouncer, cert-writer, SPIRE agent and SPIRE server;
  6. restores the event store first, then the TEG, then the registry;
  7. replaces the two volumes, starts the stack and waits for the registry's /health.

It asks you to type CONFIRM-RESTORE unless the global --yes is set.

doctor runs read-only checks and gives each row a verdict: ok, warn or fail, or not-measured when the probe could not decide (a not-measured row is never a pass). The run's verdict is fail if any row fails, else warn if any row warns, else ok. The command exits 0 either way, so scripts read .verdict from --json.

RowWhat it asks
docker-daemon, disk-free-gb, services-runningthe host and the stack (less than 60 GB free warns)
posturethe frame's declared environment and trust domain
registry-health, public-rails/health, and five public reads that must answer 200
card-versionthe signed registry card's version against the version inside the running image
policy-hashthe card's policy hash against the compliance endpoint's (it may sign in with the frame admin credential named in the stack's env to read it)
limiter-posturethe rate limit an outside caller meets: live (1200) on a production frame, bypassed on a pilot frame
xff-appended-elementthat the registry logs the address the proxy appends to X-Forwarded-For, not the one the caller wrote
supply-deltathe event store's supply audit: delta 0 and status OK
auditor-verdictthe independent auditor's verdict for this frame (TP_AUDITOR_URL, default on loopback)
replicationcross-frame peers and foreign events in the ledger (none: standalone, or not yet federated)
scrape-targetsthat Prometheus scrapes the frame's registry, TEG and event store (TP_PROMETHEUS_URL, default on loopback)
status-historythat the public status page has a metrics job and known days for every own component
bundle-rootshow many trust roots the registry's SVID bundle holds (the watcher's first poll comes 30 minutes after boot; earlier reads say not-measured)
observability-envthat PROMETHEUS_URL and VICTORIA_METRICS_URL are set

7. Agent Bundles (bundle) ​

CLI surface for the signed .tpb agent-bundle feature -- authenticated with a developer API key, not the federation license:

bundle list [--kind snapshot|template|migration] [--no-official]
bundle create --agent-did <did> [...] [--kind ...] [--visibility ...] [--out file.tpb]
bundle download <bundle-id> [--out file.tpb]
bundle restore <file.tpb> [--conflict skip|rename|overwrite] [--mode copy|mirror]
bundle verify <file.tpb>
bundle quota <developer-id>        # admin-only

8. MCP Server Mode ​

tp mcp serve runs a stdio MCP server that exposes the CLI verbs as MCP tools, so an MCP client (e.g. Claude Code) can drive your host operations -- status, logs, restart, backup, spire enrollment, license checks -- through your .mcp.json. tp mcp tools lists the tools it would expose; tp mcp version prints protocol info. tp_backup and tp_restore also take a frame argument (a frame stack directory) for the frame backup and restore of section 6a.


9. Global Flags ​

Every command honors these global flags. They belong to tp-operator itself, so they go before the command (tp-operator --json status, not tp-operator status --json):

--operator NAME       pick an operator on a multi-operator host
--frame PATH          a frame stack directory instead of an operator (section 6a)
--all-operators       apply across every discovered operator (bulk read/write)
--config PATH         explicit .env.operator path
--json                machine-readable JSON (for CI)
--quiet               errors only (cron-friendly)
--yes / -y            skip interactive confirms
--dry-run             print the intended action, execute nothing
--no-color            disable rich formatting
--verbose / -v        more output (repeatable)
--timeout SECONDS     HTTP timeout (default 30)

Tab completion: tp-operator --show-completion bash > /etc/bash_completion.d/tp-operator (zsh supported too).


10. Exit Codes ​

CodeMeaning
0Success
1User error (bad args, validation fail)
2System error (uncaught exception)
3Network error
4Auth error
5Docker unreachable
6docker-compose error
7Parent frame unreachable
8Partial success (multi-operator)
130Interrupted (Ctrl-C)

11. Where tp comes from, and the runner beside it ​

tp ships in the source release at tools/operator-cli/ (tp_cli.py, tp_operator_cli.py, a pyproject.toml and the PyInstaller specs that produce the two binaries). Beside it, tools/frame-runner/ is the host service Frame Management provisions frames through: a root-owned unix socket, a closed op set, and a policy file (frame-runner.json.example ships; the live one is /etc/theprotocol/frame-runner.json) whose images block allowlists, by tag, every image a runner-built frame may use: registry, teg, event_store, auditor and directory. A request that names no event-store, auditor or Directory image gets the first allowlisted entry; a :latest tag is never an entry, because a tag is a name and an allowlist entry is a decision.

12. Cron One-Liners ​

bash
# Daily 03:00 backup
0 3 * * * tp-operator --yes --quiet backup --out /opt/backups/$(date +\%Y\%m\%d).tar.gz

# Daily 03:30 backup of a frame (the archive is written with mode 600)
30 3 * * * tp-operator --frame /opt/frames/my-frame --yes --quiet backup --out /opt/backups/frame-$(date +\%Y\%m\%d).tar.gz

# Every 5 min: alert when the stack is not green
*/5 * * * * tp-operator --json --quiet status | jq -e '.verdict == "green"' || /usr/local/bin/alert.sh

# Weekly: check for bundle updates
0 9 * * 1 tp-operator --json update --check-only | jq -e '.update_available' && /usr/local/bin/notify.sh

Server components AGPL-v3 · client SDK Apache-2.0. If a doc and the running stack disagree, trust the stack. Legal notice (Impressum) · Privacy · Terms