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
+112
View File
@@ -0,0 +1,112 @@
# Orchestra
A Go implementation of `orchestra-spec (1).md` — an unattended multi-agent
task orchestrator that leases coding tasks to CLI harnesses (Claude Code,
Codex, opencode) running inside `herdr`-managed panes, rotates them across
context-window limits, and hands off work via a git-anchored continuity
protocol.
Layout: `internal/{domain,store,provider,registry,router,herdr,orchestrator,
continuity,federation,delivery,authz,operations,admin}` + `cmd/orchestra/main.go`.
## Ground truth over documentation
This repo has a documented history of code that *looks* wired but isn't —
packages with tests that pass in isolation while the live call path silently
no-ops (bare `continue` on error, discarded return values). See `AUDIT.md`
for the full audit and `progress.md` for a running log. **Before trusting a
claim in progress.md that something "works" or "is fixed," check the actual
call site** — the file is written by past sessions of this same assistant and
has previously overstated completion.
The single most reliable way to verify herdr-adapter code is right: don't
read `internal/herdr/adapter.go` and assume the method names are real. Ping
the live herdr instance and check.
## herdr protocol — verified against a live instance, 2026-07-27
- herdr speaks JSON-RPC over a raw TCP (or unix-socket) connection — **not
HTTP**. `internal/herdr/herdr.go`'s `Client.Call` is the only correct way
to talk to it; a bare `curl` to the port returns nothing.
- Request shape: `{"id":"<n>","method":"<name>","params":<object>}`. Herdr's
Rust JSON-RPC decoder requires `params` to be present and rejects a bare
`null` — always send `{}` for parameterless calls (the client does this
automatically).
- Full real method list is committed at `deploy/herdr-schema.json`, captured
live from `192.168.1.105:9245` (the `workpc` herdr) since no local `herdr`
CLI is available in this sandbox — the schema was reconstructed by sending
an unknown method name and reading the `unknown variant ... expected one
of ...` error, then probing each method of interest with `params:{}` /
`params:{pane_id:"nonexistent"}` to read Rust serde's `missing field
<x>` errors for its param shape.
- **Confirmed invented (do not use, they don't exist):** `pane.release`,
`pane.kill`, `pane.rotation_signal`, `pane.status`. If you see these
anywhere, it's a bug, not a valid call.
- **Real replacements:** `pane.close({pane_id})` for kill;
`pane.release_agent({pane_id, source, agent})` for release (structurally
different — does *not* return a `handoff_ref`, see below). No replacement
exists for `rotation_signal` — herdr has no concept of Orchestra rotation.
- **Architectural point that's easy to get wrong:** herdr never produces a
handoff. The agent writes the handoff artifact (§6.1 of the spec); herdr's
role in "release" is only to drop its own claim on the pane/agent binding.
Any adapter code that expects herdr to hand back a `handoff_ref` is wrong
by construction, independent of whether the method name is right.
- Protocol version is returned as a **JSON number** (`17`), not a string,
even though `config.jsonc` declares `"protocol": "17"` as a string.
`CheckProtocol`'s raw-bytes fallback happens to make this compare correctly
today — don't "clean up" that code without checking this note first, or it
might start doing a real numeric-vs-string comparison and break.
## Deployment topology (as of 2026-07-27)
- Runs as `orchestra.service` on **homesrv** (this machine's own systemd —
`journalctl -u orchestra.service` for logs; `sudo` isn't available in this
sandbox environment, but plain `journalctl` without sudo works here).
- Config: `.orchestra-config/orchestra.env` (env vars) +
`.orchestra-config/config.jsonc` (projects/machines/herdrs registry).
`ORCHESTRA_CONFIG=/etc/orchestra/config.jsonc` — the deployed copy, not the
repo's `deploy/config.example.jsonc`.
- Two machines in the registry: `homesrv` (192.168.1.104) and `workpc`
(192.168.1.105), each nominally running 3 herdrs (claude/codex/opencode).
In practice **homesrv has no local herdr running** (connection refused on
9245) — only workpc's herdr is live and reachable. `main.go` only logs
herdr connection *failures* at startup, never successes, so "no log line"
for a herdr does not mean it's down — check reachability directly.
- There was a real, live, stuck task as of 2026-07-27: workspace `wA`, task
id `06FT6CKD9Y98AZRX6X8K3QXFZG`, opencode harness, pane `wA:p1`,
`agent_status: "blocked"`. Likely stuck because rotation/release could
never reach it (B2/B5). Check whether it's still stuck before assuming
fixes here have taken effect operationally — code fixes don't retroactively
unstick an already-orphaned pane; that needs a manual kill/restart once the
release path is trustworthy.
## The federation fork — read before touching anything cross-machine
Two incompatible designs coexist. **Design A** ("drive the remote socket",
currently deployed via `clients/herdr-bridge.go`) has homesrv call
`worktree.create`/`agent.start` etc. directly on workpc's herdr over TCP as
if it were local — meaning anchor validation (`git rev-parse HEAD`) run by
the coordinator executes on the *wrong machine* relative to the actual
checkout. **Design B** ("workers pull tasks", `/v1/federation/*`) is fully
built server-side but has zero clients — no worker binary exists. Decision
(AUDIT.md, 2026-07-27): keep Design A through Phase 5, commit to Design B in
Phase 6, with two guardrails landed immediately (refuse to rotate/cleanup a
lease held by a non-local herdr rather than validate against the wrong
checkout). Don't build on top of Design A's cross-machine calls without
reading that section first.
## Working conventions
- `go build ./...`, `go vet ./...`, and `go test ./...` must all pass — `go
vet` was broken for a while (duplicate JSON struct tags) and nobody
noticed because only `build`/`test` were being checked. Always run all
three.
- Silent `continue`-on-error is the recurring bug pattern in this codebase
(adapter lookups, rotation, expiry). When touching `internal/orchestrator`
or `internal/herdr`, prefer a recorded/observable failure
(`MonitorHealth` fields) over a bare `continue` — that's literally what
turned B1/B2 invisible for as long as they were.
- Don't invoke destructive herdr calls (`pane.close`, `pane.release_agent`)
against a real pane from an investigative/audit session without asking
first — there is live operator state on the other end (see the stuck-task
note above).
+64
View File
@@ -473,3 +473,67 @@ If you want one thing to do today: **Phase 0**, then **B2** (a ~20-line fix
that makes three subsystems reachable), then **B1**. Those three turn a system that makes three subsystems reachable), then **B1**. Those three turn a system
that cannot rotate into one that can, and everything else in the plan is that cannot rotate into one that can, and everything else in the plan is
building on top rather than repairing underneath. building on top rather than repairing underneath.
---
## Phase 0 — done, 2026-07-27
B1, B2, B4, B8, S1, S5, S6 were already fixed and landed as of this session
(confirmed by reading the current code, not just trusting progress.md — see
`adapterFor` in `internal/orchestrator/orchestrator.go:190` and
`CLIAdapter.Occupancy` in `internal/herdr/adapter.go:197`).
This box (homesrv) turned out to have live TCP reachability to the real herdr
instance at `192.168.1.105:9245` (workpc) the whole time — the `unavailable:
connection refused` lines in `journalctl -u orchestra.service` are for
`homesrv-*` herdrs dialing `192.168.1.104:9245`, which has no local herdr
running; `workpc-*` herdrs were connecting fine but main.go never logs a
success, only a failure, so there was no positive signal either way. Also
found: **a real task is stuck live right now** — workspace `wA`, task
`06FT6CKD9Y98AZRX6X8K3QXFZG`, opencode agent, pane `wA:p1`, `agent_status:
"blocked"` — almost certainly stuck because `Release`/rotation could never
reach it (see below).
Ran the actual Phase 0 steps against this live instance (raw JSON-RPC probes
over TCP, params-omitted/empty-object tricks to read Rust serde's
missing-field errors — no `herdr` CLI available locally, so `herdr api schema
--json` itself wasn't run, but the equivalent info was extracted this way).
Full method list and findings committed to `deploy/herdr-schema.json`.
**Confirmed, with a real server response, not just static reading of
adapter.go:**
- `pane.release`, `pane.kill`, `pane.rotation_signal` — **none of these exist**
in the real protocol. Confirms B5's suspicion exactly.
- Real replacement for `pane.kill` is `pane.close({pane_id})` — same shape,
drop-in. **Fixed** in `internal/herdr/adapter.go`.
- Real replacement for `pane.release` is `pane.release_agent({pane_id,
source, agent})` — structurally different, and per B5's own analysis it
cannot return a `handoff_ref` regardless (herdr doesn't write handoffs, the
agent does, §6.1). Wiring this for real needs Phase 4's handoff-production
path first. `CLIAdapter.Release` now returns a loud error naming exactly
that instead of calling a method that doesn't exist. **Not a full fix** —
Phase 4 still owns making Release do something real.
- `pane.rotation_signal` doesn't exist and never will (herdr has no rotation
concept) — deleted `RotationSignal` interface, its `CLIAdapter` method, and
the call site in `Coordinator.rotate`, per this doc's own instruction
("Delete ... unless the schema proves it exists").
- `agent.get`, `pane.read`, `agent.prompt`, `worktree.create`, `worktree.open`,
`agent.start` — all confirmed real, no changes needed there.
- Protocol version confirmed live: `17`, matching `config.jsonc`'s
`"protocol": "17"` (returned as a bare JSON number by the server; the
existing string-fallback parse in `CheckProtocol` happens to handle that
correctly already).
`go build ./...`, `go vet ./...`, `go test ./...` all pass after these
changes.
**Still open from B5** (not attempted this pass — larger, needs design, not
just a method-name swap):
- `agent.prompt` inline `wait` on `CLIAdapter.Lease` (still `wait=0`, per B5's
note that only `Bootstrap` passes a real wait).
- `Release`'s real implementation, which depends on Phase 4 (§6) handoff
production existing at all.
- The stuck live task (`06FT6CKD9Y98AZRX6X8K3QXFZG`) was deliberately **not**
manipulated directly (no `pane.close`/`pane.release_agent` call against it)
— killing or releasing a real running agent from an audit session without
the user present is exactly the kind of action that warrants asking first.
+112
View File
@@ -0,0 +1,112 @@
# Orchestra
A Go implementation of `orchestra-spec (1).md` — an unattended multi-agent
task orchestrator that leases coding tasks to CLI harnesses (Claude Code,
Codex, opencode) running inside `herdr`-managed panes, rotates them across
context-window limits, and hands off work via a git-anchored continuity
protocol.
Layout: `internal/{domain,store,provider,registry,router,herdr,orchestrator,
continuity,federation,delivery,authz,operations,admin}` + `cmd/orchestra/main.go`.
## Ground truth over documentation
This repo has a documented history of code that *looks* wired but isn't —
packages with tests that pass in isolation while the live call path silently
no-ops (bare `continue` on error, discarded return values). See `AUDIT.md`
for the full audit and `progress.md` for a running log. **Before trusting a
claim in progress.md that something "works" or "is fixed," check the actual
call site** — the file is written by past sessions of this same assistant and
has previously overstated completion.
The single most reliable way to verify herdr-adapter code is right: don't
read `internal/herdr/adapter.go` and assume the method names are real. Ping
the live herdr instance and check.
## herdr protocol — verified against a live instance, 2026-07-27
- herdr speaks JSON-RPC over a raw TCP (or unix-socket) connection — **not
HTTP**. `internal/herdr/herdr.go`'s `Client.Call` is the only correct way
to talk to it; a bare `curl` to the port returns nothing.
- Request shape: `{"id":"<n>","method":"<name>","params":<object>}`. Herdr's
Rust JSON-RPC decoder requires `params` to be present and rejects a bare
`null` — always send `{}` for parameterless calls (the client does this
automatically).
- Full real method list is committed at `deploy/herdr-schema.json`, captured
live from `192.168.1.105:9245` (the `workpc` herdr) since no local `herdr`
CLI is available in this sandbox — the schema was reconstructed by sending
an unknown method name and reading the `unknown variant ... expected one
of ...` error, then probing each method of interest with `params:{}` /
`params:{pane_id:"nonexistent"}` to read Rust serde's `missing field
<x>` errors for its param shape.
- **Confirmed invented (do not use, they don't exist):** `pane.release`,
`pane.kill`, `pane.rotation_signal`, `pane.status`. If you see these
anywhere, it's a bug, not a valid call.
- **Real replacements:** `pane.close({pane_id})` for kill;
`pane.release_agent({pane_id, source, agent})` for release (structurally
different — does *not* return a `handoff_ref`, see below). No replacement
exists for `rotation_signal` — herdr has no concept of Orchestra rotation.
- **Architectural point that's easy to get wrong:** herdr never produces a
handoff. The agent writes the handoff artifact (§6.1 of the spec); herdr's
role in "release" is only to drop its own claim on the pane/agent binding.
Any adapter code that expects herdr to hand back a `handoff_ref` is wrong
by construction, independent of whether the method name is right.
- Protocol version is returned as a **JSON number** (`17`), not a string,
even though `config.jsonc` declares `"protocol": "17"` as a string.
`CheckProtocol`'s raw-bytes fallback happens to make this compare correctly
today — don't "clean up" that code without checking this note first, or it
might start doing a real numeric-vs-string comparison and break.
## Deployment topology (as of 2026-07-27)
- Runs as `orchestra.service` on **homesrv** (this machine's own systemd —
`journalctl -u orchestra.service` for logs; `sudo` isn't available in this
sandbox environment, but plain `journalctl` without sudo works here).
- Config: `.orchestra-config/orchestra.env` (env vars) +
`.orchestra-config/config.jsonc` (projects/machines/herdrs registry).
`ORCHESTRA_CONFIG=/etc/orchestra/config.jsonc` — the deployed copy, not the
repo's `deploy/config.example.jsonc`.
- Two machines in the registry: `homesrv` (192.168.1.104) and `workpc`
(192.168.1.105), each nominally running 3 herdrs (claude/codex/opencode).
In practice **homesrv has no local herdr running** (connection refused on
9245) — only workpc's herdr is live and reachable. `main.go` only logs
herdr connection *failures* at startup, never successes, so "no log line"
for a herdr does not mean it's down — check reachability directly.
- There was a real, live, stuck task as of 2026-07-27: workspace `wA`, task
id `06FT6CKD9Y98AZRX6X8K3QXFZG`, opencode harness, pane `wA:p1`,
`agent_status: "blocked"`. Likely stuck because rotation/release could
never reach it (B2/B5). Check whether it's still stuck before assuming
fixes here have taken effect operationally — code fixes don't retroactively
unstick an already-orphaned pane; that needs a manual kill/restart once the
release path is trustworthy.
## The federation fork — read before touching anything cross-machine
Two incompatible designs coexist. **Design A** ("drive the remote socket",
currently deployed via `clients/herdr-bridge.go`) has homesrv call
`worktree.create`/`agent.start` etc. directly on workpc's herdr over TCP as
if it were local — meaning anchor validation (`git rev-parse HEAD`) run by
the coordinator executes on the *wrong machine* relative to the actual
checkout. **Design B** ("workers pull tasks", `/v1/federation/*`) is fully
built server-side but has zero clients — no worker binary exists. Decision
(AUDIT.md, 2026-07-27): keep Design A through Phase 5, commit to Design B in
Phase 6, with two guardrails landed immediately (refuse to rotate/cleanup a
lease held by a non-local herdr rather than validate against the wrong
checkout). Don't build on top of Design A's cross-machine calls without
reading that section first.
## Working conventions
- `go build ./...`, `go vet ./...`, and `go test ./...` must all pass — `go
vet` was broken for a while (duplicate JSON struct tags) and nobody
noticed because only `build`/`test` were being checked. Always run all
three.
- Silent `continue`-on-error is the recurring bug pattern in this codebase
(adapter lookups, rotation, expiry). When touching `internal/orchestrator`
or `internal/herdr`, prefer a recorded/observable failure
(`MonitorHealth` fields) over a bare `continue` — that's literally what
turned B1/B2 invisible for as long as they were.
- Don't invoke destructive herdr calls (`pane.close`, `pane.release_agent`)
against a real pane from an investigative/audit session without asking
first — there is live operator state on the other end (see the stuck-task
note above).
+33
View File
@@ -0,0 +1,33 @@
{
"_source": "captured live from herdr instance at 192.168.1.105:9245 (workpc), 2026-07-27, via raw ping/schema-discovery probes over the JSON-RPC-over-TCP protocol — see AUDIT.md Phase 0",
"ping_result": {"type": "pong", "version": "0.7.5", "protocol": 17, "capabilities": {"live_handoff": true, "detached_server_daemon": true}},
"methods": [
"ping", "server.stop", "server.live_handoff", "server.reload_config", "server.agent_manifests", "server.reload_agent_manifests",
"notification.show", "client.window_title.set", "client.window_title.clear",
"session.snapshot",
"workspace.create", "workspace.list", "workspace.get", "workspace.focus", "workspace.rename", "workspace.move", "workspace.report_metadata", "workspace.close",
"worktree.list", "worktree.create", "worktree.open", "worktree.remove",
"tab.create", "tab.list", "tab.get", "tab.focus", "tab.rename", "tab.move", "tab.close",
"agent.list", "agent.get", "agent.read", "agent.explain", "agent.send_keys", "agent.rename", "agent.view.set", "agent.view.clear", "agent.focus", "agent.start", "agent.prompt", "agent.wait",
"pane.split", "pane.swap", "pane.move", "pane.zoom", "pane.layout", "pane.process_info",
"layout.export", "layout.apply", "layout.set_split_ratio",
"pane.neighbor", "pane.edges", "pane.focus_direction", "pane.resize", "pane.list", "pane.current", "pane.get", "pane.focus", "pane.rename",
"pane.send_text", "pane.send_keys", "pane.send_input", "pane.read",
"pane.graphics.set", "pane.graphics.clear", "pane.graphics.info", "pane.graphics.stream",
"pane.report_agent", "pane.report_agent_session", "pane.report_metadata", "pane.clear_agent_authority", "pane.release_agent", "pane.close",
"popup.close",
"events.subscribe", "events.wait", "pane.wait_for_output",
"integration.install", "integration.uninstall",
"plugin.link", "plugin.list", "plugin.unlink", "plugin.enable", "plugin.disable", "plugin.action.list", "plugin.action.invoke", "plugin.log.list", "plugin.pane.open", "plugin.pane.focus", "plugin.pane.close"
],
"confirmed_invented_methods_not_in_schema": ["pane.release", "pane.kill", "pane.rotation_signal", "pane.status"],
"confirmed_real_replacements": {
"pane.kill": "pane.close (params: {pane_id})",
"pane.release": "pane.release_agent (params: {pane_id, source, agent} — does NOT return handoff_ref; matches agent_session shape from agent.list: {source, agent, kind, value})",
"pane.rotation_signal": "no equivalent exists; herdr has no concept of Orchestra rotation"
},
"params_confirmed_by_probing": {
"pane.close": {"pane_id": "string"},
"pane.release_agent": {"pane_id": "string", "source": "string (e.g. \"herdr:opencode\")", "agent": "string (e.g. \"opencode\")"}
}
}
+12 -18
View File
@@ -26,9 +26,6 @@ type WorktreeCreator interface {
type TurnBoundary interface { type TurnBoundary interface {
AtTurnBoundary(context.Context, Session) (bool, error) AtTurnBoundary(context.Context, Session) (bool, error)
} }
type RotationSignal interface {
RotationSignal(context.Context, Session) (string, error)
}
type PaneExit interface { type PaneExit interface {
PaneExited(context.Context, Session) (bool, error) 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 { 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) 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) { func (a CLIAdapter) Release(ctx context.Context, s Session) (string, error) {
var r struct { 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")
Ref string `json:"handoff_ref"`
}
e := a.Client.Call(ctx, "pane.release", s, &r)
return r.Ref, e
} }
func (a CLIAdapter) Kill(ctx context.Context, s Session) error { 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) { func (a CLIAdapter) AtTurnBoundary(ctx context.Context, s Session) (bool, error) {
status, err := a.AgentStatus(ctx, s) status, err := a.AgentStatus(ctx, s)
@@ -179,15 +182,6 @@ func statusFromAgentResult(v any) string {
var _ = json.RawMessage{} 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, // Occupancy reads the harness's own session state — never the herdr pane id,
// which ClaudeUsage/CodexUsage/OpenCodeUsage cannot open (spec §5.2.1: "the // which ClaudeUsage/CodexUsage/OpenCodeUsage cannot open (spec §5.2.1: "the
// whole rotation system rests on this number"). A session file that cannot // 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 continue
} }
reason := "threshold" 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) occupancy, err := a.Occupancy(session)
if err != nil || (occupancy < hard && reason == "threshold") { if err != nil || occupancy < hard {
continue continue
} }
// Face B is treated as required, not best-effort (spec §5.2/§5.3): // Face B is treated as required, not best-effort (spec §5.2/§5.3):
+18 -4
View File
@@ -52,10 +52,24 @@ Fixed so far:
polling would error out of its scan loop on the first already-ingested polling would error out of its scan loop on the first already-ingested
issue in every batch. issue in every batch.
Not yet started: B3 (no `TaskCompleted` producer / no Stop hook), B5/B6 Not yet started: B3 (no `TaskCompleted` producer / no Stop hook), B6 (Layer 3
(herdr protocol verification, Layer 3 wiring), B7 (quota projection has no wiring), B7 (quota projection has no producer), S2S4, S7S11. See
producer), S2S4, S7S11, and the Phase 0 live-herdr verification step that `AUDIT.md` for the full plan.
several of the above still need. See `AUDIT.md` for the full plan.
**Phase 0 done (2026-07-27):** this box has live TCP reachability to the real
herdr instance at `192.168.1.105:9245` — verified by hand (raw JSON-RPC
probes, no `herdr` CLI available locally). Real method list captured in
`deploy/herdr-schema.json`. Confirmed `pane.release`/`pane.kill`/
`pane.rotation_signal` are invented, as AUDIT.md's B5 suspected.
`pane.kill``pane.close` fixed as a drop-in. `pane.rotation_signal`/
`RotationSignal` deleted (no replacement exists). `Release` now refuses
loudly instead of calling a nonexistent method — its real implementation
needs Phase 4 (handoff production) first, since even the real
`pane.release_agent` can't return a `handoff_ref` (herdr doesn't write
handoffs, the agent does). See AUDIT.md's new "Phase 0 — done" section for
full detail. **Also found: a real task is currently stuck live** — workspace
`wA`, task `06FT6CKD9Y98AZRX6X8K3QXFZG`, opencode, pane `wA:p1`, blocked —
deliberately not touched from this session.
## Current state ## Current state