Commit graph

59 commits

Author SHA1 Message Date
Kent Overstreet
cf1c64f936 Split context_state_summary: ContextBudget for compaction, UI-only for display
context_state_summary() was used for both compaction decisions (just
needs token counts) and debug screen display (needs full tree with
labels). Split into:

- Agent::context_budget() -> ContextBudget: cheap token counting by
  category, used by compact(), restore_from_log(), mind event loop
- ContextBudget::format(): replaces sections_budget_string() which
  fragily pattern-matched on section name strings
- context_state_summary(): now UI-only, formatting code stays here

Also extracted entry_sections() as shared helper with include_memories
param — false for context_state_summary (memories have own section),
true for conversation_sections_from() (subconscious screen shows all).

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-07 19:02:58 -04:00
Kent Overstreet
f33b1767da Restrict API types visibility — types module is now private
Only Message, Role, MessageContent, ContentPart, ToolCall,
FunctionCall, Usage, ImageUrl are pub-exported from agent::api.

Internal types (ChatRequest, ChatCompletionChunk, ChunkChoice,
Delta, ReasoningConfig, ToolCallDelta, FunctionCallDelta) are
pub(crate) — invisible outside the crate.

All callers updated to import from agent::api:: instead of
agent::api::types::.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-07 13:39:20 -04:00
Kent Overstreet
39dcf27bd0 Memory scores on entries, not a separate Vec
ConversationEntry::Memory gains score: Option<f64>. The scorer
writes scores directly onto entries when results arrive. Removes
Agent.memory_scores Vec and the memory_scores parameter from
context_state_summary().

Scores are serialized to/from the conversation log as memory_score.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-07 03:14:24 -04:00
Kent Overstreet
04e260c081 Kill publish_context_state() — screens lock the agent directly
F1 and F2 screens now call agent.context_state_summary() directly
via try_lock/lock instead of reading from a shared RwLock cache.
Removes SharedContextState, publish_context_state(), and
publish_context_state_with_scores().

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-07 03:03:24 -04:00
Kent Overstreet
3788695634 Run subconscious collect/trigger on every event loop iteration
Previously only fired after conscious turn completion. Now runs on
every wake — DMN timer, user input, background events. Subconscious
agents get checked regardless of what woke the loop.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-07 02:37:11 -04:00
Kent Overstreet
6191f30aec Move Subconscious + SubconsciousAgent into dmn.rs
Subconscious owns agents and shared walked state. trigger() and
collect_results() take the conscious agent Arc as a parameter.
Mind holds Subconscious behind a tokio Mutex and calls into it
from the event loop.

Drops ~170 lines from mind/mod.rs.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-07 02:31:52 -04:00
Kent Overstreet
b7ff205841 Split surface-observe into separate agents, add thalamus
- subconscious-surface: memory search + surfacing (single step)
- subconscious-observe: graph maintenance + recording (3 steps)
- subconscious-thalamus: watches conversation, nudges when stuck

Thalamus output routed as system-reminder into conscious context.
"ok" responses (nothing to say) are silently dropped.

TODO: thalamus needs inactivity timer + notification triggers,
not just post-turn firing.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-07 02:25:11 -04:00
Kent Overstreet
f3ba7e7097 Shared subconscious state — walked keys are Mind-level, not per-agent
SubconsciousSharedState holds walked keys shared between all
subconscious agents. Enables splitting surface-observe into separate
surface and observe agents that share the same walked state.

Walked is passed to run_forked() at run time instead of living on
AutoAgent. UI shows walked count in the subconscious screen header.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-07 02:13:06 -04:00
Kent Overstreet
ef868cb98f Subconscious screen: detail view with post-fork entries
Track fork point in run_forked(), capture entries added during the
run. Subconscious screen shows these in a detail view (Enter to
drill in, Esc to go back) — only the subconscious agent's own
conversation, not the inherited conscious context.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-07 02:08:48 -04:00
Kent Overstreet
c2a3844d69 In-memory output() tool — no more POC_AGENT_OUTPUT_DIR
AutoAgent intercepts output() tool calls and stores results in an
in-memory HashMap instead of writing to the filesystem. Mind reads
auto.outputs after task completion. Eliminates the env-var-based
output dir which couldn't work with concurrent agents in one process.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-07 02:04:29 -04:00
Kent Overstreet
85aafd206c Subconscious screen: show AutoAgent state
F3 screen now displays SubconsciousSnapshot from Mind's AutoAgents
instead of the old process-based AgentSnapshot. Shows running status
(phase + turn), last run time, and walked key count.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-07 01:59:09 -04:00
Kent Overstreet
94ddf7b189 AutoAgent: persistent across runs, run() vs run_forked()
AutoAgent holds config + walked state. Backend is ephemeral per run:
- run(): standalone, global API client (oneshot CLI)
- run_forked(): forks conscious agent, resolves prompt templates
  with current memory_keys and walked state

Mind creates AutoAgents once at startup, takes them out for spawned
tasks, puts them back on completion (preserving walked state).

Removes {{seen_previous}}, {{input:walked}}, {{memory_ratio}} from
subconscious agent prompts. Walked keys are now a Vec on AutoAgent,
resolved via {{walked}} from in-memory state.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-07 01:57:01 -04:00
Kent Overstreet
ba62e0a767 Resolve seen lists from ContextState, not filesystem
{{seen_current}} and {{seen_previous}} now read Memory entry keys
directly from the conscious agent's ContextState — the single source
of truth for what's been surfaced. No more reading session files
written by the old process-spawning path.

