About goal-supervisor
Goal Supervisor is a skill for keeping long-running Claude goals or workflows moving after the worker process exits. It resumes sessions from transcripts or checkpoints and uses outcome sentinels to report completion, blockers, or restart exhaustion.
Key features
- 01Restarts failed workers with backoff
- 02Resumes sessions using transcripts and checkpoints
- 03Sentinels report completion or blockers
- 04Supports supervised workflow runs
Use cases
- 01Recovering a goal after a Claude process exits
- 02Running a long workflow in an isolated session
- 03Keeping a goal supervisor alive across logout or reboot
Install
Copy the command and run it in a terminal.
npx skills add https:/ / github.com/ joelfrancisco/ skills --skill goal-supervisorAfter installing, type/goal-supervisorin Claude Code to confirm it loaded.
Bundled filessupervise.sh
Details
From the project READMEGoal Supervisor
When the claude process orchestrating a /goal dies, its subagents survive
but nothing drives the loop anymore. The state, however, lives on in the
session transcript — claude -p --resume <session-id> picks it back up with
full context. This skill launches supervise.sh (in the same
directory as this SKILL.md — resolve the absolute path from it) to exploit
that in a one_for_one supervision tree:
launchd/systemd/tmux (optional) ← keeps the supervisor alive
└── supervise.sh ← restart policy + backoff + stall detection
└── claude -p /goal ← worker, session pinned via --session-id
└── subagents ← survive on their own; the worker re-adopts state
How it resumes: the supervisor picks the session UUID itself (uuidgen +
--session-id). If the worker dies without creating .supervisor/DONE, it
relaunches with claude -p --resume <uuid> — full context preserved. The
worker is instructed to keep .supervisor/checkpoint.md (a short
claude-handoff-style summary) updated after every step; when the session
becomes unresumable (transcript missing, or resume failing fast repeatedly),
the supervisor discards it and starts a fresh session whose prompt points at
that checkpoint, so progress carries over.
Outcomes: the supervisor exits 0 on every intentional stop and leaves a
sentinel telling which one — .supervisor/DONE (goal finished),
.supervisor/BLOCKED (needs a human; blocker described in checkpoint.md),
.supervisor/GAVE_UP (restart limit hit). A non-zero exit means the
supervisor itself crashed — which is exactly what the root supervisor
restarts on. While BLOCKED or GAVE_UP is present, relaunching the
supervisor exits 0 immediately — resolve the blocker and remove the sentinel
(or pass --fresh) to retry.
Invocation
Prefer the daemon. agent-supervisord (the Rust daemon this script
prototyped) supervises the same sentinel protocol with strictly better
semantics: a model/quota wall becomes BLOCKED immediately instead of burning
the restart budget (the bash script retries into the wall), plus restart
types, groups, mailbox call/cast, and a self-watchdog. Route through it
whenever it is up; fall back to supervise.sh when it is not.
- Decide the headless permission mode with the user's intent in mind — under
-pthere are no interactive prompts, so a goal without--permission-mode acceptEdits(orbypassPermissions, if the user asks for it) stalls on the first denied edit. The daemon's per-kind defaults are goal→acceptEdits, workflow→bypassPermissions; pass--claude-argpairs to override. - If
agentctl pinganswers, add + start the job.--workdirmust be the goal's OWN directory (that is where.supervisor/lives and it must not be shared between goals; the worker still finds the repo's CLAUDE.md via the ancestor walk):
agentctl add --name goal-<slug> --kind goal \
--target "<abs path>/goals/<slug>/goal.md" \
--workdir "<abs path>/goals/<slug>" \
--claude-arg=--permission-mode --claude-arg=bypassPermissions \
--start
(--kind workflow for a Workflow script target.) Only if the daemon is
down, launch the bash fallback in the background, outside the current
session's process tree:
nohup "<path-to-this-skill>/supervise.sh" "goals/<slug>/goal.md" \
-- --permission-mode acceptEdits >/dev/null 2>&1 &
- Tell the user where to follow along, and which sentinel files signal the
outcome (same paths under both runners):
agentctl status goal-<slug>/agentctl logs goal-<slug> -f— daemon viewtail -f goals/<slug>/.supervisor/worker.log— claude output.supervisor/DONE/BLOCKED/GAVE_UP— outcome sentinels (above)- steering a live job:
agentctl cast goal-<slug> "<instruction>"(delivered at its next turn; daemon only)
supervise.sh options
--max-restarts N/--window S— restart intensity (default 10 restarts per 3600s, not counting the initial launch); once exceeded, it gives up, writesGAVE_UP, and notifies.--stall S— no session-transcript activity for S seconds → kill and restart (default 3600; 0 disables). Inactive while the transcript file is not found, so a path mismatch never kills a healthy worker. Raise it for goals with long silent phases (big builds, long subagent runs).--fresh— discard the previous session and outcome sentinels and start over (keeps the checkpoint as memory).
Workflows (--kind workflow)
Long Workflow runs die when the conversation that hosts them compacts — the fix is isolation: run the workflow in a dedicated supervised session whose conversation stays tiny, so compaction never fires. Same tree, different worker:
nohup "<path-to-this-skill>/supervise.sh" "path/to/workflow.js" --kind workflow \
-- --permission-mode bypassPermissions --model sonnet >/dev/null 2>&1 &
- The runner session calls the Workflow tool on the script, persists the
runId to
.supervisor/workflow-run-id, waits for completion, writes.supervisor/result.mdandDONE. - On death, the resumed session calls Workflow with
resumeFromRunIdfrom that file: completedagent()calls replay from the journal cache and only the remainder runs live. The resume continues under the same runId/journal. - The journal cache is session-bound: if the supervisor has to discard the session (unresumable transcript), the workflow restarts from scratch.
- Permissions:
acceptEditsis NOT enough — executing a dynamic workflow script from a headless session hits an interactive approval and the run dies on the spot. Use--permission-mode bypassPermissions(validated), or a permission allowlist covering the Workflow tool if you want something narrower. - Validated end-to-end: SIGKILL mid-run at stage 3/12 → resume → 3 stages from cache + 9 live, full result delivered.
Supervisor of the supervisor (root of the tree)
To survive logout/reboot, run supervise.sh itself under the system init. Because intentional stops exit 0, "restart only on failure" gives the right semantics on both platforms: crashes are restarted, DONE/BLOCKED/GAVE_UP stay stopped.
macOS (launchd): generate ~/Library/LaunchAgents/com.user.goal-<slug>.plist
with ProgramArguments pointing at supervise.sh + goal.md, WorkingDirectory
set to the repo, RunAtLoad, and KeepAlive: {SuccessfulExit: false}, then
load it with launchctl load.
Linux (systemd user unit): the quick way is a transient unit:
systemd-run --user --unit=goal-<slug> \
--property=Restart=on-failure --property=RestartSec=30 \
--working-directory=<repo> \
"<path-to-this-skill>/supervise.sh" "goals/<slug>/goal.md" -- --permission-mode acceptEdits
# follow along: journalctl --user -u goal-<slug> -f
To survive logout, enable linger: loginctl enable-linger $USER.
(Persistent equivalent: a .service in ~/.config/systemd/user/ with
Restart=on-failure + WorkingDirectory and systemctl --user enable --now.)
Simple alternative on both platforms: tmux new -d -s goal-<slug> '...supervise.sh ...'.