Implement handoffs using pinned beads (gt-cu7r)

Replace mail-based handoff system with pinned beads that persist
across sessions. This fixes the issue where handoff messages get
closed before successors can read them.

Changes:
- beads: Add StatusPinned constant and handoff functions:
  - HandoffBeadTitle() for well-known naming
  - FindHandoffBead() to locate role handoff bead
  - GetOrCreateHandoffBead() to ensure bead exists
  - UpdateHandoffContent() to set handoff message
  - ClearHandoffContent() to reset after reading

- cmd/handoff: Update to use pinned beads instead of mail
  - sendHandoffMail() now updates pinned bead content

- cmd/prime: Display handoff content on startup
  - outputHandoffContent() reads and shows handoff bead

- cmd/rig: Add reset command with --handoff flag
  - gt rig reset --handoff clears handoff content

Generated with Claude Code

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Steve Yegge
2025-12-19 01:51:14 -08:00
parent 717bc89132
commit 4edacde590
4 changed files with 196 additions and 31 deletions
+16 -28
View File
@@ -10,6 +10,7 @@ import (
"time"
"github.com/spf13/cobra"
"github.com/steveyegge/gastown/internal/beads"
"github.com/steveyegge/gastown/internal/style"
"github.com/steveyegge/gastown/internal/workspace"
)
@@ -95,12 +96,12 @@ func runHandoff(cmd *cobra.Command, args []string) error {
}
}
// For cycle, send handoff mail to self
// For cycle, update handoff bead for successor
if action == HandoffCycle {
if err := sendHandoffMail(role, townRoot); err != nil {
return fmt.Errorf("sending handoff mail: %w", err)
return fmt.Errorf("updating handoff bead: %w", err)
}
fmt.Printf("%s Sent handoff mail to self\n", style.Bold.Render("✓"))
fmt.Printf("%s Updated handoff bead for successor\n", style.Bold.Render("✓"))
}
// Send lifecycle request to manager
@@ -236,24 +237,12 @@ func getManager(role Role) string {
}
}
// sendHandoffMail sends a handoff message to ourselves for the successor to read.
// sendHandoffMail updates the pinned handoff bead for the successor to read.
func sendHandoffMail(role Role, townRoot string) error {
// Determine our address
var selfAddr string
switch role {
case RoleMayor:
selfAddr = "mayor/"
case RoleWitness:
selfAddr = "witness/" // Would need rig prefix
default:
selfAddr = string(role) + "/"
}
// Build handoff message
subject := "🤝 HANDOFF: Session cycling"
body := handoffMessage
if body == "" {
body = fmt.Sprintf(`Handoff from previous session.
// Build handoff content
content := handoffMessage
if content == "" {
content = fmt.Sprintf(`🤝 HANDOFF: Session cycling
Time: %s
Role: %s
@@ -264,15 +253,14 @@ Check gt mail inbox for messages received during transition.
`, time.Now().Format(time.RFC3339), role)
}
// Send via bd mail (syntax: bd mail send <recipient> -s <subject> -m <body>)
cmd := exec.Command("bd", "mail", "send", selfAddr,
"-s", subject,
"-m", body,
)
cmd.Dir = townRoot
// Determine the handoff role key
// For role-specific handoffs, use the role name
roleKey := string(role)
if out, err := cmd.CombinedOutput(); err != nil {
return fmt.Errorf("%w: %s", err, string(out))
// Update the pinned handoff bead
bd := beads.New(townRoot)
if err := bd.UpdateHandoffContent(roleKey, content); err != nil {
return fmt.Errorf("updating handoff bead: %w", err)
}
return nil