feat(deacon): implement bulletproof pause mechanism (gt-bpo2c) (#265)
Add multi-layer pause mechanism to prevent Deacon from causing damage: Layer 1: File-based pause state - Location: ~/.runtime/deacon/paused.json - Stores: paused flag, reason, timestamp, paused_by Layer 2: Commands - `gt deacon pause [--reason="..."]` - pause with optional reason - `gt deacon resume` - remove pause file - `gt deacon status` - shows pause state prominently Layer 3: Guards - `gt prime` for deacon: shows PAUSED message, skips patrol context - `gt deacon heartbeat`: fails when paused Helper package: - internal/deacon/pause.go with IsPaused/Pause/Resume functions Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -17,6 +17,7 @@ import (
|
||||
"github.com/steveyegge/gastown/internal/beads"
|
||||
"github.com/steveyegge/gastown/internal/checkpoint"
|
||||
"github.com/steveyegge/gastown/internal/constants"
|
||||
"github.com/steveyegge/gastown/internal/deacon"
|
||||
"github.com/steveyegge/gastown/internal/events"
|
||||
"github.com/steveyegge/gastown/internal/lock"
|
||||
"github.com/steveyegge/gastown/internal/rig"
|
||||
@@ -601,6 +602,11 @@ func outputStartupDirective(ctx RoleContext) {
|
||||
fmt.Println(" - If attachment found → **RUN IT** (no human input needed)")
|
||||
fmt.Println(" - If no attachment → await user instruction")
|
||||
case RoleDeacon:
|
||||
// Skip startup protocol if paused - the pause message was already shown
|
||||
paused, _, _ := deacon.IsPaused(ctx.TownRoot)
|
||||
if paused {
|
||||
return
|
||||
}
|
||||
fmt.Println()
|
||||
fmt.Println("---")
|
||||
fmt.Println()
|
||||
@@ -911,6 +917,13 @@ func showMoleculeProgress(b *beads.Beads, rootID string) {
|
||||
// Deacon uses wisps (Wisp:true issues in main .beads/) for patrol cycles.
|
||||
// Deacon is a town-level role, so it uses town root beads (not rig beads).
|
||||
func outputDeaconPatrolContext(ctx RoleContext) {
|
||||
// Check if Deacon is paused - if so, output PAUSED message and skip patrol context
|
||||
paused, state, err := deacon.IsPaused(ctx.TownRoot)
|
||||
if err == nil && paused {
|
||||
outputDeaconPausedMessage(state)
|
||||
return
|
||||
}
|
||||
|
||||
cfg := PatrolConfig{
|
||||
RoleName: "deacon",
|
||||
PatrolMolName: "mol-deacon-patrol",
|
||||
@@ -930,6 +943,32 @@ func outputDeaconPatrolContext(ctx RoleContext) {
|
||||
outputPatrolContext(cfg)
|
||||
}
|
||||
|
||||
// outputDeaconPausedMessage outputs a prominent PAUSED message for the Deacon.
|
||||
// When paused, the Deacon must not perform any patrol actions.
|
||||
func outputDeaconPausedMessage(state *deacon.PauseState) {
|
||||
fmt.Println()
|
||||
fmt.Printf("%s\n\n", style.Bold.Render("## ⏸️ DEACON PAUSED"))
|
||||
fmt.Println("You are paused and must NOT perform any patrol actions.")
|
||||
fmt.Println()
|
||||
if state.Reason != "" {
|
||||
fmt.Printf("Reason: %s\n", state.Reason)
|
||||
}
|
||||
fmt.Printf("Paused at: %s\n", state.PausedAt.Format(time.RFC3339))
|
||||
if state.PausedBy != "" {
|
||||
fmt.Printf("Paused by: %s\n", state.PausedBy)
|
||||
}
|
||||
fmt.Println()
|
||||
fmt.Println("Wait for human to run `gt deacon resume` before working.")
|
||||
fmt.Println()
|
||||
fmt.Println("**DO NOT:**")
|
||||
fmt.Println("- Create patrol molecules")
|
||||
fmt.Println("- Run heartbeats")
|
||||
fmt.Println("- Check agent health")
|
||||
fmt.Println("- Take any autonomous actions")
|
||||
fmt.Println()
|
||||
fmt.Println("You may respond to direct human questions.")
|
||||
}
|
||||
|
||||
// outputWitnessPatrolContext shows patrol molecule status for the Witness.
|
||||
// Witness AUTO-BONDS its patrol molecule on startup if one isn't already running.
|
||||
func outputWitnessPatrolContext(ctx RoleContext) {
|
||||
|
||||
Reference in New Issue
Block a user