Skip to content

refactor(session): Session Actor Refactor — pure reducer, effect executor, SessionBridge deletion#127

Merged
teng-lin merged 38 commits intomainfrom
session-actor-refactor
Feb 24, 2026
Merged

refactor(session): Session Actor Refactor — pure reducer, effect executor, SessionBridge deletion#127
teng-lin merged 38 commits intomainfrom
session-actor-refactor

Conversation

@teng-lin
Copy link
Copy Markdown
Owner

Summary

Refactors SessionRuntime from a stateful, mutation-heavy class into a pure reducer + effect executor pattern. Deletes ~1,000 lines of now-redundant infrastructure (SessionBridge, UnifiedMessageRouter) and replaces them with a flat SessionServices registry assembled by buildSessionServices().

Key changes

  • SessionData type (session-data.ts): Immutable serializable snapshot of all session state (state, messageHistory, pendingPermissions, lastStatus). The single source of truth passed through the reducer pipeline.

  • reduceSessionData / SessionStateReducer (session-state-reducer.ts): Pure function — takes (SessionData, UnifiedMessage) → SessionData. All session state mutations now flow through this reducer; no more scattered this.field = assignments in SessionRuntime.

  • EffectExecutor (effect-executor.ts, effect-types.ts): Processes SessionEffect[] emitted alongside each reduced state — broadcasts, storage writes, router calls — keeping side effects separated from business logic.

  • SessionCoordinator wiring (session-coordinator.ts, build-services.ts, session-services.ts): Introduces buildSessionServices() factory that assembles all four compose planes into a flat SessionServices registry. SessionCoordinator wires launcher, registry, policies, and services without going through SessionBridge.

  • SessionBridge deleted (session-bridge.ts removed, ~400 lines): All callers migrated to buildSessionServices() + BridgeTestWrapper. Public exports removed from src/core/index.ts and src/index.ts.

  • UnifiedMessageRouter deleted (unified-message-router.ts removed, ~640 lines + ~1,050 lines of tests): Routing logic absorbed into the reducer/effect pipeline.

  • CapabilitiesPolicy: Removed incorrect persistSession call (wrong abstraction layer).

  • BridgeFacade interface on SessionCoordinator: Backward-compat facade exposing emit, broadcastNameUpdate, renameSession, executeSlashCommand for coordinator wiring tests. Uses a local const bridge variable so spy interception works correctly.

  • E2E test fix: info.data.stateinfo.state in watchdog relaunch test (SessionInfo stores state directly, not under data).

Test plan

  • pnpm typecheck — clean
  • pnpm test — 2873 tests pass (193 test files)
  • pnpm test:e2e:claude:smoke — watchdog relaunch test passes with info.state = "starting" fix
  • Review deleted files: session-bridge.ts, unified-message-router.ts no longer referenced anywhere
  • Verify buildSessionServices() assembles all four compose planes correctly
  • Confirm SessionData flows immutably through reducer (no mutation in SessionRuntime)

…onData reducer

Tasks 3a (lastStatus), 3d+3e (messageHistory with dedup for assistant/
tool_use_summary messages) migrated from UnifiedMessageRouter into
reduceSessionData. The reducer now handles:
- lastStatus: inferred from status_change, result, stream_event
- messageHistory: append/replace with dedup logic for assistant+tool_use_summary
- backendSessionId, pendingPermissions: already in previous commit
…ith orchestration

SessionRuntime.handleBackendMessage now:
1. Runs reduceSessionData (pure) to apply state transitions
2. Runs orchestrate* methods for side effects (emitEvent, git, capabilities)
3. Delegates to routeBackendMessage for T4 broadcasting

Orchestration methods moved from router to runtime:
- orchestrateSessionInit: git, registry, capabilities, queue
- orchestratePermissionRequest: emits permission:requested event
- orchestrateAuthStatus: emits auth_status event
- orchestrateResult: auto-naming, git refresh, queue
- orchestrateStatusChange: queue on idle
- orchestrateControlResponse: capabilities policy
… and fix bridge wiring (Task 4)

UnifiedMessageRouter now handles only T4 translation (UnifiedMessage →
ConsumerMessage broadcast). All setter callbacks (setState, setLastStatus,
setMessageHistory, storePendingPermission, etc.) removed — state transitions
go exclusively through SessionStateReducer via SessionRuntime.

Router shrinks from 644 → 305 lines. Router test file trimmed by ~435 lines.

Bridge wiring fixes:
- compose-runtime-plane: add getCapabilitiesPolicy + emitEvent lazy deps
- compose-message-plane: trim createUnifiedMessageRouterDeps to {broadcaster, tracer}
- runtime-manager-factory: add emitEvent, getGitTracker, getCapabilitiesPolicy deps
- session-bridge-deps-factory: remove emitEvent from router deps factory
- session-bridge.ts: wire getCapabilitiesPolicy and emitEvent into composeRuntimePlane
- cli-message-factories: update createMockSession for data field structure
…k 2.3)

CapabilitiesPolicy.applyCapabilities was calling persistSession(session) with
the pre-setState session reference — a stale object since SessionRuntime.setState
replaces this.session via spread-reassignment.

Fix: persist in SessionRuntime.orchestrateControlResponse after handleControlResponse
returns. At that point this.session reflects any setState calls made by
applyCapabilities through the stateAccessors adapter.

Removes persistSession constructor parameter from CapabilitiesPolicy entirely;
persistence is now exclusively orchestrated by SessionRuntime.
…ter (Task 5.3)

- Create effect-types.ts: Effect discriminated union (BROADCAST,
  BROADCAST_TO_PARTICIPANTS, BROADCAST_SESSION_UPDATE, EMIT_EVENT,
  AUTO_SEND_QUEUED)
- Create effect-executor.ts: executeEffects() bridges pure reducer
  output to side-effectful runtime services; injects sessionId on
  all EMIT_EVENT payloads automatically
