Skip to content

fix(fleet): remove remaining agent-specific couplings - #74

Merged
LeTuR merged 6 commits into
mainfrom
claude/fleet-gitignore-config-rgb2e3
Sep 12, 2026
Merged

fix(fleet): remove remaining agent-specific couplings#74
LeTuR merged 6 commits into
mainfrom
claude/fleet-gitignore-config-rgb2e3

Conversation

@LeTuR

@LeTuR LeTuR commented Sep 12, 2026

Copy link
Copy Markdown
Contributor

Intent

Squash PR #74 to one commit reauthored as LeTuR (dropping the +claude.ai address), then finish what it started on two fronts the operator named as hard requirements.

FRONT 1 - fleet MUST be tool- and agent-agnostic. #74 moved the auto-merge allowlist, the publish method and the worker agent out of tracked code into gitignored operator config. A review against scripts/lib/forge.py's bar (an interface, two implementations, and selftests that DRIVE the second with the first's CLI a tripwire on PATH) found the seams asserted but not exercised, plus four remaining couplings, all fixed here:

  • extension.toml.in carried a literal agent = "claude", so a fresh clone launched the Mission Control lead on one vendor's agent regardless of what its operator runs. It is now a LEAD_AGENT placeholder rendered by scripts/install-extension.sh from the same AGENT setting in orchestration/agent.conf that every worker spawn reads, alongside the existing REPO_PATH and LEAD_GLYPH. Unset still renders thurbox's stock 'claude' because thurbox must be given a name; that is a fallback, not a coupling.
  • scripts/lib/fleet_status.py had FUEL_PROVIDER = os.environ.get(...) or "claude" - the last vendor literal in code. It now reads the operator's agent.conf (FUEL_PROVIDER, else AGENT as its own provider name), and with none named reads whichever provider holds a credential instead of one this repo picked. Three stale docstrings that told a reader fleet only works with Claude were corrected.
  • scripts/session-trust.sh refused any agent not in its per-agent dialog table and sent nothing. That refusal is correct (a bare Enter into Claude Code's dialog exits the agent) but had no escape hatch, so refuel restarted a worker on an untaught agent and never typed the brief into it. TRUST_SIGNATURE and TRUST_KEYS in agent.conf now teach it one, TRUST_KEYS=none declares no dialog, and with neither set it still refuses rather than guessing. This bug was found BY the new test below.
  • stale prose in FLEET.md and .agents/skills/fleet-queue/SKILL.md still said refuel reads the 'claude' account alone.
    The tests are the point, not the assertion: queue-selftest.sh gains 12b, which re-runs the whole of section 12 for 'nova' - an agent with no entry anywhere in the code, whose banner, transcript layout, provider and trust dialog all come from agent.conf, with CLAUDE_CONFIG_DIR unset and the quota document naming a different provider, so any built-in literal on that path fails the section. fleet-status-selftest.sh gains 6d, which drives the fuel reader over three answers to 'which provider' with a grep over fleet_status.py as the tripwire. Two existing quota-axi stubs gained the 'auth' branch they had been relying on the literal to paper over. check.sh's automerge check now also rejects the two new trust keys in the tracked example.
    Deliberate and NOT to be flagged: .no-mistakes.yaml is this repo's own gate config and correctly names its own tools; orchestration/session-profiles.yaml's 'claude' profile, session-trust.sh's per-agent table and queue.py's AGENT_LIMIT_SIGNALS are documented single-adapter tables with explicit refusal for anything absent, which is the intended design; .claude/skills, .claude/settings.json and CLAUDE.md are file paths this repo genuinely has.

FRONT 2 - per-session context is the product. Measured in words: the always-loaded set (AGENTS.md + FLEET.md + CLAUDE.md) went 5,623 at 59879e1 -> 6,854 at this PR's base -> 7,026 at its head -> 6,344 now; the five SKILL.md files went 21,312 -> 24,028 -> 23,748 -> 23,762. The growth this branch was asked about is mostly not this branch: 16 commits landed on main between 59879e1 and the PR base and account for +1,231 of the +1,403 always-loaded words and all +2,716 of the skills. Two cuts here, both against AGENTS.md's own stated rule that a file points at the authority rather than repeating it: AGENTS.md's numbered loop restated fleet-queue SKILL.md sections 1-7 and queue.sh's header in full and is an index again, and FLEET.md's loop restated AGENTS.md's inside the same context window since both are always loaded, so it keeps what is the lead's and defers the mechanics. Every rule was checked to still exist in the file that owns it before its copy was removed - the explicit constraint was not to lose a rule while cutting its restatement, and FLEET.md's 'How you report' accuracy rules were deliberately left untouched.

Deliberate constraints from the operator: do not rewrite .no-mistakes.yaml to avoid naming its own tools; do not cut FLEET.md's How-you-report accuracy rules; squash-merge only, so the PR title must match the squashed commit subject; force-push only this branch. ./scripts/check.sh exits 0 locally with every check included.

What Changed

  • Remove the last hardcoded "claude" literals: extension.toml.in/scripts/install-extension.sh now render the Mission Control lead's agent from the AGENT setting in orchestration/agent.conf (falling back to thurbox's stock "claude" only when unset), and scripts/lib/fleet_status.py reads FUEL_PROVIDER (or AGENT) from agent.conf instead of an environment-variable/code default, with the stale Claude-only docstrings and prose in FLEET.md/fleet-queue/SKILL.md corrected.
  • Give scripts/session-trust.sh an escape hatch — TRUST_SIGNATURE/TRUST_KEYS in agent.conf — so it can be taught an agent outside its built-in per-agent dialog table (or told TRUST_KEYS=none) instead of always refusing; add the tracked, name-nothing orchestration/agent.example.conf, auto-merge.example.conf, and publish.example.conf, and reject the new trust keys in check.sh's automerge check when they appear in the tracked example.
  • Extend queue-selftest.sh (section 12b) and fleet-status-selftest.sh (section 6d) to drive the new agent-agnostic paths end-to-end for a fictitious "nova" agent defined only in config; trim AGENTS.md/FLEET.md's queue-loop sections to point at fleet-queue/SKILL.md and queue.sh instead of restating their steps, and update the other SKILL.md files, .gitignore, and README.md accordingly.