{{input:walked}} still reads from the output dir (inter-run state
written by the surface agent's output() tool).

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-07 01:43:00 -04:00
Kent Overstreet
e2e0371726 Resolve subconscious prompt placeholders in Mind
Lightweight resolver handles {{seen_current}}, {{seen_previous}}, and
{{input:KEY}} using the session_id and output_dir directly instead of
env vars. Runs in trigger_subconscious before creating AutoAgent.

Removes {{memory_ratio}} from surface-observe prompt — redundant with
existing budget mechanisms.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-07 01:41:11 -04:00
Kent Overstreet
2678d64b77 Rename forked agent files to subconscious-* prefix
subconscious-surface-observe, subconscious-journal, subconscious-reflect
are Mind's forked agents. The original surface-observe, journal, reflect
remain for the standalone CLI/hook path.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-07 01:36:28 -04:00
Kent Overstreet
58ff9a4d50 Wire subconscious agents through Mind via AutoAgent
Mind now holds SubconsciousAgent state (surface-observe, journal,
reflect) and triggers them after conscious turns complete. Each
agent forks from the conscious agent's context via AutoAgent,
runs as an async task, and routes output (surfaced memories,
reflections) back into the conscious agent.

Replaces the synchronous AgentCycleState that spawned child
processes and blocked start_turn.

Also adds .agent2 files — simplified prompts for the forked model
that strip {{conversation}} and {{agent-context}} (already in the
forked context).

TODO: resolve remaining placeholders (seen_current, input:walked,
memory_ratio) in the .agent2 prompts.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-07 01:33:07 -04:00
Kent Overstreet
d5e6f55da9 Fix context budgeting and compaction
- Budget now counts exact message tokens matching what assemble_api_messages
  sends, not raw string content. Eliminates undercounting from formatting
  overhead (journal headers, personality separators, working stack).

- Load journal before trimming so trim accounts for journal cost.

- Compact before every turn, not just after turn completion. Prevents
  agent_cycle surfaced memories from pushing context over budget.

- Move agent_cycle orchestration from Agent::turn to Mind::start_turn —
  surfaced memories and reflections now precede the user message.

- Move AgentCycleState from Agent to Mind — it's orchestration, not
  per-agent state. memory_scoring_in_flight and memory_scores stay on
  Agent where they belong.

- Tag DMN entries as ConversationEntry::Dmn — compaction evicts them
  first since they're ephemeral. Compaction also prefers evicting
  memories over conversation when memories exceed 50% of entry tokens.

- Kill /retry slash command.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-06 22:43:55 -04:00
Kent Overstreet
c22b8c3a6f Unify budget and context state — single source of truth
Kill ContextBudget and recompute_budget entirely. Budget percentages,
used token counts, and compaction threshold checks now all derive from
the ContextSection tree built by context_state_summary(). This
eliminates the stale-budget bug where the cached budget diverged from
actual context contents.

Also: remove MindCommand::Turn — user input flows through
shared_mind.input exclusively. Mind::start_turn() atomically moves
text from pending input into the agent's context and spawns the turn.
Kill /retry. Make Agent::turn() take no input parameter.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-06 22:43:55 -04:00
Kent Overstreet
49cd6d6ab6 rendering 2026-04-06 22:43:55 -04:00
ProofOfConcept
36d698a3e1 Remove dead code: append_text, needs_assistant_marker, target param
append_text was the TextDelta streaming handler — replaced by
append_streaming on Agent entries. needs_assistant_marker tracked
turn boundaries for the old message path. target removed from
Agent::turn — routing now determined by entry content.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-05 22:40:38 -04:00
ProofOfConcept
f390fa1617 Delete ui_channel.rs — relocate types, remove all UiMessage/UiSender plumbing
Types relocated:
- StreamTarget → mind/mod.rs (Mind decides Conversation vs Autonomous)
- SharedActiveTools + shared_active_tools() → agent/tools/mod.rs
- ContextSection + SharedContextState → agent/context.rs (already there)
- StatusInfo + ContextInfo → user/mod.rs (UI display state)

Removed UiSender from: Agent::turn, Mind, learn.rs, all function signatures.
The entire message-passing layer is gone. All state flows through
Agent fields (activities, entries, streaming) read by the UI via try_lock.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-05 22:34:48 -04:00
ProofOfConcept
cfddb55ed9 Kill TextDelta, Info — UiMessage is dead. RAII ActivityGuards replace all status feedback
Streaming text now goes directly to agent entries via append_streaming().
sync_from_agent diffs the growing entry each tick. The streaming entry
is popped when the response completes; build_response_message pushes
the final version.

All status feedback uses RAII ActivityGuards:
- push_activity() for long-running work (thinking, streaming, scoring)
- notify() for instant feedback (compacted, DMN state changes, commands)
- Guards auto-remove on Drop, appending "(complete)" and lingering 5s
- expire_activities() cleans up timed-out notifications on render tick

UiMessage enum reduced to a single Info variant with zero sends.
The channel infrastructure remains for now (Mind/Agent still take
UiSender in signatures) — mechanical cleanup for a follow-up.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-05 22:18:07 -04:00
ProofOfConcept
eafc2887a3 Kill StatusUpdate, Activity, DmnAnnotation, ContextInfoUpdate, AgentUpdate
Status bar reads directly from Agent and MindState on each render tick.
Activity is now a field on Agent — set by agent code directly, read by
UI via try_lock. DmnAnnotation, ContextInfoUpdate, AgentUpdate were
already dead (no senders).

UiMessage down to 4 variants: TextDelta, Reasoning, Debug, Info.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-05 21:34:27 -04:00
ProofOfConcept
48beb8b663 Revert to tokio::sync::Mutex, fix lock-across-await bugs, move input ownership to InteractScreen
The std::sync::Mutex detour caught every place a MutexGuard lived
across an await point in Agent::turn — the compiler enforced Send
safety that tokio::sync::Mutex silently allows. With those fixed,
switch back to tokio::sync::Mutex (std::sync blocks tokio worker
threads and panics inside the runtime).

Input and command dispatch now live in InteractScreen (chat.rs):
- Enter pushes directly to SharedMindState.input (no app.submitted hop)
- sync_from_agent displays pending input with dimmed color
- Slash command table moved from event_loop.rs to chat.rs
- cmd_switch_model kept as pub fn for tool-initiated switches

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-05 21:13:48 -04:00
Kent Overstreet
3e1be4d353 agent: switch from tokio::sync::Mutex to std::sync::Mutex
The agent lock is never held across await points — turns lock briefly,
do work, drop, then do async API calls. std::sync::Mutex works and
can be locked from sync contexts (screen tick inside terminal.draw).

Fixes: blocking_lock() panic when called inside tokio runtime.

Co-Authored-By: Kent Overstreet <kent.overstreet@linux.dev>
2026-04-05 20:08:25 -04:00
Kent Overstreet
2b9aba0e5d fix shutdown hang and slow startup
Mind::run() breaks when input_rx channel closes (UI shut down).
Previously the DMN timer kept the loop alive forever.

UI renders immediately without blocking on agent lock. Conversation
replay happens lazily on the render tick via try_lock — the UI
shows "consciousness v0.3" instantly, fills in model info and
conversation history once Mind init completes.

Co-Authored-By: Kent Overstreet <kent.overstreet@linux.dev>
2026-04-05 16:18:10 -04:00
Kent Overstreet
120ffabfaa Kill socket, read/write subcommands
Redundant with channels

Signed-off-by: Kent Overstreet <kent.overstreet@linux.dev>
2026-04-05 05:58:51 -04:00
Kent Overstreet
7dc515b985 mind: remove Arc from MindState
MindState is now std::sync::Mutex<MindState> owned by Mind, not
Arc-wrapped. Background scoring completion signals through a
BgEvent channel instead of locking shared directly. Retry sends
a Turn command instead of pushing to shared input.

No Arc on Mind (scoped tasks), no Arc on MindState (owned by Mind).
Only Arc<Mutex<Agent>> remains — needed for background turn spawns.

Co-Authored-By: Kent Overstreet <kent.overstreet@linux.dev>
Signed-off-by: Kent Overstreet <kent.overstreet@linux.dev>
2026-04-05 05:58:51 -04:00
Kent Overstreet
aae9687de2 mind: Mind is fully &self — no &mut needed
Move turn_handle into MindState (behind the mutex). All Mind
methods now take &self. Mind can be shared across tasks without
Arc — it's Send + Sync and immutable from the outside.

Manual Clone impl for MindState skips turn_handle (not needed
for UI diffing).

Co-Authored-By: Kent Overstreet <kent.overstreet@linux.dev>
2026-04-05 05:58:51 -04:00
Kent Overstreet
8e3137fe3f mind: static assert Mind is Send + Sync
Mind is already Send + Sync — all fields use Arc or sync primitives.
Add compile-time assertion so it stays that way.

Co-Authored-By: Kent Overstreet <kent.overstreet@linux.dev>
2026-04-05 05:58:50 -04:00
Kent Overstreet
d5a706147a mind: keep supervisor alive on Mind struct
The channel daemon supervisor was created in init() and immediately
dropped. Keep it on Mind so it can restart crashed daemons.

Co-Authored-By: Kent Overstreet <kent.overstreet@linux.dev>
2026-04-05 04:32:11 -04:00
Kent Overstreet
57b0f94b54 move startup orchestration from mind to event_loop
The top-level run() that creates Mind, wires channels, spawns the
Mind event loop, and starts the UI event loop is orchestration —
it belongs with the UI entry point, not in the cognitive layer.

Renamed to event_loop::start(cli). mind/mod.rs is now purely the
Mind struct: state machine, MindCommand, and the run loop.

Co-Authored-By: Kent Overstreet <kent.overstreet@linux.dev>
2026-04-05 04:29:56 -04:00
Kent Overstreet
e449cda40f mind: absorb agent creation into Mind::new()
Mind::new() takes config + ui_tx + turn_tx, creates the agent,
conversation log, shared state internally. The top-level run()
is now just: load config, create channels, Mind::new, init, spawn,
run event_loop.

Co-Authored-By: Kent Overstreet <kent.overstreet@linux.dev>
2026-04-05 04:20:49 -04:00
Kent Overstreet
91033fe754 mind: clean startup split between Mind and event_loop
Mind::init() now handles channel daemons, observation socket, and
scoring startup. event_loop::run() creates its own idle state and
channel polling — UI concerns owned by the UI.

Startup run() is now: create agent, create Mind, init, spawn Mind,
run event_loop. Seven parameters on event_loop::run() instead of
twelve.

Remove observe_input_rx from event loop (being replaced by channel).

Co-Authored-By: Kent Overstreet <kent.overstreet@linux.dev>
2026-04-05 04:17:04 -04:00
Kent Overstreet
9d597b5eff mind: zero UI dependencies — init() + run() split
Mind::init() restores conversation from log and starts scoring.
No UiMessages sent. The UI event loop reads Mind's state after
init and displays startup info (model, restored conversation)
by reading the agent directly.

mind/mod.rs has zero UiMessage imports or sends. Complete
separation between cognitive state machine and user interface.

Co-Authored-By: Kent Overstreet <kent.overstreet@linux.dev>
2026-04-05 04:02:16 -04:00
Kent Overstreet
3ee1aa69b0 mind: zero UiMessages from Mind's run loop
UserInput display moved to UI diff — when MindState.input goes from
populated to empty (consumed by a turn), the UI displays it. Mind
no longer sends any UiMessage from its event loop.

Remaining UiMessages are only in the startup function (one-time
init info).

Co-Authored-By: Kent Overstreet <kent.overstreet@linux.dev>
2026-04-05 03:54:01 -04:00
Kent Overstreet
84fe757260 mind: remove DMN helper methods, inline field assignments
dmn_sleep/dmn_wake/dmn_pause/cycle_autonomy were just setting two
fields each. Inline the assignments at call sites. cycle_autonomy
moves to event_loop since it's UI state machine logic (deciding
which label to show).