- Expand session-state-reducer to return [SessionData, Effect[]]
  with buildEffects() producing typed effects per message type
- Wire executeEffects into SessionRuntime.handleBackendMessage;
  remove orchestrateStatusChange, orchestratePermissionRequest,
  orchestrateAuthStatus (replaced by effects); simplify
  orchestrateResult to git-refresh only
- Delete UnifiedMessageRouter and its test: all T4 broadcast/emit
  logic now lives in the pure reducer + effect executor
- Remove routeBackendMessage from SessionRuntimeDeps,
  RuntimeManagerFactoryDeps, and all wiring; remove getMessageRouter
  from compose-runtime-plane; clean up compose-message-plane,
  session-bridge-deps-factory, and session-bridge.ts accordingly
…ia buildSessionServices (Phase 6)

Task 6.1: Add SessionServices interface capturing all 10 sub-services from the
4 compose planes (runtimeApi, backendApi, infoApi, broadcastApi, lifecycleService,
persistenceService, consumerGateway, broadcaster, core, store).

Task 6.2: Add buildSessionServices() factory in session-coordinator/build-services.ts
using the same lazy-closure pattern as the old SessionBridge constructor to resolve
circular deps between planes. Rewrite SessionCoordinator to use buildSessionServices()
instead of new SessionBridge(). Add BridgeFacade interface (on/off/emit/getSession/
broadcastProcessOutput/broadcastNameUpdate/renameSession/executeSlashCommand) for
backward-compat access by E2E tests and wiring test spies.

Task 6.3: Delete src/core/session-bridge.ts.
- adapter-test-helpers: replace createBridgeWithAdapter() with buildSessionServices()
  backing; export BridgeTestWrapper type for integration test typing
- 4 integration test files: drop SessionBridge import, use BridgeTestWrapper
- team-events.integration.test: use buildSessionServices() in local factory
- ws-server-flow.integration.test: use createBridgeWithAdapter() helper
- Remove SessionBridge from src/core/index.ts and src/index.ts public exports

All 2873 tests pass, typecheck clean.
SessionInfo stores lifecycle state directly as info.state, not nested
under info.data.state — that pattern belongs to Session objects inside
SessionRuntime/SessionRepository.
@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello @teng-lin, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a fundamental architectural shift in how session state is managed, transitioning from a mutable, class-based approach to an immutable, functional reducer pattern. This change aims to improve the clarity, maintainability, and testability of session logic by centralizing state mutations and explicitly separating side effects. The refactor also significantly streamlines the core infrastructure by removing deprecated components and introducing a more cohesive service composition.

Highlights

  • Session Actor Refactor: Refactored SessionRuntime to adopt a pure reducer and effect executor pattern, moving away from a stateful, mutation-heavy class. This significantly enhances predictability and testability of session state changes.
  • Deletion of Legacy Infrastructure: Removed approximately 1,000 lines of redundant code by deleting SessionBridge and UnifiedMessageRouter. Their functionalities have been absorbed into the new reducer/effect pipeline and a flat SessionServices registry.
  • Introduction of SessionData: Introduced SessionData as an immutable, serializable type representing all session state. This ensures a single source of truth passed through the reducer pipeline, preventing scattered mutations.
  • New Service Composition: Implemented buildSessionServices() to assemble all core components (backend, consumer, message, and runtime planes) into a flat SessionServices registry, streamlining dependency management and reducing circular dependencies.
  • CapabilitiesPolicy Update: Removed the persistSession call from CapabilitiesPolicy as persistence is now handled centrally by SessionRuntime after state reduction, ensuring consistency with the new architecture.
