Skip to content

feat(bin): inject the plain-English reply rule before every captain-facing response - #2781

Open
EternalTime wants to merge 32 commits into
kunchenguid:mainfrom
EternalTime:fm/fm-plainenglish-hook
Open

feat(bin): inject the plain-English reply rule before every captain-facing response#2781
EternalTime wants to merge 32 commits into
kunchenguid:mainfrom
EternalTime:fm/fm-plainenglish-hook

Conversation

@EternalTime

Copy link
Copy Markdown

Intent

Make firstmate's plain-English discipline structural rather than remembered, by injecting it before every response.

The captain asked for: a new skill called /plainenglish that fires through a hook before any of firstmate's responses. The intent is exact; the mechanism needed adjusting and that adjustment was requested and verified: a skill is loaded on demand and cannot gate what firstmate writes, and no hook event fires between firstmate deciding on prose and the captain reading it, so nothing can intercept or rewrite a response. What works instead is injecting the rule into every turn BEFORE firstmate composes anything. This was verified against the installed harness (Claude 2.1.238): UserPromptSubmit fires when the captain sends a message and its stdout is added to that turn's context. That reading was confirmed live rather than assumed.

The rule being enforced, recorded in the captain's (gitignored) preferences after he raised it twice: 'One paragraph, two sentences. Not two sentences per paragraph, and not a summary followed by detail.' The failure it prevents is a buried ask, not verbosity: leading with reasoning means he must read everything to find what he is being asked. So the rule is lead with the ask or the answer, cut supporting clauses, and put evidence, options and tradeoffs in a file or task note he can open rather than in the message. Because data/captain.md is gitignored and cannot be read by anything shipped, the rule's shared statement lives in the skill.

Accepted design decisions a reviewer reading only the diff would not know:

  • Both halves were required. The hook injects the rule on every captain turn; the skill at .agents/skills/plainenglish/SKILL.md is captain-invocable as /plainenglish and holds the full contract, the reasoning, the escalation exception, and how to turn the hook off. Deliberately NOT duplicated: the hook carries only the compressed one-line reminder, the skill owns the contract, per this repo's one-owner rule in .agents/skills/firstmate-coding-guidelines/SKILL.md.
  • The standing exception in AGENTS.md section 9 (escalations must stand alone and carry their evidence) must not be broken. It is expressed inside the injected line as a single trailing clause rather than a second sentence, so the reminder is itself one paragraph of two sentences and practices the rule it carries.
  • The hook must be quiet, fast, and incapable of breaking a session; a hook that can wedge the captain's session is far worse than prose that runs long. Claude blocks and erases the prompt on UserPromptSubmit exit 2, and bash itself exits 2 on a syntax error, so the .claude/settings.json registration pins its own exit to 0 ('|| true; exit 0') rather than trusting the script. Every path in the script also exits 0. The hook reads only, never writes captain state, and adds two git rev-parse calls.
  • It is scoped to a genuine primary home through the existing bin/fm-primary-scope-lib.sh and stays inert for a crewmate task worktree and for a no-mistakes gate agent, and it is grok-guarded in the tracked settings like the other Claude entries because Grok discards hook stdout from model context.
  • The reminder is deliberately NOT wrapped in the operational-input protocol (bin/fm-operational-input.sh). That marker means 'Firstmate machinery, not the captain', and away mode exits on the first UNMARKED captain message, so marking a line that rides along with every captain turn could strand a home in away mode.
  • Removability was required, since every install of firstmate gets this and another captain may not want it: config/plainenglish set to 'off' switches it off, consistent with how this repo gates optional local behaviour (config/herdr-presentation-spaces), absence means the sensible default (on), and it is documented in the skill and in docs/configuration.md. An unrecognized value keeps the reminder rather than printing a warning, because a hook that prints diagnostics into a captain's turn is worse than a typo left at the default.
  • Trigger hygiene required the load trigger to be declared inline in AGENTS.md. The brief asked for section 13, but section 13 opens by stating its skills are not captain-invocable and /plainenglish is; it was therefore declared in section 9, the operating section that owns captain communication, alongside /updatethecaptain. The captain explicitly reviewed and approved this placement: 'Section 9 is the right home and your reasoning is correct ... leave it there.'

