agentleFS
sunholo-datasunholo-data/ailang/.agents/skills/mission-loop-change/SKILL.md

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

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
  1. Very specific:

    If it is running, either wait, or write via atomic replace so the running process keeps its inode (python3 -c "..." with tempfile + …

    line 91
  2. Distinctive:

    Before believing a role survives an outage, check all three:

    line 132
  3. Distinctive:

    Keep the plist versioned in the repo and copy from there.

    line 177
  4. Very specific:

    Write ~/.config/ailang/mission-<name>.env (MISSION_NAME, MISSION_REPO, MISSION_DOC, MISSION_WORKDIR) and the reviewable copy under …

    line 211
  5. Very specific:

    Add a boot-stagger offset arm for the name in _mc_boot_offset — an unknown mission silently gets 0 and joins the boot stampede.

    line 215
  6. Very specific:

    Prefer running the new mission's driver from a clone that sources pin-root.sh.

    line 221
  7. Distinctive:

    Before claiming a mission-loop change is done:

    line 233

≈ “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

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 into ailang 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 changeEditReaches the fleet via
Driver logic (probes, gates, chains)tools/launchd/mission-control.sh in the ailang repothe driver pin — committed origin/dev, so it must be pushed
A mission's models / lanes / allowlist~/.config/ailang/mission-<name>.envsourced per fire. The repo copy under tools/launchd/mission-env/ is reviewable, not deployed
Schedule / PATH / env~/Library/LaunchAgents/dev.ailang.mission-<name>.plistrequires copy + reload. Installed plists are copies, not symlinks
Anything on worldthe ailang-world repoworld 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"
done

Gate 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/')"
done
  • YES → the clone's own contents are irrelevant; it runs committed origin/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"
done

Byte-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"
done

If 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 -3

Simulate 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 none

Then 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.x

The 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)"
done

Gate 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.
  • RunAtLoad means bootstrap starts an iteration immediately. Expect it.
  • rig-watchdog re-bootstraps missing jobs every 60s, so a bare bootout does not stick. To hold a mission off you need the kill switch (~/.ailang/state/mission-<name>.disabled), not bootout.
  • 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:

  1. sed s/__NAME__/<name>/g < tools/launchd/mission-template.plist > ~/Library/LaunchAgents/dev.ailang.mission-<name>.plist — and commit a copy to the repo.
  2. Write ~/.config/ailang/mission-<name>.env (MISSION_NAME, MISSION_REPO, MISSION_DOC, MISSION_WORKDIR) and the reviewable copy under tools/launchd/mission-env/. Keep them identical (Gate 0).
  3. Seed ~/.ailang/state/mission-<name>-gh-issue with the bookkeeping issue number.
  4. Add a boot-stagger offset arm for the name in _mc_boot_offset — an unknown mission silently gets 0 and joins the boot stampede.
  5. 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.
  6. 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-drivers exit 0, new suites wired (Gate 6)
  • Reloaded and verified state, with nothing mid-iteration (Gates 3, 7)

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.