Brief.Git was a single GitSync read from ORCHESTRA_DATA (never a git checkout), and completions were counted but discarded their report_ref/ receipt. Brief.Git is now keyed by project ID and built from each project's real repo; GitSync gained Ahead/Behind vs upstream; Brief now carries Receipts pulled from each TaskCompleted payload. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W1rkJ2hBMybnJctPbcy4tT
26 KiB
Orchestra progress
Updated: 2026-07-27
AUDIT.md remediation — in progress
Working through AUDIT.md's blocking/secondary defects in order of the
"suggested order of attack." Each item below is landed, tested, and
committed individually; see the git log for the exact commits.
Fixed so far:
-
B2 — adapters were looked up by
session.Harness(the harness kind, e.g."claude") inReconcile/expire/rotate, butAdapterFactory.Herdrsis keyed by herdr instance id (e.g."homesrv-claude"). Every one of those call sites silently no-opped. AddedCoordinator.adapterFor, routed all four call sites through it. Regression test registers an adapter under a herdr-id key distinct from the harness kind and asserts rotation fires. -
B1 —
CLIAdapter.Occupancycalleda.Usage(s.PaneID), but the usage readers want a filesystem path to session state, not a herdr pane id. Addedherdr.Session.SessionFileand per-harness resolution (ClaudeSessionFileby newest-mtime under Claude Code's own project directory; codex via the existingCodexActiveUsagesqlite discovery; opencode refuses loudly — needs a live session id, not resolvable from the worktree alone). A missing/unreadable session file is now a hard error, surfaced via newSessionHealth.Occupancy/OccupancyErrorfields onGET /v1/tasks/{id}/health, not a silent zero. Still needs live verification against a real Claude Code session (the spec's own acceptance bar for this phase) — not possible from this sandbox. -
B4 — the router counted every
TaskReleased(including rotation, which is aTaskReleasedcarrying a validhandoff_ref) againstMaxAttempts, and double-counted by also incrementing on every subsequent lease. A task that rotated twice hit the defaultMaxAttempts=3and was killed. Now only a release without ahandoff_ref(expiry/crash) advances the counter. -
B8 —
X-Orchestra-Surface: systemwas reachable from an HTTP request header in bothauthz.HTTPandmain.go'ssurfaceclosure (the one every handler actually calls). Since no deployment setsORCHESTRA_SYSTEM_TOKEN, this was an unauthenticated full-control bypass reachable from any LAN caller. Both call sites now downgradesystemtowebbefore doing anything else with it. -
S1 —
Brief.From/ToandGitSync.Branch/Head/Statusall shared one JSON tag each (Go only honors the firstjson:"..."tag on a combined field declaration).go vet ./...now passes clean. -
S5 —
Store.Lease/ExpireLeasessetEvent.IDto the task id, so every lease of a task produced colliding event IDs. Nowdomain.NewID(). -
S6 — the ingest dedup path returned
nil(success) without appending;main.gothen returned an unrelated event with201. Addeddomain.ErrDuplicateandStore.TaskBySource;POST /v1/tasksnow returns the existing task with200on a duplicate. Updated every otherAppendcaller (Gitea poll/webhook, JSONL ingest) to treatErrDuplicateas expected rather than a failure — without that, Gitea polling would error out of its scan loop on the first already-ingested issue in every batch. -
B3 (partial) — added
POST /v1/harness/complete, the first automaticTaskCompletedproducer (previously only a human calling/v1/tasks/{id}/completecould ever complete a task). A Claude Code Stop hook (deploy/hooks/orchestra-stop.sh) fires on every turn boundary but only reports completion if the agent has written a.orchestra-report.mdmarker at the worktree root first — an ordinary turn boundary is a no-op, so this doesn't fire completion prematurely. The server reads the transcript locally viaherdr.ClaudeUsageto build thereceiptitself (input/cache/output token counts) rather than trusting a self-reported number, and uploads the report body to CAS forreport_ref. Guarded by an optionalORCHESTRA_HARNESS_TOKENbearer check; the event is appended withSurface: systemset directly in Go (not derived from a request header — consistent with the B8 fix that system must never be header-controlled). Not done: Codex/opencode equivalents (Claude-only for now — Codex would needCodexActiveUsage, opencodeOpenCodeUsage/OpenCodeStatus, wired the same way), and the turn-boundary decision endpoint (continue/prepare_handoff/rotate_now/refuse) from Phase 2 items 1–2 is still unbuilt — only the completion half of Phase 2 landed. No test added for the new HTTP handler;cmd/orchestra/main.gohas zero test coverage for any handler (pre-existing gap, everything lives inline inmain()) so this follows the existing (untested) pattern rather than introducing a one-off test harness. -
B5 (loose end) —
CLIAdapter.Lease's initial prompt usedwait=0, skipping the inline waitBootstrapalready used; the spec (§5.1) requires inlinewaitonagent.promptfor bootstrap injection to avoid sending into a half-rendered prompt. Changed totime.Minute, matchingBootstrap. Small, contained fix —Release's real implementation (needs Phase 4 handoff production) is still outstanding from B5. -
B6 (partial — Phase 4 items 1 and 4) — nothing wrote a
TASK.mdinto a worktree, so pickup validation had nothing to check and never ran anyway. Fixed both halves:GitWorktrees.Createnow writes and commits an immutableTASK.md(continuity.RenderTaskFile) into every freshly created worktree, andCoordinator.Startnow runscontinuity.ValidatePickup(loading the handoff from CAS, checking anchor SHA + dirty-file hashes + TASK.md hash) before bootstrapping a successor onto ahandoff_ref— a failure kills the session and emitsTaskBlockedinstead of trusting an unvalidated ref. Covered byTestGitWorktreesCommitsTaskFileandTestStartBlocksOnInvalidPickupininternal/orchestrator. Not done: handoff production (nothing yet writes a real §6.1 handoff —Releasestill refuses per B5), wiringScratchCommitbefore release, and the §6.2 bootstrap-prompt rewrite. See AUDIT.md's "B6 — partial fix" section for the full breakdown, including a named caveat: TASK.md hashing is best-effort and untested for the herdr-hosted (WorktreeCreator) worktree path. -
B5 (closed) —
CLIAdapter.Releasepreviously just refused (no real herdr method existed to call and there was nothing to validate against). Now: reads the agent-authored.orchestra-handoff.jsonfrom the worktree root, validates it withcontinuity.Decode, cross-checks its anchor SHA against the worktree's realHeadSHA(never trusts the agent's self-report outright), uploads it to CAS viacontinuity.Saveto mint thehandoff_ref, and only then calls the realpane.release_agent({pane_id, source, agent})to drop herdr's claim — sequenced last so a herdr-side error can't strand an uploaded handoff. Any failure (missing file, invalid schema, anchor mismatch, herdr error) is a refusal, whichrotatealready treats as "retry next tick" rather than stranding the task.herdr.Claude/ Codex/OpenCodenow take acontinuity.CAS(main.go passes the existing*store.Store). New tests ininternal/herdr/adapter_test.gocover all four paths against a real git worktree and a fake in-process herdr listener. Not done: nothing yet makes the agent actually write.orchestra-handoff.json(needs a stop-hook convention analogous to.orchestra-report.md) — that and the rest of Phase 4 (ScratchCommit before release, §6.2 bootstrap-prompt rewrite,MarkdownChanges) remain open. -
Phase 4 items 3, 5, 6 —
CLIAdapter.Releasenow re-verifies everyAnchor.Dirtyfile hash (previously only the top-levelAnchor.GitSHAwas checked; a file edited after the handoff was written but before release would have gone through unnoticed), then, if there were dirty entries, snapshots them atomically onto a per-task scratch branch (continuity.ScratchCommit, made idempotent so a task can rotate more than once) and rewrites the handoff's anchor to that new commit withDirtycleared before uploading — so the successor's pickup check is a single HEAD compare, not N file rehashes.CLIAdapter.Bootstrap's prompt was rewritten to point the agent atgit log/the scratch branch instead of a vague "read the handoff" instruction, and deliberately avoids claiming aGET /v1/artifacts/<ref>endpoint, since no such route exists (/v1/artifactsis POST-only).continuity.MarkdownChanges(§6.3 adjacent-task notice) had zero callers and zero tests despite being listed as implemented in an earlier snapshot — deleted rather than half-wired, per AUDIT.md's explicit "delete and record the deviation" option. New tests:TestReleaseScratchCommitsDirtyFilesBeforeUpload,TestReleaseRefusesOnStaleDirtyFile(internal/herdr/adapter_test.go). §6.3 rewired for real, 2026-07-27 (later same day): the deletedMarkdownChangesabove was zero-caller dead code, but the underlying spec requirement ("on update, the orchestra injects a notice to agents whose current task is adjacent") wasn't abandoned — rebuilt independently.continuity.ConventionsHash(root)hashes whichever ofAGENTS.md/CLAUDE.md/VOCAB.mdexist at a path;herdr.SessiongainedConventionsHash, snapshotted from the fresh worktree atCoordinator.Start; a newCoordinator.checkConventions, run everyMonitortick, recomputes the hash of the project's base repo (viaWorktreeSpec.Spec— "adjacent" = same project) for every leased session and compares it against that session's stored snapshot. A mismatch calls a new optionalherdr.ConventionsNotifiercapability (CLIAdapter.NotifyConventionsChanged, an in-paneagent.prompttelling the agent to re-read the docs) and updates the stored hash so the notice fires once per drift, not every tick. Covered byTestConventionsDriftNotifiesActiveSession(internal/orchestrator/rotation_test.go): asserts no notification while the base repo is unchanged, then one once it diverges. Was still open: Phase 4 item 2 — nothing drove any harness to write.orchestra-handoff.json, since Release only validated a file whose existence was never solicited. Closed 2026-07-27:rotate()now checks for the adapter's optionalherdr.HandoffRequestercapability; whenHandoffFileis missing at the worktree root, it prompts the agent once (CLIAdapter.RequestHandoff, mirroring the.orchestra-report.md/B3 convention — the plane asks for a handoff, it never invents one) and skips Release that tick, retrying every subsequent tick until the file appears.herdr.Session.HandoffRequestedavoids re-prompting every tick. Covered byTestRotationRequestsHandoffBeforeReleasing(internal/orchestrator/rotation_test.go), which asserts Release is never called before the file exists and fires once it does. Codex/opencode still share this same path (no harness-specific gap remains); the only leftover question is whether each harness's own Stop-equivalent hook honors the in-pane prompt to write the file before exiting, which is a live-deployment fact, not something provable from source. -
B7 (post-hoc producer) + Phase 2 turn-decision endpoint — landed together, since both are new
QuotaReported/turn-boundary paths off the same completion/turn events.POST /v1/harness/completenow appends aQuotaReportedevent (harness_idfrom the closing lease,consumedfrom the sameusage.Numerator()used for the receipt), so the router's 5h/weekly availability filter and the brief'squota_consumedstop evaluating against a permanent zero. NewCoordinator.TurnDecision(internal/orchestrator/orchestrator.go) mirrorsrotate()'s per-task logic (occupancy → turn-boundary → handoff-file → release) but runs synchronously once per turn instead of waiting forMonitor's ticker, returning one ofcontinue/prepare_handoff/rotate_now/refusevia the newPOST /v1/harness/turn. The Claude Stop hook (deploy/hooks/orchestra-stop.sh) now calls this endpoint on every ordinary turn boundary (report marker absent) instead of no-op'ing, and exits 2 onrefuseto stop the harness from finishing an unsafe turn. Covered byTestTurnDecision(internal/orchestrator/rotation_test.go): continue-below-threshold, refuse-when-not-at-boundary, and rotate_now-releases-and-emits-a-valid-TaskReleased cases. Not done: live per-harness push producers (Claude statusline, Codex rollout tail) that would give B7 a second, continuous producer independent of task completion — recorded as a design investigation in AUDIT.md ("Real harness quota sources") but not implemented; Codex/ opencode's own equivalents of the Claude Stop hook (whether their turn-boundary mechanism actually calls/v1/harness/turn) also remain unbuilt, same caveat as Phase 2 item 4 already named for/complete. -
S4 —
delivery.Fanout.Runused toreturnon the first sender error, permanently killing the notification goroutine (a single ntfy hiccup meant no notifications for the rest of the process's lifetime, since nothing restarts it). Failed sends now go through anOnErrorhook instead of aborting the loop. Cursor is also persisted now (SaveCursor→ adelivery-cursorfile next toORCHESTRA_DATA, loaded on startup), so a restart resumes from the last delivered event instead of re-notifying the entire log from seq 0.internal/deliverypreviously had zero tests; addedTestFanoutContinuesAfterSendError. -
S2 + S3 —
/v1/brief's git state used to come fromORCHESTRA_DATA(the event-log directory, not a git checkout — always reported"git unavailable"), and completions were only counted, never surfaced with proof.operations.Brief.Gitis nowmap[string]GitSynckeyed by project ID, built inmain.gofrom eachregistry.Project.Repo(falling back to a single"default"entry offORCHESTRA_REPOfor deployments without per-project repos);GitSyncgainedAhead/Behindvs upstream. NewBrief.Receipts []operations.CompletionReceiptpullsreport_ref/receiptstraight out of eachTaskCompletedevent's existing payload. Covered by an updatedTestBuildBrief.
Not yet started: Codex/opencode completion producers, S7–S11. 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
This is a working Go implementation of orchestra-spec (1).md's Layer 1–3
(substrate, harness/rotation, continuity) plus a first cut of Layer 4
(surfaces). go build ./... and go test ./... both pass. The codebase is
small (~4.6k lines across internal/{domain,store,provider,registry,router, herdr,orchestrator,continuity,federation,delivery,authz,operations,admin}
and cmd/orchestra/main.go).
Earlier revisions of this file accumulated a long, self-contradictory chronological log — gaps were listed as open in one section and then claimed closed in a later section, sometimes inaccurately. This revision replaces that log with one audited snapshot. Treat prior git history of this file as session notes, not as ground truth.
Verified fixed this pass
- Rotation emitted an invalid
TaskReleased(the previously reported highest-priority defect) — now fixed.internal/orchestrator.Coordinator.rotatebuilt the release payload as{"handoff_ref","reason"}, omitting theanchor_shathe spec (§4, §6.2) anddomain.ValidatePayloadrequire wheneverhandoff_refis present.store.Appendwould reject it, the error was discarded (if c.Store.Append(e) == nil), and the lease/session silently never rotated — the coordinator would just retry next tick with no visible failure. Fixed by addingherdr.HeadSHA(worktree)and havingrotatepopulateanchor_shafrom the real worktree HEAD before appending; if the anchor can't be read, rotation now correctly skips that tick (leaving the lease intact for TTL/next-tick reclaim) instead of emitting a payload guaranteed to fail validation. Covered byinternal/orchestrator/rotation_test.go(TestRotationEmitsValidReleaseWithAnchorSHA), which drives the realCoordinator.Monitorloop against an actual git worktree and asserts the emitted event passesdomain.ValidatePayloadwith the correct SHA — the previous end-to-end test masked this bug by manually crafting a replacementTaskReleasedevent after observing the (silently failed) adapter-side release. - The federation worker release endpoint had the same gap. The
/v1/federation/workers/{id}/releasehandler (cmd/orchestra/main.go) builtTaskReleasedfrom a request body with onlyhandoff_ref, noanchor_sha. Since a remote worker is the only party with the actual checkout (§2.1: "validate against the local checkout wherever the harness runs"), the endpoint now requires and forwards a 40-hex-charanchor_shain the request body, rejecting the call with 400 otherwise.
Multi-repo Gitea ingestion (new)
provider.Giteagained an optionalProjectfield andSourceName()("gitea"if unset,"gitea:<project>"if set) — the namespaced source doubles as the(source,external_id)dedup key, so issue #7 in two different repos never collides, and as the reflection dispatch key.- New
provider.MultiGitea{Sources map[string]Gitea}implementsTaskReflectorby looking uptask.Sourceand forwarding to the matching Gitea instance — lets several Gitea repos (one per project) share oneReflectingSink. - New
provider.GiteaSourceConfig+LoadGiteaConfigs(path)load a JSON array of{project,base_url,owner,repo,token,webhook_secret}.main.goreads this fromORCHESTRA_GITEA_CONFIGif set; each source gets its own poll supervisor (gitea:<project>) and webhook path (/v1/providers/gitea/webhook/<project>). - The legacy single-repo env vars (
ORCHESTRA_GITEA_URL/TOKEN/OWNER/REPO/ WEBHOOK_SECRET) still work unchanged whenORCHESTRA_GITEA_CONFIGis unset — same unprefixed webhook path, sameproject = ORCHESTRA_GITEA_REPOtagging, same dedup source"gitea"— so existing deployments and already-configured Gitea webhooks need no changes. - Added
internal/provider/gitea_test.go— previously there were zero tests exercising the Gitea provider at all despite progress.md's prior claim of Gitea webhook/poll test coverage; that claim was not accurate. New tests cover source-name namespacing, webhook signature verification/rejection, project tagging,MultiGiteadispatch-by-source (via twohttptest.Servers, asserting only the right one is hit), andLoadGiteaConfigsvalidation/duplicate-project rejection.
Per-project repos (new)
registry.Projectgained optionalrepo/worktree_rootfields. Each project can now resolve its own git checkout rather than every project sharing one globalORCHESTRA_REPO/ORCHESTRA_WORKTREE_ROOT— matches spec §2.2 ("projects are first-class and extensible... the binding is a field + a config entry, not a schema change").main.gobuilds aorchestrator.PerProjectGitWorktreesfrom the registry, falling back to the global default for any project that omits these fields, so single-repo deployments are unaffected. Covered byinternal/orchestrator/worktrees_test.go.
Closed this pass (were open gaps as of the last snapshot)
- Bus-level authorization.
authz.AuthorizeEventis now enforced insidestore.Appenditself — the single choke point every event passes through (HTTP handlers, router, coordinator/rotation, providers, federation relay) — not just at HTTP handlers. Event schema bumped to v2, which requires every event to declare aSurface; a newauthz.Systemsurface (full control) covers internal emitters (router leases/failures, coordinator releases/blocks, standup advisory/apply). Schema v1 events on disk still replay (tolerant reader). Covered byinternal/store/store_test.goandinternal/router/router_test.goadditions asserting a non-HTTP append with no/wrong surface is rejected. - Dual quota windows.
router.QuotaAvailabilitynow tracks a 5-hour rolling window and a 7-day weekly window independently per harness (QuotaWindowLimits{FiveHour, Weekly}), applying the conservative 80% rule to each separately — a harness over threshold on either window is unavailable. Replaces the old single-Windowfield. Covered by newrouter_test.gocases for weekly-only and 5h-only exhaustion. - Turn-boundary detection made observable, not silently optional.
Rotation still can't force a harness adapter to implement
TurnBoundaryFace B, but an adapter that fails to answer it now blocks that tick's release (never treats a failed check as "safe to proceed"), and any adapter without the capability — or one whose check errors — incrementsMonitorHealth.TurnBoundaryDegraded, exposed via the coordinator's health endpoint so degraded-safety operation is visible, not silent. - Cross-machine lease correctness has a real test.
internal/integration/federation_lease_test.go(TestCrossMachineLeaseAnchorAndQuotaArePerHost) exercises a lease claimed through the federation worker HTTP API, validates the anchor against that worker's own local checkout (not the router's), and asserts quota is accounted per-host. Spec §9 item 8 said "prove on the first federated run" — this is that proof for the primitives that exist today (registration, heartbeat, lease-claim); it does not yet run against two real physical machines. - Fuzz coverage for lifecycle payload validation.
internal/domain/fuzz_test.goaddsFuzzValidatePayloadandFuzzValidateEventcovering all event types (including malformed nestedreceipt/knowledgeshapes) — asserts no panic and always a typed error on adversarial input.
Believed accurate from prior sessions (spot-checked, not exhaustively re-verified)
- Event log: append-only JSONL, versioned envelope (schema v1), snapshot load/replay, CAS with content-hash verification at append.
domain.ValidatePayloadenforces required fields per event type, includingexpected_version/ttlonTaskLeased,anchor_shaonTaskReleased(now correctly emitted, see above),report_ref+receiptonTaskCompleted, andblockeronTaskBlocked.- Router: project→affinity→machine resolution, capability match, quota
availability at conservative 80% threshold, derived-importance ordering,
retry-then-
TaskFailed. - herdr adapters (Claude/Codex/opencode) with native occupancy readers,
optional
TurnBoundary/RotationSignal/PaneExitcapability interfaces, bootstrap/lease/release/kill. - Continuity: strict handoff schema/validation, CAS save/load, pickup
validation (HEAD match, dirty-file hashes, immutable
TASK.mdhash), scratch-branch commit/push/pull helpers. - Provider layer: JSONL watcher, Gitea webhook+poll with HMAC auth,
idempotent
(source,external_id)dedup, terminal-state reflection, supervised restart with backoff. - Federation: worker registration, heartbeat/TTL offline detection, event cursor polling/ack, lease claim endpoint.
- Authorization: bus-level capability table (notify-only / full / gated) is
applied to lifecycle and approval writes via
AuthorizeEvent. - Delivery: Telegram/ntfy fan-out for completion/failure/block/approval events.
/readyz,/v1/brief,/v1/providers/health,/v1/standupexist and return real state (not stubs).
Known open gaps (named, not silently assumed done)
- Cross-machine lease correctness is proven at the primitive level, not on
real hardware.
TestCrossMachineLeaseAnchorAndQuotaArePerHostexercises the federation worker HTTP API (registration, lease-claim, anchor validation against the worker's own checkout, per-host quota) inside one test process. Spec §9 item 8 says "prove on the first federated run" — that means an actual homesrv/workpc pair over the real mesh, which this repo cannot exercise by itself. Named here as the one item that needs a live two-machine run to fully close, not more code. - Turn-boundary Face B still degrades to occupancy-only for adapters that
don't implement it, by design — the spec's Face B is per-harness native
session state (Stop hook / rollout tail / SSE), which this repo can only
wire against a real running herdr+harness pair. The degradation is now
observable (
MonitorHealth.TurnBoundaryDegraded) and blocks-on-failure rather than silently proceeding, but whether Claude/Codex/opencode's native hooks are wired in a live deployment is a deployment-config fact, not something provable from source alone.
Everything else named as open in the previous snapshot (bus-level authorization, dual 5h/weekly quota windows, fuzz coverage of lifecycle payload validation) is now closed — see "Closed this pass" above. Broader areas (provider layer, continuity, router matching, delivery, federation registration) were spot-checked against the code and their tests and matched their described behavior.