Delete Design A, the harness-hook completion path, and retired deploy files

Acts on the seven review comments on PR #1.

Design A is gone (comment 4). clients/ deleted rather than tracked: with
workers carrying cross-machine work the bridge is undeployed, which supersedes
the 2026-07-27 "keep through Phase 5" decision. CLAUDE.md, AGENTS.md and
AUDIT.md updated from "retained" to "deleted".

The harness-hook completion path is gone (comment 10). Investigation of the
live OpenCode QA run showed orchestra-worker owns completion end to end: it
watches for .orchestra/done, confirms via AgentStatus that the agent is not
busy, then posts through /v1/federation/* with both lease epoch and expected
version. The hook scripts used a different, older convention
(.orchestra-report.md) and posted to /v1/harness/complete, which had already
been reduced to a 410 stub - so that path could not have completed a task.
Nothing exercised it, because the live run never used it. Deleted: the three
deploy/hooks scripts, the 410 route, the unmounted harnessCompletion handler,
and its test. That test passed against a handler no mux routed to, which is
the exact "looks wired but isn't" pattern CLAUDE.md warns about; the
constant-time token compare added to it earlier today goes with it, having
never been reachable. /v1/harness/turn is untouched and still live.

Retired deployment files (comments 8, 12, 14): deploy/orchestra.service and
deploy/redeploy.sh (which sudo-installed to /usr/local/bin and restarted that
unit), plus deploy/docker-api-entrypoint.sh. The entrypoint was safe to remove
once its premise was checked: env vars reach the container through
`env_file: .env` in compose.yaml, not by sourcing /etc/orchestra/orchestra.env
- only config.jsonc is bind-mounted there - and Dockerfile.api's line 17
already sets ORCHESTRA_DATA/ORCHESTRA_PORT. Dockerfile.api now execs
/app/orchestra directly. orchestra-worker.service is a different, current unit
and is kept.

deploy/config.example.json deleted as a duplicate (comment 6); the annotated
.jsonc is the one registry.go points at, and its header no longer tells the
reader to copy the file that just went away.

Documentation corrected beyond the deletions:
- CLAUDE.md's deployment section claimed the container bind-mounts
  /etc/orchestra:ro and its entrypoint sources the env file. Both wrong.
- AGENTS.md still described a systemd deployment on homesrv as of 2026-07-27.
- AUDIT.md's H5 row still described a "retained compatibility handler".
- deploy/DEPLOYMENT.md still named redeploy.sh as the deployment path.
- deploy/orchestra.env.example still cited EnvironmentFile=.

TOKEN_MINIMAL_WORKFLOW_PLAN.md (comment 2) is untouched: it and WEB_UI_PLAN.md
were both missed by REVIEW.md's documentation sweep, and reconciling a 534-line
forward-looking plan against AUDIT.md is its own task, not a review fixup.

Verified: go build ./..., go vet ./..., go test ./... all pass after the
deletions, and go list ./... has no node_modules entry. No live herdr or pane
was touched; nothing was deployed. The running image still predates this
commit until compose is rebuilt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GEugbHVYfAXFpTqDYbByEB
This commit is contained in:
kami
2026-07-31 00:29:42 +04:00
parent 56f5aac582
commit 97a9c65302
20 changed files with 114 additions and 665 deletions
+5 -3
View File
@@ -1,8 +1,10 @@
# Deployment verification
Both binaries embed their Git revision, UTC build time, and dirty flag. Build
the coordinator with `deploy/redeploy.sh`; it installs and restarts the local
`orchestra.service`.
Both binaries embed their Git revision, UTC build time, and dirty flag. The
coordinator is deployed as a Docker Compose image — see "For the Docker
coordinator deployment" below for the build that carries provenance. (The old
`deploy/redeploy.sh` + `orchestra.service` path was deleted on 2026-07-31;
`orchestra-worker.service` is a different, still-current unit.)
## Browser operator login
-66
View File
@@ -1,66 +0,0 @@
{
"projects": [
{
"id": "correx",
"machine_affinity": ["mainframe"],
"repo": "/var/lib/orchestra/repos/correx.git",
"worktree_root": "/var/lib/orchestra/worktrees/correx"
},
{
"id": "maven",
"machine_affinity": ["mainframe", "satellite"]
}
],
"machines": [
{
"id": "mainframe",
"address": "10.0.0.10:9145"
},
{
"id": "satellite",
"address": "10.0.0.11:9145"
}
],
"herdrs": [
{
"id": "mainframe-claude-1",
"machine_id": "mainframe",
"harness": "claude",
"protocol": "1",
"capabilities": ["code", "review"],
"concurrency": 2,
"quota_limit_5h": 50,
"quota_limit_weekly": 500
},
{
"id": "satellite-claude-1",
"machine_id": "satellite",
"address": "10.0.0.11:9245",
"harness": "claude",
"protocol": "1",
"capabilities": ["code"],
"concurrency": 1,
"quota_limit_5h": 20,
"quota_limit_weekly": 200
},
{
"id": "mainframe-codex-1",
"machine_id": "mainframe",
"harness": "codex",
"protocol": "1",
"capabilities": ["code"],
"concurrency": 1,
"quota_limit_weekly": 300
},
{
"id": "satellite-opencode-1",
"machine_id": "satellite",
"address": "10.0.0.11:9345",
"harness": "opencode",
"protocol": "1",
"capabilities": ["code", "review"],
"concurrency": 1,
"quota_limit_weekly": 300
}
]
}
+5 -2
View File
@@ -1,6 +1,9 @@
// Annotated reference for config.example.json (registry.Config, internal/registry/registry.go).
// Annotated reference for registry.Config (internal/registry/registry.go).
// This file is NOT valid JSON (it has comments) and is not loaded by orchestra —
// it exists purely to document fields. Copy config.example.json, not this file.
// it exists purely to document fields. Copy it, strip the comments, and install
// the result as the deployed config.jsonc (bind-mounted into /etc/orchestra/ by
// compose.override.yaml). The plain config.example.json was deleted on
// 2026-07-31 as a duplicate of this file.
{
// Static project topology. One entry per project the fleet routes tasks for.
"projects": [
-13
View File
@@ -1,13 +0,0 @@
#!/bin/sh
set -eu
# Keep the existing coordinator's secrets/config in the host-owned env file;
# Docker never needs to read or copy it. The process runs as the same numeric
# service user and can therefore read the bind-mounted file.
if [ -r /etc/orchestra/orchestra.env ]; then
set -a
. /etc/orchestra/orchestra.env
set +a
fi
export ORCHESTRA_DATA=/data
export ORCHESTRA_PORT=9145
exec /app/orchestra
-84
View File
@@ -1,84 +0,0 @@
#!/bin/sh
# Codex turn-boundary/completion poller.
#
# Unlike Claude Code, Codex has no native Stop hook — nothing calls out at a
# turn boundary. This script is meant to run as a background loop inside the
# pane alongside the codex process (AUDIT.md Phase 2 item 4, "Not done:
# Codex/opencode Stop-hook-equivalent scripts"), polling the same
# two endpoints the Claude Stop hook (orchestra-stop.sh) calls on every turn:
# - completion: if the agent has written .orchestra-report.md, POST
# /v1/harness/complete with harness=codex and the newest active rollout
# path, then remove the marker.
# - turn decision: otherwise POST /v1/harness/turn and log (never kill the
# harness process on "refuse" — there's no turn boundary to refuse at
# from outside the process the way exit-code-2 works for a real Stop
# hook; this is advisory-only for codex until app-server integration
# exists).
#
# Requires: ORCHESTRA_TASK_ID, ORCHESTRA_URL, ORCHESTRA_WORKTREE (the pane's
# cwd) set in the pane's env. Optional: ORCHESTRA_HARNESS_TOKEN,
# ORCHESTRA_POLL_INTERVAL (seconds, default 60).
# Needs: jq, find, curl.
set -u
: "${ORCHESTRA_POLL_INTERVAL:=60}"
: "${ORCHESTRA_WORKTREE:=$PWD}"
[ -z "${ORCHESTRA_TASK_ID:-}" ] && { echo "orchestra-codex-poll: ORCHESTRA_TASK_ID not set" >&2; exit 1; }
[ -z "${ORCHESTRA_URL:-}" ] && { echo "orchestra-codex-poll: ORCHESTRA_URL not set" >&2; exit 1; }
auth_header=""
if [ -n "${ORCHESTRA_HARNESS_TOKEN:-}" ]; then
auth_header="Authorization: Bearer ${ORCHESTRA_HARNESS_TOKEN}"
fi
# Newest rollout file under ~/.codex/sessions, matching CodexActiveUsage's
# own "most recently touched" heuristic (internal/herdr/occupancy.go).
latest_rollout() {
find "$HOME/.codex/sessions" -name 'rollout-*.jsonl' -type f -printf '%T@ %p\n' 2>/dev/null \
| sort -rn | head -n1 | cut -d' ' -f2-
}
report_file="${ORCHESTRA_WORKTREE%/}/.orchestra-report.md"
while true; do
sleep "$ORCHESTRA_POLL_INTERVAL"
rollout="$(latest_rollout)"
[ -z "$rollout" ] && continue
if [ -f "$report_file" ]; then
report="$(cat "$report_file")"
body="$(jq -n \
--arg task_id "$ORCHESTRA_TASK_ID" \
--arg transcript_path "$rollout" \
--arg report "$report" \
'{task_id: $task_id, harness: "codex", transcript_path: $transcript_path, report: $report}')"
if curl -fsS -X POST "${ORCHESTRA_URL%/}/v1/harness/complete" \
-H "Content-Type: application/json" \
${auth_header:+-H "$auth_header"} \
-d "$body" >/dev/null 2>&1; then
rm -f "$report_file"
exit 0
else
echo "orchestra-codex-poll: failed to report completion" >&2
fi
continue
fi
body="$(jq -n --arg task_id "$ORCHESTRA_TASK_ID" '{task_id: $task_id}')"
response="$(curl -fsS -X POST "${ORCHESTRA_URL%/}/v1/harness/turn" \
-H "Content-Type: application/json" \
${auth_header:+-H "$auth_header"} \
-d "$body" 2>/dev/null)" || {
echo "orchestra-codex-poll: failed to reach turn-decision endpoint" >&2
continue
}
decision="$(printf '%s' "$response" | jq -r '.decision // empty')"
if [ "$decision" = "refuse" ] || [ "$decision" = "rotate_now" ]; then
echo "orchestra-codex-poll: turn decision is $decision" >&2
fi
done
-79
View File
@@ -1,79 +0,0 @@
#!/bin/sh
# opencode turn-boundary/completion poller — same rationale as
# orchestra-codex-poll.sh: opencode has no native Stop hook, so this runs as
# a background loop in the pane, polling /v1/harness/turn and watching for
# the .orchestra-report.md completion marker.
#
# opencode's own SSE session-status stream (OpenCodeStatus in
# internal/herdr/occupancy.go) is a fast path this script does not use — it
# would need the pane's session id, which isn't reliably known outside the
# opencode process. Instead this falls back to the same
# most-recently-written message file under opencode's storage dir that
# OpenCodeUsage already reads as its backstop (per AUDIT.md's "Real harness
# quota sources": "the SSE stream is not rock-solid").
#
# Requires: ORCHESTRA_TASK_ID, ORCHESTRA_URL, ORCHESTRA_WORKTREE set in the
# pane's env. Optional: ORCHESTRA_HARNESS_TOKEN,
# ORCHESTRA_POLL_INTERVAL (seconds, default 60).
# Needs: jq, find, curl.
set -u
: "${ORCHESTRA_POLL_INTERVAL:=60}"
: "${ORCHESTRA_WORKTREE:=$PWD}"
[ -z "${ORCHESTRA_TASK_ID:-}" ] && { echo "orchestra-opencode-poll: ORCHESTRA_TASK_ID not set" >&2; exit 1; }
[ -z "${ORCHESTRA_URL:-}" ] && { echo "orchestra-opencode-poll: ORCHESTRA_URL not set" >&2; exit 1; }
auth_header=""
if [ -n "${ORCHESTRA_HARNESS_TOKEN:-}" ]; then
auth_header="Authorization: Bearer ${ORCHESTRA_HARNESS_TOKEN}"
fi
latest_message() {
find "$HOME/.local/share/opencode/storage/message" -type f -name '*.json' -printf '%T@ %p\n' 2>/dev/null \
| sort -rn | head -n1 | cut -d' ' -f2-
}
report_file="${ORCHESTRA_WORKTREE%/}/.orchestra-report.md"
while true; do
sleep "$ORCHESTRA_POLL_INTERVAL"
msg="$(latest_message)"
[ -z "$msg" ] && continue
if [ -f "$report_file" ]; then
report="$(cat "$report_file")"
body="$(jq -n \
--arg task_id "$ORCHESTRA_TASK_ID" \
--arg transcript_path "$msg" \
--arg report "$report" \
'{task_id: $task_id, harness: "opencode", transcript_path: $transcript_path, report: $report}')"
if curl -fsS -X POST "${ORCHESTRA_URL%/}/v1/harness/complete" \
-H "Content-Type: application/json" \
${auth_header:+-H "$auth_header"} \
-d "$body" >/dev/null 2>&1; then
rm -f "$report_file"
exit 0
else
echo "orchestra-opencode-poll: failed to report completion" >&2
fi
continue
fi
body="$(jq -n --arg task_id "$ORCHESTRA_TASK_ID" '{task_id: $task_id}')"
response="$(curl -fsS -X POST "${ORCHESTRA_URL%/}/v1/harness/turn" \
-H "Content-Type: application/json" \
${auth_header:+-H "$auth_header"} \
-d "$body" 2>/dev/null)" || {
echo "orchestra-opencode-poll: failed to reach turn-decision endpoint" >&2
continue
}
decision="$(printf '%s' "$response" | jq -r '.decision // empty')"
if [ "$decision" = "refuse" ] || [ "$decision" = "rotate_now" ]; then
echo "orchestra-opencode-poll: turn decision is $decision" >&2
fi
done
-70
View File
@@ -1,70 +0,0 @@
#!/bin/sh
# Claude Code Stop hook — fires on every turn boundary, not just completion.
# Convention: the agent signals "this task is done" by writing a report file
# named .orchestra-report.md at the worktree root before stopping. If that
# marker is present, report completion. If it is absent, this is an ordinary
# turn boundary — ask the unified turn-decision endpoint (AUDIT.md B3, Phase
# 2 items 1-2) what to do instead of no-op'ing.
#
# Requires: ORCHESTRA_TASK_ID and ORCHESTRA_URL set in the pane's env.
# Optional: ORCHESTRA_HARNESS_TOKEN if the server requires one.
#
# Reads the Stop hook's JSON payload from stdin (has "transcript_path" and
# "cwd"); needs jq.
set -eu
payload="$(cat)"
transcript_path="$(printf '%s' "$payload" | jq -r '.transcript_path // empty')"
cwd="$(printf '%s' "$payload" | jq -r '.cwd // empty')"
[ -z "$transcript_path" ] && exit 0
[ -z "${ORCHESTRA_TASK_ID:-}" ] && exit 0
[ -z "${ORCHESTRA_URL:-}" ] && exit 0
auth_header=""
if [ -n "${ORCHESTRA_HARNESS_TOKEN:-}" ]; then
auth_header="Authorization: Bearer ${ORCHESTRA_HARNESS_TOKEN}"
fi
report_file="${cwd:-.}/.orchestra-report.md"
if [ -f "$report_file" ]; then
report="$(cat "$report_file")"
body="$(jq -n \
--arg task_id "$ORCHESTRA_TASK_ID" \
--arg transcript_path "$transcript_path" \
--arg report "$report" \
'{task_id: $task_id, harness: "claude", transcript_path: $transcript_path, report: $report}')"
if curl -fsS -X POST "${ORCHESTRA_URL%/}/v1/harness/complete" \
-H "Content-Type: application/json" \
${auth_header:+-H "$auth_header"} \
-d "$body" >/dev/null 2>&1; then
rm -f "$report_file"
else
echo "orchestra-stop: failed to report completion" >&2
exit 2
fi
exit 0
fi
body="$(jq -n --arg task_id "$ORCHESTRA_TASK_ID" '{task_id: $task_id}')"
response="$(curl -fsS -X POST "${ORCHESTRA_URL%/}/v1/harness/turn" \
-H "Content-Type: application/json" \
${auth_header:+-H "$auth_header"} \
-d "$body" 2>/dev/null)" || {
echo "orchestra-stop: failed to reach turn-decision endpoint" >&2
exit 0
}
decision="$(printf '%s' "$response" | jq -r '.decision // empty')"
if [ "$decision" = "refuse" ]; then
echo "orchestra-stop: turn decision is refuse — this turn boundary is not safe to stop at" >&2
exit 2
fi
exit 0
+5 -3
View File
@@ -1,6 +1,8 @@
# Copy to /etc/orchestra/orchestra.env (chmod 600, owned by the orchestra
# user) and fill in the values you need. Referenced by orchestra.service via
# EnvironmentFile=. Every var below is read directly from os.Getenv in
# Copy to the compose directory as `.env` (chmod 600) and fill in the values
# you need; `compose.yaml` loads it via `env_file:`. The retired
# orchestra.service EnvironmentFile= path is gone as of 2026-07-31, and the
# container has no entrypoint script that sources an env file — compose passes
# these in directly. Every var below is read directly from os.Getenv in
# cmd/orchestra/main.go and the packages it wires up — grep ORCHESTRA_ in the
# repo if this list ever needs re-deriving.
-25
View File
@@ -1,25 +0,0 @@
[Unit]
Description=Orchestra task orchestrator
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=orchestra
Group=orchestra
WorkingDirectory=/var/lib/orchestra
EnvironmentFile=/etc/orchestra/orchestra.env
ExecStart=/usr/local/bin/orchestra
Restart=on-failure
RestartSec=5s
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true
ReadWritePaths=/var/lib/orchestra
# ORCHESTRA_WORKTREE_ROOT / per-project worktree_root paths and
# ORCHESTRA_REPO must live under one of these, or under /var/lib/orchestra —
# add further ReadWritePaths= lines here if you keep repos elsewhere.
[Install]
WantedBy=multi-user.target
-21
View File
@@ -1,21 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
repo_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)"
tmp_bin="$(mktemp)"
trap 'rm -f -- "$tmp_bin"' EXIT
cd "$repo_dir"
echo "Building Orchestra..."
revision="$(git rev-parse HEAD)"
build_time="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
dirty=false
if [[ -n "$(git status --porcelain)" ]]; then dirty=true; fi
go build -ldflags "-X orchestra/internal/buildinfo.Revision=$revision -X orchestra/internal/buildinfo.Time=$build_time -X orchestra/internal/buildinfo.Dirty=$dirty" -o "$tmp_bin" ./cmd/orchestra
echo "Installing /usr/local/bin/orchestra..."
sudo install -o root -g root -m 0755 "$tmp_bin" /usr/local/bin/orchestra
echo "Restarting orchestra.service..."
sudo systemctl restart orchestra.service
sudo systemctl --no-pager --lines=8 status orchestra.service