Sirens Deep: compose a real Agent Compose identity, selected by one deploy string #98

Closed
opened 2026-08-11 04:29:45 +00:00 by coilyco-ops · 6 comments
Member

Outcome

Sirens Deep gets a genuine Agent Compose identity, the full role and personality meld, flippable between roles by changing one string in the deploy repository. Sirens Echo stays void of personality exactly as it is today.

Design agreed with Kai on 2026-08-10. This issue is the durable record.

The split

  • Sirens Echo keeps its neutral profile and Sirens Discord knowledge. <aos-community-bundle> and personality meld stay forbidden in its rendered prompt.
  • Sirens Deep composes a role bundle. Those same markers become required, so a bundle that silently fails to compose is a startup failure rather than a quietly neutral bot.

ValidateSystemPrompt already enforces the forbidden direction. The change inverts it per profile rather than adding a new mechanism.

Source selection: aos public catalog plus an explicit aosk allowlist

The aos .agents/composed/ catalog is public-safe as a whole. The aosk one is not uniformly, so the compose request names an allowlist and excludes everything else by default.

In, all public-safe and mirrored on the public website:

  • writing-kai-voice (now Coilyco house style)
  • personal-preference-colors, -animals, -games, -shows, -anime, -books, -movies, -fabrication

Out, and the reason matters:

  • kai-career, kai-job-search, kai-grill-me, kai-collaboration - private context.
  • personal-preference-social - an organization can own a favorite color; it cannot own a person's social accounts. The reframe in coilyco-bridge/agentic-os-kai#853 works for tastes and for style, and does not work for biographical facts about a member. That distinction is the general rule for future additions.
  • kai-engineering-voice - portable after the de-Kapwinging in #853, but it is about producing code reviews and eng-channel posts, which is not what this agent does.
  • kai-design-language - public-safe but marked low-context: required and it is art direction.

A pre-commit check should fail on any source added outside the allowlist, so widening the surface is a reviewed act.

Shape

agent/compose-request.kdl names the catalog, the personality library, and the allowlist. The image build runs agent-compose bundle materialize once per role and bakes all eight. Deploy sets one variable:

SIRENS_DEEP_ROLE=director

Nothing is fetched upward at runtime, which keeps the config-placement rule intact: this repository authors the schema and the composition, coilyco-bridge/deploy owns the value.

Why this is reviewable

