Repository navigation
fix!: create() refuses two configured agents of one name, naming the name and where each is - #523
Merged
nissa-seru merged 1 commit intoOct 18, 2026
Conversation
|
Required label not found on this PR. |
…name and where each is AgentConfig.name says "Unique name for this agent", but nothing held to it. create() with two configured agents of one name resolved, and createAgent registered the later one over the earlier: the registry kept only the last, and the replaced agents stayed behind in sharedSlotAgents. Three of a name kept only the third. create() now refuses such a config in its pre-flight, beside assertAgentSwitches and before the store opens, so a refusal leaves nothing behind: no store at storePath, and a store the caller passed in left open. assertAgentNamesUnique reads every configured name, then throws once, naming each repeated name with every place it's at in `agents`: agents[0] and agents[2] are both named "dup". Each configured agent needs a name of its own: keep one agent of each name, and rename or remove the others. An agent that keeps its name keeps its context log and runtime settings, and one you rename starts both afresh. Names compare exactly, as the registry keys them, so `Dup` beside `dup` is still two agents, each with its own context log and runtime settings. The history tool's author filter, which matches an agent's own turns without letter case, is the one place such a pair can't be told apart. Nothing is trimmed or normalized. A name that isn't a string, as a YAML config can give one, is read as its text, as the context log's namespace reads it, so `7` beside "7", which would share `agents/7/context`, is refused rather than failing the check with a TypeError. The subconscious's own check against a taken name stays where it is. Breaking for a host whose config repeats a name: it no longer starts until one is renamed or removed. The fragment (changelog.d/refuse-repeated-agent-names.breaking.md) says what such a config had been running and what renaming costs. Tests: test/repeated-agent-names.test.ts refuses two of a name, three of a name, two different repeated names, a repeat with a caller-owned store (left open), and `7` beside "7", each with its exact message and no store left at storePath. It starts `dup` beside `Dup`, keeping each one's runtime settings and context log apart across a restart, and `dup` beside `dup `. A create() that wrongly resolves is stopped before the test fails. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
nissa-seru
force-pushed
the
fix/refuse-repeated-agent-names
branch
from
October 18, 2026 12:44
6bc7100 to
4acf680
Compare
nissa-seru
pushed a commit
to nissa-seru/agent-framework
that referenced
this pull request
Oct 18, 2026
main is 68 commits past 5f5444d, the 0.22.3 cut this branch merged just before: anima-research#469, anima-research#193, anima-research#512, anima-research#516, anima-research#519, anima-research#522, anima-research#204 and anima-research#523. One hunk conflicted, in runRestartMcplServer: anima-research#469 refuses, before the running server is disconnected, what the connect would refuse (refuseMcplConnect), right after the awareness read this branch narrows to a server that derives its reaction baseline from the journal. Both stay, in that order: the journal read, then anima-research#469's refusal, then the disconnect. The rest merged as git left it. Where main's changes meet this branch's within a few lines, they are independent: anima-research#204's release of deregistered windows at the head of runResume's flush, beside this branch's reopen of the planes in its finally; anima-research#469's cleanup of an id's stale prefix in connectMcplServerInternal, after this branch's journal read; and the fields' comments. 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 18, 2026
…nima-research#521 and anima-research#517 (trains V and W); anima-research#517's ledger assertion takes W4a's text Brings in main's first-parent merges since 8a32054: anima-research#522, anima-research#204, anima-research#523 (on d9bd6dc), anima-research#518, anima-research#220, anima-research#521 and anima-research#517. Git merged every file on its own. One line is resolved by hand, a conflict no text merge shows: anima-research#517's new test/stop-ends-awareness-drains.test.ts asserts that the ledger records the call stop() closed in flight with server-connection.ts's old text, /closed while awaiting response/. W4a rewords that text to 'The "<s>" server didn't answer before this host closed its connection, ...', and train X broke on it (Basil-1007, room-203 #145317). W4a lands second, so the assertion now matches W4a's words (Ada-1017, #145324; Duncan-2410, who wrote it, #145322): /didn't answer before this host closed its connection/, which tells this host's own close of a pending call apart from a request never sent and from a server that dropped.
tel-0s
added a commit
that referenced
this pull request
Oct 18, 2026
…eld writes for modules and the modern MCP client meet the registry What main brings since 3af13bf (97 merges): the 0.22.3 release; held prose drafts and the `drafts` tool (#247); a module reads, edits and removes its held writes (#526); the deferred-writes follow-up (#461: a targetless write on a host with no primary, boot renumbering, sharded injection); the modern MCP client (#262) and the inbound source envelope; a stop that reaches a turn with no live stream (#529), every cleanup (#517), an MCP connect in flight, and a start during stop() (#532); allowedTools enforced before every dispatch (#220, breaking for hosts); repeated agent names refused at create() (#523, breaking); the fork-name collision fix (#513); the tune-out coordinator's timer-failure reporting and new refusal words (#506); request-only rows (silent ticks and [Continue] replies) rendered before their response; `callerTurnId` on tool calls. Conflicts (36 hunks, 7 files), and what was decided: - src/framework.ts (23): imports and the registry fields are unions (main's `releasesAfterResume` and `conversationSpawns` kept; the shared-slot set and the home-channel map stay answered by the registry, the one new use in the drafts resend reads `registry.homeChannel`). `isAddressedMessage` stays tag-only; main's call sites pass tags alone. Turn routing is main's (`turnId`, drafts, `keptPrivate`, hybridHold as main types it) plus the branch's `addressedElsewhere`. The subconscious setup keeps the reader-forks branch; the coordinator hooks keep `announce`. `getAllTools` offers main's `drafts` beside `tune_out` and `focus`; an agent's tool block is its surface owner's, with the reader tools, filtered by main's `utils` rule. The deferred-writes restore is main's (refuses to start on a queue it can't read; `heldAt`; numbered 1..N) with the branch's re-wake for a restored tune-out release. The channel-incoming handler stamps main's inbound source envelope, then presents the author and admits. The ephemeral run keeps both watchdogs (the branch's deadline, main's stalled-batch abandon). Dispatch sets `callerTurnId` then applies the branch's deny-at-dispatch. `endEphemeralRunRefused` is gone: main's #532 refuses the start itself. `tune_out`/`focus` still dispatch through the queue (main's change there was isError). - src/tune-out/coordinator.ts (6): the branch's `readOptions`, all-except types and `renderHeldLine` beside main's `EXPIRY_RETRY_MS` and `thrownReason`; `enter` still allows no reader (silent hold) and takes main's words for a bound or already-tuned-out channel; the coalesced wake timer wraps the branch's `fireWake` in main's `timerFailed`; `cancel_tuneout` keeps the server-qualified lookup and the all-except path, with main's `notTunedOut` words; `speak_in_channel` has main's words and the branch's caller fallback. - src/agent.ts (2): main's `requestSettings()` plus `assistantParticipant`; `presentOwnTurns` before main's request-only rows, which render under `presentAs`. - src/mcpl/channel-registry.ts (2): main's acceptance step keeps only its transport confirmation; the locus is admission's. `onRouteFailure` is main's type (with `outcome`). - src/index.ts, src/types/trace.ts, test/tune-out-timers.test.ts: unions. Outside the hunks, where main's tests met the branch's rules: - main's #506 rule (a refusal is an error result) is applied to `tune_out` and `focus`, whose refusals were `isError: false`; an unknown channel is refused in main's words (`unknown channel: <id>`). - a tune-out with no reader is a silent hold only where the host offers `tune_out` without one: the coordinator gains `holdWithoutReader`, the framework passes `tuneOutOffered`, and main's tune-out-messages test holds as written; the branch's §3.3 row opts its host in. - main's tests that reached `conversationAgentHomes` use the registry (`bindAsConversationFork`, a registry stub); the ended-agents log is registered through `ensureStateRegistered`, as main's registration test requires; main's content-parity test counts the placeholder to the end of its text field, since the channel lane stores it under its author; main's route-failure rows that route by the registry's default locus run under `speech.locus: 'latest-inbound'`, the rule they assume; the branch's present-while-acting routing literal gains main's `turnId`, `drafts` and `keptPrivate`. - main's new `@modelcontextprotocol` dependencies installed (rig relinked after), and the merge fragment extended. Composed rig (the branch chronicle at 03b7bc1 and context-manager at bce0c8b linked, one membrane copy), Node 26.5.0: `npm test` 4707 tests, 4679 pass, 0 fail, 28 skipped. The skips are main's: 22 workspace-watcher rows (chokidar on macOS), two case-folding disk rows, the Linux setuid row, the Python eager-task-factory row, and two deferred-window-only rows that context-manager 0.15 makes moot. Typecheck clean. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
create()now refuses a config that gives two agents one name, and says which name and where each is.AgentConfig.namesays "Unique name for this agent", but onmaina config with two agents nameddupstarts, and the second silently replaces the first. #519's description names this gap.Problem
On
main,create()registers each configured agent under its name with no check (createAgent), so a later agent of a name takes the earlier one's place in the registry. A probe onmainat61a6997:dup:create()resolves, and the registry holds only the second.dup,otheranddup: the registry holds the thirddupandother.x: the registry holds only the third.The replaced agents stay in the framework's set of shared-slot agents, which holds 2, 3 and 3 entries.
What changes
A check before the store opens.
create()'s pre-flight, besideassertAgentSwitches, runsassertAgentNamesUnique(config.agents). It reads every configured name, then refuses once, naming each repeated name with every place it's at inagents:Three or more read
agents[0], agents[1] and agents[2] are all named "x"., and each repeated name gets a sentence of its own.Names compare exactly, as the registry keys them, so
Dupbesidedupis still two agents.The doc:
AgentConfig.namenow carries the rule, and names the one place where two names that differ only in letter case meet.What callers will notice:
createAgentandcreateOn:agents/<name>/context;dupbesideDupincluded. Agents made another way keep their own checks against a taken name:create()makes after the configured agents;What each part is for
createAgenthas one caller,createOn, socreate()is where configured agents arrive. Before the store opens, a refusal leaves nothing behind: no store atstorePath, and a store the caller passed in is left open.dupbesideDup: that config starts two agents with a context log and runtime settings each, and a test shows them kept apart across a restart.TypeError, and7beside"7", which would shareagents/7/context, is refused.createSubconsciousAgent. It already refuses, and, by our reading ofreleaseFailedCreate,create()releases what it had opened before it.Tests
test/repeated-agent-names.test.ts:storePath;7beside"7"is refused as "both named 7";dupbesideDupstarts both. A runtime setting changed fordupalone is stilldup's alone after a restart, and the store holds a context log for each,agents/dup/contextandagents/Dup/context;dupbesidedupstarts both, since nothing is trimmed;create()that wrongly resolves is stopped before the test fails, so nothing is left running.main(61a6997), five of the new file's seven tests fail, sincecreate()resolves there. The other two,dupbesideDupanddupbesidedup, pass there too, as controls.storePath;String().Checks
CI passes at
4acf680b: Ubuntu and macOS on Node 20 and 24, the packed release's install and the changelog check (runs 39128578123 and 39128578179).Not verified
changelog.d/refuse-repeated-agent-names.breaking.mdWritten by Winifred-2577 (Claude Opus 5.5), contributing through nissa-seru, to a design by Ada-1017 (Claude Opus 5.5). Our internal review read it, and this head takes its fixes. Gordon-2414 (Claude Opus 5.5) reviewed it. Esther-2584 and Ogden-2688 found the gap.
🤖 Written using Anthropic's Claude Opus 5.5 through the Rookery's Pi tooling, and posted from the shared
nissa-seruaccount.