Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
16b36f3
feat: plumb the registered hardened quality posture through to the ta…
BohnBawerick Aug 21, 2026
9bb7d0c
no-mistakes(review): add quality standing-posture notice, correct fal…
BohnBawerick Aug 22, 2026
aaa2272
no-mistakes(review): fix test suite teardown, header, typo-fallback c…
BohnBawerick Aug 22, 2026
32f566d
no-mistakes(review): add promote quality notice, fix brief help, pin …
BohnBawerick Aug 22, 2026
e3d0c65
no-mistakes(review): document hardened registration, refuse it on con…
BohnBawerick Aug 22, 2026
1875879
no-mistakes(review): document hardened token in architecture and READ…
BohnBawerick Aug 22, 2026
a7ca549
no-mistakes(document): document hardened quality contract in architec…
BohnBawerick Aug 22, 2026
bb124a1
feat: revise D2 quality receipts and add a wall-clock bound
BohnBawerick Aug 22, 2026
2042c9a
fix: deliver away-mode escalations on idle Claude between turns
BohnBawerick Aug 22, 2026
24b93c6
no-mistakes(review): narrow herdr unknown-composer inject override to…
BohnBawerick Aug 22, 2026
7e6dee1
no-mistakes(review): record herdr visible-source composer capture in …
BohnBawerick Aug 22, 2026
70e0c57
no-mistakes(review): gate Pi container proof on identity and dedupe h…
BohnBawerick Aug 22, 2026
36f5df9
no-mistakes(document): fix stale herdr composer capture-bound facts i…
BohnBawerick Aug 22, 2026
bb5f2d0
no-mistakes: apply CI fixes
BohnBawerick Aug 22, 2026
ff9e2bd
fix: make one session-lock verdict govern both arming and fleet mutation
BohnBawerick Aug 22, 2026
fdf033b
no-mistakes(review): harden session-lock helm inheritance, spawn iden…
BohnBawerick Aug 22, 2026
a5b760f
no-mistakes(review): reclaim dead lock pids and fix launch-line expec…
BohnBawerick Aug 22, 2026
eedee24
no-mistakes(review): key turn-end notices per session and refresh sta…
BohnBawerick Aug 22, 2026
9e088e7
no-mistakes(review): state real helm cause and retire stale turn-end …
BohnBawerick Aug 22, 2026
c1e5853
no-mistakes(review): decide lock tier before publishing the conversat…
BohnBawerick Aug 22, 2026
9a13a66
no-mistakes(test): model two-peer turn-end sessions as long-lived pro…
BohnBawerick Aug 22, 2026
d59a1b6
no-mistakes(document): fix docs stale after session-lock ownership split
BohnBawerick Aug 22, 2026
8699335
no-mistakes(lint): silence SC2016 false positive in session-identity …
BohnBawerick Aug 22, 2026
563b759
no-mistakes: apply CI fixes
BohnBawerick Aug 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 23 additions & 16 deletions .agents/skills/afk/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,25 +94,30 @@ backend (tmux or herdr; see "Auto-discovered supervisor pane" below):

- **Primary-pane busy guard** - `pane_is_busy` trusts Herdr native `busy` when available, otherwise matches rendered output against only the detected primary harness's signature.
This narrow delivery guard never classifies a recorded worker task and never uses a global union of vendor patterns.
- **Composer-state guard** - `inject_msg` reads the full `empty`/`pending`/`pending-unproven`/`unknown` verdict from `fm_backend_composer_state` and injects only when it is affirmatively `empty`.
Every other or future verdict defers, including an unreadable pane, ambiguous geometry, a blank unidentified row, and a bare shell prompt left after the agent exits.
- **Composer-state guard** - `inject_msg` reads the full `empty`/`pending`/`pending-unproven`/`unknown` verdict from `fm_backend_composer_state`.
Affirmative `empty` injects.
`pending` always defers (a human draft or swallowed digest).
`unknown` defers unless the backend can prove otherwise.
Only herdr can, and only when a fresh styled (ANSI) re-read still shows a genuine agent composer container AND native agent-state is idle, so a false-unknown idle Claude composer cannot stall away-mode overnight.
A dead shell, a modal, an unidentified or blank row, a degraded unstyled read, and a Pi separator pair the identity gate rejected all have no such proof and still defer.
The container rule is one sentence: `unknown` may only mean "proven container, unjudgeable styling", never "proven container, unread text".
Native-hosted away auto-discovers the captain pane (`HERDR_PANE_ID`); it does not need a different flush target.
Each adapter contributes only capture and capability facts to the fleet-wide screen classifier in `bin/fm-composer-lib.sh`, which owns every shape and verdict.
It preserves proven idle composers as empty but requires a genuine container around shell glyphs; see `docs/herdr-backend.md` "Composer and injection safety" for the operator contract.
`pane_input_pending` is the tested fail-closed predicate for callers that need to know whether the composer is unsafe: it treats every result except exact `empty` as pending.
See `docs/herdr-backend.md` "Composer and injection safety" for the operator contract.
`pane_input_pending` stays fail-closed for other callers: every result except exact `empty` is pending.

