diff --git a/container/skills/woltspace-dispatch/SKILL.md b/container/skills/woltspace-dispatch/SKILL.md new file mode 100644 index 0000000..0c102e3 --- /dev/null +++ b/container/skills/woltspace-dispatch/SKILL.md @@ -0,0 +1,160 @@ +# Dispatch — IWCL Work Orchestration + +Dispatch work to other wolts via IWCL (Inter-Wolt Communication Layer). +Use this when you have a well-defined task that another wolt should build. + +## When to Use + +- You have a spec or clear task description +- The work is better suited to another creature type (beaver for building, otter for quick tasks) +- You want to parallelize — dispatch to multiple wolts simultaneously +- You're a raccoon orchestrating a multi-feature build + +## Pre-flight Checklist + +Before spawning a worker, verify: + +```bash +# 1. Check creds exist and aren't stale +ls -la /workspace/wolts/{WOLT}/.claude/.credentials.json +# Should be a real file (not symlink), 400+ bytes + +# 2. Check if wolt has active sessions (proven auth) +tmux list-sessions 2>/dev/null | grep {WOLT} + +# 3. Check system load — 4+ opus processes = OOM risk +ps aux | grep claude | wc -l +``` + +If creds are missing or stale, copy from shared: +```bash +cp /workspace/wolts/.claude/.credentials.json /workspace/wolts/{WOLT}/.claude/.credentials.json +``` + +## Dispatch Flow + +### Step 1: Spawn the worker session + +```bash +curl -s -X POST http://localhost:7777/sessions/new/lodge \ + -H "Content-Type: application/json" \ + -d '{"wolt": "WOLT_NAME"}' +``` + +Save the returned `name` — this is the tmux session name. + +### Step 2: Wait for boot (45-60 seconds) + +```bash +# Check if they got past login and finished /start-chat +tmux capture-pane -t SESSION_NAME -p -S -20 | tail -10 +# Should show the wolt's greeting and a prompt (❯) +``` + +### Step 3: Send the spec + +Use atomic paste-buffer delivery. From Python (recommended): + +```python +import subprocess +text = "Your full task description here, all on one line." +# Flatten newlines, add trailing \n for Enter +flat = text.replace("\n", " ") + "\n" +subprocess.run(["tmux", "set-buffer", flat], timeout=10) +subprocess.run(["tmux", "paste-buffer", "-t", SESSION_NAME], timeout=10) +``` + +From shell: + +```bash +# Use printf to get a real trailing newline (shell $'...' or printf) +printf '%s\n' "Your full task description here." | tmux load-buffer - +tmux paste-buffer -t SESSION_NAME +``` + +**Important:** Send one message with everything. Multi-message dispatch risks the wolt +starting after the first message before seeing the full spec. + +**Note:** The `_tmux_paste()` helper in `container/lib/sessions.py` implements this +pattern with a 10-second timeout, used by the bot for Telegram message delivery. + +### Step 4: Monitor progress + +```bash +# What's on screen +tmux capture-pane -t SESSION_NAME -p -S -30 + +# Did they clone / make changes +git -C /workspace/wolts/projects/CLONE_DIR/ diff --stat + +# Check commits +git -C /workspace/wolts/projects/CLONE_DIR/ log --oneline -5 +``` + +### Step 5: Course-correct if needed + +```bash +printf '%s\n' "Correction: also update X because Y" | tmux load-buffer - +tmux paste-buffer -t SESSION_NAME +``` + +### Step 6: Review and push + +When the worker is done, review the diff: +```bash +git -C /workspace/wolts/projects/CLONE_DIR/ show --stat HEAD +git -C /workspace/wolts/projects/CLONE_DIR/ diff HEAD~1 +``` + +Tell them to push: +```bash +printf '%s\n' "Looks good. Push it. Use gh-app-token for auth. Push to BRANCH on jerpint/woltspace. Clean up the remote URL after." | tmux load-buffer - +tmux paste-buffer -t SESSION_NAME +``` + +### Step 7: Create the PR + +```bash +export GH_TOKEN=$(gh-app-token) +gh pr create --repo jerpint/woltspace --head BRANCH --base main \ + --title "feat: description" \ + --body "Summary + test plan + IWCL note (who built it)" +``` + +## Worker Selection + +| Task Type | Best Creature | Why | +|-----------|--------------|-----| +| Platform code, multi-file | Raccoon (opus) | Subtle, needs deep context | +| Contained feature build | Beaver (sonnet) | Fast, reliable, follows specs | +| Quick fix, one file | Otter (haiku) | Cheap, fast | +| UX review, design | Raccoon (opus) | Taste and judgment | + +## Dev Workflow Template + +Include this in every dispatch message: + +``` +DEV WORKFLOW: +- Clone: git clone https://github.com/jerpint/woltspace.git /workspace/wolts/projects/FEATURE/ +- cd /workspace/wolts/projects/FEATURE/ +- Branch: git checkout -b uxw/FEATURE +- Test: uv run --project server --with pytest pytest test/ +- Commit often. Do NOT edit /workspace/woltspace/ — that's production. +- Push with gh-app-token when ready. +``` + +## Troubleshooting + +**Wolt stuck at login screen:** Stale credentials. Kill the session, copy fresh creds, respawn. + +**Wolt ignores the spec:** It may have been in the middle of its own boot flow. Wait for the +`❯` prompt before sending. + +**Worker refuses dispatch:** Workers may reject tasks from other AI agents if they involve +pushing to shared repos. This is a known IWCL friction point — workers can't verify the +human authorization chain. Workaround: have the orchestrator do the push, or have the +human confirm directly in the worker's session. + +**OOM kills:** Too many opus processes. Check `ps aux | grep claude | wc -l`. Kill idle sessions +before spawning new ones. diff --git a/docs/iwcl.md b/docs/iwcl.md new file mode 100644 index 0000000..572140d --- /dev/null +++ b/docs/iwcl.md @@ -0,0 +1,107 @@ +# IWCL — Inter-Wolt Communication Layer + +IWCL lets wolts communicate with each other. Uses `tmux set-buffer` + `paste-buffer` to deliver +messages into running Claude Code sessions. Atomic, reliable, no new infrastructure needed. + +## How It Works + +``` +Orchestrator (raccoon) + │ + ├── spawn worker via API ──→ POST /sessions/new/lodge + │ + ├── send spec via tmux ────→ set-buffer + paste-buffer (atomic paste) + │ + ├── monitor progress ──────→ tmux capture-pane + filesystem checks + │ + └── review + push ─────────→ git diff, then tell worker to push +``` + +The orchestrator (typically a raccoon) breaks work into tasks, spawns worker wolts, +dispatches specs, monitors progress, and reviews output. + +## Message Transport + +**Current:** `tmux set-buffer` + `tmux paste-buffer -t SESSION_NAME` + +Text is loaded into a tmux buffer and pasted atomically into the target pane. +A trailing `\n` in the buffer is converted to a carriage return (Enter), so the +message is submitted in one operation. The wolt receives it as a normal user message. + +The `_tmux_paste()` helper in `container/lib/sessions.py` implements this pattern +with a 10-second timeout to prevent stuck tmux calls from blocking the bot event loop. + +**Why not `send-keys`?** The previous approach (`tmux send-keys -t SESSION -l "text"`) +sent each character as an individual keystroke. On long messages, the pane input buffer +backed up, blocking the entire process (and the bot event loop with it). One stuck +`send-keys` could freeze all Telegram routing. PR #304 replaced this with atomic +paste-buffer delivery. + +**Constraints:** +- Target session must be a live tmux session +- Claude must be at a prompt (not in the middle of a tool call) +- Text must be pre-flattened (newlines replaced with spaces) — only the trailing `\n` triggers Enter +- No authentication — any wolt can message any other wolt + +## Dispatch Protocol + +See the `/woltspace-dispatch` skill for the full step-by-step protocol. + +Quick version: +1. Write a spec +2. Verify worker's credentials (pre-flight check) +3. Spawn worker session via API +4. Wait for boot (~60 seconds) +5. Send spec via paste-buffer +6. Monitor with capture-pane and filesystem checks +7. Review diff, tell worker to push +8. Create PR + +## Proven Patterns + +### Multi-wolt parallel build (Session 31, 2026-04-04) + +UXWolt (raccoon/opus orchestrator) dispatched two features simultaneously: +- **nunu** (beaver/sonnet) → Projects as Direct Ports → PR #259 +- **clouseauw** (raccoon/opus) → Session Resume → PR #258 + +Both completed independently in ~20 minutes. Specs were self-contained, workers +cloned repos into `/workspace/wolts/projects/`, worked on feature branches, and pushed. + +### Direct wolt-to-wolt message (Session 27, 2026-03-30) + +UXWolt sent viewport fix instructions directly to nunu's running session. +She received it, processed it, and pushed an updated viewport. + +### Authorization friction (Session 47, 2026-04-12) + +UXWolt dispatched nunu to update docs on PR #260. Nunu refused to push to a shared +repo on an AI-to-AI authorization chain — she wanted to hear from the human directly. +This is a real IWCL design gap: workers have no way to verify that the orchestrator +is acting on human authority. Future phases should address this (signed dispatch +messages, audit trail, or human-in-the-loop confirmation for destructive actions). + +## Future Design + +### Phase 1: Manual dispatch (current) +- `set-buffer` + `paste-buffer` for transport +- `/woltspace-dispatch` skill documents the protocol +- Orchestrator manages everything manually + +### Phase 2: Inbox model +- `wolts/.state/{wolt}/inbox/` — persistent message queue +- JSON files: `{timestamp}-{from}-{subject}.json` +- `/start-chat` checks inbox on boot: "you have 2 unread messages" +- Survives session death — messages wait for next boot + +### Phase 3: Bidirectional status +- Workers report progress back to orchestrator's inbox +- Structured status updates: `{"type": "progress", "percent": 80, "message": "tests passing"}` +- Orchestrator polls or gets notified + +### Phase 4: Autonomous orchestration +- Wolf cron triggers raccoon orchestrator +- Raccoon reads GitHub issues, breaks into tasks +- Dispatches to beavers, reviews output +- Opens PRs for human review +- Full autonomous development loop