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:
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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": [
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user