VM API

Documentation for the exe.dev agent orchestration system

⚡ Quick Start

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

TABLE OF CONTENTS

Worker Loops ~/bin/worker

Autonomous agent sessions that run until complete or hitting context limits. Sessions hand off state to the next session automatically.

Start a loop

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)

Check running loops

worker list

Shows all workers with status, session count, and working directory.

Stop a loop

worker stop <name>       # graceful stop
worker stop-all          # stop everything

View logs

worker log <name>        # show recent output
tail -f ~/.workers/<name>.log  # live tail

Monitor with pings

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!

Ping events

EventMeaning
🚀 STARTWorker started
✅ ACTIVEPeriodic ping — commits detected
📋 SESSIONNew session starting
✅ SESSION-ENDSession completed normally
🟡 CTX-HIGHContext window >70%
🟡 CTX-FULLContext limit hit, cycling
⚠️ ERRORSError patterns detected in logs
❌ ERRORWorker hit a fatal error
🏁 DONETask completed successfully
🏁 MAX-REACHEDHit max session limit
🛑 STOPWorker stopping

Adjust running worker

# 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

Key behaviors

FeatureBehavior
Context limitStops at 75% context, extracts handoff, starts next session
Handoff extractionQueries SQLite for last think blocks, injects into next session
Directory lockOnly one worker per directory (prevents conflicts)
State file~/.workers/<name>.json
Ping log~/.workers/<name>.ping.log (last 200 entries)
Model selectionConfigurable via --model flag, default: claude-opus-4.6
Ping intervalConfigurable via --ping flag, default: 60s
Anomaly detectionFlags error spikes, high context usage, commit activity
Deja integrationQueries deja for project memories at session start
Steering a running loop: Update deja memories. The agent queries deja at each session start, so new memories steer direction.

Handoff Recovery

When a session ends (context limit, interruption, or natural completion), the orchestrator extracts state and injects it into the next session.

How it works

  1. Session hits context limit or is interrupted
  2. Orchestrator cancels the session
  3. Orchestrator queries SQLite for last 3 think tool calls
  4. Extracts HANDOFF: or DONE: from last text message
  5. Injects extracted context into next session prompt

Handoff types

TypeSourceResult
DONE:Agent's last messageWorker marks complete, stops
HANDOFF:Agent's last messageMessage injected into next session
InterruptedLast think blocksThoughts summarized and injected

SQLite extraction

# 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
"
Best practice: Use the think tool to record your reasoning. These thoughts are preserved across session boundaries.

Deja Memory deja.coey.dev

Persistent memory that survives across sessions. Worker loops automatically query deja at session start.

Query for relevant memories

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.

Store a learning

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
  }'
Don't pollute deja: Running tests against deja created 48 garbage entries that drowned out 13 real ones. Use unique markers and clean up after tests.

Memory types

TypeUse for
Project stateCurrent status, blockers, next steps
Learnings"When X happens, do Y" patterns
FailuresThings that didn't work (prevent repeats)

Past shelley sessions are searchable. Find what you tried before.

Search past sessions

~/bin/shelley-search "pattern" [limit]

Finds conversations mentioning the pattern, shows context snippets.

Get compact context for injection

~/bin/shelley-recall "error message" [limit]

Returns HANDOFF notes and relevant snippets, formatted for prompt injection.

Auto-recall in health checks: When gates/health.sh finds errors, it can auto-search past sessions and include relevant context.

Context Management ~/bin/ctx

Selective editing of the context window. Delete tool outputs you don't need anymore.

Check context size

ctx size <conversation_id>

Search for messages

ctx search <conversation_id> "search term"

Returns message IDs matching the term.

Delete messages

ctx forget <msg_id> [msg_id...]

List recent messages

ctx list <conversation_id> [limit]

Workflow: "forget the websocket stuff"

  1. ctx search <conv> "websocket" — find message IDs
  2. ctx forget id1 id2 id3 — delete them
  3. ctx size <conv> — verify tokens freed

Conversation Management ~/bin/conv

Manage conversations programmatically. Archive, cancel, check status.

List conversations

# All conversations
conv list

# Only currently working (actively processing)
conv list --working

Check status

conv status <id|slug>

# Example output:
# ID:       cSJ3NR3
# Slug:     orchestrator
# Model:    claude-opus-4.5
# Archived: false
# Context:  26561 tokens

Archive/Unarchive

# Archive a conversation (hides from list)
conv archive orchestrata

# Bring it back
conv unarchive orchestrata

Cancel

# Stop a running conversation
conv cancel <id|slug>
Tip: Use partial slug matches. conv status orch finds "orchestrator".

API Reference localhost:9999

HTTP endpoints for conversation management. All require X-Exedev-Userid header.

MethodEndpointDescription
GET/api/conversationsList all conversations (includes working field)
GET/api/conversation/{id}Get conversation details + context_window_size
POST/api/conversation/{id}/archiveArchive a conversation
POST/api/conversation/{id}/unarchiveUnarchive a conversation
POST/api/conversation/{id}/cancelCancel a running conversation

Example: Check if conversation is running

curl -s "http://localhost:9999/api/conversations" \
  -H "X-Exedev-Userid: $USER" | jq '.[] | select(.working == true)'

Example: Get context size

curl -s "http://localhost:9999/api/conversation/cSJ3NR3" \
  -H "X-Exedev-Userid: $USER" | jq '.context_window_size'
# 26561

Health Checks gates/health.sh

Worker loops auto-run gates/health.sh before each session. Output is injected into the prompt.

Create a health check

#!/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

Auto-rescue uncommitted work

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
Why auto-rescue? Warnings don't work. Agents hit context limits and die before committing. The next session warns but continues building on uncommitted changes. Auto-rescue breaks this cycle.

What to check

CheckBlocking?Why
Uncommitted changesAuto-fixRescue orphaned work automatically
Type errorsYesDon't add features on broken builds
Explicit anyWarningReview, but not always blocking
TODO markersNoAwareness, not blocking
console.logNoClean up eventually

Orchestrator

The orchestrator coordinates run lifecycle. It does not manage agent reasoning mid-session. It handles boundaries and recovery.

Responsibilities

Boundaries

Orchestrator actions by trigger

TriggerAction
Context limit reachedCancel session, extract handoff, start next
Agent outputs DONE:Mark worker complete, stop
Agent outputs HANDOFF:Extract message, inject into next session
Session interruptedExtract last think blocks, inject into next session
Max sessions reachedMark worker as max_reached, stop

Quick Reference

Start autonomous work

# 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 ...

Search past work

# What did we try before?
~/bin/shelley-search "websocket"
~/bin/shelley-recall "error TS2345"

Free up context

# Find and delete old messages
ctx search CONV_ID "big tool output"
ctx forget MSG_ID1 MSG_ID2
ctx size CONV_ID