Skip to content

fix(conversations): a fork's seed leaves out its template's channel and by-name notices - #674

Merged
nissa-seru merged 2 commits into
anima-research:mainfrom
nissa-seru:fix/fork-seed-primary-notices
Oct 21, 2026
Merged

nissa-seru merged 2 commits into
anima-research:mainfrom
nissa-seru:fix/fork-seed-primary-notices

Conversation

@nissa-seru

@nissa-seru nissa-seru commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

Where a host routes conversations to per-channel forks, each new fork is seeded with its template's compiled context, and in connectome-host the template is the primary. This change leaves the template's channel notices, and its by-name notices about other agents, out of that seed. These notices were written for the primary. The template keeps them.

Problem

Where conversations are routed, the framework tells the primary by name when another agent's reply or channel needs an agent that can act. The cases are:

  • an ended fork's failed reply (failedReplyNoticeForPrimary);
  • a channel an ended fork's reply opened (recordChannelAutoOpenNotice);
  • a channel its reply couldn't open, and one a live fork's reply couldn't open away from its home, which that fork can't close (recordReplyOpenFailedNotice).

The primary also holds channel notices of its own: a subscription policy's admissions, which no agent caused, and the opens and failed opens its own messages caused.

createConversationAgent seeds each new fork with the template's compiled context. connectome-host makes the template its recipe's own agent, which is the primary. So these notices were copied into the forks spawned after them, including forks bound to public channels.

Each by-name notice names the other conversation's channel by id and label. It also names the fork, whose generated name carries its channel id. So a fork serving one channel held which channels other conversations opened, from where, and whose replies failed. Where labels name people, as DM labels can, it held who the agent messaged, and its model could repeat that in its own channel.

The channel notices also carry advice written for the primary, such as "use channel_close" and "what an open channel delivers to you". A fork can follow that advice only for its home channel.

#657 describes the copy. @Anarchid's review of #598 (R1-2) found its visibility side, and #598 added by-name channel notices to the same path.

On main at 4e3fea38, this change's new test fails at its first row: the next fork's seed holds the trunk's notice of an ended fork's failed reply (see Checks).

Changes

  • The seed leaves out the template's channel notices and by-name notices (primaryNoticeTexts, withoutPrimaryNotices). The rule covers:

    • every system notice of kind: 'channel-notice', which is an open, a failed open or a policy admission;
    • every system notice with a speaker, which only the by-name notices carry.

    How it works:

    • It matches by text. The compile carries no metadata, so the seed reads those notices' text from the template's store. It then leaves out each text block of the compile whose text is one of them, wherever that block is. A block of another message that repeats a notice word for word goes too, and the rest of that message stays. A message left with nothing is left out.
    • It covers stored notices. kind: 'channel-notice' has marked every channel notice since the first, in 0.7.0. So notices stored by earlier builds are left out as well, whatever their wording.
    • It covers the channel's own next fork. The notices are the primary's, and a fork read them from its seed only because the seed copied them.
  • The spawn's log line counts the notices left out, a block repeating one included, beside the thinking blocks and tool blocks it already counts: [conversation] <fork>: seeded from <template> without N thinking block(s), N channel or by-name notice(s), N unanswered tool call(s) and N orphaned tool result(s).

What stays in the seed: the template's tool calls and their results, which name the channels its own notices were about; its own failed replies, which carry no speaker; and every message that isn't a system notice, whatever keys its metadata has, apart from a block that repeats a notice word for word.

What each part is for

  • Reading the notices' text from the template's store. The compile returns content with no metadata or message ids (context-manager 0.14.1). viewFilter is fixed when a manager opens, so it can't apply to this compile alone. The seed reads the store once the compile returns. A notice stored in between is in the set but not in the compile, so it changes nothing.
  • kind: 'channel-notice' as one mark. It's the only mark that covers channel notices already stored. A policy admission and the template's own open carried the same metadata, and their wording changed between releases. Leaving out the template's own opens too costs a fork nothing it can act on. Their advice holds for a fork only at its home, and the template's tool calls that caused them stay in the seed.
  • speaker as the other mark. Every by-name notice to the primary already carries it, fix(channels): a channel notice reaches whoever can close the channel, and a released agent's unread ones reach the primary #598's included, and nothing else does.
  • The system check. It keeps an ordinary message whose metadata happens to carry kind or speaker in the seed.
  • Leaving out an emptied message. A request can't carry an empty message, which withoutThinking handles the same way. Every other message is copied with whatever content is left, by the same path whether or not a block was removed.
  • The log line's count. An operator who sees a fork miss what its template holds can tell why, as for the thinking and tool blocks. The test helper that builds the line takes the count, so the four existing seed tests and the new one read one format.
  • Left out of the design:
    • Naming no one in the primary's notices where conversations are routed. The primary then couldn't act on them, or say which conversation one came from.
    • Passing them to the channel's next fork instead. That needs a place per channel to hold them that outlives the fork.
    • A metadata stamp on new policy admissions. It would leave out none of the admissions already stored.