Co-Authored-By: Kent Overstreet <kent.overstreet@linux.dev>
2026-04-05 03:50:21 -04:00
Kent Overstreet
556a56035b mind: inline compaction (not async), remove check_compaction
Compaction is CPU-only on in-memory data — no reason to spawn a
task. Inline it in run_commands as a synchronous agent lock + compact.

Co-Authored-By: Kent Overstreet <kent.overstreet@linux.dev>
2026-04-05 03:43:53 -04:00
Kent Overstreet
2d6a17e773 mind: move CycleAutonomy to event_loop, simplify MindCommand
CycleAutonomy just flips DMN state — handled directly in event_loop
by locking shared MindState. MindCommand::Hotkey replaced with
MindCommand::Interrupt — the only command that needs Mind's async
context (abort handles, kill processes).

Remove dmn_interval() wrapper — inline self.dmn.interval().

MindCommand is now: Turn, Compact, Score, Interrupt, NewSession, None.

Co-Authored-By: Kent Overstreet <kent.overstreet@linux.dev>
2026-04-05 03:41:47 -04:00
Kent Overstreet
402bae4178 mind: restore age_out_images and publish_context_state after turns
These were called from handle_turn_result before the refactor but
got lost during the MindState migration. Re-add them in the turn
completion path. Delete the trivial refresh_context_state wrapper.

