12 KiB
thothctl pi lifecycle contract
thothctl is the only component that drives Docker lifecycle operations. The core container
does not mount a Docker socket, and Pi is never updated in a running container.
Inspection and configuration
thothctl --installation /absolute/path/thothii-installation.yaml pi status
thothctl --installation /absolute/path/thothii-installation.yaml pi doctor
thothctl --installation /absolute/path/thothii-installation.yaml pi test
thothctl --installation /absolute/path/thothii-installation.yaml pi logs
thothctl --installation /absolute/path/thothii-installation.yaml pi configure
status executes the image-bundled pi --version. doctor compares that value with both the
container's PI_VERSION contract and the io.thothii.pi.version image label; a merely nonempty
version is not sufficient. doctor and test also require a healthy core, a successful Pi smoke,
valid settings, and an exact selected provider/model pair from the backend's available model
entries. pi check remains an alias for pi test. Logs are always a bounded, sanitized 200-line
snapshot; there is no follow mode.
On a TTY, pi configure presents numbered provider, model, and thinking choices. Providers and
models come from the backend's closed model list, and the model choices are restricted to the
selected provider. In non-interactive use, all choices must be explicit:
thothctl --installation /absolute/path/thothii-installation.yaml pi configure \
--provider zai --model glm-5.2 --thinking medium
The helper snapshots exact settings-file existence and raw bytes, applies the new values atomically,
and verifies the readback and rendered-configuration digest. A helper, readback, or digest failure
restores those exact bytes when the prior file existed; on a clean installation it removes the new
file and verifies the absent/default state. Empty prior files are supported. The command reports
the actual host path from PI_AUTH_FILE; credentials remain in that protected host file and must
never be passed as flags.
Installation-managed Pi provider/model configuration is declarative only. Any JSON value beginning
with ! is rejected recursively in the complete models.json before it can supply management
choices, and the exact selected provider/model and credential payload is checked again before the
isolated smoke files are written. The API returns only the fixed
Pi provider/model configuration is invalid message; rejected commands, paths, and secrets are
never included. Use $NAME/${NAME} environment references in models.json, or omit apiKey and
provide the selected credential through the protected PI_AUTH_FILE, THT_MODEL_API_KEY_FILE, or
THT_SECRETS_FILE contract. A literal leading exclamation mark uses Pi's $! escape. Direct
secret-file references are not a models.json feature: ThothII converts its managed key source to
the provider-native child environment, while PI_AUTH_FILE is mounted as Pi's protected credential
store.
Supported Compose entry points and current image
Use thothctl start, stop, status, logs, and doctor for ordinary installation lifecycle
operations. All thothctl Compose commands automatically include the installation-specific
durable selector when it exists:
<projectDirectory>/.thothctl/<installation-id>/current-image.yaml
This selector is part of the supported installation state: it keeps a verified Pi image selected
across a fresh thothctl process, stop/start, reconcile, and source checkout whose base image is
digest-pinned. Do not delete or hand-edit it. Direct raw docker compose lifecycle commands bypass
this protection and are unsupported. Advanced documented Compose rendering must use
scripts/compose-with-preflight.sh and include the same selector with -f when present; connector
secret overrides must never bypass that preflight wrapper.
Reloading Pi configuration
Configuration reload is a separate lifecycle operation from an image update:
thothctl --installation /absolute/path/thothii-installation.yaml pi restart --yes [--drain]
--yes is required after reviewing the planned core recreation. Restart activates the durable
maintenance gate before it checks sessions. Without --drain, active open sessions refuse the
command. With --drain, the command polls the authenticated session inventory until no active
sessions remain; it never terminates sessions and the wait is bounded.
Restart retains the exact current image and does not build, pull, select, tag, or upgrade an image.
It recreates only core with --no-deps --force-recreate; frontend and named volumes are not
recreated. Before reopening admission, it verifies health, the unchanged Pi version, the
provider/model/settings smoke, unchanged non-secret rendered configuration, the current image
identity, and the complete persistence-mount fingerprint.
Restart and update keep separate recovery state:
<projectDirectory>/.thothctl/<installation-id>/restart-state.json
<projectDirectory>/.thothctl/<installation-id>/update-state.json
The files are mode 0600 and share one installation lifecycle lock, so a restart cannot race an
update. A restart refuses an incomplete update or restart state. After core mutation, a failure
leaves admission gated and preserves restart-state.json; the operator must use status/logs and
maintenance recovery rather than deleting state files.
Updating Pi
Every update requires a pinned version, an explicit source, and confirmation:
thothctl --installation /absolute/path/thothii-installation.yaml pi update \
--version 0.81.0 --source build --yes
thothctl --installation /absolute/path/thothii-installation.yaml pi update \
--version 0.81.0 --source pull \
--image registry.example.invalid/thothii-core@sha256:<64-lowercase-hex-digits> --yes
--source build rebuilds only core with PI_VERSION=<version>. --source pull requires an
immutable digest reference; mutable tags, URL forms, and credential-bearing references are
rejected. --source is never inferred.
Before inventory, update activates the durable maintenance gate. Activation writes
/data/settings/maintenance.json in the mounted settings volume, closes admission, and waits for
all leases. A recreated candidate reads that marker at startup and therefore starts gated. The
loopback-only control endpoints cannot be reached through the frontend proxy and do not depend on
the configured authentication principal mode. Lost activation/deactivation responses are resolved
by querying gate status only when the original result is unknown. An explicit file or directory
durability failure is never converted to success by matching readback: the control API reports
maintenance_durability_failed, keeps or restores the safest durable marker state, and requires
recovery.
Open, unarchived sessions stop an update. After an operator has completed or otherwise drained
their work, --drain makes the command poll the authenticated bare-array
GET /sessions?scope=all response until no active sessions remain.
The configured core.image is never retagged or mutated. Each installation transaction creates
unique candidate and previous tags, including when two installations share a configured tag or
the configured image is digest-pinned. A temporary lifecycle-only Compose override selects those
tags for build, recreate, and rollback. Candidate build, pull, or candidate-tag failures happen
before mutation_started and therefore never recreate or roll back core. After verification, the
temporary candidate selector is atomically promoted to the durable current-image override.
Rollback atomically promotes the previous selector. Terminal cleanup removes only transaction
files and never deletes the durable selector.
Only core is recreated, with --no-deps --force-recreate; frontend is not recreated and no
volume-replacement flags are used. Verification checks health; exact requested Pi version at all
three declared boundaries (the candidate executable, PI_VERSION environment, and
org.opencontainers.image.version image label); the provider/model/settings smoke; unchanged
non-secret rendered configuration; and the complete persistence-mount fingerprint.
Recovery, rollback, and maintenance cleanup
Recovery state and lock diagnostics live under:
<projectDirectory>/.thothctl/<installation-id>/update-state.json
<projectDirectory>/.thothctl/<installation-id>/restart-state.json
<projectDirectory>/.thothctl/<installation-id>/*.lock.owner.json
Each recovery file is mode 0600. Update state records transaction-scoped image identities, mount
fingerprints, target version/source, configuration digest, phase, and timestamp; restart state
records the retained image and its verification inputs. Neither file contains credentials, endpoint
values, secret paths, Compose output, or logs. A cross-platform OS advisory file lock serializes
both lifecycle operations; a crashed owner releases the lock automatically. Owner metadata is
diagnostic only and cannot wedge acquisition if empty, partial, or stale.
Any post-candidate failure explicitly confirms or reactivates maintenance and rescans sessions
before compensation. Automatic rollback selects the transaction's previous image through the
lifecycle override and clears maintenance only after the previous image, configuration, mounts,
health, Pi smoke, and terminal recovery write are verified. Ambiguous compensation remains gated.
If the candidate core is stopped and cannot serve the maintenance endpoint, rollback proves that
state with Compose and writes the marker through a one-off previous-image core container sharing
the settings volume. It does not require the failed candidate, a host Node runtime, or the Docker
socket inside a container. The restored core is then recreated, verified, and rescanned before the
gate can open.
For a failed update with update-state.json, first run:
thothctl --installation /absolute/path/thothii-installation.yaml pi rollback --yes
Rollback operates on update state only. A failed restart with restart-state.json retains its
current image and has no candidate image to roll back; first inspect status and logs, repair the
reported problem, then use maintenance recovery.
Inspect and clean a stale durable gate with:
thothctl --installation /absolute/path/thothii-installation.yaml pi maintenance status
thothctl --installation /absolute/path/thothii-installation.yaml pi maintenance recover --yes
maintenance recover verifies and removes an interrupted restart state before it processes update
state. It completes an interrupted verified-image promotion, safely finalizes a preparation
interrupted before core mutation, and refuses other pending mutations. For terminal or absent
recovery state, it removes only a stale transaction override, verifies the running installation
when the gate is active, and only then removes the durable marker and reopens admission. It never
removes current-image.yaml. If rollback or recovery fails, leave the marker in place, preserve
the relevant recovery state, repair the reported Docker/configuration issue, and rerun rollback or
maintenance recovery.
Missing confirmation, invalid arguments, active sessions, and an interrupted transaction exit
2. Docker and verification failures exit nonzero with concise, redacted guidance. Direct
read-only/log commands preserve the original Docker child exit code.
Go dependency security boundary
The supported toolchain is Go 1.26.5, released 2026-07-07, with module language version
1.26.0. The Docker builder is pinned by both patch tag and the multi-platform manifest-list
digest:
golang:1.26.5-bookworm@sha256:1ecb7edf62a0408027bd5729dfd6b1b8766e578e8df93995b225dfd0944eb651
That manifest provides both linux/amd64 and linux/arm64/v8 builders. Go's official release
history is the authority for the patch level (https://go.dev/doc/devel/release); the Docker
Official Image is the authority for the builder (https://hub.docker.com/_/golang).
golang.org/x/sys, used by the Windows durable-replace implementation, is pinned to v0.47.0.
The directly used github.com/sirupsen/logrus is pinned to v1.9.1, which removes
GO-2025-4188 from the imported package set. The build contract verifies the exact toolchain,
dependencies, digest, and all five supported target builds (Windows amd64, Darwin amd64/arm64, and
Linux amd64/arm64). go mod verify, tests including the race detector, go vet, and
govulncheck are release gates.