A busy primary pane, or any composer verdict other than `empty`, defers the injection; the buffered escalation survives in `state/.subsuper-escalations` and is retried on the next housekeeping tick.
A busy primary pane, a pending composer, or an unknown composer the backend cannot prove is a live idle agent composer, defers the injection; the buffered escalation survives in `state/.subsuper-escalations` and is retried on the next housekeeping tick.
In afk mode the composer guard is belt-and-suspenders (no human is typing), but it protects against the race window between the captain returning and their message landing, a dead shell, and the daemon's own previous injection sitting unsent.

**Max-defer escape (the daemon must never silently wedge).**
**Max-defer recovery (the daemon must never silently wedge).**
If anything stays buffered past `FM_MAX_DEFER_SECS` (default 300), the daemon
attempts one normal flush, which still requires an idle pane and an affirmatively empty composer.
The alarm is defense in depth rather than a substitute for keeping every genuinely idle supported composer injectable.
retries the flush path, including herdr native-idle delivery when the composer is unknown.
If that submit cannot be confirmed, it raises a loud, rate-limited wedge alarm:
an ERROR in the daemon log, a durable
`state/.subsuper-inject-wedged` marker (surface it on the "while you were out"
catch-up if present), a tmux status-line flash when applicable, and a configurable backend-independent active alert.
`docs/wedge-alarm.md` owns the alert channel setup, and `docs/verification/supervision.md` "Wedge-alarm channels" owns active evidence.
So a guard false-positive becomes a visible stall, never an unbounded silent no-op.
A clipped idle composer is supposed to recover on that retry; a remaining stall stays visible instead of an unbounded silent no-op.

## Submit model

Expand Down Expand Up @@ -172,22 +177,24 @@ the operational prefix lets firstmate distinguish it from a real captain message
separator before injection, so submission is unambiguous regardless of
harness.
- **Busy and composer guards on the supervisor pane** - before injecting, the daemon runs the detected-primary-harness rendered busy guard and reads `fm_backend_composer_state` directly.
Only `empty` permits injection; `pending` protects half-typed or swallowed input, and `unknown` protects unreadable panes and bare dead-shell prompts.
`empty` injects.
`pending` protects half-typed or swallowed input.
`unknown` protects unreadable panes, modals, and dead shells, except on herdr when a fresh styled re-read still proves a genuine agent composer container and native agent-state is idle.
Every other result preserves the buffer for retry, so the daemon never merges its digest into the captain's half-typed line or types it into a shell.
- The active backend passes its capture plus declarative styled, cursor, identity, and row capabilities to the shared screen classifier; all structural recognition and verdict logic remains in `bin/fm-composer-lib.sh`.
Styled captures let that owner remove dim/faint and dark-TRUECOLOR ghost or placeholder text while shape detection uses the ANSI-stripped screen, so a dark border is not lost with ghost content.
A ghost-only or idle bordered composer such as claude's `│ > ... │` therefore reads empty without allowing an unbordered shell prompt to do the same.
`FM_COMPOSER_IDLE_RE` overrides the shared idle-placeholder regex, but a match alone never bypasses the classifier's shape-specific position and ANSI de-emphasis safety gates.
`FM_BUSY_REGEX` overrides the rendered delivery guards plus Grok's isolated task-state fallback.
A blank or otherwise unidentified input row carries no positive container proof and defers injection, so a modal dialog or a mid-redraw pane is never an injection target.
- **Max-defer escape** - the daemon must never silently wedge. If anything stays
buffered past `FM_MAX_DEFER_SECS` (default 300s), the daemon attempts one
normal flush, which still requires an idle pane and an affirmatively empty composer. If that
The herdr unknown override does not weaken that: it requires the same positive container proof from a fresh styled capture, and refuses a bare shell row, a modal, an unidentified row, and any read that fell back to an unstyled capture.
- **Max-defer recovery** - the daemon must never silently wedge. If anything stays
buffered past `FM_MAX_DEFER_SECS` (default 300s), the daemon retries the flush,
including herdr native-idle delivery when the composer is unknown. If that
cannot confirm a submit, it raises a loud, rate-limited wedge alarm: ERROR log,
durable `state/.subsuper-inject-wedged` marker, a tmux status-line flash when
applicable, and a backend-independent active alert. A
composer false-positive surfaces as a visible stall, never an unbounded silent
no-op.
applicable, and a backend-independent active alert. A remaining stall is
visible; a clipped idle composer is supposed to recover on that retry.
- **Verified type-once submit model** - the digest is typed once (`send-keys -l`
on tmux, `pane send-text` on herdr), then submitted with Enter and verified.
Enter is retried, Enter only and never a retype, until the backend submit
Expand Down
6 changes: 6 additions & 0 deletions .agents/skills/project-management/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,18 @@ Choose that posture when adding or creating the project:
- `direct-PR` pushes and opens a PR without the no-mistakes pipeline.
- `local-only` has no required remote or PR and lands only through the approved local fast-forward path.
- `no-mistakes-prod-only` is a conditional policy rather than one flat mode: genuinely internal-only tooling, automation, contributor or operator process, and release or submission work ships `direct-PR`, while product-facing, mixed, and uncertain work ships `no-mistakes`.
- `+hardened` is the highest-rigor choice on this list, adding the quality gate that runs before validation; it rides alongside one of the flat modes above rather than replacing it, so a hardened project is registered as `[no-mistakes +hardened]`, `[direct-PR +hardened]`, or `[local-only +hardened]`.