Changelog
  • fix_router.py
    • Added a new Python script to modify TypeScript files, updating method signatures to include prevData and adjusting state access in unified-message-router.ts and its test file.
  • node_modules
    • Added a new file, likely a .gitignore entry, to manage node_modules.
  • src/core/backend/backend-connector.adapter-selection.test.ts
    • Updated session property access from direct session.field to session.data.field to align with the new SessionData structure.
    • Modified test setups to explicitly assign session.data = session for compatibility with the new data model.
  • src/core/backend/backend-connector.failure-injection.test.ts
    • Adjusted session property access to use session.data.field.
    • Added logic to ensure session.data is initialized in createSession.
  • src/core/backend/backend-connector.lifecycle.test.ts
    • Migrated session property access to session.data.field.
  • src/core/backend/backend-connector.test.ts
    • Updated session property access to session.data.field and added session.data = session in test setups.
    • Refactored expect calls for improved readability.
  • src/core/backend/backend-connector.ts
    • Updated session.adapterName to session.data.adapterName in resolveAdapter.
  • src/core/bridge/runtime-manager-factory.test.ts
    • Removed routeBackendMessage from dependencies.
    • Added emitEvent, getGitTracker, gitResolver, and getCapabilitiesPolicy to dependencies.
  • src/core/bridge/runtime-manager-factory.ts
    • Updated RuntimeManagerFactoryDeps to remove routeBackendMessage and include emitEvent, getGitTracker, gitResolver, and getCapabilitiesPolicy.
  • src/core/bridge/session-bridge-deps-factory.ts
    • Removed imports for CapabilitiesPolicy and UnifiedMessageRouterDeps.
    • Updated type definitions for CapabilitiesPolicyStateAccessors and QueueStateAccessors to reference SessionData properties.
    • Removed the createUnifiedMessageRouterDeps function.
  • src/core/capabilities/capabilities-policy.integration.test.ts
    • Updated state access to session.data.state.field.
    • Removed persistSession from dependency creation and usage.
  • src/core/capabilities/capabilities-policy.test.ts
    • Adjusted state access to session.data.state.field and removed persistSession related logic.
  • src/core/capabilities/capabilities-policy.ts
    • Updated CapabilitiesStateAccessors to use SessionData['state'].
    • Removed persistSession dependency and its invocation.
  • src/core/consumer/consumer-gateway.test.ts
    • Modified session property access to session.data.field.
  • src/core/index.ts
    • Removed the export of SessionBridge.
  • src/core/interfaces/session-bridge-coordination.ts
    • Imported SessionData and updated type definitions to use SessionData properties for session state.
  • src/core/messaging/unified-message-router.test.ts
    • Removed the entire test file for UnifiedMessageRouter.
  • src/core/messaging/unified-message-router.ts
    • Removed the entire UnifiedMessageRouter class file.
  • src/core/session-bridge.adapter-consumer.integration.test.ts
    • Updated SessionBridge type to BridgeTestWrapper and adjusted state access to session.data?.state.
  • src/core/session-bridge.integration.test.ts
    • Changed SessionBridge type to BridgeTestWrapper and updated createAuthBridge to use createBridgeWithAdapter.
  • src/core/session-bridge.ts
    • Removed the entire SessionBridge class file.
  • src/core/session-bridge/compose-consumer-plane.test.ts
    • Updated session state access to session.data.state.
  • src/core/session-bridge/compose-message-plane.test.ts
    • Adjusted session state and message history access to session.data.state and session.data.messageHistory.
    • Removed messageRouter from the returned plane object.
  • src/core/session-bridge/compose-message-plane.ts
    • Removed UnifiedMessageRouter import and its instantiation.
    • Updated MessagePlane type to no longer include messageRouter.
    • Removed gitResolver and gitTracker from dependencies as they are now passed directly to SessionRuntime.
    • Removed persistSession call from CapabilitiesPolicy instantiation.
  • src/core/session-bridge/compose-runtime-plane.test.ts
    • Removed mocks for route and getMessageRouter.
    • Added mocks for getCapabilitiesPolicy and emitEvent.
    • Updated assertions to reflect changes in runtime dependencies.
  • src/core/session-bridge/compose-runtime-plane.ts
    • Removed UnifiedMessageRouter import.
    • Updated ComposeRuntimePlaneOptions to include getCapabilitiesPolicy and emitEvent, and removed getMessageRouter.
    • Modified SessionRuntime dependencies to directly pass emitEvent, gitTracker, gitResolver, and capabilitiesPolicy.
    • Removed routeBackendMessage from SessionRuntime dependencies.
  • src/core/session-coordinator.ts
    • Imported EventEmitter and buildSessionServices.
    • Introduced BridgeFacade interface for backward compatibility.
    • Replaced SessionBridge with SessionServices and a BridgeFacade wrapper.
    • Updated various policy services to use specific APIs from SessionServices instead of the monolithic bridge.
    • Modified onProcessSpawned handler to use services.infoApi for seeding and adapter naming.
    • Updated connectBackend and closeSession to use services.backendApi and services.lifecycleService respectively.
    • Added a private closeSessions method to handle session shutdown and tracer destruction.
  • src/core/session-coordinator/build-services.ts
    • Added a new factory function to assemble the four compose planes into a flat SessionServices object, resolving circular dependencies.
  • src/core/session-services.ts
    • Added a new interface defining the SessionServices registry, consolidating all service APIs.
  • src/core/session/effect-executor.ts
    • Added a new module with executeEffects function to process Effect[] returned by the reducer, bridging to side-effectful services.
  • src/core/session/effect-types.ts
    • Added a new module defining the Effect union type, representing various side effects.
  • src/core/session/git-info-tracker.ts
    • Updated GitStateAccessors to use SessionData['state'].
  • src/core/session/message-queue-handler.test.ts
    • Updated session property access from session.field to session.data.field for lastStatus and queuedMessage.
  • src/core/session/message-queue-handler.ts
    • Updated QueueStateAccessors to use SessionData['lastStatus'].
  • src/core/session/session-data.test.ts
    • Added a new test file to verify the compile-time immutability of the SessionData type.
  • src/core/session/session-data.ts
    • Added a new interface defining SessionData as the immutable, serializable slice of a session's state.
  • src/core/session/session-repository.test.ts
    • Updated mock session creation and assertions to use the new session.data property for state access.
    • Modified getSnapshot and persist tests to reflect the new SessionData structure.
  • src/core/session/session-repository.ts
    • Introduced readonly data: SessionData to the Session interface, encapsulating all serializable state.
    • Moved backendSessionId, state, pendingPermissions, messageHistory, pendingMessages, queuedMessage, lastStatus, adapterName, and adapterSupportsSlashPassthrough into the data property.
    • Updated getSnapshot, getAllStates, createSession, persist, and persistSync methods to align with the new SessionData structure.
  • src/core/session/session-runtime.test.ts
    • Updated mock session creation to use the new data structure for state properties.
    • Modified SessionRuntimeDeps to include gitTracker, gitResolver, emitEvent, capabilitiesPolicy, and autoSendQueuedMessage.
    • Removed routeBackendMessage from dependencies.
    • Adjusted assertions to use runtime.getState(), runtime.getLastStatus(), etc.
    • Added new tests for orchestration logic related to session_init, result, status_change, team events, permission_request, and auth_status.
  • src/core/session/session-runtime.ts
    • Updated SessionRuntimeDeps to include gitTracker, gitResolver, emitEvent, capabilitiesPolicy, and autoSendQueuedMessage.
    • Changed the session property from readonly to mutable to allow reassigning the session object with new SessionData.
    • Refactored handleBackendMessage to utilize reduceSessionData and executeEffects for state management and side effects.
    • Introduced orchestrateSessionInit, orchestrateControlResponse, orchestrateResult, and emitTeamEvents methods to manage complex side effects and orchestration.
    • Updated all direct accesses and mutations of session.field to interact with this.session.data.field or create new SessionData objects.
  • src/core/session/session-state-reducer.test.ts
    • Added baseData helper function for creating minimal SessionData.
    • Updated tests to use reduceSessionData and assert on SessionData properties.
    • Added comprehensive tests for lastStatus, backendSessionId, pendingPermissions, and messageHistory (assistant, result, tool_use_summary) within the new reducer context.
  • src/core/session/session-state-reducer.ts
    • Introduced reduceSessionData as the primary entry point, returning a [SessionData, Effect[]] tuple.
    • Added buildEffects function to generate side effects based on state transitions.
    • Implemented reduceBackendSessionId, reducePendingPermissions, reduceLastStatus, and reduceMessageHistory as dedicated sub-reducers for specific SessionData fields.
    • Updated reduceStatusChange to handle permissionMode and is_compacting immutably.
  • src/core/slash/slash-command-chain.test.ts
    • Updated session.adapterSupportsSlashPassthrough to session.data.adapterSupportsSlashPassthrough.
  • src/core/slash/slash-command-chain.ts
    • Modified state access from session.state to session.data.state and session.adapterSupportsSlashPassthrough to session.data.adapterSupportsSlashPassthrough.
  • src/core/team/team-events.integration.test.ts
    • Replaced SessionBridge with buildSessionServices and a custom test bridge object.
    • Updated state access in assertions to snapshot?.state.team.
  • src/index.ts
    • Removed the export of SessionBridge.
  • src/server/ws-server-flow.integration.test.ts
    • Replaced SessionBridge with createBridgeWithAdapter (which now uses buildSessionServices).
  • src/testing/adapter-test-helpers.ts
    • Defined BridgeTestWrapper interface to provide a bridge-like facade for tests.
    • Updated createBridgeWithAdapter to use buildSessionServices and return a BridgeTestWrapper.
    • Modified setupInitializedSession to accept BridgeTestWrapper.
  • src/testing/cli-message-factories.ts
    • Updated createMockSession to support specifying SessionData properties via a data key in overrides, aligning with the new SessionData structure.