Co-Authored-By: Kent Overstreet <kent.overstreet@linux.dev>
2026-04-05 03:38:42 -04:00
Kent Overstreet
01b07a7f28 mind: unify MindCommand, add command queue pattern
MindCommand replaces both Action and MindMessage — one type for
everything: turns, compaction, scoring, hotkeys, new session.

State methods return MindCommand values. The run loop collects
commands into a Vec, then drains them through run_commands().
Compact and Score now flow through the same command path as
everything else.

Removes execute(), MindMessage from event_loop. Mind's run loop
is now: select! → collect commands → run_commands().

mind/mod.rs: 957 → 516 lines.

Co-Authored-By: Kent Overstreet <kent.overstreet@linux.dev>
2026-04-05 03:34:43 -04:00
Kent Overstreet
07ca136c14 mind: double-buffer MindState for UI diffing
UI event loop clones MindState on each render tick, diffs against
the previous copy, and generates status updates from changes. Mind
no longer sends UiMessage::StatusUpdate — state changes are detected
automatically by the UI.

Removes update_status from both Mind and event_loop. DMN state
changes, turn tracking, scoring status all flow through the diff.

Zero UiMessage sends from Mind's run loop for state changes.

Co-Authored-By: Kent Overstreet <kent.overstreet@linux.dev>
2026-04-05 03:24:08 -04:00
Kent Overstreet
54cd3783eb mind: move send_context_info and update_status to event_loop
Both are pure UI operations that read config/shared state and format
display messages. No Mind state mutation involved.

Co-Authored-By: Kent Overstreet <kent.overstreet@linux.dev>
2026-04-05 03:12:50 -04:00
Kent Overstreet
4eb0c891c4 mind: move DMN commands to event_loop, remove MindMessage variants
/dmn, /sleep, /wake, /pause now lock MindState directly from the
UI event loop. No MindMessage roundtrip needed — they're just
state transitions + info display.

MindMessage reduced to: Hotkey (Interrupt, CycleAutonomy),
NewSession, Score. Everything else handled directly by UI.

Co-Authored-By: Kent Overstreet <kent.overstreet@linux.dev>
2026-04-05 03:08:36 -04:00
Kent Overstreet
7adc333219 mind: move state to MindState, Mind becomes thin event loop
MindState (behind Arc<Mutex<>>) holds all cognitive state: DMN,
turn tracking, pending input, scoring, error counters. Pure state
transition methods (take_pending_input, complete_turn, dmn_tick)
return Action values instead of directly spawning turns.

Mind is now just the event loop: lock MindState, call state methods,
execute returned actions (spawn turns, send UiMessages). No state
of its own except agent handle, turn handle, and watch channel.

mind/mod.rs: 957 → 586 lines.

Co-Authored-By: Kent Overstreet <kent.overstreet@linux.dev>
2026-04-05 03:05:28 -04:00
Kent Overstreet
792e9440af mind: shared MindState for pending input
Add MindState behind Arc<Mutex<>> for state shared between Mind
and UI. Pending user input goes through shared state instead of
MindMessage::UserInput — UI pushes, Mind consumes.

Mind checks for pending input after every event (message received,
turn completed, DMN tick). User input is prioritized over DMN ticks.

This enables the UI to display/edit/cancel queued messages, and
removes the last MindMessage variant that carried data.

Co-Authored-By: Kent Overstreet <kent.overstreet@linux.dev>
2026-04-05 02:52:56 -04:00
Kent Overstreet
05d6bbc912 move hotkey handlers from Mind to event_loop
cycle_reasoning, kill_processes, and AdjustSampling only need the
Agent lock — they're pure Agent operations. Handle them directly
in the UI event loop instead of routing through Mind.

Mind now only receives Interrupt and CycleAutonomy as hotkeys,
which genuinely need Mind state (turn handles, DMN state).

mind/mod.rs: 957 → 688 lines across the session.

Co-Authored-By: Kent Overstreet <kent.overstreet@linux.dev>
2026-04-05 02:44:58 -04:00
Kent Overstreet
64add58caa mind: move all slash commands to event_loop dispatch
All slash command routing now lives in user/event_loop.rs. Mind
receives typed messages (NewSession, Score, DmnSleep, etc.) and
handles them as named methods. No more handle_command() dispatch
table or Command enum.

Commands that only need Agent state (/model, /retry) run directly
in the UI task. Commands that need Mind state (/new, /score, /dmn,
/sleep, /wake, /pause) send a MindMessage.

Mind is now purely: turn lifecycle, DMN state machine, and the
named handlers for each message type.

Co-Authored-By: Kent Overstreet <kent.overstreet@linux.dev>
2026-04-05 02:40:45 -04:00
Kent Overstreet
b05c956ab8 mind: add turn_watch, move /retry to event_loop
Add tokio::sync::watch for turn_in_progress state. Commands in the
UI event loop can wait for turns to complete via wait_for() instead
of checking-and-bailing.

Move /retry to event_loop: waits for turn completion, pops agent
history, sends retried text as MindMessage::UserInput. Mind doesn't
need to know about retry — it just sees a new input message.

Make agent field pub on Mind for UI access.

Co-Authored-By: Kent Overstreet <kent.overstreet@linux.dev>
2026-04-05 02:37:51 -04:00
Kent Overstreet
178824fa01 move UI commands from Mind to event_loop
/quit, /help, /save handled directly in the UI event loop.
/model and /model <name> moved to event_loop as cmd_switch_model().
Mind no longer needs tui::App for any command handling.

Mind's handle_command now only has commands that genuinely need
Mind state: /new, /retry, /score (turn_in_progress, DMN, scoring).

Co-Authored-By: Kent Overstreet <kent.overstreet@linux.dev>
2026-04-05 02:29:44 -04:00