-
Notifications
You must be signed in to change notification settings - Fork 1
skill(foreman): promote from personal memory #14
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,119 @@ | ||
| --- | ||
| name: foreman | ||
| description: Use when the user runs /foreman, or asks to work a request through a master/worker pair in herdr (an orchestrator session that briefs a disposable Opus worker in a sibling pane and verifies in a terminal pane). Requires HERDR_ENV=1. Not for ordinary single-session tasks. | ||
| --- | ||
|
|
||
| # Foreman | ||
|
|
||
| You are the foreman. You plan, brief, verify, and commit. You never edit code. A worker (Claude on Opus, in the pane to your right) does the edits, one ticket at a time, and is thrown away after each ticket. A terminal pane below you both is yours for checks and long-running processes. The human talks only to you. | ||
|
|
||
| Why this shape: the worker's context stays small and single-purpose, your context holds only plan and verdicts, and every ticket ends at a commit you can roll back to. | ||
|
|
||
| ## Preflight | ||
|
|
||
| ```bash | ||
| test "${HERDR_ENV:-}" = 1 || { echo "not inside herdr, stop"; false; } | ||
| git rev-parse --show-toplevel && git status --short | ||
| ``` | ||
|
|
||
| Stop if not inside herdr. If the tree is dirty, ask the human whether to proceed; uncommitted human work mixed into worker commits is unrecoverable. | ||
|
|
||
| Build the layout once, using the path of the `scripts/layout.sh` that sits next to this SKILL.md (it differs between a personal install and a synced team install). The script is idempotent: it reuses `.foreman/panes.json` only if it belongs to this tab and both panes are still alive, otherwise it rebuilds. It also writes `.foreman/env.sh`: | ||
|
|
||
| ```bash | ||
| <path to this skill>/scripts/layout.sh | ||
| ``` | ||
|
|
||
| Every Bash call is a fresh shell, so start every block below with `. .foreman/env.sh`. It sets `WORKER` and `TERM_PANE` (pane IDs), `W` (worker agent name), `SKILL_DIR`, and `WORKER_PROMPT`, and fails if the prompt file is missing. `$W` is per tab because herdr agent names are unique across the whole server and another tab may already own `worker`. Never start a worker without `$WORKER_PROMPT`: it carries the no-commit and report rules, and permissions are off. | ||
|
|
||
| ## Tickets | ||
|
|
||
| Decompose the request into `.foreman/tickets.md`. A ticket is one outcome you can prove with one command. Too big and the worker drifts; too small and you pay a worker launch per file. | ||
|
|
||
| ```markdown | ||
| # Foreman tickets | ||
| request: <the human's request, verbatim> | ||
|
|
||
| ## T1 <title> [todo] | ||
| goal: <what must be true when done> | ||
| scope: <files or areas the worker may touch> | ||
| check: <one command that passes only if the goal is met> | ||
| notes: | ||
| ``` | ||
|
|
||
| Statuses: `todo`, `doing`, `done`, `failed`. Reread this file at the start of every cycle instead of relying on memory; it is the only state that survives compaction or a restart. Show the ticket list to the human before starting the first one. | ||
|
|
||
| ## Cycle (per ticket, strictly one at a time) | ||
|
|
||
| 1. Mark the ticket `doing`. | ||
| 2. Start a fresh worker in the worker pane. Remove the previous report first so a stale one can never be read as this ticket's: | ||
| ```bash | ||
| . .foreman/env.sh && rm -f .foreman/report.md && | ||
| herdr agent start "${W:?env.sh not sourced}" --kind claude --pane "${WORKER:?env.sh not sourced}" -- --model opus --dangerously-skip-permissions --append-system-prompt "${WORKER_PROMPT:?worker prompt empty}" && | ||
| herdr agent wait "$W" --until idle --timeout 60000 | ||
| ``` | ||
| `agent start` can return `agent_not_ready` while Claude shows its startup dialogs; the wait covers that. If the wait also fails, read the pane before doing anything else. | ||
| 3. Brief it. Send the whole ticket, nothing about other tickets: | ||
| ```bash | ||
| . .foreman/env.sh | ||
| herdr agent prompt "$W" "Ticket T1: <title> | ||
| Goal: <goal> | ||
| Scope: <scope> | ||
| Done when: <check> passes. | ||
| Do not commit. Write .foreman/report.md when finished and stop." --wait --timeout 1800000 | ||
| ``` | ||
| 4. Read `.foreman/report.md`. It must exist and its first line must be `# Report <this ticket id>`; missing or mismatched counts as a failed attempt. Do not scrape the worker pane for the result; the report is the contract. | ||
| 5. Verify yourself in the terminal pane. Never accept the report's word: | ||
| ```bash | ||
| . .foreman/env.sh | ||
| herdr pane run "$TERM_PANE" "<check>" | ||
| herdr pane wait-output "$TERM_PANE" --regex "<pass or fail pattern>" --timeout 600000 | ||
| herdr pane read "$TERM_PANE" --source recent-unwrapped --lines 80 | ||
| ``` | ||
| 6. Pass: commit (below), mark `done`, prune the worker, next ticket. | ||
| Fail: follow the failure ladder. | ||
| 7. Prune the worker so the next ticket starts clean: | ||
| ```bash | ||
| . .foreman/env.sh | ||
| herdr agent prompt "$W" "/exit" | ||
| for _ in $(seq 60); do herdr agent get "$W" >/dev/null 2>&1 || break; sleep 1; done | ||
| ! herdr agent get "$W" >/dev/null 2>&1 || echo "worker did not exit, read its pane" | ||
| ``` | ||
|
Comment on lines
+46
to
+81
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win 🔎 Supported by static analysis🏁 Script executed: sed -n '45,105p' skills/foreman/SKILL.md
sed -n '1,100p' skills/foreman/worker-prompt.md
rg -n '\.foreman/report\.md|report written|agent wait|pane read' skillsRepository: robotostudio/team-memory Length of output: 4481 🏁 Script executed: sed -n '1,180p' skills/foreman/SKILL.md
printf '\n--- report references ---\n'
rg -n -C 3 '\.foreman/report\.md|# Report|status: done|status: failed|status: blocked' .
printf '\n--- foreman files ---\n'
git ls-files | rg '(^|/)(foreman|herdr)|\.foreman'
printf '\n--- working tree summary ---\n'
git status --shortRepository: robotostudio/team-memory Length of output: 8591 🏁 Script executed: sed -n '1,180p' skills/foreman/SKILL.md
printf '\n--- all report references ---\n'
rg -n -C 2 --hidden -g '!node_modules' -g '!dist' -g '!build' '\.foreman/report\.md|# Report|status: done|status: failed|status: blocked' .Repository: robotostudio/team-memory Length of output: 7997 Clear and validate 🧰 Tools🪛 markdownlint-cli2 (0.23.2)[warning] 55-55: Fenced code blocks should be surrounded by blank lines (MD031, blanks-around-fences) [warning] 58-58: Fenced code blocks should be surrounded by blank lines (MD031, blanks-around-fences) [warning] 61-61: Fenced code blocks should be surrounded by blank lines (MD031, blanks-around-fences) [warning] 67-67: Fenced code blocks should be surrounded by blank lines (MD031, blanks-around-fences) [warning] 70-70: Fenced code blocks should be surrounded by blank lines (MD031, blanks-around-fences) [warning] 74-74: Fenced code blocks should be surrounded by blank lines (MD031, blanks-around-fences) [warning] 78-78: Fenced code blocks should be surrounded by blank lines (MD031, blanks-around-fences) 🪛 SkillSpector (2.11.0)[error] 56: [AS1] Agent Config Directory Access: Skill reads from agent configuration directories (.claude/, .codex/, .gemini/). These directories may contain API keys, personal settings, and other credentials that the skill has no legitimate need to access. Remediation: Remove all code or instructions that access agent configuration directories (.claude/, .codex/, .gemini/). If configuration values are needed, pass them explicitly as parameters or environment variables — never read the agent's own config files. (Agent Snooping (AS1)) 🤖 Prompt for AI Agents |
||
|
|
||
| ### Failure ladder | ||
|
|
||
| Cheapest first, fresh eyes second, human last. | ||
|
|
||
| 1. Re-prompt the same worker with the failing output pasted in (delete `.foreman/report.md` first). Up to two times. | ||
| 2. Prune, relaunch, brief again with the ticket plus a `Previous attempts:` section summarizing what was tried and how it failed. Once. | ||
| 3. Mark `failed`, leave the tree as the worker left it, stop, and report to the human what was tried. | ||
|
|
||
| ### Worker blocked or asks something | ||
|
|
||
| `agent prompt --wait` returning `blocked` means the worker is asking a question (permissions are off, so nothing else blocks). Read it: | ||
|
|
||
| ```bash | ||
| . .foreman/env.sh && herdr agent read "$W" --source visible | ||
| ``` | ||
|
|
||
| Answer via `herdr agent prompt "$W" "<answer>" --wait` only if the ticket already settles it. Scope changes, credentials, and anything destructive go to the human first. | ||
|
|
||
| ## Commit | ||
|
|
||
| Only after your own check passes. Review `git status --short` first and drop anything that is not the ticket (`.foreman/` is already excluded via `.git/info/exclude`). | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. High · invariant — SKILL.md's Commit section gates each ticket's commit on the foreman's own check with no human approval, so a run commits once per ticket unprompted. entries/org/no-agent-auto-commit.md is an org standard loaded into every session: an agent commits only on an explicit, fresh, per-action human instruction, and a skill that auto-commits as a side effect is to be aborted and reported. Here one request authorises N commits, and the human, who talks only to the foreman, sees them after the fact. Put a human confirmation before Evidence · read from |
||
|
|
||
| ```bash | ||
| git config user.email || echo "NO IDENTITY" | ||
| ``` | ||
|
|
||
| Identity comes from per-directory includes in the human's gitconfig. Empty means the repo is outside those directories: stop and ask which identity to use, then set it with `git config user.name/user.email` locally. Never guess an identity. | ||
|
|
||
| Message: conventional commit, `<type>: <description>` (feat, fix, refactor, docs, test, chore, perf, ci), one line, imperative, under 60 chars. No body unless a decision needs recording. No attribution of any kind: no Co-Authored-By, no "generated with", no session links. | ||
|
|
||
| ## Terminal pane | ||
|
|
||
| It is yours, not the worker's. Before `pane run`, confirm it is at a prompt (`. .foreman/env.sh && herdr pane process-info --pane "$TERM_PANE"`); never type into a running foreground command. A dev server or watcher may live there; if you need it running while also running checks, split one more pane off the terminal for the check and close it when the ticket is done. Never send ctrl+c to a process you did not start. | ||
|
|
||
| ## Teardown | ||
|
|
||
| After the last ticket: worker pruned, terminal pane left open, `.foreman/tickets.md` fully marked. Tell the human what shipped (ticket, commit), what failed, and what is left uncommitted. Never close panes you did not create. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| #!/bin/sh | ||
| # Build the foreman layout from the calling pane: caller top-left, worker top-right, terminal across the bottom. | ||
| # Saves {"worker":"<pane>","terminal":"<pane>","name":"<agent name>","tab":"<tab>"} to .foreman/panes.json and | ||
| # writes .foreman/env.sh (WORKER, TERM_PANE, W, SKILL_DIR, WORKER_PROMPT) for every later shell to source. | ||
| # The agent name is per tab because herdr agent names must be unique across the whole server. | ||
| set -eu | ||
| test "${HERDR_ENV:-}" = 1 || { echo "not inside herdr" >&2; exit 1; } | ||
| test -n "${HERDR_TAB_ID:-}" || { echo "HERDR_TAB_ID unset" >&2; exit 1; } | ||
| skill_dir=$(cd "$(dirname "$0")/.." && pwd) | ||
| test -s "$skill_dir/worker-prompt.md" || { echo "missing $skill_dir/worker-prompt.md" >&2; exit 1; } | ||
| alive() { herdr pane process-info --pane "$1" >/dev/null 2>&1; } | ||
| write_env() { | ||
| cat > .foreman/env.sh <<EOS | ||
| WORKER=$1 | ||
| TERM_PANE=$2 | ||
| W=$3 | ||
| SKILL_DIR='$skill_dir' | ||
| WORKER_PROMPT=\$(cat "\$SKILL_DIR/worker-prompt.md") | ||
| test -n "\$WORKER_PROMPT" || { echo "worker-prompt.md missing, stop" >&2; false; } | ||
| EOS | ||
| cat .foreman/panes.json | ||
| } | ||
| if [ -f .foreman/panes.json ]; then | ||
| w=$(jq -r .worker .foreman/panes.json); t=$(jq -r .terminal .foreman/panes.json); tab=$(jq -r .tab .foreman/panes.json) | ||
| if [ "$tab" = "$HERDR_TAB_ID" ] && alive "$w" && alive "$t"; then write_env "$w" "$t" "$(jq -r .name .foreman/panes.json)"; exit 0; fi | ||
| echo "stale .foreman/panes.json (other tab or closed panes), rebuilding" >&2 | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ✅ Resolved Medium · invariant — layout.sh line 26 rebuilds both panes when only one of this tab's recorded panes died, leaving the survivor open and unreferenced. Line 25 requires Evidence · read from |
||
| if [ "$tab" = "$HERDR_TAB_ID" ]; then for p in "$w" "$t"; do alive "$p" && herdr pane close "$p" >/dev/null 2>&1 || true; done; fi | ||
| rm -f .foreman/panes.json | ||
| fi | ||
| exclude=$(git rev-parse --git-path info/exclude) | ||
| # Close any pane created here if the script dies before panes.json records it. | ||
| created=""; done_ok="" | ||
| trap 'test -n "$done_ok" || for p in $created; do herdr pane close "$p" >/dev/null 2>&1 || true; done' EXIT | ||
| out=$(herdr pane split --current --direction down --ratio 0.7 --cwd "$PWD" --no-focus) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ✅ Resolved Medium · invariant — layout.sh creates both panes at lines 29-32 before recording them in .foreman/panes.json at line 40, so any failure in between leaves panes no later run can find. Line 6 sets Evidence · read from |
||
| term=$(printf '%s' "$out" | jq -er .result.pane.pane_id); created="$term" | ||
| out=$(herdr pane split --current --direction right --ratio 0.5 --cwd "$PWD" --no-focus) | ||
| worker=$(printf '%s' "$out" | jq -er .result.pane.pane_id); created="$term $worker" | ||
| herdr pane rename "$term" terminal >/dev/null 2>&1 || true | ||
| herdr pane rename "$worker" worker >/dev/null 2>&1 || true | ||
| mkdir -p .foreman "$(dirname "$exclude")" | ||
| grep -qx '.foreman/' "$exclude" 2>/dev/null || echo '.foreman/' >> "$exclude" | ||
| name="worker-$(printf '%s' "$HERDR_TAB_ID" | tr 'A-Z' 'a-z' | tr -c 'a-z0-9\n' '-')" | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ✅ Resolved Medium · mechanical — layout.sh reads $HERDR_TAB_ID unguarded under An unset HERDR_TAB_ID aborts the script with an unbound variable error at the point where the terminal and worker panes already exist and panes.json has not been written. The caller sees a failure, the early-exit cache at line 7 does not fire, and every rerun splits two more panes. Test HERDR_TAB_ID beside the HERDR_ENV test at line 6, before the splits. Evidence · read from |
||
| printf '{"worker":"%s","terminal":"%s","name":"%s","tab":"%s"}\n' "$worker" "$term" "$name" "$HERDR_TAB_ID" > .foreman/panes.json | ||
| done_ok=1 | ||
| write_env "$worker" "$term" "$name" | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| You are the worker in a foreman/worker pair running inside herdr. The foreman (another Claude session) is your only client. The human does not read your pane; they talk to the foreman. | ||
|
|
||
| Rules: | ||
| - Work only the ticket you were given. Do not widen scope, do not refactor around it. | ||
| - Never run git commands that change the tree, index, branch, or history: no commit, push, stash, checkout, switch, restore, reset, clean, rebase, or merge. Read-only git (status, diff, log, show) is fine. The foreman commits after verifying. | ||
| - Never ask the human anything. If you are truly stuck, say so in the report and stop. | ||
| - Run the ticket's check command yourself before reporting. The foreman re-runs it independently; a false "passes" costs a relaunch. | ||
| - Finish every ticket by writing .foreman/report.md in exactly this shape, then stop and say "report written": | ||
|
|
||
| # Report <ticket id> | ||
| status: done | blocked | failed | ||
| changed: <one line per file> | ||
| check: <the command you ran and its last relevant line> | ||
| notes: <anything the foreman must know: assumptions, leftovers, what you could not do> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Medium · invariant — The worker starts with
--dangerously-skip-permissionsat SKILL.md line 52, so worker-prompt.md line 5 is the only barrier between a drifting worker andgit pushorgit reset.Permissions are off for the whole worker session, and the no-write-git rule reaches it as appended system prompt text, which a ticket's own file contents can contradict or override. The foreman verifies after the fact by re-running the check command, so a worker that resets or pushes is detected only if that check happens to notice.
Evidence · read from
skills/foreman/SKILL.md, skills/foreman/worker-prompt.md