Problem
crates/tui/src/runtime_threads.rs is a 7,133-line module holding four concerns: the persistent thread store, the RuntimeThreadManager orchestrator, turn execution/monitoring, and the DynamicToolExecutor bridge. The numbers have grown since this was filed: impl RuntimeThreadManager now spans ~4,900 lines (~90 methods) and monitor_turn alone is ~1,035 lines. The precondition called out at filing — extract tests first — has landed (runtime_threads/tests.rs, 7,718 lines, 119 tests), so the module split is unblocked. This code owns persisted runtime state, so the split must be pure code motion with the persisted JSON shape untouched.
Current evidence
Measured at v0.9.1 (2026-07-18), crates/tui/src/runtime_threads.rs:
- Constants, test fault-injection, stream-delta batching, prompt-summary merge helpers: lines 55–320 (
coalesce_stream_delta at 149, merge_summary_into_prompt at 208, validated_record_id at 243, CURRENT_RUNTIME_SCHEMA_VERSION = 2 at 272).
- Persisted record types: lines 324–585 (
RuntimeTurnStatus 324, TurnItemKind 335, ThreadRecord 359, TurnRecord 422, TurnItemRecord 474, RuntimeEventRecord 495, RuntimeEventReplay 509, RuntimeStoreState 525, event-append error types 541–585).
RuntimeThreadStore + impl: lines 587–1163. On-disk layout: <root>/{threads,turns,items,events}/<id>.json[l] plus state.json (see RuntimeThreadStore::open, line 603).
- Manager config + request/response/pending types: lines 1164–1615 (
RuntimeThreadManagerConfig 1164, CreateThreadRequest 1205, StartTurnRequest 1249, ThreadDetail 1276, ActiveThreads 1433, pending approval/user-input/dynamic-tool entries 1546–1615).
impl RuntimeThreadManager: lines 1616–6517, including monitor_turn (5263–6297), start_turn (4303), seed_thread_from_messages (3613), ensure_engine_loaded (4848), event emission (emit_event 2474, append_and_broadcast_event 2491, events_since 4805, replay_events 4825, subscribe_events 2461), and recovery (recover_interrupted_state 6358).
DynamicToolExecutor bridge: helpers at 6519–6568 and impl crate::tools::spec::DynamicToolExecutor for RuntimeThreadManager at 6569–6722 (trait defined at crates/tui/src/tools/spec.rs:35).
- Free helpers at the tail: LRU (6723–6773),
parse_mode (6774), tool_kind_for_name (6787), AgentRebindHint/collect_agent_rebind_hints (6810–6857), summarize_text (6858), and the security-sensitive store path-safety family — checked_runtime_store_root (6895), checked_existing_runtime_store_dir (6926), reject_symlinked_store_file (6951), rollback_failed_event_append (6964), reject_symlinked_store_dir (6978).
- Tests already extracted:
crates/tui/src/runtime_threads/tests.rs (7,718 lines, 119 tests, use super::*). Schema/back-compat guards exist: store_load_thread_rejects_newer_schema_version (tests.rs:1703), current_runtime_schema_version_is_two_on_v066 (tests.rs:2214), torn-event-log repair coverage around tests.rs:1952.
- External consumers:
crates/tui/src/runtime_api.rs, crates/tui/src/runtime_api/sessions.rs, crates/tui/src/task_manager.rs.
Scope
Land as small PRs in this order (each step is one reviewable move; runtime_threads.rs stays the module root, submodules join runtime_threads/tests.rs):
runtime_threads/types.rs — persisted record/request/response/pending structs and enums (lines 324–585 and 1164–1615 region types). Serde attributes move verbatim.
runtime_threads/store.rs — RuntimeThreadStore + impl (587–1163) together with the path-safety and event-log-repair helpers from the tail (6895–7132); these are load-bearing security checks and must move whole, not be reimplemented.
runtime_threads/events.rs — event projection/emission: RuntimeEventRecord-adjacent replay types, coalesce_stream_delta, the emit_*/append_and_broadcast_event/events_since/replay_events cluster, and collect_agent_rebind_hints.
runtime_threads/dynamic_tools.rs — the DynamicToolExecutor impl and its helpers (6519–6722).
runtime_threads/turn_executor.rs — monitor_turn (5263–6297) plus the claimed-turn monitors (monitor_claimed_turn 4168, spawn_claimed_turn_monitor 4209, settle_claimed_turn_failure 4000) split out of the manager impl last.
- Whatever remains of
impl RuntimeThreadManager (thread CRUD, start/steer/interrupt/compact, engine loading, recovery) stays in the root or moves to runtime_threads/manager.rs as a final, mechanical step.
Key files
crates/tui/src/runtime_threads.rs
crates/tui/src/runtime_threads/tests.rs
crates/tui/src/tools/spec.rs
crates/tui/src/runtime_api.rs
crates/tui/src/task_manager.rs
Acceptance criteria
Verification
cargo test -p codewhale-tui --bin codewhale-tui --locked runtime_threads
cargo test -p codewhale-tui --bin codewhale-tui --locked runtime_api
cargo fmt
Out of scope
- Any change to persisted thread/turn/item/event JSON, file naming, or directory layout (that requires its own issue with migration tests).
- Behavior changes to approval/user-input/dynamic-tool settlement timeouts or turn lifecycle.
task_manager.rs / runtime_api.rs refactors beyond import updates.
Related
Triage note: body restructured for agent execution on 2026-07-18; prior comment refinements folded in. Original wording preserved in edit history.
Problem
crates/tui/src/runtime_threads.rsis a 7,133-line module holding four concerns: the persistent thread store, theRuntimeThreadManagerorchestrator, turn execution/monitoring, and theDynamicToolExecutorbridge. The numbers have grown since this was filed:impl RuntimeThreadManagernow spans ~4,900 lines (~90 methods) andmonitor_turnalone is ~1,035 lines. The precondition called out at filing — extract tests first — has landed (runtime_threads/tests.rs, 7,718 lines, 119 tests), so the module split is unblocked. This code owns persisted runtime state, so the split must be pure code motion with the persisted JSON shape untouched.Current evidence
Measured at v0.9.1 (2026-07-18),
crates/tui/src/runtime_threads.rs:coalesce_stream_deltaat 149,merge_summary_into_promptat 208,validated_record_idat 243,CURRENT_RUNTIME_SCHEMA_VERSION = 2at 272).RuntimeTurnStatus324,TurnItemKind335,ThreadRecord359,TurnRecord422,TurnItemRecord474,RuntimeEventRecord495,RuntimeEventReplay509,RuntimeStoreState525, event-append error types 541–585).RuntimeThreadStore+ impl: lines 587–1163. On-disk layout:<root>/{threads,turns,items,events}/<id>.json[l]plusstate.json(seeRuntimeThreadStore::open, line 603).RuntimeThreadManagerConfig1164,CreateThreadRequest1205,StartTurnRequest1249,ThreadDetail1276,ActiveThreads1433, pending approval/user-input/dynamic-tool entries 1546–1615).impl RuntimeThreadManager: lines 1616–6517, includingmonitor_turn(5263–6297),start_turn(4303),seed_thread_from_messages(3613),ensure_engine_loaded(4848), event emission (emit_event2474,append_and_broadcast_event2491,events_since4805,replay_events4825,subscribe_events2461), and recovery (recover_interrupted_state6358).DynamicToolExecutorbridge: helpers at 6519–6568 andimpl crate::tools::spec::DynamicToolExecutor for RuntimeThreadManagerat 6569–6722 (trait defined atcrates/tui/src/tools/spec.rs:35).parse_mode(6774),tool_kind_for_name(6787),AgentRebindHint/collect_agent_rebind_hints(6810–6857),summarize_text(6858), and the security-sensitive store path-safety family —checked_runtime_store_root(6895),checked_existing_runtime_store_dir(6926),reject_symlinked_store_file(6951),rollback_failed_event_append(6964),reject_symlinked_store_dir(6978).crates/tui/src/runtime_threads/tests.rs(7,718 lines, 119 tests,use super::*). Schema/back-compat guards exist:store_load_thread_rejects_newer_schema_version(tests.rs:1703),current_runtime_schema_version_is_two_on_v066(tests.rs:2214), torn-event-log repair coverage around tests.rs:1952.crates/tui/src/runtime_api.rs,crates/tui/src/runtime_api/sessions.rs,crates/tui/src/task_manager.rs.Scope
Land as small PRs in this order (each step is one reviewable move;
runtime_threads.rsstays the module root, submodules joinruntime_threads/tests.rs):runtime_threads/types.rs— persisted record/request/response/pending structs and enums (lines 324–585 and 1164–1615 region types). Serde attributes move verbatim.runtime_threads/store.rs—RuntimeThreadStore+ impl (587–1163) together with the path-safety and event-log-repair helpers from the tail (6895–7132); these are load-bearing security checks and must move whole, not be reimplemented.runtime_threads/events.rs— event projection/emission:RuntimeEventRecord-adjacent replay types,coalesce_stream_delta, theemit_*/append_and_broadcast_event/events_since/replay_eventscluster, andcollect_agent_rebind_hints.runtime_threads/dynamic_tools.rs— theDynamicToolExecutorimpl and its helpers (6519–6722).runtime_threads/turn_executor.rs—monitor_turn(5263–6297) plus the claimed-turn monitors (monitor_claimed_turn4168,spawn_claimed_turn_monitor4209,settle_claimed_turn_failure4000) split out of the manager impl last.impl RuntimeThreadManager(thread CRUD, start/steer/interrupt/compact, engine loading, recovery) stays in the root or moves toruntime_threads/manager.rsas a final, mechanical step.Key files
crates/tui/src/runtime_threads.rscrates/tui/src/runtime_threads/tests.rscrates/tui/src/tools/spec.rscrates/tui/src/runtime_api.rscrates/tui/src/task_manager.rsAcceptance criteria
RuntimeThreadStore+ path-safety helpers) lives in its own module, separate from runtime turn execution.DynamicToolExecutorimpl lives outside the manager block/module.runtime_threads/tests.rspass unmodified (they useuse super::*; keep moved items reachable viapub(crate)/re-exports rather than widening topub).threads/,turns/,items/,events/,state.json,schema_version: 2) are byte-for-byte unchanged; the schema-guard tests at tests.rs:1703 and tests.rs:2214 still pass without edits.runtime_apiandtask_managercompile without import-path churn (root re-exports preservecrate::runtime_threads::Xpaths).Verification
Out of scope
task_manager.rs/runtime_api.rsrefactors beyond import updates.Related
Triage note: body restructured for agent execution on 2026-07-18; prior comment refinements folded in. Original wording preserved in edit history.