Skip to content

feat: add mechanical dispatch guardrails and NAS auto-deploy-sync#623

Open
inivosdigital wants to merge 41 commits into
kunchenguid:mainfrom
inivosdigital:fm/ship-fm-verify-trivial-tier
Open

feat: add mechanical dispatch guardrails and NAS auto-deploy-sync#623
inivosdigital wants to merge 41 commits into
kunchenguid:mainfrom
inivosdigital:fm/ship-fm-verify-trivial-tier

Conversation

@inivosdigital

Copy link
Copy Markdown

What Changed

  • Add mechanical quality guardrails for crewmate dispatch: fm-risk-tripwire.sh, fm-tier-guard.sh, and fm-ultracode-guard.sh, wired into fm-spawn.sh/fm-dispatch-select.sh for model/effort tiering, plus a verify-trivial Light verification tier and --light-verify brief scaffolding in fm-brief.sh.
  • Add fm-nas-deploy-sync.sh to auto-sync and restart a project's live NAS deployment after a landed ship task, and surface failed critical systemd services as a bootstrap diagnostic.
  • Harden fm-merge-local.sh to push local-only merges to origin when a remote exists, fix fm-teardown.sh's landed-work detection for fast-forwarded/local-main content, and isolate per-worktree docker-compose project names in fm-spawn.sh.

Risk Assessment

⚠️ Medium: The diff is large (43 files, ~3700 lines) and touches safety-critical paths (teardown's landed-work detection, automated NAS/pm2 production restarts, dispatch tiering guardrails), but each piece is thoroughly documented and covered by extensive new tests, and no outright correctness bugs were found; the one substantive concern is an unscoped, untested push-to-remote behavior added to the local-only merge path.

Testing

Completed 1 recorded test check.

Pipeline

Updates from git push no-mistakes

⏭️ **intent** - skipped

✅ No issues found.

🔧 **Rebase** - 2 issues found → auto-fixed ✅
  • ⚠️ AGENTS.md - merge conflict rebasing onto origin/main
  • ⚠️ docs/scripts.md - merge conflict rebasing onto origin/main

🔧 Fix applied.
✅ Re-checked - no issues remain.

⚠️ **Review** - 1 warning
  • ⚠️ bin/fm-merge-local.sh:74 - fm-merge-local.sh's new post-merge step pushes the project's default branch to origin for every local-only merge whenever an origin remote exists, but AGENTS.md section 6 defines local-only mode as "local branch, no remote, no PR". The script's own comment frames the motivation narrowly (keeping firstmate's own fork alive after the origin/upstream swap), but the implementation is generic to any project registered as local-only, so a local-only project that happens to carry an origin remote (e.g. added later for backup) will now silently start pushing to it on every merge without a separate captain confirmation for that push action. There is also no dedicated test file for fm-merge-local.sh (no tests/fm-merge-local.test.sh, and no invocation-level test of the new push branch), so this new remote-mutating behavior is unverified.
✅ **Test** - passed

