refactor: deliver messages directly to agent mailbox, remove TUI inbox routing#84
Merged
yishuiliunian merged 1 commit intomainfrom Apr 5, 2026
Merged
Conversation
…x routing TUI previously buffered messages in a local Inbox and used agent_idle as a routing decision: when agent was busy, messages were queued locally and an interrupt signal was used to wake the agent to drain the queue. This was TUI concept leakage into the agent runtime — agent internal state was being directly mutated by the display layer, and interrupt was overloaded with "cancel work" AND "new message arrived" semantics. The worst symptom: ephemeral agents exited immediately on user input because the yield_now idle check fired before the TUI's delayed inbox forward could complete the IPC round-trip. Architecture principles applied: - Agent owns its internal state (idle/busy); external actors interact only via mailbox delivery + explicit interrupt - TUI state (observable.status, is_idle) is derived solely from agent events, never set directly - InterruptSignal carries only cancel/shutdown semantics Key changes: - Remove agent_idle field; derive via AgentViewState::is_idle() from status - Remove Inbox struct entirely (dead after routing bypass) - TUI calls append_user_display() + route_message() directly — no buffering - Ephemeral idle check: drop yield_now(), drain_pending() is reliable now - drain_pending() returns Vec<AgentInput> so Control commands are not silently dropped - Merge InputFromClient into AgentInput (Interrupt variant was vestigial) - Status transition rolls back on emit failure for consistency - Upgrade observation-channel try_send drops from debug to warn for prod visibility 49/49 tests pass, zero clippy warnings, rustfmt clean.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Architecture changes
Principle: Agent owns its internal state. External actors interact only via mailbox delivery + explicit interrupt. TUI state is derived solely from agent events.
AgentViewState::is_idle()fromobservable.statusappend_user_display() + route_message()directly, no local bufferingyield_now(),drain_pending()is reliable now that messages go directly to agent mailboxVec<AgentInput>— Control commands no longer silently droppedFiles changed
loopal-session: removedinbox.rs, addedis_idle()/is_active_agent_idle(), newappend_user_display()loopal-tui: direct message delivery inkey_dispatch_ops.rs, allagent_idlereaders migratedloopal-runtime:drain_pending_input()processes Control inline, ephemeral idle simplified, status transition rollbackloopal-agent-server:HubFrontendusesAgentInput,drain_pending()preserves Controlloopal-agent-hub: observation event drops logged at warn levelloopal-protocol:InterruptSignalsemantics documented,ObservableAgentState::statuscontractTest plan