Operating Vita
This page is the operator's manual: how to stand Vita up, keep it running, verify changes before they ship, and extend it. Where other pages describe what a feature does, this one covers the surfaces a reimplementer touches first β the unified service manager, the verification gates, the flow orchestrator, the skill system, the agent launcher, the meta-status APIs, and the encrypted vault.
Everything routes through one entry point: the ./run script at the repo root. There are no Vita systemd units β ./run is the single source of truth for process lifecycle. Alerts about a dead service recommend ./run <svc> restart, never systemctl.
The ./run service manager
run is the repository's large Bash control plane (/home/zack/work/vita/run). It defines a service registry with a unified lifecycle interface, bundles full-stack dev commands, and exposes dev tooling (verification gates, type checks, generated contracts, lint, and metrics).
The script pins all ports at the top, so they are unambiguous:
API_PORT=33800 # internal API + scheduler
PUBLIC_API_PORT=33802 # read-only API for public tunnels, no scheduler
WEB_PORT=33801 # Vite dev server
STEPWISE_PORT=8341 # Stepwise flow server
DOCS_PORT=47447 # built MkDocs site
The unified service interface
Every registered service speaks the same five-verb interface:
./run <service> [start|stop|restart|status|log]
The registry (SERVICES_LIST in run) is the canonical list of services:
| Service | What it is | Default URL |
|---|---|---|
api |
Internal API server plus the scheduler loop engine | http://localhost:33800 |
public-api |
Read-only API for public tunnels β no scheduler | http://localhost:33802 |
web |
Vite dev server (web UI) | http://localhost:33801 |
telegram |
Telegram bot | β |
claude-daemon |
Claude daemon | β |
cadence-daemon |
Cadence daemon | β |
stepwise |
Stepwise flow server | http://localhost:8341 |
service-monitor |
Host-side health monitor (see below) | β |
podcast-tunnel |
ngrok tunnel β public podcast feed | β |
reports-tunnel |
ngrok tunnel β public reports site | β |
rss-tunnel |
ngrok tunnel β public RSS feed | β |
eu-tunnel |
ngrok tunnel β EU region | β |
docs |
MkDocs build + UTF-8 static server + docs tunnel | https://vita-docs.ham.xyz |
bp-demo |
Bikepacking partner-embed sandbox and tunnel | https://bp-test.ham.xyz |
Note: The split between
api(scheduler-bearing) andpublic-api(read-only, scheduler-less) is deliberate: only one process runs the scheduler/loop engine so jobs never double-fire, while the public tunnels are served by a process that can be exposed safely.
How dispatch works: at the bottom of run, if the first argument matches a service name, it calls service_dispatch <svc> <action>. Dispatch resolves three convention-named Bash functions per service (dashes map to underscores) and calls the right one:
start_fn="start_${svc//-/_}" # e.g. start_service_monitor
stop_fn="stop_${svc//-/_}"
status_fn="svc_status_${svc//-/_}"
startβ idempotent: most start functions check a PID file (.logs/<svc>.pid) orpgrepfirst and adopt or skip an already-running process.stopβ terminates by PID file, cleans up sockets.restartβ stop,sleep 1, start.statusβ prints one status line (HTTP health check where applicable).logβtail -fthe service's log (paths resolved by_svc_log_path; most live under.logs/, daemons under~/.local/run/vita/, stepwise under.stepwise/logs/server.log).
To register a new service you add its name to SERVICES_LIST, define start_<name> / stop_<name> / svc_status_<name>, and add a log path to _svc_log_path().
Full-stack commands
./run dev [--profile local-safe|local-daemons|full] # start the whole stack
./run stop # stop everything
./run restart [--profile ...] # stop then start
./run status # status of all services
dev/restart parse a profile that gates which optional services come up:
| Profile | API + public-api + web + telegram + stepwise | Daemons (claude, cadence) | Tunnels |
|---|---|---|---|
local-safe |
yes | no | no |
local-daemons |
yes | yes | no |
full (default) |
yes | yes | yes |
Individual toggles also exist: --with-daemons, --with-tunnels, --no-streamdock-refresh. The API start path waits up to 30s for GET /api/v1/health to return before declaring success.
Dev tooling commands
./run typecheck # mypy regression gate (api/src) + tsc (web/)
./run test # API tests only (api/tests)
./run verify-fast # fast verification gate (see below)
./run verify-standard # fast gate + full API + full root tests
./run verify-full # alias for test-all (the full local validation)
./run test-all # API + root + web build + iOS subset
./run health [full] # codebase hygiene checks
./run skills-lint # validate SKILL.md frontmatter
./run architecture-metrics # emit router/task/data-dir/skill counts
./run gen-types # generate TS types from the OpenAPI schema
./run gen-capabilities # regenerate capability Markdown + JSON contracts
./run capabilities <cmd> # list/spec/call through the shared capability layer
Verification gates
Before any non-trivial edit, run a gate. They are layered β each tier subsumes the one below it, trading speed for coverage.
| Gate | What it runs | Use when |
|---|---|---|
verify-fast |
codebase health summary, the unsafe-writes gate, web typecheck, a board-schema API smoke test, and a curated set of critical root smoke tests (session coordination, sentinel registry/loop/API, parallel sessions, aloop contract, inference) | before most edits |
verify-standard |
verify-fast + the full API test suite (api/tests) + the full root test suite (tests/) |
before something risky |
verify-full / test-all |
API tests, root tests, web build (type + bundle check), and an iOS vitaTests subset via xcodebuild when available |
before a release-grade change |
Tip: The unsafe-writes gate (
scripts/codebase/health.py --gate-unsafe-writes) fails the build if new code writes files outside the data-integrity helpers without being on the allowlist (scripts/codebase/unsafe_writes_allowlist.json). This is how the system enforces that all durable writes go throughatomic_write()/atomic_append()rather than rawopen(...).write().
The fast gate's curated test list lives inline in run (the fast_tests array) β these are the tests that catch the highest-blast-radius regressions (scheduler, sessions, sentinel) cheaply.
Health checks
./run health # summary mode
./run health full # full codebase hygiene report
health runs scripts/codebase/health.py, which spans many checks (skill frontmatter validity, unsafe-write detection, and more). It is also the gate that verify-fast invokes first, so a broken-hygiene tree fails fast.
Architecture metrics
./run architecture-metrics --summary
# routers=77 tasks=71 data_dirs=119 skills=57
scripts/codebase/architecture_metrics.py computes the system-wide counts that the docs and dashboards cite, so they are never hand-maintained:
| Metric | How it's counted |
|---|---|
routers |
FastAPI routers under api/src/routers/ |
tasks |
LoopRegistration( literals in the loop engine β the canonical scheduler-task count |
data_dirs |
top-level directories under data/ |
skills |
.claude/skills/*/ directories with a valid SKILL.md |
Warning: Never hardcode these numbers in docs or code β they drift. Read them from
architecture-metricsinstead. (Snapshot verified 2026-07-15: routers=77, tasks=71, data-dirs=119, skills=57.)
Documentation publishing
The public documentation is a registered service:
./run docs restart
./run docs status
./run docs log
start_docs() runs three stages in order:
uv run mkdocs build --cleanrendersdocs/pages/usingmkdocs.yml;scripts/docs/gen_llms.pyadds raw Markdown,llms.txt,llms-full.txt, and the generated capability contracts under/api/capabilities/; andscripts/docs/serve.pyservessite/on port 47447 with explicit UTF-8 types for text, Markdown, and JSON before the ngrok tunnel exposesvita-docs.ham.xyz.
Narrative pages are hand-authored. The files under
docs/api/capabilities/ are generated from the live capability registry and
must be refreshed with ./run gen-capabilities, not edited manually. The
public Capability Layer page explains the design; the
generated files are the exact operation contracts.
Stepwise flow orchestration
Stepwise is the multi-step workflow engine β a standalone CLI (stepwise, installed on PATH at ~/.local/bin/stepwise, never wrapped in uv run or python -m). A flow is a FLOW.yaml describing a pipeline of agent and script steps. Vita ships ~60 flows under flows/ β software-dev kits (plan / implement variants), research, council/tribunal deliberation, podcast pipelines, the executive loop, the capacity dispatch family, code review, docs audit, and more.
Note: Stepwise itself is a separate repository (
~/work/stepwise), but it is always driven from~/work/vitaβ flows live invita/flows/, the job DB and runtime state live invita/.stepwise/. Nevercdto the stepwise repo to run a flow.
Running flows
stepwise agent-help # source-of-truth flow + CLI reference
stepwise run flows/<flow> --name "verb: noun" --wait # run a flow
./run stepwise [start|stop|restart|status|log] # manage the flow server
Operating conventions (enforced project-wide):
- Always run from
~/work/vita. - Always pass
--nameso jobs get human-readable labels in the DB. - Always use
--waittogether with a backgrounded bash call.--waitblocks until completion and prints a JSON result; running it backgrounded means you get notified on completion/failure without polling. Synchronous mode (no--wait) spams the terminal;--asyncthen forgetting to check strands the job. - Never run two
stepwise runcalls for the same flow β that spawns duplicate agents and burns tokens.
Kits group related flows; stepwise agent-help <kit> shows the flows inside one. Run agent-help once per session β it is the always-current reference for flows, their inputs/outputs, and the CLI, so you never need to read raw FLOW.yaml files.
State, the DB, and recovery
Runtime state lives entirely under .stepwise/:
| Path | Holds |
|---|---|
.stepwise/stepwise.db |
the job database (jobs, steps, schedule ticks, step runs) |
.stepwise/jobs/, .stepwise/step-io/ |
per-job working files and step I/O |
.stepwise/logs/server.log |
the flow server log |
.stepwise/snapshots/ |
the integrity-gated snapshot ring (last 10 kept) |
.stepwise/registry/, .stepwise/templates/ |
flow registry + templates |
Stepwise has a history of a DB-corruption outage class (btrfs copy-on-write + WAL mmap producing "database disk image is malformed"). Two antibodies were added:
- A snapshot ring.
scripts/stepwise_snapshot_ring.pyruns at flow boot (nightly sleep + executive-loop boots), doingVACUUM INTO .stepwise/snapshots/stepwise-*.db, keeping the last 10, integrity-checked. - A recovery command.
./run stepwise recoverstops the server, quarantines the corrupt DB, restores the newest snapshot that passesPRAGMA integrity_check, and restarts.
./run stepwise recover --dry-run # preview: live-db integrity, chosen snapshot, quarantine name
./run stepwise recover # do it
./run stepwise recover --force # override safety guards (e.g. live DB looks healthy)
The stepwise start path also pre-checks DB integrity and warns if .stepwise/ is not marked NoCOW (+C) on btrfs.
See Research and Deliberation for flows built on this engine.
The skill system
A skill is the unit of agent capability and slash command. Each lives in .claude/skills/<name>/ as a SKILL.md with YAML frontmatter, optionally plus supporting scripts and references. Use ./run architecture-metrics --summary for the live count (57 valid skills in the 2026-07-15 snapshot).
Minimal frontmatter:
---
name: my-skill
description: One line that tells the agent when to auto-apply this skill.
---
# My Skill
...instructions, references, examples...
skills-lint
./run skills-lint # human-readable
./run skills-lint --json # machine-readable {issues, count}
scripts/skills/lint.py delegates to check_skill_frontmatter() in scripts/codebase/health.py, which enforces:
| Rule | Failure |
|---|---|
| frontmatter is present | "missing opening/closing frontmatter delimiter" |
name and description keys exist |
"missing required keys" |
name equals the folder name |
"name 'X' does not match folder 'Y'" |
name is unique |
"duplicate skill name" |
The same check runs inside ./run health, so broken skill metadata fails the hygiene gate too. The exit code is non-zero when any issue is found, which makes skills-lint usable as a CI gate.
Tip: Because the
namemust match the folder and be unique, the skill registry is collision-free by construction β the slash command/my-skillmaps deterministically to one directory.
High-stakes builds with oneshot
The opt-in oneshot skill is Vita's expensive, evidence-first build protocol
for work explicitly framed as graded, high-blast-radius, or βkitchen sink.β It
was reorganized in July 2026 around four phases:
- plan β define success at the appropriate altitude and enumerate a keep/change/drop scope map;
- work β implement the settled plan using existing patterns;
- verify β fresh-context adversarial code review, real interface exercise, screenshot review, and a completeness critic, looping back on real findings;
- present β build a reviewable evidence surface and a cold-reader handoff.
The main agent remains the judge and delegates exploration and independent
verification. UI work is not complete without exercising the real running
surface; non-UI work uses the strongest primary evidence available. Evidence is
routed by audience: RWGPS work uses an org-visible Cadence plat, while Vita and
other personal work use a Vita canvas. The skill follows each repository's
actual version-control conventionβVita itself remains on local master.
The agent launcher
./run agent launches a named Claude-Code agent identity β a different persona/config/toolset than the default session. Identities are JSON files in agents/ (developer, planner, ideator, telegram, brief-developer, user-terminal, with base.json as the shared parent and _schema.json validating them).
./run agent --list # list launchable agents with descriptions
./run agent --list-all # include non-launchable agents
./run agent <name> # launch an agent identity
./run agent --validate # validate all agent configs
./run agent --show <name> # print resolved config without launching
./run agent --reset # clear lock + restore default symlink
Internally (scripts/agents/launcher.py) a launch:
- Ensures the symlink structure exists β on first run it renames the real
.claude/directory to.claude.default/and replaces.claudewith a symlink pointing at it. - Acquires a lock (
.claude.lock) so two agents can't claim the workspace at once. - Resolves the agent's config chain (
base.jsonβ agent) and merges it. - Generates the agent's context directory atomically.
- Switches the
.claudesymlink to point at the chosen agent's dir (relative path), thenexecsclaude. - On exit (or
--reset), restores the.claudesymlink back to.claude.default.
This symlink-switching is the isolation mechanism: each agent gets its own .claude contents (settings, skills, context) for the duration of the run without touching the others.
Note: These launcher agents are distinct from the board personas in
agents/board/(Aurelius, Dalio, da Vinci, Jobs, Musk, Rogers, Roosevelt) β those are advisory voices consumed by the Board flows, not launchable Claude-Code sessions.
Meta-status APIs
Two read endpoints give an at-a-glance view of the running system.
System-kernel overview
GET /api/v1/system-kernel/overview
api/src/routers/system_kernel.py returns a single snapshot spanning three subsystems:
| Section | Source | Contents |
|---|---|---|
project_registry |
scripts/projects/registry.py |
project counts + a few highlighted slugs |
loop_engine |
api/src/loop_engine (group_counts, list_loop_registrations) |
scheduler group counts + highlighted loops (e.g. sentinel, vdb_daily) |
session_kernel |
api/src/session_kernel.py |
the currently active voice session, if any |
This is the consolidated meta-status surface: storage, loops, and sessions in one call. It rides on the loop-engine refactor that made LoopRegistration the canonical scheduler-task source β see Scheduling and Executive.
Service monitor
A host-side background service (scripts/service_monitor/host_runner.py, started by ./run dev) health-checks Vita services every 5 minutes and persists results:
| File | Holds |
|---|---|
data/services/monitor_state.json |
current per-service state |
data/services/monitor_history.jsonl |
append-only check history |
It is exposed via api/src/routers/service_monitor.py:
GET /api/v1/services/monitors # all monitors + current state
GET /api/v1/services/state # current state snapshot
POST /api/v1/services/check # trigger a check
It runs from the host (not Docker) so it can do socket/process checks containers can't. Manage it like any other service: ./run service-monitor [start|stop|status|log]. When it reports a dead service, the remediation is always ./run <svc> restart.
The encrypted vault
data/vault/ stores sensitive personal data that should never exist in plaintext on disk. There are no helper scripts β it is operated with raw openssl per data/vault/README.md.
Layout:
| File | Role |
|---|---|
manifest.enc |
encrypted index of all entries (descriptions, tags, metadata) |
<uuid>.enc |
one encrypted entry per file, named by UUID |
Encryption: AES-256-CBC with PBKDF2 key derivation (100,000 iterations) via openssl enc. The passphrase exists only in the operator's memory and is supplied per session β never stored on disk.
# Decrypt the manifest to see what's in the vault (in-memory only)
openssl enc -d -aes-256-cbc -salt -pbkdf2 -iter 100000 \
-in data/vault/manifest.enc -pass pass:"PLACEHOLDER_PASSPHRASE"
# Decrypt one entry
openssl enc -d -aes-256-cbc -salt -pbkdf2 -iter 100000 \
-in data/vault/<ENTRY_UUID>.enc -pass pass:"PLACEHOLDER_PASSPHRASE"
Warning: Never write decrypted content to disk β decrypt into memory/context only. When adding an entry, write plaintext to
/tmp, encrypt todata/vault/<uuid>.enc, update the manifest, thenshred -uthe plaintext.
Threat model: an attacker with machine access sees only N opaque .enc files and the README β no content, no categories, no metadata, because the manifest itself is encrypted. The README explicitly forbids speculating about contents from the existence of entries; this page documents the mechanism only.
File Locations
| Path | What it is |
|---|---|
run |
the unified service manager + dev tooling entry point |
scripts/codebase/health.py |
codebase hygiene checks, skill frontmatter, unsafe-writes gate |
scripts/codebase/architecture_metrics.py |
system-wide router/task/data-dir/skill counts |
scripts/skills/lint.py |
skills-lint entry point |
.claude/skills/<name>/SKILL.md |
a skill definition |
scripts/agents/launcher.py |
agent launcher (symlink-switching, locking) |
agents/*.json, agents/board/*.json |
agent identities and board personas |
flows/<flow>/FLOW.yaml |
a Stepwise flow definition |
.stepwise/ |
Stepwise job DB, snapshots, logs, runtime state |
scripts/stepwise_recover.py, scripts/stepwise_snapshot_ring.py |
recovery + snapshot ring |
api/src/routers/system_kernel.py |
/api/v1/system-kernel/overview |
scripts/service_monitor/host_runner.py, api/src/routers/service_monitor.py |
service monitor + its API |
data/vault/, data/vault/README.md |
encrypted personal data + its operating manual |
.logs/ |
per-service PID files and logs |
Where to go next
- Getting Started β the daily slash-command workflow.
- Architecture β the system-wide component map.
- Scheduling and Executive β the loop engine behind the scheduler-task count.
- Research, Deliberation, Board β flows built on Stepwise.
- Model Guidance β model routing (auto-maintained; frontier models are
claude-opus-4-8andclaude-fable-5).