Activity
  • The pull request author, teng-lin, has refactored the core session management logic.
  • The changes include the deletion of SessionBridge and UnifiedMessageRouter, indicating a significant architectural overhaul.
  • A new SessionData type and buildSessionServices factory have been introduced to manage session state and dependencies.
  • All 2873 tests across 193 test files are passing, ensuring the stability of the refactor.
  • Specific e2e tests, such as the watchdog relaunch test, have been fixed and verified.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

…ionRuntime

Replace 4 inline deps.persistSession calls with purpose-specific methods:
- persistNow() for critical mutations (setAdapterName, sendUserMessage)
- markDirty() for frequent transitions (handleBackendMessage, orchestrateControlResponse)

markDirty() collapses multiple rapid state changes into a single write
via a 50ms debounce timer.
…s (~450 lines removed)

Fold the four compose-*-plane.ts files directly into buildSessionServices().
All lazy circular-dep resolution stays; only the function-call layer is removed.

- Delete: compose-runtime-plane.ts, compose-consumer-plane.ts,
          compose-message-plane.ts, compose-backend-plane.ts, types.ts
- Delete: corresponding .test.ts files (4 files, 6 tests)
- Move BridgeCoreContext definition into session-services.ts
- Move SessionBridgeInitOptions definition into build-services.ts

Closes plan Task 6.3 (second half).
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

The pull request refactors SessionRuntime into a pure reducer and effect executor pattern, deleting SessionBridge and UnifiedMessageRouter. This significantly reduces complexity and improves maintainability by centralizing state mutations and separating side effects. The changes also introduce SessionData for immutable state snapshots and SessionServices for a flat registry of services. The Python script fix_router.py is used to update references to the removed UnifiedMessageRouter and adapt the routeBackendMessage signature. Overall, the refactor aligns with functional programming principles, making the session management more predictable and testable.

I am having trouble creating individual review comments. Click here to see my feedback.

src/core/session/effect-executor.ts (1-8)

critical

This new file effect-executor.ts is a crucial part of the refactoring, as it centralizes the execution of side effects emitted by the pure reducer. This clearly separates concerns and improves the testability of the state management logic.

src/core/session-bridge/compose-message-plane.ts (117)

critical

The removal of messageRouter from the returned object is consistent with the deletion of the UnifiedMessageRouter class as part of the refactor.

src/testing/cli-message-factories.ts (71-144)

critical

The createMockSession function is significantly refactored to support the new SessionData structure. It now correctly extracts SessionData properties from overrides and initializes the data object, ensuring that mock sessions adhere to the new immutable state pattern. This is a critical change for maintaining consistency across tests.

src/index.ts (67)

critical

The removal of SessionBridge export is consistent with the deletion of the SessionBridge class as part of the refactor.

src/core/bridge/session-bridge-deps-factory.ts (125)

critical

The removal of createUnifiedMessageRouterDeps is a significant change, as UnifiedMessageRouter has been deleted and its responsibilities absorbed into the reducer/effect pipeline within SessionRuntime.

src/core/index.ts (45)

critical

The removal of SessionBridge export is consistent with the deletion of the SessionBridge class as part of the refactor.

src/core/session-bridge/compose-message-plane.ts (8)

critical

The removal of createUnifiedMessageRouterDeps is consistent with the deletion of UnifiedMessageRouter and its integration into SessionRuntime's internal logic.

src/core/session-bridge/compose-message-plane.ts (17)

critical

The removal of UnifiedMessageRouter import is consistent with the deletion of the UnifiedMessageRouter class as part of the refactor.

src/core/session-bridge/compose-message-plane.ts (56)

critical

The removal of messageRouter: UnifiedMessageRouter; from the MessagePlane interface is consistent with the deletion of the UnifiedMessageRouter class as part of the refactor.

src/core/session/effect-types.ts (1-10)

critical

This new file effect-types.ts is a crucial part of the refactoring, as it defines the Effect type, which represents a typed description of a side effect. This enables the pure reducer pattern by separating side effects from state mutations.

src/core/session-bridge/compose-runtime-plane.test.ts (28)

critical

The removal of const getMessageRouter = vi.fn(() => ({ route })); is consistent with the deletion of UnifiedMessageRouter and its integration into SessionRuntime's internal logic.

fix_router.py (47-48)

critical

This line removes the direct state mutation from unified-message-router.ts, which is a key part of refactoring to a pure reducer pattern. This is a critical change for centralizing state management.

src/core/session/session-state-reducer.ts (44-56)

critical

The new reduceSessionData function is the core of the pure reducer pattern. It takes SessionData and a UnifiedMessage, applies various sub-reducers to produce the next state, and then builds a list of Effect objects. This clearly separates state mutation from side effects.

src/core/session-bridge/compose-runtime-plane.test.ts (59)

critical

The removal of routeBackendMessage: (session: Session, message: unknown) => void; from the runtime.deps interface is consistent with the deletion of UnifiedMessageRouter and its integration into SessionRuntime's internal logic.

src/core/session-bridge/compose-runtime-plane.ts (14)

critical

The removal of UnifiedMessageRouter import is consistent with the deletion of the UnifiedMessageRouter class as part of the refactor.

src/core/session/session-state-reducer.ts (37-38)

critical

The addition of imports for Effect and SessionData are crucial for the new pure reducer pattern, as reduceSessionData now operates on SessionData and returns Effect[].

src/core/session/session-runtime.ts (704-709)

critical

The executeEffects call is crucial for handling side effects after the state has been reduced. This clearly separates pure state logic from impure side effects, improving testability and maintainability.

src/core/session/session-runtime.ts (684-688)

critical

The introduction of prevData and the call to reduceSessionData are central to the refactoring. reduceSessionData is a pure function that returns the next state and a list of effects, adhering to the new reducer pattern.

src/core/session-coordinator.ts (51)

critical

The removal of SessionBridge import is consistent with the deletion of the SessionBridge class as part of the refactor.

src/core/session/session-runtime.ts (119)

critical

The change from private readonly session: Session, to private session: Session, // readonly removed — we reassign via spread is a critical change that enables the immutable state pattern. By allowing session to be reassigned, SessionRuntime can now update its internal session reference with new SessionData objects produced by the reducer, ensuring immutability.

src/core/session/session-runtime.ts (100)

critical

The removal of routeBackendMessage from SessionRuntimeDeps is consistent with the deletion of UnifiedMessageRouter and its integration into SessionRuntime's internal logic.

src/core/session/session-runtime.ts (39)

critical

The addition of reduceSessionData import is crucial for the new pure reducer pattern, as SessionRuntime now uses this function to manage session state.

src/core/session-coordinator.ts (150-165)

critical

The call to buildSessionServices is the core of the refactoring, assembling all the new service planes into a single services object. This replaces the monolithic SessionBridge constructor with a more modular approach.

src/core/session/session-runtime.test.ts (339)

critical

The removal of routeBackendMessage: () => calls.push("route"), is consistent with the deletion of UnifiedMessageRouter and its integration into SessionRuntime's internal logic.

src/core/session-coordinator/build-services.ts (1-13)

critical

This new file build-services.ts is a crucial part of the refactoring, centralizing the assembly of all four compose planes into a single SessionServices object. This significantly reduces complexity in SessionCoordinator and improves modularity.

src/core/session/session-repository.ts (193-202)

critical

The introduction of the data property encapsulating state, pendingPermissions, messageHistory, pendingMessages, queuedMessage, lastStatus, adapterName, and adapterSupportsSlashPassthrough is a core change for implementing the immutable SessionData pattern. This centralizes the serializable state.

src/core/session/session-repository.ts (37-41)

critical

The Session interface is updated to reflect the new SessionData structure. The data property now holds the serializable state, while other properties remain for runtime handles. This is a significant architectural change for state management.

src/core/team/team-events.integration.test.ts (26)

critical

The change from SessionBridge to buildSessionServices is consistent with the deletion of the SessionBridge class and the new service-oriented architecture.

src/core/session-services.ts (1-9)

critical

This new file session-services.ts defines the SessionServices interface, which is a central part of the refactoring. It provides a clear and flat registry of all services, replacing the monolithic SessionBridge and improving modularity.

src/core/session/session-data.ts (1-9)

critical

This new file session-data.ts defines the SessionData interface, which is a central part of the refactoring. It enforces immutability for session state, making state management more predictable and testable.

src/core/bridge/runtime-manager-factory.ts (39-48)

medium

The removal of routeBackendMessage and the addition of emitEvent, gitTracker, gitResolver, and capabilitiesPolicy reflect the refactoring of UnifiedMessageRouter and the new responsibilities of SessionRuntime in orchestrating these services. This is a significant change in the architecture.

src/core/session/session-runtime.ts (109-112)

medium

The addition of gitTracker, gitResolver, emitEvent, and capabilitiesPolicy to SessionRuntimeDeps reflects the new responsibilities of SessionRuntime in orchestrating these services directly, rather than relying on UnifiedMessageRouter.

src/core/session/session-runtime.test.ts (38-49)

medium

The addition of gitTracker, gitResolver, emitEvent, and capabilitiesPolicy to the makeDeps function reflects the new responsibilities of SessionRuntime in orchestrating these services directly, rather than relying on UnifiedMessageRouter.

src/core/session-coordinator.ts (167-192)

medium

The creation of the bridge facade is a crucial step for maintaining backward compatibility with existing tests and utilities that relied on the SessionBridge interface. This allows for a gradual transition to the new service-oriented architecture.

src/core/session-coordinator.ts (146-148)

medium

The introduction of _bridgeEmitter as a plain EventEmitter is a key part of the refactoring, replacing the direct event emission from SessionBridge and allowing for a more modular event handling approach.

src/core/session-coordinator.ts (120-122)

medium

The introduction of services and _bridgeEmitter as private readonly properties is central to the refactoring. services holds the new flat registry of session services, and _bridgeEmitter replaces the event emission mechanism previously handled by SessionBridge.

src/core/bridge/runtime-manager-factory.test.ts (31-42)

medium

The removal of routeBackendMessage and the addition of emitEvent, getGitTracker, gitResolver, and getCapabilitiesPolicy reflect the refactoring of UnifiedMessageRouter and the new responsibilities of SessionRuntime in orchestrating these services. This is a significant change in the architecture.

src/core/session-coordinator.ts (82-111)

medium

The introduction of the BridgeFacade interface is a good design choice for maintaining backward compatibility with existing E2E tests and utilities while transitioning away from the SessionBridge class. This facade abstracts the underlying service changes.

src/core/session-bridge/compose-runtime-plane.ts (103-111)

medium

The removal of routeBackendMessage and the addition of emitEvent, gitTracker, gitResolver, and capabilitiesPolicy reflect the refactoring of UnifiedMessageRouter and the new responsibilities of SessionRuntime in orchestrating these services. This is a significant change in the architecture.

src/core/session-bridge/compose-runtime-plane.ts (65-66)

medium

The removal of getMessageRouter and the addition of getCapabilitiesPolicy and emitEvent reflect the refactoring of UnifiedMessageRouter and the new responsibilities of SessionRuntime in orchestrating these services.

src/core/session-coordinator/build-services.ts (48-61)

medium

The composeRuntimePlane call with lazy getters for getOrCreateSession, getBroadcaster, getQueueHandler, getSlashService, getBackendConnector, getPersistenceService, getGitTracker, getCapabilitiesPolicy, and emitEvent effectively resolves circular dependencies between the planes. This is a well-designed solution for complex inter-service communication.

src/core/session/session-runtime.ts (712-718)

medium

The orchestration of high-level side effects for session_init, result, and control_response is now handled directly within SessionRuntime, centralizing these complex interactions.

src/core/session/session-state-reducer.ts (89-253)

medium

The buildEffects function is a well-designed component that constructs a list of Effect objects based on the message type and the previous/next SessionData. This centralizes the logic for determining what side effects should occur after a state transition, making the reducer pure and the effects explicit.

src/core/session/session-runtime.ts (726-777)

medium

The new orchestrateSessionInit method encapsulates the complex logic for handling session_init messages, including backend session ID storage, git info resolution, registry population, and capabilities policy initialization. This centralizes and clarifies the initialization flow.

src/core/session-bridge/compose-runtime-plane.ts (40-41)

medium

The removal of getMessageRouter and the addition of getCapabilitiesPolicy and emitEvent reflect the refactoring of UnifiedMessageRouter and the new responsibilities of SessionRuntime in orchestrating these services.

src/core/session-bridge/compose-runtime-plane.test.ts (48-49)

medium

The removal of routeBackendMessage and the addition of getCapabilitiesPolicy and emitEvent reflect the refactoring of UnifiedMessageRouter and the new responsibilities of SessionRuntime in orchestrating these services.

src/core/session/session-runtime.ts (790-806)

medium

The new orchestrateResult method handles result messages, including git info refreshes and broadcasting session updates. This centralizes and clarifies the result message flow.

src/core/session/session-state-reducer.ts (564-635)

medium

The new reduceMessageHistory function centralizes the logic for updating the message history, including handling assistant messages, result messages, and tool use summaries. It correctly identifies and updates existing messages or appends new ones, ensuring that the message history is consistently managed.

src/core/session-bridge/compose-runtime-plane.test.ts (29-33)

medium

The addition of getCapabilitiesPolicy and emitEvent reflects the new responsibilities of SessionRuntime in orchestrating these services directly, rather than relying on UnifiedMessageRouter.

src/core/team/team-events.integration.test.ts (160-169)

medium

The createBridgeWithAdapter helper is updated to use buildSessionServices and return a bridge facade that exposes necessary methods. This is a crucial adaptation for tests to interact with the new service-oriented architecture.

src/core/session/session-runtime.ts (780-787)

medium

The new orchestrateControlResponse method handles control_response messages, delegating to capabilitiesPolicy and ensuring that any state changes are persisted. This centralizes and clarifies the control response flow.

src/testing/adapter-test-helpers.ts (168-187)

medium

The new BridgeTestWrapper type defines a minimal bridge-like facade for testing purposes. This is a good approach to maintain backward compatibility with existing tests while transitioning to a new service-oriented architecture.

src/testing/adapter-test-helpers.ts (193-251)

medium

The createBridgeWithAdapter function is updated to use buildSessionServices and return a BridgeTestWrapper facade. This is a crucial adaptation for tests to interact with the new service-oriented architecture, ensuring that the tests are compatible with the refactored codebase.

src/core/bridge/runtime-manager-factory.ts (18-23)

medium

The removal of routeBackendMessage and the addition of emitEvent, getGitTracker, gitResolver, and getCapabilitiesPolicy reflect the refactoring of UnifiedMessageRouter and the new responsibilities of SessionRuntime in orchestrating these services. This is a significant change in the architecture.

…/ and clean up stale imports

- Move all 12 src/core/bridge/ modules to src/core/session-coordinator/:
  runtime-manager, runtime-manager-factory, runtime-api, backend-api,
  session-lifecycle-service, session-info-api, session-broadcast-api,
  session-persistence-service, session-deps-factory (renamed from
  session-bridge-deps-factory), bridge-event-forwarder, slash-service-factory,
  message-tracing-utils

- Delete src/core/bridge/ directory (now empty)