Risk Assessment

✅ Low: This fix round only replaces a source-grep tripwire in fleet-status-selftest.sh with a genuine behavioral test and adds two missing entries to README.md; both changes were verified against the code they describe and match round-1's requested fixes, with no new functional risk. Spot-checks of the broader diff's front1/front2 claims (fuel provider discovery, lead-agent rendering/validation, session-trust escape hatch, automerge check coverage, and the AGENTS.md/FLEET.md dedup with rules relocated intact into fleet-queue/SKILL.md) all held up under inspection.

Testing

Baseline ./scripts/check.sh (full suite) already passed; on top of that I ran the three checks most directly tied to the stated intent — status, queue, and automerge — and all three passed cleanly, including the specific new behavioral tests the PR describes (fleet-status-selftest.sh §6d's credential-discovery test with a fictitious provider, and queue-selftest.sh §12b's full second-agent 'nova' run exercising the trust-dialog escape hatch and transcript/quota seams). Manual inspection confirmed the four named code fixes (extension.toml.in's LEAD_AGENT placeholder, fleet_status.py's operator-driven provider lookup, session-trust.sh's TRUST_SIGNATURE/TRUST_KEYS, and the automerge check covering the two new keys) are present as described, the previously-flagged source-grep tripwire was removed and replaced with a behavioral assertion, README.md's conf list is complete, and the always-loaded doc word count (6,344) matches the intent's claimed figure exactly. No test failures, no missing evidence, no findings.

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

🔧 **Rebase** - 6 issues found → auto-fixed ✅
  • ⚠️ .agents/skills/fleet-queue/SKILL.md - merge conflict rebasing onto origin/claude/fleet-gitignore-config-rgb2e3
  • ⚠️ AGENTS.md - merge conflict rebasing onto origin/claude/fleet-gitignore-config-rgb2e3
  • ⚠️ FLEET.md - merge conflict rebasing onto origin/claude/fleet-gitignore-config-rgb2e3
  • ⚠️ orchestration/agent.example.conf - merge conflict rebasing onto origin/claude/fleet-gitignore-config-rgb2e3
  • ⚠️ scripts/check.sh - merge conflict rebasing onto origin/claude/fleet-gitignore-config-rgb2e3
  • ⚠️ scripts/lib/fleet_status.py - merge conflict rebasing onto origin/claude/fleet-gitignore-config-rgb2e3

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

🔧 **Review** - 2 issues found → auto-fixed ✅
  • ⚠️ scripts/fleet-status-selftest.sh:650 - The new §6d tripwire ends with if grep -qiE 'claude|anthropic' scripts/lib/fleet_status.py; then fail .... This is a source-content-only assertion (grepping implementation source for a vendor string) rather than an observation of behavior — the exact anti-pattern the test-quality rule forbids ('Never add a test whose only evidence is that it ... greps ... implementation source code and finds or omits particular strings'). It sits alongside genuinely behavioral assertions in the same section (three real invocations of fleet-status.sh / probe_fuel() over different agent.conf settings), so it isn't load-bearing for the section's real coverage, but it is itself a new source-grep assertion and should be removed or replaced with a behavioral check (e.g., asserting probe_fuel()/authenticated_providers() never falls back to a hardcoded vendor on a machine with no operator config and no 'claude' credential, which the three cases above already partially cover).
  • ℹ️ README.md:126 - The new paragraph lists the operator's gitignored settings and their tracked .example counterparts (owners.txt, voice.conf, session-glyphs.conf, auto-merge.conf) but omits the two new ones added by this same change, orchestration/publish.conf and orchestration/agent.conf, even though both are documented in .gitignore right above with the same rationale. Minor completeness gap, not a functional issue.

🔧 Fix: Replace grep tripwire with behavioral test; add missing README conf entries
✅ Re-checked - no issues remain.

✅ **Test** - passed

✅ No issues found.

  • ./scripts/check.sh
  • ./scripts/check.sh status — runs scripts/fleet-status-selftest.sh including new §6d, which stubs quota-axi with a fictitious 'glorbnak' provider (never named in fleet_status.py) plus AGENT/FUEL_PROVIDER settings from a relocated agent.conf, proving the fuel reader follows the operator's config/credentials rather than a 'claude' literal; passed
  • ./scripts/check.sh queue — runs scripts/queue-selftest.sh including new §12b, which re-runs the full refuel/trust/transcript flow of §12 for 'nova' (an agent absent from AGENT_LIMIT_SIGNALS and every code table) driven entirely by a relocated agent.conf (AGENT, FUEL_PROVIDER, LIMIT_BANNER, TRANSCRIPT_DIR, TRUST_SIGNATURE, TRUST_KEYS), with CLAUDE_CONFIG_DIR unset and a quota document naming a different provider as a tripwire; passed (queue: ok)
  • ./scripts/check.sh automerge — validates orchestration/auto-merge.example.conf, publish.example.conf and agent.example.conf ship no operator-specific values, now including the new TRUST_SIGNATURE/TRUST_KEYS keys; passed
  • Manual inspection: extension.toml.in line 368 uses agent = "__LEAD_AGENT__" rendered by scripts/install-extension.sh from orchestration/agent.conf's AGENT (falling back to thurbox's stock 'claude' only when unset), confirming the described placeholder wiring
  • Manual inspection: scripts/check.sh's check_automerge() now checks TRUST_SIGNATURE and TRUST_KEYS in the AGENT key loop, confirming the fixed automerge check
  • wc -w AGENTS.md FLEET.md CLAUDE.md — confirmed the always-loaded doc set is 6,344 words, matching the intent's stated post-cut figure exactly
  • Confirmed the previously-flagged grep tripwire (grep -qiE 'claude|anthropic' scripts/lib/fleet_status.py) is no longer present in scripts/fleet-status-selftest.sh, replaced by the behavioral §6d discovery test
  • Confirmed README.md now documents both orchestration/publish.conf and orchestration/agent.conf alongside the other gitignored settings
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

LeTuR and others added 6 commits September 11, 2026 23:19
Where fleet may merge was a literal in scripts/lib/queue.py, so naming a
repository meant committing it to a PUBLIC, agnostic repo — and every clone
inherited the last operator's merge rights over repositories that are not
theirs. Four such commits is what it took to notice.

