One lane stall watchdog iteration: measure lane progress on three channels, classify the stall cause, apply the remedy or resolve the judgment through the decision ladder — instead of waiting on a reply — then record the outcome and report. Reads the unified --auto doctrine at .claude/rules/moai/workflow/auto-semantics.md for the ladder, the gate inventory, and the record formats.
84%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
Passed
No findings from the security scan
[HARD] Before the first watchdog pass of a session running this skill, read BOTH role-gated rule files in full: .claude/rules/moai/workflow/factory-dispatch.md and .claude/rules/moai/workflow/cross-session-messaging.md. A Codex-only project deploys the same files under .moai/policies/ (for example .moai/policies/workflow/factory-dispatch.md) — read whichever layout this project carries. The always-loaded surface carries only their stubs; the lane obligations and the messaging rules the ladder operates under live in those two files.
One watchdog pass for a lane session (a factory lane running
under moai cc / moai glm / moai codex). The law — the ladder, the
outcome transitions, the gate inventory, the view–SSOT rule, the record
formats — lives in .claude/rules/moai/workflow/auto-semantics.md. Read
that doctrine before the first pass and whenever a step below needs its
detail; this body is the executable procedure.
The watchdog never asks the operator a question and never answers an approval gate on anyone's behalf. Everything that would have been a question becomes either a resolved judgment (the ladder) or an explicit wait record. A keep-set gate — environment-impossible, operator-held, or an irreversible external-shared operation — is never satisfied here: the recheck reads the gate surface, and finding no change, it re-records the wait and yields.
Renew this lane's own card lease before anything else, through the factory renew verb, with the card id this lane holds (the same id the snapshot path below names):
moai factory renew <card-id> --jsonRead result from the verb's JSON object on stdout. A refusal exits non-zero after printing that object, so read stdout even when the exit status is non-zero. result is one of renewed, refused-expired, refused-not-holder, or error. Hold the value for the snapshot: §1 copies it into the snapshot field renew_result. A refused renewal does not end the pass. A refused-expired, refused-not-holder, or error result is recorded, and the pass continues to §1 and measures in the same pass. Do not retry the renewal within the pass; the next pass renews again.
This step renews the card the lane is executing and no other card: a lane whose cards are all merge-ready, held, or waiting for the serial slot does not renew in that pass, and the pass carries the previous renew_result forward.
Three channels (doctrine §3–4):
git rev-parse --short HEAD in the lane's own worktree..moai/reports/<card-id>/
and the active SPEC's progress.md.moai integration status.Compare against the previous snapshot at
.moai/state/watchdog/<card-id>.json (fields: observed_at, head_sha,
evidence_mtime_max, window_state, renew_result, last_change_at). The
lane is stalled only when ALL THREE channels are unchanged since the previous
snapshot AND at least N minutes have passed since the last observed channel
change, which last_change_at records (auto-semantics §4). N
defaults to 15 minutes and is a per-invocation parameter — never a config
key. No previous snapshot → record a fresh one with last_change_at set to
its own observed_at, no stall verdict (fail-open first observation). One
channel moving is progress — evidence mtime advancing without a commit
included: end the iteration with a one-line status — unless the lane itself
is parked on a delegate (§3.5): then the movement is the delegate's, and the
deliverable read in §3.5 decides, not this rule. A pass that moved a channel
sets last_change_at to its observed_at; a pass that moved none carries
the previous value forward. Write the new snapshot after the verdict, in the
same pass as the renewal of §0a: the four measurement fields (observed_at,
head_sha, evidence_mtime_max, window_state), last_change_at (the value
this pass settled, per the rule above), and renew_result.
| Cause | Signature |
|---|---|
| awaited-judgment | the lane stopped where a judgment was needed (a gate, a verdict, a choice) and no reply arrived |
| blocked-by | the card waits on a predecessor card; the predecessor's landing state is the open question |
| shell-error | the lane halted on an unadjudicated command failure (a non-zero exit with no recorded disposition) |
| accidental-stop | the session died or was interrupted mid-card — an API error (a 429) ends the turn with no further model action — and no deliberate stop was recorded |
| awaited-delegate | the lane parked on delegated work — a spawned agent's report, a background run's completion — and the report or notice has not arrived; an available idle notice that promised a later report counts, because that agent sends nothing more unless it is messaged |
No stall / progress detected → end the iteration.
Resolve through the ladder IN ORDER (doctrine §6–7). Availability failures fail-open downward; negative or inconclusive verdicts never auto-proceed, and at an authority gate they fail closed.
moai decision read --scope card:<id> (the
card's rulings plus every standing ruling; doctrine §11: the append-only
board under the moai home, keyed by the project). A recorded judgment →
follow it. A judgment that says wait → explicit wait record (with an id;
doctrine §14). A record whose resolves names an open wait ends that
wait. board=absent / board=empty → step 3; never wait on the board
being filled. While a wait on the leader stays open, keep the one-shot
short recheck of doctrine §14 armed.moai MCP audit tool (codex_audit,
glm_audit, claude_audit, or audit_multi) for an independent second
opinion. Positive → proceed per verdict. Negative → fail-closed: no
proceed; record + treat as a gate boundary (doctrine §7). Tool absent → step 4. Inconclusive
→ record it, → step 4.workflow.jev.enabled is true; a bounded
proceed / retry / wait judgment with repeatable, side-effect-free
parameters; never for completion, merge, or queue outcomes. Gated off or
unavailable → step 5 (fail-open by design).
Shadow record: keep the verbatim jev_ask request and the verbatim
response (including model) with the decision record (§5 below); the
decision stays the lane's (doctrine §12).Write a decision record naming whichever step resolved the judgment (§5 below).
Read the blocking predecessor's on-disk evidence directly (its
.moai/reports/<card-id>/ path, its progress record). Evidence shows the
predecessor landed → resume. Otherwise record an explicit wait: reason +
predecessor id + recheck point. Where the predecessor was dropped, the wait
record names the queue relation-removal verb as the operator escape — the
lane does NOT run it (queue mutation is prohibited). The queue-level pickup
filter is another surface's decision; this remedy only reads.
Adjudicate the error: transient (network blip, lock contention, flaky timing) vs determinate (missing remote, bad ref, usage error).
Never halt indefinitely on an unadjudicated error; never loop silently.
Resume by checkpoint return: use the session-handoff resume message with the
worktree-anchored Block 0 (git rev-parse --show-toplevel → this worktree),
resuming from the last recorded checkpoint in the card's progress record.
Never restart the card from zero.
Read the delegate's deliverable on disk — the artifacts it was asked to write, its output file, its commits — against the delegation record (what was asked, where it was to land). Never read the absence of its message as the absence of its work.
available
sends nothing more unless it is messaged, so the report may never come.SendMessage only while it is a
live teammate. Never address a teammate stopped with TaskStop by name — one
message revives it as an ownerless writer
(cross-session-messaging.md § Rules). Record which.update_plan on codex — and read
judgments ONLY from disk. A view is never the SSOT; a long-form plan
document is never the board.Every autonomous gate transition and every ladder-resolved judgment writes ONE grep-able line on the disk board:
decision record: decided_by=<runner+role> evidence_refs=<paths+verdict-ids> ladder_path=<step>decided_by names the deciding runner and role; evidence_refs names the
artifact paths and verdict ids the decision rests on; ladder_path names
the ladder step (①–⑤) or the gate row that resolved it. The sync audit
re-reads these records — write them so a stranger can re-verify.
The queue-level pickup decision — the pickup filter that excludes unrelated or blocked cards before a pick — belongs to the queue surface's owner. This watchdog is queue-readonly: it reads evidence and state surfaces, performs zero queue mutation, and never picks, drops, edits, or unrelates anything.
Every iteration ends in exactly one of:
modu-ai/moai-adk
4fa2aa9
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.