- Fix broken import in build-services.ts:
  ../bridge/session-bridge-deps-factory.js → ./session-deps-factory.js
  All other ../bridge/* → ./*

- Fix stale import in consumer-gateway.ts:
  ../interfaces/session-bridge-coordination.js → ../interfaces/session-coordination.js

- Update session-services.ts: type imports from ./bridge/ → ./session-coordinator/

- Fix implicit any in session-deps-factory.ts registerConsumer identity param
- Fix implicit any in build-services.ts emit lambda via explicit cast

All 2867 tests pass, pnpm typecheck clean.
Delete modules that were pure pass-through delegations with no logic:
- BackendApi (50 lines) — inlined as connectBackendForSession helper
- SessionBroadcastApi (60 lines) — replaced with direct broadcaster calls
- SessionPersistenceService (39 lines) — replaced with direct store calls
- bridge-event-forwarder (28 lines) — inlined into build-services.ts

Move message-tracing-utils.ts → src/core/messaging/ (was misplaced
in session-coordinator/, but it's a pure T1 boundary utility)

SessionServices interface simplified:
- backendApi    → backendConnector (raw connector)
- broadcastApi  removed (broadcaster already exposed)
- persistenceService  removed (store already exposed)
+ capabilitiesPolicy  added (needed for disconnectBackend path)

All consumers updated: session-coordinator.ts, adapter-test-helpers.ts,
team-events.integration.test.ts.

All 2851 tests pass, pnpm typecheck clean.
Inline into build-services.ts (the single composition root):
- runtime-manager-factory.ts (51 lines) — createRuntimeManager
- slash-service-factory.ts (68 lines) — createSlashService
- session-deps-factory.ts (224 lines) — all dep factory functions

Absorb SessionInfoApi into SessionCoordinator:
- session-info-api.ts (57 lines) — getSession, seedSessionState,
  setAdapterName, getAllSessions now inlined as private helpers

SessionServices interface updated:
- infoApi  removed (absorbed into coordinator)
+ runtimeManager  added (coordinator needs it for session reads)

session-coordinator/ contents reduced from 14 → 8 files (4 source + 4 tests).
Remaining source files that require Milestone 3 to absorb:
- build-services.ts — composition root
- runtime-manager.ts — per-session runtime map
- runtime-api.ts — lease-guarded runtime operations
- session-lifecycle-service.ts — session create/close lifecycle

All 2842 tests pass, pnpm typecheck clean.
…t/Effect pattern

Tasks 5.1-5.3 from architecture plan:
- Added SessionEvent discriminated union for inputs to process()
- Added SessionRuntime.process(event) as single canonical entry point
- Updated RuntimeApi, RuntimeManager, and SessionLifecycleService to route all actions (backend message, inbound command, policy command, lifecycle signal) through process()
- Effect types and Executor were already built earlier and working
- All 2846 tests pass
- removed runtime-api.ts, runtime-manager.ts, session-lifecycle-service.ts
- inlined simple facade access to session runtime inside build-services.ts
- simplified SessionServices interface types
…essionData

Per the architecture plan, both halves of per-session state should live in
session-data.ts: SessionData (immutable, serializable) and SessionHandles
(mutable, non-serializable runtime references). session-repository.ts
re-exports SessionHandles for backward compatibility.
- Drop onInboundObserved, onInboundHandled, onBackendMessageObserved,
  onBackendMessageHandled, onSignal — none were wired up in production
  (build-session-services.ts), only existed as migration scaffolding.
- Remove the 2 test cases that tested those deleted hooks.
- Port all test calls from private handleBackendMessage/handleInboundCommand/
  handleSignal/handlePolicyCommand to the public process() entry point,
  aligning tests with the documented single-entry-point design.
- SessionRuntimeDeps is now a clean, minimal interface aligned with
  the architecture-post-refactor.md specification.
…GNAL

Replace the two separate event types (LIFECYCLE_SIGNAL with flat string
union, POLICY_COMMAND with PolicyCommand object) with a single typed
SYSTEM_SIGNAL variant carrying a SystemSignal discriminated union.

SystemSignal kinds (all UPPER_SNAKE_CASE):
  BACKEND_CONNECTED / BACKEND_DISCONNECTED / SESSION_CLOSED
  CONSUMER_CONNECTED / CONSUMER_DISCONNECTED
  GIT_INFO_RESOLVED / CAPABILITIES_READY
  IDLE_REAP / RECONNECT_TIMEOUT / CAPABILITIES_TIMEOUT

SessionRuntime.process() now has 3 clean cases:
  BACKEND_MESSAGE | INBOUND_COMMAND | SYSTEM_SIGNAL

handleSystemSignal() merges the old handleSignal() + handlePolicyCommand()
into one method dispatching on signal.kind.

All call sites updated: build-session-services.ts (4 sites),
session-runtime.test.ts (5 sites), session-event.test.ts (rewritten).

Matches architecture-post-refactor.md SessionEvent spec exactly.
SessionData now has a 'readonly lifecycle: LifecycleState' field.
SessionRuntime.transitionLifecycle() now spread-updates this.session.data
instead of assigning a private field.

This means lifecycle is part of the pure, serializable session state —
matching the architecture-post-refactor.md contract where:
  SessionData = all immutable state the reducer can inspect and produce.

Changes:
- session-data.ts: add lifecycle field (+ LifecycleState import)
- session-runtime.ts: remove private lifecycle field;
    getLifecycleState() → this.session.data.lifecycle
    transitionLifecycle() → spread-update data.lifecycle
    getSessionSnapshot() → read from data
- session-repository.ts: seed lifecycle: 'awaiting_backend' in createSession
- session-data.test.ts: add lifecycle to inline data literal
- cli-message-factories.ts: add 'lifecycle' to dataKeys + defaultData
…session-state-reducer

- Created sessionReducer as the single entry point for all pure state transitions.
- Abstracted pure history array mutations to history-reducer.ts.
- Abstracted pure effect descriptions to effect-mapper.ts.
- Bounded session-state-reducer.ts strictly to AI context reduction (SessionState).
- Updated SessionRuntime.process() to call the top-level pure sessionReducer and execute effects.
- Moved SessionRuntimeDeps interface to build-session-services.ts to decouple runtime from orchestration dependencies.
- Ensure all 2800+ tests pass with the new separated architecture.
…ttern

Delete build-session-services.ts (~867 lines) and session-services.ts (~115 lines).
Replace all accessor callback bags with direct getRuntime/service references.

Phase 1: Replace CapabilitiesStateAccessors, QueueStateAccessors, and
ConsumerPlaneRuntimeAccessors with getRuntime across capabilities-policy,
message-queue-handler, backend-connector, and consumer-gateway.

Phase 2: Inline all composition into SessionCoordinator constructor.
Coordinator now owns runtimes map and constructs all services directly.
Delete the 5 create* factory functions and forwardBridgeEventWithLifecycle.

Phase 3: Simplify SessionRuntimeDeps from a callback bag to direct service refs.
Replace persistSession/sendToBackend/tracedNormalizeInbound/warnUnknownPermission
callbacks with store/backendConnector/tracer/logger. Remove ensureMutationAllowed
guard and canMutateSession/onMutationRejected hooks. Remove onSessionSeeded and
onInvalidLifecycleTransition hooks (inline logger.warn and gitTracker calls).

Net: ~800 lines removed, zero behaviour changes, all 181 test files pass.
Route SYSTEM_SIGNAL through sessionReducer() instead of calling
transitionLifecycle() directly, activating the previously unreachable
reduceSystemSignal() branch. Lifecycle transitions are identical.
Add dedicated test files for effect-mapper, effect-executor, and
history-reducer (all previously at 0% or partial), and extend
session-state-reducer tests to cover compacting, modelUsage, control_response,
session_lifecycle effects, and reducer edge-case branches.

New files: effect-mapper.test.ts, effect-executor.test.ts, history-reducer.test.ts
+53 tests across 5 files (+2871 total, was 2816)
Add 18 tests covering previously uncovered branch conditions in
session-runtime.ts and session-reducer.ts:

session-runtime.ts (80.57% → 89.2% branches, 100% lines):
- sendControlRequest: null backendSession early-return branch
- handleBackendMessage: history trim when backend messages exceed limit
- orchestrateSessionInit: session_id event emission, gitResolver with
  cwd, slash_commands/skills registry hydration, applyCapabilities path
- orchestrateControlResponse: markDirty when session reference changes
- orchestrateResult: git update broadcast when refreshGitInfo truthy
- applyLifecycleFromBackendMessage: running/compacting → active branches
- sendPermissionResponse: event emission when no backendSession
- storePendingPermission: public accessor coverage

session-reducer.ts (92.08% → 94.96% branches, 100% lines):
- status_change with permissionMode → BROADCAST_SESSION_UPDATE patch
- tool_progress message → BROADCAST effect
- configuration_change with m.mode string → permissionMode in patch
- INBOUND_COMMAND path (reduceInboundCommand) for active and closing

Overall branch coverage: 85.95% → 90.68% (exceeds 90% threshold)
…ctor

- Merge session-coordinator.wiring.test.ts into session-coordinator.test.ts
  (new TrackingProcessManager + "SessionCoordinator wiring" describe block)
- Move session-coordinator.permission-flow.integration.test.ts
  → src/core/coordinator/permission-flow.integration.test.ts
  (fix relative import path for new location)
- Rename session-services.integration.test.ts
  → session-core.integration.test.ts (SessionBridge class no longer exists)
- Rename session-services.adapter-consumer.integration.test.ts
  → session-core.adapter-consumer.integration.test.ts
- Update BridgeTestWrapper JSDoc in adapter-test-helpers.ts
- Update docs/architecture.md to reflect post-refactor module names
The worktree's node_modules is a symlink to the main repo's node_modules.
.gitignore had `node_modules/` (trailing slash) which only matches
directories, not symlinks — so the symlink was tracked and committed in
d810fbf.

On CI checkout this became a regular file at that path, causing pnpm
`mkdir node_modules` to fail with ENOTDIR (exit 236).

- git rm --cached node_modules (untrack the symlink)
- Add bare `node_modules` to .gitignore to cover files/symlinks too
- Remove dangling ToC entry (What Changed From Pre-Refactor)
- Fix SessionCoordinator API: remove non-existent process(sessionId, event),
  add renameSession(), executeSlashCommand(), store/broadcaster/backendConnector
- Fix SystemSignal types: remove TIMEOUT, remove extra fields (backendSession,
  gitInfo, capabilities), add SESSION_CLOSED
- Fix INBOUND_COMMAND: add ws: WebSocketLike field
- Fix Effect type: remove SEND_TO_BACKEND, RESOLVE_GIT_INFO,
  SEND_CAPABILITIES_REQUEST, APPLY_CAPABILITIES, TRACE_T4 (these are
  handled by SessionRuntime methods directly, not through the effect system)
- Fix EffectExecutor section: list only the 5 actual effects it dispatches
- Fix SessionData: remove id (lives on Session), add backendSessionId
- Fix file layout: move type files to session/ (effect-types.ts,
  session-event.ts, session-data.ts), add session-lease-coordinator.ts,
  update interfaces/ to show actual files, update all line counts
- Fix inbound/outbound flow diagrams: runtime.process() not coordinator.process()
- Fix BackendConnector description: BACKEND_CONNECTED has no backendSession field
- Update status header: target → current state
@teng-lin teng-lin merged commit 0429dcc into main Feb 24, 2026
6 checks passed
@teng-lin teng-lin deleted the session-actor-refactor branch February 24, 2026 00:15
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.

1 participant