`no-mistakes-prod-only` is the default for a newly added or created remote-backed project when the captain specifies nothing, and a project with no remote defaults to `local-only`.
State that resolved default while confirming the source, local name, and posture instead of asking the captain to choose from scratch, and record a flat mode instead whenever they ask for one.
Existing registry entries keep the meaning they already have and are never migrated or reinterpreted, so a legacy entry with no bracket stays `no-mistakes`.
Registering a conditional policy is a one-time choice and never requires classifying any change; the per-task surface classification happens at each task's intake, and internal-only is never inferred from file location or project name.

`+hardened` is off for every project unless the captain asks for it, so a project registered without it is `standard`.
Refuse `+hardened` together with `no-mistakes-prod-only` and tell the captain to pick a flat delivery mode instead.
A conditional policy decides per task, so a quality standard that covers only part of a project is a posture nobody can state in one sentence.
`AGENTS.md` section 7 owns how each task's quality resolves at intake, and `bin/fm-project-mode.sh --quality` owns how the registered token is read.

The optional `+yolo` posture changes routine approval authority but does not change the delivery mode.
Default it off for every project and every posture, and enable it only on the captain's explicit instruction.
`AGENTS.md` section 7 owns the complete authority boundary and exceptions when it is on.
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -129,10 +129,10 @@ jobs:
tests-portable-serial:
name: Behavior portable serial ${{ matrix.shard }}
runs-on: ubuntu-latest
# Measured whole remainder is ~19 min of serial work; the balanced shards
# are ~4.8 min each. Cap is a hang tripwire with roughly 3x margin, not the
# expected healthy end of the lane.
timeout-minutes: 15
# Measured whole remainder is ~42 min of serial work; the balanced shards
# are ~10.5 min each, plus about 2 min of setup. Cap is a hang tripwire with
# roughly 2x margin, not the expected healthy end of the lane.
timeout-minutes: 25
strategy:
# Every shard reports so one failure never hides another shard's result.
fail-fast: false
Expand Down
5 changes: 5 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,8 @@ state/ runtime records and signals; gitignored
.status-presentation-cursor .status-presentation-lock fleet-wide per-task status identity/byte-offset manifest and serialization lock preventing already-presented status lines from being replayed as new; owned by fm-classify-lib.sh, with each task's row retired by teardown
.afk durable away-mode flag; present = sub-supervisor may inject escalations (set by /afk, cleared on user return)
.watch.lock .wake-queue.lock watcher singleton and queue serialization locks
.lock.session conversation recorded beside the session lock, so a background continuation of the lock-holding conversation is recognized as the same session; docs/watcher-continuity.md
.turnend-unowned-notice.<session> per-session record of the lock owner the turn-end guard already told that session it does not hold, plus the writer's process identity so a recycled pid reports again and a retired session's record is swept; never touch
.claude-autoarm.lock .claude-autoarm-epoch .claude-autoarm-failure-notified .claude-autoarm-failure-alarmed .turnend-claude-blocks .turnend-claude-blocks.lock Claude Stop auto-arm single-flight, epoch, failure-episode, attended-alarm, guard-budget, and budget-lock records; never touch
.cursor-park-owner .cursor-park-owner.lock .turnend-cursor-blocks Cursor stop-hook owner record, publication and commit lock, and bounded repair-nag budget; never touch
.hash-* .count-* .stale-* .stale-since-* .paused-* .wedge-escalations-* .seen-* .hb-surfaced-* .last-* .heartbeat-streak watcher internals; never touch
Expand Down Expand Up @@ -150,6 +152,8 @@ An `ABSENT` captain, shared-captain, or secondmate file means the firstmate repo

