chore(marathon): add plan-driven marathon coding infrastructure

Model the setup on existing marathon instances (FORGE/AIVC/NEEDLE):
- .marathon/instruction.md — per-iteration autonomous prompt, plan-driven off
  docs/plan/plan.md with the 7-phase order, settled invariants, and commit/push
  loop protocol
- .marathon/launch-marathon.sh — self-contained `while true` loop piping the
  instruction to `claude --print`, sourcing model/proxy config from an
  untracked .marathon/env (keeps internal endpoints out of this public repo)
- .marathon/env.example — config template
- .gitignore — ignore .marathon/env, logs/, *-config/, and .beads/*.db

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
jedarden 2026-05-25 21:42:55 -04:00
parent 0276706340
commit bc5075a016
4 changed files with 175 additions and 0 deletions

7
.gitignore vendored Normal file
View file

@ -0,0 +1,7 @@
# Marathon: keep host-specific/internal config and runtime state local
.marathon/env
.marathon/logs/
.marathon/*-config/
# Beads: db is regenerable from issues.jsonl
.beads/*.db

20
.marathon/env.example Normal file
View file

@ -0,0 +1,20 @@
# Copy to .marathon/env (untracked) and fill in.
# Model/proxy config for the marathon launcher. Sourced and exported by launch-marathon.sh.
# Anthropic-compatible endpoint (e.g. an Anthropic-API-compatible proxy in front of your model).
export ANTHROPIC_BASE_URL="https://your-anthropic-compatible-endpoint"
# Auth token for that endpoint (a placeholder is fine if the proxy handles auth).
export ANTHROPIC_AUTH_TOKEN="set-me"
# Pin every model slot to one model so the loop is deterministic.
export ANTHROPIC_MODEL="glm-4.7"
export ANTHROPIC_DEFAULT_OPUS_MODEL="glm-4.7"
export ANTHROPIC_DEFAULT_SONNET_MODEL="glm-4.7"
export ANTHROPIC_DEFAULT_HAIKU_MODEL="glm-4.7"
export CLAUDE_CODE_SUBAGENT_MODEL="glm-4.7"
# If the endpoint uses a self-signed cert on a VPN entrypoint, you may need:
# export NODE_TLS_REJECT_UNAUTHORIZED=0
export DISABLE_AUTOUPDATER=1
export DISABLE_TELEMETRY=1

99
.marathon/instruction.md Normal file
View file

@ -0,0 +1,99 @@
# Trail Boss — Marathon Coding Instruction
You are an autonomous developer implementing **Trail Boss**. Each iteration you read this
file, do **one coherent unit of work** toward the plan, verify it, commit, push, and the loop
restarts you fresh. Work is driven by `docs/plan/plan.md` (the authoritative design).
## Project overview
Trail Boss is a single-pane attention router for interactive AI coding-agent sessions
(Claude Code, running in tmux panes). When a session blocks waiting on the human — a
permission prompt, or a finished turn awaiting the next instruction — Trail Boss surfaces it
in a flat FIFO dead-letter queue and **navigates the operator to the live tmux pane** to act.
It never injects input ("navigator, not relay"). The human is on the loop, engaged only by
exception.
## Working directory
This repository (the marathon runs with its CWD set to the repo root). Use **relative paths**.
## Authoritative docs — read the relevant parts before coding
- `docs/plan/plan.md` — the complete design: capabilities, detection model, architecture,
the daemon/presentation split, the switching & keybindings surface, phases, open questions.
- `docs/research/claude-code-mechanics.md` — the Claude Code hook/env/payload facts
(probe-confirmed): `$TMUX_PANE` is in the hook env; the single `trailboss-emit.sh`;
`Stop` + `PermissionRequest` are the two enqueue triggers.
- `docs/notes/decisions.md` — settled decisions. **Do not re-litigate these** (navigator-not-
relay, flat FIFO/no-priority, Notification dropped, collector-is-the-daemon, etc.).
## Current state
Design is complete; **no implementation code exists yet**. The marathon starts at Phase 1.
Track progress in `PROGRESS.md` at the repo root (create it on the first iteration).
## Iteration protocol
Each iteration:
1. **Orient** — read `PROGRESS.md` (if present) to see what's done and what's next. Skim the
relevant section of `docs/plan/plan.md`.
2. **Pick one unit** — the next unblocked item in the phase order below. Don't skip ahead;
foundational work first. Stay focused on a single coherent change.
3. **Implement** — write the code/scripts. Follow the settled design in the docs; if you make
a new design decision, record it in `docs/notes/decisions.md`.
4. **Verify** — actually exercise what you built (run the script, hit the endpoint, fire the
hook). Don't mark something done on code-read alone. For hook/tmux behavior, test in a
throwaway tmux session under `~/scratch` or a temp dir — never disturb other tmux sessions,
and never `send-keys` into panes you don't own.
5. **Commit + push** — mandatory every iteration: `git add` the specific files, commit with a
conventional message (`feat(scope): …`, `fix(scope): …`, `docs(scope): …`), then
`git push origin main`. Origin mirrors to GitHub automatically.
6. **Update `PROGRESS.md`** — what you did, what's next, any new known issues.
If a phase's work is genuinely blocked, write down why in `PROGRESS.md` and pick the next
most-impactful unblocked unit.
## Phase order (see `docs/plan/plan.md` "Implementation phases" for detail)
1. **Probe `PermissionRequest`** — confirm it fires for the gate types in use and capture its
payload shape (the one remaining unknown). `Stop`/`SessionStart`/`$TMUX_PANE` are already
probe-confirmed. Record findings in `docs/research/claude-code-mechanics.md`.
2. **Emitter**`trailboss-emit.sh`: forwards the hook stdin payload to the collector and
injects `$TMUX_PANE`. Plus the `settings.json` hook wiring (all hooks → this one script).
3. **Daemon (control plane)** — ingest endpoint (loopback only), SQLite state, the self-healing
`session_id → pane` registry, the transcript reconcile loop, and the FIFO queue. Expose
`/next` and `/skip`. Behind the normalized stuck/unstuck adapter contract so detection stays
harness-agnostic. Default stack: fork disler's hook event store (Bun + SQLite) unless a
better fit emerges — record the choice in `docs/notes/decisions.md`.
4. **Navigation** — resolve a pane id to `switch-client`/`select-window`/`select-pane`;
compute the next head on resolve/skip (operator-initiated jump, no forced focus-steal).
5. **Presentation**`display-popup` queue picker + the Next/Skip keybindings + an optional
status-line "N stuck" segment.
6. **Walking skeleton** — close the loop end-to-end: stuck pane → loaded into focus → interact
→ reconcile dequeues → next stuck session loads. This is the first real milestone.
7. **Iterate** — auto-jump toggle, skip/cooldown tuning, embedded `link-window` view, a second
harness adapter to validate the abstraction.
## Settled invariants (enforce; do not violate)
- **Navigator, not relay.** Never send synthesized input into a session. Delivery is
navigation to the live pane; any `send-keys` is human-authored text only and secondary.
- **The queue never lies.** The transcript JSONL is ground truth; reconcile is authoritative
over hook events.
- **Flat FIFO, no priority.** `permission` and `stopped` are treated identically; `reason` is
display-only.
- **Loopback-only ingest.** The collector binds `127.0.0.1` only.
## Git workflow
- All work goes to `main` (no feature branches for marathon work).
- Commit + push every iteration. Conventional commit messages.
- **Never force-push; never amend published commits.**
## Constraints
- Don't run a TUI inside this agent's own terminal — test TUIs in a separate tmux session.
- Keep the repo **public-clean**: no internal infrastructure hostnames, cluster names, tokens,
or absolute home paths in committed files. Use relative paths and env/config indirection.
- Don't add dependencies or abstractions the current phase doesn't need.

49
.marathon/launch-marathon.sh Executable file
View file

@ -0,0 +1,49 @@
#!/usr/bin/env bash
# Trail Boss marathon launcher.
# Runs claude headless in a loop: each iteration pipes instruction.md to
# `claude --print`, which does one unit of work, commits, pushes, and exits;
# the loop restarts it fresh. Model/proxy config comes from .marathon/env
# (untracked — copy env.example to env and fill in).
#
# Usage:
# ./.marathon/launch-marathon.sh # session "trailboss-marathon"
# ./.marathon/launch-marathon.sh my-session
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_DIR="$(dirname "$SCRIPT_DIR")"
INSTRUCTION_FILE="$SCRIPT_DIR/instruction.md"
CONFIG_DIR="$SCRIPT_DIR/glm47-config"
ENV_FILE="$SCRIPT_DIR/env"
LOG_DIR="$SCRIPT_DIR/logs"
SESSION_NAME="${1:-trailboss-marathon}"
[ -f "$ENV_FILE" ] || { echo "Missing $ENV_FILE — copy env.example to env and fill it in." >&2; exit 1; }
[ -f "$INSTRUCTION_FILE" ] || { echo "Missing $INSTRUCTION_FILE" >&2; exit 1; }
# shellcheck disable=SC1090
set -a; source "$ENV_FILE"; set +a # exports ANTHROPIC_BASE_URL, ANTHROPIC_AUTH_TOKEN, model slots, etc.
mkdir -p "$LOG_DIR" "$CONFIG_DIR"
if tmux has-session -t "$SESSION_NAME" 2>/dev/null; then
echo "Session '$SESSION_NAME' already exists. Attach: tmux attach -t $SESSION_NAME"
exit 0
fi
MODEL="${ANTHROPIC_MODEL:-glm-4.7}"
LOG="$LOG_DIR/session_$(date +%Y%m%d_%H%M%S).jsonl"
LOOP_CMD="cd '$REPO_DIR' && unset CLAUDECODE && export CLAUDE_CONFIG_DIR='$CONFIG_DIR' && while true; do
echo \"[trailboss-marathon] iteration start \$(date -Iseconds)\"
cat '$INSTRUCTION_FILE' | claude --model '$MODEL' --dangerously-skip-permissions --output-format stream-json --verbose --print 2>&1 | tee -a '$LOG'
ec=\$?
echo \"[trailboss-marathon] iteration done (exit \$ec) \$(date -Iseconds)\"
if [ \$ec -ne 0 ]; then sleep 30; else sleep 5; fi
done"
tmux new-session -d -s "$SESSION_NAME" -c "$REPO_DIR"
tmux send-keys -t "$SESSION_NAME" "$LOOP_CMD" Enter
echo "Marathon running: $SESSION_NAME (model: $MODEL)"
echo " Attach: tmux attach -t $SESSION_NAME Stop: tmux kill-session -t $SESSION_NAME"
echo " Logs: $LOG"