Skip to content

CLI Reference

The wh CLI is the primary control plane for Wheelhouse — used by human operators and agents alike.

FlagDescription
--format jsonOutput as JSON (all commands)
--no-colorStrip ANSI colors; Unicode symbols preserved
--helpShow help (works offline)
CodeMeaning
0Success
1Error
2Plan change detected (wh topology plan)
CommandDescription
wh topology lint <file>Validate .wh syntax
wh topology plan <file>Preview topology changes
wh topology apply <file>Apply a topology
wh topology destroy <file>Destroy a topology
wh psList running components (agents + surfaces)
wh logs <agent>Stream agent logs
wh statusTopology health summary
wh stream create <name>Create a stream
wh stream listList streams
wh stream delete <name>Delete a stream
wh stream tail <name>Live stream of objects
wh surface cli --stream <name>Interactive CLI surface (PUB/SUB to broker)
wh surface restart <name>Kill and respawn a surface with its original env
wh surface stop <name>Stop a surface without respawning it
wh secrets initInitialize credential wizard
wh memoryShow agent memory (MEMORY.md)
wh compactTrigger stream compaction
wh doctorCheck topology and git health
wh completion <shell>Generate shell completion
NAME STATUS STREAM PROVIDER UPTIME
──────────────────────────────────────────────────────────
researcher running main podman 2d 14h
summarizer running main podman 2d 14h
! watcher stopped alerts podman —
3 agents · 2 running · 1 stopped

! prefix + column highlight mark a stopped or degraded agent. Column structure is frozen — builds muscle memory.

With --format json:

{
"components": [
{ "name": "researcher", "status": "running", "stream": "main", "provider": "podman", "uptime": "2d 14h" }
],
"summary": { "total": 3, "running": 2, "stopped": 1 }
}
Changes to apply:
+ agent researcher (new) podman · claude-3-5-sonnet
~ agent summarizer (update) replicas: 1 → 2
! stream legacy-alerts (destroy)
1 to create · 1 to update · 1 to destroy
Run 'wh topology apply topology.wh' to apply these changes.

Prefix legend: + create · ~ update · ! destroy.

With --format json, the response includes has_changes: bool as a top-level field for agent-readable consumption:

{
"has_changes": true,
"changes": [
{ "op": "create", "kind": "agent", "name": "researcher" },
{ "op": "update", "kind": "agent", "name": "summarizer", "diff": { "replicas": [1, 2] } },
{ "op": "destroy", "kind": "stream", "name": "legacy-alerts" }
]
}

Lint errors use compiler-style format — file:line: field 'X' — reason:

topology.wh:14: field 'max_replicas' is required — prevents unconstrained autonomous scaling
topology.wh:8: field 'retention' — expected duration string (e.g. "30d"), got integer

Each line: [ISO8601] [TypeName] [publisher] content

[2026-03-12T10:00:01Z] [TextMessage] [donna] Hello — I'm ready.
[2026-03-12T10:00:03Z] [SkillInvocation] [researcher] {"skill":"summarize","query":"..."}
[2026-03-12T10:00:04Z] [SkillResult] [summarize] {"result":"Summary: ..."}

Filter by type with --filter type=<TypeName>:

Terminal window
wh stream tail main --filter type=TextMessage

Content is truncated at 120 characters. Use --verbose to disable truncation.

--format json is a first-class output contract, not an afterthought. Every command’s JSON schema is stable — breaking changes require a major version bump.

Designed for agent consumption: wh topology plan --format json gives agents a structured decision context. wh ps --format json gives monitoring pipelines machine-parseable status.

Terminal window
# Check if topology has pending changes (agent use)
wh topology plan topology.wh --format json | jq '.has_changes'
# Get running agent names
wh ps --format json | jq '[.components[] | select(.status == "running") | .name]'

Connect to a stream as an interactive terminal surface. Probes broker liveness before connecting; reconnects automatically on transient failures.

Terminal window
wh surface cli --stream main
wh surface cli --stream main --format json
Connected to stream 'main'. Type a message and press Enter. Ctrl+C to quit.
> Hello
[donna] Hi! How can I help?

With --format json, each incoming message is printed as a JSON object:

{ "publisher": "donna", "type": "TextMessage", "content": "Hi! How can I help?" }

Endpoint env vars (defaults match broker defaults):

Env varDescription
WH_PUB_ENDPOINTOverride broker PUB socket address
WH_SUB_ENDPOINTOverride broker SUB socket address
WH_CONTROL_ENDPOINTOverride broker control socket (used for liveness probe)

Manage the lifecycle of deployed surface processes without a full wh topology apply cycle.

Terminal window
cd ~/my-agents # must be run from the topology directory
wh surface restart telegram # kill + respawn with the same env vars
wh surface stop telegram # kill without respawning

restart re-reads env vars from the running process (ps eww) so secrets and routing config are preserved automatically. If the surface isn’t running, it starts fresh.

stop kills the process and removes the PID file. Use this before a binary upgrade or to take a surface offline deliberately.

Both commands error if the surface name is not found in the committed topology:

Error: surface 'unknown' not found in deployed topology
Terminal window
wh completion bash >> ~/.bashrc
wh completion zsh >> ~/.zshrc
wh completion fish > ~/.config/fish/completions/wh.fish