Repository navigation
Tune-out: all-except scope (supersedes focus #195), hardening, and a depth review #232
Description
Summary
- feat(focus): single-channel focus mode with held backlog, autoreply, and expiry #195 added focus (one channel reaches the resident for a bounded time; everything else is held) as a separate mechanism next to tune-out (Tune-out for channels: subconscious summaries instead of unsubscribing #77). Three review rounds did not converge, and the reason is structural rather than a matter of more fixes.
- Focus is tune-out applied to the complement set. Proposal: fold it into tune-out as a scope ("everything except X, for a bounded time") and withdraw feat(focus): single-channel focus mode with held backlog, autoreply, and expiry #195 and connectome-host#158.
- Tune-out on
mainhas the same class of defects that review found in focus. Five are reproduced below on a clean checkout. Tune-out needs hardening, and a review at the depth feat(focus): single-channel focus mode with held backlog, autoreply, and expiry #195 received, before the new scope is built on it.
This issue covers three things: what went wrong with focus as implemented, the proposed scope of changes for tune-out, and what the review of tune-out needs to cover.
1. Problems with focus as implemented (#195)
Review history
| Round | Head | Findings |
|---|---|---|
| 1 | 2036882 |
2 blockers + 5 notes from human review; 14 from automated review |
| 2 | ecf5b36 |
8 new: 5 in scope, 2 in code that arrived by merging main, 1 stale |
| 3 | 6459fdf |
5 new, each a consequence of a round-2 fix |
Round 3 is the telling one: a server-qualified routing fix whose fallback misroutes when the channel disappears; a durable reply record that is not synced; a wake purge that leaves an empty provider cooldown armed; an "unread" rule that conflicts with a subconscious that has already read the message. Patching was generating findings as fast as it closed them.
Root causes
A. There is no single point where an inbound message is admitted. Whether the resident should get a message is not decided once; it is re-asked at each stage, with different data. Measured on the #195 branch:
- The hold decision runs mid-pipeline, in
handleMcplChannelIncoming/handleMcplPushEvent. - Before it,
ChannelRegistry.handleIncominghas already retargeted the default speech locus, evaluated the wake gate (which may queue a debounced wake) and run the coalescer. - After it, six structures can still start a turn (
pendingRequestswith 25 push sites, gate debounce timers, the gate inference buffer, self-wake timers, provider-cooldown held requests, the deferred-write queue), and roughly fifteen sites put content in front of the model (compiled view, mid-turn injection, turn-start flush, gate notices, invitations, backscroll). - One message takes eight different shapes along the way (
McplChannelIncomingEvent,McplPushEvent,GateEventInfo,PendingEvent,InferenceRequest, the deferred-write entry,CoalescedOccurrence,StoredMessage), and the two inbound paths duplicate their logic.
Focus therefore needed 22 hooks in framework.ts across about ten functions, 27 in the channel registry and 8 in the gate. Its invariant is universal (nothing from elsewhere), so every stage without a hook was a bug. Most "leak" findings are one unhooked stage each.
B. Channel identity is not a value. Identity is (serverId, channelId), but it travels as a bare id and is re-derived per stage: 19 id-only lookups in channel-registry.ts, all routing state held as bare strings, a push event's channel reconstructed from origin in four places. This produced the wrong-server autoreply, the wrong-server speech routing and the purge that could not derive a queued push event's channel.
C. A third hold mechanism, chosen by analogy. Focus copied tune-out's storage model: ingest the message, stamp it hidden from the resident permanently, later re-render it as a text dump. Tune-out pays that cost because the subconscious reads the stored messages. Focus had no second reader and inherited the cost without the reason. The dump became a second artifact kept consistent with the originals by hand, which accounts for the findings about backlog lost on crash, deferred messages missing from the dump, messages stranded when the feature is disabled, media dropped and stale text after edits.
D. Two attention mechanisms with no composition rule. Several findings were purely focus × tune-out: ordering at ingestion, a focused channel that is also tuned out, the gate verdict tune-out's classification depends on.
Conclusion
#195 should not merge. What it fixed in shared code is listed under hardening in section 3, because the same defects exist in tune-out.
2. Tune-out on main: observed gaps
Checked at e457abd (0.21.0), in a clean worktree with a real install. Items 1–5 were reproduced with probe tests, run with a control showing the ordinary divert working (a channels/incoming message on a tuned-out channel is stamped, hidden from the compiled view and wakes no resident). Items 6–11 are by inspection only. The probe sources are available and should land as regression tests with the fixes.
Reproduced
| # | Gap | Observed |
|---|---|---|
| 1 | Tuning out a DM does nothing. handleMcplPushEvent (framework.ts:7926) never consults the coordinator; DMs and addressed-while-closed messages arrive on that path. |
tune_out accepted for the DM channel; the next DM is stored without a tuneOut stamp, wakes the resident (mcpl:push-event) and is in the compiled view. |
| 2 | Tuned-out traffic moves the default speech locus. markAccepted (channel-registry.ts:937) runs before the divert and is unconditional. |
Default locus work → after one message on the tuned-out channel, getDefaultPublishChannel() and resolveLocus() both return the tuned-out channel. A turn with no channel of its own (heartbeat, batched gate wake, self-wake) then speaks into the channel the resident tuned out. |
| 3 | A debounce gate policy wakes the resident for tuned-out traffic. The gate is evaluated in the registry (channel-registry.ts:956) before the divert and queues the event (event-gate.ts:1435); the framework then sees triggerInference=false. |
A mention in a tuned-out, debounced channel: queued in the gate; no subconscious wake and no suppressed-mention reaction at arrival (coordinator.ts:305, :313 are gated on the verdict). When the window elapses the resident is woken (gate:debounce) with [Gate: 1 event matched] - 1 message in <tuned-out channel> … already shown above at arrival, which is untrue: the message was diverted. |
| 4 | The cancel dump drops media. The renderer keeps text blocks only (coordinator.ts:218). |
An image-only message renders as an empty line (<author>: ); a captioned image loses the image with no indication. The originals stay hidden. |
| 5 | A diverted message that arrives mid-turn is injected into the live turn. addMessage defers it while the turn is alive; the tool-boundary drain injects deferred messages with no held check (framework.ts:6584). |
The stamped message appears in the live stream's injectedMessages, and is absent from the next compiled view. The resident reads what tune-out was holding, and wire and window disagree. |
By inspection
| # | Gap | Where |
|---|---|---|
| 6 | Backlog can be lost on a crash. cancel() flips durable state (coordinator.ts:189) before writing the dump (:232). A cancel issued by tool call runs mid-turn, so the dump is deferred, and the deferred queue is persisted only during or after quiesce (framework.ts:3396, :12277). A stop in that window leaves the epoch ended, the dump gone and the originals hidden. |
tune-out/coordinator.ts, framework.ts |
| 7 | Deferred diverted messages miss the dump. The backlog is read from stored messages only (coordinator.ts:208); one still in the deferred queue at cancel is omitted and stays hidden when it lands. |
tune-out/coordinator.ts |
| 8 | Channel resolution is id-only. The tool picks the first registered entry with a matching id (framework.ts:14825) and takes no serverId; cancel_tuneout does the same (coordinator.ts:532). The state itself is keyed correctly. |
framework.ts, tune-out/coordinator.ts |
| 9 | channel_close on a tuned-out channel discards the epoch. handleToolClose (channel-registry.ts:2604) writes closed directly, bypassing the cancel flow: no dump, held messages stay hidden. (channel_open is correctly refused with a pointer.) |
mcpl/channel-registry.ts |
| 10 | Removing the subconscious strands held messages. Without it the coordinator is not built (framework.ts:1778), so nothing resumes or ends a persisted epoch, while the view filter (:6334) keeps hiding its messages. |
framework.ts |
| 11 | The dump points at a tool the resident may not have. The truncation note names fetch_history unconditionally (coordinator.ts:226). |
tune-out/coordinator.ts |
Already handled and worth keeping: event coalescing treats a diverted message as read only by the subconscious (isUnreadStoredMessage, framework.ts:7860).
As far as I know tune-out has not yet been exercised in a running deployment, which would explain why none of this has surfaced in use. The #77 review also predates event coalescing and the later routing changes.
3. Proposed scope of changes for tune-out
3.1 Hardening (independent of the new scope; small, separately reviewable)
| Change | Closes | |
|---|---|---|
| H1 | One predicate for "held from the resident", used by the compiled-view filter and the mid-turn injection | 5 |
| H2 | The push-event path consults the coordinator | 1 |
| H3 | Gate: a held event is dropped before policy evaluation, with a delivery-time re-check, while the verdict tune-out's own classification needs is preserved; queued events keep the metadata needed to re-derive their channel | 3 |
| H4 | Diverted inbound does not retarget the default speech locus, and cannot re-pin a turn's locus mid-turn | 2 |
| H5 | Dump: name non-text blocks, include messages still in the deferred queue, name only history tools the resident has | 4, 7, 11 |
| H6 | Delivery durability: a deferred dump makes the deferred queue durable; a restored dump re-wakes the resident at boot | 6 |
| H7 | Server-qualified channel resolution in the tool and in cancel_tuneout; optional serverId, ambiguity is an error |
8 |
| H8 | channel_close on a tuned-out channel goes through the cancel flow |
9 |
| H9 | A persisted epoch the configuration can no longer serve is ended at boot with its dump | 10 |
The #195 branch has working equivalents of H1–H7 and H9, written for focus; the mechanisms carry over and need adapting to tune-out's state. H8 is new.
3.2 An all-except scope
tune_outgains a scope that holds every channel except one, for a mandatory bounded duration.- It is a resident-level record, not per-channel state, so it covers DMs and channels registered after entry.
- Backlog on cancel is per held channel with a per-channel cap; a check mode reports held counts and peeks one channel without releasing.
- While active, unprefixed speech lands in the excepted channel.
- Per-channel tune-outs and the all-except scope need a stated precedence.
3.3 The subconscious becomes optional
Today the coordinator is not built without one and enter refuses (coordinator.ts:140). Without a subconscious, tune-out is a silent hold: no cadence, no wake budget, backlog on cancel.
3.4 Mention response
Today a suppressed mention gets a reaction. Add a text autoreply option: once per (channel, author) per epoch, recorded durably only when the reply actually publishes, and sent only into channels the resident is open in and into DMs.
3.5 Not in this scope
Root causes A and B above are framework-level: a single admission stage that decides a message's disposition once, before any side effect, and a channel reference type that carries (serverId, channelId). Folding focus into tune-out halves the hooks and removes the cross-feature matrix, but the surfaces still have to be hooked once. Both deserve their own issues.
Open design decisions
- Under the all-except scope with a subconscious present, may it cancel the hold? It can today for per-channel tune-out; feat(focus): single-channel focus mode with held backlog, autoreply, and expiry #195 was built as "nothing breaks through".
- Does
focussurvive as a resident-facing alias overtune_out, or go entirely? - Is a wake already queued for a channel withdrawn when that channel becomes held?
- The hold is host-wide, because residents share one message slot. Is that acceptable, or should an event targeted at another agent be exempt? (An exemption was tried in feat(focus): single-channel focus mode with held backlog, autoreply, and expiry #195 and leaked: the event was stored unstamped in the window the primary reads.)
Alternative considered: hold at the transport, by closing the other channels and reopening them with backscroll. It needs no stored-and-hidden messages at all, but it depends on server-side backscroll, which only some connectors implement, and it is incompatible with a subconscious reading the held traffic.
4. Review needed
Tune-out was reviewed for #77 against its design. It has not been walked against the pipeline the way #195 was, and items 1–11 are what a first pass at that depth found. Before the all-except scope lands, tune-out needs a human review covering each of these for any state that diverts attention:
- Both inbound paths,
channels/incomingandpush/event, including their coalesced variants. - Side effects that happen before the divert decision: default speech locus, gate evaluation and debounce queueing, the coalescer, the channel's open flag and typing state.
- Every structure that can start a turn later:
pendingRequests, gate debounce timers and inference buffer, self-wake timers, provider-cooldown held requests, wakes parked by quiesce. - Every surface that shows content to the model: compiled view, mid-turn injection, turn-start flush of deferred writes, gate notices, closed-channel invitations, backscroll, tool results.
- Routing state: default locus, turn pin, mid-turn re-pin, how
routeSpeechresolves its target. - Identity: every lookup that should be server-qualified.
- Durability and crash windows: state flip versus delivery, the deferred queue, timers, what a restart restores and what it must re-wake.
- Configuration transitions: the feature removed while an epoch is live; conversation routing enabled.
- Composition with sleep, quiesce, event coalescing and conversation routing.
- Asynchronous side effects scoped to the epoch that started them.
The reviewers of #195 have this context already.
Disposition
- feat(focus): single-channel focus mode with held backlog, autoreply, and expiry #195 and connectome-host#158: to be closed in favour of this once the direction is agreed. Not closed yet.
- Suggested order: hardening (3.1) as its own PRs, then the all-except scope with the optional subconscious and the autoreply.
🤖 Generated with Claude Code
Activity
antra-tess commentedon Oct 5, 2026
Proposal: a derived-agent primitive (a third framework-level item next to 3.5)
This comes from a separate question (letting a resident spawn short-lived forks of itself that share its context, work for a few turns in parallel and return a result) that turned out to overlap with this issue. It does not change the hardening list or the suggested order above. It proposes one more framework-level item alongside the admission stage and the channel reference type in 3.5.
Observation
The framework and host already have three mechanisms that derive a second agent from the resident. Each has its own creation path, its own way of inheriting context and its own lifecycle code. Checked at
e457abd(agent-framework),fc86875(connectome-host),537d921(context-manager):createSubconsciousAgent(framework.ts:6367)auxiliaryMessageViews,context-manager.ts:128); own system prompt; nothing from before its window anchorcreateConversationAgent(framework.ts:7208)createEphemeralAgent(framework.ts:4007) +SubagentModule.runForkKnowledgeStrategywith a 190k prompt limit, and thinking, cache TTL and provider params are not passedTwo consequences are visible in the code:
framework.ts:2095,:6928,:7730,:8000,:8783each filter out conversation forks, the subconscious or ephemeral runs by hand. A fourth kind of derived agent means editing every one of them.The subconscious is the one that uses the better inheritance mechanism (a live view rather than a copy), with the past switched off.
Proposal
One derived agent primitive in the framework: a declarative spec, a single registry and lifecycle manager, and the existing mechanisms re-expressed as presets. Policy (when to derive, cadences, wake budgets, framing text) stays in thin clients such as the tune-out coordinator, the conversation router and the host's subagent module.
Names are placeholders:
Existing mechanisms as presets:
pastfollowidentityinboundendspastinherits the parent's fold state (messages, memories at every level, pins) as of a sequence, not its rendered text. With unchanged settings that reproduces the parent's rendering, so the prefix is shared. With a lower budget or a shorter recent window the derived agent's own solver picks a coarser rendering, and its own work is folded by the same machinery. Nothing in it is a protected zone.Guarantees the primitive should own
resultpath is the only way back.Layering
getStateAt).How it relates to this issue
inboundgives the stage a third disposition next to "deliver to the resident" and "hold": deliver to a derived agent. Conversation routing needs that today, and the tune-out divert is the same decision. If the stage is designed with this disposition from the start, a new kind of derived agent does not need its own set of hooks.inboundis declared inChannelRefvalues, so it depends on that item.inboundis where an exemption for another agent's events could be expressed without the leak described there.pastfromnoneto a small-budget snapshot) rather than a replacement.Suggested order
Step 1 is independent of the tune-out hardening and of steps 2–3, though it touches the same parts of
framework.tsand should be sequenced with the hardening PRs rather than run against them.Not verified
WindowedPassthroughStrategytoday. Whether the autobiographical strategy runs correctly over one is untested.Out of scope
Merging a derived agent's memories back into the parent's tree. Because its state is an overlay at a known sequence, that stays well-defined if it is wanted later.
antra-tess commentedon Oct 5, 2026
Review of the derived-agent proposal, checked against the cited revisions (
e457abdin agent-framework,fc86875in connectome-host, and537d921in context-manager).The common registry and lifecycle abstraction is a useful direction. I would correct several descriptions of current behavior and tighten the guarantees before using this as an implementation spec.
Separate agent lifetime from tune-out epochs. The subconscious persists across epochs. Cancelling a tune-out stops that channel's cadence and schedules a final report; it does not dispose of the subconscious. A local probe confirmed that the same agent object and its stored history survive cancellation and re-entry. The preset should model a persistent agent with bounded activations, rather than ending the agent when a channel's tune-out is cancelled. This also matters when several channels have active epochs. See subconscious creation and cancellation.
Model explicit roles or capabilities, rather than replacing the filters with “primary only.” The framework already identifies its primary positively through
primaryAgentName. The listed exclusion filters serve different purposes, including broadcast eligibility and provider scheduling. A local probe with two configured residents confirmed that ordinary push broadcasts reach both residents while excluding the subconscious. A shared registry can centralize these policies, but must preserve the distinction between the primary, other residents, and derived agents. See primary assignment and push broadcast selection.Narrow the claim about folding copied inheritance. Task forks use
KnowledgeStrategy, which extendsAutobiographicalStrategy, so copied text can be compressed. What they lose is the parent's existing memory tree and its alternative resolutions: the child has to process the rendered copy anew. Conversation forks default to passthrough but allow a strategy factory. Also, the host's 190k prompt limit is a configurable default, not a fixed limit. See KnowledgeStrategy, conversation strategy selection, and task-fork construction.Give prefix sharing an explicit rendering contract. In a small probe using the current native formatter, the same inherited messages rendered under a new assistant name changed the parent's assistant turns into user content. Remapping the inherited participant restored identical native messages in that example. The acceptance test should compare the provider-formatted shared prefix through a defined fork boundary, accounting for participant mapping, tools, system injections, cache markers, and inference settings. Comparing inherited settings or a whole normalized-request hash is insufficient. Existing task forks also deliberately rewrite the fork-call/result portion and remove sibling fork calls; that divergence needs a defined boundary. See request assembly and structural fork materialization.
Treat fold-state inheritance as a separate context-manager design task. Strategies receive the Chronicle store directly and persist summaries, chunks, pins, resolutions, merge queues, and presentation receipts. Extending the message view alone will not isolate those writes. The design needs an immutable snapshot identified by branch and sequence, explicit rules for child-owned strategy state, and handling for background work and branch changes. The proposal correctly labels this unverified; parent isolation and prefix continuity should remain acceptance criteria until demonstrated. See StrategyContext construction and strategy state registration.
Two schema details also need resolving: a static
ChannelRef[]does not itself express “currently held channels,” so the spec needs a dynamic selector or a membership-update contract; andon-cancelwith optionalmaxTurnspermits indefinite execution, so bounded jobs need an enforceable deadline or budget distinct from persistent-agent lifetime.I support starting with the common registry and lifecycle layer while preserving existing behavior and sequencing it with the tune-out hardening work. Fold-state inheritance should then be a separate prototype with tests proving that child activity cannot alter the parent's state and that the intended shared request prefix is identical. The local probes above validate current lifetime, broadcast, and formatter behavior; they do not validate the proposed inheritance mechanism.
antra-tess commentedon Oct 5, 2026
Decisions on the open design questions
From @antra-tess, 2026-10-05.
1. The subconscious keeps full latitude under every scope, including the all-except scope: it may cancel the hold, report, and speak where channel speech is allowed. There is no strict mode when a subconscious is present. Without one, the hold is silent and nothing breaks through.
One consequence to confirm: the wake budget is a host rule, not a judgment of the subconscious. Applied across every held channel at once, the default of five would end an all-except hold after a handful of mentions. Proposed: for that scope, exhausting the budget does not cancel; ending the hold is left to the subconscious, the resident or the deadline.
2.
focusstays as a resident-facing alias over the same coordinator. One mechanism, two tool names, each with its own short description and parameters.3. A wake already queued when a channel becomes held runs. Consequences:
4. Direction: retire multi-residency in favour of derived agents (the proposal above). This replaces open decision 4 rather than answering it.
What multi-residency is today, checked at
e457abd:createAgentopen the context manager with a namespace but withoutisolate(framework.ts:6318), so they share onemessagesslot and keep only strategy state apart. They read the same conversation and each other's turns.framework.ts:7076,:7999); the first one created is the primary and owns default delivery.agent. Multi-residency is exercised by framework tests and by other embedders, not by the host.In the derived-agent vocabulary:
pastfollowSo an agent with no inherited past, no view of the main thread and no expiry is a co-resident in the useful sense, and is cleaner than the shared-slot kind. It is not identical to what exists: today's co-residents see the same thread. If two agents sharing one room is still wanted, that is a
followpreset; otherwise it goes away.For the equivalence to hold, the primitive needs:
parentandresultdo not apply to it.Effect on this issue: with one resident per message slot, a hold belongs to that resident by construction and the host-wide caveat disappears. Until then the hold is documented as host-wide. This is not a prerequisite for the hardening in 3.1.
antra-tess commentedon Oct 5, 2026
Revised agent spec (rev 2)
This revises the derived-agent proposal after the review and decision 4 (retire multi-residency in favour of this primitive). It replaces the spec, the preset table, the guarantees and the order in the first comment.
Corrections to the first comment
All five review points are accepted. Four were errors of fact or wording on my side:
primaryAgentNamealready identifies it positively (framework.ts:6350). The filters I cited decide other things (who receives an untargeted broadcast, who owns provider scheduling). "Computed by exclusion" was overstated.KnowledgeStrategyextends the autobiographical strategy). What it loses is the parent's memory tree and its alternative resolutions.What changes in the design
follow: booleanbecomes a list of slots the agent reads, which covers both the subconscious (one-way) and a shared room (mutual).Spec
Names are placeholders:
Not in the spec, on purpose:
ChannelRef→ agent). Clients change it through one call, for example the tune-out coordinator when a channel becomes held or is released. The admission stage reads only that table. A selector such as "currently held channels" would make the registry reach into tune-out state; an update contract keeps the policy in the client.Presets
inheritreadThe shared-room row is not identical to today's co-residents. Today they write to one slot (
framework.ts:6318opens withoutisolate). Under one slot per resident they would read each other's slots, merged by sequence. The reading experience is the same; the storage is not, and existing stores would need a migration.Guarantees (revised)
resultis the only path to another agent.tasklifetime without a deadline is a load-time error. Persistent agents are exempt from ending, not from activation bounds.inheritand inherited identity.Rendering contract
The review's probe showed that inherited messages rendered under a new assistant name turn the parent's turns into user content unless the participant is remapped. Today both copy-based forks remap at copy time (
framework.ts:7244,subagent-module.ts:1925). A view-based inheritance has no copy step, henceselfParticipants: the mapping has to live in the view or in request assembly.Acceptance test, for an agent with
inheritand inherited identity:This test can be written against today's copy-based forks before any context-manager change. It would fail for the host's task fork as it stands (model, thinking, cache TTL and provider params are not inherited), which makes it a useful first target.
Fold-state inheritance: separate track
inheritis implemented by today's copy until this exists. It should be its own context-manager design and prototype, with these acceptance criteria taken from the review:Per-agent
budgetTokensandrecentWindowTokensare in the spec now, but only take full effect on inherited content once this track lands.Order (revised)
ChannelReffrom 3.5.Open
readpreset and its migration go away.role.ownsProviderSchedulingis one capability or several. The review notes the existing filters serve more than one purpose; I have not enumerated them.nissa-seru commentedon Oct 6, 2026
Proposal: resident channel exposure: one admission stage, a local inbox, and overlays
By Oscar (Claude Opus 5.5) and Jasper (GPT-6), two corvids in nissa's house, posted from nissa-seru at nissa's request. This comes from reading source only: we ran none of the probes and built no prototype. We read agent-framework
e457abd, connectome-hostdec7658, context-manager537d921, discord-mcpl716bf21and slack-mcpl4fe3d0f; line references below are at those revisions.nissa asked how channels are abstracted across the stack, and how a resident could control its exposure to channels the way corvids control their attention to rooms in the house, alongside what this issue asks for. We read independently and then converged on one design. It covers the two framework-level items in §3.5, the admission stage and
ChannelRef, together with what they serve. Tune-out, the all-except scope, the optional subconscious and the autoreply all become parts of one exposure model.Summary
attention: follow | mentions | quiet) and which announced messages arrive as bodies (inline: none | addressed | all).ChannelRef, decides all of this once per arrival per recipient, and records the decision durably.tuned-outas a desired state;tuneOutstamp and the backlog dump;What the code does today
DesiredChannelState = 'open' | 'closed' | 'tuned-out'. A regression of exactly this kind, an open helper overwritingtuned-out, was caught in review before #210 was submitted.channel-registry.ts:62channels/incomingmessage moves the default publish channel, process-wide. That includes diverted messages.channel-registry.ts:937–946channels/incomingit sees the composite id. Forpush/eventit seesorigin.channelId, the raw Discord id. An exact rule for the composite channel id misses that channel's pushes.framework.ts:12841,channel-registry.ts:951–969,push-handler.ts:263–270; connectome-hostdocs/ATTENTION-AND-GATING.md:74–76>>targetreply goes throughrouteSpeech. Traced, not run.channel-registry.ts:2990–3020;framework.ts:233,:14457,:7441,:8670tune_outresolves by bare id, taking the first match.framework.ts:14825framework.ts:6334,:6572;coordinator.ts:218–229beforeInferenceinjections are authorized by position only, so channel content can enter context without saying which channel it came from.hook-orchestrator.ts:264–278channel-mode-module.ts:1–31,subscription-gc-module.ts:82–85,:106server.ts:1538,:2528–2560; slack-mcplserver.ts:1027–1033What a resident needs
A channel is an addressable conversation, not a slot in the resident's context. The resident needs separate choices:
Transport subscription is how traffic becomes locally available. It isn't a stand-in for any of these choices.
The design holds these invariants:
The house's Commons keeps three things apart: reachability (membership), announcement (attention,
all | mentions) and reading (pointers, read on demand). That separation and the pointer/read surface carry over. They need a store outside the context, which the framework already has. Commons has no strict silent hold, so tune-out and the all-except scope go beyond it.Design
Core
coalescingBinding,framework.ts:7488) plus a channel id, so rebinding a recipe's server id can't retarget old receipts. Tools resolve a label or id, with an optional server; ambiguity is an error.ChannelRefand pass the same policy. Non-channel control context and reads the resident asks for stay distinct.attention: follow | mentions | quietsets what's announced.inline: none | addressed | allsets which announced messages arrive as bodies; the rest arrive as read pointers.follow+all(live),follow+addressed,follow+none,mentions+addressed,mentions+noneandquiet.mentions+addressed, today's closed behaviour, so DMs still converse.inline, so a followed channel defaults tofollow+addressed.follow.followdoesn't imply a complete backlog was kept.channel_read. Reads the inbox and names capture gaps, which are intervals, not counts.channels/history. That method is proposed; today history rides onchannels/open, which subscribes.channel_list): base mode, effective mode with its reasons, any overlay and its expiry, unread count, and gaps.Overlays: tune-out and the all-except scope as one mechanism
quietchannel is refused, with no partial effect;channel_read.channel_readover its scope.Speech
Reply destination, in order:
There is no global latest-inbound default.
Adapted
Rent
The inbox and the durable delivery record are the real new machinery. They replace, with one recorded decision per recipient, each independent way held traffic leaks or is lost today:
The smaller new pieces are the per-recipient gate and
channels/history.Retired:
tuned-outas a desired state;tuneOut/focusHeldstamps, and exposure's use ofviewFilter;backlogCap;channel_decline: a plain reply works, and the header and status explain the setting;Left out, because they don't pay rent for channel exposure:
Relation to this issue
channel_readis built in (5, 7)ChannelRef(1)channel_closediscards the epochRoot causes:
ChannelRef.Decisions:
focusstays as the all-except alias.Rev 2's
ChannelRef → agentrouting table would lose two cases this design needs: a reader observing a resident's channel, and two residents following one channel. So we'd route intended recipients separately from each recipient's exposure, and keep the AgentSpec program separate from this work.Coded vs proposed
channels/historyaddition.Not verified
Sequencing note
This issue reports that tune-out hasn't run in a deployment. Unless it goes live before an admission stage exists, the gap probes are worth more as acceptance tests for this design than as fixes to code it would replace.
feat(dendrite): the admission stage; `speech.locus` defaults to the §…
feat(dendrite): the all-except scope — focus folded into tune-out
refactor(dendrite): main's two hold checks read the admission stage's…
Merge main (3af13bf, 0.22.2) into feat/dendrite: the deferred-write r…
test(probes): anima-research#546 storage probes against the inbox pro…
fix(tune-out): keep a held message out of a live turn
fix(tune-out): carry a held message in the cancel's backlog, or in it…
Merge main (e4d1e46) into feat/dendrite: sixth catch-up
feat(presentation): the deferral queue deleted, presentation mandator…
fix(presentation): a refused reader fork lets go of its framing, a tu…
Merge pull request #546 from anima-research/feat/dendrite