The list is now orchestration/auto-merge.conf: the operator's own file,
gitignored beside owners.txt, voice.conf and session-glyphs.conf, read on
every shepherd pass so an edit needs no reinstall and no restart.
orchestration/auto-merge.example.conf is the tracked copy and names NOTHING,
so a fresh clone merges nowhere until its operator says otherwise, and
shepherd says so by name rather than producing the same silence a repo nobody
listed would. FLEET_AUTO_MERGE_REPOS still REPLACES the set.

Nothing about the merge gates moved: the head branch must live in that repo,
the body must carry a no-mistakes attestation for its CURRENT head, every
check must have concluded and passed, the forge must call it mergeable, and
whoever opened it must be able to push there. Squash stays the only method.

It also closes a latent bug the old comment named: the literal was never
parsed, so a bare slug written into it would have matched nothing, refused
nothing and failed no test. One parser now serves both sources, so the file
can no more carry an unqualified entry than the environment can.

Gated against regression. check.sh gains an `automerge` check that fails if
the tracked copy ever names a repository or if a fresh clone would read a
non-empty set — proved to fail by adding an entry. queue-selftest.sh's 9i and
9j, which existed to assert that two named operator repositories were on the
list, become one section about the mechanism: a repository the file names
merges on exactly the old gates, an unvetted one is still handed back, a bare
slug is refused in the file as in the environment, no file at all merges
nothing and says which file would name one, and the tracked copy names none.
The run reads a throwaway list via FLEET_AUTO_MERGE_ROOT so its verdicts no
longer depend on whose machine it ran on.

The same pass de-personalises what was left: an operator's project name in a
--condition help example and their GitHub login in the shepherd fixtures.

./scripts/check.sh exits 0 locally, every check included.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hp6M6Hw8TYWtJHGfSuwd4F
A review of all five skills against two standards this repo already sets:
fleet-queue §2's own style contract (cut rhetorical contrast and persuasion,
keep every measured fact) and AGENTS.md's rule that a file points at the
authority rather than repeating it.

Audited for defects first — every referenced path, every queue.sh subcommand
and every § cross-reference resolves, so nothing here is a correctness fix.
What was wrong was density and one ordering bug.

The structural cuts, which are where the value is:

- fleet-pane §1 described what the pane RENDERS — fuel bars, the ⇡ row's
  composition, the order a narrow column drops parts in — which
  interface/fleet_queue.lua's header owns and the skill's own opening sentence
  says it owns. Kept what an agent acts on (the FUEL_GLYPH shear remedy, that
  `green` is not the ok colour, the name-probe that explains most of §7) and
  pointed at the file for the rest.
- fleet-onboarding step 6 restated fleet-pane §4 almost verbatim: the same
  script flags, the same guarantees, the same guard explanation. It now keeps
  its own four-option ask and the block to print, and delegates the mechanics.
- fleet-queue §5d repeated three of AGENTS.md's four reconciler bullets,
  including the same incident and duration — and AGENTS.md is always in the
  lead's context, so that was duplication inside one context window. Kept the
  two points that are about the lead rather than the loop.
- update-fleet §5b was mine from the previous commit and was a 25-line section
  about a one-time migration in a skill about routine updates. Now six lines
  and a command.

One real defect: fleet-queue's §5a sat AFTER §5d and at a different heading
level, so §5d's own title referenced a section the reader had not reached.
Moved into order, levelled to ###.

fleet-queue's frontmatter description named no command, so "run shepherd" or
"what is blocked" had only the skill's name to match on. It now lists the
queue's verbs.

Every edit was checked against a fact extractor that diffs code spans, fenced
blocks, flags, dates and quantities before and after. thurbox-session and
update-fleet lost none; the rest lost only rendering internals the lua header
owns and strings that moved to the skill that owns them.

Measured: 24,038 words to 22,822, and markdown issues 23 to 10 with every
remaining one pre-existing and deliberate. The rhetorical-construction count
barely moved, and that is the honest result rather than a miss — in this
domain most `is not` clauses are correcting a belief the reader would
otherwise hold, which is exactly what the style contract permits.

./scripts/check.sh exits 0, every check included.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hp6M6Hw8TYWtJHGfSuwd4F
…t code

queue.py's own comment said the publish methods are ARTIFACT SHAPES and not
tool names, "which is the whole of fleet's agnosticism, and it lasts exactly as
long as nothing branches on it". One of the three shapes was `no-mistakes` —
one operator's pipeline — the code branched on it, the attestation format was
that tool's, and tracked POLICY.md shipped it as every clone's default. The
same sweep found the agent hardcoded the same way.

THE PUBLISH SEAM. The third shape is `attested`: a pull request whose body
carries an attestation for the commit that would merge. What an attestation
LOOKS like is ATTESTATION_MARKER in the operator's gitignored
orchestration/publish.conf, so fleet reads their pipeline's format rather than
dictating one; PIPELINE_COMMIT_PREFIX is theirs too and is empty by default,
so fleet never claims to know which commits a pipeline wrote. `no-mistakes` is
accepted wherever a method is read and means `attested`, so existing task
records load unedited. The default method and its free-text command moved out
of tracked POLICY.md into the same conf — a block still in POLICY.md is
honoured and warns once on stderr, so a live fleet keeps working while it
moves. The fixer brief now quotes the task's own recorded `how` instead of a
command this repo picked.

THE AGENT SEAM. `--agent claude` is gone from all three dispatch sites: with
no AGENT in orchestration/agent.conf, session create keeps thurbox's own
agents.toml default, which is where that answer already lived. refuel no longer
gates on a literal `claude` window — it takes FUEL_PROVIDER, or maps the agent
in hand through AGENT_PROVIDERS (identity by default), and reports
`undetermined` — which restarts nothing — when tasks disagree or when
`quota-axi auth` holds no credential for the derived name. Fleet carries no
provider list; it asks quota-axi, so the set grows with the tool.

HOW AN AGENT SAYS IT RAN OUT is now a table with one entry per agent fleet has
actually WATCHED do it — `claude` today — the same shape as session-trust.sh's
per-agent dialog table. An agent with no entry answers `undetermined` rather
than being guessed at, and LIMIT_BANNER / TRANSCRIPT_DIR teach fleet one
without a code change. Nothing is matched here that nobody observed: a guessed
pattern restarts a live worker mid-turn.

