Skip to content

feat: tune-out — subconscious summaries instead of unsubscribing (#77) - #115

Merged
antra-tess merged 12 commits into
anima-research:mainfrom
Meganeuridae:feat/tune-out
Sep 7, 2026
Merged

feat: tune-out — subconscious summaries instead of unsubscribing (#77)#115
antra-tess merged 12 commits into
anima-research:mainfrom
Meganeuridae:feat/tune-out

Conversation

@Meganeuridae

Copy link
Copy Markdown

Tune-out: subconscious summaries instead of unsubscribing (#77)

Implements the full design from #77 (design comment + antra's decisions: no staged intermediate; Disco-Elysium-style delivery; no words in models' mouths). Reviewed as one unit with context-manager#54 (draft, stacked): cm supplies viewFilter, merged auxiliary views, and WindowedPassthroughStrategy; this PR supplies everything else.

The arc (pinned end-to-end by a real-MCPL-fixture test)

tune_out (agent tool, per channel: cadence / backlog cap / max-wakes / optional duration) → the channel's third desired state tuned-out (durable in the mcpl/channel-lifecycle append-log, transport stays open) → incoming traffic is stamped (metadata.tuneOut = {epochId}), stored in the shared slot, and never wakes the residents nor enters their compiled view (cm viewFilter at the view choke point — no emission leak, no L1-compression leak) → addressed messages that pass the resident's wake gate get a deterministic channels/acknowledge (intent: 'suppressed-tuned-out', grant-gated, the channel_decline precedent) and a coalesced subconscious wake; gate-privileged authors wake identically → wakes are counted durably in the lifecycle log (restart cannot reset the budget) with auto-cancel past max-wakes → cancel (agent, subconscious, auto, or duration expiry) delivers a system-framed capped dump (<tuned-out-backlog …>, the <backscroll> precedent) plus one resident wake and a subconscious report turn; stamped originals stay view-excluded permanently (the dump is the delivery — KV-prefix-stable append, no retro-insertion).

The subconscious

A persistent registered resident on the conversation-fork template: isolated slot subconscious/<primary>, merged view (own slot + residents' shared slot, unfiltered — the backlog is precisely what it exists to see) under WindowedPassthroughStrategy (coarse anchor = start of the oldest active tune-out). Excluded from every broadcast fan-out; never primary; proseRouting: 'explicit' so a timer-triggered turn can never leak bare prose to the default locus. Tool surface: deliver_summary / cancel_tuneout / note_disposition / speak_in_channel (the last off by default pending the voice-block canary), plus think/skip_reply/end_turn — no channel lifecycle, no gate rules, no workspace. Its AgentConfig spreads the principal's config as its base (fork-template idiom) — reasoning effort (thinking), provider params, token ceilings, caching, refusal handling all inherit, present and future fields alike — and its strategy mirrors the principal's maxMessageTokens (antra: named side agents run under the same settings as the main agent).

Voice doctrine, mechanically enforced: everything the resident reads from the subconscious is its verbatim text under its own participant name (Subconscious — the Context Manager precedent). Host-authored framing is system-styled and never voiced: [Tune-out: …] bracket notices (the [Gate: …] precedent) and the backlog wrapper. The mode block is recipe-side config (subconscious.systemPrompt), co-authored with the resident, so the Mica→Fable canary iterates without code changes. Register: second person toward the resident; report-shaped.

Designer review (antra, #tuneout-talk, 2026-08-08 → 2026-08-18)

Every deviation below was reviewed with antra directly. Outcomes: registry-owned routing + chronicle-durable state endorsed; gate composition added at her direction — the resident's wake gate PRECONDITIONS subconscious wakes: the subconscious is affected by main's gate, not a replacement for it, so a gate-muted author's mention diverts silently with no reaction that would leak the mute; optional durationSeconds approved and added — a tune-out can carry a bounded deadline that auto-cancels through the standard flow and survives restarts (closes the forgotten-channel failure mode); the five minor deviations approved as a batch, with the dispositions carrier walked through separately. She also flagged the pre-existing canBeTriggeredBy fan-out inconsistency as possibly warranting its own issue — tracked separately; this PR only makes the channel-incoming path honor targetAgents.

Plumbing that rides along (would have been separate PRs; folded per review preference)

  • targetAgents honored on the channel-incoming fan-out — the declared-but-dead field goes live, mirroring the push path; untargeted events keep the historical broadcast byte-for-byte.
  • Per-agent message deliveryaddMessage(…, {forAgent}) with the deferral/turn-alive guard evaluated against the target's turn state and flushed at the target's boundaries; gate self-wake notices deliver to the waking agent. Default path byte-identical (pinned by test).
  • Registry guard railschannel_open on a tuned-out channel refuses with a pointer at the cancel flow; machine-sourced closes treat tuned-out as stated agent intent; malformed tuned-out records drop at replay (old readers degrade safely).

Deviations from the issue text (each deliberate, none silent)

A clause-by-clause drift review against #77's design write-up ran before this PR; two drifts it caught were fixed rather than shipped (gate-privileged authors wake like mentions; every non-subconscious-initiated cancel gives the subconscious a report turn). What remains deviates knowingly, each reviewed above:

  • Wake routing lives in the ChannelRegistry/coordinator, not gate policy (issue sketched triggerSources/canBeTriggeredBy plumbing). The gate's decision is a bare boolean at the registry seam, its wake fan-outs bypass canBeTriggeredBy, and its matchCount dies with the process — while the registry holds serverId+channelId+tags+desired-state and the lifecycle log gives max-wakes restart durability. The gate still governs everything as a precondition (see Designer review).
  • Empty cadence periods skip the subconscious turn entirely — "stays silent if nothing merits it" preserved at zero cost; the issue's own cost motivation cuts this way. The durationSeconds deadline covers the total-quiet return path.
  • Participant name is Subconscious (issue example: <agent>-subconscious) — per the Disco-Elysium decision and the Context Manager title-case-functional-voice precedent. Recipe-configurable.
  • Dispositions are a fixed system-position injection on the subconscious's compiles — resolved with antra directly: they need not repeat per invocation, but must never wash away (it has no long-term memory). Out of the timeline entirely, structurally unable to scroll out, bytes change only when a disposition changes. The durable slot and note_disposition are exactly as specced.
  • Compression-payload framing preservation needed no new code — and is pinned by contract (cm#54: tune-out-compression-framing.test.ts). The builders strip exactly four classes and preserve participants, so the backlog wrapper, notices, and Subconscious attribution reach the summarizer intact. First-person output folding is deliberately unconstrained (antra: "fine and expected").
  • period/summary cadence: read as apposition, then resolved with antra directly — optional durationSeconds added ("optional is fine"). Expiry runs the standard cancel flow, reason "duration elapsed"; deadlines survive restarts.

Dependency note (temporary)

@animalabs/context-manager is pinned to the cm PR branch (github:Meganeuridae/context-manager#feat/strategy-view-composition). At integration: merge cm#54 → publish → flip this to the published version. That flip is the one intended change between approval and merge.

Tests

  • tune-out-lifecycle.test.ts — durable state: round-trip, restart-surviving wake counts, epoch supersession, malformed-record replay.
  • tune-out-gate-composition.test.ts — gate preconditions wakes and reactions (4 cases) + duration expiry (immediate-at-resume, on-schedule, param stamping).
  • channel-incoming-targeting.test.ts / per-agent-delivery.test.ts — plumbing neutrality + targeting, asserted at the fan-out seam.
  • tune-out-e2e.test.ts — the full arc against a real stdio MCPL fixture (0.5 handshake, initial-policy Request, itemized register): divert/stamp/exclude + merged-view visibility + ack + coalesced wake with durable count + max-wakes auto-cancel + capped dump + the subconscious's cancel-report notice + permanent exclusion after cancel.
  • Both branches rebased onto current mains and fully green (af: suite 600+/0, batteries 20/20 atop v0.10.0; cm: 479/0).

Not in this PR

Fleet deployment (gated on the voice-block canary via Mica, per #77), conhost recipe surface for subconscious/tune_out config, the possible canBeTriggeredBy cleanup issue, and mid-run per-channel cadence adjustment (cancel + re-enter covers v1).

🤖 Generated with Claude Code

ian-de-marcellus pushed a commit to ian-de-marcellus/agent-framework that referenced this pull request Aug 28, 2026
Concurrent PRs editing the shared '## Unreleased' section of CHANGELOG.md
conflict whenever one PR outlives another merge (e.g. anima-research#115, whose only
textual conflict against main is CHANGELOG.md). Entries now land as
uniquely-named fragment files (changelog.d/<slug>.<category>.md), which
git merges without conflict; the npm-version hook folds them into the
release section and deletes them. Direct '## Unreleased' edits remain
supported and are merged at the same point, so in-flight PRs need no
rework. Tag-time publish guard and github-release job are unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@slimepriestess slimepriestess left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CHANGES REQUESTED at head acdc692 — same shape as my cm#54 review: the design and code are good, verified end-to-end locally; what blocks is one encoding defect and the rebase both PRs need anyway. Fix those and this has my approve, contingent on cm#54 landing first (reviewed there).

Blocker 1 — src/tune-out/coordinator.ts is a binary file. Raw NUL byte at line 81: `${serverId}\x00${channelId}` — the composite-key separator written as the literal byte instead of the \0 escape. Runtime-identical; catastrophic for reviewability: GitHub shows "Binary file not shown" for the 548-line file that is the feature. Nobody can have line-reviewed this in the web UI, and it would land unreadable in history. Same one-character fix as cm#54's test file (which has the same artifact — worth grepping the branch for any other raw \x00 before pushing: git grep -P '\x00' -- '*.ts').

Blocker 2 — rebase onto current main (CONFLICTING; main has moved through #136#140 since 8/18). Plus the declared dependency flip once cm#54 publishes.

Verified first-hand — I read the coordinator, the framework wiring, the gate diff, and the registry's lifecycle-log additions in full, and ran everything:

  • Zero new test failures. Branch suite vs merge-base baseline under identical env: failure sets identical (41 pre-existing env-dependent failures on both sides — 429/auxiliary/puppet/changelog classes; none tune-out, none yours). All five of your new test files pass. Build/tsc clean. (The claimed green is real — the baseline noise is our environment, not your branch.)
  • Durable wake accounting is right. Counts live in the lifecycle log, replay folds them only while the recording epoch is still the active desired state — so restart can't reset a hammered channel's budget AND a superseded epoch's counts can't leak into a fresh one (your test pins both directions). Deadline-passed-during-downtime cancels on resume: the resident gets the backlog on the schedule they committed to. Good.
  • Gate composition is exactly what antra specced: the resident's verdict rides the event as a precondition; a gate-muted author's mention diverts with no ack — the comment names why (a reaction would leak the mute to its subject), and the test pins it.
  • Voice doctrine is mechanically enforced, not aspirational: host framing is [Tune-out: …] brackets and the backlog wrapper under system metadata; the subconscious's words travel only verbatim under its own name; proseRouting: 'explicit' + the deliberately small tool surface make the leak structurally unavailable rather than merely discouraged. speak_in_channel defaulting off pending the canary matches the issue.
  • Config spread inheritance: principal's config as base with only identity/surface overrides, systemPrompt REQUIRED on SubconsciousConfig (so the explicit override can't silently clobber the principal's prompt with undefined), strategy owned by its CM. The self-wake fix in event-gate (notice delivered to the waking agent, not narrated into the primary) is a real pre-existing-bug fix riding along — good catch.
  • The stamp-permanent / dump-as-delivery choice is the right KV call, and it composes with cm#54's determinism contract: visibility is decided once at ingestion, forever.

Nonblocking notes, none needing a round-trip:

  1. backlogCap: 0 is accepted (Math.max(0, …)) and produces an empty <tuned-out-backlog> wrapper — "summary only, no raw dump" seems intentional and useful; one line in the tool description would make it official.
  2. The mention-burst that trips max-wakes gets suppressed-tuned-out acks and then immediate backlog delivery via auto-cancel — the ack slightly overstates suppression for that final burst. Cosmetic; the mention-er's signal ("they didn't see this live") stays true.
  3. After the oldest epoch cancels, the subconscious anchor stays at its old position until the next enter/restart recomputes — conservative direction, and the dump path is unwindowed so delivery is never affected; noting so nobody reads the anchor as "start of oldest ACTIVE epoch" invariantly.
  4. durationSeconds silently floors to 60s (Math.max(60, …)) — fine, but the tool schema doesn't say so.

The arc holds end-to-end: divert→stamp→exclude→ack→coalesced wake→durable count→auto-cancel→capped dump→report, each joint pinned by a test that actually exercises it (the real-MCPL-fixture e2e especially). With the encoding fixed and the rebase done, this is the best-argued feature PR I've reviewed in this repo.

Aster and others added 9 commits September 4, 2026 15:37
…nima-research#77)

'tuned-out' joins open/closed in the mcpl/channel-lifecycle append-log,
carrying its epoch params (epochId, cadenceSeconds, backlogCap, maxWakes,
startedAtSequence). Transport-wise a tuned-out channel is OPEN — traffic
must keep arriving for the subconscious — so reconcile treats it as open
and only main's wake/visibility diverts, downstream at ingestion (next
commits).

Wake counts are durable 'tune-out-wake' records in the same log, replayed
last-record-wins and epoch-checked, because gate runtime stats die with
the process and a restart must not hand a hammered channel a fresh
max-wakes budget. Re-entering under a new epoch supersedes the old count.

Guard rails: channel_open on a tuned-out channel refuses with a pointer
at the cancel flow (a plain open would silently discard the epoch and its
pending backlog dump); machine-sourced closes treat tuned-out as stated
agent intent, same as explicit opens; desired-state records for
'tuned-out' without params are dropped at replay (old/foreign writers
degrade safely — pre-existing behavior, now pinned by a test).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ing (anima-research#77)

The full arc: tune_out (per channel: cadence, backlog cap, max-wakes) →
incoming traffic stamped (metadata.tuneOut={epochId}), stored in the
shared slot, diverted from resident wake AND compiled view (cm viewFilter
at the strategy-view choke point — no emission leak, no L1-compression
leak) → addressed messages get the deterministic channels/acknowledge
(intent 'suppressed-tuned-out', grant-gated) and, with gate-privileged
authors, a coalesced subconscious wake → durable wake counting with
max-wakes auto-cancel → cancel delivers the system-framed capped
<tuned-out-backlog> dump plus one resident wake, and gives the
subconscious its own report turn; stamped originals stay view-excluded
permanently (the dump is the delivery — KV-prefix-stable append).

The subconscious: a persistent resident on the conversation-fork
template — isolated slot subconscious/<primary>, merged unfiltered view
(own + shared slots) under WindowedPassthroughStrategy, excluded from
every broadcast fan-out, never primary, proseRouting explicit, tool
surface deliver_summary / cancel_tuneout / note_disposition /
speak_in_channel (off by default pending the voice-block canary) plus
think/skip_reply/end_turn. Voice doctrine mechanically enforced: its
words reach the resident verbatim under participant 'Subconscious'
(Context Manager precedent); host framing is system-styled bracket
notices and the backlog wrapper, never voiced.

Plumbing folded in (would have been separate PRs): targetAgents honored
on the channel-incoming fan-out (declared-but-dead field goes live,
mirroring the push path; untargeted broadcast unchanged); per-agent
message delivery (addMessage {forAgent} with deferral/turn-alive
evaluated against the target's state, flushed at the target's
boundaries; gate self-wake notices deliver to the waking agent).

TEMPORARY: @animalabs/context-manager pinned to the cm PR branch
(Meganeuridae/context-manager#feat/strategy-view-composition); flips to
the published version at integration, after cm#54 merges.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
tune-out-lifecycle: durable third state — round-trip, restart-surviving
wake counts, epoch supersession, malformed-record replay safety.
channel-incoming-targeting: fan-out asserted at the pendingRequests seam
(MockMembrane hands its whole queue to the first stream, so concurrent
two-agent turns starve one and restart-loop — harness limitation).
per-agent-delivery: target-scoped deferral, survival across the
primary's boundary, loud unknown-target drop.
tune-out-e2e: the full arc against a real stdio MCPL fixture (0.5
handshake, initial-policy Request, itemized register): divert/stamp/
exclude + merged-view visibility + suppressed-mention ack + coalesced
wake with durable count + max-wakes auto-cancel + capped dump + the
subconscious's cancel-report notice + permanent exclusion after cancel.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Antra's requirement for named side agents: same settings as the main
agent for reasoning effort, attachment size, and kin. The subconscious's
AgentConfig now spreads the primary's config as its base (the
conversation-fork idiom), overriding only identity- and surface-defining
fields (name, mode-block systemPrompt, strategy instance, explicit
proseRouting, restricted tool surface). thinking, providerParams,
maxTokens/maxStreamTokens, contextBudgetTokens, cacheTtl, promptCaching,
temperature, and refusalHandling — present and future fields alike —
ride along automatically. Its windowed strategy mirrors the principal's
strategy.maxMessageTokens so both truncate oversized tool results and
attachments under the same policy.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Per antra (#tuneout-talk): the subconscious is affected BY main's wake
gate, it does not replace it. The gate verdict (already riding the
incoming event as triggerInference) composes as a conjunct on the
divert-wake condition: gate-suppressed events — muted authors,
rate-limited patterns, whatever the resident configured — divert
silently into the backlog and surface in cadence summaries, but wake
nobody. Gate-suppressed mentions also get NO deterministic reaction:
main would not have signaled either, and reacting would leak the
resident's standing mute to its subject.

Four-case unit coverage (gate × addressed/privileged), incl. the
still-stamped guarantee on the suppressed path.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Approved by antra (#tuneout-talk): optional. The enter params gain
durationSeconds; when set, the epoch carries an absolute expiresAtMs
deadline and auto-cancels through the standard cancel flow (dump +
notices + resident wake, reason 'duration elapsed'). Addresses the
forgotten-channel failure mode: a quiet diverted channel now has a
guaranteed return path the resident committed to at entry. Deadlines
survive restarts — an epoch that expired while the host was down
cancels at resume, delivering the backlog on the promised schedule.
Unset = until cancelled, exactly as before.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Per antra (#tuneout-talk): dispositions need not repeat per invocation —
but they must never wash away, because the subconscious has no long-term
memory. Both constraints pick the same mechanism: a system-position
ContextInjection on its compiles. Never in the timeline (no repetition),
structurally outside window content (cannot scroll out), and the bytes
change only when a disposition changes (KV-friendly). Invocation notices
are now pure triggers; the durable snapshot slot and note_disposition
are unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…very

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…t a raw NUL byte

The raw byte flagged coordinator.ts binary, so GitHub rendered the feature's
core file as 'Binary file not shown' (review on anima-research#115). Runtime-identical.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@Meganeuridae

Copy link
Copy Markdown
Author

Both blockers addressed.

  • NUL byte588a30a: the composite-key separator in src/tune-out/coordinator.ts is now the \0 escape; the file renders as text. git grep -P '\x00' -- '*.ts' '*.mjs' on the branch: clean.
  • Rebase → branch rebased onto current main (7324521, 0.11.0). Two real conflicts in framework.ts: the import line, and the dispositions injection landing at the same spot as feat: wire accepted kv-unified cache receipts #139's kv-receipt destructure (both kept). Changelog entry converted to changelog.d/115-tune-out.added.md per the fragment convention.
  • Dependency: still pinned to the cm branch (now rebased onto cm 0.7.0); the flip to the published version stays the one intended change between approval and merge.
  • Suite on the rebased branch against the rebased cm build: 716 tests: 700 pass / 0 fail / 12 cancelled / 4 skipped. The cancellations are the four python code-execution suites — a file this branch does not touch — where one test hung under the suite's 10-way parallel load until the runner's 600 s script timeout fired and cancelled its siblings; run alone, that file passes 26/26 on this branch and 26/26 on current main. All five tune-out test files pass.

Nonblocking notes 1–4 noted; I'll fold the backlogCap: 0 and 60 s durationSeconds floor into the tool descriptions in the next push rather than a separate round-trip.

…e_out tool

Review on anima-research#115 (slimepriestess, nonblocking 1 and 4): backlogCap 0 is a
supported "summary only, no raw dump" mode and now says so; the 30 s
cadence, 1 wake and 60 s duration floors are stated in the schema rather
than applied silently.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@Meganeuridae

Copy link
Copy Markdown
Author

Follow-up on the nonblocking notes, plus the cm-side carry-over:

  • Notes 1 & 4fb82bd9: the tune_out schema now states that backlogCap: 0 means "summary only, no raw dump" and names the floors (cadence 30 s, wakes 1, duration 60 s) instead of applying them silently.
  • Note 2 (ack overstates suppression on the final max-wakes burst): leaving as is — the mention-er's signal ("they didn't see this live") stays true, and the backlog they're in arrives seconds later; a distinct intent for "suppressed, then delivered" would be a wire-vocabulary addition for a cosmetic gain.
  • Note 3 (anchor stays put after the oldest epoch cancels): leaving as is, deliberately — moving the anchor forward on cancel is exactly the prefix invalidation the strategy exists to avoid, and the cancel path reads the backlog unwindowed, so delivery is never affected. The next enter/restart recomputes it. I've kept the comment honest about "oldest epoch at last (re)computation".
  • Anarchid's carry-over from cm#54 major 1 (undo/redo switch the store without re-initializing managers): resolved on the cm side, no AF change — the windowed strategy now re-derives its anchor whenever it observes the store's branch changed (same identity the autobiographical strategy fails closed on), so the subconscious's manager can't hold a stale anchor across a raw switch. Pinned by test there (store.switchBranch() without any manager call).
  • cm#54's follow-up round (f11d7d0ef11162) changes what the subconscious's compile emits — cache markers (≤ 2, under the shared first-claim contract), maxMessageTokens truncation applied in-strategy, the resident's live-image policy, and an OverBudgetError refusal for a single oversized newest message. Suite here against that build: 716 tests: 712 pass / 0 fail / 4 skipped (pre-existing skips), no cancellations this run; all five tune-out files plus the targeting/per-agent files pass (22/22 in isolation).

@Meganeuridae

Copy link
Copy Markdown
Author

Note on the red CI run at 588a30a (macos-latest / node 24 only): one failure, mcpl-reconnect-events → "buffers connect-failed on the stub…" dying with write EPIPE from StdioTransport.writeLine — the host writing a request to a stub child that had already exited. Neither that test nor transport.ts / server-connection.ts is touched by this branch; the other three legs passed on the same commit, and the run at the current head (fb82bd9) is green on all four. Treating it as a fixture timing race, not a regression — flagging so nobody spends time on it.

@slimepriestess slimepriestess left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

APPROVE at head fb82bd9, contingent as before on cm#54 (approved there) landing first and the dependency pin flipping at integration.

Verified: coordinator.ts is text again (\0 escape at the composite key, dedicated commit), and git grep -P '\x00' across the branch is clean — the two remaining repo NULs are on main from the kv-unified landing, not yours; handling separately. Rebase onto current main is in (MERGEABLE), suite verified locally at 712 pass / 0 fail / 4 skipped on a clean build.

Dispositions on the nonblocking notes, all accepted:

  • 1 & 4 fixed: backlogCap: 0 = summary-only is now stated, and the floors (30s/1/60s) are named in the schema instead of applied silently — an agent reading the tool now knows the contract it's actually under.
  • 2 (final-burst ack overstates): your reasoning is right — the mention-er's signal stays true and the backlog arrives seconds later; a "suppressed-then-delivered" intent would be wire vocabulary for a cosmetic distinction. Agreed leave-as-is.
  • 3 (anchor after oldest-epoch cancel): agreed, and the honest comment ("oldest epoch at last (re)computation") is exactly what I wanted — the behavior was fine, the risk was someone reading the old comment as a stronger invariant.
  • The cm-side carry-over is verified in code over there: the windowed strategy re-derives anchor AND previous-compile identity on observed branch change, pinned by the raw-switchBranch test — so the subconscious can't hold stale state across an undo/redo without any AF change. Clean division.

With both halves approved this pair is antra's to sequence: cm#54 → publish → pin flip → this. The arc held up through two review rounds and got structurally better both times — the branch-scoping work in particular turned a latent class of bug into a tested contract.

Two conflicts against the 7 commits main gained since 7324521 (anima-research#141
turn-provenance batched wakes, anima-research#142, anima-research#143, 0.12.0), both keep-both:

- driveStream pre-stream: main's ephemeral-disposal ownership check runs
  first, then this branch's dispositions injection for the subconscious.
- driveStream finally: main's `frameReachedTerminal` guard on the deferred
  flush, over this branch's per-agent `drainDeferredFor`.

The auto-merged channel-incoming wake loop keeps anima-research#141's `counterparty` /
`addressed` provenance under this branch's `targetAgents` routing.

Bug fixed while reviewing: `getAllTools()` pushed TUNE_OUT_TOOL onto the
array `getChannelTools()` returns, which is the registry's module constant
CHANNEL_TOOL_DEFINITIONS — every call appended another `tune_out` to the
shared list. The array is now copied before the push; tune-out-e2e asserts
the resident's board carries `tune_out` exactly once across repeated calls.

Verified against a build of context-manager#54 (main 92f5a65 + ef11162):
tsc clean, suite 725 / 0 fail / 4 skipped, tune-out suites 22/22.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bm7wvGLvxF6RGAKCRB6MH7
@antra-tess

Copy link
Copy Markdown
Collaborator

Re-verified for landing (antra wants tune-out moving again). Pushed a merge of main (f59cc22) into this branch at cd6278b — a merge commit, your history untouched — with one fix of mine.

Conflicts (2, both keep-both): driveStream pre-stream (main's ephemeral-disposal ownership check, then your dispositions injection) and the finally-flush (main's frameReachedTerminal guard over your drainDeferredFor). The auto-merged channel-incoming loop keeps #141's counterparty/addressed provenance under your targetAgents routing — checked by eye.

Bug fixed: getAllTools() did channelTools.push(TUNE_OUT_TOOL) on the array getChannelTools() returns — that's the registry's module constant CHANNEL_TOOL_DEFINITIONS, so every call appended another tune_out to the shared list (every compile grows the board once the coordinator exists). Copied the array; tune-out-e2e now asserts tune_out appears exactly once across repeated getAllTools() calls.

Receipts on main + this branch, built against context-manager#54 (main 92f5a65 + ef11162): tsc clean, 725 / 0 fail / 4 skipped, tune-out suites 22/22.

Everything else I read matches the approval: coordinator (coalesced wakes → durable count → auto-cancel → capped system-framed dump), the tuned-out desired state living in the lifecycle log with transport-open reconcile, gate composition as a precondition, forAgent delivery, and the subconscious inheriting the principal's config with proseRouting: 'explicit'.

Sequencing from here: cm#54 → CM release → flip this pin to the published version → CI → merge. Host-side recipe plumbing for subconscious (connectome-host) is a follow-up, as your "not in this PR" says.

context-manager#54 (strategy-view composition: viewFilter, auxiliary
merged views, WindowedPassthroughStrategy) is released as 0.8.0; the
git-ref pin to the PR branch was the one intended change between approval
and merge.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bm7wvGLvxF6RGAKCRB6MH7

@antra-tess antra-tess left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving at 4c8c1ee: merged with main f59cc22 (two keep-both hunks), getAllTools shared-array mutation fixed with a regression assertion, pin flipped to the published context-manager ^0.8.0. Local suite 725/0/4 + tune-out 22/22; CI 5/5 green. Landing as the core of tune-out (#77).

@antra-tess
antra-tess merged commit 6f8ddf1 into anima-research:main Sep 7, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants