Documentation for the exe.dev agent orchestration system
One command to install all tools:
curl -fsSL https://vm-api-docs.pages.dev/install.sh | bash
Then start a worker loop:
worker start myproject --task "implement feature X" --dir /path/to/project
Or give an agent this prompt:
Set up the exe.dev worker loop system: curl -fsSL https://vm-api-docs.pages.dev/install.sh | bash
Then run: worker list
Autonomous agent sessions that run until complete or hitting context limits. Sessions hand off state to the next session automatically.
worker start <name> --task "description of work" --dir /path/to/project [--max 10] [--model name] [--ping 60]
# Default model: claude-opus-4.6
# Default ping: 60s (set --ping 0 to disable)
worker list
Shows all workers with status, session count, and working directory.
worker stop <name> # graceful stop
worker stop-all # stop everything
worker log <name> # show recent output
tail -f ~/.workers/<name>.log # live tail
Workers emit structured pings every N seconds (default: 60s) with context usage, commit activity, and anomaly detection.
worker ping <name> # show last 20 pings
worker ping <name> 50 # show last 50 pings
worker watch <name> # live tail (Ctrl+C to stop)
Example ping output:
=== my-worker | running | session 3/10 | since 2026-02-07T16:17:44+00:00 ===
[16:18:30] 🚀 START | task: implement feature X | ping:60s
[16:19:31] ✅ ACTIVE | s1/10 ctx:23% +2commits [feat: add login page]
[16:20:32] ✅ ACTIVE | s1/10 ctx:45% +1commits [fix: auth redirect]
[16:21:05] 🟡 CTX-FULL | 162000 tokens, cycling session
[16:21:07] ✅ SESSION-END | s1 done | commits: feat: add login page
[16:21:10] 📋 SESSION | starting 2/10
[16:22:12] ⚠️ ERRORS | s2/10 ctx:31% errors:4
[16:30:00] 🏁 DONE | task complete!
| Event | Meaning |
|---|---|
| 🚀 START | Worker started |
| ✅ ACTIVE | Periodic ping — commits detected |
| 📋 SESSION | New session starting |
| ✅ SESSION-END | Session completed normally |
| 🟡 CTX-HIGH | Context window >70% |
| 🟡 CTX-FULL | Context limit hit, cycling |
| ⚠️ ERRORS | Error patterns detected in logs |
| ❌ ERROR | Worker hit a fatal error |
| 🏁 DONE | Task completed successfully |
| 🏁 MAX-REACHED | Hit max session limit |
| 🛑 STOP | Worker stopping |
# Change max sessions on a running worker
worker adjust <name> --max 50 [--model name]
# Example: bump orchestrator to 50 sessions
worker adjust orchestrator --max 50
# Example: change model on running worker
worker adjust orchestrator --model claude-sonnet-4-20250514
| Feature | Behavior |
|---|---|
| Context limit | Stops at 75% context, extracts handoff, starts next session |
| Handoff extraction | Queries SQLite for last think blocks, injects into next session |
| Directory lock | Only one worker per directory (prevents conflicts) |
| State file | ~/.workers/<name>.json |
| Ping log | ~/.workers/<name>.ping.log (last 200 entries) |
| Model selection | Configurable via --model flag, default: claude-opus-4.6 |
| Ping interval | Configurable via --ping flag, default: 60s |
| Anomaly detection | Flags error spikes, high context usage, commit activity |
| Deja integration | Queries deja for project memories at session start |
When a session ends (context limit, interruption, or natural completion), the orchestrator extracts state and injects it into the next session.
think tool calls| Type | Source | Result |
|---|---|---|
DONE: | Agent's last message | Worker marks complete, stops |
HANDOFF: | Agent's last message | Message injected into next session |
| Interrupted | Last think blocks | Thoughts summarized and injected |
# What the orchestrator queries:
sqlite3 ~/.config/shelley/shelley.db "
SELECT json_extract(json(llm_data), '$.Content[0].ToolInput.thoughts')
FROM messages
WHERE conversation_id='CONV_ID'
AND json_extract(json(llm_data), '$.Content[0].ToolName') = 'think'
ORDER BY sequence_id DESC LIMIT 3
"
think tool to record your reasoning. These thoughts are preserved across session boundaries.
Persistent memory that survives across sessions. Worker loops automatically query deja at session start.
curl -s -X POST https://deja.coey.dev/inject \
-H "Content-Type: application/json" \
-d '{"context": "describe your task", "format": "prompt", "limit": 5}'
Returns learnings relevant to the context. Worker loops do this automatically.
curl -s -X POST https://deja.coey.dev/learn \
-H "Authorization: Bearer $(cat ~/.deja-api-key)" \
-H "Content-Type: application/json" \
-d '{
"trigger": "when this is relevant",
"learning": "what to remember",
"confidence": 0.9
}'
| Type | Use for |
|---|---|
| Project state | Current status, blockers, next steps |
| Learnings | "When X happens, do Y" patterns |
| Failures | Things that didn't work (prevent repeats) |
Past shelley sessions are searchable. Find what you tried before.
~/bin/shelley-search "pattern" [limit]
Finds conversations mentioning the pattern, shows context snippets.
~/bin/shelley-recall "error message" [limit]
Returns HANDOFF notes and relevant snippets, formatted for prompt injection.
gates/health.sh finds errors, it can auto-search past sessions and include relevant context.
Selective editing of the context window. Delete tool outputs you don't need anymore.
ctx size <conversation_id>
ctx search <conversation_id> "search term"
Returns message IDs matching the term.
ctx forget <msg_id> [msg_id...]
ctx list <conversation_id> [limit]
ctx search <conv> "websocket" — find message IDsctx forget id1 id2 id3 — delete themctx size <conv> — verify tokens freedManage conversations programmatically. Archive, cancel, check status.
# All conversations
conv list
# Only currently working (actively processing)
conv list --working
conv status <id|slug>
# Example output:
# ID: cSJ3NR3
# Slug: orchestrator
# Model: claude-opus-4.5
# Archived: false
# Context: 26561 tokens
# Archive a conversation (hides from list)
conv archive orchestrata
# Bring it back
conv unarchive orchestrata
# Stop a running conversation
conv cancel <id|slug>
conv status orch finds "orchestrator".
HTTP endpoints for conversation management. All require X-Exedev-Userid header.
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/conversations | List all conversations (includes working field) |
| GET | /api/conversation/{id} | Get conversation details + context_window_size |
| POST | /api/conversation/{id}/archive | Archive a conversation |
| POST | /api/conversation/{id}/unarchive | Unarchive a conversation |
| POST | /api/conversation/{id}/cancel | Cancel a running conversation |
curl -s "http://localhost:9999/api/conversations" \
-H "X-Exedev-Userid: $USER" | jq '.[] | select(.working == true)'
curl -s "http://localhost:9999/api/conversation/cSJ3NR3" \
-H "X-Exedev-Userid: $USER" | jq '.context_window_size'
# 26561
Worker loops auto-run gates/health.sh before each session. Output is injected into the prompt.
#!/bin/bash
# gates/health.sh - exit 0 = healthy, exit 1 = blockers
# Type check
ERRORS=$(npx tsc --noEmit 2>&1 | grep "error TS" | head -10)
if [ -n "$ERRORS" ]; then
echo "❌ Type errors - FIX FIRST:"
echo "$ERRORS"
exit 1
fi
echo "✅ Build passes"
exit 0
Sessions can die mid-work (context limit, timeout, crash). Health checks should rescue orphaned changes, not just warn:
# Auto-commit orphaned work at session start
UNCOMMITTED=$(git status --porcelain | grep -E "^( M|M |\?\?)" | head -10)
if [ -n "$UNCOMMITTED" ]; then
echo "⚠️ Uncommitted work found - auto-committing..."
# Stage modified files
git status --porcelain | grep -E "^( M|M )" | awk '{print $2}' | xargs -r git add
# Stage new code files (not temp files)
git status --porcelain | grep -E "^\?\?" | awk '{print $2}' | \
grep -E "\.(ts|js|svelte|json|md|sh)$" | xargs -r git add
# Commit with rescue message
if ! git diff --cached --quiet; then
git commit -m "Auto-rescue: uncommitted work from previous session"
echo "✅ Committed rescued work"
fi
fi
| Check | Blocking? | Why |
|---|---|---|
| Uncommitted changes | Auto-fix | Rescue orphaned work automatically |
| Type errors | Yes | Don't add features on broken builds |
Explicit any | Warning | Review, but not always blocking |
| TODO markers | No | Awareness, not blocking |
| console.log | No | Clean up eventually |
The orchestrator coordinates run lifecycle. It does not manage agent reasoning mid-session. It handles boundaries and recovery.
~/.workers/*.json files| Trigger | Action |
|---|---|
| Context limit reached | Cancel session, extract handoff, start next |
Agent outputs DONE: | Mark worker complete, stop |
Agent outputs HANDOFF: | Extract message, inject into next session |
| Session interrupted | Extract last think blocks, inject into next session |
| Max sessions reached | Mark worker as max_reached, stop |
# Start a worker loop
worker start myproject --task "implement feature X" --dir /path/to/project --max 20
# Monitor it
worker list
worker log myproject
# Steer by updating memory
curl -X POST https://deja.coey.dev/learn ...
# What did we try before?
~/bin/shelley-search "websocket"
~/bin/shelley-recall "error TS2345"
# Find and delete old messages
ctx search CONV_ID "big tool output"
ctx forget MSG_ID1 MSG_ID2
ctx size CONV_ID