agent/rendered/*.prompt.txt already snapshots the assembled prompt and a pre-commit hook fails on drift. Extend it to emit one snapshot per role, so flipping SIRENS_DEEP_ROLE produces a reviewable diff of exactly what that persona tells the model. Current sizes for reference: Echo 5717 bytes from two policy roots, Deep 3091 from one. A role bundle will move that materially, and the byte count is the cheapest early warning.

The favorite-colour question, resolved

Agent Compose gives each seat its own accent (#b39258 engineer, #cb7471 director). The composed favorites give Coilyco purple and black. These do not collide once the favorites belong to the organization: the seat colour is the agent's own, the palette is the organization's. No post-processing needed.

Never impersonate

The agent shares house taste and house style. It never claims to be a specific person and never answers as one. People in a guild the operator does not moderate cannot be left unsure whether they are talking to a real human.

Prerequisites

  • internal/community/skillpack.go reads COMPOSED.md - done, landed in #93.
  • The composed sources are reframed onto the organization - done, coilyco-bridge/agentic-os-kai#853.
  • agent-compose must exist inside forgejo.coilysiren.me/coilyco-flight-deck/agentic-os:release. Unverified. The whole build-time bundling approach depends on it, and if it is absent the bundle has to be produced elsewhere and copied in.

Complete when

  • A tracked compose request names the catalog and the allowlist, and a check rejects sources outside it.
  • The image carries one verified bundle per role.
  • SIRENS_DEEP_ROLE selects the role at deploy time with no rebuild.
  • Sirens Deep's rendered prompt contains the role and personality surface; Sirens Echo's still does not, and both are asserted.
  • One prompt snapshot per role is tracked and gated.
  • The agent shares taste and style without claiming to be a person.
  • coilyco-bridge/agentic-os-kai#855 - the pronoun rail regression introduced by the scrub, which this agent will inherit.
  • #87 - the deployed agent misgendering a member, which the composed sources are meant to prevent.
  • #76 - the August 19 gap, of which the persona is one item.
## Outcome Sirens Deep gets a genuine Agent Compose identity, the full role and personality meld, flippable between roles by changing one string in the deploy repository. Sirens Echo stays void of personality exactly as it is today. Design agreed with Kai on 2026-08-10. This issue is the durable record. ## The split * **Sirens Echo** keeps its neutral profile and Sirens Discord knowledge. `<aos-community-bundle>` and `personality meld` stay **forbidden** in its rendered prompt. * **Sirens Deep** composes a role bundle. Those same markers become **required**, so a bundle that silently fails to compose is a startup failure rather than a quietly neutral bot. `ValidateSystemPrompt` already enforces the forbidden direction. The change inverts it per profile rather than adding a new mechanism. ## Source selection: aos public catalog plus an explicit aosk allowlist The aos `.agents/composed/` catalog is public-safe as a whole. The aosk one is not uniformly, so the compose request names an allowlist and excludes everything else by default. **In**, all public-safe and mirrored on the public website: * `writing-kai-voice` (now Coilyco house style) * `personal-preference-colors`, `-animals`, `-games`, `-shows`, `-anime`, `-books`, `-movies`, `-fabrication` **Out**, and the reason matters: * `kai-career`, `kai-job-search`, `kai-grill-me`, `kai-collaboration` - private context. * `personal-preference-social` - **an organization can own a favorite color; it cannot own a person's social accounts.** The reframe in `coilyco-bridge/agentic-os-kai#853` works for tastes and for style, and does not work for biographical facts about a member. That distinction is the general rule for future additions. * `kai-engineering-voice` - portable after the de-Kapwinging in #853, but it is about producing code reviews and eng-channel posts, which is not what this agent does. * `kai-design-language` - public-safe but marked `low-context: required` and it is art direction. A pre-commit check should fail on any source added outside the allowlist, so widening the surface is a reviewed act. ## Shape `agent/compose-request.kdl` names the catalog, the personality library, and the allowlist. The image build runs `agent-compose bundle materialize` once per role and bakes all eight. Deploy sets one variable: ``` SIRENS_DEEP_ROLE=director ``` Nothing is fetched upward at runtime, which keeps the config-placement rule intact: this repository authors the schema and the composition, `coilyco-bridge/deploy` owns the value. ## Why this is reviewable `agent/rendered/*.prompt.txt` already snapshots the assembled prompt and a pre-commit hook fails on drift. Extend it to emit one snapshot per role, so flipping `SIRENS_DEEP_ROLE` produces a reviewable diff of exactly what that persona tells the model. Current sizes for reference: Echo 5717 bytes from two policy roots, Deep 3091 from one. A role bundle will move that materially, and the byte count is the cheapest early warning. ## The favorite-colour question, resolved Agent Compose gives each seat its own accent (`#b39258` engineer, `#cb7471` director). The composed favorites give Coilyco purple and black. These do not collide once the favorites belong to the organization: the seat colour is the agent's own, the palette is the organization's. No post-processing needed. ## Never impersonate The agent shares house taste and house style. It never claims to be a specific person and never answers as one. People in a guild the operator does not moderate cannot be left unsure whether they are talking to a real human. ## Prerequisites * `internal/community/skillpack.go` reads `COMPOSED.md` - **done**, landed in #93. * The composed sources are reframed onto the organization - **done**, `coilyco-bridge/agentic-os-kai#853`. * `agent-compose` must exist inside `forgejo.coilysiren.me/coilyco-flight-deck/agentic-os:release`. **Unverified.** The whole build-time bundling approach depends on it, and if it is absent the bundle has to be produced elsewhere and copied in. ## Complete when * A tracked compose request names the catalog and the allowlist, and a check rejects sources outside it. * The image carries one verified bundle per role. * `SIRENS_DEEP_ROLE` selects the role at deploy time with no rebuild. * Sirens Deep's rendered prompt contains the role and personality surface; Sirens Echo's still does not, and both are asserted. * One prompt snapshot per role is tracked and gated. * The agent shares taste and style without claiming to be a person. ## Related * `coilyco-bridge/agentic-os-kai#855` - the pronoun rail regression introduced by the scrub, which this agent will inherit. * #87 - the deployed agent misgendering a member, which the composed sources are meant to prevent. * #76 - the August 19 gap, of which the persona is one item.
Author
Member

Prerequisite resolved: agent-compose is in the release image

The issue lists this as Unverified and says the whole build-time bundling approach depends on it. It holds.

coilyco-flight-deck/agentic-os docker/dev-base/install-common.sh:118 installs the binary to /usr/local/bin/agent-compose and symlinks acompose; verify-common.sh runs agent-compose version and agent-compose roster as a build check. This repository's Dockerfile already builds FROM forgejo.coilysiren.me/coilyco-flight-deck/agentic-os:release in both stages, so the tool is present where bundling would run.

The CLI surface matches the plan: agent-compose compose [request.kdl] --out --target and agent-compose bundle materialize --role --harness --out, plus verify and project --scope home for the staged-home handoff. Local version v2.19.0.

Blocker: a compose request cannot narrow what a source contributes

The plan's core mechanism does not exist. It says the compose request "names an allowlist and excludes everything else by default". Agent Compose does not work that way.

Selection is owned by each source repository's own .agents/roles.kdl, not by the requesting repository. Per docs/kdl-contracts.md, a request names role, delivery, model-tier, and source entries. It has no vocabulary for restricting a source's catalogue. agent/compose/roles.kdl in this repository is therefore inert as a filter: it would only bind this repository's own .agents/composed/ catalogue, and this repository has none.

Reproduction

Composed role "creator" against both catalogues in an isolated HOME and PROJECTS_ROOT:

compose {
    role "creator"
    delivery "native-skills"
    source "aos-public" root="projects/coilyco-flight-deck/agentic-os" required=#true
    source "aos-kai" root="projects/coilyco-bridge/agentic-os-kai" required=#true
}

Every source the issue puts Out was selected anyway, because coilyco-bridge/agentic-os-kai's own creator role binds them:

  • kai-career, kai-job-search - named as private context
  • kai-linkedin-voice, kai-linkedin-video - a member's personal channel
  • personal-preference-social - the exact case the favorite-colour rule was written to exclude
  • kai-engineering-voice - named as out of scope for this agent

It also pulled the full ordinary catalogue, including repo-lore, repo-inbox, repo-voice-corpus, repo-deploy, repo-infrastructure, repo-agentic-os-xxx, and every mcp-tools-* pointer. Removing operating_context from the host config changed nothing: same bundle hash, same selection.

aos-public alone stays public-safe and does deliver the wanted writing-* craft skills, but it drops personal-preference-* and writing-kai-voice, which are the two things the issue wants from the aosk catalogue. It also carries unrelated ordinary skills (tooling-gpg-ssm, tooling-tailscale, tooling-warp, tooling-zsh, kai-tech-prefs).

What this means

The allowlist has to move to wherever the binding actually happens, which is the source repository. The choice of where is a design decision, not a mechanical fix, so the wiring stops here pending it. The remaining items in Complete when are all downstream of it: the Dockerfile compose stage, SIRENS_DEEP_ROLE, the validator inversion, and the per-role snapshots.

The prerequisite bullet can be marked done regardless.

## Prerequisite resolved: agent-compose is in the release image The issue lists this as **Unverified** and says the whole build-time bundling approach depends on it. It holds. `coilyco-flight-deck/agentic-os` `docker/dev-base/install-common.sh:118` installs the binary to `/usr/local/bin/agent-compose` and symlinks `acompose`; `verify-common.sh` runs `agent-compose version` and `agent-compose roster` as a build check. This repository's `Dockerfile` already builds `FROM forgejo.coilysiren.me/coilyco-flight-deck/agentic-os:release` in both stages, so the tool is present where bundling would run. The CLI surface matches the plan: `agent-compose compose [request.kdl] --out --target` and `agent-compose bundle materialize --role --harness --out`, plus `verify` and `project --scope home` for the staged-home handoff. Local version v2.19.0. ## Blocker: a compose request cannot narrow what a source contributes The plan's core mechanism does not exist. It says the compose request "names an allowlist and excludes everything else by default". Agent Compose does not work that way. Selection is owned by **each source repository's own** `.agents/roles.kdl`, not by the requesting repository. Per [`docs/kdl-contracts.md`](https://forgejo.coilysiren.me/coilyco-flight-deck/agent-compose/src/branch/main/docs/kdl-contracts.md), a request names `role`, `delivery`, `model-tier`, and `source` entries. It has no vocabulary for restricting a source's catalogue. `agent/compose/roles.kdl` in this repository is therefore inert as a filter: it would only bind this repository's own `.agents/composed/` catalogue, and this repository has none. ### Reproduction Composed `role "creator"` against both catalogues in an isolated `HOME` and `PROJECTS_ROOT`: ```kdl compose { role "creator" delivery "native-skills" source "aos-public" root="projects/coilyco-flight-deck/agentic-os" required=#true source "aos-kai" root="projects/coilyco-bridge/agentic-os-kai" required=#true } ``` Every source the issue puts **Out** was selected anyway, because `coilyco-bridge/agentic-os-kai`'s own creator role binds them: * `kai-career`, `kai-job-search` - named as private context * `kai-linkedin-voice`, `kai-linkedin-video` - a member's personal channel * `personal-preference-social` - the exact case the favorite-colour rule was written to exclude * `kai-engineering-voice` - named as out of scope for this agent It also pulled the full ordinary catalogue, including `repo-lore`, `repo-inbox`, `repo-voice-corpus`, `repo-deploy`, `repo-infrastructure`, `repo-agentic-os-xxx`, and every `mcp-tools-*` pointer. Removing `operating_context` from the host config changed nothing: same bundle hash, same selection. `aos-public` alone stays public-safe and does deliver the wanted `writing-*` craft skills, but it drops `personal-preference-*` and `writing-kai-voice`, which are the two things the issue wants from the aosk catalogue. It also carries unrelated ordinary skills (`tooling-gpg-ssm`, `tooling-tailscale`, `tooling-warp`, `tooling-zsh`, `kai-tech-prefs`). ### What this means The allowlist has to move to wherever the binding actually happens, which is the source repository. The choice of where is a design decision, not a mechanical fix, so the wiring stops here pending it. The remaining items in **Complete when** are all downstream of it: the Dockerfile compose stage, `SIRENS_DEEP_ROLE`, the validator inversion, and the per-role snapshots. The prerequisite bullet can be marked done regardless.
Author
Member

Correction: the allowlist mechanism already exists

My previous comment claimed a compose request cannot narrow a source. That is wrong, and I am correcting it before anything gets built on it.

A request's source node takes exactly one of root or declaration (internal/schema/schema.go:271-295). Everything above used root=, which is the "take the source's whole catalogue as its own roles.kdl dictates" form. The declaration= form is the other half: the consumer names a .kdl file that enumerates precisely the skills it admits, by explicit path (schema.go:918-960). That is a consumer-side allowlist, and it is already in the grammar and documented in docs/kdl-contracts.md.

So the plan's mechanism is real. It just is not spelled the way this issue assumed, and it needs no new file convention or agent-compose change.

Verified

compose {
    role "creator"
    delivery "native-skills"
    source "aos-public" declaration="staged/aos-public/aos-public.kdl" required=#true
    source "aos-kai" declaration="staged/aos-kai/aos-kai.kdl" required=#true
}

Result, agent-compose verify: bundle verified: 15 skills // 18 files

  • roster:core - role-creator, personality-editorial, personality-nurturing, personality-warm
  • aos-public - tooling-discord-community-host, tooling-customer-success-signal-routing, tooling-customer-success-trust-repair, writing-social-cultural-reading, writing-social-editorial-loop, writing-social-trust-boundaries, writing-voice-adaptation
  • aos-kai - writing-kai-voice, personal-preference-colors, personal-preference-games, personal-preference-animals

Nothing outside the named set. No kai-career, no kai-job-search, no kai-linkedin-*, no personal-preference-social, no repo-* pointers, no mcp-tools-*. Compare the root= run in the previous comment, which pulled all of those.

What carries over from the previous comment

Two findings stand.

The roster is supplied by agent-compose itself, not by a source. role-creator and the three personalities arrive even when both sources are bounded declarations. Sirens Deep gets its full identity and meld regardless of how narrow the catalogue selection is.

root= on a source is unsafe here. With root=, the source repository's own roles.kdl decides, and coilyco-bridge/agentic-os-kai's creator role deliberately binds Kai's career, job-search, and LinkedIn context because that role serves Kai. This repository must use declaration= for both sources, never root=.

Consequences for the plan

  • agent/compose/roles.kdl is the wrong document. It is a provider-side graph for a catalogue this repository does not have. The reviewed set moves into source declarations, and internal/community/compose_test.go retargets onto those.
  • Declarations enumerate exact names, no globs. That suits an allowlist: widening is a visible line in a diff, which is what this issue asked for.
  • A declaration's paths resolve beneath the declaration file's own directory, so the build must stage the admitted skill bodies next to it. COMPOSED.md is renamed to SKILL.md when staged.

Next: the Dockerfile compose stage, then SIRENS_DEEP_ROLE, the validator inversion, and per-role snapshots.

## Correction: the allowlist mechanism already exists My previous comment claimed a compose request cannot narrow a source. That is wrong, and I am correcting it before anything gets built on it. A request's `source` node takes **exactly one of `root` or `declaration`** (`internal/schema/schema.go:271-295`). Everything above used `root=`, which is the "take the source's whole catalogue as its own `roles.kdl` dictates" form. The `declaration=` form is the other half: the consumer names a `.kdl` file that enumerates precisely the skills it admits, by explicit path (`schema.go:918-960`). That is a consumer-side allowlist, and it is already in the grammar and documented in [`docs/kdl-contracts.md`](https://forgejo.coilysiren.me/coilyco-flight-deck/agent-compose/src/branch/main/docs/kdl-contracts.md). So the plan's mechanism is real. It just is not spelled the way this issue assumed, and it needs no new file convention or agent-compose change. ### Verified ```kdl compose { role "creator" delivery "native-skills" source "aos-public" declaration="staged/aos-public/aos-public.kdl" required=#true source "aos-kai" declaration="staged/aos-kai/aos-kai.kdl" required=#true } ``` Result, `agent-compose verify`: **bundle verified: 15 skills // 18 files** * `roster:core` - `role-creator`, `personality-editorial`, `personality-nurturing`, `personality-warm` * `aos-public` - `tooling-discord-community-host`, `tooling-customer-success-signal-routing`, `tooling-customer-success-trust-repair`, `writing-social-cultural-reading`, `writing-social-editorial-loop`, `writing-social-trust-boundaries`, `writing-voice-adaptation` * `aos-kai` - `writing-kai-voice`, `personal-preference-colors`, `personal-preference-games`, `personal-preference-animals` Nothing outside the named set. No `kai-career`, no `kai-job-search`, no `kai-linkedin-*`, no `personal-preference-social`, no `repo-*` pointers, no `mcp-tools-*`. Compare the `root=` run in the previous comment, which pulled all of those. ### What carries over from the previous comment Two findings stand. **The roster is supplied by agent-compose itself**, not by a source. `role-creator` and the three personalities arrive even when both sources are bounded declarations. Sirens Deep gets its full identity and meld regardless of how narrow the catalogue selection is. **`root=` on a source is unsafe here.** With `root=`, the source repository's own `roles.kdl` decides, and `coilyco-bridge/agentic-os-kai`'s creator role deliberately binds Kai's career, job-search, and LinkedIn context because that role serves Kai. This repository must use `declaration=` for both sources, never `root=`. ### Consequences for the plan * `agent/compose/roles.kdl` is the wrong document. It is a provider-side graph for a catalogue this repository does not have. The reviewed set moves into source declarations, and `internal/community/compose_test.go` retargets onto those. * Declarations enumerate exact names, no globs. That suits an allowlist: widening is a visible line in a diff, which is what this issue asked for. * A declaration's paths resolve beneath the declaration file's own directory, so the build must stage the admitted skill bodies next to it. `COMPOSED.md` is renamed to `SKILL.md` when staged. Next: the Dockerfile compose stage, then `SIRENS_DEEP_ROLE`, the validator inversion, and per-role snapshots.
Author
Member

Source placement decided: promote, do not vendor

coilyco-bridge/agentic-os-kai is private and this repository is public, so the four wanted sources could not reach a public image build. Kai chose promotion over vendoring a copy or handing the build a private-repo credential.

writing-kai-voice, personal-preference-colors, personal-preference-games, and personal-preference-animals move into coilyco-flight-deck/agentic-os, which is public. The claim that makes this correct rather than convenient is one this issue already made: they are organization-owned house taste, and each body already reads "These are CoilyCo's, held in common by everyone who works under the name."

The placement rule is now written down in docs/composed-house-taste.md in agentic-os, with personal-preference-social as the instructive failure: same prefix, still private, because social accounts are a member's and not the organization's.

Land 997 first. In between, both catalogues carry byte-identical copies, which shadow harmlessly. The reverse order leaves two selectors in the private graph matching nothing.

Kai's own composition is unaffected

Composed all eight roster roles against both catalogues before and after the change. Byte-identical skill sets in every role, with the four now resolving from aos-public. The design and creator roles in the public graph gained the bindings that carried these privately.

Note on the compose doc

docs/sirens-echo-compose.md says the reframing "took renaming the personal sources to the kai- prefix rather than moving them between repositories, because Sirens Deep composes both catalogs and a move only changes which one a glob finds a skill in." That reasoning assumed root= sources and globs. With declaration= the consumer enumerates exact names, so which catalogue holds a source is exactly what matters. That paragraph needs rewriting when the wiring lands.

writing-kai-voice keeps its name through the promotion so existing selectors keep resolving. The name now understates its scope. Renaming is a separate change.

Blocked on

The Dockerfile compose stage needs the promoted sources present in agentic-os main, so the remaining wiring waits on 997.

## Source placement decided: promote, do not vendor `coilyco-bridge/agentic-os-kai` is private and this repository is public, so the four wanted sources could not reach a public image build. Kai chose promotion over vendoring a copy or handing the build a private-repo credential. `writing-kai-voice`, `personal-preference-colors`, `personal-preference-games`, and `personal-preference-animals` move into `coilyco-flight-deck/agentic-os`, which is public. The claim that makes this correct rather than convenient is one this issue already made: they are organization-owned house taste, and each body already reads "These are CoilyCo's, held in common by everyone who works under the name." The placement rule is now written down in `docs/composed-house-taste.md` in agentic-os, with `personal-preference-social` as the instructive failure: same prefix, still private, because social accounts are a member's and not the organization's. * https://forgejo.coilysiren.me/coilyco-flight-deck/agentic-os/pulls/997 - the promotion * https://forgejo.coilysiren.me/coilyco-bridge/agentic-os-kai/pulls/861 - the matching removal **Land 997 first.** In between, both catalogues carry byte-identical copies, which shadow harmlessly. The reverse order leaves two selectors in the private graph matching nothing. ### Kai's own composition is unaffected Composed all eight roster roles against both catalogues before and after the change. Byte-identical skill sets in every role, with the four now resolving from `aos-public`. The `design` and `creator` roles in the public graph gained the bindings that carried these privately. ### Note on the compose doc `docs/sirens-echo-compose.md` says the reframing "took renaming the personal sources to the `kai-` prefix rather than moving them between repositories, because Sirens Deep composes both catalogs and a move only changes which one a glob finds a skill in." That reasoning assumed `root=` sources and globs. With `declaration=` the consumer enumerates exact names, so which catalogue holds a source is exactly what matters. That paragraph needs rewriting when the wiring lands. `writing-kai-voice` keeps its name through the promotion so existing selectors keep resolving. The name now understates its scope. Renaming is a separate change. ### Blocked on The Dockerfile compose stage needs the promoted sources present in agentic-os `main`, so the remaining wiring waits on 997.
Author
Member

Five of six now done, one still open

Two gaps found in an audit against main are closed. Third-party status per bullet:

  • Tracked compose request names the catalog and the allowlist, and a check rejects sources outside it - done. request.kdl uses declaration= for every source, roles.kdl is the allowlist, composepolicy.go holds the denied set and the private-repository set, and four tests in compose_test.go enforce them.
  • The image carries one verified bundle per role - done, and now actually per role. See #149 below.
  • SIRENS_DEEP_ROLE selects the role at deploy time with no rebuild - done. Was mechanism-only.
  • Deep's prompt carries the role and personality surface, Echo's does not, both asserted - done. ValidateSystemPrompt inverts per profile and a non-composed profile carrying <composed-identity> is a startup failure.
  • The agent shares taste and style without claiming to be a person - done. See #148 below.
  • One prompt snapshot per role is tracked and gated - still open, deferred to #125. The snapshot renders PlaceholderComposed, so the byte count this issue wanted as "the cheapest early warning" does not reflect a real bundle. Now more useful than when it was deferred, because there are eight bundles to compare rather than one, and they run 8.6 KB to 47 KB.

#149, the role flip had nothing to flip to

roles.kdl declared one role and the staging loop grepped that file for the role list, so the image carried one bundle. SIRENS_DEEP_ROLE=director failed startup with no composed bundle for role "director".

Kai's call: the file is purely additive, and if a role exists in agent-compose at all then Sirens Deep can use it. The roster is now the authority. All eight roles bake, verified in the real image build:

stage-compose-sources: baking 8 roster roles
role engineer: 0 sources admitted   bundle verified: 4 skills // 7 files
role director: 0 sources admitted   bundle verified: 4 skills // 7 files
...
role creator: 19 sources admitted   bundle verified: 23 skills // 27 files
role ai: 0 sources admitted         bundle verified: 6 skills // 9 files

A role with no allowlist entry composes the roster identity alone, which this file already promised and the loop never delivered. Checked through the real runtime path too: resolveBundlePath, LoadBundle, BuildSystemPrompt, and ValidateSystemPrompt all succeed for director, strats, creator, and ai.

The allowlist keeps bounding which skills a role may pull, so the requirement that widening the surface is a reviewed act is unchanged.

#148, Never impersonate had no enforcement

The section states the requirement and nothing implemented it. ValidateResponseStyle returns nil for social, so Deep's replies got grounding validation and nothing else. Every deterministic voice check lived in ValidateNeutralStyle, which binds Sirens Echo, the profile with no persona to claim.

Two layers now. identityPolicy joins the shared prompt sections so both profiles render it and validateSharedPolicy fails a build that drops it. ValidateIdentityClaim runs on every reply for every style, beside grounding rather than inside the style check, and rejects a first-person human claim, a denial of being an agent, and answering as the configured principal.

Narrow on purpose. Saying it is an agent, saying it is a bot and not a person, naming its own identity, and mentioning the principal in the third person are all tested as allowed. A rejection reaches the member as the response-check notice from #138 rather than as silence.

Note on the compose page

The rewrite that comment three asked for did land: the paragraph reasoning from root= and globs is gone.

## Five of six now done, one still open Two gaps found in an audit against `main` are closed. Third-party status per bullet: * **Tracked compose request names the catalog and the allowlist, and a check rejects sources outside it** - done. `request.kdl` uses `declaration=` for every source, `roles.kdl` is the allowlist, `composepolicy.go` holds the denied set and the private-repository set, and four tests in `compose_test.go` enforce them. * **The image carries one verified bundle per role** - done, and now actually per role. See #149 below. * **`SIRENS_DEEP_ROLE` selects the role at deploy time with no rebuild** - done. Was mechanism-only. * **Deep's prompt carries the role and personality surface, Echo's does not, both asserted** - done. `ValidateSystemPrompt` inverts per profile and a non-composed profile carrying `<composed-identity>` is a startup failure. * **The agent shares taste and style without claiming to be a person** - done. See #148 below. * **One prompt snapshot per role is tracked and gated** - **still open**, deferred to #125. The snapshot renders `PlaceholderComposed`, so the byte count this issue wanted as "the cheapest early warning" does not reflect a real bundle. Now more useful than when it was deferred, because there are eight bundles to compare rather than one, and they run 8.6 KB to 47 KB. ## #149, the role flip had nothing to flip to `roles.kdl` declared one role and the staging loop grepped that file for the role list, so the image carried one bundle. `SIRENS_DEEP_ROLE=director` failed startup with `no composed bundle for role "director"`. Kai's call: the file is purely additive, and if a role exists in agent-compose at all then Sirens Deep can use it. The roster is now the authority. All eight roles bake, verified in the real image build: ``` stage-compose-sources: baking 8 roster roles role engineer: 0 sources admitted bundle verified: 4 skills // 7 files role director: 0 sources admitted bundle verified: 4 skills // 7 files ... role creator: 19 sources admitted bundle verified: 23 skills // 27 files role ai: 0 sources admitted bundle verified: 6 skills // 9 files ``` A role with no allowlist entry composes the roster identity alone, which this file already promised and the loop never delivered. Checked through the real runtime path too: `resolveBundlePath`, `LoadBundle`, `BuildSystemPrompt`, and `ValidateSystemPrompt` all succeed for `director`, `strats`, `creator`, and `ai`. The allowlist keeps bounding which skills a role may pull, so the requirement that widening the surface is a reviewed act is unchanged. ## #148, Never impersonate had no enforcement The section states the requirement and nothing implemented it. `ValidateResponseStyle` returns `nil` for `social`, so Deep's replies got grounding validation and nothing else. Every deterministic voice check lived in `ValidateNeutralStyle`, which binds Sirens Echo, the profile with no persona to claim. Two layers now. `identityPolicy` joins the shared prompt sections so both profiles render it and `validateSharedPolicy` fails a build that drops it. `ValidateIdentityClaim` runs on every reply for every style, beside grounding rather than inside the style check, and rejects a first-person human claim, a denial of being an agent, and answering as the configured principal. Narrow on purpose. Saying it is an agent, saying it is a bot and not a person, naming its own identity, and mentioning the principal in the third person are all tested as allowed. A rejection reaches the member as the response-check notice from #138 rather than as silence. ## Note on the compose page The rewrite that comment three asked for did land: the paragraph reasoning from `root=` and globs is gone.
Author
Member

Complete

#125 landed as 4858b07, so every Complete when bullet is now satisfied. Closing this out.

  • Tracked compose request names the catalog and the allowlist, and a check rejects sources outside it
  • The image carries one verified bundle per role, all eight
  • SIRENS_DEEP_ROLE selects the role at deploy time with no rebuild
  • Deep's rendered prompt carries the role and personality surface, Echo's does not, both asserted
  • One prompt snapshot per role is tracked and gated
  • The agent shares taste and style without claiming to be a person

On the last bullet, and the one design call inside it

agent/rendered/roles/<role>.bundle.txt records what each baked role selected: role skill, model tier, personalities, sources, and the sorted skill set.

It carries no bodies and no digests, which is a deliberate departure from a byte-exact snapshot. AOS_CATALOG_REF floats on main by design, so a record built from bodies would move on every upstream commit and turn main red for a change nobody in this repository can review. What does move it is a role gaining or losing a skill, which is the change this issue wanted widening to be: a visible diff.

The byte count this issue asked for as "the cheapest early warning" is printed per role and never gated, for the same reason. Current spread, from the CI image build:

role director: 4 skills, prompt 12090 bytes
role creator: 23 skills, prompt 50863 bytes

That is the four-fold difference a role bundle makes, which is what the warning was for.

Loading the bundles also renders and validates every role's prompt. A bundle that failed to compose would otherwise ship as exactly the quietly neutral bot this issue was written to prevent, and one filed under the wrong slug would make SIRENS_DEEP_ROLE select the wrong identity with nothing downstream to catch it. Both now fail the build rather than a deployment's startup.

The gate lives in the image's compose stage, so it fires on pull requests now that #129 landed. Neither new verb runs in pre-commit, so local hooks stay hermetic with no catalogue checkout and no bundles.

Verification

Against the real catalogue and inside the image, not reasoned about:

  • clean build passes and prints all eight roles
  • granting director a skill in roles.kdl and rebaking fails the check, naming director
  • editing a tracked record fails the image build with exit 1 at the check step
  • an upstream body edit does not move a record, asserted in a hermetic test

Both mutations reverted.

#126, the inert repositories block in roles.kdl, is still open and still inert. It is a separate decision about whether Sirens Deep should carry those ordinary-skill providers at all.

## Complete #125 landed as `4858b07`, so every **Complete when** bullet is now satisfied. Closing this out. * Tracked compose request names the catalog and the allowlist, and a check rejects sources outside it * The image carries one verified bundle per role, all eight * `SIRENS_DEEP_ROLE` selects the role at deploy time with no rebuild * Deep's rendered prompt carries the role and personality surface, Echo's does not, both asserted * **One prompt snapshot per role is tracked and gated** * The agent shares taste and style without claiming to be a person ## On the last bullet, and the one design call inside it `agent/rendered/roles/<role>.bundle.txt` records what each baked role selected: role skill, model tier, personalities, sources, and the sorted skill set. It carries no bodies and no digests, which is a deliberate departure from a byte-exact snapshot. `AOS_CATALOG_REF` floats on `main` by design, so a record built from bodies would move on every upstream commit and turn `main` red for a change nobody in this repository can review. What does move it is a role gaining or losing a skill, which is the change this issue wanted widening to be: a visible diff. The byte count this issue asked for as "the cheapest early warning" is printed per role and never gated, for the same reason. Current spread, from the CI image build: ``` role director: 4 skills, prompt 12090 bytes role creator: 23 skills, prompt 50863 bytes ``` That is the four-fold difference a role bundle makes, which is what the warning was for. Loading the bundles also renders and validates every role's prompt. A bundle that failed to compose would otherwise ship as exactly the quietly neutral bot this issue was written to prevent, and one filed under the wrong slug would make `SIRENS_DEEP_ROLE` select the wrong identity with nothing downstream to catch it. Both now fail the build rather than a deployment's startup. The gate lives in the image's compose stage, so it fires on pull requests now that #129 landed. Neither new verb runs in pre-commit, so local hooks stay hermetic with no catalogue checkout and no bundles. ## Verification Against the real catalogue and inside the image, not reasoned about: * clean build passes and prints all eight roles * granting `director` a skill in `roles.kdl` and rebaking fails the check, naming `director` * editing a tracked record fails the image build with exit 1 at the check step * an upstream body edit does not move a record, asserted in a hermetic test Both mutations reverted. ## What remains related but out of scope #126, the inert `repositories` block in `roles.kdl`, is still open and still inert. It is a separate decision about whether Sirens Deep should carry those ordinary-skill providers at all.
Author
Member

Unblocked, and cleared to proceed

Direction from Kai, 2026-08-12 session. Recording here so the implementing engineer does not re-derive it.

The blocker is gone. coilyco-flight-deck/agentic-os#997 merged at 2026-08-12T01:10:46Z. The four promoted sources — writing-kai-voice, personal-preference-colors, -games, -animals — are in the public catalogue. Every remaining item in Complete when is clear to start.

Priority: this is one of two demo-track tickets named top of queue for the week to August 19, alongside #122.

Remaining wiring, in order

  1. Dockerfile compose stage
  2. SIRENS_DEEP_ROLE selection at deploy time
  3. ValidateSystemPrompt inversion per profile
  4. Per-role snapshots — see #125, decided separately

Two constraints carried from the correction comment

Use declaration= for both sources, never root=. With root= the source repository's own roles.kdl decides, and coilyco-bridge/agentic-os-kai's creator role deliberately binds kai-career, kai-job-search, kai-linkedin-*, and personal-preference-social. That is correct for Kai's own composition and wrong for this agent. The consumer-side allowlist is the declaration= form.

Declarations enumerate exact names with no globs, and their paths resolve beneath the declaration file's own directory, so the build stages admitted skill bodies next to it. COMPOSED.md is renamed to SKILL.md when staged.

Rewrite the stale paragraph in docs/sirens-echo-compose.md. It currently argues the reframing "took renaming the personal sources to the kai- prefix rather than moving them between repositories, because Sirens Deep composes both catalogs and a move only changes which one a glob finds a skill in." That reasoning assumed root= sources and globs. With declaration= the consumer enumerates exact names, so which catalogue holds a source is precisely what matters — and #997 moved them. The paragraph is now wrong on its own terms.

agent/compose/roles.kdl is not being deleted. See #126 — Kai chose to materialize all three declared providers rather than remove the block.

## Unblocked, and cleared to proceed Direction from Kai, 2026-08-12 session. Recording here so the implementing engineer does not re-derive it. **The blocker is gone.** `coilyco-flight-deck/agentic-os#997` merged at 2026-08-12T01:10:46Z. The four promoted sources — `writing-kai-voice`, `personal-preference-colors`, `-games`, `-animals` — are in the public catalogue. Every remaining item in **Complete when** is clear to start. **Priority: this is one of two demo-track tickets named top of queue** for the week to August 19, alongside #122. ## Remaining wiring, in order 1. Dockerfile compose stage 2. `SIRENS_DEEP_ROLE` selection at deploy time 3. `ValidateSystemPrompt` inversion per profile 4. Per-role snapshots — see #125, decided separately ## Two constraints carried from the correction comment **Use `declaration=` for both sources, never `root=`.** With `root=` the source repository's own `roles.kdl` decides, and `coilyco-bridge/agentic-os-kai`'s creator role deliberately binds `kai-career`, `kai-job-search`, `kai-linkedin-*`, and `personal-preference-social`. That is correct for Kai's own composition and wrong for this agent. The consumer-side allowlist is the `declaration=` form. Declarations enumerate exact names with no globs, and their paths resolve beneath the declaration file's own directory, so the build stages admitted skill bodies next to it. `COMPOSED.md` is renamed to `SKILL.md` when staged. **Rewrite the stale paragraph in `docs/sirens-echo-compose.md`.** It currently argues the reframing "took renaming the personal sources to the `kai-` prefix rather than moving them between repositories, because Sirens Deep composes both catalogs and a move only changes which one a glob finds a skill in." That reasoning assumed `root=` sources and globs. With `declaration=` the consumer enumerates exact names, so which catalogue holds a source is precisely what matters — and #997 moved them. The paragraph is now wrong on its own terms. ## Related decision `agent/compose/roles.kdl` is **not** being deleted. See #126 — Kai chose to materialize all three declared providers rather than remove the block.
Sign in to join this conversation.
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#98
No description provided.