Skip to content

refactor: session debt cleanup — state machine, storage ISP, creation/close unification - #171

Merged
Leoyzen merged 14 commits into
refactor/agentwolf_v1from
refactor/session-debt-cleanup
Jul 17, 2026
Merged

refactor: session debt cleanup — state machine, storage ISP, creation/close unification#171
Leoyzen merged 14 commits into
refactor/agentwolf_v1from
refactor/session-debt-cleanup

Conversation

@Leoyzen

@Leoyzen Leoyzen commented Jul 16, 2026

Copy link
Copy Markdown
Collaborator

Session Debt Cleanup

Closes #170.

Implements the session-debt-cleanup OpenSpec change — a comprehensive refactor of the session management subsystem across 6 phases (88/89 tasks complete).

Summary

Phase Tasks Description
1 9 State machine mapping & invariants (SessionStateMapper)
2 15 Storage ISP decomposition (7 Protocols + adapter)
3 14 Storage bug fixes & SQLSessionStore elimination
4 18 Creation path unification (all protocols → SessionPool.create_session())
5 10 Session module splitting (5 large files split into mixins)
6 18 Close path unification & protocol migration

Phase 1 — State Machine Mapping & Invariants

  • Fixed SessionData.status docstring (removed completed/failed, added closed)
  • Created SessionStateMapper with invariant checking and reconciliation
  • Removed all RunStatus references from AGENTS.md
  • 37 new tests (unit + integration)

Phase 2 — Storage ISP Decomposition & Adapter

  • Defined 7 @runtime_checkable Protocols: SessionPersistence, MessagePersistence, SessionMetadata, CommandLog, ProjectStoreProtocol, CheckpointStore, StatsAggregator
  • Created StorageProviderAdapter implementing all 7 Protocols
  • 20 new tests for protocol conformance and delegation

Phase 3 — Storage Bug Fixes & SQLSessionStore Elimination

  • Fixed SQLModelProvider.save_session() with dialect-aware UPSERT (was delete-then-insert)
  • Fixed _session_from_db() to read status field (was always defaulting to "active")
  • Migrated all consumers from SessionStore API to SessionPersistence API
  • Deleted SQLSessionStore (335 LOC) — all consumers now use SQLModelProvider
  • 24 new tests for round-trip, checkpoint, edge cases, E2E lifecycle

Phase 4 — Creation Path Unification

  • All 6 protocol servers (ACP, A2A, AG-UI, OpenAI API, OpenCode, Vercel) now delegate to SessionPool.create_session()
  • Unified session ID generation via generate_session_id() (removed all uuid.uuid4() usage)
  • Added SessionPool.create_child_session() as first-class API
  • ACP resume_session() now acquires _get_resume_lock() for thread safety
  • 16 new tests for creation unification

Phase 5 — Session Module Splitting

Pure structural refactors (no behavior changes) using mixin pattern:

File LOC Split Into
session_pool.py 1844 session_pool_messaging.py, session_pool_teams.py, session_pool_runs.py, session_pool_config.py
session_controller.py 1438 session_controller_runs.py, session_controller_close.py, session_controller_agent.py
sql_provider.py 1047 sql_messages.py, sql_sessions.py, sql_projects.py
session_pool_integration.py 1491 opencode_session_routes.py, opencode_event_bridge.py, opencode_message_bridge.py
acp_server/session.py 1028 session_lifecycle.py, session_events.py, session_agent_mgmt.py

Phase 6 — Close Path Unification & Protocol Migration

  • Standardized 7-step cleanup ordering in _close_session_unlocked(): cancel RunHandle → await completion → MCP cleanup → agent __aexit__ → session persistence → EventBus unsubscription → cascade close children
  • SessionPool.close_session() delegates to SessionController.close_session()
  • ACPSessionManager.close_session() delegates to SessionPool.close_session()
  • Removed deprecated receive_request() — all callers migrated to send_message()
  • MCP cleanup and agent __aexit__ guaranteed in close path
  • 14 ACP snapshots pass, full suite: 2684 passed, 17 skipped, 1 pre-existing failure (test_vision, unrelated)

Test Results

  • 2684 tests pass (up from 509 at baseline due to new tests)
  • 17 skipped, 1 pre-existing failure (test_vision, unrelated to changes)
  • Ruff lint + format clean
  • All 14 ACP snapshot tests pass

Files Changed

113 files changed, +9990 / -6190 lines

Deferred

Task 0.1 (integration test fixture) intentionally deferred — requires a dedicated CI environment with real model endpoints.

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request performs a significant refactoring of the session management and storage layers, splitting monolithic files into mixins for better maintainability. Key changes include unifying session creation paths through SessionPool, replacing the deprecated SessionStore protocol with SessionPersistence and other granular ISP protocols, and updating the SQLModelProvider to implement these protocols directly. The review identified two high-severity issues where prompt stringification in SessionPoolMessagingMixin and SessionPoolRunsMixin would destroy multimodal content; these should be addressed by flattening prompt lists instead of joining them into strings.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread src/agentpool/orchestrator/session_pool_messaging.py
Comment thread src/agentpool/orchestrator/session_pool_runs.py
…/close unification

Implements the session-debt-cleanup OpenSpec change (88/89 tasks complete).

Phase 1 — State Machine Mapping & Invariants:
- Fixed SessionData.status docstring (removed completed/failed, added closed)
- Created SessionStateMapper with invariant checking and reconciliation
- Removed all RunStatus references from AGENTS.md
- 37 new tests (unit + integration)

Phase 2 — Storage ISP Decomposition & Adapter:
- Defined 7 @runtime_checkable Protocols (SessionPersistence, MessagePersistence,
  SessionMetadata, CommandLog, ProjectStoreProtocol, CheckpointStore, StatsAggregator)
- Created StorageProviderAdapter implementing all 7 Protocols
- 20 new tests for protocol conformance and delegation

Phase 3 — Storage Bug Fixes & SQLSessionStore Elimination:
- Fixed SQLModelProvider.save_session() with dialect-aware UPSERT
- Fixed _session_from_db() to read status field (was always defaulting to active)
- Migrated all consumers from SessionStore API to SessionPersistence API
- Deleted SQLSessionStore (335 LOC) — all consumers now use SQLModelProvider
- 24 new tests for round-trip, checkpoint, edge cases, E2E lifecycle

Phase 4 — Creation Path Unification:
- All 6 protocol servers now delegate to SessionPool.create_session()
- Unified session ID generation via generate_session_id() (removed uuid usage)
- Added SessionPool.create_child_session() as first-class API
- ACP resume_session() now acquires _get_resume_lock()
- 16 new tests for creation unification

Phase 5 — Session Module Splitting:
- Split session_pool.py (1844 LOC) into 4 mixin files
- Split session_controller.py (1438 LOC) into 3 mixin files
- Split sql_provider.py (1047 LOC) into 3 mixin files
- Split session_pool_integration.py (1491 LOC) into 3 mixin files
- Split acp_server/session.py (1028 LOC) into 3 mixin files
- All splits are pure structural refactors with no behavior changes

Phase 6 — Close Path Unification & Protocol Migration:
- Standardized 7-step cleanup ordering in _close_session_unlocked()
- SessionPool.close_session() delegates to SessionController.close_session()
- ACPSessionManager.close_session() delegates to SessionPool.close_session()
- Removed deprecated receive_request() — all callers migrated to send_message()
- MCP cleanup and agent __aexit__ guaranteed in close path
- 14 ACP snapshots pass, full suite: 2684 passed, 17 skipped, 1 pre-existing failure

Refs: #170
@Leoyzen
Leoyzen force-pushed the refactor/session-debt-cleanup branch from d96cd79 to a187398 Compare July 17, 2026 01:39
@Leoyzen
Leoyzen force-pushed the refactor/session-debt-cleanup branch from 45e6934 to 4a00588 Compare July 17, 2026 01:49
@Leoyzen

Leoyzen commented Jul 17, 2026

Copy link
Copy Markdown
Collaborator Author

/gemini review

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request performs a significant refactoring of the session management and storage layers to improve consistency and maintainability. Key changes include splitting monolithic modules into mixins (e.g., SessionController, ACPSession, SessionPool), unifying session creation paths, and decomposing the legacy StorageProvider into 7 focused Protocols following the Interface Segregation Principle. My feedback identifies several high-severity issues: potential TypeError exceptions when flattening multimodal prompts, resource leaks during cleanup due to improperly handled exceptions in finally blocks, and a race condition in session resumption due to improper lock handling. I have also recommended using safe lookups instead of factory methods to avoid recreating phantom sessions.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread src/agentpool_server/acp_server/session_manager.py
Comment thread src/agentpool/orchestrator/session_pool_runs.py
Comment thread src/agentpool/orchestrator/session_pool_messaging.py
Comment thread src/agentpool/orchestrator/session_pool_messaging.py
Comment thread src/agentpool/orchestrator/session_pool_runs.py
Comment thread src/agentpool/agents/acp_agent/turn.py
Comment thread src/agentpool_server/opencode_server/opencode_message_bridge.py Outdated
Comment thread src/agentpool_server/acp_server/session_lifecycle.py
Leoyzen added 3 commits July 17, 2026 10:23
…linter

- Add class-level type annotations and TYPE_CHECKING method stubs to all
  mixin classes for attributes/methods provided by the main class (73 mypy
  errors fixed across 12 files)
- Fix ruff lint errors in tests/ (TC001, D403, F841, PLW0108, SIM105, BLE001)
- Fix ruff format on 3 test files
- Remove deleted session_store from import-linter ignore list in pyproject.toml
- Fix ACP turn flatten logic: add isinstance(p, list) check before extend()
- Fix no-any-return errors with explicit cast() in session.py and event_bridge
…split

- Restore contextlib.suppress(asyncio.CancelledError, RuntimeError) around
  gen.aclose() and event_bus.unsubscribe() in _run_stream_run_turn finally
  block — was lost when method moved to session_pool_runs.py mixin
- Restore deps parameter extraction from kwargs and pass to
  _create_run_handle → AgentRunContext — was lost in Phase 5 split
- Catch asyncio.CancelledError in shutdown() alongside Exception —
  CancelledError is BaseException since Python 3.8, not caught by
  'except Exception'. Prevents test break behavior from crashing shutdown.
- Update _create_run_handle stub in SessionPoolMessagingMixin to match
  new deps parameter signature

Fixes 3 core test failures in test_break_behavior.py:
  test_simple_break_after_n_events
  test_break_with_exception_handling
  test_conversation_history_after_break
@Leoyzen

Leoyzen commented Jul 17, 2026

Copy link
Copy Markdown
Collaborator Author

/gemini review

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request performs a major refactoring of the session management and orchestration layers, modularizing SessionController and SessionPool into dedicated mixins, unifying session creation paths, and integrating session persistence directly into SQLModelProvider. The review feedback highlights several critical issues that must be addressed: a race condition on RunHandle in spawn_subagent, a bug in the session close sequence that overwrites the 'checkpointed' status to 'closed' (breaking resume functionality), redundant dead code in opencode_session_routes.py, a potential resource leak when stopping child event consumers, and an unhandled asyncio.CancelledError during generator closure that could skip crucial cleanup steps.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread src/agentpool/capabilities/runloop_delegation.py Outdated
Comment thread src/agentpool/orchestrator/session_controller_close.py Outdated
Comment thread src/agentpool/orchestrator/session_controller_close.py
Comment thread src/agentpool/orchestrator/session_controller_close.py Outdated
Comment thread src/agentpool_server/opencode_server/opencode_session_routes.py Outdated
Comment thread src/agentpool_server/opencode_server/opencode_event_bridge.py
Comment thread src/agentpool/orchestrator/session_pool_runs.py
Leoyzen added 4 commits July 17, 2026 11:18
…tatus, CancelledError cleanup