Both tracked example files name nothing. check.sh's `automerge` check now
fails any tracked setting that carries a repository, a tool, a vendor or an
agent, and reads every occurrence rather than the first — proved by appending
a leak below a correct line and watching it fail.

queue-selftest.sh gets its own throwaway publish and agent settings so its
verdicts stop depending on whose machine ran it, and section 8h becomes the
unconfigured-clone proof: no POLICY frontmatter and no publish.conf defaults to
`pr` with no tool named anywhere in the brief, the shipped example names no
METHOD but `pr` and no HOW at all, the three methods are artifact shapes, and
the retired spelling still resolves.

./scripts/check.sh exits 0, every check included.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hp6M6Hw8TYWtJHGfSuwd4F
… config

Three things fleet treated as facts about the world were facts about one
operator: which repositories it may merge, which pipeline produces a
publishable artifact, and which agent its workers run. All three were
literals in tracked code, in a PUBLIC repo, so every clone inherited the
last operator's answers.

THE MERGE SET. Where fleet may merge was a literal in scripts/lib/queue.py,
so naming a repository meant committing it — and every clone inherited merge
rights over repositories that are not theirs. It is now
orchestration/auto-merge.conf, the operator's own gitignored file beside
owners.txt and session-glyphs.conf, read on every shepherd pass so an edit
needs no reinstall and no restart. The tracked example names NOTHING, so a
fresh clone merges nowhere until its operator says otherwise, and shepherd
says which file would name one rather than producing the same silence as a
repo nobody listed. FLEET_AUTO_MERGE_REPOS still REPLACES the set. It also
closes a latent bug the old comment named: the literal was never parsed, so
a bare slug written into it would have matched nothing and failed no test.
One parser now serves both sources.

THE PUBLISH SEAM. queue.py's own comment said the publish methods are
ARTIFACT SHAPES and not tool names, "which is the whole of fleet's
agnosticism, and it lasts exactly as long as nothing branches on it". One of
the three shapes was `no-mistakes` — one operator's pipeline — the code
branched on it, the attestation format was that tool's, and tracked
POLICY.md shipped it as every clone's default. The third shape is now
`attested`: a change request whose body carries an attestation for the
commit that would merge. What an attestation LOOKS like is
ATTESTATION_MARKER in the operator's gitignored orchestration/publish.conf,
so fleet reads their pipeline's format rather than dictating one;
PIPELINE_COMMIT_PREFIX is theirs too and is empty by default, so fleet never
claims to know which commits a pipeline wrote. `no-mistakes` is accepted
wherever a method is read and means `attested`, so existing task records
load unedited. The default method and its free-text command moved out of
tracked POLICY.md into the same conf — a block still in POLICY.md is
honoured and warns once on stderr, so a live fleet keeps working while it
moves.

THE AGENT SEAM, in all four places an agent's name reached code:

- DISPATCH. `--agent claude` is gone from all three spawn sites. With no
  AGENT in orchestration/agent.conf, session create keeps thurbox's own
  agents.toml default, which is where that answer already lived.
- THE LEAD ITSELF, which the first pass missed: extension.toml.in carried
  `agent = "claude"` in tracked configuration, so a fresh clone launched
  Mission Control on one vendor's agent whatever its operator runs. It is now
  `__LEAD_AGENT__`, rendered by install-extension.sh from the same AGENT
  setting every worker reads, next to `__REPO_PATH__` and `__LEAD_GLYPH__`;
  unset still renders thurbox's stock `claude`, because thurbox must be given
  a name.
- REFUEL. It no longer gates on a literal `claude` window: FUEL_PROVIDER, or
  the agent in hand mapped through AGENT_PROVIDERS (identity by default), and
  `undetermined` — which restarts nothing — when tasks disagree or quota-axi
  holds no credential for the derived name. Fleet carries no provider list; it
  asks quota-axi, so the set grows with the tool. fleet_status.py's own
  `FUEL_PROVIDER = ... or "claude"` was the last literal and is gone: the
  screen leads with the operator's provider, and with none named reads
  whichever one holds a credential rather than one this repo picked.
- THE TRUST DIALOG, which §12b below found. session-trust.sh keeps one row
  per agent fleet has watched and REFUSES an unlisted one — correctly, since
  a guessed Enter exits Claude Code — but had no way for an operator to teach
  it theirs. So refuel restarted a worker on an untaught agent and never typed
  the brief into it. TRUST_SIGNATURE and TRUST_KEYS in agent.conf now teach it
  one, `TRUST_KEYS=none` says there is no dialog, and with neither set it
  still refuses rather than guessing.

How an agent says it RAN OUT stays a table with one entry per agent fleet has
actually watched do it; an agent with no entry answers `undetermined` rather
than being guessed at. A guessed pattern restarts a live worker mid-turn.

DRIVEN, NOT ASSERTED. scripts/lib/forge.py is this repo's standard — an
interface, two implementations, and selftests that drive the second with the
first's CLI a tripwire on PATH. The agent seam did not meet it: §12 ran
`claude` end to end, so every built-in would have passed. queue-selftest.sh
§12b now runs the whole of §12 again for `nova`, an agent with no entry
anywhere in the code: the banner, the transcript layout, the provider and the
trust dialog all come from agent.conf, CLAUDE_CONFIG_DIR is unset and the
quota document names another provider, so a literal reached for on that path
fails the section. fleet-status-selftest.sh §6d does the same for the fuel
reader over three answers to "which provider" — the operator's agent, an
explicit pin, and an unconfigured clone — with a grep over fleet_status.py
as the tripwire.

check.sh's `automerge` check fails any tracked setting carrying a repository,
a tool, a vendor or an agent, reading every occurrence rather than the first,
and now covers the two trust keys.

CONTEXT, WHICH IS THE PRODUCT HERE. Measured in words, over the files a lead
loads every session:

                       59879e1    #74 base    #74 head       now
  AGENTS+FLEET+CLAUDE     5,623       6,854       7,026     6,344
  the five SKILL.md      21,312      24,028      23,748    23,762