✅ No issues found.

  • command -v tmux >/dev/null || { echo "tmux is required for e2e tests" >&2; exit 1; }; tmux -V; rc=0; for t in tests/*.test.sh; do echo "== $t =="; bash "$t" || rc=1; done; exit "$rc"
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

@inivosdigital
inivosdigital force-pushed the fm/ship-fm-verify-trivial-tier branch 3 times, most recently from 8344b80 to d5cdb24 Compare July 16, 2026 01:28
Michael added 27 commits July 15, 2026 22:29
Two treehouse worktrees of the same project share a leaf directory
basename, so Compose's default project-naming collides across them on
container/volume/network names. fm-spawn.sh now derives a stable name
from the worktree's own pool-slot path and drops it at
.treehouse-compose-project (git-excluded, like the other turn-end
pointer files), recorded in meta as compose_project=. fm-brief.sh tells
ship/scout crewmates to consume it on every docker compose invocation
and to avoid fixed host ports as a second collision vector.
…r-compose note to fm-brief.sh header comment
Claude crewmates and secondmates are separate claude processes launched
by fm-spawn.sh; they never inherit the primary's .claude/settings.local.json,
so they only compact at the hard context wall instead of proactively.

Add CLAUDE_CODE_AUTO_COMPACT_WINDOW=200000 to the claude launch template.
The var is capped at the model's real context window, so a fixed 200000
lands auto-compaction around 200k regardless of whether the spawned model's
window is 200k or (as verified live for --model sonnet on this account,
which carries the 1M-context beta) ~1M. The primary's own two-var form
(WINDOW=1000000 + PCT_OVERRIDE=20) assumes a 1M window and is not safe to
copy blindly for a smaller-window model/account combination.

grok, codex, opencode, and pi launches are left untouched: grok's binary
is not verified to honor Claude Code env vars, and the other three ignore
them harmlessly but should not carry a var meant for a harness they aren't.
Bump CLAUDE_CODE_AUTO_COMPACT_WINDOW from 200000 to 300000 for claude
crewmates and secondmates, matching the primary session's own updated
target.

The override is capped at the model's real context window, so it is a
no-op below 300000 and only takes effect above it; today's standard
--model sonnet crewmate spawn resolves to the ~1M-context beta, well
above the new threshold. Updated the fm-spawn.sh header comment,
launch_template() comment, and the harness-adapters SKILL.md fact to
describe this conditional behavior instead of the "regardless of
window" framing that only held at the old 200000 value.
content_in_default() only ever diffed a branch against the fetched
refs/remotes/origin/$name, so a no-mistakes-mode task whose branch was
merged into the LOCAL default branch by hand (e.g. after its upstream
PR was closed unmerged) was never recognized as landed and teardown
refused it. It now also checks the local refs/heads/$name ref.
…tests

content_in_default()'s diff-based comparison (from local main's own prior
rewrite) no longer depends on git merge-tree --write-tree, so the two
tests that used to skip themselves on older git now run and pass here.
Extends config/crew-dispatch.json's schema with model/effort/ultracode
axes per the resource-tiering design: docs/examples/crew-dispatch.json
gains rules for the firstmate-repo supervision-backbone carve-out,
safety-critical/security/migration work (opus/xhigh, ultracode-flagged),
and architectural/product tradeoffs (opus/xhigh).

Adds the four hardened guardrails as mechanical checks rather than prose
aspirations:
- bin/fm-tier-guard.sh: firstmate-observable escalation triggers (diff
  size and elapsed time vs. the trivial tier's envelope, plus a general
  heavy-scale ceiling for any tier).
- bin/fm-risk-tripwire.sh: a keyword/path trip-wire against a task's
  brief and diff, independent of the natural-language dispatch match.
- bin/fm-ultracode-guard.sh: flag/reviewed/check subcommands that
  mechanically confirm a genuinely independent second pass ran before an
  ultracode-flagged task reaches PR-ready.
- A documented model-verification gate in AGENTS.md section 4, extending
  the existing unverified-harness discipline to model names.

Verification tiering (light/standard/heavy no-mistakes runs) is
deliberately out of scope here and left for a follow-up change.
The read-tree portability rewrite restructured content_in_default() into
an if/elif that checks exactly one candidate ref: the fetched origin ref
when a remote exists, falling back to the local ref only when there is no
remote at all. That silently dropped the case a no-mistakes-mode project
with a closed, unmerged PR but work fast-forwarded into local main by
hand - the fetched origin ref exists but does not contain the work, so
the local ref is never even checked. Restore checking both candidates in
sequence via a shared content_matches_ref helper, matching the pre-rewrite
behavior tests/fm-teardown.test.sh's local-land regression test expects.
A merged PR does not, by itself, reach the live site: deployed apps run from a
separate checkout under /mnt/nas/experiments/<name>/, managed by pm2,
independent from the projects/<name> clone used for development. This gap
required a manual pull+restart to notice and fix.

bin/fm-nas-deploy-sync.sh fast-forwards a project's live NAS checkout and
restarts its pm2 process(es), looked up from a captain-private
data/nas-deployments.md mapping (best-effort, non-fatal, mirrors
fm-fleet-sync.sh's dirty/diverged safety exactly). bin/fm-teardown.sh now
calls it automatically after a landed ship task, including local-only merges.
Michael and others added 14 commits July 15, 2026 22:38
…-backend.md with the NAS auto-deploy-sync feature and cmux liveness fix.
Add config/critical-services (local, gitignored): one systemd unit name
per line, blank lines and full-line "#" comments ignored. bin/fm-bootstrap.sh
reads it in the detect-only diagnostics and prints one read-only
SERVICE_FAILED line per failed unit via systemctl is-failed (no root), with
a "failed since" timestamp when available. An absent or empty file, or a host
without systemctl, is a no-op; nothing is ever auto-restarted.

Wire it into AGENTS.md's config list and section 3's diagnostic-handling
list, docs/configuration.md, a copyable docs/examples/critical-services,
and tests.
Swap remotes on this working copy so origin is the captain's fork and
upstream is the read-only parent template, and keep the fork alive:

- fm-merge-local.sh: after a local fast-forward, best-effort push the
  default branch to origin when it exists, so a push-backed local-only
  default branch never silently drifts behind local main. Skipped
  silently for no-remote local-only projects.
- fm-bootstrap.sh: always-on UPSTREAM_DRIFT diagnostic reporting how far
  local main diverges from upstream/main in both directions plus
  days-since-merge-base, escalating wording past 30 commits behind or a
  10-day-old merge-base. The upstream fetch lives only in the locked
  mutating fleet-sync sweep; the count-and-report runs unconditionally
  from the already-present upstream ref, never fetching in the
  detect-only path.
- Document the local-only landing pattern and the opportunistic,
  non-blocking upstream PR in AGENTS.md, the UPSTREAM_DRIFT trigger in
  the bootstrap-diagnostics skill, and the inverted remote layout in
  CONTRIBUTING.md.
…late

Post-swap origin is the captain's fork, so /updatefirstmate only
converges the fleet onto that fork (a no-op on the primary source of
truth) and never pulls the read-only upstream template. Folding
upstream-template improvements into local main is the separate reviewed
reconciliation task surfaced by the UPSTREAM_DRIFT bootstrap line, not a
self-update. Note it inline in AGENTS.md section 12 and in the
updatefirstmate skill.
Crew session for this task crashed (tmux/captain core dump). This
commit preserves in-flight work from the orphaned worktree so it is
not lost. A tar backup also exists under
/home/orangepi/fm-recovery-backups/.
@inivosdigital
inivosdigital force-pushed the fm/ship-fm-verify-trivial-tier branch from d5cdb24 to e836503 Compare July 16, 2026 02:47
@kunchenguid

kunchenguid commented Jul 16, 2026

Copy link
Copy Markdown
Owner

Automated reminder: thanks for the PR! This branch currently has a merge conflict with the base branch.

When you get a chance, please rebase onto (or merge) the latest base branch, resolve the conflict, and push. After that, checks will re-run and the PR will get looked at again.

Noted for firstmate#623 at e8365038.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants