Skip to content

feat: make user address preference-aware instead of mandating a captain salutation#692

Open
s3-oz wants to merge 8 commits into
kunchenguid:mainfrom
s3-oz:fm/preferred-address-override-x3
Open

feat: make user address preference-aware instead of mandating a captain salutation#692
s3-oz wants to merge 8 commits into
kunchenguid:mainfrom
s3-oz:fm/preferred-address-override-x3

Conversation

@s3-oz

@s3-oz s3-oz commented Jul 18, 2026

Copy link
Copy Markdown

Intent

The developer wanted to remove Firstmate's mandatory rule requiring every response to address the user as "captain," replacing it with a preference-aware address contract: use the preferred name from data/captain.md when present (Oz, addressed only when useful), fall back to neutral prose with no required salutation, and keep nautical language strictly optional. They required preserving "captain" as the internal role name for authority and lifecycle concepts, not hardcoding "Oz" into shared tracked templates, updating any duplicate or contradictory salutation mandates in docs/tests/skills, and adding regression coverage that rejects a mandatory salutation while allowing the role vocabulary. They constrained the change to be minimal (one sentence per Markdown line, plain dashes, no unrelated cleanup) and to follow the firstmate coding guidelines skill. After committing, they directed the agent to ship the change through the no-mistakes validation pipeline without --yes, resolving gates along the way: authorizing a fix to a contradictory canned "Captain" message in the updatefirstmate skill, clearing a ShellCheck 0.11.0 lint blocker, and redirecting the push to a fork at s3-oz/firstmate after a 403 on the upstream repo.

What Changed

  • Replaced the mandatory "address the user as captain" rule in AGENTS.md with a preference-aware contract: use the preferred name recorded in data/captain.md (or the shared data/captain-shared.md) when present, otherwise fall back to neutral prose with no required salutation; nautical language stays optional and "captain" remains the internal role name for authority/lifecycle concepts.
  • Updated stale salutation wording that contradicted the new contract in the README quickstart example and the afk, fmx-respond, and updatefirstmate skills, pointing address guidance at the AGENTS.md contract instead.
  • Added regression coverage in tests/fm-captain-translation-contract.test.sh that rejects reintroduction of a mandatory captain salutation while allowing role vocabulary, and hardened related tests (sanitizing an inherited NO_MISTAKES_GATE in the sessionstart-nudge test, skipping the shellcheck assertion when shellcheck is absent). The full test suite passed through the no-mistakes pipeline, including a negative check that re-adding the old mandate fails the contract test.

Risk Assessment

✅ Low: The round's only change is a one-sentence documentation contract edit plus its matching test assertion, applied exactly as the user instructed with no runtime code touched.

Testing

Ran the full test suite baseline plus the four changed test files (all pass), proved the new regression test rejects a reintroduced mandatory "captain" salutation via a temporary negative mutation, and demonstrated the preference-aware contract end-to-end by running the real fm-session-start.sh with a data/captain.md preference and capturing it in the agent-facing context digest. No visual evidence applies — this is a CLI/prompt-contract change with no rendered UI surface; CLI transcripts are the end-user surface.

Evidence: New AGENTS.md address contract (agent-facing preamble)

Use the user's stated preferred name or form of address from data/captain.md or inherited data/captain-shared.md when present, and use direct address only when it is useful. When no preference exists, default to neutral direct prose with no required salutation or repeated form of address. Use light nautical seasoning only when it fits: the occasional "aye", "on deck", "shipshape", "under way", or "ahoy" may land naturally. Keep that seasoning optional, never let it override a stated address preference, and never let it obscure technical content...

# Firstmate

You are the first mate.
The user is the captain.
This file is your entire job description.

Use the user's stated preferred name or form of address from `data/captain.md` or inherited `data/captain-shared.md` when present, and use direct address only when it is useful.
When no preference exists, default to neutral direct prose with no required salutation or repeated form of address.
Use light nautical seasoning only when it fits: the occasional "aye", "on deck", "shipshape", "under way", or "ahoy" may land naturally.
Keep that seasoning optional, never let it override a stated address preference, and never let it obscure technical content; never use it in commits, briefs, PRs, or anything crewmates or other tools read; drop the playful flavor entirely when delivering bad news or relaying serious findings.
For captain-facing escalation style and outcome phrasing, see section 9.
Evidence: End-to-end: fm-session-start.sh injects the captain.md address preference into the agent's context digest

data/captain.md -------------------------------------------------------------------------------- Preferred name: Oz. Address me as Oz only when direct address is actually useful; no salutation required. data/captain-shared.md (shared, main-authoritative, read-only in secondmate homes) -------------------------------------------------------------------------------- ABSENT

data/captain.md
--------------------------------------------------------------------------------
Preferred name: Oz.
Address me as Oz only when direct address is actually useful; no salutation required.

data/captain-shared.md (shared, main-authoritative, read-only in secondmate homes)
--------------------------------------------------------------------------------
ABSENT
Evidence: Regression test rejects a reintroduced mandatory salutation (negative mutation)

== Reintroducing old mandatory salutation lines into AGENTS.md == == Running tests/fm-captain-translation-contract.test.sh == not ok - AGENTS.md reintroduced a mandatory captain salutation exit=1

== Reintroducing old mandatory salutation lines into AGENTS.md ==
== Running tests/fm-captain-translation-contract.test.sh ==
not ok - AGENTS.md reintroduced a mandatory captain salutation
exit=1
Evidence: Targeted test transcript (4 changed test files, all passing)
== tests/fm-captain-translation-contract.test.sh ==
ok - chat address follows private preference without removing internal captain vocabulary
ok - section 9 owns the positive captain-facing translation contract
ok - scout remains allowed in private captain chat
ok - compressed safety labels require concrete plain renderings
ok - section 9 maps high-risk internal vocabulary families
ok - captain chat rejects verbatim internal evidence while private reports stay precise
ok - outward-facing skill handoffs point to the section 9 owner
ok - skills cross-reference section 9 instead of duplicating the mapping list
exit=0
== tests/fm-brief.test.sh ==
ok - fm-brief.sh: bash -n succeeds
ok - fm-brief.sh: --help renders the complete header
ok - fm-brief.sh: no-mistakes/direct-PR/local-only briefs generate cleanly
ok - fm-brief.sh: faster paths use configured authority without stacked review
ok - fm-brief.sh: no-mistakes DOD wording avoids the apostrophe regression
ok - fm-brief.sh: ship project-memory wording carries the AGENTS.md authoring bar
ok - fm-brief.sh: --herdr-lab emits the complete hard safety contract
ok - fm-brief.sh: --herdr-lab uses its quoted Firstmate-owned helper path
ok - fm-brief.sh: ship and scout scaffolds make omitted Herdr intent fail-visible
ok - fm-brief.sh: Herdr lab contract covers scouts and rejects secondmate misuse
ok - fm-brief.sh: --no-projects scaffolds a project-less charter and guards misuse
ok - fm-brief.sh: custom pause verb renders in every scaffold
ok - fm-brief.sh: investigation and visual-review completions load the shared decision policy
ok - fm-brief: scout and secondmate code paths still scaffold well-formed briefs
exit=0
== tests/fm-cd-pretool-check.test.sh ==
ok - cd-guard acceptance matrix: 63 cases x 5 harness entry forms, block/allow all correct
ok - cd-guard: fires in a secondmate home (its own primary session is a primary)
ok - cd-guard: inert in a crewmate/scout task worktree (linked git worktree)
ok - cd-guard: inert in a non-firstmate repo (no AGENTS.md)
ok - cd-guard: inert when not inside a git repo
ok - cd-guard: reproduces the cwd leak and denies the exact command that causes it
ok - cd-guard: fails open on empty stdin
ok - cd-guard: fails open on unparseable stdin JSON
ok - cd-guard: fails open (never blocks) when node is missing
ok - cd-guard: fails open on the stdin path when jq is missing
ok - cd-guard: prefilter fast-allows (skips node) when no cd/pushd/popd substring is present
ok - cd-guard: fm-cd-command-policy.mjs CLI honors the deny/allow output contract
ok - .claude/settings.json: PreToolUse invokes the cd-guard alongside the arm guard
ok - .codex/hooks.json: PreToolUse invokes the cd-guard alongside the arm guard
ok - .grok primary cd hook: PreToolUse invokes the cd-guard
ok - .opencode cd plugin: tool.execute.before invokes the cd-guard and blocks by throwing
ok - .pi primary extension: tool_call runs the cd-guard alongside the watcher-arm check
ok - bin/fm-cd-pretool-check.sh is shellcheck-clean
exit=0
== tests/fm-sessionstart-nudge.test.sh ==
ok - fm-sessionstart-nudge: a genuine primary gets exactly one instruction line
ok - fm-sessionstart-nudge: NO_MISTAKES_GATE is silent
ok - fm-sessionstart-nudge: .no-mistakes gate common-dir is silent
ok - fm-sessionstart-nudge: an unmarked linked task worktree is silent
ok - fm-sessionstart-nudge: a marked linked secondmate home is a primary
ok - fm-sessionstart-nudge: a checkout without state is silent
ok - fm-sessionstart-nudge: a lock holder in process ancestry is already run
ok - OpenCode session.created delivers the exact wrapper nudge once per session
ok - all five verified harnesses register the shared session-start nudge
exit=0

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

