Skip to content

feat(engine): defer RouterContext.attach to run after registry bootstrap - #2422

Merged
jfallows merged 1 commit into
developfrom
claude/router-attach-phase-847
Aug 23, 2026
Merged

feat(engine): defer RouterContext.attach to run after registry bootstrap#2422
jfallows merged 1 commit into
developfrom
claude/router-attach-phase-847

Conversation

@jfallows

Copy link
Copy Markdown
Contributor

Description

RouterContext.attach(RouterConfig) ran as the first statement of EngineWorker.doInit() — before this worker's own EngineRegistry is constructed, and before EngineManager has processed any namespace. Its contract already lets an implementation attach a synthesized composite namespace via RouteableContext.attachComposite() as part of that same call (RouteableContext's own javadoc: "Attaches a synthesized NamespaceConfig to the engine alongside operator-authored namespaces"), which reads and mutates per-worker registry state that doesn't exist yet at that point in engine bootstrap.

Every existing RouterContext implementation (EngineRouterContext, the test router, and a real composite-synthesizing router built against this SPI) already builds its stream factory once, at construction time in Router#supply(RouteableContext), and attach() just returns that value unchanged — RouterConfig itself goes unused in every implementation. So the stream factory returned by attach() never actually depended on attach() running early during doInit(); only bindings' own construction-time capture of EngineContext#streamFactory() did (every binding factory does this.streamFactory = context.streamFactory(); in its own constructor, called from doInit()).

This splits the two concerns:

  • RouterContext#streamFactory() (new) — returns the already-constructed stream factory. EngineWorker sources this immediately after Router#supply(RouteableContext) returns, in its own constructor — independent of when attach() runs.
  • RouterContext#attach(RouterConfig) — now void. EngineWorker.doInit() no longer calls it. Instead, Engine#start() calls it once per worker, dispatched onto that worker's own thread (mirroring the existing EngineWorker#attach(NamespaceConfig) pattern), only after EngineManager#start() has run the engine's bootstrap config through to completion — so any registry-dependent setup a router's attach() performs (like attaching a composite namespace) now runs with the registry and EngineManager's live configuration already in place.

No behavior change for any existing router: EngineRouterContext#attach() becomes an empty no-op, matching what it always effectively did.

Test plan

  • ./mvnw checkstyle:check -pl runtime/engine — 0 violations
  • ./mvnw clean verify -pl runtime/engine — 228 unit tests + 210 integration tests, 0 failures/errors

Generated by Claude Code

RouterContext.attach(RouterConfig) was called as the first statement of
EngineWorker.doInit(), before this worker's own EngineRegistry exists and
before EngineManager has processed any namespace, yet its contract already
lets an implementation attach a synthesized composite namespace via
RouteableContext.attachComposite() as part of that same call — a namespace
registration that reads current per-worker registry state.

Every existing RouterContext implementation already builds its stream
factory once, at construction time in Router.supply(), and attach() just
returns that value unchanged; RouterConfig itself is unused. So the
stream factory never depended on attach() running early — only bindings'
own construction-time capture of it did.

Split the two: add RouterContext.streamFactory(), sourced by EngineWorker
right after RouterContext is constructed, and change attach() to void,
called once per worker only after Engine.start() has run the engine's own
manager.start() bootstrap to completion, dispatched onto that worker's own
thread the same way ordinary namespace attachment already is.

@jfallows jfallows left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

LGTM

@jfallows
jfallows merged commit 823ac36 into develop Aug 23, 2026
3 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.

2 participants