Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
160 changes: 160 additions & 0 deletions container/skills/woltspace-dispatch/SKILL.md
Original file line number Diff line number Diff line change
@@ -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.
107 changes: 107 additions & 0 deletions docs/iwcl.md
Original file line number Diff line number Diff line change
@@ -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