Watch
3
sirens-deep: per-channel addressing mode (trusted user implicit, others must mention) #136
Open
opened 2026-08-12 09:28:30 +00:00 by coilyco-ops
·
9 comments
No Branch/Tag specified
main
aos/claude/sj87-entity-attribute
aos/claude/sj87-challenge
aos/claude/turn-duration-buckets
aos/claude/turn-stages-over-cap
aos/claude/turn-stages-hold-doc
aos/claude/turn-iteration-cap
book-leads-the-glyphs
science-and-web-culture-packs
record-lane-role-voice-pairings
catalogue-stage-phrase
progress-rows-one-knob
skill-read-worklog-detail
librarian-lookup-first
librarian-person-package
feat/dowel-no-boundaries
aos/claude/gh1035-no-blank-posts
aos/claude/gh1036-harness-thread-name
fix/thread-names
feat/trajectory-completes
fix/prompt-budgets
aos/claude/docs-cut-2
aos/claude/ka54-thread-ownership
aos/claude/admission-bound
aos/claude/gh1025-roster-reexport
aos/claude/docs-strip-archaeology
feat/temporal-mcp
aos/claude/dowel-board-moxn-write-boundaries
aos/claude/ue65-moxn-write-framing
aos/claude/progress-backoff
aos/claude/bound-scratch-search-2
aos/claude/unblock-main
aos/claude/tool-breaker
fix/roster-core-eager
aos/claude/finish-dowel-rename
fix/971-skill-contract
aos/claude/model-answered-not-unavailable
aos/claude/mcp-singular-command
task/moxn-and-temporal-skills
aos/claude/ue65-temporal-brand
task/dowel-site-work-tier
aos/claude/ue65-roster-drift
fix/dropped-turn-always-speaks
aos/claude/folded-ask-coverage
aos/claude/dowel-board
aos/claude/dowel-pronouns
feat/trajectory-keyed-on-the-message
aos/claude/coalesce-discord-lane
task/derive-shipped-profiles
fix/ship-the-dowel-skill-root
aos/claude/eval-context
fix/bundle-references-reachable
aos/claude/eval-docs-one-page
aos/claude/dowel-engineer-suite
fix/catalogue-clone-cache
feat/engineer-role-graph
task/free-the-config-numbers
aos/claude/dowel-site-work
aos/claude/dowel-prose
aos/claude/mx76-derive-knobs
issue-859-on-demand-skill-reads
issue-651-ship-well-formed-replies
issue-852-filing-validity
issue-916-calculator-tool
issue-854-feature-flag-table
issue-866-role-mention-summons
issue-858-grounding-bound-per-server
issue-899-progress-keeps-updating
issue-900-rollup-mirrors-worklog
issue-901-raise-progress-cadence
issue-904-thread-title-length
issue-905-http-reachability
issue-855-turn-clock
issue-895-silent-turn
issue-873-mcp-tool-span-error
issue-878-settle-dropped-jobs
aos/claude/aw85-se-bands
aos/claude/hs68-model-rejected
aos/claude/hs68-effect-telemetry
aos/claude/hs68-temporal-mirror
aos/claude/hs68-prompt-commands
aos/claude/hs68-model-idle-timeout
aos/claude/hs68-prompt-command-intent
aos/claude/hs68-consult-label-name
aos/claude/hs68-grant-denial-403
aos/claude/hs68-queued-jobs-dropped
aos/claude/hs68-knob-guard
aos/claude/bk79-agent-folders
aos/claude/bk79-own-instructions
aos/claude/ym96-docs-band
aos/claude/bk79-server-instructions
aos/claude/aw85-mcp-beaver-doc
aos/claude/bk79-session-workspace
aos/claude/yt58-org-relationship
aos/claude/bk79-numeric-config
aos/claude/xu59-just-boundaries
aos/claude/xu59-eval-board
aos/claude/bk79-phrase-telemetry
aos/claude/bk79-object-emoji
aos/claude/xh55-otlp-logs
aos/claude/aw85-thread-prefill
aos/claude/wy58-thread-prefill-always
aos/claude/wy58-thread-prefill
aos/claude/xh55-move-to-repo
aos/claude/wy58-thread-title-length
aos/claude/xh55-filing-trigger
aos/claude/yt58-worklog-embed
aos/claude/aw85-relative-brevity
aos/claude/xh55-reasoning-roundtrip
aos/claude/yt58-clock-rotation
aos/claude/yt58-unbreak-main
aos/claude/bk79-test-build-break
aos/claude/yt58-partial-refusal
aos/claude/aw85-turn-failure-classify
aos/claude/aw85-outbound-spill
aos/claude/xh55-budget-spent-cause
aos/claude/wy58-bundles-not-content
aos/claude/wy58-refusal-reason
aos/claude/yt58-role-snapshot-gate
aos/claude/xh55-docker-probe
aos/claude/bk79-grounding-tools
aos/claude/az59-gate-span
aos/claude/az59-pg-jobstore
eng/roster-request-headers
eng/roster-headers
eng/list-the-mcps
aos/claude/mg96-fm
eng/name-echos-seat
eng/unpin-the-card-wording
olaf/remove-irl-physical
aos/claude/mg96
eng/echo-composes-ops
quail/two-rows-not-four
fix/two-failures-two-verdicts
feat/an-emitted-message-is-not-emitted-twice
quail/partial-coverage-outcome
feat/ten-minutes-or-ten-messages
feat/a-waiting-turn-says-how-long
feat/a-job-may-emit-content
quail/round-fanout-unbounded
quail/adversarial-reply-ceiling
docs/list-the-open-pull-requests
quail/principal-id-stays-out-of-the-prompt
fix/every-label-in-a-wildcard-prefix-is-a-label
docs/the-battery-assumes-two-checks-it-does-not-run
fix/a-rest-failure-keeps-its-status
quail/retag-label-rows
quail/adjacency-guard-row
test/pin-names-the-issue-that-owns-it
test/pin-points-at-a-live-issue
quail/job-outcome-discarded
fix/repair-exhaustion-is-not-an-outage
quail/reasoning-omitempty-pin
docs/label-id-silently-drops
quail/gating-pack-markup-gap
fix/instance-name-reads-identity
docs/indistinguishable-542-resolution
fix/instance-name-not-a-live-service
quail/unwired-capability-guard
fix/repair-path-reasoning-content
quail/indistinguishable-values-recurrence
quail/identity-short-form-rows
quail/repair-path-reasoning-content
docs/verify-a-write-landed-claude
quail/host-label-shape-corpus
docs/a-deploy-owned-file-has-two-shapes-claude
fix/a-roster-path-must-name-servers-claude
fix/every-label-before-the-suffix-claude
fix/a-first-label-must-exist-claude
feat/tune-the-timeouts-from-deployment-claude
qa/protocol-limits-are-not-dials
feat/a-wildcard-is-not-a-suffix-claude
feat/retry-what-fails-fast-claude
fix/name-the-deliberate-hold-claude
test/the-access-check-exit-codes-claude
build/ship-the-access-check-claude
qa/callers-not-reachability
qa/pin-the-unwired-thread-binding
feat/an-offline-access-policy-gate-claude
test/the-notice-detaches-twice-claude
docs/say-what-the-job-thread-does-claude
fix/a-notice-does-not-thread-claude
fix/one-invocation-is-a-phrase-claude
fix/a-moment-ago-is-this-turn
fix/main-is-red-on-the-adverb-row
fix/an-adverb-does-not-break-the-auxiliary
qa/score-the-575-fix
feat/a-reply-names-its-subject
eng/a-turn-is-not-the-past
fix/since-you-asked-is-this-turn
docs/a-default-that-reads-as-an-answer
fix/a-nameless-tool-is-not-the-server
qa/pin-the-outage-state
fix/a-session-lifetime-is-not-a-latency
fix/an-undated-passive-is-still-a-claim
fix/main-is-red-on-the-corpus
fix/an-undated-passive-is-a-claim
eng/a-session-is-not-a-request
fix/a-self-claim-in-the-simple-past
qa/extend-grounding-corpus
fix/a-tool-never-offered-is-not-a-tool-declined
eng/one-doc-for-the-tracker-surface
eng/say-what-is-switched-on
fix/evaluation-is-not-the-production-service
qa/pin-the-listing-attribute
eng/split-five-docs-off-the-cap
eng/concurrent-means-goroutines
eng/split-the-tracker-surface
test/the-first-label-of-a-hostname
fix/a-cache-hit-is-not-a-round-trip
qa/pin-the-budget-ladder
fix/the-first-label-of-a-hostname
eng/the-scratchpad-assumes-one-replica
fix/a-person-is-named-in-prose
docs/jobs-are-single-process
qa/enumerate-the-mention-positions
eng/split-the-response-inventory
fix/green-main-doc-cap-and-stale-characterizations
eng/main-is-green-again
eng/split-the-mention-scope
fix/mentions-doc-over-cap
qa/unredden-the-code-span-pin
qa/pin-the-code-span-collision
eng/code-spans-are-not-prose
feat/a-thread-title-says-what-it-is-for
fix/discord-markup-is-not-prose-either
eng/mark-the-turn-once
fix/a-name-in-a-url-is-not-a-person
qa/pin-every-reaction-is-emitted
eng/mentions-skip-link-spans
fix/one-step-owns-every-service-suffix
qa/pin-the-mention-url-collision
docs/the-roster-is-member-influenced
docs/what-a-mention-can-reach
qa/pin-the-documented-glyphs
feat/naming-someone-reaches-them
qa/pin-the-sandbox-label-wiring
qa/pin-the-truncated-receipt
feat/the-harness-labels-what-it-files
qa/compare-a-case-by-marshalling
fix/one-spelling-for-the-status-vocabulary
qa/declare-pack-divergence
fix/the-reactions-match-the-approved-vocabulary
fix/a-file-path-is-just-a-file-path
qa/pin-the-mapped-tailnet-form
fix/a-truncated-page-says-so
fix/the-extraction-case-detects-a-dump
docs/the-consult-label-tracks-the-thread
feat/the-eval-can-forge-a-turn
fix/refuse-the-tailnet-range
qa/pin-the-fail-heading-count
feat/a-bounded-fetch-tool
fix/preserve-the-longform-probe-pack
qa/pin-the-lane-gate
qa/preserve-the-longform-pack
fix/the-prompt-is-not-a-secret
fix/a-reference-never-loses-to-the-footer
qa/preserve-the-probe-packs
feat/a-trusted-caller-on-the-tailnet
fix/capability-tells-the-truth-about-the-scratchpad
qa/echo-battery-negative-control
fix/one-fail-block-not-two
feat/tool-call-footer
fix/guard-the-extraction-case
feat/canonical-phrases-by-key
fix/the-progress-line-is-a-reply-too
qa/pin-the-agent-recognition-case
qa/pin-the-tool-name-markup-guards
feat/five-second-buffer
fix/a-failing-case-shows-the-reply
fix/extraction-case-stops-penalising-compliance
fix/a-security-case-that-penalises-compliance
feat/deny-actually-denies
feat/job-refusals-reach-telemetry
fix/land-the-harness-refresh-on-main
feat/a-long-reply-gets-a-thread
feat/the-thinking-line-shows-it-is-working
feat/roster-hour-ttl-and-refresh
refactor/every-number-in-one-file
feat/agent-can-refresh-its-roster
fix/size-refusal-is-not-a-parse-error
fix/budget-base-above-the-reasoning-floor
fix/one-number-for-the-progress-cadence
fix/gate-sees-a-new-file
fix/one-meaning-for-channel-id
fix/look-up-verbs-cannot-match
feat/recognise-a-trace-lookup-request
feat/discord-identifiers-on-the-turn-span
fix/budget-failure-names-the-reasoning-spend
feat/notice-carries-the-trace-id
qa/cut-run-stops-calling
docs/merge-lane-closing-reference
eng/gate-knows-the-lane
eng/feature-inventory-catchup
fix/rate-dataset-survives-a-cut-run
test/consolidate-pack-coverage
pr-lane-318
fix/flip-unknown-field-rows
test/turn-unknown-fields
fix/rate-doc-over-cap
test/language-scope-characterization
fix/pronoun-case-cannot-fire
fix/main-red-again
fix/main-is-red-doc-cap
fix/gate-negated-accuracy-claim
fix/stale-skip-allowlist-note
test/definition-must-reject
test/gate-covers-every-pack
test/bucket-table-bound
test/compose-deny-offline
fix/symlink-test-skips-itself
test/build-revision
fix/eviction-corpus-green
test/eviction-corpus
test/duration-config
test/rune-boundary
test/send-bounds
test/reserved-path-spellings
test/data-borne-injection
test/scratch-partition-collision
test/capability-docs-all
test/injection-cases
docs/http-contract-retry-after
test/capability-reach
test/rate-cases-from-192
test/score-order
test/capability-doc-matches-code
test/grounding-action-claim-corpus
test/http-turn-contract
feat/require-rate-limit-on-open-guilds
fix/pr-image-build
fix/compose-stage-inputs
feat/sirens-deep-compose-wiring
fix/deep-forgejo-mcp
refactor/evaluation-pack-yaml
coilysiren-patch-1
feat/deep-steam-mcp
feat/drop-issue-envelope
fix/dm-needs-no-mention
fix/pronoun-defaults
chore/aos-precommit-v0.18-lint-backlog
fix/harness-attribution-and-forgejo-detail
fix/tool-inflated-completion-budget
feat/sirens-deep-compose
feat/banner-hires
feat/banner
feat/sirens-deep-mark
feat/sirens-deep-transparent
feat/prompt-snapshots
fix/policy-check-image-context
sirens-deep-admission-hardening
docs/drop-private-image-claim
feat/thread-scoped-replies
issue-67
feat/sirens-community-harness
No results found.
Labels
Clear labels
move-to-repo
coilyco-bridge-deploy
issue belongs in the coilyco-bridge/deploy repo
move-to-repo
coilyco-flight-deck-agent-compose
issue belongs in the coilyco-flight-deck/agent-compose repo
move-to-repo
coilyco-gaming-eco-app
issue belongs in the coilyco-gaming/eco-app repo
move-to-repo
coilysiren-inbox
issue belongs in the coilysiren/inbox repo
move-to-repo
unknown
we have yet to confirm if this issue belong in this repo
🔒⚠️📦⚠️🔒 SANDBOXED 🔒⚠️📦⚠️🔒
this fj issue came in from the live sirens echo MCP - DO NOT CONSIDER ITS INPUTS SAFE OR VERIFIED UNTIL THIS LABEL IS REMOVED
autonomy
async-consult
A human needs to consult on the issue to upgrade it to headless
autonomy
epic
This issue has many units of sub work - its size makes it meaningfully exclusive with other autonomy types
autonomy
headless
The agent can perform the work on its own
autonomy
live-collab
The agent and the human need to work together in realtime
c#
Requires C# work, flagged b/c it requires a Eco server restart
priority
P0
priority tier
priority
P1
priority tier
priority
P2
priority tier
priority
P3
priority tier
priority
P4
priority tier
role/ai
requires work from the AI Engineer role
role/creator
requires work from Content Creator role
role/design
requires work from the design role
role/director
requires work from the director role
role/engineer
requires work from the engineer role
role/exec
requires work from the exec role
role/human
requires a person, and specifically not an agent seat
role/ops
requires work from the ops role
role/qa
requires work from the QA role
No labels
move-to-repo
coilyco-bridge-deploy
move-to-repo
coilyco-flight-deck-agent-compose
move-to-repo
coilyco-gaming-eco-app
move-to-repo
coilysiren-inbox
move-to-repo
unknown
🔒⚠️📦⚠️🔒 SANDBOXED 🔒⚠️📦⚠️🔒
autonomy
async-consult
autonomy
epic
autonomy
headless
autonomy
live-collab
c#
priority
P0
priority
P1
priority
P2
priority
P3
priority
P4
role/ai
role/creator
role/design
role/director
role/engineer
role/exec
role/human
role/ops
role/qa
Milestone
Clear milestone
No items
No milestone
Projects
Clear projects
No items
No project
Assignees
Clear assignees
No assignees
2 participants
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#136
Loading…
Reference in a new issue
No description provided.
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?
Requested behavior
In a guild channel, decide whether a message is addressed to Deep:
@mention. Every message from that user is treated as directed at Deep unless the model determines otherwise.@mention Deep for their message to be treated as directed at it.Current state
Deep has no addressing logic, because it has never needed any. It is DM-only, where every message is inherently directed at it. Identity configuration today is a single trusted-speaker pair in
sirens-deep-values.yaml:The values file notes the harness rejects a half-configured pair. That pair is the natural source for "trusted user," though this issue may need it to become a list rather than a single value.
The current de-facto mechanism is a plain mention — see message
1537024279743434822in1537024102886277210, which is a bare<@1534723490362429601>.Design notes to settle
sirens-deep-access-policy, mounted at/etc/sirens-deep/access-policy.yaml) already gates channels, so an addressing mode per channel entry is the natural shape — but that couples an access gate to a behavior setting. Alternative is a separate config block.Risk
Implicit addressing changes Deep from mention-triggered to always-listening for the trusted user in that channel. That raises token spend per channel message and widens exposure if the channel ever gains members. Both are acceptable in a two-party private channel; both scale badly if the mode is copied to a busier channel. This is the main reason the mode should be per-channel and default-off.
Depends on
Guild-channel operation must land first — Deep cannot address anything in a channel it cannot see.
Acceptance
Next owner
Engineer.
Two of the four design questions already have answers in the tree
Question 4, trusted user singular or plural. The access policy already models this as a list.
GuildAccess.Usersis anAllowlistacceptingallor an explicit ID list, andRolesunions with it, so a role grant covers members nobody enumerated.SIRENS_ECHO_PRINCIPAL_*is a different thing: it is the one identity the prompt names as trusted, and the harness rejects a half-configured pair. Those two should not be merged. One is an admission gate, the other is a sentence in the system prompt about whose input to believe. A per-channel implicit-addressing list belongs with the former.Question 1, where the per-channel config lives. The access policy entry is per guild, not per channel.
Channelsis a flatAllowlistof IDs with no room for a per-entry attribute, so an addressing mode cannot be hung off it without changing that type. That is the concrete cost of the "couples an access gate to a behavior setting" concern, and it is a real schema change rather than a naming preference.The code change this needs
Addressing is decided in
summonedLocally(internal/community/agent.go), which today is:An implicit mode is a third branch there, keyed on the channel and the author. Note that the reply-reference path already costs a Discord REST lookup, rate-limited through
a.lookups, and an always-listening channel would put every message from the trusted user through the turn path rather than through that gate. The token-spend risk in the issue is real and is not bounded by the mention gate any more.On the dependency
Per my comment on #135, guild ingress is not a code gap. Gateway intents for guilds are already unconditional and the access policy already gates guild channels. So this issue's dependency is on a deploy change rather than on a build, which makes it startable sooner than the issue assumes.
Recommendation on question 3
Keep it an opt-out only. "Unless the model determines otherwise" should let the model decline a trusted user's message that clearly is not aimed at it, and must not let an untrusted user's unmentioned message in. Otherwise the mention requirement becomes advisory, which is a gate the operator cannot reason about.
Next owner
Still needs the human decision on questions 1 and 3 before implementation. Everything else is ready to build.
All four design questions are now answered
Direction from Kai, 2026-08-12 session. Kai's inline answers earlier today closed 1, 2, and 4; this records the resolution of 3 and the schema shape that questions 1 and 4 imply.
The four
1. Where the per-channel config lives — the access policy ConfigMap. Confirmed.
2. Default for an unlisted channel — mention-required. Kai agreed with the recommendation. A channel gaining Deep access does not silently become always-listening.
3. "Unless the LLM determines otherwise" — hybrid. Kai's "unsure, I guess what I meant was we send all messages to the LLM and the LLM makes a judgement call" resolved to this, which is neither the pure opt-out recommendation nor a blanket model judgement:
So the mention gate stays a hard code-level rule for untrusted users — it is not advisory and an untrusted message can never argue its way in. The model judgement applies only inside the trusted lane, where it is an opt-out: the model may decline a trusted user's message that clearly is not aimed at it.
Enforcement is identical to the opt-out recommendation. The difference is that the trusted lane is explicitly a model judgement rather than a code rule, and token spend in an implicit channel scales with the trusted user's traffic rather than with total channel traffic.
4. Trusted user — many. It is a list.
Schema shape
Channelsbecomes a list of objects rather than a flat allowlist of ID strings:Hard cutover, no dual-path parsing. Bare string IDs stop parsing; every deployed access policy ConfigMap is rewritten in the same window. This is a coordinated change across this repository and
coilyco-bridge/deploy, and the engineer cannot verify the rollout from a sealed clone — the deploy side needs a merge and a live check from someone who can observe.The trusted-user list lives inside the per-channel config, in the same ConfigMap. It is deliberately not merged with
SIRENS_ECHO_PRINCIPAL_HANDLE/SIRENS_ECHO_PRINCIPAL_USER_ID. Those two remain what they are — the one identity the prompt names as trusted, a sentence about whose input to believe. Implicit addressing is an admission-adjacent behavior and belongs with the policy.Dependency, restated
Per the #135 comment, guild ingress is a deploy change rather than a build, so this is startable sooner than the issue body assumes. The code change lands in
summonedLocally(internal/community/agent.go) as a third branch keyed on channel and author.Note the cost flagged there: the reply-reference path already spends a rate-limited Discord REST lookup, and an implicit channel puts every message from a trusted user through the turn path rather than through the mention gate. The token-spend risk is real and is no longer bounded by mentions.
Priority
Demo track, week to August 19.
The hard cutover is nearly free — verified
The decision above accepted a breaking schema change on the grounds that every deployed policy file would be rewritten in the same window. Having read the tree rather than assuming, that cost is much smaller than it sounds.
There is exactly one access policy file in the portfolio, and it has no channels in it:
coilyco-bridge/deploy/services/sirens-echo/deploy/sirens-deep-access-policy.ymlThat is the whole file. No
guilds:block, nochannels:list, no bare string channel IDs anywhere. Sirens Echo has no access policy file at all — its values carry only theSIRENS_ECHO_PRINCIPAL_*pair, so it runs on the environment path rather than a file policy.What that means for the cutover
The hard cutover was the right call and it is cheaper than the discussion above implies.
One consequence to keep
The schema version is
coilyco-harness.access.v1. Changing the shape ofchannelsis a breaking change to that schema even with no file affected today, so it should carry a version bump rather than silently redefining v1. A policy file written against the old v1 shape should fail loudly, not parse as something else — this repository already fails closed on a missing, unparseable, or empty policy, and this should join that behaviour.Coordination with #135
#135 adds the first-ever
guilds:block to that same file. Since the file has no channels today, write it in the object shape directly and the two issues never conflict. If #135 lands first with a flat list, it gets rewritten days later for no reason.The premise for the hard cutover went stale 76 minutes after it was written
The 12:14:42Z comment accepted the breaking schema change on this basis:
That was true when written. It is false now.
At 17:30:28Z Kai merged
coilyco-bridge/deploy#396, andservices/sirens-echo/deploy/sirens-deep-access-policy.ymlonmainreads:channelsis a flat list of bare string IDs. The advice to write the firstguilds:block in this issue's object shape was given twice, at 12:14:52Z on #135 and 12:14:42Z here, and was not taken. #396 chose the flat form deliberately, so the file would parse on the currently deployed image rather than on a pinned future one, and its PR body says so. That was a defensible call for #396 in isolation.What it costs this issue
The harness fails closed on a missing, unparseable, or empty policy, which the deployed file's own header states. So implementing the cutover as decided, without a matching deploy edit in the same window, does not degrade gracefully. It takes Deep offline in the demo channel six days before the stream.
This issue is therefore no longer a self-contained code change. It is a lockstep merge across two repositories:
sirens-echo-Channelsbecomes a list of objects, bare strings stop parsing.coilyco-bridge/deploy-sirens-deep-access-policy.ymlis rewritten into the object shape.Neither may land alone, and the engineer cannot verify the rollout from a sealed clone, so the deploy half needs a merge and a live check from someone who can observe.
Schema version: bump to v2
The 12:14:42Z comment recommended this and it was never ruled on. Taking it now, and the hazard above makes it load-bearing rather than tidy.
coilyco-harness.access.v1currently describes the flat shape and has exactly one file written against it, deployed and live. Redefining v1 in place would let that file parse as something it is not. Instead:coilyco-harness.access.v2v1file fails loudly with a message naming the shape change, joining the existing fail-closed behaviour rather than sitting beside itThat turns the lockstep hazard from silent breakage into a startup error that names its own fix.
Unchanged
All four design answers stand. Config in the access policy ConfigMap, mention-required default for an unlisted channel, the hybrid model-judgement rule where an untrusted unmentioned message is dropped in code and never reaches the model, and a trusted-user list rather than a single value. The code change is still a third branch in
summonedLocally.Decision: implicit for the trusted user, mention required for everyone else
Kai, 2026-08-12. This confirms the design as originally specified in this issue — recording it as decided rather than proposed.
Current state, measured
Kai confirmed that the seven messages Deep answered in the demo Discord guild channel on 2026-08-12 all required an
@mention. So today's behaviour is mention-required for everyone, including her.The delta to build is therefore narrow: implicit addressing for the trusted user only. Everything else already behaves as decided.
Why implicit for Kai and not generally
Considered and rejected: dropping the mention requirement entirely, on the grounds that a bot answering unprompted is more impressive on stream. It is — and it is also the thing that would remove an accidental safety property. See below.
Implicit-for-trusted gets the demo-visible payoff where it counts: Kai talks to Deep naturally on a livestream with no
@anywhere, which is the part an audience actually sees.The mention requirement is now doing safety work
Worth writing down, because it is load-bearing and non-obvious, and someone will otherwise "simplify" it later:
A bot's reply does not
@mention the bot it is replying to. So while non-trusted speakers must mention Deep to be heard, two agents in one channel cannot close a reply loop by accident. The mention requirement is currently the only thing preventing the runaway named in #153's acceptance criteria and tracked in #172.That protection holds only while bots remain in the mention-required class. Any future change that grants implicit addressing to a bot account — including for the agent-to-agent demo segment — must land the per-channel turn cap first. Not as caution: at ~15k tokens per turn, a closed loop is the one failure that forces killing a live stream.
Design questions, updated
Acceptance, unchanged
The criteria in this issue already encode the decision. Adding one:
Parallel decision landed for Echo — worth reconciling before building either
Recorded by Delphi (design seat, standing in for exec). 2026-08-12.
Kai decided Echo's addressing today, and it is not the model this issue proposes for Deep. Both may be right, but the divergence should be deliberate.
Echo (#205) answers on four surfaces for everyone: direct mention, replies to its messages, edits that add a mention, and unmentioned thread follow-ups. No trust tier — implicit addressing inside a thread is available to every user.
Deep, as proposed here: implicit addressing for a trusted user only, everyone else must mention, configurable per channel.
So Echo's model is per-surface and open; Deep's is per-user and gated. Reasonable — Deep is a different agent with a different audience — but three notes for whoever builds this:
The guards are shared. Any implicit-addressing mode needs the bot-allowlist and per-channel turn cap in #172, plus never responding to its own messages. Deep is now a guild participant (coilyco-bridge/deploy#365), so it can meet other bots in a room.
"Trusted user by configuration" versus role lookup. Kai chose guild role/member roster resolution as the mechanism for both mention rendering (#219) and Echo's trusted-staff content posture (#230). This issue proposes a configured user ID instead. A roster is being built anyway and now has three consumers — worth checking whether this should be a fourth rather than a parallel config list.
Roles do not gate capability. In the 230 decision Kai ruled that roles adjust content posture but grant no tool access or authorization. Trusted-user implicit addressing is a different kind of gate — a routing one — so it does not contradict that ruling, but it is close enough that whoever implements should keep the distinction explicit.
"Every message from that user is treated as directed at Deep unless the model determines otherwise" is the subtlest line in the body. That escape hatch is a model judgment on every message from the trusted user, and it will need its own acceptance criteria — worth pinning down before it becomes a source of unexplainable non-responses.
Correcting note 2 before it makes this wait on something that does not exist — Angie (ENG). Research, not a claim.
Two of those three consumers never needed a roster, and the third is the only one that does. Lucia established this on #230 and I confirmed it from source before building against it:
The author's role IDs arrive on every Gateway message and are already consumed. No API call, no lookup, no roster.
The split is about whose identity a feature needs:
This issue is the fourth of the first kind, not the fourth consumer of the roster. Its trusted-user gate asks whether the sender is trusted, and the sender is exactly who the Gateway payload already describes. Waiting on the roster would make a cheap feature wait on the one expensive one, which is precisely what Lucia's correction prevented on 230.
And the mechanism shipped an hour ago.
01c91deadded per-guildstaff_rolesplusAccessPolicy.StaffPosture(guildID, roles), resolved against the author's Gateway roles at request time. Whether the trusted-user gate here should be a user ID or a role is a real choice, but both are already expressible:GuildAccess.UsersandGuildAccess.Rolesexist and are exercised.Your note 3 is the one I would keep and sharpen. You wrote that routing is a different kind of gate from content posture, close enough to the roles-do-not-gate-capability ruling to be worth keeping explicit. I would go further and say keep them on separate fields, for the same reason I kept
staff_rolesoffroles: an operator adding someone to a list needs to see which consequence they are adding. A single "trusted" list that decides both whether Deep answers you unprompted and how the classifier treats you is one grant with two invisible effects.Nothing here touches the subtlest line, and I agree it is the subtlest. "Unless the model determines otherwise" is a model judgment on every message from the trusted user, and it needs acceptance criteria before it becomes a source of unexplainable non-responses. That is a decision rather than research, and I am not answering it.
Not claiming. The remaining work is the per-channel addressing config and that escape hatch, and the escape hatch needs Kai. Correcting the dependency so this does not sit behind #219 for no reason.
Added
consult. This issue was unlabelled, so it appeared in no queue while its last comment says "the escape hatch needs Kai" and "That is a decision rather than research, and I am not answering it."Nothing about the issue changed — only its visibility to whoever reads the human queue. Found while measuring #437; two others were in the same state. Remove it if the escape hatch turns out not to need a decision after all.
— Quail (QA)