The growth this branch was asked about is mostly not this branch: 16 commits
landed on main between 59879e1 and its base, and they are +1,231 of the
+1,403 always-loaded words and all +2,716 of the skills. The docs commit here
was -280 on the skills and +172 on the always-loaded set.

Two cuts, both against AGENTS.md's own rule that a file points at the
authority rather than repeating it. AGENTS.md's numbered loop restated
fleet-queue's §1-§7 and queue.sh's header in full; it is an index again, with
every rule checked to still exist in the file that owns it before its copy
went. FLEET.md's loop restated AGENTS.md's inside the same context window —
both are always loaded — so it keeps what is the lead's and defers the
mechanics. The forge bullet stopped summarising a docstring that says it owns
the subject. Net: the always-loaded set is 682 words below this PR's head and
510 below its base.

./scripts/check.sh exits 0, every check included.
@LeTuR LeTuR changed the title fix(queue): take the auto-merge allowlist out of tracked code fix(fleet): remove remaining agent-specific couplings Sep 12, 2026
@LeTuR
LeTuR merged commit 285199c into main Sep 12, 2026
11 checks passed
@LeTuR
LeTuR deleted the claude/fleet-gitignore-config-rgb2e3 branch September 12, 2026 09:57
LeTuR added a commit that referenced this pull request Sep 12, 2026
…78)

## Intent

Fleet task queue-check-alias/01-01-accept-legacy-alias.
`./scripts/queue.sh check` rejected every task record whose publish
method is `no-mistakes` — the method #74 renamed to `attested` — even
though AGENTS.md's contract is that `no-mistakes` is still accepted
wherever a method is read and means `attested`. Archived records are
never rewritten, so the control plane's gate stayed red for good (23
problems on the live queue). Goal: make check accept the alias by
normalising it to attested, and still reject a genuinely invalid method.
Hard constraints from the brief: fix the validator, never rewrite the
operator's gitignored records; do not widen the accepted set beyond
attested, pr, push plus the no-mistakes alias; keep the alias in ONE
place (PUBLISH_ALIASES / publish_method() in scripts/lib/queue.py)
rather than a second spelling in the validator — that drift is the bug.
Tests written first in scripts/queue-selftest.sh and seen failing on the
old code for exactly that reason. Decision: an aliased method passes
SILENTLY, not with a notice — the contract says it is accepted, nothing
writes the old word any more, and the records carrying it are archived
history nobody can or should edit, so a notice would be permanent noise
with no action to take. Deliberate scope extension within the same bug
class: two more readers bypassed publish_method() the same way and were
fixed with tests that failed first — task_publish() let a no-mistakes
record fall back to the operator default (a silent downgrade to pr on a
fresh clone), and `queue.sh add --publish no-mistakes` was refused by
argparse choices despite the code comment saying it works (fixed with
type=publish_method, which argparse applies before choices, so the
record stores attested). Reach: queue_root() anchors to the checkout the
script lives in, so a worker worktree, a pipeline worktree or CI
validates its own empty queue — this bug only turned check.sh red on the
control-plane checkout itself, not in worker gates.

## What Changed

- `cmd_check()` in `scripts/lib/queue.py` now normalizes a record's
`publish.method` through `publish_method()` before validating it against
`PUBLISH_METHODS`, so archived records still carrying the retired
`no-mistakes` word pass `queue.sh check` instead of being flagged as
invalid, while a genuinely unknown method is still rejected.
- `task_publish()` normalizes the task's declared publish method through
`publish_method()` before checking membership in `PUBLISH_METHODS`, so a
record written as `no-mistakes` resolves to `attested` instead of
silently falling back to the operator's default publish method.
- `queue.sh add --publish` now uses `type=publish_method` on the
argparse argument (applied before `choices`), so passing `--publish
no-mistakes` is accepted and stored as `attested` instead of being
rejected by argparse.
- `scripts/queue-selftest.sh` adds test coverage for all three fixes:
`check` accepting the `no-mistakes` alias while still rejecting an
unknown method, `add --publish no-mistakes` succeeding and recording
`attested`, and `task_publish`/`show` resolving a `no-mistakes`-tagged
record to `attested` rather than falling back to `pr`.

## Risk Assessment

✅ Low: Small, well-scoped fix: all three readers of the publish method
(cmd_check, task_publish, --publish argparse) now route through the
single publish_method()/PUBLISH_ALIASES normalisation, the validator
still rejects genuinely unknown methods, the alias is accepted silently
as required, no records are rewritten, and new tests reproduce the
original failure (red check on an aliased record) and pass after the
fix.

## Testing

Baseline `./scripts/check.sh` had already passed on the target commit.
To directly test the user intent, I ran the full
`scripts/queue-selftest.sh` on the target commit (9e359dc) and confirmed
all six new alias-regression assertions pass: `check` silently accepts a
task record carrying the retired `no-mistakes` method (normalizing it to
`attested` with no notice, as the intent specifies), still rejects a
genuinely bogus method (`carrier-pigeon`) without also flagging the
alias, `queue.sh add --publish no-mistakes` succeeds and persists
`attested` (never the old word), and `task_publish`/`show` read a
`no-mistakes`-tagged record as `attested` rather than silently
downgrading to the clone's `pr` default. To prove this is a real
regression test and not a tautology, I copied that same test file into a
throwaway worktree checked out at the pre-fix base commit (a81bf77) and
reran it: it failed for exactly the reasons described in the brief —
`check` reported `publish method 'no-mistakes' is not one of attested,
pr, push`, `add --publish no-mistakes` was refused by argparse's
`choices`, and `show` reported the record as verified `pr` instead of
`attested`. This end-to-end before/after comparison demonstrates the fix
resolves the reported control-plane gate failure without widening the
accepted method set or duplicating the alias spelling outside
`scripts/lib/queue.py`. The worktree was left clean; the temporary
comparison worktree was removed.

## Pipeline

