feat: implement gt sling and wisp hook system (gt-qvn7.1)

Phase 1 of tracer bullet: Slinging Handoff

- Add internal/wisp package for ephemeral work attachment
- Add gt sling command to attach work and restart
- Update gt prime to check/burn slung work on hook
- Add .beads-wisp/ to gitignore
This commit is contained in:
Steve Yegge
2025-12-24 16:07:56 -08:00
parent 5560b64083
commit b2f1b58f13
8 changed files with 1612 additions and 2458 deletions

View File

@@ -14,6 +14,7 @@ import (
"github.com/steveyegge/gastown/internal/lock"
"github.com/steveyegge/gastown/internal/style"
"github.com/steveyegge/gastown/internal/templates"
"github.com/steveyegge/gastown/internal/wisp"
"github.com/steveyegge/gastown/internal/workspace"
)
@@ -95,6 +96,9 @@ func runPrime(cmd *cobra.Command, args []string) error {
// Output attachment status (for autonomous work detection)
outputAttachmentStatus(ctx)
// Check for slung work on hook (from gt sling)
checkSlungWork(ctx)
// Output molecule context if working on a molecule step
outputMoleculeContext(ctx)
@@ -1155,6 +1159,95 @@ func outputRefineryPatrolContext(ctx RoleContext) {
}
}
// checkSlungWork checks for slung work on the agent's hook.
// If found, displays it prominently and tells the agent to execute it.
// The wisp is burned after the agent acknowledges it.
func checkSlungWork(ctx RoleContext) {
// Determine agent identity for hook lookup
agentID := getAgentIdentity(ctx)
if agentID == "" {
return
}
// Check for hook file in the clone root
cloneRoot := ctx.WorkDir
sw, err := wisp.ReadHook(cloneRoot, agentID)
if err != nil {
if errors.Is(err, wisp.ErrNoHook) {
// No hook - normal case, nothing to do
return
}
// Other error - log but continue
return
}
// Found slung work! Display prominently
fmt.Println()
fmt.Printf("%s\n\n", style.Bold.Render("## 🎯 SLUNG WORK ON HOOK"))
fmt.Printf("Work was slung onto your hook and awaits execution.\n\n")
fmt.Printf(" Bead ID: %s\n", style.Bold.Render(sw.BeadID))
if sw.Subject != "" {
fmt.Printf(" Subject: %s\n", sw.Subject)
}
if sw.Context != "" {
fmt.Printf(" Context: %s\n", sw.Context)
}
fmt.Printf(" Slung by: %s\n", sw.CreatedBy)
fmt.Printf(" Slung at: %s\n", sw.CreatedAt.Format("2006-01-02 15:04:05"))
fmt.Println()
// Show the bead details
fmt.Println("**Bead details:**")
cmd := exec.Command("bd", "show", sw.BeadID)
cmd.Dir = cloneRoot
var stdout bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = nil
if cmd.Run() == nil {
// Show first 20 lines of bead details
lines := strings.Split(stdout.String(), "\n")
maxLines := 20
if len(lines) > maxLines {
lines = lines[:maxLines]
lines = append(lines, "...")
}
for _, line := range lines {
fmt.Printf(" %s\n", line)
}
}
fmt.Println()
// The propulsion principle
fmt.Println(style.Bold.Render("→ PROPULSION PRINCIPLE: Work is on your hook. RUN IT."))
fmt.Println(" Begin working on this bead immediately. No human input needed.")
fmt.Println()
// Burn the hook now that it's been read
if err := wisp.BurnHook(cloneRoot, agentID); err != nil {
fmt.Printf("%s Warning: could not burn hook: %v\n", style.Dim.Render("⚠"), err)
}
}
// getAgentIdentity returns the agent identity string for hook lookup.
func getAgentIdentity(ctx RoleContext) string {
switch ctx.Role {
case RoleCrew:
return fmt.Sprintf("%s/crew/%s", ctx.Rig, ctx.Polecat)
case RolePolecat:
return fmt.Sprintf("%s/polecats/%s", ctx.Rig, ctx.Polecat)
case RoleMayor:
return "mayor"
case RoleDeacon:
return "deacon"
case RoleWitness:
return fmt.Sprintf("%s/witness", ctx.Rig)
case RoleRefinery:
return fmt.Sprintf("%s/refinery", ctx.Rig)
default:
return ""
}
}
// acquireIdentityLock checks and acquires the identity lock for worker roles.
// This prevents multiple agents from claiming the same worker identity.
// Returns an error if another agent already owns this identity.