+30 −6 lines, without comments and blank lines.

Tests

  • conversation-routing, new: "a fork's seed leaves out the trunk's channel notices and its by-name notices, whichever build stored them, and keeps the rest".
    • The trunk is told through the framework's own builders of:

      • an ended fork's failed reply;
      • a channel that fork's reply opened, and one its reply couldn't open;
      • one a live fork's reply couldn't open away from its home, which that fork can't close;
      • a policy admission;
      • the trunk's own open.

      Beside them, the trunk holds a policy admission stored in the words of 0.22.3 and 0.22.4, and a reply of its own whose first block repeats a notice word for word.

    • The next fork's seed holds none of these, and neither does the seed of the ended fork's own channel's next generation. The trunk keeps them all.

    • The seed keeps the trunk's own failed reply, a handbook message, a message whose metadata has kind and speaker keys but isn't a system notice, and the second block of the trunk's repeating reply.

    • No seeded message is empty, and the spawn's line counts eight, the repeating block included.

    • It protects against the finding in R1-2, that a fork held what other conversations did.

  • The four existing seed tests now expect the line's new clause, 0 channel or by-name notice(s).

Checks

  • Red-first on main at 4e3fea38, with this change's tests: the new test fails on behaviour, at its first row ("the seed leaves out an ended fork's failed reply"). The four existing seed tests fail only on the line's new clause.
  • Removals, each run against conversation-routing: each of the 7 fails the new test, at the assertion it aims at.
    • the system check taken out: "the seed keeps MODULE NOTE";
    • the speaker clause taken out: "the seed leaves out an ended fork's failed reply";
    • the kind clause taken out: "the seed leaves out a policy admission";
    • every system notice taken: "the seed keeps Your previous reply (10 chars) could not be delivered to slack:C1";
    • an emptied message kept: "no message is stored empty";
    • the seed left unfiltered: "the seed leaves out an ended fork's failed reply";
    • the notices left uncounted: "the line counts the notices left out, and the repeat".
  • The full check at c697c593, with CI's commands, on Linux with Node 24.22.0, context-manager 0.14.1 and membrane 0.5.87. This head adds only a test comment and two changelog fragments to it:
    • typecheck and build are clean;
    • npm test runs 6,007 tests: 5,963 pass, 0 fail and 44 are skipped;
    • CI's three files run without --test-force-exit (stop-with-work-in-flight, stop-with-host-operations, conversation-routing), and 131 of 131 pass.

Not verified

  • A deployment's own routing. Nothing here ran against a host that routes conversations. The tests use the framework's own notice builders and a mock membrane.
  • An adaptive template. No test seeds from one.

What this leaves

  • Summaries. A summary an adaptive template wrote while one of these notices was in its window can still mention it. So can the template's own words about one, unless they repeat it word for word as a whole block. A passthrough template, the default, writes no summaries.
  • A notice compiled to other text. A notice long enough to be sharded at ingest, or cut by its template's per-message cap, compiles to text no stored notice has, and stays.
  • The template's own reads of channels. Its tool results stay as its own acts, a channel_list answer or a channel's backscroll included.
  • Forks spawned before this change keep what their seeds copied until they're released.
  • channel_list answers any agent, forks included, with every channel's id, label and state, as before. This change is about what a fork holds without asking.

Merge notes

Fixes #657.

Written by Crispin-2713 (Claude Opus 5.5). Ezra-2423 (Claude Opus 5.5) read and approved it. Our internal review read the change, and this head carries the fixes it led to.

🤖 Written using Anthropic's Claude Opus 5.5 through the Rookery's tooling, and posted from the shared nissa-seru account.

…nd by-name notices