Updates from [git push
no-mistakes](https://github.com/kunchenguid/no-mistakes)

<!-- no-mistakes-pipeline-attestation:v1
{"head_sha":"9e359dc6a5dd591d62ad07106aab2f51eedf578e","steps":[{"step":"intent","status":"completed"},{"step":"rebase","status":"completed"},{"step":"review","status":"completed"},{"step":"test","status":"completed"},{"step":"document","status":"completed"},{"step":"lint","status":"completed"},{"step":"push","status":"completed"},{"step":"pr","status":"running"},{"step":"ci","status":"pending"}]}
-->

<details>
<summary>✅ **intent** - passed</summary>

✅ No issues found.
</details>

<details>
<summary>✅ **Rebase** - passed</summary>

✅ No issues found.
</details>

<details>
<summary>✅ **Review** - passed</summary>

✅ No issues found.
</details>

<details>
<summary>✅ **Test** - passed</summary>

✅ No issues found.
- `./scripts/check.sh`
- <code>`./scripts/queue-selftest.sh` on target commit 9e359dc (full
pass, ~147s)</code>
- <code>`./scripts/queue-selftest.sh` (target&#39;s version of the file)
run against pre-fix queue.py at base commit a81bf77 in a temporary
worktree — reproduced the exact 5 reported failures, confirming the
regression test is genuine</code>
- <code>Manual inspection of `PUBLISH_ALIASES`/`publish_method()` in
scripts/lib/queue.py:388-395 confirming the alias is defined once and
routed through by `cmd_check`, `task_publish()`, and the `--publish`
argparse `type=`</code>
</details>

<details>
<summary>✅ **Document** - passed</summary>

✅ No issues found.
</details>

<details>
<summary>✅ **Lint** - passed</summary>

✅ No issues found.
</details>

<details>
<summary>✅ **Push** - passed</summary>

✅ No issues found.
</details>
LeTuR added a commit that referenced this pull request Sep 13, 2026
…84)

## Intent

Fleet task remote-host-support/02-02-psmux-and-windows. The operator
asked, verbatim: 'Fleet disaptching to a psmux should work, maybe
running fleet on windows should work also ?'. The brief required a
decision with evidence and the smallest change that makes it real:
verify each of the three couplings the old refusal cited (probes, brief
copy, result fetch) against the real Windows host windows-hp (PowerShell
5, psmux 3.3.6), answer A (dispatch TO Windows) and B (fleet RUNNING on
Windows) separately, and build A only if contained, as a seam per
coupling with the POSIX implementation unchanged and a PowerShell one
beside it, driven by a test to the standard scripts/lib/forge.py set
(second implementation driven with a tripwire on PATH), proven against
windows-hp. Hard constraints: do not regress the POSIX path (existing
remote selftests unchanged), do not weaken the refusal to make the error
go away (a half-working spawn would leave a worktree on a Windows box),
no new dependency (no PowerShell module, Cygwin or busybox), say what
could not be tested. B was to be answered, not implemented. Findings and
decisions: all three couplings were real (printf/[ -d ] do not exist in
PowerShell; cat > path writes nothing; PS5 stdin/stdout go through the
ibm850 code page so non-ASCII does not survive), plus a fourth the
refusal never named: the trust handoff (psmux captures panes with spaces
removed, so session-trust.sh's signature never matched; Claude Code
2.1.247 on Windows pre-selects 'Yes', so the table's 'down enter' chose
'No, exit' and the agent exited - observed live). Built: a HostShell
seam in scripts/lib/queue.py selected by hosts.toml's multiplexer (the
same proxy thurbox uses, HostDef::is_windows): PosixShell issues
byte-for-byte the previous commands including the /bin/sh -lc login
wrap; PowerShell sends UTF-16LE base64 -EncodedCommand (thurbox's own
host_powershell_c rule, because -Command quoting is rewritten by the
host's default shell), carries file bytes as base64 both directions,
joins paths with backslash, and runs the forge probe's ssh key check via
bounded Start-Process because inline '& ssh' hangs inside an ssh session
on Windows (measured: & ssh, ssh -n and cmd /c <NUL all hung past 45s).
Any multiplexer other than tmux/psmux is still refused by name at add; a
Windows box that hosts.toml calls tmux is still caught by the first
probe. Deliberately, exit codes carry no meaning beyond zero/non-zero
and ssh's 255, because Windows folds a PowerShell exit code; answers are
words on stdout sharing one line protocol with the POSIX scripts.
reportable() strips OpenSSH's post-quantum advisory and PowerShell
CLIXML / error trailer so failure reasons name the real error - the only
POSIX-visible change is that error text. session-trust.sh now matches
whitespace-insensitively and sends Enter alone when the selector already
sits on the accepting option; a dialog defaulting to No still gets 'down
enter'. Tests in scripts/queue-selftest.sh §11(i)/(j): a fake Windows
host (windows-host.py in the ssh stub) decodes every -EncodedCommand,
moves real bytes into a fake C:\ tree, and logs any non-encoded (POSIX)
command as a tripwire, with a claim proving the tripwire fires; §11(a-h)
unchanged except the refusal row now uses oddbox (unknown multiplexer)
instead of winbox. The Windows reap claim holds its topic open with a
condition-blocked task because of a pre-existing, deliberately unfixed
bug: a topic whose last remote task lands while its host is down
archives in the same pass and reap then never revisits the kept session.
Known limits, accepted: thurbox gates hook status off on psmux hosts so
Windows workers stay 'unreported' and reap never releases them;
windows-hp has no GitHub credentials so no full worker run was possible;
GitLab origin, non-22 ssh port and PowerShell 7 on Windows are untested.
B answered in docs/PR only: yes with WSL, native is a port of 22 bash
scripts plus the setsid supervisor. Docs updated: scripts/queue.sh
header, fleet-queue and thurbox-session skills. This run exists to
attest PR #80 (task was queued as 'pr' by the #74 bug; the operator's
standing method is attested).

## What Changed

