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:
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user