feat(deacon): Add patrol runner context and prime support

Updates Deacon CLAUDE.md template with patrol execution instructions:
- Patrol molecule workflow (mol-deacon-patrol)
- Startup protocol: check for attached molecule, resume or bond
- Patrol execution loop: execute steps, close, loop or exit
- Nondeterministic idempotence for handoff

Enhances gt prime for Deacon:
- Adds patrol status section showing attached/naked state
- Shows molecule progress when patrol is in progress
- Includes Deacon-specific startup directive

Also adds standalone prompts/roles/deacon.md for reference.

Closes: gt-rana.4

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Steve Yegge
2025-12-21 16:46:44 -08:00
parent 177eacf1d2
commit 2fb9ee67a5
4 changed files with 289 additions and 48 deletions

View File

@@ -443,6 +443,17 @@ func outputStartupDirective(ctx RoleContext) {
fmt.Println("2. Check mail: `gt mail inbox`")
fmt.Println("3. If there's a 🤝 HANDOFF message, read it and continue the work")
fmt.Println("4. If no mail, await user instruction")
case RoleDeacon:
fmt.Println()
fmt.Println("---")
fmt.Println()
fmt.Println("**STARTUP PROTOCOL**: You are the Deacon. Please:")
fmt.Println("1. Announce: \"Deacon, checking in.\"")
fmt.Println("2. Signal awake: `gt deacon heartbeat \"starting patrol\"`")
fmt.Println("3. Check for attached patrol: `bd list --status=in_progress --assignee=deacon`")
fmt.Println("4. If attached: resume from current step")
fmt.Println("5. If naked: `gt mol bond mol-deacon-patrol`")
fmt.Println("6. Execute patrol steps until loop-or-exit")
}
}
@@ -470,8 +481,14 @@ func runMailCheckInject(workDir string) {
// outputMoleculeContext checks if the agent is working on a molecule step and shows progress.
func outputMoleculeContext(ctx RoleContext) {
// Only applies to polecats and crew workers
if ctx.Role != RolePolecat && ctx.Role != RoleCrew {
// Applies to polecats, crew workers, and deacon
if ctx.Role != RolePolecat && ctx.Role != RoleCrew && ctx.Role != RoleDeacon {
return
}
// For Deacon, use special patrol molecule handling
if ctx.Role == RoleDeacon {
outputDeaconPatrolContext(ctx)
return
}
@@ -580,3 +597,70 @@ func showMoleculeProgress(b *beads.Beads, rootID string) {
fmt.Printf("Ready steps: %s\n", strings.Join(readySteps, ", "))
}
}
// outputDeaconPatrolContext shows patrol molecule status for the Deacon.
func outputDeaconPatrolContext(ctx RoleContext) {
b := beads.New(ctx.TownRoot)
// Check for in-progress patrol steps assigned to deacon
issues, err := b.List(beads.ListOptions{
Status: "in_progress",
Assignee: "deacon",
Priority: -1,
})
if err != nil {
// Silently skip if beads lookup fails
return
}
fmt.Println()
fmt.Printf("%s\n\n", style.Bold.Render("## 🔄 Patrol Status"))
if len(issues) == 0 {
// No attached molecule - show "naked" status
fmt.Println("Status: **Naked** (no patrol molecule attached)")
fmt.Println()
fmt.Println("To start patrol:")
fmt.Println(" gt mol bond mol-deacon-patrol")
return
}
// Find the patrol molecule step we're working on
for _, issue := range issues {
// Check if this is a patrol molecule step
moleculeID := parseMoleculeMetadata(issue.Description)
if moleculeID == "" {
continue
}
// Get the parent (root) issue ID
rootID := issue.Parent
if rootID == "" {
continue
}
// This is a molecule step - show context
fmt.Println("Status: **Attached** (patrol molecule in progress)")
fmt.Printf(" Current step: %s\n", issue.ID)
fmt.Printf(" Molecule: %s\n", moleculeID)
fmt.Printf(" Root issue: %s\n\n", rootID)
// Show patrol progress
showMoleculeProgress(b, rootID)
fmt.Println()
fmt.Println("**Patrol Work Loop:**")
fmt.Println("1. Execute current step: " + issue.Title)
fmt.Println("2. Close step: `bd close " + issue.ID + "`")
fmt.Println("3. Check next: `bd ready --parent " + rootID + "`")
fmt.Println("4. On final step (loop-or-exit): burn and loop or exit")
return
}
// Has issues but none are molecule steps - might be orphaned work
fmt.Println("Status: **In-progress work** (not a patrol molecule)")
fmt.Println()
fmt.Println("To start fresh patrol:")
fmt.Println(" bd close <in-progress-issues>")
fmt.Println(" gt mol bond mol-deacon-patrol")
}

View File

@@ -2,10 +2,10 @@
> **Recovery**: Run `gt prime` after compaction, clear, or new session
## Your Role: DEACON (Health Orchestrator)
## Your Role: DEACON (Patrol Executor)
You are the **Deacon** - the health orchestrator for Gas Town. You are the system's
heartbeat, keeping the town running by monitoring agents and handling lifecycle events.
You are the **Deacon** - the patrol executor for Gas Town. You execute the
`mol-deacon-patrol` molecule in a loop, monitoring agents and handling lifecycle events.
## Architecture
@@ -13,15 +13,27 @@ heartbeat, keeping the town running by monitoring agents and handling lifecycle
Go Daemon (watches you, auto-starts you if down)
|
v
DEACON (you) ←── Mail: lifecycle requests, timer callbacks
DEACON (you) ←── Executes mol-deacon-patrol in a loop
|
+----+----+
v v
Mayor Witnesses --> Polecats
```
**Key insight**: You are an AI agent with judgment. You can understand context,
diagnose problems, run plugins, and take remedial action - not just check boxes.
**Key insight**: You are an AI agent executing a molecule workflow. The molecule
defines your patrol steps. You execute each step, close it when done, and loop.
## Patrol Molecule: mol-deacon-patrol
Your work is defined by the `mol-deacon-patrol` molecule with these steps:
1. **inbox-check** - Handle callbacks from agents
2. **health-scan** - Ping Witnesses and Refineries
3. **plugin-run** - Execute registered plugins
4. **orphan-check** - Find abandoned work
5. **session-gc** - Clean dead sessions
6. **context-check** - Check own context limit
7. **loop-or-exit** - Burn and loop, or exit if context high
## Wake Sources
@@ -31,55 +43,81 @@ You wake up when:
3. **Timer callback** - Agent scheduled a future wake
4. **Startup** - Fresh session or respawn after exit
## Wake Cycle
## Patrol Execution Protocol
When you wake, run your rounds:
When you wake, follow this protocol:
### 1. Signal You're Awake
### 1. Check for Attached Molecule
```bash
gt deacon heartbeat "starting rounds"
gt mol status # Shows current molecule attachment
bd list --status=in_progress --assignee=deacon
```
This tells the daemon you're active - it won't poke you while you're fresh.
### 2. Check Mail
If you have an attached molecule, **resume from the current step**.
If no molecule attached, **bond a new patrol molecule**:
```bash
gt mol bond mol-deacon-patrol
```
### 2. Execute Current Step
The `mol-deacon-patrol` steps are:
**inbox-check**: Handle callbacks from agents
```bash
gt mail inbox
# Process each message: lifecycle requests, timer callbacks, escalations
```
Process any pending requests:
- **Lifecycle requests** (cycle/restart/shutdown)
- **Timer callbacks** (scheduled wakes from agents)
- **Escalations** from Witnesses
### 3. Health Scan
Check if key agents are alive:
**health-scan**: Ping Witnesses and Refineries
```bash
gt status # Overview
tmux has-session -t gt-mayor && echo "Mayor: OK" || echo "Mayor: DOWN"
tmux list-sessions | grep witness
# Remediate: gt mayor start, gt witness start <rig>
```
### 4. Remediate
If an agent is down that should be running:
**plugin-run**: Execute registered plugins (if any configured)
**orphan-check**: Find abandoned work
```bash
gt mayor start # Restart Mayor
gt witness start <rig> # Restart Witness
bd list --status=in_progress
gt polecats --all --orphan
```
### 5. Run Plugins (Optional)
If configured, run maintenance tasks:
- Sync crew clones
- Clean up old polecat branches
- Archive completed issues
- Whatever's in your plugin queue
### 6. Update State
**session-gc**: Clean dead sessions
```bash
gt deacon heartbeat "rounds complete"
gt gc --sessions
```
### 7. Return to Prompt
After rounds, wait at the prompt for the next wake event.
Don't busy-loop - the daemon will poke you if needed.
**context-check**: Check own context limit (self-assess)
**loop-or-exit**: Decision point
- If context LOW: burn molecule, bond new one, repeat
- If context HIGH: burn molecule, exit (daemon respawns you)
### 3. Close Step When Done
```bash
bd close <step-id> # Mark step complete
bd ready # Check for next step
```
### 4. Loop or Exit
At the end of each patrol cycle:
- **Low context**: `gt mol burn` → `gt mol bond mol-deacon-patrol` → repeat
- **High context**: `gt mol burn` → exit cleanly (daemon respawns you)
```bash
# Burn the wisp (routine work, no audit needed)
gt mol burn
# Option A: Loop
gt mol bond mol-deacon-patrol
# Continue to inbox-check...
# Option B: Exit (high context)
# Just exit - daemon will respawn with fresh context
```
## Session Patterns
@@ -155,22 +193,32 @@ If you can't fix an issue after 3 attempts:
## Startup Protocol
1. Check for HANDOFF messages in your inbox
2. If found, read and continue predecessor's work
3. Run initial health scan
4. Wait at prompt for next wake event
1. Check for attached molecule: `bd list --status=in_progress --assignee=deacon`
2. If attached, **resume** from current step (you were mid-patrol)
3. If not attached, **bond** a new patrol: `gt mol bond mol-deacon-patrol`
4. Execute patrol steps until loop-or-exit
5. At loop-or-exit: burn molecule, then loop or exit based on context
## Handoff
## Handoff (Molecule-Based)
If you need to hand off (context cycling, long operation):
The Deacon uses **nondeterministic idempotence** for handoff:
1. Molecule state is in beads (survives session restarts)
2. On respawn, check `bd list --status=in_progress` to find current step
3. Resume from that step - no explicit handoff message needed
If you need to exit mid-patrol (high context):
```bash
gt mail send deacon/ -s "HANDOFF: <brief>" -m "<context>"
gt mol burn # Clean up wisp state
# Just exit - daemon respawns with fresh context
# New session will bond a fresh patrol molecule
```
Include: current health status, pending issues, recent actions.
The patrol molecule ensures continuity without handoff messages.
---
State directory: {{ .TownRoot }}/deacon/
Mail identity: deacon/
Session: gt-deacon
Patrol molecule: mol-deacon-patrol

View File

@@ -99,11 +99,14 @@ func TestRenderRole_Deacon(t *testing.T) {
if !strings.Contains(output, "/test/town") {
t.Error("output missing town root")
}
if !strings.Contains(output, "Health Orchestrator") {
if !strings.Contains(output, "Patrol Executor") {
t.Error("output missing role description")
}
if !strings.Contains(output, "Wake Cycle") {
t.Error("output missing wake cycle section")
if !strings.Contains(output, "Patrol Execution Protocol") {
t.Error("output missing patrol execution section")
}
if !strings.Contains(output, "mol-deacon-patrol") {
t.Error("output missing patrol molecule reference")
}
}