perf(rpc): use bd daemon protocol to reduce subprocess overhead
Some checks failed
CI / Check for .beads changes (push) Has been cancelled
CI / Check embedded formulas (push) Has been cancelled
CI / Test (push) Has been cancelled
CI / Coverage Report (push) Has been cancelled
CI / Lint (push) Has been cancelled
CI / Integration Tests (push) Has been cancelled
Windows CI / Windows Build and Unit Tests (push) Has been cancelled

Replace bd subprocess calls in gt commands with daemon RPC when available.
Each subprocess call has ~40ms overhead for Go binary startup, so using
the daemon's Unix socket protocol significantly reduces latency.

Changes:
- Add RPC client to beads package (beads_rpc.go)
- Modify List/Show/Update/Close methods to try RPC first, fall back to subprocess
- Replace runBdPrime() with direct content output (avoids bd subprocess)
- Replace checkPendingEscalations() to use beads.List() with RPC
- Replace hook.go bd subprocess calls with beads package methods

The RPC client:
- Connects to daemon via Unix socket at .beads/bd.sock
- Uses JSON-based request/response protocol (same as bd daemon)
- Falls back gracefully to subprocess if daemon unavailable
- Lazy-initializes connection on first use

Performance improvement targets (from bd-2zd.2):
- gt prime < 100ms (was 5.8s with subprocess chain)
- gt hook < 100ms (was ~323ms)

Closes: bd-2zd.2
This commit is contained in:
obsidian
2026-01-24 17:13:07 -08:00
committed by John Ogle
parent 62fb0243b5
commit 2f893f0ad3
4 changed files with 440 additions and 61 deletions

View File

@@ -10,7 +10,6 @@ import (
"github.com/spf13/cobra"
"github.com/steveyegge/gastown/internal/beads"
"github.com/steveyegge/gastown/internal/events"
"github.com/steveyegge/gastown/internal/runtime"
"github.com/steveyegge/gastown/internal/style"
)
@@ -183,15 +182,8 @@ func runHook(_ *cobra.Command, args []string) error {
fmt.Printf("%s Replacing completed bead %s...\n", style.Dim.Render(""), existing.ID)
if !hookDryRun {
if hasAttachment {
// Close completed molecule bead (use bd close --force for pinned)
closeArgs := []string{"close", existing.ID, "--force",
"--reason=Auto-replaced by gt hook (molecule complete)"}
if sessionID := runtime.SessionIDFromEnv(); sessionID != "" {
closeArgs = append(closeArgs, "--session="+sessionID)
}
closeCmd := exec.Command("bd", closeArgs...)
closeCmd.Stderr = os.Stderr
if err := closeCmd.Run(); err != nil {
// Close completed molecule bead (use force for pinned)
if err := b.CloseForced(existing.ID, "Auto-replaced by gt hook (molecule complete)"); err != nil {
return fmt.Errorf("closing completed bead %s: %w", existing.ID, err)
}
} else {
@@ -232,10 +224,9 @@ func runHook(_ *cobra.Command, args []string) error {
return nil
}
// Hook the bead using bd update (discovery-based approach)
hookCmd := exec.Command("bd", "update", beadID, "--status=hooked", "--assignee="+agentID)
hookCmd.Stderr = os.Stderr
if err := hookCmd.Run(); err != nil {
// Hook the bead using beads package (uses RPC when daemon available)
status := beads.StatusHooked
if err := b.Update(beadID, beads.UpdateOptions{Status: &status, Assignee: &agentID}); err != nil {
return fmt.Errorf("hooking bead: %w", err)
}

View File

@@ -2,7 +2,6 @@ package cmd
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"os"
@@ -340,29 +339,13 @@ func detectRole(cwd, townRoot string) RoleInfo {
return ctx
}
// runBdPrime runs `bd prime` and outputs the result.
// This provides beads workflow context to the agent.
// runBdPrime outputs beads workflow context directly.
// This replaces the bd subprocess call to eliminate ~40ms startup overhead.
func runBdPrime(workDir string) {
cmd := exec.Command("bd", "prime")
cmd.Dir = workDir
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
// Skip if bd prime fails (beads might not be available)
// But log stderr if present for debugging
if errMsg := strings.TrimSpace(stderr.String()); errMsg != "" {
fmt.Fprintf(os.Stderr, "bd prime: %s\n", errMsg)
}
return
}
output := strings.TrimSpace(stdout.String())
if output != "" {
content := beads.GetPrimeContent(workDir)
if content != "" {
fmt.Println()
fmt.Println(output)
fmt.Println(content)
}
}
@@ -689,34 +672,20 @@ func ensureBeadsRedirect(ctx RoleContext) {
// checkPendingEscalations queries for open escalation beads and displays them prominently.
// This is called on Mayor startup to surface issues needing human attention.
// Uses beads package which leverages RPC when daemon is available.
func checkPendingEscalations(ctx RoleContext) {
// Query for open escalations using bd list with tag filter
cmd := exec.Command("bd", "list", "--status=open", "--tag=escalation", "--json")
cmd.Dir = ctx.WorkDir
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
// Query for open escalations using beads package (uses RPC when available)
b := beads.New(ctx.WorkDir)
escalations, err := b.List(beads.ListOptions{
Status: "open",
Label: "escalation",
Priority: -1,
})
if err != nil || len(escalations) == 0 {
// Silently skip - escalation check is best-effort
return
}
// Parse JSON output
var escalations []struct {
ID string `json:"id"`
Title string `json:"title"`
Priority int `json:"priority"`
Description string `json:"description"`
Created string `json:"created"`
}
if err := json.Unmarshal(stdout.Bytes(), &escalations); err != nil || len(escalations) == 0 {
// No escalations or parse error
return
}
// Count by severity
critical := 0
high := 0