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:
parent
0276706340
commit
bc5075a016
4 changed files with 175 additions and 0 deletions
7
.gitignore
vendored
Normal file
7
.gitignore
vendored
Normal 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
20
.marathon/env.example
Normal 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
99
.marathon/instruction.md
Normal 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
49
.marathon/launch-marathon.sh
Executable 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"
|
||||
Loading…
Add table
Reference in a new issue