June 2, 2026

Flat ACT loop lands: delegates, universal compaction, big test cut

34 commits land the ACT Loop Orchestrator Refactor (TKT-795), replacing the per-channel MessageProcessor subclass architecture with a single flat MessageProcess

34 commits land the ACT Loop Orchestrator Refactor (TKT-795), replacing the per-channel MessageProcessor subclass architecture with a single flat MessageProcessor.process() driven by per-channel ProcessorConfig objects. All four compaction strategies, turn-0 seeding, async delegate dispatch, and the four delegate tools now route through one path with no MP subclasses.

Phase T6–T13 deletes the eight dead MP subclass files (user_message_processor, dmn_message_processor, external_agent_message_processor, episode_encoder_processor, super_episode_encoder_processor, user_summary_processor, geo_pattern_processor, pattern_match_processor) and renames execute()→run() as the single Ability entrypoint. Dead camelCase shims, pre_dispatch, handle(), the INTERNAL flag, and the run()→execute() shim all removed; bash.run() folds in the destructive-command escalation that pre_dispatch used to handle. 37 abilities remain.

The 1561-line configs/channels.py monolith splits into a configs/channels/ package with _common, compaction, dmn, episode_encoder, external_agent, geo_pattern, pattern, skill_suggestion, super_episode, user, and user_summary. The dead subagent_compaction channel/config is removed — the universal-compaction redesign routes both trail >90% and history >80% through the single COMPACTION_CONFIG, with _compact_trail taking priority in _loop(). _seed_turn_zero() dispatches blocking memory recall + per-file attachment uploads through Ability.dispatch.

Four delegate tools replace SubagentAbility and SubagentProcessor: web_search, research, summariser, web_browse (the last renamed from ‘browser’ to avoid a flat-registry collision with the raw browser ability). Each is a standalone Ability that builds a clean-context ProcessorConfig inside run() and calls MessageProcessor.process(); shared _delegate.py provides DELEGATE_TOOL_NAMES, the recursion blocklist, and act-trail rendering. The async primitive in api/chat.py uses Ability.get_active_delegates() / Ability.cancel_delegate().

Three correctness fixes land alongside the refactor. AbilityRegistry.build_tools was left as the T2 stub return [], sending zero tools to the model — canonical get_tools() logic ported in. A _base↔_registry circular import left AbilityRegistry/PolicyService as None (crashing every turn-zero seed); dispatch now self-heals aliases on first use and revives the ‘Unknown tool’ graceful path. The moments pin/remember flow was returning HTTP 400 because the chat UI never held a transcript_id; backend now resolves the assistant turn from message_text via extract_plaintext, and create_moment still accepts an explicit integer transcript_id for programmatic callers.

Final commit cuts 2,659 LOC across tests and configs: removes four mock-collaborator unit-test files (ability_dispatch, async_delegate, compaction_universal, turn_zero_seeding) and trims two more, establishing the project discipline that unit tests cover pure functions while nightly scenarios cover orchestration. Earlier phase cuts trimmed nine test files by ~4,367 LOC while preserving every observable behavior — test_ability_compaction_internal.py, test_processor_config.py, test_act_loop_regression_guards.py, and other §9b tombstones deleted. Channels configs strip 88 redundant Apache license headers. Suite gates report 1344/1431 unit tests green with ruff clean.

Doc reconciliation completes the picture: 04-ARCHITECTURE.md is rewritten for the flat model and delegate tools (37-name concrete-abilities list, USAGE_CLASS_TO_CONTEXT four contexts, mode-gate documented as dormant linking TKT-800, registry build_tools + policy_visible surface), with sibling 09-TOOLS.md and 13-MESSAGE-FLOW.md aligned to the same facts. A chat-side fix routes delegate synthesis through renderMarkupTo so it matches normal assistant text rendering — no client-side sanitization added, with nh3 remaining the single allowlist authority.

  • ACT-loop refactor deletes 8 MessageProcessor subclasses; execute()→run() rename as single Ability entrypoint; flat process() path with no MP subclasses

  • Four delegate tools replace SubagentAbility/SubagentProcessor: web_search, research, summariser, web_browse; shared _delegate.py with recursion blocklist and ASYNC_CAPABLE wrappers

  • AbilityRegistry.build_tools was a stub returning [] — canonical get_tools() logic ported in; circular import left AbilityRegistry/PolicyService as None, fixed via idempotent self-heal on first dispatch

  • Universal compaction in MessageProcessor._loop(): _compact_trail (>90%) and _compact_history (>80%) with trail priority; 1561-line channels.py split into per-channel package; dead subagent_compaction deleted

  • Test discipline cut removes 2,659 LOC of mock-collaborator unit tests; unit tests reserved for pure functions, orchestration covered by nightly scenarios

  • Moments pin/remember fixed: server resolves assistant transcript row from message_text via extract_plaintext, accepts integer transcript_id for programmatic callers; 04-ARCHITECTURE.md rewritten for flat model + delegate tools