Fix 1: run_handle.start("") race condition in runloop_delegation.py
- Replace dual start() call with EventBus subscription to avoid
  corrupting RunHandle state from concurrent start() calls

Fix 2-4: Checkpointed status overwritten in session_controller_close.py
- Add checkpointed parameter to _close_session_unlocked()
- Skip _mark_session_closed() when session was already checkpointed
- Pass checkpointed flag from _close_session_run_turn()

Fix 5: Remove dead elif block in opencode_session_routes.py
- The elif condition was identical to the if condition (unreachable)

Fix 6: stop_event_consumer exception handling in opencode_event_bridge.py
- Wrap each child stop_event_consumer in try-except so one failure
  doesn't prevent remaining children from being stopped

Fix 7: CancelledError not caught in cleanup paths
- Use save-and-re-raise pattern in session_pool_runs.py and
  session_pool_messaging.py to ensure cleanup runs even when
  gen.aclose() raises asyncio.CancelledError (BaseException)
…shot test

Replace direct _execute_turn(agent=None) call with the existing
_make_run_handle + _StubTurn pattern from tests/lifecycle/test_run_loop.py.
Spy on snapshot_store.save() to capture the RUNNING snapshot (which has
prompts_serialized) before the post-turn IDLE snapshot overwrites it.
@Leoyzen

Leoyzen commented Jul 17, 2026

Copy link
Copy Markdown
Collaborator Author

/gemini review

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request refactors the session management and orchestration layers by splitting SessionController and SessionPool into modular mixins, decomposing StorageProvider into focused protocols, unifying session creation paths with sortable IDs, and improving multimodal prompt serialization. Feedback on these changes highlights several critical improvements: using .extend() instead of .append() to prevent nested lists in recovery prompts, guarding agent.conversation access against AttributeError, wrapping cleanup operations in finally blocks with try-except blocks, restoring the 300-second timeout for subagents to prevent hangs, and optimizing the message cache eviction loop from $O(N^2)$ to $O(N)$.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread src/agentpool/orchestrator/run.py Outdated
Comment thread src/agentpool/orchestrator/session_controller_runs.py
Comment thread src/agentpool/orchestrator/session_pool_messaging.py
Comment thread src/agentpool/orchestrator/session_pool_runs.py
Comment thread src/agentpool/capabilities/runloop_delegation.py Outdated
Comment thread src/agentpool/orchestrator/session_pool.py Outdated
Leoyzen added 2 commits July 17, 2026 14:22
…O(N) eviction

1. run.py: Change .append(deserialized) to .extend(deserialized) in
   _handle_recovery() — preserves individual prompt structure instead
   of nesting as a single list item.

2. runloop_delegation.py: Restore 300s timeout on EventBus subscription
   using asyncio.timeout(). On timeout, yield RunErrorEvent instead of
   raising TimeoutError so parent agent handles it gracefully.

3. session_pool.py: Optimize _evict_message_cache from O(N²) to O(N)
   with single-pass candidate collection and bulk eviction.
- session_controller_agent.py: Remove hasattr(self.pool, 'todos') —
  AgentPool.__init__ always sets self.todos = TodoTracker()
- session_pool_runs.py: Replace getattr(event, 'event', event) with
  direct event.event access — EventEnvelope always has .event attribute
Leoyzen added 3 commits July 17, 2026 16:56
…al CancelledError

- ruff: fix lint+format on tests/orchestrator/test_aexit_hang.py
- run.py: add _force_cancelling flag set by cancel() before task.cancel()
- start(): only catch CancelledError when _force_cancelling is True;
  external task.cancel() (test cleanup) propagates normally
- Fixes 2 core test timeouts: test_worker_emits_subagent_events,
  test_subagent_event_depth_propagation
@Leoyzen
Leoyzen merged commit a56b104 into refactor/agentwolf_v1 Jul 17, 2026
9 checks passed
Leoyzen added a commit that referenced this pull request Jul 17, 2026
…reation/close unification (#171)

fixup! refactor: session debt cleanup — state machine, storage ISP, creation/close unification (#171)
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