fix(herdr): B5 — replace invented pane.kill/release/rotation_signal with real methods

Verified against a live herdr instance (192.168.1.105:9245) that pane.kill,
pane.release, and pane.rotation_signal never existed in the protocol, as
AUDIT.md's B5 suspected. Real method list captured in deploy/herdr-schema.json.

- Kill now calls the real pane.close({pane_id}).
- RotationSignal interface/method/call-site deleted; no real equivalent exists.
- Release now refuses loudly instead of calling a nonexistent method — the
  real pane.release_agent can't return a handoff_ref either way (herdr
  doesn't write handoffs, the agent does), so a real fix needs Phase 4
  handoff production first.

Also documents Phase 0 findings in AUDIT.md/progress.md, and adds
CLAUDE.md/AGENTS.md with project-specific knowledge (herdr protocol facts,
deployment topology, a currently-stuck live task, the federation fork) for
future sessions.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W1rkJ2hBMybnJctPbcy4tT
This commit is contained in:
kami
2026-07-27 21:05:53 +04:00
parent 02d93fb63d
commit 63cda5557e
7 changed files with 352 additions and 28 deletions
+12 -18
View File
@@ -26,9 +26,6 @@ type WorktreeCreator interface {
type TurnBoundary interface {
AtTurnBoundary(context.Context, Session) (bool, error)
}
type RotationSignal interface {
RotationSignal(context.Context, Session) (string, error)
}
type PaneExit interface {
PaneExited(context.Context, Session) (bool, error)
}
@@ -78,15 +75,21 @@ func (a CLIAdapter) Lease(ctx context.Context, task, worktree string) (Session,
func (a CLIAdapter) Bootstrap(ctx context.Context, s Session, ref string) error {
return a.Client.Prompt(ctx, s.PaneID, fmt.Sprintf("Read handoff %s, validate the anchor and TASK.md, then continue.", ref), time.Minute)
}
// Release previously called the invented "pane.release" method expecting a
// handoff_ref back. Neither exists in the real protocol (confirmed against a
// live herdr instance, AUDIT.md Phase 0): the real method is
// pane.release_agent(pane_id, source, agent), which only releases herdr's
// claim on the agent session — it cannot return a handoff_ref, because herdr
// does not write handoffs, the agent does (§6.1). Wiring this correctly needs
// the Phase 4 handoff-production path (agent writes handoff, stop hook
// uploads it to CAS, plane validates and mints the ref) before Release has
// anything real to return. Refusing loudly until then rather than calling a
// method that doesn't exist.
func (a CLIAdapter) Release(ctx context.Context, s Session) (string, error) {
var r struct {
Ref string `json:"handoff_ref"`
}
e := a.Client.Call(ctx, "pane.release", s, &r)
return r.Ref, e
return "", fmt.Errorf("adapter: Release not implemented — pane.release is not a real herdr method and handoff production (AUDIT.md Phase 4) is not wired yet")
}
func (a CLIAdapter) Kill(ctx context.Context, s Session) error {
return a.Client.Call(ctx, "pane.kill", s, nil)
return a.Client.Call(ctx, "pane.close", map[string]any{"pane_id": s.PaneID}, nil)
}
func (a CLIAdapter) AtTurnBoundary(ctx context.Context, s Session) (bool, error) {
status, err := a.AgentStatus(ctx, s)
@@ -179,15 +182,6 @@ func statusFromAgentResult(v any) string {
var _ = json.RawMessage{}
func (a CLIAdapter) RotationSignal(ctx context.Context, s Session) (string, error) {
var r struct {
Reason string `json:"reason"`
}
if err := a.Client.Call(ctx, "pane.rotation_signal", s, &r); err != nil {
return "", err
}
return r.Reason, nil
}
// Occupancy reads the harness's own session state — never the herdr pane id,
// which ClaudeUsage/CodexUsage/OpenCodeUsage cannot open (spec §5.2.1: "the
// whole rotation system rests on this number"). A session file that cannot
+1 -6
View File
@@ -451,13 +451,8 @@ func (c *Coordinator) rotate(ctx context.Context, hard float64) {
continue
}
reason := "threshold"
if signal, ok := a.(herdr.RotationSignal); ok {
if r, signalErr := signal.RotationSignal(ctx, session); signalErr == nil && r != "" {
reason = r
}
}
occupancy, err := a.Occupancy(session)
if err != nil || (occupancy < hard && reason == "threshold") {
if err != nil || occupancy < hard {
continue
}
// Face B is treated as required, not best-effort (spec §5.2/§5.3):