One unattended factory foreman iteration: watch the backlog queue, dispatch the next operator-picked card to an isolated worker, collect completion evidence on read (not on claims), and report. This is the body the project's loop.md driver invokes each iteration of a bare /loop; it can also be invoked directly to test one cycle by hand.
91%
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 action 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 dispatch protocol, the card classes, and the queue boundaries this loop consumes live in those two files.
One unattended pass of the factory foreman: watch the backlog queue, dispatch
the next operator-picked card to an isolated worker, collect completion
evidence, report. The queue surface is moai todo (the moai gtd spelling serves the same queue); the dispatch protocol and
card classes live in the factory dispatch rule (.claude/rules/moai/workflow/factory-dispatch.md).
foreman — an auxiliary role of the leader: the unattended watcher that dispatches the already-picked card to an isolated worker when no leader session is holding the queue.
AskUserQuestion is removed from the tool pool while this skill is active —
that is the mechanical guarantee that the loop cannot stop and ask. Anything
that would have been a question becomes a line in the iteration report, and
anything that genuinely needs the operator's decision becomes a blocked card
under Boundaries below.
Deployment: start a session in the project, run bare /loop, then
background the session — loop tasks carry over to the background session and
keep running without a terminal. Esc cancels the pending wakeup of a
waiting loop. A recurring loop expires seven days after creation; restart it
when the queue still needs a foreman. Background monitors do not survive a
session resume — the first iteration after a resume re-arms the queue watch.
The session's permission settings must already allow what this loop uses (queue reads, git inspection, the worker spawn). A permission prompt that surfaces while unattended stalls the iteration until someone attaches; pre-approving that surface in project settings is the operator's setup step, not something this loop can do for itself.
picked are dispatchable. Never run moai gtd add; never run
moai gtd next <n> — that mutation is the operator's pick. Never invent,
reword, or reorder cards. An empty queue is a legitimate state: say so and
idle. A batch authorization (/moai:todo --auto — the operator's typed
invocation-as-approval) is the card-pick gate's autonomous form
(.claude/rules/moai/workflow/auto-semantics.md §9): within it, serial
consumption on its own judgment, outside the keep-set (bar a [보류 card,
which is ranked last, not excluded), is authorized; queue
ADMISSION stays the operator's. A lane takes its card through
moai factory next --card <id>, never through this loop.picked, name it blocked-for-operator in
the report together with the decision it waits on, and move on.one writer per tree, owned by
.claude/rules/moai/core/agent-common-protocol.md § Background Agent
Execution. The foreman keeps one worker in flight so a failed iteration has
exactly one author to read.isolation: "worktree" on the
spawn; relative paths in the prompt — the worker's CWD is its worktree
root). Nothing writes to the shared checkout.Queue watch. If no backlog monitor is live (first iteration, or after a resume), arm one Monitor on the queue file, re-arming it at each expiry:
command:
# The queue directory, exactly as the project reports it: the canonical
# location the adopting moai todo verbs leave the queue in. No fallback
# path is computed; a failure stops the watch loudly.
d=$(moai paths --field todo-dir) || { echo "queue watch stopped: moai paths failed"; exit 1; }
last=init
while true; do
cur=$(cksum "$d"/backlog.db "$d"/backlog.db-wal 2>/dev/null)
[ -n "$cur" ] || cur=missing
if [ "$cur" != "$last" ]; then
[ "$last" != init ] && echo "backlog changed"
last=$cur
fi
sleep 5
donetimeout_ms: 1800000
description: backlog queue watch
The persistent option no longer exists — every Monitor now carries a
deadline, timeout_ms is a required input, and the watch above is an
unbounded loop that never ends on its own. 1800000 is the longest
deadline the tool documents accepting; a larger value is capped rather
than honoured. At expiry the loop is killed and one notice arrives with
the event count, so an iteration that still needs the watch re-arms it.
A non-interactive (-p) session is reported to cap the deadline lower
than an interactive one; that lower cap is not observable from the tool
input schema, so treat a shorter-than-requested expiry there as expected
rather than as a fault.
The watch takes the queue directory from moai paths, the confirmed source
of the project's database locations, so a linked worktree watches the
primary checkout's queue, not a directory local to its own tree, and the
directory stays the same after a legacy queue is adopted. When that command
fails the watch stops with a visible message instead of falling back to a
path of its own. The queue is the database, and a backlog.json beside it is an export or
a legacy leftover — never the queue — so a watch pointed at the JSON on a
migrated project polls a file that never changes and reports nothing,
forever. The write-ahead log is watched alongside the database because a
committed write can sit in backlog.db-wal with the database image
byte-identical until a checkpoint folds it back; watching the database
alone misses exactly those mutations. The checksum poll emits one line
per change and costs two tiny reads every five seconds. Do not tighten
the interval, and do not arm a second watcher. Each emitted
line, like each scheduled wakeup, is a prompt to run this same idempotent
iteration — an iteration that finds nothing to do ends quickly.
Database paths — never guess.
moai paths; never guess them.~/.moai/todo.db, ~/.moai/backlog.db, ~/.moai/db/todo.db, ~/.moai/db/factory.db, ~/.moai/db/<key>/backlog.db, <root>/.moai/state/backlog.db, <root>/.moai/state/factory.db, <root>/.moai/state/factory/factory.db.sqlite3-open a guessed path: opening a missing file creates an empty one..db, .db-wal, and .db-shm together.moai todo list --json, moai factory status.Read the queue. moai gtd list --json (lock-free). A missing queue
file is an empty queue, never an error. Records carry id, text,
spec_id, and state (queued | picked | dropped).
Collect before dispatch. If a worker dispatched by an earlier iteration has returned, read its evidence file first (step 6). If a worker is still running, end the iteration with a one-line status.
Choose the dispatchable card. The oldest picked item with no live
worker and no recorded blocker. queued items are not yours to pick outside
a batch authorization.
Dispatch one worker with the Agent tool, isolation: "worktree". The
dispatch is a fixed-field address block — a pointer, not a copy, ten
lines at most:
card: <id>
spec: <SPEC-ID> # only when the card carries one
cmd: <the card's work in one line; the phase command when a SPEC is attached>
evidence: .moai/reports/<card-id>/evidence.mdRoute the spawn to the agent the card's work matches under the standard
delegation rules; where no specialist matches, a general-purpose spawn
with a domain whitelist. The worker prompt carries the block plus these
standing orders: rename the worktree branch to WT-<slug> (a
descriptive slug, never the card id) first;
implement the card; verify lane-locally; write the evidence file with
decisions, verbatim output tails of the checks run, explicit gaps, and
residual risk; commit by explicit pathspec; never push.
Collect on evidence. When the worker returns, read the evidence file
it names. Advance the card — moai gtd done <t-id> — only when the
evidence shows the work complete: verbatim passing output present, gaps
named. A missing, unreadable, or stale evidence file is a gap: the card
stays picked, the report says why, and the card is not re-dispatched
this iteration. Absence of a failure signal is not a pass.
Report. Close with two to six lines: queue summary, what was dispatched or collected, which evidence was read, blocked cards and the decisions they wait on. This report is what the operator reads on reattach — name what you read, not what you were told.
The single-worker dispatch above is the only mode. Fanning a card out to numbered factory lanes — the multi-lane launcher surface — is separate work; when the foreman grows that routing, it lands here as a second dispatch mode chosen per card. Until then this loop spawns one subagent per card, reads no factory state, and launches no lanes.
Stop the loop (ScheduleWakeup with stop: true), with a one-line reason,
when the loop cannot do its job: the queue file is repeatedly unreadable,
the queue watch cannot be armed, or this skill's own surface is broken. A
transient worker failure is not a loop failure — record it on the card and
let the next iteration decide whether to re-dispatch.
modu-ai/moai-adk
59464c5
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.