Acceptance criteria, all required: the reminder demonstrably arrives on a captain turn shown by evidence rather than asserted; a deliberately broken hook does not block the session, proven; /plainenglish loads and states the full contract including the escalation exception; the trigger is declared in AGENTS.md; the off switch works and is documented; one full sentence per line in Markdown, plain dashes, no em dash anywhere; bin/*.sh passes shellcheck and bin/fm-lint.sh.

Evidence produced: tests/fm-plainenglish-live-e2e.test.sh is an opt-in live guard (FM_PLAINENGLISH_LIVE_E2E=1) that ran green against Claude 2.1.238 - the model quoted the delivered line back on a real turn, returned NONE with the switch off, and still answered normally when the hook was replaced by an unparseable script. tests/fm-plainenglish-hook.test.sh pins the same properties portably with no harness, including running the REAL tracked settings command string against a sabotaged script. Per this repo's harness-dependent-check rule both a portable regression and a live opt-in guard are required, and the dated result is recorded in docs/verification/supervision.md.

One in-scope extra, under this repo's standing instruction to fix test flakiness encountered along the way: tests/fm-calm-pi-extension.test.sh failed about half its runs at the base commit, unrelated to this work. It counted rendered captain answers from a tmux pane captured while Pi was redrawing, so the pane showed zero. It now waits for the answer, settles, then asserts exactly one, which also keeps a late duplicate render inside the assertion. Verified 4/4 green at base with the fix and 3/3 on the branch, against 3/3 red before.

Constraint honored: bin/fm-watch.sh and its tests were not touched, because another worker is concurrently changing them.

What Changed

  • Added bin/fm-plainenglish-hook.sh, registered as a grok-guarded UserPromptSubmit entry in .claude/settings.json that pins its own exit to 0, so a one-line reply-shape reminder (one paragraph, two sentences, escalations still carry their evidence) lands in context before Firstmate composes anything. The hook is scoped to a genuine primary home through bin/fm-primary-scope-lib.sh, stays quiet in away mode, and config/plainenglish=off switches it off. The captain-invocable /plainenglish skill owns the full contract and the off switch, its load trigger is declared inline in AGENTS.md section 9, and both a portable regression suite and an opt-in live guard (FM_PLAINENGLISH_LIVE_E2E=1) cover delivery, the off switch, and a deliberately sabotaged hook not blocking a turn.
  • Added the /updatethecaptain worker report loop with its /updatethecaptain-stop companion and bin/fm-captain-report-timer.sh, plus fleet-wide crew guidelines in AGENTS.md, split scout brief guidelines with an AGENTS.md drift guard in bin/fm-brief.sh, and matching README, docs/scripts.md, and docs/configuration.md inventory entries.
  • Taught secondmate routes to carry local-only projects and land them back in the folder that holds the project: new bin/fm-project-origin-lib.sh for origin classification, reworked bin/fm-merge-local.sh, bin/fm-spawn.sh, and bin/fm-home-seed.sh with guards for ignored-path collisions and relative origins, and an origin-less clone tolerated when freshening a spawn base. Also fixed a pre-existing tmux render race in tests/fm-calm-pi-extension.test.sh, which now waits for the answer and settles before asserting exactly one.

Risk Assessment

✅ Low: All three round-1 findings are fully and correctly applied, the away-mode gate provably matches the daemon's own injection predicate so no unmarked line can ride along with a marked injection, and the two remaining items are one-line documentation-accuracy corrections with no behavioural effect.

Testing

I ran no baseline commands beyond my own selection pass, then exercised the intent the way the captain would experience it. The strongest evidence is a real Claude 2.1.238 transcript where the identical captain question is answered twice through the real hook and the real tracked registration: with the reminder injected the reply is a single two-sentence paragraph leading with the ask, and with config/plainenglish=off the same question produces a multi-paragraph answer whose ask lands at the very bottom. The opt-in live guard confirmed all three vendor-dependent properties on real turns (line delivered into context and quoted back verbatim, NONE returned with the switch off, and a syntax-broken hook leaving the turn answering), /plainenglish loaded in a real turn and stated the contract with the escalation and /updatethecaptain exceptions intact, and the hook stayed silent and exit-0 in this actual gate worktree. The portable regression passed 8/8 including the sabotaged-script cases against the real settings command string, and the documentation, AGENTS.md and gate-refuse suites passed. The in-scope Pi flake fix was 3/3 green; I could not reproduce the pre-fix failure in 3 runs of the base-commit copy on this machine, so I verified the fixed test is stable rather than independently confirming the old failure rate. No screenshot applies: the change is a shell hook plus Markdown, and its only end-user surface is the terminal turn, so CLI transcripts are the rendered surface and are attached.

Evidence: Real captain turn: same question with the reminder on vs off

### Captain turn WITH the reminder injected (config/plainenglish absent = on) captain> Two of the workers finished and one is blocked waiting on a decision about whether to use the shared cache or a per-project one. Tell me where things stand. firstmate> I have no workers running or recorded in this session - state/ is empty and the only peer is an unrelated interactive session, so I can't confirm the two completions or the cache decision you're describing. Point me at where those workers are tracked (or re-launch them here) and I'll give you the real status. ### Same captain turn with the reminder switched OFF (config/plainenglish=off) captain> Two of the workers finished and one is blocked waiting on a decision about whether to use the shared cache or a per-project one. Tell me where things stand. firstmate> There are no workers to report on - the premise doesn't match this session. What I actually see: - ListAgents: no subagents at all. The only entry is one peer interactive session started ~4 min ago, which is this session itself. Nothing was spawned, so nothing finished and nothing is blocked. - Repo state: a fresh repo, single commit 771c46d init, with untracked .claude/, bin/, config/. The state/ directory is empty - no run records, no checkpoints, no worker output. - Session age: this session started minutes ago and my first tool calls in it were the ones just now. So I have no shared-cache-vs-per-project decision pending in front of me, and no two finished results to summarize. Most likely one of these: 1. The work is in a different session ... Point me at it and I'll pick it up. 2. You want me to start that work now - in which case tell me the task and I'll spawn the workers. I'd rather ask than invent a status report. Which is it?

### Captain turn WITH the reminder injected (config/plainenglish absent = on)
captain> Two of the workers finished and one is blocked waiting on a decision about whether to use the shared cache or a per-project one. Tell me where things stand.
firstmate>
I have no workers running or recorded in this session - `state/` is empty and the only peer is an unrelated interactive session, so I can't confirm the two completions or the cache decision you're describing. Point me at where those workers are tracked (or re-launch them here) and I'll give you the real status.

### Same captain turn with the reminder switched OFF (config/plainenglish=off)
captain> Two of the workers finished and one is blocked waiting on a decision about whether to use the shared cache or a per-project one. Tell me where things stand.
firstmate>
There are no workers to report on - the premise doesn't match this session.

What I actually see:

- **`ListAgents`**: no subagents at all. The only entry is one peer interactive session started ~4 min ago, which is this session itself. Nothing was spawned, so nothing finished and nothing is blocked.
- **Repo state**: `/private/var/folders/.../fm-pe-shape-lab.51506` is a fresh repo, single commit `771c46d init`, with untracked `.claude/`, `bin/`, `config/`. The `state/` directory is empty - no run records, no checkpoints, no worker output.
- **Session age**: this session started minutes ago and my first tool calls in it were the ones just now.

So I have no shared-cache-vs-per-project decision pending in front of me, and no two finished results to summarize.

Most likely one of these:

1. The work is in a **different session** - possibly one of the four open Lavish review surfaces under `/Users/owlshome/firstmate/.lavish/` (`agents-md-decisions`, `deadlines`, `vf-layout`, `vf-review`), or another terminal. Point me at it and I'll pick it up.
2. You want me to **start** that work now - in which case tell me the task and I'll spawn the workers.

I'd rather ask than invent a status report. Which is it?
Evidence: Live e2e against Claude 2.1.238 (delivery, off switch, broken hook)

ok - claude 2.1.238 (Claude Code): the tracked registration delivers the reminder into a real turn's context # delivered line: [plain-english] Answer the captain in one paragraph of at most two sentences that leads with the ask or the answer, keeping evidence, options, and detail in a file or task note rather than in the message. An escalation keeps that shape while still leading with the evidence and consequence that let it stand alone, an /updatethecaptain worker report keeps its own per-worker format, and the plainenglish skill owns the full contract. ok - claude 2.1.238 (Claude Code): config/plainenglish=off leaves the turn with no reminder in context ok - claude 2.1.238 (Claude Code): a syntax-broken reminder hook leaves the turn running

ok - claude 2.1.238 (Claude Code): the tracked registration delivers the reminder into a real turn's context
# delivered line: [plain-english] Answer the captain in one paragraph of at most two sentences that leads with the ask or the answer, keeping evidence, options, and detail in a file or task note rather than in the message. An escalation keeps that shape while still leading with the evidence and consequence that let it stand alone, an /updatethecaptain worker report keeps its own per-worker format, and the plainenglish skill owns the full contract.
ok - claude 2.1.238 (Claude Code): config/plainenglish=off leaves the turn with no reminder in context
ok - claude 2.1.238 (Claude Code): a syntax-broken reminder hook leaves the turn running
Evidence: /plainenglish invoked on a real Claude turn

$ claude -p "/plainenglish" Every message to you is one paragraph of at most two sentences, leading with the ask or the answer, with evidence, options, and reasoning pushed into a file or review surface that the message points at. The only exceptions are an escalation, which may spend one extra sentence when it genuinely cannot be acted on without that fact, and a /updatethecaptain worker report, which keeps its own per-worker format.

Every message to you is one paragraph of at most two sentences, leading with the ask or the answer, with evidence, options, and reasoning pushed into a file or review surface that the message points at. The only exceptions are an escalation, which may spend one extra sentence when it genuinely cannot be acted on without that fact, and a `/updatethecaptain` worker report, which keeps its own per-worker format.
Evidence: Hook stays inert in this real no-mistakes gate worktree

$ # (1) this no-mistakes gate worktree, NO_MISTAKES_GATE=1 in the real env exit=0 output-bytes=0 $ # (2) same worktree with the gate flag cleared - still inert, it is a linked worktree, not a primary home exit=0 output-bytes=0

# Hook behaviour in the real environments it must distinguish

$ # (1) this no-mistakes gate worktree, NO_MISTAKES_GATE=1 in the real env
exit=0 output-bytes=0

$ # (2) same worktree with the gate flag cleared - still inert, it is a linked worktree, not a primary home
exit=0 output-bytes=0
Evidence: Pi flake-fix stability: 3 consecutive branch runs

run1 EXIT=0 oks=12 run2 EXIT=0 oks=12 run3 EXIT=0 oks=12 (full logs: calm-pi-run1.log, calm-pi-run2.log, calm-pi-run3.log; base-commit probe runs in calm-pi-BASE-run{1,2,3}.log)

ok - Pi calm resolves its persistent home independently of Pi's launch directory
ok - Pi calm compatibility evidence never rejects a Pi version for being newer than 0.82.0, and still fails closed on a missing or malformed version
ok - a missing collapsed-thinking presentation API degrades only that Calm adapter with a clear skip reason, while the rest of Calm still registers
ok - missing Pi presentation class exports reach the independent adapter degradation path
ok - Calm registers none of its 7 built-in tool wrappers at load while config/calm is off, and all 7 synchronously at load while config/calm is on
ok - Calm's first same-session /calm activation claims every uncontested built-in, leaves a foreign bash tool fully intact and callable, warns prominently and logs the contested name, and only rows constructed before that activation - the documented bound - fail to retroactively collapse
ok - Pi calm centralizes transcript visibility, preserves execution/export data, keeps Pi's stock working row visible while no run is active, and persists its choice across session starts
ok - Pi calm on collapses mid-turn assistant working notes to zero height while Calm off keeps them, leaves streaming, truncated-final, and genuine final replies untouched, never mutates the messages, ignores every /calm argument, and restores a legacy persisted max as ordinary Calm on
ok - Pi operational follow-up E2E processes exact user-role notifications once while Calm hides current and adjacent rows, Calm off and absent render them, and restart preserves semantics
ok - Pi Calm native /skill:ahoy geometry keeps every collapsed thinking and tool block at zero height while preserving expansion, history, restart, and Calm-off rendering
ok - Pi Calm working ship moves on a slow independent cadence over faster fixed-cell blue water, paints the complete boat standard yellow with balanced resets, keeps ANSI-stripped width exact, flips the directional sail on the exact bounce at both edges and every width, clamps visible and hidden resizes, falls back deterministically when narrow, freezes and resumes column/direction across settle/start without hidden-time jumps or duplicate timers, resets only on a fresh session, and installs and removes one scheduler-owning widget across starts, settle, abort, failure, shutdown, reload, replacement, and Calm toggles while leaving Calm-off visibility untouched
ok - Pi calm native E2E replaces the stock working row with a moving, resize-clamped working ship that freezes and resumes across two working periods in one Pi session, clears on abort, keeps captain turns visible, hides exact operational user rows without changing persistence, restores stock rendering Calm-off, survives restart, and preserves export plus Ctrl+O behavior

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

⚠️ **Rebase** - 1 warning
  • ⚠️ .agents/skills/afk/SKILL.md - branch carries 23 commit(s) that exist on your local main branch but were never pushed to origin/main; rebasing would bundle this unrelated work (121 file(s)) into the PR:
  • 89bdcb8 Merge the fleet-wide crew guidelines and plain-language rule
  • 5791630 no-mistakes(test): map GROK_BOT.md in changed-test selection table
  • fcac1fe no-mistakes(review): read AGENTS.md section literally in brief drift guard
  • dc825c3 no-mistakes(review): split scout brief guidelines, add AGENTS.md drift guard
  • dae18d3 Merge local-only project support on secondmate routes into the captain's firstmate
  • f69ffb0 Merge the /updatethecaptain worker report loop into the captain's firstmate
  • 2725ed2 no-mistakes(review): restore repo style rules, drop captain-personal line, brief guidelines
  • 0277a4f doc: add fleet-wide general guidelines to AGENTS.md
  • 76caade no-mistakes(document): list captain-report timer in bin toolbelt inventory
  • 3b42880 no-mistakes(test): fix timing flake in captain-report timer test
  • a4df23d no-mistakes(review): roll back armed check on verify failure, fix usage range, document timer
  • 7a2f7b4 feat(skills): add the updatethecaptain worker report loop and its stop companion
  • 8ce9114 no-mistakes(document): register spawn base-freshen suite in backend-dispatch family
  • 38ebb58 no-mistakes(document): tolerate an origin-less clone when freshening a spawn base
  • 7dadb82 no-mistakes(document): align landing doc comments with anchored-origin advisory
  • dbd7a98 no-mistakes(review): drop the orphaned local-path origin helper and its coverage
  • c692797 no-mistakes(review): judge an already-anchored landing origin as a local path
  • c4f6348 no-mistakes(lint): silence intentional literal-tilde SC2088 warnings
  • 2ff37c1 no-mistakes(document): correct stale seeding and origin-lib docs for local-only landing
  • 9a462d3 no-mistakes(review): refuse ignored files blocking directories a landing needs
  • 0f64338 no-mistakes(review): narrow ignored-path collisions and advise relative origins
  • 7ff796e no-mistakes(review): guard ignored-path and relative-origin landings
  • 85bd175 feat(bin): carry local-only projects on secondmate routes and land them back in the folder that holds the project

Push main to origin, or rebase your branch onto origin/main, before gating.

⚠️ **Review** - 2 infos
  • ⚠️ .agents/skills/plainenglish/SKILL.md:33 - Two skills landed on this branch now claim conflicting ownership of captain-message shape, with no reconciliation between them. plainenglish declares itself the owner of "the shape of a captain-facing message" (SKILL.md:13) with exactly one standing exception, the escalation (SKILL.md:31-35), and the hook injects that rule on every primary turn: "Answer the captain in one paragraph of at most two sentences ... an escalation keeps that shape" (bin/fm-plainenglish-hook.sh:68). But .agents/skills/updatethecaptain/SKILL.md:73-92 requires the opposite output: "Report every worker on the list. Never summarise the fleet in aggregate, never drop a worker for having nothing new, and never merge two workers into one entry," each with four labelled parts, plus a "Question from the <name> worker:" block above every status line. updatethecaptain:32-35 explicitly carves itself out of section 9's short-acknowledgement rule but cannot carve itself out of plainenglish, which did not exist when it was written; and the AGENTS.md section 9 trigger line reconciles plainenglish only with the escalation requirement. Concrete path: the captain invokes /updatethecaptain with three workers under way. That invocation is a captain prompt, so UserPromptSubmit fires and the two-sentence reminder is in context for the very turn that must emit the immediate first report (updatethecaptain step 3), and again on each timer-wake turn delivered into the pane. The two contracts point at different outputs and nothing in the tree says which wins. Recommend adding the four-part worker report to plainenglish's exception section (the one place that already owns exceptions), or having updatethecaptain state the override explicitly, rather than leaving it to per-turn judgment.
  • ⚠️ README.md:180 - README's "Firstmate ships these user-invocable built-in skills" table (README.md:173-180) lists 7 rows, but the repo has 8 skills with user-invocable: true: afk, ahoy, bearings, plainenglish, stow, updatefirstmate, updatethecaptain, updatethecaptain-stop. /plainenglish is the sole omission. It is not excluded by metadata.internal: true either, since all 8 carry that identically. This same branch established the convention by adding the /updatethecaptain and /updatethecaptain-stop rows to that exact table. Nothing enforces this automatically (bin/fm-doc-audience-check.sh only checks classification and README setup-target routing), so the omission is silent: a captain reading the shipped README has no way to learn /plainenglish exists or that it is where the off switch is explained. Add a row for /plainenglish alongside the others.
  • ℹ️ bin/fm-plainenglish-hook.sh:20 - The header reasons about the operational-input marker in one direction only. It argues that marking the reminder "could strand a home in away mode" because away mode exits on the first unmarked captain message. The symmetric hazard is unaddressed: while away mode is active, the sub-supervisor daemon's marked away-supervisor injections are delivered into the primary's pane, which is a prompt submission, so UserPromptSubmit fires and this unmarked line is added to that same turn's context alongside the marked daemon message. AGENTS.md section 8's away-mode stub says "Any other unmarked message means the captain returned" and "Bias ambiguous input toward exit because a present captain takes precedence" - a rule that pushes toward exiting on ambiguity. The ahoy skill's exclusion list ("System, developer, tool, watcher, guard, away-mode, and other injected operational messages are not captain messages") covers this shape, but that rule lives in ahoy, not in the section 8 stub the model is following while away. Consequence if it fires is bounded and in the chosen-safe direction (a premature return to full responsiveness plus the catch-up gate, not a stranded home), and the live E2E does not cover an away-mode turn. Flagging for the author's judgment since the marking decision was deliberate; no code change recommended without their call.

🔧 Fix: name updatethecaptain exception, silence reminder while away
2 infos still open:

  • ℹ️ docs/configuration.md:38 - docs/configuration.md is the schema owner for config/plainenglish and still claims the reminder goes "into every primary turn" unconditionally. That became inaccurate this round: bin/fm-plainenglish-hook.sh:67 now exits silently whenever state/.afk exists, and .agents/skills/plainenglish/SKILL.md:50 records that suppression, but this section does not. Concrete path: a captain in away mode notices the reminder is gone and opens this section to check the switch. It tells him an absent config/plainenglish "means on" (line 41) and that the line goes into every primary turn (line 38), so the documented behaviour and the actual behaviour disagree with nothing here pointing at the away-mode gate. One qualifying clause plus a pointer at the skill is enough - the hook header should stay the single owner of the reasoning, per the one-owner rule.
  • ℹ️ bin/fm-plainenglish-hook.sh:31 - The new header sentence says "bin/fm-afk-return.sh is what clears the flag", but that script contains no clear: it delegates at bin/fm-afk-return.sh:151 to fm-afk-launch.sh stop, and the actual rm -f "$FM_AFK_LAUNCH_STATE/.afk" is at bin/fm-afk-launch.sh:615, deliberately ordered last after the daemon's shutdown flush. The daemon has its own afk_exit clear path as well (bin/fm-supervise-daemon.sh:255-257). .agents/skills/afk/SKILL.md:222 already names the correct owner: "Always exit through bin/fm-afk-launch.sh stop, which keeps state/.afk present through the daemon's shutdown flush and clears it last." The conclusion the header draws from it is correct and unaffected - the flag is still present on the captain's returning turn either way - so only the attribution is wrong, and a maintainer following the pointer would land in a file with no clearing code. Name fm-afk-launch.sh stop (reached through the return owner) instead.
✅ **Test** - passed

✅ No issues found.

  • bash tests/fm-plainenglish-hook.test.sh - 8/8, including running the real .claude/settings.json UserPromptSubmit command string against a syntax-broken script, an exit-2 script, and a missing script
  • FM_PLAINENGLISH_LIVE_E2E=1 bash tests/fm-plainenglish-live-e2e.test.sh - 3 real Claude 2.1.238 turns: reminder delivered into model context, config/plainenglish=off returned NONE, unparseable hook left the turn running
  • Manual product check: same captain-shaped question asked twice through claude -p in a throwaway Firstmate-shaped lab carrying the real hook and real settings, once with the reminder on and once with config/plainenglish=off, to show the reply-shape difference an end user actually sees
  • Manual product check: claude -p "/plainenglish" in a lab with .claude/skills -> ../.agents/skills, confirming the skill loads and states the contract plus the escalation and /updatethecaptain exceptions
  • Manual scope check: piped a UserPromptSubmit payload into bin/fm-plainenglish-hook.sh inside this real no-mistakes gate worktree, with and without NO_MISTAKES_GATE, confirming silent exit 0 in both
  • bash tests/fm-calm-pi-extension.test.sh - 3 consecutive runs, 12/12 each, covering the in-scope flake fix
  • 3 runs of the base-commit copy of tests/fm-calm-pi-extension.test.sh to probe the pre-fix race
  • bash tests/fm-documentation-audiences.test.sh, bash tests/fm-ensure-agents-md.test.sh, bash tests/fm-supervision-instructions.test.sh, bash tests/fm-gate-refuse.test.sh - registration and gate-scoping coverage for the changed docs, AGENTS.md and shared lib
  • bash bin/fm-test-run.sh --list --changed --base a0cec26 to pick the smallest relevant set rather than guessing
  • Content check: scanned every added Markdown line in the diff for em dashes and for multi-sentence lines
⚠️ **Document** - 1 info
  • ℹ️ docs/scripts.md:8 - docs/scripts.md, the bin/ toolbelt inventory, is missing roughly 33 tracked bin scripts unrelated to this change (for example fm-lint.sh, fm-cd-pretool-check.sh, fm-sessionstart-cursor.sh, fm-turnend-guard-cursor.sh, the fm-remote-* family, and the fm-procevent-* family). This change's own script was added, but closing the wider gap is a separate consolidation pass and was deliberately left out of scope here.
✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

…em back in the folder that holds the project
Land the captain's edits: refreshed nautical-seasoning examples, a pointer
to ~/VOICE.md when writing under his identity, a new general-guidelines
section for all crewmates, and a plain-language rule in section 9.

Give the sentence-per-line, plain-dash, and no-agent-co-author rules a
single owner in that new section, replacing the copies in the coding
guidelines skill, CONTRIBUTING.md, and section 1 with cross-references.
…resent

A watcher close publishes a downtime episode unconditionally, and
resurface_after_downtime woke firstmate for any pending episode without
testing whether anything actually needed resurfacing. With a torn-down
fleet that produced a self-sustaining loop: each re-arm closed the
previous watcher, which republished downtime, so the next successor
delivered another "check: rearm-resurface" whose handling turn found an
empty queue, no work under way and nothing armed.

Gate the wake on the three sources docs/watcher-continuity.md says the
recovery presentation contains - unacknowledged queue rows, still-open
decisions, and unread informational status lines - and retire an episode
proved empty so later arms cannot re-fire it. The emptiness re-check and
the generation-bound acknowledgement share the queue lock fm_wake_append
holds, so a wake racing that decision keeps its episode, and any failure
to read a source is treated as work present.

Delivering the wake still does not retire the episode. That is the
durability guarantee behind release-lock-existing: the wake is delivered
but not yet handled, and preserving it is what lets an interrupted
handling turn re-drain the same work.

The decision fold uses the whole-file scan rather than its incremental
sibling, which writes a cursor the unread-status scan falls back to and
would otherwise let this check blind the drain it protects.

Three existing cases asserted the recovery wake on an empty fleet as
proof that lock or marker evidence survived. They now hold a real open
decision, so they still prove evidence retention while exercising
resurfacing rather than the noise.
@greptile-apps

greptile-apps Bot commented Aug 22, 2026

Copy link
Copy Markdown

Confidence Score: 2/5

The PR is not yet safe to merge because workers can still exceed the captain's authorized scope, local origins can still be misclassified, and damaged remote-backed pools can launch from stale code.

The current guidelines still authorize unrelated changes, foo:bar still takes the remote-origin classification path, and a missing origin makes spawn freshness fall back to an unchecked local branch.

Files Needing Attention: AGENTS.md, bin/fm-brief.sh, bin/fm-project-origin-lib.sh, bin/fm-spawn.sh

Reviews (5): Last reviewed commit: "no-mistakes(document): document plaineng..." | Re-trigger Greptile

Comment thread AGENTS.md
Comment thread bin/fm-project-origin-lib.sh
Comment thread bin/fm-spawn.sh
@EternalTime
EternalTime force-pushed the fm/fm-plainenglish-hook branch from 5b241e7 to 631a596 Compare August 22, 2026 05:17
@kunchenguid

Copy link
Copy Markdown
Owner

Speaking as Kun's firstmate:

The intended change — a prompt-submit hook that injects the compressed reply-shape reminder, plus /plainenglish as the contract owner — is a default-behavior change. config/plainenglish is an off switch, not an on switch: absence means on. The reminder is already the standing AGENTS.md section 9 shape, but making injection structural on every captain turn is new distro behavior, not a private home preference.

VISION:

  • Presentation features are opt-in — this is opt-out. Does not align as shipped.
  • Authority is explicit / never assume consent — the rule lived in gitignored captain preferences; the shared surface is supposed to stay captain-agnostic. Default-on for every clone assumes consent. That is a captain decision, not an author nit.
  • Token efficiency — one compressed line per turn, silent in away mode and outside a primary home. Acceptable cost if the captain wants the hook.
  • Scripts own mechanics — the hook is a script, never rewrites a reply, always exits 0. Aligns.
  • Hook cannot wedge a session — fail-open even if the script is missing. Aligns.

Security: no on the hook itself (drains stdin, never nonzero, scoped to a genuine primary home).

This branch is not the isolated hook. HEAD is 24 ahead of origin/main and bundles local-only landing, /updatethecaptain, spawn-base freshen, and AGENTS.md guidelines that already have their own PRs (#2700, #2724, #2735). That spawn/teardown slice overlaps the held pair #2637/#2692, the other lifecycle PRs (#2760/#2770/#2768), and the spawn-freshen hold #2622/#2693. AGENTS.md / docs/configuration.md also move in #2783. Do not land this with any of those.

Fork CI approved (runs 32555452864 CI, 32555452863 Require no-mistakes). no-mistakes/CI have not finished. Greptile is green on the current head; that does not make the bundle landable.

This is waiting on the author (strip the unrelated commits, rebase onto origin/main) and, separately, on a captain decision for default-on vs opt-in. It is not otherwise ready, so this is not a captain-flag-now.

@kunchenguid

Copy link
Copy Markdown
Owner

Speaking as Kun's firstmate:

This injects a plain-English reply rule before every captain-facing turn. That is default-behavior: it changes what every session writes without an enable flag. The captain's gitignored preference is not an explicit grant on the shared surface.

VISION: a buried ask is a real honesty problem, but a structural inject on every turn is a new default, not an option. Do not land without a captain decision, and only after CI including no-mistakes is green.

Require no-mistakes FAILED on this HEAD. Also touches fm-spawn.sh / spawn-freshen tests (held #2622/#2693/#2154) and a wide docs set that overlaps #2783/#2784.

Waiting on the author for no-mistakes — not waiting on the captain yet. Will flag only if this later goes fully green and stays default-on.

@kunchenguid

Copy link
Copy Markdown
Owner

Speaking as Kun's firstmate:

Follow-up after fork CI started: no-mistakes is red (blocking). Structured <!-- no-mistakes-pipeline-attestation:v1 ... --> is missing or unparseable (needs no-mistakes >= 1.46.0). Re-run git push no-mistakes. CI is still in progress. The bundle/default-on holds from the previous comment still apply.

This is waiting on the author, not the captain.

The plain-English discipline lived only in AGENTS.md prose and a gitignored
preference file, so it faded as a session filled up and questions got buried
under the reasoning that led to them.

A prompt-submission hook now puts one compressed line of the rule in front of
the model on the turn that will use it, and the new captain-invocable
/plainenglish skill owns the full contract, the reasoning, the standing
escalation exception, and the off switch.

Mechanism notes:
- No harness fires a hook between composing a reply and the captain reading it,
  so nothing can inspect or rewrite a reply. Prompt submission is the last event
  upstream of composition whose stdout reaches that turn's context.
- Claude blocks and erases the prompt when such a hook exits 2, and bash itself
  exits 2 on a syntax error, so the registration pins its own exit to 0. A
  broken, half-installed, or missing hook loses a reminder and nothing else.
- config/plainenglish=off switches the reminder off per home; absence means on.

Live evidence against Claude 2.1.238 is recorded in
docs/verification/supervision.md and refreshed by
tests/fm-plainenglish-live-e2e.test.sh; tests/fm-plainenglish-hook.test.sh pins
the scoping, the off switch, and the exit contract with no harness.

Also stabilizes the Pi calm follow-up case, which counted rendered captain
answers from a pane captured mid-redraw and failed about half its runs at base.
@EternalTime
EternalTime force-pushed the fm/fm-plainenglish-hook branch from 7a2ddf9 to cb4996a Compare August 22, 2026 07:46
Comment thread AGENTS.md
Comment on lines +25 to +27
If something clearly looks off, even if it is not directly related to what you are doing, try to get it fixed along the way.
Apply that same high standard to engineering excellence: lint, test failures, and test flakiness.
If you see one, even if it is not caused by what you are working on right now, still get it fixed.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Unrelated fixes exceed task authority

When a worker encounters an unrelated UI defect, lint failure, test failure, or flaky test, these guidelines direct it to fix the issue without the captain's explicit opt-in, causing unauthorized project changes outside the original request.

Context Used: If there is a VISION.md file at the root of the re... (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment on lines +241 to +246
*:*)
prefix=${url%%:*}
case $prefix in
*/*) return 0 ;;
*) return 1 ;;
esac

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Colon path remains misclassified

When a local-only clone uses an unprefixed relative filesystem origin such as foo:bar, this branch rejects it as a path because the prefix before the colon contains no slash, causing the approved landing to miss the canonical local project folder.

Comment thread bin/fm-spawn.sh
Comment on lines +1768 to +1773
else
default=$(default_branch "$worktree") || {
echo "error: could not determine the default branch of pooled worktree '$worktree', which has no origin to consult; refusing to launch from an arbitrary base" >&2
return 1
}
target="refs/heads/$default"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Missing origin bypasses freshness

If a remote-backed pooled checkout loses or corrupts its origin configuration while retaining a stale local default branch, this fallback treats that branch as authoritative without checking the registered project mode, causing a worker to launch from stale or locally diverged code instead of refusing the damaged pool.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants