Kelly Deacon Architecture — Unified Patrol Daemon (PIP-73)¶
Type: Infrastructure component
PIP: PIP-73 (Unified Deacon Daemon), building on PIP-72 (GUPP-inspired timeout enforcement)
Related: gas-town-daemon-architecture, steve-yegge-gupp, kelly-gas-town-gap-analysis, ralph-protocol
Bibliography¶
Sources: gas-town-daemon-architecture concept page; steve-yegge Gas Town emergency user manual; dark-factory-kb PIP-72/73 gap analysis. URLs in article body are implementation artifacts (property list examples in Go code), not external citations.
Overview¶
The Kelly Deacon is the implementation of Gas Town's patrol daemon concept within the Kelly factory. It is a single Go binary that enforces liveness, detects stale work, and fires completion hooks — the infrastructure guarantee that steve-yegge-gupp's "if work exists, you must run" principle actually holds.
The Deacon is not an agent. It does not use an LLM. It is deterministic infrastructure with fixed patrol logic, configurable timeouts, and predictable behavior. This separation is deliberate: the thing that catches stuck agents must itself never get stuck.
Implementation: Go Binary¶
The Deacon is compiled as a single Go binary (deacon) with no runtime dependencies beyond the Beads/Dolt substrate. This is a deliberate choice:
- Deterministic — no LLM inference, no stochastic behavior, no prompt injection risk
- Fast startup — Go binary starts in milliseconds, not seconds
- Single deployment unit — no container, no runtime, no Node.js version conflicts
- Low resource — patrols 50+ agent hooks with negligible CPU and memory
Binary Structure¶
deacon/
├── main.go # Entry point, config loading, signal handling
├── patrol.go # Core patrol loop
├── timeout.go # Timeout detection and escalation logic
├── respawn.go # Auto-respawn logic (GUPP enforcement)
├── hooks.go # Subagent-complete hook firing (PIP-73)
├── config.go # TOML config parsing
└── launchd.go # Launchd/systemd integration helpers
Phase 1: Patrol in_progress — Timeout Detection + Auto-Respawn¶
PIP reference: PIP-72 (GUPP-inspired timeout enforcement)
The Deacon's primary patrol loop iterates over all beads in in_progress state. For each bead, it checks:
- Last activity timestamp — when did the assigned agent last touch this bead?
- Hook age — how long has this bead been on the agent's hook without progress?
- Agent liveness — is the assigned agent still responding to heartbeats?
Timeout Thresholds¶
[deacon.timeouts]
stale_warning = 300 # 5 minutes: log warning
stale_alert = 600 # 10 minutes: fire alert to Mayor
stale_kill = 900 # 15 minutes: kill agent, re-queue bead
dead_agent = 120 # 2 minutes: agent heartbeat missing → dead
Auto-Respawn (GUPP Enforcement)¶
When the Deacon detects a stale bead (timeout exceeded), it executes the GUPP enforcement sequence:
1. Log: "Deacon: bead {id} stale on agent {agent} for {duration}"
2. Kill: send SIGTERM to the stuck agent process
3. Wait: 10 seconds for graceful shutdown
4. Force: SIGKILL if still alive
5. Re-queue: transition bead from in_progress → queued
6. Notify: write a stale-event bead for the Mayor to surface
7. Respawn: Refinery reassigns the bead to a new agent
This is the operationalization of GUPP: the Deacon ensures that "if work exists, you must run" is enforced externally, not just by agent discipline.
Kelly Integration with RALPH¶
The Deacon's timeout logic integrates with Kelly's ralph-protocol:
| Deacon Event | RALPH Action |
|---|---|
| Stale bead (first occurrence) | Retry attempt 1 — immediate respawn |
| Stale bead (second occurrence) | Retry attempt 2 — immediate respawn |
| Stale bead (third occurrence) | Retry attempt 3 — immediate respawn |
| Stale bead (4th-7th occurrence) | Geometric backoff: 2s, 4s, 8s, 16s |
| Stale bead (7th+ occurrence) | Escalate to operator with full diagnostics |
| Same error twice | Immediate escalation (RALPH hard rule) |
The Deacon passes diagnostics between retries — each respawn includes the error context from the previous attempt, so the next agent knows why the prior one failed.
Phase 2: Patrol in_review — Stale Detection + Discord Alerts¶
The second patrol loop handles beads in in_review state — work that has been completed by an agent but is awaiting human review or approval.
The in_review Problem¶
In multi-agent pipelines, completed work often waits for human review. If the human is away or distracted, review beads accumulate. Unlike in_progress beads (which have an active agent), in_review beads have no agent assigned — they're waiting on the human.
Stale Review Detection¶
[deacon.review]
stale_reminder = 3600 # 1 hour: first reminder
stale_escalate = 86400 # 24 hours: escalate priority
stale_archive = 604800 # 7 days: archive with warning
When a review bead exceeds the stale threshold:
1. Discord alert — send a message to the configured channel: "🔔 Review pending: {bead_title} — waiting {duration}"
2. Priority bump — increase bead priority so it surfaces to the top of the Mayor's filter
3. Summary injection — write a brief summary of what needs review so the human can decide without reading the full artifact
Discord Integration¶
The Deacon communicates stale reviews via Discord webhooks:
[deacon.discord]
webhook_url = "https://discord.com/api/webhooks/..."
channel_id = "1497982313760292955"
mention_on_escalate = true
Alerts are structured messages with:
- Bead title and ID
- Time waiting
- Brief summary of the artifact
- Link to the artifact file
- Severity indicator (🟡 warning, 🔴 escalated)
Phase 3: Subagent-Complete Hook (PIP-73)¶
PIP reference: PIP-73 (Unified Deacon Daemon)
The third phase is the Deacon's completion hook — when a bead transitions to done, the Deacon fires a notification that triggers downstream processing.
Hook Lifecycle¶
Agent completes bead
│
▼
Bead state: in_progress → done
│
▼
Deacon detects transition (patrol loop)
│
▼
Fire subagent-complete hook:
├── Notify parent agent (if spawned by sub-agent)
├── Update pipeline state
├── Trigger downstream bead release (dependency graph)
└── Log completion event for TEA audit
TypeScript Plugin Hook¶
The Deacon's completion hook interfaces with the OpenClaw TypeScript plugin system:
// Plugin hook signature (PIP-73)
interface SubagentCompleteHook {
onSubagentComplete(event: {
beadId: string;
agentId: string;
completedAt: Date;
outputArtifacts: string[];
exitCode: number;
duration: number;
}): Promise<void>;
}
The TypeScript plugin is responsible for:
- Pipeline state updates — advance the pipeline to the next stage
- Gate validation — verify output artifacts exist and pass quality checks
- Notification routing — inform the Mayor and any waiting parent agents
- TEA audit logging — record the completion event for later quality review
Hook Execution Flow¶
Deacon patrol loop detects bead.done
│
▼
Write hook-event bead to Dolt
│
▼
TypeScript plugin reads hook-event
│
▼
Plugin executes:
├── validate artifacts
├── update pipeline state
├── release dependent beads
└── notify Mayor
│
▼
Hook-event bead marked as processed
Retry Logic: The Escalation Ladder¶
The Deacon implements a three-tier retry/escalation ladder:
Tier 1: Immediate Retry (Attempts 1–3)¶
stale detected → kill agent → re-queue bead → new agent claims
No backoff. No delay. The Deacon treats attempts 1–3 as transient failures — the agent probably crashed, and a fresh agent will succeed.
Tier 2: Geometric Backoff (Attempts 4–7)¶
stale detected → wait (2^attempt seconds) → re-queue bead
Backoff sequence: 2s, 4s, 8s, 16s. The Deacon suspects the bead itself may be problematic (bad instructions, impossible task, corrupted context). Backoff gives the system time to stabilize and gives the human a chance to intervene.
Tier 3: Escalation (Attempt 7+)¶
stale detected → write escalation bead → notify operator via Discord
The Deacon stops retrying and escalates to the human. The escalation includes:
- Full diagnostic history (all 7+ attempts with timestamps and errors)
- The original bead content
- Agent logs from the most recent attempt
- Recommended actions (split bead, fix instructions, manual intervention)
RALPH Integration Detail¶
| Retry Tier | Deacon Action | RALPH Rule |
|---|---|---|
| 1–3 | Immediate re-queue | Standard retry |
| 4–7 | Geometric backoff | Pass diagnostics between attempts |
| 7+ | Escalate to operator | 3 failures on same task → mandatory escalation |
| Same error 2x | Immediate escalation | RALPH hard rule: never retry same error |
Launchd Agent: ai.openclaw.deacon¶
On macOS, the Deacon runs as a launchd agent for automatic startup and supervision:
Launchd Plist¶
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>ai.openclaw.deacon</string>
<key>ProgramArguments</key>
<array>
<string>/usr/local/bin/deacon</string>
<string>--config</string>
<string>~/.openclaw/deacon.toml</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardOutPath</key>
<string>~/.openclaw/logs/deacon.log</string>
<key>StandardErrorPath</key>
<string>~/.openclaw/logs/deacon.err</string>
<key>ThrottleInterval</key>
<integer>10</integer>
</dict>
</plist>
Lifecycle Management¶
# Load the daemon
launchctl load ~/Library/LaunchAgents/ai.openclaw.deacon.plist
# Check status
launchctl list | grep deacon
# Stop the daemon
launchctl unload ~/Library/LaunchAgents/ai.openclaw.deacon.plist
# View logs
tail -f ~/.openclaw/logs/deacon.log
Systemd Equivalent (Linux)¶
[Unit]
Description=OpenClaw Deacon Patrol Daemon
After=network.target dolt.service
[Service]
Type=simple
ExecStart=/usr/local/bin/deacon --config /etc/openclaw/deacon.toml
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
Configuration Reference¶
# deacon.toml — Deacon configuration
[deacon]
patrol_interval = 30 # seconds between patrol cycles
log_level = "info" # debug, info, warn, error
log_format = "json" # json or text
[deacon.timeouts]
stale_warning = 300 # 5 min: log warning
stale_alert = 600 # 10 min: alert Mayor
stale_kill = 900 # 15 min: kill + re-queue
dead_agent = 120 # 2 min: heartbeat missing
[deacon.review]
stale_reminder = 3600 # 1 hour: first reminder
stale_escalate = 86400 # 24 hours: escalate
stale_archive = 604800 # 7 days: archive
[deacon.retry]
max_immediate = 3 # attempts 1-3: no backoff
backoff_base = 2 # seconds, doubles each attempt
max_backoff = 7 # attempt 7+: escalate
same_error_escalate = true # RALPH: same error = immediate escalate
[deacon.discord]
webhook_url = ""
channel_id = ""
mention_on_escalate = true
[deacon.hooks]
fire_on_complete = true # PIP-73: fire subagent-complete hook
hook_timeout = 30 # seconds to wait for hook processing
Interaction with Other Daemons¶
| Daemon | Interaction with Deacon |
|---|---|
| Boot | Handles heartbeat traffic so Deacon patrol loop isn't interrupted |
| Witness | Deacon handles liveness; Witness handles quality. No overlap. |
| Refinery | Deacon re-queues stale beads; Refinery reassigns them |
Kelly vs Gas Town Deacon¶
| Aspect | Gas Town Deacon | Kelly Deacon (PIP-73) |
|---|---|---|
| Language | Go | Go |
| State store | Dolt/Beads | Dolt/Beads |
| Patrol scope | All hooks | All in_progress + in_review beads |
| Completion hooks | Native | PIP-73 TypeScript plugin |
| Retry logic | Re-queue | RALPH-integrated (3-tier ladder) |
| Launchd/systemd | Yes | Yes (ai.openclaw.deacon) |
| Discord alerts | Via Mayor | Direct webhook |
The Kelly Deacon extends Gas Town's Deacon with RALPH integration and direct Discord alerting — Kelly-specific adaptations of the same core pattern.
Related Articles¶
gas-town-daemon-architecture, gas-town-naming-conventions, gas-town-mayor-pattern, steve-yegge-gupp, ralph-protocol, kelly-gas-town-gap-analysis
Source Attribution¶
- steve-yegge-gas-town — Deacon role defined in Gas Town architecture
- steve-yegge-gupp — GUPP as the principle Deacon enforces
- kelly-gas-town-gap-analysis — Kelly vs Gas Town Deacon comparison
- PIP-72, PIP-73 — Internal proposals for Deacon implementation