mission-loop-change
Skill38 starsChanged 8 days agoApache-2.0View on GitHub
Change or add a mission loop safely — routing/lanes, schedule and cadence, driver code, plists, and adding a whole new mission. Use when editing tools/launchd/mission-control.sh, any dev.ailang.mission-* plist or mission-*.env, changing a mission's models/fallbacks/interval, porting a fix to the world fork, bootstrapping a new mission loop (e.g. for ailang-parse), or when asked why a mission-loop change "didn't take". Covers reach, live-edit hazards, drought simulation and reload.
- Reads credentials
In this repository85 other files it gives its agents.
- AGENTS.mdAGENTS.md
- CLAUDE.mdCLAUDE.md
- docs/llms.txtllms.txt
- llms.txtllms.txt
- agent-inboxSkill
- ailang-core-triageSkill
- ailang-packagesSkill
- benchmark-managerSkill
- benchmark-runnerSkill
- builtin-developerSkill
- cli-doc-maintainerSkill
- cloud-cascade-debugSkill
- cloud-setupSkill
- codebase-organizerSkill
- collaboration-hubSkill
- coordinator-helperSkill
- design-doc-creatorSkill
- design-spec-auditorSkill
- docs-syncSkill
- eval-analyzerSkill
- eval-gap-finderSkill
- gemini-cli-helperSkill
- github-issue-triageSkill
- headless-runnerSkill
- local-model-onboardingSkill
- local-ollama-evalSkill
- mission-briefSkill
- mission-controlSkill
- model-managerSkill
- motoko-analyzerSkill
- parser-developerSkill
- post-releaseSkill
- pr-monitorSkill
- prompt-managerSkill
- release-managerSkill
- sonarcloud-triageSkill
- sprint-evaluatorSkill
- sprint-executorSkill
- sprint-plannerSkill
- test-coverage-guardianSkill
- trace-debuggerSkill
- use-ailangSkill
- wasm-debuggerSkill
- agent-inboxSkill
- ailang-core-triageSkill
- ailang-packagesSkill
- benchmark-managerSkill
- benchmark-runnerSkill
- builtin-developerSkill
- cli-doc-maintainerSkill
- cloud-cascade-debugSkill
- cloud-setupSkill
- codebase-organizerSkill
- collaboration-hubSkill
- coordinator-helperSkill
- design-doc-creatorSkill
- design-spec-auditorSkill
- docs-syncSkill
- eval-analyzerSkill
- eval-gap-finderSkill
- The first 60 shown.
The file
We split this skill into the rules it gives an agent
7 rules. About 0 are common, 3 are distinctive, and 4 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈3
Distinctive · estimate
This author's own choices. Worth a look.
4
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 91
If it is running, either wait, or write via atomic replace so the running process keeps its inode (python3 -c "..." with tempfile + …
- Distinctive: line 132
Before believing a role survives an outage, check all three:
- Distinctive: line 177
Keep the plist versioned in the repo and copy from there.
- Very specific: line 211
Write ~/.config/ailang/mission-<name>.env (MISSION_NAME, MISSION_REPO, MISSION_DOC, MISSION_WORKDIR) and the reviewable copy under …
- Very specific: line 215
Add a boot-stagger offset arm for the name in _mc_boot_offset — an unknown mission silently gets 0 and joins the boot stampede.
- Very specific: line 221
Prefer running the new mission's driver from a clone that sources pin-root.sh.
- Distinctive: line 233
Before claiming a mission-loop change is done:
≈ “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.
Files
Changing a Mission Loop
The mission loops are four long-running agents on the rig. They are easy to edit and easy to edit without effect — the recurring failure is not a broken change, it is a change that lands somewhere nothing reads. Every rule here is a measured incident.
Read this first: the friction is catalogued, with counts, in
design_docs/planned/v0_36_0/m-mission-loop-workbench.md. Once that ships, most of this file collapses intoailang mission install|doctor. Until then the procedure below is the control.
Gate 0 — Which surface actually runs?
There are six places a mission's behaviour can be declared, and they are not kept in sync by anything. Before editing, decide which one you need:
| Want to change | Edit | Reaches the fleet via |
| Driver logic (probes, gates, chains) | tools/launchd/mission-control.sh in the ailang repo | the driver pin — committed origin/dev, so it must be pushed |
| A mission's models / lanes / allowlist | ~/.config/ailang/mission-<name>.env | sourced per fire. The repo copy under tools/launchd/mission-env/ is reviewable, not deployed |
| Schedule / PATH / env | ~/Library/LaunchAgents/dev.ailang.mission-<name>.plist | requires copy + reload. Installed plists are copies, not symlinks |
Anything on world | the ailang-world repo | world runs a FORK and no pin — see Gate 2 |
The trap, measured 2026-09-05: the versioned mission-docs.env widened the planner
allowlist and its own comment declared itself "the sprint's only deployment surface". It was
never copied to ~/.config. The docs mission has been routing that work to opus instead of
codex ever since, with a green CI arm — because the test asserts against the repo copy.
# ALWAYS check before and after editing an env file:
for m in v1 docs motoko world; do
cmp -s tools/launchd/mission-env/mission-$m.env ~/.config/ailang/mission-$m.env \
&& echo "$m: in sync" || echo "$m: *** DIVERGED — the repo copy is not what runs"
doneGate 1 — Does the change reach the mission you mean?
A mission runs the driver its plist's ProgramArguments points at. Three of four re-exec from
the driver pin (committed origin/dev of their own clone's repo); world does not.
for m in v1:ailang docs:ailang-docs motoko:ailang-motoko world:ailang-world; do
n=${m%%:*}; P=~/dev/sunholo-data/${m##*:}/tools/launchd/mission-control.sh
printf "%-7s re-execs-from-pin=%s\n" "$n" \
"$(grep -c '^\s*\. "\$REPO/tools/launchd/lib/pin-root.sh"' $P 2>/dev/null | sed 's/^0$/NO/;s/^[1-9].*/YES/')"
doneYES→ the clone's own contents are irrelevant; it runs committedorigin/dev. Push or it does not ship. A "N behind" line about the source clone is expected and harmless.NO(world) → it runs the working tree directly. Committing is not enough and not required; the file on disk is what executes.
Gate 2 — World is a fork
world lives in a separate GitHub repo with its own ~1000-line copy of the driver and no
pin-root.sh. It has silently missed every routing fix landed in sunholo-data/ailang.
- Any driver change you make for the fleet must be made twice until the de-fork lands.
- Port verbatim, then prove it:
for fn in _mc_set_controller select_model; do
diff <(sed -n "/^${fn}()/,/^}/p" ~/dev/sunholo-data/ailang/tools/launchd/mission-control.sh) \
<(sed -n "/^${fn}()/,/^}/p" ~/dev/sunholo-data/ailang-world/tools/launchd/mission-control.sh) \
>/dev/null && echo "$fn: identical" || echo "$fn: DIFFERS"
doneByte-identical is what lets you claim the upstream suite covers the port. Anything else and you owe world its own tests.
Gate 3 — Never edit a driver that is executing
World executes its driver straight from the working tree, and bash reads a script incrementally — truncating it under a live interpreter feeds it garbage mid-iteration.
for f in ~/.ailang/state/mission-*.pid; do
[ -f "$f" ] || continue; p=$(cat "$f")
kill -0 "$p" 2>/dev/null && echo "RUNNING: $(basename $f) pid=$p"
doneIf it is running, either wait, or write via atomic replace so the running process keeps its
inode (python3 -c "..." with tempfile + os.replace, or sed > tmp && mv tmp file).
open(w) and in-place sed -i are unsafe. The pinned missions are exempt — the pin runs from a
separate worktree.
Gate 4 — Prove it, before it fires for real
Nothing here spends tokens except where noted. MISSION_DRY_RUN=1 exits before the
iteration but after the probes, so it reports real resolved lanes.
# Working tree instead of the pinned copy — REQUIRED or you are testing the old driver.
# Use AILANG_DRIVER_PINNED, NOT AILANG_DRIVER_PIN=0: an env file that runs `export
# AILANG_DRIVER_PIN=1` (world's does) overrides the latter, the driver re-execs into the pin, and
# you silently test the old copy (measured 2026-09-26 — the old log line appeared in the output).
AILANG_DRIVER_PINNED=worktree-test MISSION_WORKDIR=<the mission's checkout> \
MISSION_PROFILE=<name> MISSION_DRY_RUN=1 \
/bin/bash tools/launchd/mission-control.sh 2>&1 | tail -3Simulate the Friday Anthropic drought instead of waiting for Friday. Point a role at a model
that cannot answer; the pre-flights degrade it down its chain and lanes= reports the result:
AILANG_DRIVER_PIN=0 MISSION_PROFILE=<name> MISSION_DRY_RUN=1 \
MISSION_DESIGNER_MODEL='claude:claude-drought-sim' \
MISSION_EVALUATOR_MODEL='claude-drought-sim' \
/bin/bash tools/launchd/mission-control.sh 2>&1 | grep -E 'anthropic|lanes='
# healthy -> lanes=ok, roles unchanged. drought -> lanes=DEGRADED(n) with each handoff named.Force the memory gate the same way (MISSION_MIN_AVAIL_GB=9999 MISSION_MEM_WAIT=2); it must log
STILL SHORT … yield and never reach DRY RUN ok. A gate you have only seen pass is a gate
you have not tested.
Gate 5 — Config declared is not config walked
A MISSION_<ROLE>_FALLBACK only does something if a pre-flight loop walks that role and
matches the value's provider prefix. MISSION_EVALUATOR_FALLBACK sat dead for ten days because
the loops iterated PLANNER EXECUTOR and the skill reads no *_FALLBACK var at all.
Before believing a role survives an outage, check all three:
grep -n 'for role in' tools/launchd/mission-control.sh # is the role in the list?
grep -n 'case "$val" in' tools/launchd/mission-control.sh # does an arm match its prefix?
grep -c 'MISSION_.*_FALLBACK' .claude/skills/mission-control/SKILL.md # 0 = the skill reads noneThen prove it with the drought simulation above. Do not reason about it.
Gate 6 — Tests, and the ones that do not run
make test-launchd-drivers # must be exit 0; bash 3.2, the rig has no 4.xThe suites are grep-based structural assertions over $HERE/mission-control.sh, so copying a
suite into another repo tests that repo's driver — that is how world gets coverage today.
Two suites were orphaned (present but absent from make/test.mk) and CI never ran them. When
you add a suite, wire it — one line in LAUNCHD_SUITES (suites run 4 at a time, so keep its
temp state under mktemp/$$; use LAUNCHD_SERIAL_SUITES only for an arm that measures the
machine itself) — and check nothing else has drifted out:
for f in tools/launchd/test_*.sh; do
grep -q "$(basename $f)" make/test.mk || echo "ORPHAN (CI never runs): $(basename $f)"
doneGate 7 — Installing and reloading
cp tools/launchd/dev.ailang.mission-<name>.plist ~/Library/LaunchAgents/
plutil -lint ~/Library/LaunchAgents/dev.ailang.mission-<name>.plist # never skip
launchctl bootout "gui/$(id -u)/dev.ailang.mission-<name>"
launchctl bootstrap "gui/$(id -u)" ~/Library/LaunchAgents/dev.ailang.mission-<name>.plist
launchctl print "gui/$(id -u)/dev.ailang.mission-<name>" | grep -E 'state|properties'- Check Gate 3 first — bootout kills a running iteration.
RunAtLoadmeans bootstrap starts an iteration immediately. Expect it.rig-watchdogre-bootstraps missing jobs every 60s, so a barebootoutdoes not stick. To hold a mission off you need the kill switch (~/.ailang/state/mission-<name>.disabled), notbootout.- Keep the plist versioned in the repo and copy from there. World's lived only in LaunchAgents for months, so its schedule was unreviewable.
Scheduling: what the knobs actually do
StartInterval re-arms from the job's EXIT, not its start — measured across four gaps on two
missions (5403/5372/5379s against 5400s; 14390s against 14400s). So:
StartInterval: period = duration + interval (a full interval idle, every cycle)
KeepAlive + ThrottleInterval: period = max(duration, throttle)ThrottleInterval is measured from the start, which is what makes it a floor rather than a
gap. It is load-bearing under KeepAlive: the yield paths (kill switch, overlap guard, memory
gate) exit in seconds, and bare KeepAlive would respawn them in a tight loop.
Cadence is a measured decision, not a preference. On 2026-09-02 three loops were tightened in
the same hour: fleet stall rate 6% → 33%, and total starts did not rise. Change one loop,
hold the others, and re-read the stall rate the next day — signature claude idle with a
descendant alive >=2400s. Durations and verdicts are in
~/.ailang/state/mission-<name>-slot-verdicts.log (durable; /tmp driver logs do not survive a
reboot).
Adding a whole new mission
Follow docs/docs/guides/mission-bootstrap.md (Steps 3.6 and 4 were rewritten for the registry
on 2026-09-26). The registry exists now: missions/<name>.toml, then ailang mission install <name>
(renders for review) and ailang mission apply <name> (installs and loads), run from the MAIN
checkout. Worked example: the fleet mission (M-HARNESS-MISSION-LOOP). The older manual path is
kept below for reference only:
sed s/__NAME__/<name>/g < tools/launchd/mission-template.plist > ~/Library/LaunchAgents/dev.ailang.mission-<name>.plist— and commit a copy to the repo.- Write
~/.config/ailang/mission-<name>.env(MISSION_NAME,MISSION_REPO,MISSION_DOC,MISSION_WORKDIR) and the reviewable copy undertools/launchd/mission-env/. Keep them identical (Gate 0). - Seed
~/.ailang/state/mission-<name>-gh-issuewith the bookkeeping issue number. - Add a boot-stagger offset arm for the name in
_mc_boot_offset— an unknown mission silently gets 0 and joins the boot stampede. - Give the plist a PATH that includes
/usr/sbin, or rely on the driver's own append. Omitting it disabled the boot stagger on two missions. - Dry-run it (Gate 4) before bootstrapping, then Gate 7.
Prefer running the new mission's driver from a clone that sources pin-root.sh. A fork is how
world ended up invisible.
Harness work belongs to the fleet mission (2026-09-26)
Product loops no longer change the loop harness: the driver enables a per-fire pre-push scope
guard (tools/launchd/githooks/pre-push), and they file ailang mission ticket file instead. The
fleet mission works those tickets, and this skill's pre-flight is its done-gate. An attended
session (no MISSION_NAME) is unaffected and can still make changes like these directly.
The five-line pre-flight
Before claiming a mission-loop change is done:
- Edited the surface that actually runs (Gate 0), and repo/installed copies match
- Confirmed reach — pushed if pinned; on disk if not (Gate 1); ported to world if fleet-wide (Gate 2)
- Dry-ran healthy and degraded (Gate 4); any new gate proven non-vacuous
make test-launchd-driversexit 0, new suites wired (Gate 6)- Reloaded and verified state, with nothing mid-iteration (Gates 3, 7)
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
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.

