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:

  1. Last activity timestamp — when did the assigned agent last touch this bead?
  2. Hook age — how long has this bead been on the agent's hook without progress?
  3. 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.


gas-town-daemon-architecture, gas-town-naming-conventions, gas-town-mayor-pattern, steve-yegge-gupp, ralph-protocol, kelly-gas-town-gap-analysis

Source Attribution