May 26, 2026

ACT Rework: Cooperative Cancel, Subagent Actions, Stop+Undo

The ACT loop received a major rework

The ACT loop received a major rework. The stop button was moved from the chat input box into the ACT cycle bubble, and the legacy SteerAbility was deleted, removing all steer-related plumbing and the intercept parameter from dispatch_message(). Enter key no longer sends messages on any device. Mid-ACT user messages trigger cooperative cancellation: a POST /chat/interrupt cancels the active turn, _cleanup_cancelled_turn() deletes tool_calls and transcript rows, and the backend concatenates original and new messages for a fresh turn. Race conditions were addressed with cancel checks after LLM calls, guarded active UMP references to prevent cleanup collisions, and backend message concatenation.

Subagent abilities gained three new actions besides spawn: list returns active subagents with metadata, stop cancels a subagent by ID, and direct stops then respawns with concatenated prompt. A policy matrix defaults list and stop to allow everywhere (read-only/control), while spawn and direct are allowed in chat and external contexts but denied in subconscious, with an ask subagent fallback. A new GET /chat/subagents/active endpoint provides frontend hydration on reconnect, and _SUBAGENT_OVERRIDE dict in policy service handles context divergence between chat and subagent.

The stop button’s undo behavior was reimplemented. requestStop() now immediately restores UI state: removes the ACT cycle bubble and the user’s message bubble, restores the original message text into the textarea, aborts any pending WebSocket callbacks, and resets the send state. The backend cancellation is fire-and-forget. The stop icon changed from square to undo arrow with a tooltip.

Ability error surfacing was fixed. Previously, _build_success_result extracted only the text key from dict-returning abilities, silently discarding error messages. Browser timeouts, URL blocks, and unavailability left the LLM with an empty result, causing indefinite retries. Now error messages are prepended as [ERROR] ..., logged, dispatch status set to error, and SSE ok field reflects failure.

Several operational improvements: a one-time migration moves legacy .voice-deps-installed stamp to the DB’s voice_enabled setting; the installer upgrade check was simplified to detect the app/ directory existence; run.sh now exports ~/.local/bin in PATH for non-login shells; and architecture docs were updated to reflect the new POST endpoint and removed steer references.

  • SteerAbility removed; dispatch_message() no longer accepts intercept param, all steer plumbing deleted

  • Stop button now performs instant UI undo — removes ACT cycle + user bubble, restores input text, aborts callbacks

  • SubagentAbility extended with list, stop, direct actions; per-context policy matrix defaults (list/stop allowed everywhere, spawn/direct restricted)

  • Cooperative cancellation via POST /chat/interrupt cleans up cancelled turn (DELETE tool_calls + transcript rows), handles mid-turn redirect

  • Ability error messages now surfaced as [ERROR] ... in LLM result, dispatch status ‘error’, SSE ok false — fixes infinite retries

  • Enter key disabled for message sending; Send button is sole submission method