A lead-side rule has no delivery surface: fleet.charters reaches only spawned members, and it is per-daemon — so an operator policy aimed at "the fleets" cannot reach a single lead #591
Open
opened 2026-09-19 09:52:16 +02:00 by ltms
·
3 comments
No Branch/Tag Specified
main
worker/fleetd-612-unita-87807e-1
worker/612-b3-mcpwirings-da2b58-3
worker/612-b2-cb185-176d3a-2
worker/612-b1-completion-457459-1
worker/612-agaps-73a926-2
worker/608-sleeps-3a64ff-3
worker/621-b4520b-1
worker/618-b83894-2
worker/fleetd-615-e05481-5
worker/lead-autocompact-5f1ab2-3
worker/fleetd-613-f85deb-3
worker/fleetd-608-flaky-nudge-test-d0c2d1-3
worker/lead-context-gauge-ad404f-1
worker/gauge-wiring-9158c1-4
worker/redeploy-slowstart-ead0e5-5
worker/charter-bytes-13668c-6
worker/rollover-outcome-291483-2
worker/589-f64303-2
worker/593-1a8025-5
worker/589-fcd2aa-1
worker/568-9fdaa2-3
worker/571-attempted-outcome-5739f7-2
worker/581-completionresolver-cas-sites-0542b7-6
worker/562-loop-health-wiring-test-99611c-5
worker/562-surface-loop-health-7df5cc-4
worker/575-waiter-cleanup-sites-62ad80-1
worker/572-answer-lock-release-46a9ae-5
worker/567-probe-channel-leak-a38fc5-6
worker/551-record-before-send-7cbf56-1
worker/561-listener-fanout-survives-a-throw-61d538-2
worker/555-redeploy-main-flow-seam-65c2f5-2
worker/556-injector-owns-registration-e027a5-1
worker/552-post-restart-mktemp-abort-bc2672-4
worker/553-onstatus-completion-leak-0da881-2
worker/550-shasum-linux-196132-1
worker/538-loop-dies-on-error-4a5eeb-6
worker/426-health-coverage-ef1fd4-4
worker/504-failed-reported-clean-3cfd66-3
worker/537-capturedlog-close-e4c437-2
worker/459-broken-link-targets-cadc17-5
worker/535-appender-leak-fe74c1-1
worker/512-part2-shutdown-detection-434701-9
worker/529-logger-level-sweep-2a5533-8
worker/528-drain-gate-call-site-5de83d-7
charter/forge-mcp-vs-token
worker/521-swap-guard-unpinned-28e931-5
worker/519-probe-test-harness-d25ab8-4
worker/525-logger-level-leak-1b4eb0-6
worker/518-fleetmcp-resolver-wiring-8ef96c-1
worker/512-drain-complete-line-7edd71-3
worker/517-abort-branch-and-jar-id-41b641-2
worker/500-9e52c9-3
worker/509-4912f4-2
worker/511-9a4b23-1
worker/493-479f45-2
worker/505-03f8b2-1
worker/492-followup-detect-unclear
worker/501-a31fa0-7
worker/498-451d1c-5
worker/494-1015ce-2
worker/492-209647-1
worker/489-001902-2
worker/480-relative-handover-path-906323-1
worker/480-b-handover-skill-45bf1f-5
worker/474-followup-source-pin-f54a55-17
worker/474-charter-check-on-reload-f54a55-17
worker/466-quarantine-repeatcount-report
worker/393-opencode-skill-seeding-71854b-13
worker/469-canonical-tool-names-2a472a-16
worker/466-quarantine-escalation-5ae9c1-15
worker/446-hot-exhausted-pattern-0af580-6
worker/464-charter-tool-name-guard-a85635-12
worker/463-listfleet-default-fails-open-f1c76c-11
worker/458-invariant-5-by-purpose-862f9a-10
worker/439-coordinator-row-gate-bc032a-8
worker/449-herdr-protocol-576015-4
worker/450-abstract-spawn-599e1c-5
worker/437-ack-refuses-177d91-1
worker/444-placement-window-feb56a-2
worker/440-helddurable-derived-d462d7-13
worker/425-rework-placement-resolve-c58ba1-9
worker/421-lead-peek-held-msgs-cdbad2-10
worker/435-fixed-policy-cap-fe11de-12
worker/422-gate-state-observability-9e79d6-11
worker/431-memberregistry-live-readers-cdbad2-10
worker/424-architect-slot-hot-038b41-7
worker/422-model-gate-spawn-c29f48-6
worker/425-default-profile-live-f55534-8
worker/415-coverage-wording-2cbf9c-5
worker/416-3ad1da-1
worker/418-588283-3
worker/deterministic-stamp-race-409-3cb7b6-10
worker/armed-reads-live-config-404-ed931f-9
worker/reply-peer-refusal-391-5a34bd-7
worker/models-allowlist-aa9e9b-3
worker/ttl-stamp-race-399-f1122f-8
worker/scrub-receipt-400-316b3e-5
worker/exhaustion-detection-395-105105-6
worker/scrub-abort-394-316b3e-5
fix/scrub-uid-abort
worker/task-scrub-517574-2
worker/t386-clock-bd5b78-4
worker/t384-scrub-813790-5
worker/t381-cc-748314-2
worker/t373-336973-2
worker/t365-3920c5-3
worker/t358-6e989b-1
worker/t355-8b321c-1
worker/fleetd-369-hermetic-git-tests-e8b19a-3
worker/fleetd-368-stale-lead-binding-f5682e-2
worker/fleetd-360-deploy-units-0d3793-1
worker/359-dead-lead-tabs-f1253b-4
worker/362-worktree-skills-c03e51-3
worker/361-coord-visibility-655144-1
362-plugin-visibility-and-drift
worker/errscan-bed2ca-2
worker/amqp-log-identity-bed2ca-2
worker/withdefaults-guard-561704
worker/sleepguard-82076d-1
worker/fd334-9ee1b6-5
worker/fd348-f1ab27-4
worker/fd335-a71c35-1
worker/fd342-174a17-2
worker/fd345-490d0f-3
worker/fleetd-337-5ec7d4-21
worker/fleetd-341-af5a6b-24
worker/fleetd-339-5ca0a2-23
worker/fleetd-338-83a4a1-22
worker/fleetd-333-281f46-18
worker/fleetd-329-11bdbb-16
worker/fleetd-330-2770fb-17
worker/fix-326-50506e-15
worker/fix-324-3e9bbf-14
worker/fix-323-b8287d-13
worker/fix-316b-bd0860-11
worker/fix-318-76ca36-9
worker/fix-317-486aec-8
worker/fix-315-ce47c5-6
worker/fix-307-275890-6
worker/fix-308-b4f664-7
worker/fix-309-ec3939-8
worker/fix-310-7a3974-9
worker/fix-302-52ad0e-9
worker/fix-298-ce1acb-8
worker/fix-297-66bd11-7
worker/fix-296-104622-6
worker/fix-293-bare-closetab-eb22b5-3
worker/fix-280-gone-ask-lapse-bca98e-2
worker/fix-290-reapidle-guard-coverage-9b0dd1-1
worker/fix-285-trust-seed-8f3565-10
worker/fix-284-backend-error-seat-85912c-11
worker/fix-282-chained-ask-e6d0bb-8
worker/fix-283-teardown-leaks-f40dfa-9
worker/fix-281-pin-handler-actions-4921ac-7
worker/audit-rendezvous-lifecycle-d072ae-2
worker/audit-health-placement-1a2476-6
worker/audit-teardown-exits-e207a5-3
worker/audit-launcher-asymmetry-27e370-4
worker/audit-rest-authz-6ca53c-5
worker/investigate-275-abandon-asking-fdef52-8
worker/fix-274-worktree-leak-b0095d-7
worker/fix-273-exhausted-pattern-9665b5-6
worker/fleetd-267-model-check-bd8068-1
worker/fleetd-131-archunit-18b834-7
worker/fleetd-266-sshagent-rename-a014ff-6
worker/fleetd-184-uid-claim-8e1f31-4
worker/fleetd-184-warn-b381ee-10
worker/fleetd-184-docs-be1d12-9
worker/fleetd-257-9bf010-7
worker/fleetd-103-23a113-6
worker/fleetd-247-342356-5
worker/fleetd-116-04dea8-4
worker/fleetd-252-a830e0-3
worker/fleetd-111-7e8673-9
worker/fleetd-155c-f8ef4b-8
worker/fleetd-176-b928ca-3
worker/fleetd-249-7a7878-2
worker/cb248-composition-root-b-9acdf7-15
worker/cb148-envrc-default-fa6c82-12
worker/cb201-unit5-wiring-6c12e6-8
worker/cb241-fallback-echo-1175e9-11
worker/cb149-trust-dialog-2392a5-9
worker/cb134-148-overlay-visible-c9b986-10
worker/cb234-session-id-keyed-04e1fc-1
worker/cb201-unit3-nudge-abdf5c-6
worker/cb201-unit2-policy-c1102c-5
worker/cb201-unit4-outcome-a13bfa-7
worker/cb201-unit1-classifier-91b9b1-4
worker/cb201-227-refine-831980-3
worker/cb175-model-readback-0f085f-1
worker/cb222-charter-tmpdir-17f013-1
worker/cb226-architect-slot-race-cd3aa8-3
worker/cb224-worktree-root-group-024523-2
worker/cb-123-role-demotion-c600f7-2
worker/cb-219-opencode-roots-1f677e-1
worker/cb214-claude-session-id-b9eab4-4
worker/cb213-zdotdir-wrong-process-dd6de4-3
worker/cb211-exhaustion-classification-9546e0-2
worker/cb137-ambiguous-task-4df3d8-4
worker/cb209-agentsessionid-4dfdb6-2
worker/cb185-hostenvnames-2692b5-3
worker/cb206-opencode-sqlite-128718-2
worker/cb185-worktree-group-fc0c99-1
worker/cb-137-ask-ticket-e7760c-2
worker/cb-172-broker-uri-d36ae4-4
worker/cb-175-model-readback-76ead6-3
worker/cb-161-pane-ancestry-293510-1
worker/cb-164-rebase-885863-8
worker/cb-164-empty-scrape-false-success-1a80af-3
fix/cb-197-ticket-ttl-from-completion
worker/cb-189-remote-url-coverage-4692f3-1
worker/cb-185-blockers-027756-4
worker/cb-192-gap-log-11b631-2
worker/cb-633-fix-5f4396-3
worker/cb185-router-d6436d-3
worker/cb185-router-routing-gaps-9e9d33-3
worker/cb185-paneids-992586-2
worker/cb-633-allow-list-union-ed374b-1
worker/cb-157-credential-in-remote-url-496e44-2
worker/cb-641-health-herdr-evidence-8f1f54-6
worker/cb-640-health-msg-evidence-99c9cd-1
worker/cb-642-fleets-status-skill-bbbc40-5
cb-634-ide-mcp
worker/lead-comms-wiring-c014b9-7
worker/lead-mailbox-c19577-6
worker/autocompact-window-82bc2f-5
worker/cb-634-probe-18056f-4
worker/cb635-broker-urienv
worker/cb-632-config-retry-8e0efa-7
lead/cb-622e-claude-md
lead/cb-622-followup
worker/cb-622a-165dff-1
lead/cb-622d-opencode-mount
worker/cb-622b-717c67-2
worker/cb-622c-ab7759-3
worker/cb-617b2-20ca4b-3
worker/cb-617a-5c2f4a-1
worker/cb596-4e49ef-3
worker/cb586-10500c-1
worker/cb-606-b9343a-25
worker/cb604-1445f8-24
worker/cb582-477374-21
worker/cb584-8c2281-22
worker/cb600-e6b9a9-20
worker/cb602-ce257f-19
worker/cb601-b42837-18
worker/cb598-6c7ba7-17
worker/cb599-740fe4-16
worker/cb597-282224-15
worker/cb590fix-185e9a-10
worker/cb528-recovery-race
worker/cb594-96bead-8
worker/cb590-916766-2
worker/cb527-997d99-3
worker/cb592-env-leak-3cbf9c-1
worker/cb588-async-ticket-nudge-3218f7-5
worker/cb578b-9dcb13-6
worker/cb581-d24826-5
worker/m2-u5-ef8c42-15
worker/cb578a-516499-2
worker/cb576-01a04b-17
worker/cb579-lead-tab-acba06-20
worker/cb580-terminal-health-ed6058-21
worker/cb577-f36fdc-18
worker/cb573b-3db06f-16
worker/cb568c-f36fdc-18
worker/cb568-drop-cause-c3ac1c
worker/cb575-cancelled-notification-c3ac1c
worker/m4-sol-a2cbec-3
worker/cb574-async-ask-c3ac1c
worker/cb573-health-model-8ca857-14
worker/cb572-unknown-target-7f2e35-13
worker/u4-700706-9
worker/u3-b9fcb6-6
worker/u2-ef5b68-4
worker/u1-469dce-1-clean
worker/u1-469dce-1
worker/cb-564-health-events-70cf7e-2
worker/cb-565-recycle-drops-role-98e58f-3
worker/cb-563-missing-reply-df2866-1
worker/cb-562-readiness-gate-silent-6c23c9-3
worker/cb-560-architect-presence-da8155-1
worker/cb-561-architect-silent-off-a71cab-2
worker/cb-548-bind-architect-slot-fe1b8c-1
worker/parity-overlay-settings-5fb711-1
secrets-central-store
cb-559-hot-key-correction
cb-557-fleet-role-pools
worker/cb-553-maxload-explicit-spawn-305ee3-6
worker/cb-551-idle-lead-heartbeat-f1633c-1
worker/cb-544-drain-preserves-worktree-925fad-3
worker/cb-552-docs-sync-1cb9cf-4
worker/cb-548-rendezvous-guard-rebased
worker/cb-548-rendezvous-guard-116b53-10
worker/cb-548-authz-v2-586df6-8
worker/cb-548-authz-264363-5
salvage/cb-528b-codex-home
salvage/cb-528a-codex-launcher
CB-518-primary-flow
feature/peer-launcher-spi
cb-103-injector
v1.1.0
v1.0.0
Labels
Clear labels
blocked
needs-live-proof
ready-to-delegate
silent-default
Cannot start until something else lands. The body says what.
Merged and green, but never shown working on the running daemon. Not the same as done.
Scope, files and acceptance criteria are written. A worker can be briefed from the body alone.
A feature that compiles, passes tests, and ships turned off. Nine recurrences and counting.
No Label
Milestone
No items
No Milestone
Projects
Clear projects
No project
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: fleet/fleetd#591
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
What the operator asked for
2026-09-19, in their own words:
and immediately after:
So the policy has two halves, and they are aimed at two different kinds of session:
Half B already has a config surface. Half A has none. That is this issue.
What exists today — measured 2026-09-19 on the Mac daemon
fleet.charters.<role>is real, live, and does what it says.The live config holds exactly one entry,
architect, atfleetd/fleetd.yaml:292.It is read at spawn from the live config, not from a startup snapshot —
HerdrPeerLauncher.java:499:It is backend-neutral, because that code sits in the shared base class. Both
ClaudeCodeLauncher.java:49andOpenCodeLauncher.java:59areextends HerdrPeerLauncher, so a Claude member and an OpenCode member get the same text.It is hot —
ConfigRef.java:609listschartersamong the keys a reload really applies.No restart, no redeploy.
It is validated.
FleetConfig.java:2702-2709refuses a charter whose key is not a rolewire name or whose value is blank, and
Fleetd.java:175-178refuses one that names an MCPtool this server does not register.
The existing
architectcharter already carries the bounded-rounds deadlock rule and ends with"the lead decides". Under the new policy that last clause is now the end of the escalation
chain rather than a step before the operator. So half B needs a small wording change, not a new
mechanism.
Gap 1 — a charter cannot reach a lead, by construction
charterForis keyed byMemberRole(FleetConfig.java:1272):Its only caller is
spawnInternal. A lead is not spawned by fleetd — it is a human-startedsession that fleetd recognises by its tab label (
fleet.leaders.*.tab). There is no launch forfleetd to attach text to, so there is no path by which any
fleetd.yamlkey can deliver aninstruction to a lead.
Today the only place half A can live is
CLAUDE.md. That is per-repo, not per-fleet: ittravels with a git checkout, so two fleets working the same repo necessarily share it, and one
fleet working two repos necessarily cannot have one policy. Neither grouping is the one the
operator asked for.
Gap 2 — charters are per-daemon, and nothing keeps two hosts in step
The Mac and fleet01 each hold their own
fleetd.yaml. Both files are gitignored, so neither isin version control, and nothing compares them. Setting a rule "for the fleets" is N manual edits
with no check that the N results match — and a fleet whose edit was missed keeps obeying the old
rule silently, which is the failure mode that is hardest to notice.
Acceptance criteria
Write these as properties under a change, not as the presence of a construct.
fresh lead on each of two daemons and spawn an architect on each. All four sessions must read
the new text. A test must fail if any one of the four still reads the old text.
lead reads differs. A test that only asserts the key parses, or that the text is non-blank, does
not satisfy this — it stays green when delivery is removed. See #506 for that family.
(
fleet_list,/healthz, or a startup log line) must say the policy text differs betweenthem. Silent divergence is the defect, so "it works when both are correct" is not a pass.
behaviour
FleetConfig.java:2702gives charters today — a bad value must not start the daemon.Notes for whoever picks this up
it puts workflow policy into what is meant to be a message bus. The charter-plus-lead-gate shape
is the decided one.
how
CLAUDE.mdand the new surface avoid contradicting each other. Two sources of truth for onerule is worse than the gap.
event when architects settle a decision without the operator.
Correction — one claim in the description is wrong
The description says charters are applied by hand on each host "with nothing comparing the
results". That is false, and the fleet01 lead caught it. I have confirmed it in the code.
CharterReceipt(CB-571) already fingerprints the exact charter bytes a member received, andfleet_listreports it per member ascharterSha256alongsidecharterSource. From this host'sfleet_lista few minutes ago, on threedevmembers:Two properties make it usable as a cross-host instrument, both read from
CharterReceipt.compose:REPLY_CHARTER, orthe reply charter alone when no role charter is configured;
different profiles and different hosts still produce the same hash for the same text.
So the correct statement is not "nothing compares the results" but "nobody is comparing
them" — a different and much cheaper problem.
A precision that matters when comparing two hosts
charterSource: "none"means no charter is configured for that role, so the digest is thereply charter alone. The hash above is therefore not comparable with a hash from an
architectmember, which carries the role charter too. Compare like with like: same role, andcheck
charterSourcebefore reading the digest.Concrete example from today. fleet01's architect, still on the pre-change charter because their
safety classifier blocked the
fleetd.yamlwrite, reports15e2a7daa35db4af015718244c6ecb32d35da9b18c88444d81f346ffcc0ee37b. That is the pre-changebaseline for
fleet.charters.architectplus the reply charter.Adopted between the two fleets
The fleet01 lead proposed, and I have accepted, that we exchange
charterSha256whenever eitherhost touches
fleet.charters.*— the same protocol we already use for theCLAUDE.mdblock sha,and for the same reason. It costs one line in a coordination message.
What this does NOT fix, which is what the ticket is actually about
The member charter has an instrument; the lead contract has neither a config surface nor a
hash. Gap 1 in the description stands unchanged, and it is the more important half. Gap 2 is
softened from "undetectable" to "detectable, if someone looks".
One more lead-facing surface, found while measuring #595
leadRollover.bootstrapText(FleetConfig.java:1401) is operator-authored text infleetd.yamlthat fleetd types into a lead's pane. It is configured on this host. So the daemon can already
deliver text to a lead — the limit is timing, not capability: it fires only during a roll. That
may be the seam a lead-side surface should reuse rather than invent. Noted here because it
weakens the description's "there is no path by which any
fleetd.yamlkey can deliver aninstruction to a lead" — accurate for charters, too strong as written.
An architect is looking at the design now.
The instrument got used across two hosts today, and it found a divergence nobody knew about
Follow-up to the correction above. The fleet01 lead and I ran the
charterSha256comparison forreal. It worked, and the first thing it found was not today's change.
Measured, both hosts, 2026-09-19
0f895e51…3b254309…fleet_listreports1bbd5f7d…, 2178 bytes15e2a7da…, 1223 bytesBoth daemons log the arithmetic and it checks out on each: 1377 + 2 + 799 = 2178 here,
422 + 2 + 799 = 1223 there.
REPLY_CHARTERis 799 bytes on both hosts, so a composed-hashmismatch between us can only be the role-charter text.
The two fleets' architect charters had already diverged by ~500 bytes before today's edit, and
neither lead knew. They have been briefing architects from different contracts for an unknown
length of time. This is the failure the ticket predicts, found by accident on the first use of
the instrument.
It is a divergence, not a subset. My five pre-change paragraphs are 141, 257, 119, 202 and
209 bytes; no prefix and no pair of them sums to their 422, so their text is differently worded
rather than a shorter selection of mine.
Three properties of
CharterReceiptthat belong in this ticket1.
charterBytesis as diagnostic ascharterSha256, and cheaper. The fleet01 lead'swords: "the hash says 'different', the byte count says 'different by how much', and the second
is what turns a mismatch into a diagnosis." They satisfied my positive control without spawning
anything, because the byte count was already in their spawn log. Any surface that reports the
hash must report the byte count beside it.
2. The profile is deliberately NOT in the digest, and that must not be "fixed".
CharterReceipt.composetakes the profile as a field but hashes only the composed text. That iswhat makes the comparison work across hosts whose fleets differ: my daemon refuses an architect
on
gx("no architect slot for profile 'gx' — fleet.architects carries profiles: opus, sol")while fleet01 spawns one there without complaint. Two fleets that cannot run each other's spawn
commands can still compare each other's charters. Folding the profile into the digest is an
obvious-looking improvement that would destroy exactly the cross-host comparison this ticket
wants. Whoever implements here should add a test that pins it.
3. The digest covers the COMPOSED string, so it cannot isolate the role charter on its own.
It works today only because
REPLY_CHARTERhappens to be byte-identical on both hosts — wemeasured that, we did not assume it. If two daemons ever run different versions, the protocol
silently starts reporting charter differences that are really reply-charter differences. The
positive control (spawn a role with no configured charter; the digest is then the reply charter
alone —
f3e28dda…, 799 bytes) is what separates the two, and it should be written down as partof the protocol rather than rediscovered.
Protocol now in use between the two fleets
Exchange
charterSha256andcharterByteswhenever either host touchesfleet.charters.*, and run the reply-charter control before believing any mismatch. Same shapeas the
CLAUDE.mdblock-sha exchange.None of this closes Gap 1. The lead contract still has no config surface and no fingerprint, and
that remains what the ticket is for.
CORRECTION 3, and an architect design position
An architect member (profile
sol, branchworker/591-dab0a9-4) was asked to design the delivery surface. It read the code and returned a position. It also corrected me. I verified the correction myself before writing it here.The correction: "fleetd never starts a lead" is false
I wrote that a lead is always human-started and only ever recognised by tab label. That is wrong.
LeadLaunchercan start one.Measured in
fleetd/src/main/java/dev/ltms/fleet/lead/LeadLauncher.javatoday::174-181— a lead with atab:and noprofile:is recognise-only. The log line says so: "it can be recognised but not launched. Addprofile:under fleet.leaders. to have fleetd start it.":324— a lead with aprofile:is launched by fleetd throughAgentControl, usingleadArgv(profile).So there are two kinds of lead, not one. The correct statement is: a recognise-only lead is human-started; a lead with a
profile:is started by fleetd.What that opens, and immediately closes again
A launcher is a text surface — that is how every member gets its charter. So fleetd does have a launch-time hook for a lead. It deliberately puts nothing in it.
leadArgvat:360-368:That is a third lead-facing surface, alongside
leadRollover.bootstrapText, and it is intentionally empty with a comment defending the emptiness. The ticket's conclusion does not change —fleet.charters.<role>still cannot reach any lead — but the reason is now "the one launch hook that exists was deliberately left empty", not "no hook exists". Those need different fixes, and the comment at:364has to be answered by whoever writes one.The architect's position, in short
Its recommendation, which I have not yet accepted or rejected:
fleet.contractinfleetd.yamlholding one document for all roles, with role labels inside it.fleet.charters.<role>stays, narrowed to member-only turn mechanics. Its argument for the boundary: one repo can serve several fleets and one fleet can use several repos, so a repo rule cannot model either case, whilefleetd.yamlalready owns roles, charters and rollover text.fleet_whoami, read throughConfigRefon every call so a config reload shows up on the next call. The generic rule inCLAUDE.mdchanges from "call once per session" to "call at the start of each task", which makes that the refresh boundary.injectable()acceptsBLOCKED, so a push receipt can say delivered while the text lands in an open prompt. And rollover is optional, destructive, and only fires on a roll — after a roll,bootstrapTextshould say "callfleet_whoami" rather than carry another copy.fleet_listshows the daemon's current contract digest and each lead's last-delivered digest. A mismatch then means that lead has not fetched the current text. This is the lead-side twin ofCharterReceipt(CB-571), and it is what makes a hot config field safe: without it, a stale running lead is invisible.Limits it stated itself
Unchecked by the architect: the prompt-size cost of giving a shared contract to every member; whether Claude Code or OpenCode actually puts the MCP
initializeinstructions field into model context (it checked withjavapthat the local SDK's builder has one staticinstructions(String), not per-caller and not live, but did not test client behaviour); whether any common config source is reachable from both hosts; and live end-to-end rollover success.It changed no files and ran no tests.
Open, not decided
A blank configured contract failing config load matches how a blank member charter is already refused at
FleetConfig.java:2702-2709, so that part is consistent with the existing code. Thefleet.contractname and whether the document should be one blob or per-role are the parts I would expect to change. I am recording the position, not adopting it.