Doctrine

Idle-heartbeat loop in feed-based (`local_external`) mode — sleep, don't stop

feed-protocolturn-disciplineidle-heartbeatanti-idlelocal-external-mode

Idle-heartbeat loop in feed-based (local_external) mode — sleep, don't stop

The rule

When feed-bracket close-read shows no claimable work for your lane + warm context + capacity, DO NOT END THE TURN. Enter an idle-heartbeat loop:

1. sleep 120 (platform equivalent wait; 120 seconds default). 2. Re-read the fleet feed (fleet-active-queue + grep for recent DONE transitions that may have unblocked downstream tickets). 3. If claimable work now exists → CLAIM and work (loop back into ORA-2026-0015 turn discipline). 4. If feed is still empty → repeat from step 1.

Exit conditions:

  • A new claimable ticket appears (→ claim + work).
  • Your project backlog is completely empty (no AWAITING_CLAIM items
  • remaining across your project, no unblocked BLOCKED items under your warm context, no parent dependencies about to close).

  • Explicit operator retirement signal (tram_retire-equivalent or feed post
  • from Chad/STRAT naming you for retire).

Why this doctrine is needed

Boot Protocol §5a already documents this loop pattern, but the vocabulary is TRAM-based (tram_unread, session_heartbeat, session_list). Current fleet runs local_external mode (FLEET_FEED.md). Seats reading AGENTS.md / GEMINI.md / CLAUDE.md see the feed-bracket rule (ORA-2026-0015) which says "loop back to work IF claimable," but have no adapted rule for the "sleep-and-retry if not claimable" half of the pattern. They default to end-of-turn on an empty read, which is a doctrine violation equivalent to the CAMBER-10 failure (FAL ORA-2026-0020).

Symptoms of violation

  • Seat finishes a ticket, posts DONE, closing feed-bracket read shows empty
  • queue, seat stops — even though their project backlog has BLOCKED items that will unblock within minutes as other seats finish upstream work.

  • Operator observes "no one is working" despite multiple CLAIMED tickets,
  • because seats are idle between turns instead of resting-and-resuming.

  • STRAT has to pressure or re-paste to wake seats for work that should have
  • self-absorbed.

Correct behavior

  • After DONE posting, run feed-bracket close read.
  • If claimable → claim + work (ORA-2026-0015).
  • If nothing claimable but project backlog has BLOCKED items → sleep 120,
  • re-read, look for unblocks; repeat.

  • If nothing claimable AND project backlog exhausted → post a IDLE_HOLDING
  • or equivalent liveness note, then sleep 120 loop (seat is reachable for fresh dispatches, not dead).

  • Only terminate session on explicit retirement signal or platform
  • constraint (e.g., session timeout).

Platform notes

  • Codex CLI / Claude CLI: bash sleep 120 in the turn-loop framing; use
  • fleet-active-queue --since-minutes 5 on re-check to catch recent DONE transitions that may have unblocked work.

  • Codex Desktop / Claude Desktop: these sessions do not auto-loop in the
  • same way; they typically need an operator prompt to continue. In Desktop mode, the wake packet should explicitly include this idle-heartbeat instruction, or the seat should be asked to run a loop within a single long-lived turn (using ScheduleWakeup-equivalent or self-spawned heartbeat subprocess).

  • Gemini CLI: same as Codex CLI pattern.

Verification

Seat has internalized the rule when:

  • Between CLAIMED and next-CLAIMED posts, there's either active work mtime
  • activity OR a visible sleep/wait gap that's explained by this loop.

  • Zero "end-of-turn-on-empty-queue" failures when BLOCKED items in the
  • seat's project are about to unblock.

Related

  • ORA-2026-0015 — Feed-bracket every turn. This doctrine extends the
  • claim-trigger rule with a sleep-and-retry clause for the empty-queue case.

  • ORA-2026-0019 — Wake-work pairing. Wake packets in feed-based mode
  • should include the idle-heartbeat rule explicitly if the seat is expected to work through a multi-ticket backlog autonomously.

  • Boot Protocol §5a — Original TRAM-based formulation of this loop.