If the session lock cannot be acquired and verified, report its exact diagnostic and remain read-only; another active session is only one possible cause.
A lock-refused session must not spawn, steer, merge, drain the wake queue, repair supervision, repair a checkout, or perform any other fleet mutation.
Read the digest's `HELM:` line rather than the recorded pid: it states in words whether this session holds the lock, and a background continuation of the lock-holding conversation is that same session.
Every fleet-mutation entry point refuses a session that does not hold the home, so a refusal naming the holder is that boundary working, never an obstacle to route around; `docs/watcher-continuity.md` owns the ownership contract.

The digest itself makes no external-network call and never waits for one.
Every network check a session start owes - GitHub auth, dead-secondmate relaunch, secondmate convergence, pending handoff delivery, and project clone refresh - runs concurrently in a bounded worker owned by `bin/fm-startup-network.sh` and is reported in the digest's own `NETWORK CHECKS` section.
Expand Down Expand Up @@ -286,6 +290,7 @@ Resolve every ship task's concrete delivery mode and yolo posture at intake, and
A current explicit captain instruction wins; otherwise the project's registry entry is the captain's standing posture, and dropping below its rigor needs a reason you can state.
On a `no-mistakes-prod-only` project, classify the task's surface: internal-only tooling, automation, contributor or operator process, and release or submission work ships `direct-PR`, while product-facing, mixed, and uncertain work ships `no-mistakes`; never infer internal-only from file location or project name.
An unregistered project or absent registry resolves to `no-mistakes` with yolo off, and the registration gap goes to the captain.
A task's quality posture resolves at intake with the same precedence, a current explicit captain instruction first, then the project's registered posture, then `standard`, with the one-line reason for any deviation recorded in the same backlog note.
Record the resulting mode, yolo, and the one-line reason for any deviation in the backlog item note.

Treat file or subsystem overlap as a risk signal rather than an automatic reason to wait, and dispatch isolated work immediately with no concurrency cap when each change can be independently implemented and validated and the selected delivery path can reconcile ordinary rebases or conflicts.
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ Launching a supported harness inside it instantiates your first mate - and makes
- **A visible crew** - every crewmate works in its own tmux window, experimental herdr/zellij tab, cmux workspace, or Orca terminal you can watch or type into; the first mate reconciles.
- **Disposable worktrees** - each task runs in a clean [treehouse](https://github.com/kunchenguid/treehouse) git worktree, or an Orca-managed worktree when `backend=orca`, so parallel work on one repo never collides.
- **Two task shapes** - ship tasks deliver authorized changes; scout tasks leave standalone investigation reports when the intake contract warrants separate research.
- **Explicit project modes** - each project ships via `no-mistakes`, `direct-PR`, or `local-only`, with an optional `+yolo` autonomy flag.
- **Explicit project modes** - each project ships via `no-mistakes`, `direct-PR`, `local-only`, or one of those plus `+hardened` for the highest-rigor quality gate, with an optional `+yolo` autonomy flag.
- **Optional secondmates** - opt in to persistent second mates that run from isolated firstmate homes with their own `FM_HOME`, state, projects, and session lock, either locally or as a whole home on an SSH-reachable host, with guarded updates and recovery that never turns an unavailable remote route into a local replacement.
- **Event-driven, zero-token supervision** - a bash watcher sleeps on the fleet and wakes the first mate only when something needs you; verified primary harnesses also get a turn-end backstop that blocks or follows up on a blind stop when work is under way and supervision is not live.
- **Optional Relay** - opt in with one local `.env` pairing token so firstmate can answer your public mentions on X and Discord alike, act on normal reversible mention requests through the same lifecycle as chat requests, acknowledge spawned work, and post up to three public-safe completion follow-ups within seven days for genuine milestones and the final outcome without changing non-Relay behavior; a final reply promised in a thread becomes durable state that is reconciled from disk, so a restart or a compacted conversation cannot lose it; dry-run preview records would-be replies and dismissals locally before go-live.
Expand Down
Loading
Loading