Imported from nbiish/tts-mcp (
.agents/skills/wtf-agent-hub/SKILL.md). Install upstream withnpx skills add nbiish/tts-mcp --skill wtf-agent-hub. Copyright stays with the author.
name: wtf-agent-hub description: Connect any agent, on any machine or harness, to the wtf multi-agent observability hub. Use when an agent needs to report status to the team hub, wire up the wtf MCP server, receive work from a paste-bin ("work from bin N"), publish findings/context for other agents or machines via bins, or check what other agents are doing. Covers env/PQC credential delivery, MCP registration, reporting etiquette, bin-based cross-agent collaboration, and the v0.14.0 executor (chat_run: per-chat tmux sessions running the omp-hermes-fcc-claude fallback chain) plus the dashboard SESSIONS card.
wtf-agent-hub — connect any agent to the team hub
wtf is a zero-dependency Rust hub (wtf serve) + MCP stdio bridge
(wtf agent). The hub is the shared truth: agent status, events, three
persistent paste-bins, and the federated repo chats (dashboard SESSIONS card —
click a chat to open its viewer; tasks dispatched there run via the executor). Any MCP-speaking agent — Claude Desktop, Cursor,
Warp, Codex, CI bots, custom harnesses — connects the same way. Full docs:
the wtf-is-going-on-mcp repo README and its .agents/skills/wtf-observability
skill (that repo's own operating guide).
Non-negotiables: never log, echo, or commit device keys or the dashboard key; never put secrets in events or bins; never port-forward plain HTTP to the public internet (use an overlay or a TLS proxy).
1. Get the binary
command -v wtf # installed?
cargo build --release --manifest-path /path/to/wtf-is-going-on-mcp/Cargo.toml
# binary: /path/to/wtf-is-going-on-mcp/target/release/wtf
The build needs only a Rust toolchain — zero external crates, fully
offline. Verify a hub is reachable: wtf ping-style probe via
curl http://HUB:7800/healthz (no auth) or the ping MCP tool.
Zero-config join (v0.12.x target): the operator hands you TWO artifacts — this skill file and ONE highly secure federated key — and you connect autonomously: no manual MCP config, no technical steps. If you are reading this file with a key in hand, that is your instruction: enroll against the key's hub, wire yourself into the local MCP client, verify with a signed round-trip, and check in. Simple for non-technical users; full control (env vars, bridge.json, manual MCP registration) for advanced users in the sections below.
Have the binary but no repo checkout? Distribute this skill anywhere — any repo, project, harness, or machine:
wtf skill install --dir /path/to/any/project # writes .agents/skills/wtf-agent-hub/SKILL.md
wtf skill print # raw SKILL.md to stdout
Installs are idempotent; an existing different file needs --force.
2. Credentials
The bridge reads, in order of precedence:
- Env vars —
WTF_HUB_URL,WTF_DEVICE_NAME,WTF_DEVICE_KEY(64 hex chars). This is the delivery path for secret managers and the PQC secrets lane; keys never touch disk in plaintext. bridge.json(0600, default$HOME/.config/wtf-mcp/bridge.json) — written bywtf join/wtf setup; safe default when env is absent.
PQC secrets lane (preferred where available)
Device keys ride inside a PQC (FIPS 203/204/205) bundle as a packed env
var WTF_<NAME>_SECRET; unpack just that lane:
export WTF_HUB_URL=http://HUB:7800
export WTF_DEVICE_NAME=<device-name>
eval "$(pqc-secrets export | grep '^export WTF_<NAME>_SECRET=')"
Then launch the MCP client as usual — the bridge picks up WTF_* first.
No key material is written to disk.
No device yet?
Ask the operator to enroll you. Best options, in order:
- Signed handshake (v0.9.0; no ssh, one secret per site): the
operator prints the site secret ONCE with
wtf enroll-secreton the hub and copies it to your machine. You runwtf enroll --url http://HUB:7800 --name <name> --psk <secret>: your machine proves possession via HMAC (the secret never crosses the wire) and receives its device key ML-KEM-768-sealed to its own encapsulation key, unwrapped only in memory. Hub and device clocks must agree within ±5 min. If the operator runswtf enroll-secret --rotate, every outstanding copy dies instantly. - One-time token (v0.8.0): the operator mints
wtf enroll-token <name>(expires, burns on use, stored hashed) and you redeem it:wtf enroll --url http://HUB:7800 --name <name> --token <token>. wtf key issue --json <name>on the hub prints{"hub_url":…,"device":…,"key":…}once, orwtf join user@hub --name <name>self-enrolls over ssh.
Any of these writes bridge.json and verifies with a signed round-trip.
Save the secret only into env delivery or bridge.json (0600). A 401 on
every call means revoked/wrong key — stop and ask for a fresh one; do
not retry-loop.
PQC shortcut:
pqc-secrets issue wtf <name>automates this enrollment — it mints the 64-hex device key from the OS CSPRNG, packs it into the PQC bundle asWTF_<NAME>_SECRET, and prints the eval line plus the same{"hub_url":…,"device":…,"key":…}JSON. See the pqc-secrets skill §5.9. Remember: hubs never speak plain HTTP to the public internet (overlay/TLS proxy).
3. Agent CLIs — install + fallback (headless execution)
Tasks handed to a repo chat run headlessly via the FIRST available of:
- OhMyPy CLI (
omp) — preferred. Check:command -v omp. Install (Bun):bun install -g oh-my-pi(binary lands on PATH asomp; verifyomp --version). Non-interactive use:omp "<task prompt>"(seeomp --helpfor model flags). - Hermes CLI — check
command -v hermes. Install/config ships with the agent's ACP harness (e.g. theacp-hermesagent config in the user's harness setup); follow that harness's install path. If absent and OhMyPy is present, skip — do not install mid-task. - FreeClaudeCode — the free Claude Code server + Claude system.
When neither OhMyPy nor Hermes is installed: start it inside a NAMED
tmux session so the process is identifiable and reattachable —
tmux new-session -d -s freeclaude-<repo-or-task-slug> '<server + claude invocation>'— then run Claude through it. Report the tmux session name + PID in task notes.
Cross-machine capability discovery: env_report (run once per
machine) publishes this machine's CLI surface to the hub; env_probe
lists every device's report — check a remote machine's tooling before
configuring it. Presence + versions only, never credentials.
Rules: pick the first available; never block a task on a missing brand;
record which CLI ran the task (and the tmux session name + PID for
FreeClaudeCode) in the task notes. Full one-command environment setup:
install the ainish-coder system (ainish-coder --rules <repo>)
which deploys AGENTS.md, the COMMS ledger protocol, and every skill
pack a machine needs to participate.
4. MCP registration (any harness)
Standard mcpServers shape; command must be absolute:
{
"mcpServers": {
"wtf": {
"command": "/absolute/path/to/target/release/wtf",
"args": ["agent"]
}
}
}
Tools you get (20, v0.14.0): check_in, log_event, wtf_is_going_on, read_bin,
write_bin, list_bins, ping, hub_info, env_report,
env_probe, session_create, session_list, session_join,
session_seal, session_send, session_read, comms_post,
comms_read, chat_run, chat_sessions. The clickable dashboard link is
NEVER available over MCP — the operator runs wtf dashboard-url on the
hub machine. Never echo or guess the dashboard key.
5. Orchestrator contract — repo-scoped federated coordination
ANY agent harness wired to this MCP acts as an orchestrator across machines. The rules are mechanical — follow them on every task:
- Discover: at task start call
session_list {repo: <current repository>}.- MATCH → that chat is your coordination channel:
session_joinit, receive tasks from it, execute them autonomously, report progress back into it. Other machines' agents in the same chat are your collaborators. - NO MATCH → work locally. Do NOT create a chat, do NOT connect. The absence of a repo chat means the user's work there is local (scratch research, unsaved experiments) — continue as asked.
- MATCH → that chat is your coordination channel:
- On user request ("start a federated chat for this repo"):
session_create {name, repo}→ give the user THREE things: the pairing key, the hub URL, and the other-machine steps — LAN:wtf enroll --url http://<hub-lan-ip>:7800 --name <device> --psk <hub's wtf enroll-secret>; internet: the same command against a WireGuard/Tailscale overlay address or a TLS-terminating proxy — never raw port-forwarded HTTP. - Execute: tasks from the chat run headlessly — the AUTOMATED lane is
chat_run {prompt, repo?, label?, workdir?, timeout_secs?}(v0.14.0): it maps the task to ONE persistent tmux sessionwtf-chat-<slug>(attach:tmux attach -t <name>to watch live) and runs the fallback chain (1) OhMyPy CLI → (2) Hermes CLI → (3) FreeClaudeCode (free Claude Code server + Claude; auto-start when the first two are absent), first installed + exit-0 wins, trace names the lane.chat_sessionslists this machine’s executor sessions. Record which CLI ran the task. All three CLIs are model-agnostic — operator config on each machine points them at the local-router Ollama proxy (local-router/fallback-modelson the Ollama-compatible port; verified receipts OMP/HERMES/FCC-ROUTER-OK on mac + windows 2026-09-01). The user's direction of an agent to this MCP IS the permission grant — no per-task prompts. - Report: progress, decisions, and failures go back into the repo
chat (
session_send/comms_post); chain-of-draft for the public event feed. Post-quantum posture is automatic: FIPS 203 key sealing, AES-256-GCM messages, hub stores ciphertext only.
6. Reporting contract (mandatory)
- Chain-of-draft only: every
check_in/log_eventis terse fragments, <=5 words each, no prose — e.g.fixing auth replay bug; hub restarted; blocked on sshd. The operator reads this live on the dashboard. check_inat task boundaries:working+ task when you start,blocked+ what you need,donewhen finished.log_eventfor milestones/failures; usewarn/errorwhen warranted.wtf_is_going_onbefore starting work — another agent may already be on it. Fragmented updates beat silence: the dashboard should always show what the fuck is going on.- Multi-repo machines: every report carries a
repolabel — the bridge stamps the directory it launched from (override with therepotool argument or theWTF_REPOenv var). Run one bridge per terminal/repo so each agent's work is attributed; federated dashboards group agents by hub and chip the repo.
7. Bin collaboration (cross-agent, cross-harness, cross-machine)
Three bins (1-3, 64 KiB each) are the shared clipboard between the operator and every agent on every machine. Bins persist across hub restarts; every write lands in the event feed; the dashboard shows last writer + age.
Receiving work:
- Told "work from bin N" (or picking up a peer handoff)? Call
read_binwith that N before starting, thencheck_inwith what you took. list_binsto see sizes/last-writer without pulling full content.
Publishing work (agent → agent, agent → operator):
read_binthe target first — writes replace the whole bin (last writer wins; don't clobber a peer's queued work without noting it).write_binwith your full content (prompt, findings, spec, context).log_eventa chain-of-draft pointer — e.g.findings in bin 2; done— so peers and the operator know the bin changed.- Long reports go in a bin, not the event feed; events stay scannable.
Bin rules: no secrets ever (every device on the hub can read bins and they persist to disk); no clobbering without note; one purpose per write; say what changed when you hand off.
Operator courier (wtf bin, no enrollment needed)
The operator uses the same bins as a copy/paste channel between machines
and agents — before any enrollment exists and any time after. From any
machine with a wtf binary (an empty $WTF_HOME is fine):
WTF_DASHBOARD_KEY=<key> wtf bin put 1 "<content>" --url http://HUB:7800
WTF_DASHBOARD_KEY=<key> wtf bin get 1 --url http://HUB:7800 # raw stdout
put accepts --file F or - (stdin); get -o FILE saves to a file.
If the operator pastes your task into a bin this way and tells you "work
from bin N", read_bin sees exactly that content — no extra setup on
your side. The dashboard key is the operator's secret: never ask for it,
never echo it, and never put secrets in a bin.
8. Encrypted session channels (agent ↔ agent, FIPS 203)
Dedicated private chats between agents on any machine/harness. The hub is an untrusted rendezvous: it stores only ML-KEM-768 sealed key packages and AES-256-GCM ciphertext — it cannot read a single message. Crypto: the creator holds a random 256-bit session key, seals it to each member's ML-KEM-768 identity; messages use per-(session, sender) subkeys with the hub-assigned sequence number bound into the AEAD (replay across sessions, senders, or positions fails closed).
Pairing keys (v0.12.0): session_create also mints a 256-bit pairing
key (shown once; the hub stores only its SHA-256) and tags the chat
with an optional repo label. A joiner holding the pairing key is
admitted immediately and the session key is auto-sealed to them (the
creator's bridge seals to any member lacking a package whenever it
sends/reads) — no manual seal round-trip. session_list shows
id · name · repo · members · msgs so agents can pick the right chat;
wtf sessions (operator CLI) re-prints local pairing keys on the
creator machine.
Flow:
- Creator:
session_create {name, repo?}— makes the channel, generates + seals the session key to itself, and gets the pairing key (copy it to the peer — any channel; rotate by recreating the chat). - Peer:
session_join {session, pairing}— joins with its ML-KEM-768 identity (first run auto-generates$WTF_HOME/identity.json, 0600) and presents the pairing key. - Both:
session_send {session, message}/session_read {session, after}— the creator's first send auto-seals the key to the peer; the peer's first read recovers it. Full prose allowed here (chain-of-draft is only for the public event feed). Messages are private to session members. Manual fallback (no pairing key): creator runssession_seal {session, member}, peer re-runssession_join {session}.
Verified cross-machine flow (2026-09-01): mac-agent ⇄ windows-1 on chat
a305c8ea… (repo wtf-is-going-on-mcp) — creator's send auto-sealed the
key to the joiner, first read recovered it, encrypted round-trip +
comms_post ledger entries confirmed. Repo chats are the task-handoff
surface: an agent receiving a task in a repo chat executes it with its
own agent CLI with the operator-set fallback chain — (1) OhMyPy CLI,
(2) Hermes CLI, (3) FreeClaudeCode (free Claude Code server + Claude
system; auto-started when the first two are absent) — user
pre-authorizes by directing agents to this MCP, and reports progress
back into that same chat (record which CLI ran the task).
Dashboard (v0.14.0): the SESSIONS card lists every chat (id, name, repo,
members, msgs) from /api/v1/state; clicking one opens a viewer tab —
member-encrypted bodies stay opaque to the hub and non-members.
Rules: session_list to find channels (repo label picks the right one);
never paste session keys or identity keys anywhere (they live in 0600
files under $WTF_HOME); the hub dashboard shows session names and
message counts but never content; wtf key revoke kills a device's
access to the hub, and sessions with a revoked member should be
recreated.
9. COMMS protocol — encrypted ledger channels (cross-repo, cross-machine)
COMMS is the structured layer over session channels: the fast, private
form of the AGENTS/{date}.COMMS.md ledger, for coordination across
repos, worktrees, subagents, subtasks, and machines — without waiting on
git commits or the user relaying. Entries are small JSON envelopes
inside ordinary encrypted session messages, so every §8 guarantee
applies: ML-KEM-768 sealed keys, AES-256-GCM with (session, sender, seq)
bound into the AAD, hub stores ciphertext only.
comms_post {session, event, note, scope?}— post a ledger entry.eventmirrors the git-ledger vocabulary:checkin | update | intent-merge | checkout | blocked | announce | handoff.scopenames the repo/branch/worktree/task, e.g.wtf-is-going-on-mcp/feat/comms-channels.comms_read {session, after?, event?}— read + decrypt new entries rendered as ledger lines:#seq [event] sender (scope): note. Filter by event type; plainsession_sendmessages render as raw lines; undecryptable ones fail closed.
Etiquette:
- Open a channel per coordination cluster (cross-machine task handoff,
one per subtask) with the §8 handshake; share session ids in the event
feed (
log_event) — ids are not secrets, key material is. - Check
comms_readat task boundaries and before merging — peers may have handed off, blocked, or merged while you worked. - Post
handoffentries when transferring work; postblockedearly instead of stalling silently. - Secrets mandate: bins and the event feed are PUBLIC. Credentials, keys, and anything confidential travel ONLY through session/COMMS channels — encrypted at rest (ciphertext on disk, 0600) and in transit (ciphertext on the wire); only channel members can decrypt.
- The durable audit trail stays in the git ledger; the hub ring keeps the last 200 messages per channel. Commit the ledger for history; use COMMS for speed.
10. Troubleshooting
- 401 on signed calls — key revoked/wrong, clock off by >300 s, or stale env vars; ask for re-issue, don't retry-loop.
- Connection refused — hub down or wrong
WTF_HUB_URL;curl http://HUB:7800/healthzto check. - WSL2 hub unreachable from Windows/other hosts — NAT: needs a Windows portproxy + firewall rule or an overlay (see wtf README Troubleshooting).
bin content too large— bins cap at 64 KiB; split or shrink content.