🔧 **Review** - 3 issues found → auto-fixed ✅
  • ℹ️ AGENTS.md:7 - The new address contract in AGENTS.md names only data/captain.md as the preference source, but AGENTS.md:111 also establishes data/captain-shared.md as the main-authoritative shared captain-preference file inherited by secondmates. A preferred name recorded only in captain-shared.md would fall outside the letter of the new rule. Likely acceptable because captain.md is documented as canonical and carries pointers to the shared file, but worth confirming the omission is intentional.
  • ℹ️ tests/fm-captain-translation-contract.test.sh:28 - test_chat_address_respects_private_preference asserts entire AGENTS.md sentences verbatim, so any future rewording of the address contract will break the test even when the semantics are preserved. This matches the file's existing assertion style and is deliberate regression coverage, so no change is needed.
  • ℹ️ tests/fm-cd-pretool-check.test.sh:438 - The shellcheck-absent guard reports the test as passing when shellcheck is not installed, which silently skips lint verification on machines without it. This mirrors the identical pre-existing pattern in tests/fm-arm-pretool-check.test.sh:531, so it is consistent repo style rather than a new weakening.

🔧 Fix: accept captain-shared.md as address-preference source
✅ Re-checked - no issues remain.

✅ **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"
  • Full suite baseline for t in tests/*.test.sh; do bash $t; done (ran successfully before this session)
  • bash tests/fm-captain-translation-contract.test.sh including the new test_chat_address_respects_private_preference (all ok)
  • bash tests/fm-brief.test.sh, bash tests/fm-cd-pretool-check.test.sh, bash tests/fm-sessionstart-nudge.test.sh (all ok)
  • Negative regression check: temporarily appended the old 'Address the user as "captain"' mandate to AGENTS.md and re-ran the contract test — it failed with not ok - AGENTS.md reintroduced a mandatory captain salutation, then restored AGENTS.md (clean git status)
  • Repo-wide grep for surviving mandatory-salutation language (Address the user as, Captain,, zero direct address) — only internal role-vocabulary mentions remain
  • End-to-end: ran real bin/fm-session-start.sh in a sandbox FM_HOME with data/captain.md stating a preferred name (Oz, no salutation) and captured the context digest showing the preference injected verbatim
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

s3-oz and others added 8 commits July 18, 2026 15:57
…t-nudge test

The no-mistakes gate harness exports NO_MISTAKES_GATE=1, which correctly
silences the nudge, so the genuine-primary assertions failed when the
suite ran under the gate. Clear the variable at the top of the test;
test_gate_env_is_silent still sets it explicitly to cover that path.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.

1 participant