From cb97b3640ce19ea8a3f6a9d06808a1ac5f19bc4f Mon Sep 17 00:00:00 2001 From: Levi Neely Date: Sat, 14 Mar 2026 20:13:17 +0100 Subject: [PATCH] p9: remove pending_approval/pending_review from protocol MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Drop status constants, readPendingFromStore, pending-approval/pending-review/resume ctl handlers, and the pending virtual file from the 9P directory listing. No migration needed — status is a string field; unused values simply won't appear. Co-Authored-By: Claude Sonnet 4.6 --- internal/p9/beads.go | 94 ------------------------------------------- internal/p9/server.go | 6 +-- 2 files changed, 1 insertion(+), 99 deletions(-) diff --git a/internal/p9/beads.go b/internal/p9/beads.go index c59a2c0..bde9ae3 100644 --- a/internal/p9/beads.go +++ b/internal/p9/beads.go @@ -18,15 +18,6 @@ import ( "go.uber.org/zap" ) -// Approval gate statuses extend the beads status set locally. -// These statuses are not surfaced by GetReadyWork (which only returns -// open/in_progress), ensuring bots do not claim work that is pending -// human approval or review. -const ( - StatusPendingApproval = bd.Status("pending_approval") // Awaiting APPROVAL_RESPONSE from human - StatusPendingReview = bd.Status("pending_review") // Awaiting REVIEW_RESPONSE from human -) - // Capability level labels — portable model-tier hints carried on beads. // These are label values of the form "capability:". The Conductor // maps them to backend-specific model names at session-spawn time. @@ -260,8 +251,6 @@ func (b *BeadsFS) readFromMount(m *MountedProject, endpoint string) ([]byte, err limit = 100 } return b.readReadyFromStore(m.store, limit) - case len(parts) == 1 && parts[0] == "pending": - return b.readPendingFromStore(m.store) case len(parts) == 1 && parts[0] == "blocked": return b.readBlockedFromStore(m.store) case len(parts) == 1 && parts[0] == "stale": @@ -542,24 +531,6 @@ func (b *BeadsFS) readBlockedFromStore(store bd.Storage) ([]byte, error) { return json.MarshalIndent(blocked, "", " ") } -// readPending returns beads in pending_approval or pending_review status, -// i.e. those awaiting a human APPROVAL_RESPONSE or REVIEW_RESPONSE. - -func (b *BeadsFS) readPendingFromStore(store bd.Storage) ([]byte, error) { - issues, err := store.SearchIssues(b.ctx, "", bd.IssueFilter{}) - if err != nil { - return nil, err - } - pending := []*bd.Issue{} - for _, issue := range issues { - if issue.Status == StatusPendingApproval || issue.Status == StatusPendingReview { - pending = append(pending, issue) - } - } - return json.MarshalIndent(pending, "", " ") -} - - func (b *BeadsFS) readDependenciesMetaFromStore(store bd.Storage, beadID string) ([]byte, error) { deps, err := store.GetDependenciesWithMetadata(b.ctx, beadID) if err != nil { @@ -937,71 +908,6 @@ func (b *BeadsFS) executeCtlOnStore(store bd.Storage, cmd string) error { } return store.CreateIssues(b.ctx, issues, actor) - // Approval gate commands. - // Bots should call these after sending an APPROVAL_REQUEST or REVIEW_REQUEST - // to the user. The human responds via the inbox UI; on approve/reject the - // bot calls "resume" or "fail" to continue or abort work. - - case "pending-approval": - // Atomically set status=pending_approval and assignee (defaults to "user"). - // Usage: pending-approval [assignee] - // Canonical workflow: - // 1. Bot sends APPROVAL_REQUEST to user mailbox - // 2. Bot calls: echo "pending-approval " | 9p write anvillm/beads/ctl - // 3. Human approves → bot calls: echo "resume " | 9p write anvillm/beads/ctl - // 4. Human rejects → bot calls: echo "fail 'rejected'" | 9p write anvillm/beads/ctl - if len(args) < 1 { - return fmt.Errorf("usage: pending-approval [assignee]") - } - assignee := "user" - if len(args) > 1 { - assignee = args[1] - } - updates := map[string]any{ - "status": StatusPendingApproval, - "assignee": assignee, - } - return store.UpdateIssue(b.ctx, args[0], updates, actor) - - case "pending-review": - // Atomically set status=pending_review and assignee (defaults to "user"). - // Usage: pending-review [assignee] - // Canonical workflow: - // 1. Bot sends REVIEW_REQUEST to user mailbox - // 2. Bot calls: echo "pending-review " | 9p write anvillm/beads/ctl - // 3. Human responds → bot calls: echo "resume " | 9p write anvillm/beads/ctl - // 4. Human rejects → bot calls: echo "fail 'review failed'" | 9p write anvillm/beads/ctl - if len(args) < 1 { - return fmt.Errorf("usage: pending-review [assignee]") - } - assignee := "user" - if len(args) > 1 { - assignee = args[1] - } - updates := map[string]any{ - "status": StatusPendingReview, - "assignee": assignee, - } - return store.UpdateIssue(b.ctx, args[0], updates, actor) - - case "resume": - // Atomically set status=in_progress and assignee after human approval/review. - // Usage: resume [assignee] - // Assignee defaults to "user"; pass the bot's agent ID to hand work back to a bot. - // Use "fail" instead if the human rejected. - if len(args) < 1 { - return fmt.Errorf("usage: resume [assignee]") - } - assignee := "user" - if len(args) > 1 { - assignee = args[1] - } - updates := map[string]any{ - "status": bd.StatusInProgress, - "assignee": assignee, - } - return store.UpdateIssue(b.ctx, args[0], updates, actor) - case "unclaim": // Atomically clear assignee and reset status to open. // Usage: unclaim diff --git a/internal/p9/server.go b/internal/p9/server.go index 82f0b94..2b99bd7 100644 --- a/internal/p9/server.go +++ b/internal/p9/server.go @@ -555,7 +555,7 @@ func (s *Server) walk(cs *connState, fc *plan9.Fcall) *plan9.Fcall { if _, isMount := mounts[mountName]; isMount { // Check if name is a control file or bead ID switch name { - case "cwd", "ctl", "list", "ready", "pending", "stats", "blocked", "stale", "query", "config": + case "cwd", "ctl", "list", "ready", "stats", "blocked", "stale", "query", "config": qid = plan9.Qid{Type: QTFile, Path: qidBeadsBase + hashID(mountName+name)} newPath = path + "/" + name default: @@ -1235,10 +1235,6 @@ func (s *Server) readDir(path string, offset uint64, count uint32) []byte { Qid: plan9.Qid{Type: QTFile, Path: qidBeadsBase + hashID(mountName+"ready")}, Mode: 0444, Name: "ready", Uid: "q", Gid: "q", Muid: "q", }) - dirs = append(dirs, plan9.Dir{ - Qid: plan9.Qid{Type: QTFile, Path: qidBeadsBase + hashID(mountName+"pending")}, Mode: 0444, Name: "pending", - Uid: "q", Gid: "q", Muid: "q", - }) dirs = append(dirs, plan9.Dir{ Qid: plan9.Qid{Type: QTFile, Path: qidBeadsBase + hashID(mountName+"stats")}, Mode: 0444, Name: "stats", Uid: "q", Gid: "q", Muid: "q",