agentleFS
fusebase-devfusebase-dev/fusebase-flow/.agents/skills/liveness-discipline/SKILL.md

liveness-discipline

Skill9 starsChanged 28 days agoMITView on GitHub

Use when launching any long or silent work that could hang — your own probe/script/deploy/fetch-loop/browser-automation, a sub-agent, or a workflow — or when the operator says "the background task hangs", "the agent sits idle", "it never resumes", "is it still running?", or a turn ended waiting on something that never came back, or an orchestrating session must wait on background work. Delivers FR-27's never-launch-bare protocol: bounded/watchdog wrapper, in-turn completion, or BLOCKED-AT return; the outside-watcher default and host wake paths; how to diagnose a suspected hang by activity not 0-byte existence; how to recover. Do NOT use to justify a blocking gate or a hook that claims to verify the protocol (a hang is invisible from inside; it is watched from outside), and do NOT use for delegated sub-agent turn-completion alone (task-delegation owns that slice — cross-linked here).

The file

We split this skill into the rules it gives an agent

2 rules. About 0 are common, 2 are distinctive, and 0 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈2

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 0

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Before launching ANY long/silent work, attach at least one of these.

    line 32
  2. Distinctive:

    Check activity: last log line, output file mtime, byte growth, last progress marker — NOT mere "the file exists" (a 0-byte file proves …

    line 84

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Liveness Discipline (FR-27)

The failure (why this rule exists)

A long/silent command (probe, test script, deploy, fetch loop, browser automation), a sub-agent, or a harness-auto-backgrounded workflow that HANGS emits no completion event. The host therefore never re-invokes the agent, which idles indefinitely while appearing "done" — until a human asks "is it still running?". The hang→no-event→idle failure is invisible from inside: the hung call fires no post-call hook, the agent waiting on it cannot act, and a host stall watchdog that excludes tool-call time does not see it. It is visible from outside: a separate bounded watcher that polls activity (SHA, mtime, byte count) and exits on progress, completion or stall (§ Wake interval). So this rule is not enforced by a gate, and a Stop-time "watchdog: applied" signal would be attestation theatre. Enforcement = safe-default tooling (hooks/local/lib/bounded-run.sh) + present-by-construction delivery (this skill in context at launch time) + an outside watcher (§ Wake interval).

Guardrail (the rule's floor)

Quality/safety floor unchanged. Never launch bare — but a liveness guarantee is the floor, not a license to kill a slow-but-progressing job. A bounded deadline is sized for the work, never so tight it trains premature kills (the FR-26 "budget gate trains truncation" lesson, inverse). Diagnose before you kill.

Protocol — never launch bare → attach ≥1 liveness guarantee

Before launching ANY long/silent work, attach at least one of these. In preference order:

#GuaranteeWhenHow
1Complete in-turnthe work fits the current turnrun foreground with a hard timeout; do not background it at all
2Bounded wrapper + incremental loggingthe work is genuinely long and you must background itsource hooks/local/lib/bounded-run.sh + one bounded_run call (below); the wrapper emits a terminal timeout line so the job reaches completion-or-death, never a silent idle
3BLOCKED-AT-<gate> + record-then-read pointerthe remaining wait is UNBOUNDED (human approval gate, external event with no ETA)return the explicit BLOCKED-AT-<gate> verdict + what "cleared" looks like + a pointer to where reality is recorded; the orchestrator re-dispatches when the gate clears (flow-skills/task-delegation Blocked-return rule)

Mandatory regardless of which guarantee you pick:

  • Internal watchdog from the FIRST version — put the deadline in at authoring time, not after it hangs once. Retrofitting a timeout after the first idle is too late.
  • Per-call timeouts — every individual fetch/RPC/subprocess inside a loop carries its own timeout, not just the outer wrapper.
  • Always flush partial results — write durable facts AS THEY OCCUR (skeleton first, rows as earned) so a kill at the deadline still leaves evidence (flow-skills/task-delegation progress ledger, flow-skills/smoke-testing record-then-read).
  • Incremental progress logging to stderr — a job that prints "still working: step 3/8" cannot masquerade as a hang, and bounded_run does this for you.

Compact bounded-run example (D5 — the copy-paste skeleton)

bounded_run <deadline-secs> <progress-label> -- <cmd> [args...] sources the shared core from run-with-timeout.sh (so the ffhc_* health-check API is untouched), bounds the MONITORED process to the deadline, logs a heartbeat to stderr every interval, and ALWAYS emits a terminal line — either the command's completion or a bounded-run: TIMEOUT line — so the job reaches completion-or-death instead of a silent idle.

#!/usr/bin/env bash
set -uo pipefail
source "$(git rev-parse --show-toplevel)/hooks/local/lib/bounded-run.sh"

# Bound a slow re-verify probe to 120s; heartbeat to stderr; terminal line guaranteed.
bounded_run 120 "re-verify probe" -- ./scripts/reverify.sh --target "$URL"
rc=$?
# rc 124 = deadline elapsed; rc 137 = ignored SIGTERM, SIGKILLed after the -k grace.
# On timeout the wrapper already printed the terminal "bounded-run: TIMEOUT" line —
# surface it, flush partial results, then diagnose (do not blind-retry).

# Long run: launch the WRAPPER as the host's background command (Claude Code: run_in_background), or `&`
# only from a shell that outlives the wait — never `&` under it. Then wait on its terminal line in <=230 s
# slices (tool timeout >=240 s); bounded_wait rc 124 = not yet: re-issue it, or arm a watcher.
bounded_run 1800 "suite" -- ./scripts/run-suite.sh >suite.log 2>&1 &
bounded_wait suite.log '^bounded-run: (done|TIMEOUT|SKIPPED).* — suite( \(rc |$)'

Heartbeat file. Each call also writes state/audit/liveness/<label>-<pid>-<n>.hb (git-excluded on upgrade; BOUNDED_RUN_HB_DIR moves it, empty disables; pruned after 7 days untouched): a header, one running <s>/<deadline> line per heartbeat, then done / TIMEOUT / SKIPPED. It proves the wrapper is alive, not that the work progresses. No terminal record = the wrapper has not returned yet: an rc-137 return lands after deadline + the 5 s kill grace, so treat it as hung only with no terminal record by launch + deadline + grace + ~30 s (MSYS reap slack).

Honest scope (D7 — do not overstate). bounded_run bounds the monitored process only. It does NOT:

  • kill a grandchild you &-detached or Start-Process'd out from under it — so don't detach under the wrapper; keep the work in the monitored process tree;
  • interrupt an uninterruptible OS wait;
  • prove the host re-invokes you — it ensures the process reaches completion/death so a host that surfaces background completion has an event to deliver, and so a 0-byte silent idle becomes a visible timeout line.

Therefore: put a deadline INSIDE long scripts too (an event-loop-blocked setTimeout won't fire; an inner fetch needs its own timeout), don't &-detach under the wrapper, and always flush partial results.

Diagnose a suspected hang (activity, not existence)

A slow-but-progressing job is NOT a hang. Distinguish before you kill:

  • Check activity: last log line, output file mtime, byte growth, last progress marker — NOT mere "the file exists" (a 0-byte file proves nothing) and NOT "the process is listed".
  • Slow but progressing (mtime advancing, new log lines, byte count climbing) → let it run; widen the deadline if the work legitimately needs it.
  • Stalled (no activity past the expected interval, no heartbeat, deadline elapsed) → confirmed hang; recover.

Recover from a confirmed hang

  1. Stop the stalled process (the wrapper's timeout line means it already terminated; otherwise terminate it).
  2. Re-run the robust version — the same launch with a deadline + incremental logging attached (never blind-retry the bare launch; that's the two-strike anti-pattern, FR-26).
  3. Clean residue — partial files, locks, half-written state from the killed run.
  4. Retry only transient rate-limits with bounded, labeled backoff; a structural hang is not a rate-limit and re-attempting it unchanged is forbidden.

Zero-trust sub-agent liveness (mandatory — WS8)

A dispatched sub-agent (or Codex) is a long/silent launch under someone else's control — the same hang→no-event→idle failure applies, plus you cannot see its process. Never trust or passively wait on its completion ping. The completion notification can be missing, late, or fire on a transcript that is 0 bytes. Apply the zero-trust protocol:

StepAction
Poll, don't waitProactively check the sub-agent's liveness via GIT PROGRESS (new commits / advancing SHA), process activity, or output-file byte growth — event-driven where possible (a bounded watcher polls for you — § Wake interval), otherwise at most every 4 min — NOT the 0-byte transcript file's mere existence (existence proves nothing; the diagnose-by-activity rule above applies). A missing completion ping is evidence of neither completion nor death — it is no information at all; only observed progress (or its absence past the expected interval) decides which.
Re-dispatch a transient stallA transient provider rate-limit / server error / no-start is a DISPATCH failure, never the slice's verdict — never record it as FAILED, BLOCKED, or "the delegate couldn't do it"; the work was never attempted. Recipe: re-dispatch with a minimal "try again", inside the bounded delegate-retry envelope below (3 attempts / 5 min / labeled backoff), and only then judge the work. A structural hang is not a rate-limit — do not blind-retry it unchanged. Provider-limit death ≠ lost session (proven 2026-07-07): a delegated sub-agent that dies mid-run on a provider rate/session limit usually keeps its context — SendMessage the "try again" to the SAME agent id and it resumes from where it stopped. Inside the envelope do NOT spawn a fresh agent (loses context, redoes work; its cache is still warm) and do NOT re-send the full brief (the resumed agent still has it). Past the envelope the successor rule applies; task-delegation § Correction ownership sets the context bound on reuse.
Verify before trustingBefore you trust ANY sub-agent's output, verify the FINAL git state yourself: clean linear history, the expected commits landed, 0 mirror drift (mirror-skills.sh --check), gate evidence present. A completion ping is a claim, not proof.
Sync for simple work; background+poll for long runsPrefer synchronous sub-agent runs for short autonomous work — a backgrounded sub-agent can't autonomously self-resume if it yields mid-task. For a LONG autonomous run where you want progress visibility, backgrounding IS viable when paired with active polling (this table's poll rule) + SendMessage-resume on a provider-limit death (row above) — proven 2026-07-07 to continue the same session. Either way, never passively background-and-wait: the run must produce progress, resume on death, or return BLOCKED-AT-<gate> (task-delegation turn-completion).

This is the sub-agent application of the FR-27 floor; flow-skills/task-delegation owns the turn-completion / BLOCKED-AT return shape (cross-linked below, not duplicated).

Wake interval (canonical)

Every model-side check re-reads the waiting agent's whole context, and a check after its cache lifetime re-writes it (token-economy TE-20). Poll in the shell; wake the model on events. A liveness check reads one SHA, mtime or byte count — never the output (that is evidence: record-then-read).

CaseInterval
Bounded watcher — default for an orchestrating or main session on a host that wakes it on background completion (§ Host wake paths): a background shell loop under bounded_run, no model tokens, reading SHAs, mtimes and state/audit/liveness/*.hb heartbeats (bounded_wait = the one-file form)polls as often as useful; exits on progress, completion, stall or its deadline (a bounded wakeup — silence still wakes you), and every exit wakes you
Delegated session (cannot be re-invoked after its turn)in-turn bounded check loop, each ≤4 min (task-delegation turn-completion)
No watcher can wake youat most every 4 min — under the 5-min cache TTL of sub-agents and of hosts whose TTL is unknown; a longer main-session interval is an operator choice stated in chat
Stall verdictno observed progress across two consecutive checks past the slice's expected interval → § Diagnose a suspected hang
Delegate never started§ Bounded delegate-retry envelope (~60 → ~90 → ~120 s) — the one prescribed model-side cadence under 4 min

Large-context orchestrator (host listed in § Host wake paths as able to wake you; otherwise the "No watcher can wake you" row): after dispatch, arm the watcher, then end the turn — the watcher does FR-27's proactive polling shell-side. On each wake read the one fact it reports: still running → arm a fresh watcher before ending the turn again; done → § Zero-trust Verify before trusting, then trust the result; stalled → § Diagnose a suspected hang. A report-back is an extra wake, never the only one (a missing ping is no information). Never poll model-side while a watcher can wake you.

A watcher cannot cover: your own hung foreground call (its wake queues behind it — bound that call), a session that cannot be re-invoked after its turn (→ in-turn loop), a host freeze (a same-host watcher freezes too).

Host wake paths

HostWakes a turn-ended main sessionNote
Claude Codea background shell command's exit (task notification) — the watcher's path · a background sub-agent / Workflow completion notification — never the only wake · a scheduled task firing (incl. a /loop iteration) — never the only wake · a message another session sends — never the only wake · an asyncRewake command hook exiting 2 (its stderr arrives as a system reminder; only exit 2 wakes, so map the alert to exit 2)report-back, scheduled-task and cross-session turns: hooks docs § UserPromptSubmit · a Workflow agent's stall restart excludes time in its own tool calls; only your watcher sees a hung call
Codex / othersnone documented≤4-min checks (row "No watcher can wake you"); guidance only

Bounded delegate-retry envelope (canonical — the ONE exception)

The single sanctioned exception to don't-poll-while-running / record-then-read (token-economy TE-08) and to the two-strike rule (TE-05). It covers ONLY a delegate that never STARTED — transient provider rate-limit, server error, no-start. A structural hang, a started-then-wrong result, and a failing approach are all outside it and get zero retries under this envelope.

BoundValue (binding — one number, not a range)
Max attempts3 re-dispatches ("try again" to the SAME agent id), counted from the first no-start
Max wall-time5 minutes total across all attempts, whichever limit is hit first
Backofflabeled and stated in chat each time: ~60s → ~90s → ~120s — e.g. "retry 2/3 after ~90s; no progress observed since sha:abc1234"
Progress readexactly ONE cheap progress check per interval (advancing SHA / new commit / output-file byte growth). Never tail output, never a second read inside one interval
Exhaustion (attempts OR wall-time)STOP retrying. Read the task-delegation progress ledger → re-brief a SUCCESSOR with verify-from-records. No successor possible → return BLOCKED-AT-delegate-no-start + what cleared looks like + the ledger path. Never FAILED-<reason> and never BLOCKED-AT-<the-slice's-gate> — the slice was never attempted

When delegate-progress polling overrides record-then-read: record-then-read presumes a durable surface that outlives the run. A delegate that never started writes nothing, so no record exists to read afterwards — the interval progress read is the only available evidence, and it is capped at one read per interval. The moment the delegate IS producing progress the override ends: judge the work from its commits and artifacts, never by watching it run (liveness checks continue at § Wake interval — a SHA, mtime or byte count, never the output).

Re-dispatches inside this envelope are not two-strike attempts: the approach was never executed. A retry after the delegate has started IS a strike.

Cross-links (reuse, do not duplicate)

SliceCanonical homeRelationship
Delegated sub-agent turn-completion + BLOCKED-ATflow-skills/task-delegation/SKILL.md (turn-completion / Blocked-return)FR-27 is the GENERAL rule (covers the agent's own work too); task-delegation keeps the sub-agent slice — cross-linked, not narrowed (D6)
Record-then-read / observability-gap findingflow-skills/smoke-testing/SKILL.md § Verification cost disciplinethe prescribed remedy for "how do I know it finished?" — read durable evidence once after the run; no durable surface = an observability-gap finding, not agent-side polling
Don't poll while it runsflow-skills/token-economy/SKILL.md (record-then-read row)agent-side watching of OUTPUT costs tokens linear with wall-clock; bound + log + read records instead. A liveness check (§ Wake interval) reads only a SHA, mtime or byte count and is not watching. Its ONE exception is § Bounded delegate-retry envelope above (canonical here; TE-05/TE-08 and task-delegation point at it)

Anti-patterns

  • Launching a background probe/deploy/fetch-loop with no timeout, or ending the turn on background work with no watcher armed — "running in background — I'll resume when it completes" (you won't — nothing re-invokes you for a hung call; on a host listed in § Host wake paths a bounded watcher's exit, or a bounded_run job's own exit, does; elsewhere, and in a delegated session, stay in-turn with ≤4-min checks or return BLOCKED-AT-<gate>).
  • Adding a hook or Stop-time signal that claims to VERIFY this protocol was applied — attestation theatre (the inert-lever anti-pattern). Detecting a stall is the outside watcher's job (§ Wake interval), never an attestation.
  • A blocking gate on background launches — the failure is semantic (real hang vs legitimate long-live run); a gate trains premature kills (intelligence damage).
  • &-detaching under bounded_run and assuming the deadline still bounds the detached grandchild (it does not).
  • Diagnosing a hang by "the output file exists" (0-byte existence proves nothing) instead of by activity/mtime/last-progress.
  • Blind-retrying the same bare launch after it hung (two-strike, FR-26) instead of re-running the robust, bounded, logged version.
  • Reading a delegate's transient provider-limit death as a task verdict (FAILED / "it couldn't") — the work was never attempted; re-dispatch "try again", retrying only inside § Bounded delegate-retry envelope above (max 3 attempts / 5 min), then judge.
  • Treating a missing completion ping as either completion or death — it is no information; poll for progress instead.
  • Re-pasting this protocol into every handoff — it lives here; handoffs carry the promoted Hard-invariant clause + a pointer (FR-23/FR-24).

Clean-room note

Original Fusebase Flow content. Designed after reviewing public AI coding workflow patterns; no third-party code, prompts, skill files, or hook scripts are copied. The bounded-run tooling reuses Fusebase Flow's own run-with-timeout.sh core (not a third-party watchdog). See docs/source-map.md.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.