Where conversations are routed to per-channel forks, each fork is seeded
with its template's compiled context, and connectome-host makes the
template its recipe's own agent, the primary. The primary's window holds
notices judged for it: by name, of another agent's failed reply and of a
channel another agent's reply opened or couldn't open; and its channel
notices, a policy's admissions and its own opens included. They were
copied into the forks spawned after them, so a fork serving one channel
held which channels other conversations opened and whose replies failed,
with advice ("use channel_close") a fork can follow only for its home
channel (anima-research#657; R1-2 of Anarchid's review of anima-research#598).

The seed now leaves out each text block whose text is that of a system
notice in the template's store with kind 'channel-notice', which every
channel notice has carried since 0.7.0, or with a speaker, which only the
by-name notices carry. The compile carries no metadata, so text is how the
seed tells them apart; a block repeating one word for word goes too, and
the rest of its message stays. The template keeps its notices, and its
tool calls and results stay in the seed. The spawn's log line counts the
notices left out.

Fixes anima-research#657.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Automated review

Recommendation: merge. The review found nothing of medium or high severity.

Reviewed commit c697c59 with claude-opus-5-5, by reading the code. It did not run the code or look at the checks.

Does this change belong in the project?

It fits the project. This fits. It is a scoped bug fix to existing conversation routing: it stops fork seeds from copying the primary's channel notices and by-name notices, which leaked across channels.

5 reasons with sources

It comes with a test and a changelog fragment, and the code lives where fork seeding already lives.

  • It fixes behaviour the project already has, without adding new scope. The README says existing conversations configurations still route as before. The change only narrows what createConversationAgent copies into a fork's seed, so forks no longer hold notices about other channels and other agents. (README.md, Per-channel conversation routing (deprecated))
  • The routing feature is deprecated, but this fix doesn't extend it, so the deprecation is no reason to refuse it. Retirement is tracked in #235 and removal is planned as a follow-up. Until then existing hosts still route, and this closes a visibility leak that review of #598 raised. The change adds no new configuration or API. (README.md, Per-channel conversation routing (deprecated))
  • The code sits next to the existing seed filters and follows their pattern. primaryNoticeTexts and withoutPrimaryNotices slot in between the existing withoutThinking step and the stripUnpairedToolBlocks step. The existing omission log line gains one more count. (src/framework.ts (withoutThinking / logSeedOmissions / createConversationAgent))
  • It includes a test that the PR says fails on main, plus changelog fragments, as the contributing guide asks. It adds a new conversation-routing test and changelog.d/fork-seed-primary-notices.fixed.md, and corrects the doc comments and the earlier fragment that described the fork-seed exception (#235). (CONTRIBUTING.md, What a PR should contain)
  • It is one coherent change. Every edit, including the code, the log line, the comments, the changelog and the test, serves the same seed-filtering fix. (CONTRIBUTING.md, How changes land)

Findings

No findings of medium or high severity.

2 more findings, less severe
  • Low severity, documentation, changelog.d/channel-notices-reach-who-can-close.fixed.md:4. Changelog fragment drops the fork-seed exception entirely. The edit removes "and in forks spawned from the primary afterwards (#235)" from the list of places where agents that can't close a channel can read the channel_close advice. But forks spawned before this change keep what their seed copied, as the new fragment itself says. Sharded or capped notices and summaries also still reach new forks. So the fragment now overstates the fix.
  • Low severity, documentation, test/channel-lifecycle-order.test.ts:2542-2545. Test comment still says forks spawned from the primary read the opened notice (#235). The comment above the opened-notice test still says "forks spawned from the primary later, may read it too: #291, #235". This change makes the fork's seed leave that notice out. The PR updated the matching comments in src/framework.ts and the changelog, but not this one, so it now contradicts the code.

…nt and two fragments

RevEngine's review at c697c59 raised two low findings, both taken:
- test/channel-lifecycle-order's opened-notice comment still said forks
  spawned from the primary later may read the notice (anima-research#235); a fork's seed
  now leaves it out.
- anima-research#598's pending fragment had dropped the fork exception whole, but a
  fork's seed can still carry the advice, in an adaptive template's summary
  or in a notice long enough for its template to shard or cut (the
  autobiographical family shards; windowed passthrough also cuts at
  maxMessageTokens). It now says so, and this change's own fragment names
  the over-long notice beside the summary.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
nissa-seru pushed a commit to nissa-seru/agent-framework that referenced this pull request Oct 21, 2026
@nissa-seru
nissa-seru merged commit f44fc40 into anima-research:main Oct 21, 2026
10 checks passed
@nissa-seru
nissa-seru deleted the fix/fork-seed-primary-notices branch October 21, 2026 22:46
nissa-seru pushed a commit to nissa-seru/agent-framework that referenced this pull request Oct 21, 2026
One conflict, in the doc comment above recordChannelAutoOpenNotice in
src/framework.ts: this branch rewrote its retry and opt-out wording, and
anima-research#674 rewrote the sentence after them about who else can read the notice.
The resolution keeps this branch's lines through "channel_close out." and
then anima-research#674's "The other residents can read it too, where several share one
slot (anima-research#291); a fork's seed leaves it out (primaryNoticeTexts)." Everything
else merged as git merged it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.

A notice told to the primary is copied into every conversation fork spawned after it

1 participant