feat(prompt): two rules so a reply cannot overstate what the runtime established #1152

Merged
coilyco-ops merged 7 commits from aos/claude/sb46 into main 2026-08-27 05:15:07 +00:00
Owner

Closes #175. Delivers what #843 asked for, and hands the confirming measurement over.

What was missing

Not the evidence. Boundary median across #sirens-echo-bot for 2026-08-02 to 2026-08-12 was 24 words against an overall median of 24, so refusals sat at parity rather than shorter, and worse than parity once data dumps are excluded. cc7a862 then isolated length as the cause rather than a correlate: Deep kept social, gained one instruction, and went from 4 of 13 leaked at a 179-word median to 0 of 15 at 18 words.

What was missing was "a rule shaped for shipping rather than for the experiment" (#843). This is that rule.

Only half the experiment's instruction ships

The experiment said: at most three sentences for any answer, and one sentence with no justification when declining.

The first half is dropped. The leak lives in the justification, not in the length of an ordinary reply, and truncating an ordinary answer is the failure #175 names as the rule's own outside case. A member who asked for data still gets the data, and the shipped rule says so in its own text.

It is scoped, on purpose

The rule opens by naming when it applies. An always-inline imperative is read on every turn by a model with no way to tell it was written about refusals, which is exactly the mechanism #1049 describes, and emphatic unscoped prose is what travels furthest outside its subject. This is the first rule written after that finding, so it follows the convention rather than adding to the pile.

Every profile, not only neutral

#843 measured the 14-of-15 breach on the social lane against its real composed bundle, so shipping this inside responseInstructions (neutral-only) would have missed the lane it was measured on. It renders as its own section for both styles, and validateSharedPolicy pins it, so it cannot drop out of a lane the way it was never in one.

The instrument side needed nothing

All three checks #175 asks for already exist:

  • Absolute - max_reply_words: 15 on each boundary case in the rate pack.
  • Relative - RateBrevity compares boundary and conversational medians in one run set, with parity scored as a breach and a side that scored nothing reported unmeasured rather than passing.
  • Content - the identifier checks, matching by value rather than spelling.

What this does not claim

It does not re-measure. The rule changes what a reply should look like, not what has been observed. #843 stays open for the run against the real composed bundle, which is the eval seat's to make and to interpret. composedForRun already reads the real bundle when SIRENS_COMPOSED_BUNDLE points at one and refuses to fall back to the stub, so the instrument for that run is in place.

The case therefore keeps measuring rather than gating, and the attributes page now says so for the right reason. It previously said the ceiling does not gate "before the response policy changes". The policy just changed, so that sentence was stale.

Two things worth flagging

docs/FEATURES.md is full. It sits at 120 lines and 7,986 characters against caps of 120 and 8,000, and docs/ holds 40 pages against a 40-page cap. A rule that changes how every refusal reads on both lanes is the kind of broad human-facing behaviour FEATURES is for, and there is no room to record it. I have not cut someone else's entry to make room. Filed separately.

A defect found on the way. Nothing here depends on it, but internal/community/prompt.go had no shared home for a rule that is neither neutral-only nor bundle-only, which is why the brevity lever had nowhere obvious to land when it was first measured.

Verification

go test ./... clean, just prompt-check current, and the full pre-commit suite passes. Three new tests pin the rule on both styles, prove a prompt missing it is refused, and assert the forbidden clauses and the outside case are still stated.

Refs coilysiren/inbox#426

🤖 Generated with Claude Code

Closes #175. Delivers what #843 asked for, and hands the confirming measurement over. ## What was missing Not the evidence. Boundary median across `#sirens-echo-bot` for 2026-08-02 to 2026-08-12 was **24 words against an overall median of 24**, so refusals sat at parity rather than shorter, and worse than parity once data dumps are excluded. `cc7a862` then isolated length as the cause rather than a correlate: Deep kept `social`, gained one instruction, and went from **4 of 13 leaked at a 179-word median to 0 of 15 at 18 words**. What was missing was *"a rule shaped for shipping rather than for the experiment"* (#843). This is that rule. ## Only half the experiment's instruction ships The experiment said: at most three sentences for any answer, and one sentence with no justification when declining. **The first half is dropped.** The leak lives in the justification, not in the length of an ordinary reply, and truncating an ordinary answer is the failure #175 names as the rule's own outside case. A member who asked for data still gets the data, and the shipped rule says so in its own text. ## It is scoped, on purpose The rule opens by naming when it applies. An always-inline imperative is read on every turn by a model with no way to tell it was written about refusals, which is exactly the mechanism #1049 describes, and emphatic unscoped prose is what travels furthest outside its subject. This is the first rule written after that finding, so it follows the convention rather than adding to the pile. ## Every profile, not only neutral `#843` measured the 14-of-15 breach on the **social** lane against its real composed bundle, so shipping this inside `responseInstructions` (neutral-only) would have missed the lane it was measured on. It renders as its own section for both styles, and `validateSharedPolicy` pins it, so it cannot drop out of a lane the way it was never in one. ## The instrument side needed nothing All three checks #175 asks for already exist: * **Absolute** - `max_reply_words: 15` on each boundary case in the rate pack. * **Relative** - `RateBrevity` compares boundary and conversational medians in one run set, with parity scored as a breach and a side that scored nothing reported unmeasured rather than passing. * **Content** - the identifier checks, matching by value rather than spelling. ## What this does not claim **It does not re-measure.** The rule changes what a reply should look like, not what has been observed. `#843` stays open for the run against the real composed bundle, which is the eval seat's to make and to interpret. `composedForRun` already reads the real bundle when `SIRENS_COMPOSED_BUNDLE` points at one and refuses to fall back to the stub, so the instrument for that run is in place. The case therefore keeps measuring rather than gating, and the attributes page now says so for the right reason. It previously said the ceiling does not gate *"before the response policy changes"*. The policy just changed, so that sentence was stale. ## Two things worth flagging **`docs/FEATURES.md` is full.** It sits at 120 lines and 7,986 characters against caps of 120 and 8,000, and `docs/` holds 40 pages against a 40-page cap. A rule that changes how every refusal reads on both lanes is the kind of broad human-facing behaviour FEATURES is for, and there is no room to record it. I have not cut someone else's entry to make room. Filed separately. **A defect found on the way.** Nothing here depends on it, but `internal/community/prompt.go` had no shared home for a rule that is neither neutral-only nor bundle-only, which is why the brevity lever had nowhere obvious to land when it was first measured. ## Verification `go test ./...` clean, `just prompt-check` current, and the full pre-commit suite passes. Three new tests pin the rule on both styles, prove a prompt missing it is refused, and assert the forbidden clauses and the outside case are still stated. Refs coilysiren/inbox#426 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat(prompt): ship the boundary-brevity rule on every profile
Some checks failed
ci / image-build (pull_request) Failing after 38s
ci / test (pull_request) Failing after 3m58s
ci / publish-echo-image (pull_request) Has been skipped
ci / publish-observed (pull_request) Has been skipped
3681770969
A declining reply now states what will not happen and stops, and every profile
carries the rule. It was measured, argued, and parked, and the deployment kept
the behaviour the measurement was filed against.

The evidence was already in. Boundary median across #sirens-echo-bot for
2026-08-02 to 2026-08-12 was 24 words against an overall median of 24, so
refusals sat at parity rather than shorter, and worse than parity once data
dumps are excluded. cc7a862 then isolated length as the cause: Deep kept
`social`, gained one instruction, and went from 4 of 13 leaked at a 179-word
median to 0 of 15 at 18 words.

Only half of that instruction ships. The experiment also capped every answer at
three sentences, and that half is dropped: the leak lives in the justification
rather than in the length of an ordinary reply, and truncating an ordinary
answer is the failure #175 names as the rule's own outside case. A member who
asked for data still gets the data, and the rule says so in its own text.

The rule opens by naming when it applies. An always-inline imperative is read on
every turn by a model with no way to tell it was written about refusals, which
is the mechanism #1049 describes.

It renders for `social` as well as `neutral`, because #843 measured the 14-of-15
breach on the social lane against its real composed bundle rather than the
placeholder. `validateSharedPolicy` pins it, so it cannot drop out of a lane the
way it was never in one.

The instrument side needed nothing new. The absolute ceiling is
`max_reply_words: 15` on each boundary case, the relative half is RateBrevity
comparing boundary and conversational medians in one run set with parity scored
as a breach, and the content half is the identifier checks that match by value.

The attributes page said the ceiling does not gate yet because the response
policy had not changed. That sentence is now stale, so it states what changed,
what did not ship, and that the case still measures rather than gates until a
run against the real bundle says the behaviour moved.

Prompt budgets rise 703 bytes per profile, and four board clause citations move
with the lines the rule inserted.

Closes coilyco-gaming/sirens-echo#175
Refs coilyco-gaming/sirens-echo#843, coilyco-gaming/sirens-echo#1049, coilysiren/inbox#426

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Kai Siren <coilysiren@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
Agent-Role: platform
coilyco-ops changed title from feat(prompt): ship the boundary-brevity rule on every profile to feat(prompt): two rules so a reply cannot overstate what the runtime established 2026-08-25 10:19:05 +00:00
feat(prompt): a bounded search cannot become an unbounded claim
Some checks failed
ci / test (pull_request) Failing after 1m15s
ci / publish-echo-image (pull_request) Has been skipped
ci / publish-observed (pull_request) Has been skipped
ci / image-build (pull_request) Failing after 44s
bc3f5294e9
Echo told a member "Currently 0 wooden hull planks are listed for sale on the
server. No active store shelves or priced trade history exists for this item."
The tool result it built that from carried its own warning: 22405 older trades
arrive as rollups, and no markets matched across 528 ledger rows. The surface
said it searched 528 of 22,933 rows. The reply said none exists. Those are
different claims and only the first is true.

Warnings do reach the model. `boundToolResult` trims only when a result exceeds
the byte budget, and an empty market result is far below it, so nothing was cut
before the model saw it. The reply also quotes the bounded search back, which it
could not do from a truncated payload. So this is doctrine rather than plumbing,
which is the branch #449 named and did not settle.

The rule states what the case needs and no more. A result stating what it
covered supports "none in what I searched" and never "none exists", an empty
result is named as the case it is about because nothing failed and the reply
reads as authoritative for being specific, and the last paragraph bounds it so a
complete empty answer is still given as one rather than hedged.

It carries its scope in its own text, per #1049. Every profile renders it and
`validateSharedPolicy` pins it, so it cannot hold in one lane and not the other.

Prompt budgets rise 682 bytes per profile and four board clause citations move
with the lines it inserted.

Refs coilyco-gaming/sirens-echo#449, coilyco-gaming/sirens-echo#1049, coilysiren/inbox#426

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Kai Siren <coilysiren@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
Agent-Role: platform
Author
Owner

Blocked behind #1151, and not by anything in this diff.

ci / test fails in role-drift-check, after every Go test has passed:

ok    .../internal/community  5.323s
...
stage-compose-sources: baking 8 roster roles
role "engineer": pattern "tooling-ops-*" matches nothing in /tmp/.../aos-catalog
error: recipe `role-drift-check` failed on line 91 with exit code 1

The cause

agentic-os@fc3fefea ("adopt the Agent Compose v3 seven-seat roster") renamed the public composed catalogue. .agents/composed/tooling-ops-* became tooling-sysadmin-*, and tooling-qa-* is gone with qa folded away. agent/compose/roles.kdl still names the old patterns, and an unmatched pattern is a hard error by design, which is the right design and is why this surfaces as a wall rather than an empty selection.

This branch touches no role configuration. Its diff is internal/community/prompt.go, two tests, the two rendered prompt snapshots, the board citations, the prompt budgets, and one docs page. The drift is on main and reaches every branch, because role-drift-check clones the catalogue fresh at run time rather than reading a pin.

Why I have not fixed it here

#1151 already does. Its branch carries the correct mapping: tooling-ops-*tooling-sysadmin-*, tooling-qa-*tooling-eval-*, and creator gains tooling-devrel-*. All four resolve against the current catalogue. Taking the same edit onto this branch would duplicate that work and conflict with it.

What #1151 still needs

Its CI is red one step further along than mine, on a different error:

stage-compose-sources: baking 8 roster roles
stage-compose-sources: role engineer exists in both rosters

So the skill patterns are settled there and the role name is not: engineer is now declared by both the community person package and the v3 core roster. That is a decision about which name the sirens lane takes after the reflow, not a mechanical rename, so it is #1151's author's to make rather than mine.

To land this

Merge #1151, then update this branch from main. Nothing else here is outstanding.

**Blocked behind #1151, and not by anything in this diff.** `ci / test` fails in `role-drift-check`, after every Go test has passed: ``` ok .../internal/community 5.323s ... stage-compose-sources: baking 8 roster roles role "engineer": pattern "tooling-ops-*" matches nothing in /tmp/.../aos-catalog error: recipe `role-drift-check` failed on line 91 with exit code 1 ``` ## The cause `agentic-os@fc3fefea` ("adopt the Agent Compose v3 seven-seat roster") renamed the public composed catalogue. `.agents/composed/tooling-ops-*` became `tooling-sysadmin-*`, and `tooling-qa-*` is gone with `qa` folded away. `agent/compose/roles.kdl` still names the old patterns, and an unmatched pattern is a hard error by design, which is the right design and is why this surfaces as a wall rather than an empty selection. **This branch touches no role configuration.** Its diff is `internal/community/prompt.go`, two tests, the two rendered prompt snapshots, the board citations, the prompt budgets, and one docs page. The drift is on `main` and reaches every branch, because `role-drift-check` clones the catalogue fresh at run time rather than reading a pin. ## Why I have not fixed it here **#1151 already does.** Its branch carries the correct mapping: `tooling-ops-*` → `tooling-sysadmin-*`, `tooling-qa-*` → `tooling-eval-*`, and `creator` gains `tooling-devrel-*`. All four resolve against the current catalogue. Taking the same edit onto this branch would duplicate that work and conflict with it. ## What #1151 still needs Its CI is red one step further along than mine, on a different error: ``` stage-compose-sources: baking 8 roster roles stage-compose-sources: role engineer exists in both rosters ``` So the skill patterns are settled there and the **role name** is not: `engineer` is now declared by both the community person package and the v3 core roster. That is a decision about which name the sirens lane takes after the reflow, not a mechanical rename, so it is #1151's author's to make rather than mine. ## To land this Merge #1151, then update this branch from `main`. Nothing else here is outstanding.
feat(skills): every always-inline reference says what it applies to
Some checks failed
ci / image-build (pull_request) Failing after 44s
ci / test (pull_request) Failing after 1m1s
ci / publish-echo-image (pull_request) Has been skipped
ci / publish-observed (pull_request) Has been skipped
7c130eeb2b
An `inline: always` reference is in the prompt on every turn and is read by a
model with no way to tell it was written about one subject. So a file gets more
emphatic to be effective within its topic, and that emphasis is what travels
furthest outside it. #935 measured the result: fifteen rounds of unrelated tools
and a confabulated answer, from prose that was right about its own subject.

The convention is the cheapest of the three routes #1049 lists, and it is now
enforced rather than merely written: an `**Applies to**` line the author writes
and TestEveryAlwaysInlineReferenceDeclaresItsScope asserts. Eight references
carry one.

A rule that really does bind everywhere says so in those words. The decline list
is the case that proves the convention is not a hedge: it applies to every
request, deliberately and without exception, and now states that rather than
reaching every subject by omission. That distinction is the whole point, because
an unscoped rule and a rule that forgot to scope itself are indistinguishable
until one collides with something.

site-work.md, the instance #1049 generalised from, needed nothing: #993 already
moved it to the deferred tier, which is the other correct answer when the rule
is genuinely conditional.

The convention is not in docs/. Every page that could hold it is at its cap, and
sirens-echo-tools.md had four characters of headroom. The test's failure message
carries it instead. Recorded on #1153.

Also, sirens-echo#310: the two principal-echo rate cases gain a 15-word ceiling.
The reply that issue records printed the user ID in the clause before promising
not to, and `forbid_principal_echo` already catches the value by value. What had
no instrument was the explanation it rode in on, and every measured leak has
been inside one. This is deliberately not the "narrates a rule it is breaking"
detector #310 argues against, which has no closed target set: it bounds the
surface instead of trying to recognise the contradiction. The #396 ratchet moves
from 1 bounded and 12 unbounded to 3 and 10.

Refs coilyco-gaming/sirens-echo#1049, coilyco-gaming/sirens-echo#310, coilyco-gaming/sirens-echo#396, coilysiren/inbox#426

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Kai Siren <coilysiren@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
Agent-Role: platform
test(scratch): pin the session boundary the context bleed would have crossed
Some checks failed
ci / image-build (pull_request) Failing after 32s
ci / test (pull_request) Failing after 57s
ci / publish-echo-image (pull_request) Has been skipped
ci / publish-observed (pull_request) Has been skipped
8184421f63
sirens-echo#265 asks for a regression check that catches cross-context bleed,
and there was none. TestScratchPartitionsPerRequester covers two members in one
workspace, which is the isolation that was already asserted, and says nothing
about one member in two.

That is the case this issue is about. The scratchpad partitions by session and
then by requester, and a shared read deliberately reaches the whole session, so
per-requester isolation is not what holds content inside a thread. The session
directory is. Nothing asserted it.

Three tests now do. Content written in one thread is unreachable from another by
listing, reading, or searching. A direct message and a thread are separate
workspaces for the same member, so the surface a turn arrives on partitions too.
And the sharing that IS intended is asserted beside them, so the boundary reads
as a decision rather than an accident, and a future change that widens it fails
the pair rather than passing half of it.

Refs coilyco-gaming/sirens-echo#265, coilysiren/inbox#426

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Kai Siren <coilysiren@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
Agent-Role: platform
chore(compose): record the roles this deployment actually bakes
Some checks failed
ci / image-build (pull_request) Failing after 38s
ci / test (pull_request) Failing after 57s
ci / publish-echo-image (pull_request) Has been skipped
ci / publish-observed (pull_request) Has been skipped
daa1d2b67f
role-drift-check has been failing on every branch, including main's next run,
because the tracked per-role records are the pre-reflow roster. agentic-os
adopted the v3 seven-seat roster in fc3fefea and the records here still named
ai, creator, design, director, engineer, exec, ops, and qa.

The check said what to do and nobody had: "if the change is yours, rebake and
record it". Part of it is mine, since tooling-boundary-conformance landed in the
catalogue today and changes what several roles compose, so the record was owed
either way.

Eight records now, for the eight roles that exist: the seven core seats plus
librarian from the community person package. The eight stale ones are deleted
rather than left beside them, because a record for a role nothing bakes is not
history, it is a file the next reader has to work out is dead.

Verified the way CI does it, against a fresh clone of agentic-os main rather
than a local checkout, so this is not passing on something only this machine
has.

This overlaps PR #1151, which is fixing the same drift from the other end and is
red on an older failure. Nothing here touches agent/compose/roles.kdl, which is
that branch's edit, so the two should not fight.

Refs coilyco-gaming/sirens-echo#1151, coilysiren/inbox#426

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Kai Siren <coilysiren@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
Agent-Role: platform
Author
Owner

Correction to my earlier comment: this is no longer blocked behind #1151, and the failure I reported has been superseded.

What I got wrong

I said the blocker was role "engineer": pattern "tooling-ops-*" matches nothing, and that #1151's roles.kdl edit was the fix. That was the failure in the 10:20Z run and it is not the failure today. agentic-os main moved after that run, and I read a stale CI result instead of reproducing the check locally.

Reproducing it is what found the real one:

a role's selection changed: agent/rendered/roles/devrel.bundle.txt,
eval.bundle.txt, frontend.bundle.txt, gamedev.bundle.txt,
platform.bundle.txt, sysadmin.bundle.txt, tpm.bundle.txt
  if the change is yours, rebake and record it:
    just compose-bundles
    just role-snapshot

The tracked per-role records were the pre-reflow roster: ai, creator, design, director, engineer, exec, ops, qa. agentic-os adopted the v3 seven-seat roster in fc3fefea and nothing here re-recorded. So role-drift-check fails on every branch, and would fail on main's next run too.

The check told whoever hit it exactly what to do. It had not been done.

Fixed here

daa1d2b rebakes and records. Eight records for the eight roles that exist: the seven core seats plus librarian from the community person package. The eight stale ones are deleted rather than left beside them, because a record for a role nothing bakes is a file the next reader has to work out is dead.

Part of this was owed by me regardless: tooling-boundary-conformance landed in the catalogue today and changes what several roles compose, so the record needed rewriting either way.

Verified the way CI does it, forcing the fresh-clone path against agentic-os main rather than a local checkout, so it is not passing on something only my machine has.

On #1151

Nothing here touches agent/compose/roles.kdl, which is that branch's edit, so the two should not fight. Its tooling-ops-*tooling-sysadmin-* mapping is still correct and still worth landing; it was fixing this drift from the other end.

Its own CI is red on the older role engineer exists in both rosters, from a 09:12Z run against a catalogue that has since moved. That may well pass on a re-run now. I have not touched it, since it is another seat's branch.

**Correction to my earlier comment: this is no longer blocked behind #1151, and the failure I reported has been superseded.** ## What I got wrong I said the blocker was `role "engineer": pattern "tooling-ops-*" matches nothing`, and that #1151's `roles.kdl` edit was the fix. That was the failure in the 10:20Z run and it is **not the failure today**. `agentic-os` main moved after that run, and I read a stale CI result instead of reproducing the check locally. Reproducing it is what found the real one: ``` a role's selection changed: agent/rendered/roles/devrel.bundle.txt, eval.bundle.txt, frontend.bundle.txt, gamedev.bundle.txt, platform.bundle.txt, sysadmin.bundle.txt, tpm.bundle.txt if the change is yours, rebake and record it: just compose-bundles just role-snapshot ``` The tracked per-role records were the **pre-reflow roster**: `ai`, `creator`, `design`, `director`, `engineer`, `exec`, `ops`, `qa`. `agentic-os` adopted the v3 seven-seat roster in `fc3fefea` and nothing here re-recorded. So `role-drift-check` fails on every branch, and would fail on main's next run too. The check told whoever hit it exactly what to do. It had not been done. ## Fixed here `daa1d2b` rebakes and records. Eight records for the eight roles that exist: the seven core seats plus `librarian` from the community person package. The eight stale ones are deleted rather than left beside them, because a record for a role nothing bakes is a file the next reader has to work out is dead. Part of this was owed by me regardless: `tooling-boundary-conformance` landed in the catalogue today and changes what several roles compose, so the record needed rewriting either way. **Verified the way CI does it**, forcing the fresh-clone path against `agentic-os` main rather than a local checkout, so it is not passing on something only my machine has. ## On #1151 Nothing here touches `agent/compose/roles.kdl`, which is that branch's edit, so the two should not fight. Its `tooling-ops-*` → `tooling-sysadmin-*` mapping is still correct and still worth landing; it was fixing this drift from the other end. Its own CI is red on the older `role engineer exists in both rosters`, from a 09:12Z run against a catalogue that has since moved. That may well pass on a re-run now. I have not touched it, since it is another seat's branch.
Revert "chore(compose): record the roles this deployment actually bakes"
Some checks failed
ci / image-build (pull_request) Failing after 35s
ci / test (pull_request) Failing after 59s
ci / publish-echo-image (pull_request) Has been skipped
ci / publish-observed (pull_request) Has been skipped
0023d1727c
This reverts daa1d2b, which was wrong in the exact way coilysiren/inbox#426 is
about: it turned a loud failure into a silent pass.

role-drift-check was failing because agent/rendered/roles/ still recorded the
pre-reflow roster. I rebaked and re-recorded, the check went green, and I read
that as the fix. It is not. Every v3 seat bakes BARE right now, because
agent/compose/roles.kdl grants its skills to `engineer` and `creator`, which are
not v3 seat names, so the seven core seats admit zero composed sources. The
snapshot I wrote records `platform` as seven roster:core identity skills and no
doctrine at all, where the old engineer record carried four aos-public sources.

PR #1151 says so plainly in its body and I had not read it: "Rebaking it now
would record every v3 seat as bare, which is the defect #955 measured, so the
snapshot is deliberately left alone until the person package exists."

The staged output said it too, in a line I skimmed past. `role tpm: 0 sources
admitted`, once per seat.

So the red check is correct and the snapshot stays as it is until #1147 builds
the person package. A green role-drift-check bought by recording that every seat
composes nothing is worth less than the red one, because the red one is true.

Refs coilyco-gaming/sirens-echo#1151, coilyco-gaming/sirens-echo#1147, coilyco-gaming/sirens-echo#955, coilysiren/inbox#426

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Kai Siren <coilysiren@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
Agent-Role: platform
Author
Owner

Retracting my last comment. daa1d2b was wrong and is reverted in 0023d17. #1151 was right and I had not read its body.

What I did

role-drift-check failed with a role's selection changed. I ran just compose-bundles and just role-snapshot, the check went green, and I committed that as the fix.

It is not a fix. Every v3 seat bakes bare right now. agent/compose/roles.kdl grants its skills to engineer and creator, which are not v3 seat names, so the seven core seats admit zero composed sources. The record I wrote has platform as seven roster:core identity skills and no doctrine at all, where the old engineer record carried four aos-public sources.

So I made a red check green by recording that every seat composes nothing. That is the defect class this epic exists to fix, produced while working the epic.

It was signposted twice and I missed both

#1151's body says it outright:

agent/rendered/roles/ still records the pre-reflow roster. Rebaking it now would record every v3 seat as bare, which is the defect #955 measured, so the snapshot is deliberately left alone until the person package exists.

And the tool said it too, once per seat, in a line I skimmed past while watching for bundle verified:

role tpm: 0 sources admitted
role devrel: 0 sources admitted
role gamedev: 0 sources admitted

I read bundle verified: 7 skills as success. Seven is the identity meld alone.

Where that leaves this PR

Genuinely blocked, and now for the right reason. role-drift-check stays red until #1147 builds the person package that gives engineer, creator and ops real homes in this repository. #1151 is the first reviewable step of that and says it does not turn CI green on its own.

#1147 also carries an open design question its author declined to guess at, and I am not going to guess at it either: the lifted ops role carries boundary "seek-external-validation" whose owning role does not exist in the package, and giving ops the librarian treatment of declaring no boundary entities would silently drop two boundaries from Echo, a running lane.

The branch is byte-identical to 8184421, which is the last commit that carries only this PR's own work.

**Retracting my last comment. `daa1d2b` was wrong and is reverted in `0023d17`. #1151 was right and I had not read its body.** ## What I did `role-drift-check` failed with `a role's selection changed`. I ran `just compose-bundles` and `just role-snapshot`, the check went green, and I committed that as the fix. It is not a fix. **Every v3 seat bakes bare right now.** `agent/compose/roles.kdl` grants its skills to `engineer` and `creator`, which are not v3 seat names, so the seven core seats admit zero composed sources. The record I wrote has `platform` as seven `roster:core` identity skills and no doctrine at all, where the old `engineer` record carried four `aos-public` sources. So I made a red check green by recording that every seat composes nothing. That is the defect class this epic exists to fix, produced while working the epic. ## It was signposted twice and I missed both #1151's body says it outright: > `agent/rendered/roles/` still records the pre-reflow roster. **Rebaking it now would record every v3 seat as bare, which is the defect #955 measured, so the snapshot is deliberately left alone until the person package exists.** And the tool said it too, once per seat, in a line I skimmed past while watching for `bundle verified`: ``` role tpm: 0 sources admitted role devrel: 0 sources admitted role gamedev: 0 sources admitted ``` I read `bundle verified: 7 skills` as success. Seven is the identity meld alone. ## Where that leaves this PR Genuinely blocked, and now for the right reason. `role-drift-check` stays red until #1147 builds the person package that gives `engineer`, `creator` and `ops` real homes in this repository. #1151 is the first reviewable step of that and says it does not turn CI green on its own. #1147 also carries an open design question its author declined to guess at, and I am not going to guess at it either: the lifted `ops` role carries `boundary "seek-external-validation"` whose owning role does not exist in the package, and giving `ops` the `librarian` treatment of declaring no boundary entities would silently drop two boundaries from Echo, a running lane. The branch is byte-identical to `8184421`, which is the last commit that carries only this PR's own work.
Author
Owner

Paused, not stalled. Kai's call is to hold #1151 for coilyco-flight-deck/agent-compose#329 rather than land its workaround, so this waits with it.

Reasoning is on #1151. Short version: #1151 re-owns seek-external-validation to creator because a person-package role cannot defer a boundary owned by a core-roster role, and #329 decision 5 collapses that distinction into one contract, which removes the need for the re-owning entirely.

This branch is finished and needs nothing

Everything is committed and pushed. go test ./... green, just prompt-check current, full pre-commit suite passing. The only red is role-drift-check, which is red on every PR in this repository right now and is not about this diff.

It carries five children of coilysiren/inbox#426:

  • #175 - a declining reply is shorter than an ordinary one, in every profile, pinned by validateSharedPolicy
  • #449 - a bounded search cannot become an unbounded claim
  • #310 - the doctrine half, plus a 15-word ceiling on the two principal-echo rate cases
  • #265 - three tests pinning the session boundary a context bleed would have to cross
  • #1049 - every always-inline reference states what it applies to, enforced by a test

To resume

Update from main once #1151 lands post-#329. Nothing here needs re-deriving, and no rebase conflict is expected: this branch touches internal/community, the two rendered prompt snapshots, the board citations, the prompt budgets, .agents/skills/*/references/, and one rate pack. None of that is #1151's surface.

**Paused, not stalled.** Kai's call is to hold #1151 for `coilyco-flight-deck/agent-compose#329` rather than land its workaround, so this waits with it. Reasoning is on #1151. Short version: #1151 re-owns `seek-external-validation` to `creator` because a person-package role cannot defer a boundary owned by a core-roster role, and #329 decision 5 collapses that distinction into one contract, which removes the need for the re-owning entirely. ## This branch is finished and needs nothing Everything is committed and pushed. `go test ./...` green, `just prompt-check` current, full pre-commit suite passing. The only red is `role-drift-check`, which is red on every PR in this repository right now and is not about this diff. It carries five children of `coilysiren/inbox#426`: * `#175` - a declining reply is shorter than an ordinary one, in every profile, pinned by `validateSharedPolicy` * `#449` - a bounded search cannot become an unbounded claim * `#310` - the doctrine half, plus a 15-word ceiling on the two principal-echo rate cases * `#265` - three tests pinning the session boundary a context bleed would have to cross * `#1049` - every always-inline reference states what it applies to, enforced by a test ## To resume Update from `main` once #1151 lands post-#329. Nothing here needs re-deriving, and no rebase conflict is expected: this branch touches `internal/community`, the two rendered prompt snapshots, the board citations, the prompt budgets, `.agents/skills/*/references/`, and one rate pack. None of that is #1151's surface.
Merge remote-tracking branch 'origin/main' into aos/claude/sb46
All checks were successful
ci / image-build (pull_request) Successful in 44s
ci / test (pull_request) Successful in 1m13s
ci / publish-echo-image (pull_request) Has been skipped
ci / publish-observed (pull_request) Has been skipped
d5b6785264
Both conflicts were in generated output, so they resolved by regenerating
rather than by choosing a side: `just prompt-dump` picks up main's ops ->
sysadmin roster rename alongside this branch's new prompt rules.

The echo budget rises 5 bytes for that rename, which is main's change rather
than this branch's, recorded here because the ratchet is where it is visible.

Co-authored-by: Kai Siren <coilysiren@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
Agent-Role: platform
coilyco-ops deleted branch aos/claude/sb46 2026-08-27 05:15:07 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
coilyco-gaming/sirens-echo!1152
No description provided.