- Added a `HostShell` seam in `scripts/lib/queue.py` (`PosixShell` plus
a new `PowerShell` implementation) so remote dispatch's three probes,
brief copy, and result fetch each speak the shell a host actually runs:
POSIX for a `tmux` host (byte-for-byte unchanged, including the `/bin/sh
-lc` login wrap), PowerShell for a `psmux` (native Windows) host via
UTF-16LE base64 `-EncodedCommand`, base64 file transfer in both
directions, backslash path joining, and a bounded `Start-Process` SSH
key check to avoid an inline `& ssh` hang observed on Windows.
`host_entry()` now refuses any multiplexer other than `tmux`/`psmux` by
name instead of refusing every non-`tmux` host.
- Added `reportable()` to strip OpenSSH's post-quantum advisory and
PowerShell's CLIXML/error-trailer noise from probe output so reported
failures name the real error, on both POSIX and Windows hosts.
- Updated `scripts/session-trust.sh` to match trust-dialog signatures
with whitespace stripped from both sides (psmux captures panes with
spaces removed) and to send `Enter` alone whenever the dialog's selector
is already on the accepting option, instead of always sending the fixed
`down enter` sequence that would otherwise pick "No, exit" on Claude
Code's Windows-defaulted dialog.
- Extended `scripts/queue-selftest.sh` with a fake Windows host
(decoding `-EncodedCommand`, writing into a fake `C:\` tree, and
tripwiring any non-encoded POSIX command) covering the new PowerShell
path, updated the existing refusal-row test to use an unknown
multiplexer name instead of assuming any non-`tmux` host is refused, and
updated `scripts/queue.sh`'s header comment plus the
`fleet-queue`/`thurbox-session` skill docs to describe the two-shell
behavior.

## Risk Assessment

✅ Low: The fix-round change (scripts/lib/queue.py:2515, switching to
[Console]::Out.Write) correctly bypasses PowerShell's console-width
formatter, resolving the line-wrap truncation risk flagged in round 1;
the rest of the HostShell seam, session-trust.sh's
whitespace-insensitive/selector-aware matching, and the behavioral
(non-string-matching) selftest coverage are consistent, well-scoped, and
match the stated intent with no regression to the POSIX path.

## Testing

Baseline ./scripts/check.sh (full gate, including the complete
queue-selftest.sh with its Windows/psmux dispatch and result-fetch
coverage) already passed with no regressions. Because that suite's fake
Windows-host stub doesn't model PowerShell's real non-interactive
line-wrapping and no PowerShell interpreter is available on this
machine, I wrote and ran a focused manual verification against the real
decode()/last_out_of() functions using the actual pre-fix and post-fix
read() output shapes and the suite's own realistic result.md fixture:
the pre-fix shape silently decodes to wrong bytes (the reported bug,
reproduced), and the post-fix shape decodes to the exact original bytes,
confirming the fix closes the gap for a realistic payload.

<details>
<summary>Evidence: Manual verification script (models pre-fix vs
post-fix PowerShell output shapes through the real
decode/last_out_of)</summary>

```text
#!/usr/bin/env python3
"""Manual verification for the psmux/Windows result-fetch line-wrap fix.

Real PowerShell 5, run non-interactively over an ssh exec channel (no pty),
cannot query a console width and falls back to a default host buffer width of
80 columns. A plain string emitted as the pipeline's *implicit* output goes
through the default formatter (Out-Default), which hard-wraps continuous text
(no whitespace to break on) at that width when writing it out. `[Console]::
Out.Write(...)` instead writes straight to the stdout stream, bypassing the
formatter/pipeline entirely, so it is never wrapped.

`scripts/lib/queue.py`'s `PowerShell.decode()` recovers the base64 payload
with `last_out_of(stdout)`, which keeps only the LAST non-empty line. Wrapped
output would silently discard every earlier chunk.

This script imports the real `last_out_of`/`PowerShell.decode` from the
target commit (unchanged across both commits) and feeds it two prepared
stdout strings that model what real PowerShell would hand back for the two
`read()` implementations:
  - pre-fix (commit 5efbde3): bare `[Convert]::ToBase64String(...)` as the
    script's last expression -> implicit pipeline output -> wrapped.
  - post-fix (commit 873c14c): `[Console]::Out.Write(...)` -> direct stream
    write -> not wrapped.

No real PowerShell interpreter is available on this machine (no pwsh/
powershell, and installing one is out of scope for this worktree), so the
wrapping step is a documented-behavior model, not a captured real PowerShell
run. It is not a substitute for running this against a real Windows host;
the operator's own PR notes that windows-hp validation already covered the
happy path with a small enough payload to not trigger the wrap.
"""
import base64
import sys

sys.path.insert(0, "scripts/lib")
import queue as q  # noqa: E402

BUFFER_WIDTH = 80

# The exact fixture queue-selftest.sh §11(i) writes as the Windows worker's
# result.md (a realistic ~140-byte real task result, non-ASCII included).
RESULT_MD = (
    "---\noutcome: shipped\n"
    "artifact: https://github.com/remote-owner/app/pull/4343\n---\n"
    "Built it on winbox — café ✓.\r\nWritten by Windows.\r\n"
).encode()


def wrapped_stdout(payload_b64: str) -> str:
    """What PowerShell's default formatter emits for a bare-expression string
    result when no real console width can be queried: hard-wrapped at 80
    columns, one physical line per chunk."""
    return "\n".join(
        payload_b64[i : i + BUFFER_WIDTH] for i in range(0, len(payload_b64), BUFFER_WIDTH)
    )


def direct_stdout(payload_b64: str) -> str:
    """What `[Console]::Out.Write(...)` emits: the raw string, one line."""
    return payload_b64


payload_b64 = base64.b64encode(RESULT_MD).decode("ascii")
print(f"fixture: {len(RESULT_MD)} raw bytes -> {len(payload_b64)} base64 chars "
      f"(> {BUFFER_WIDTH}-column buffer width, so wrapping would trigger)\n")

ps = q.PowerShell()

pre_fix_stdout = wrapped_stdout(payload_b64)
post_fix_stdout = direct_stdout(payload_b64)

print("--- pre-fix behavior model (bare expression -> Out-Default wraps) ---")
print(f"stdout lines: {len(pre_fix_stdout.splitlines())}")
decoded = ps.decode(pre_fix_stdout)
if decoded is None:
    print("decode() result: None (ValueError swallowed) -- fetch_result would "
          "report a loud, safe failure")
elif decoded.encode("utf-8", "surrogateescape") == RESULT_MD:
    print("decode() result: UNEXPECTEDLY matched original bytes")
else:
    print("decode() result: silently WRONG (truncated/garbage) bytes -- the bug")
print()

print("--- post-fix behavior model ([Console]::Out.Write -> single line) ---")
print(f"stdout lines: {len(post_fix_stdout.splitlines())}")
decoded = ps.decode(post_fix_stdout)
ok = decoded is not None and decoded.encode("utf-8", "surrogateescape") == RESULT_MD
print(f"decode() result matches original {len(RESULT_MD)}-byte result.md exactly: {ok}")

sys.exit(0 if ok else 1)
```
</details>
<details>
<summary>Evidence: Verification run output: pre-fix shape decodes to
wrong bytes, post-fix shape decodes exactly</summary>

<code>fixture: 137 raw bytes -&gt; 184 base64 chars (&gt; 80-column
buffer width, so wrapping would trigger)&#10;&#10;--- pre-fix behavior
model (bare expression -&gt; Out-Default wraps) ---&#10;stdout lines:
3&#10;decode() result: silently WRONG (truncated/garbage) bytes -- the
bug&#10;&#10;--- post-fix behavior model ([Console]::Out.Write -&gt;
single line) ---&#10;stdout lines: 1&#10;decode() result matches
original 137-byte result.md exactly: True</code>

```text
fixture: 137 raw bytes -> 184 base64 chars (> 80-column buffer width, so wrapping would trigger)

--- pre-fix behavior model (bare expression -> Out-Default wraps) ---
stdout lines: 3
decode() result: silently WRONG (truncated/garbage) bytes -- the bug

--- post-fix behavior model ([Console]::Out.Write -> single line) ---
stdout lines: 1
decode() result matches original 137-byte result.md exactly: True
```
</details>
- Outcome: ⚠️ 1 info across 1 run (9m2s)

## Pipeline

Updates from [git push
no-mistakes](https://github.com/kunchenguid/no-mistakes)

<!-- no-mistakes-pipeline-attestation:v1
{"head_sha":"873c14c703b870c946e67b7ab5284e75b116b3e9","steps":[{"step":"intent","status":"completed"},{"step":"rebase","status":"completed"},{"step":"review","status":"completed"},{"step":"test","status":"completed"},{"step":"document","status":"completed"},{"step":"lint","status":"completed"},{"step":"push","status":"completed"},{"step":"pr","status":"running"},{"step":"ci","status":"pending"}]}
-->

<details>
<summary>✅ **intent** - passed</summary>

✅ No issues found.
</details>

<details>
<summary>✅ **Rebase** - passed</summary>

✅ No issues found.
</details>

<details>
<summary>🔧 **Review** - 1 issue found → auto-fixed ✅</summary>

- ⚠️ `scripts/lib/queue.py:2515` - PowerShell.read() emits the base64
payload via the pipeline&#39;s implicit output
(`[Convert]::ToBase64String(...)` as the script&#39;s last expression),
which goes through PowerShell&#39;s default
`Out-Default`/console-formatting layer. When powershell.exe runs
non-interactively over an exec-channel ssh session (no pty is allocated
here — ssh_argv has no `-t`), it cannot query a real console width and
falls back to a default buffer width (commonly 80), and that formatter
wraps long string output at that width even though it&#39;s being
redirected to a pipe, not a terminal — a well-known PowerShell gotcha
for exactly this invocation shape. `PowerShell.decode()` (line
2518-2522) recovers the payload with
`base64.b64decode(last_out_of(stdout), ...)`, and `last_out_of` (line
2235) returns only the LAST non-empty line of stdout. Any result.md
whose base64 encoding exceeds one wrapped line (roughly &gt;60 raw bytes
— true of essentially any real task result, e.g. the test&#39;s own
~140-byte fixture result) would have its earlier lines silently
discarded, leaving only the tail chunk to decode. This mechanism
isn&#39;t exercised by the new queue-selftest.sh (i) coverage because
the fake windows-host.py stub emits the base64 with a single Python
`print()` and never simulates PowerShell&#39;s own line-wrapping, so the
gap is untested. Depending on the wrapped tail&#39;s length modulo 4,
decode() will usually raise ValueError (safe, loud failure —
fetch_result reports &#34;the host answered, but not with the file&#39;s
bytes&#34;) but could also occasionally decode to
truncated-but-valid-looking bytes, silently returning a wrong
(truncated) result. Either way this would make the freshly-built
&#34;result fetch&#34; coupling non-functional for realistic Windows
task outputs, contradicting the intent&#39;s claim that this coupling
was verified end-to-end. The fix is mechanical: have the PowerShell
script write the base64 string directly to the output stream (e.g.
`[Console]::Out.Write(...)`) instead of relying on the formatted
pipeline output, bypassing the width-based wrapping entirely.

🔧 Fix: fix(queue): write PowerShell result base64 with Console.Out to
avoid wrap truncation
✅ Re-checked - no issues remain.
</details>

<details>
<summary>⚠️ **Test** - 1 info</summary>

- ℹ️ The existing queue-selftest.sh section 11(i) Windows result-fetch
coverage still round-trips base64 through a plain Python print() in its
ssh stub and does not model PowerShell&#39;s non-interactive
console-width line-wrapping, so it cannot regression-guard this specific
fix on its own; it did not need to per the recorded review decision
(code-only auto-fix), and real end-to-end confirmation would require a
real Windows/PowerShell host, which the operator&#39;s own PR notes was
only available for a payload too small to trigger the wrap.
- `./scripts/check.sh`
- `./scripts/check.sh (baseline, already run successfully before this
phase - includes the full scripts/queue-selftest.sh, notably section
11(i)/(j) Windows/psmux dispatch and result-fetch coverage)`
- `git show 5efbde3:scripts/lib/queue.py and git show
873c14c:scripts/lib/queue.py - diffed PowerShell.read() across the
pre-fix and post-fix commits to confirm the exact code shapes exercised
below`
- `python3
/home/magicletur/.no-mistakes/evidence/01M2CGKZ3AQ6MJ4M38AX997FEP/verify_wrap_fix.py
- manual verification importing the real queue.py
PowerShell.decode()/last_out_of(), feeding it a modeled pre-fix
(wrapped) vs post-fix (unwrapped, [Console]::Out.Write) stdout for the
real ~137-byte non-ASCII result.md fixture used by queue-selftest.sh
section 11(i)`
</details>

<details>
<summary>✅ **Document** - passed</summary>

✅ No issues found.
</details>

<details>
<summary>✅ **Lint** - passed</summary>

✅ No issues found.
</details>

<details>
<summary>✅ **Push** - passed</summary>

✅ No issues found.
</details>
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