Keep name and last_run_entries on SubconsciousAgent directly,
not just on the AutoAgent (which gets replaced with a placeholder
during spawned runs). Snapshot reads stable fields.
Co-Authored-By: Proof of Concept <poc@bcachefs.org>
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>
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>
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>
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>
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>
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>
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>
{{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>
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>
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>
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>
Add Log variant to ConversationEntry that serializes to the
conversation log but is filtered out on read-back and API calls.
AutoAgent writes debug/status info (turns, tokens, tool calls)
through the conversation log instead of a callback parameter.
Removes the log callback from run_one_agent, call_api_with_tools,
and all callers.
Co-Authored-By: Proof of Concept <poc@bcachefs.org>
Instead of snapshotting assemble_api_messages() at construction, the
forked backend pushes step prompts and tool results into the agent's
context.entries and reassembles messages each turn. Standalone backend
(oneshot CLI) keeps the bare message list.
Co-Authored-By: Proof of Concept <poc@bcachefs.org>
Agent::fork() clones context for KV cache sharing with conscious agent.
AutoAgent runs multi-step prompt sequences with tool dispatch — used by
both oneshot CLI agents and (soon) Mind's subconscious agents.
call_api_with_tools() now delegates to AutoAgent internally; existing
callers unchanged.
Co-Authored-By: Proof of Concept <poc@bcachefs.org>
- Fix sync logic to only break at matching assistant messages
- When assistant message changes (streaming → final), properly pop and re-display
- Add debug logging for sync operations (can be removed later)
The bug: when tool calls split an assistant response into multiple entries,
the sync logic was breaking at the assistant even when it didn't match,
causing the old display to remain while new entries were added on top.
The fix: only break at assistant if matches=true, ensuring changed entries
are properly popped before re-adding.
Co-Authored-By: ProofOfConcept <poc@bcachefs.org>
Signed-off-by: Kent Overstreet <kent.overstreet@linux.dev>
Replace pop+push of streaming entries with finalize_streaming() which
finds the unstamped assistant entry and updates it in place. The
streaming entry IS the assistant message — just stamp it when done.
Also: set dirty flag on agent_changed/turn_watch so the TUI actually
redraws when the agent state changes. Publish context state on F2
switch so the debug screen shows current data.
Age out images during compact() so old screenshots don't bloat the
request payload on startup.
Co-Authored-By: Proof of Concept <poc@bcachefs.org>
- 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>
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>
active_screen is now the F-key number (1-based), dispatch is just
screens[active_screen - 1].tick() everywhere. Eliminates the
special-cased interact variable and duplicated if/else branching.
Also adds diff_mind_state() for dirty-flag tracking and gates the
bottom-of-loop render on dirty, avoiding redundant redraws.
Co-Authored-By: Proof of Concept <poc@bcachefs.org>
ratatui's Paragraph with Wrap does full unicode grapheme segmentation
on render — including for scrolled-off content. Cache per-line wrapped
heights on PaneState (recomputed only on width change or new lines),
then slice to only the visible lines before handing to ratatui.
Eliminates O(total_lines) grapheme work per frame, replacing it with
O(viewport_height) — ~30 lines instead of potentially thousands.
Co-Authored-By: Proof of Concept <poc@bcachefs.org>
budget() called tiktoken on every UI tick, which was the main CPU hog
during rapid key input. Move the cached ContextBudget onto ContextState
and recompute only when entries actually change (push_entry, compact,
restore_from_log).
Co-Authored-By: Proof of Concept <poc@bcachefs.org>
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>
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>
Reasoning tokens: dropped for now, will land in context entries later.
Debug sends: converted to dbglog! macro (writes to debug.log).
Activity: now a field on Agent, set directly, read by UI via try_lock.
score_memories_incremental takes agent Arc for activity writes.
UiMessage down to 2 variants: TextDelta, Info.
Co-Authored-By: Proof of Concept <poc@bcachefs.org>
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>
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>
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>
An assistant entry can have text + multiple tool calls. route_entry
now returns Vec<(PaneTarget, String, Marker)> — tool calls go to
tools pane, text goes to conversation, all from the same entry.
Pop phase iterates the vec in reverse to pop correct number of
pane items per entry.
Co-Authored-By: Kent Overstreet <kent.overstreet@linux.dev>
Store content lengths of rendered entries. On each tick:
- Generation changed → full pane reset
- Entries removed → pop from tail
- Last entry content length changed → pop and re-render (streaming)
- New entries → route and push
PaneState gains pop_line() for removing the last rendered entry.
This handles streaming (last entry growing), compaction (generation
bump), and normal appends.
Co-Authored-By: Kent Overstreet <kent.overstreet@linux.dev>
PaneTarget enum + route_entry() function: given a ConversationEntry,
returns which pane it belongs to (or None to skip). The sync loop
becomes: detect desync → pop, then route new entries.
Routing: User→Conversation, Assistant→ConversationAssistant,
tool_calls→Tools, Tool results→ToolResult, Memory/System→None.
Co-Authored-By: Kent Overstreet <kent.overstreet@linux.dev>
Route agent entries to correct panes:
- User messages → conversation (cyan, User marker)
- Assistant text → conversation (Assistant marker)
- Assistant tool_calls → tools pane (yellow)
- Tool results → tools pane (truncated at 20 lines)
- Memory/system-reminder entries → skipped
- System role → skipped
Two phases: detect generation change (reset panes if needed),
then route new entries. PaneState is the rendered view of agent
entries, updated incrementally.
Co-Authored-By: Kent Overstreet <kent.overstreet@linux.dev>
InteractScreen holds agent ref, syncs conversation display from
agent.entries() on each tick via blocking_lock(). Tracks generation
counter and entry count to detect compactions and new entries.
Agent gets a generation counter, incremented on compaction and
non-last-entry mutations (age_out_images).
sync_from_agent() is the single path for pane updates. UiMessage
handle_ui_message still exists but will be removed once sync
handles all entry types (streaming, tool calls, DMN).
Co-Authored-By: Kent Overstreet <kent.overstreet@linux.dev>
Build legend string from actual screen labels instead of hardcoded
constant. Computed once at startup via OnceLock, accessible from
all screen draw methods.
Co-Authored-By: Kent Overstreet <kent.overstreet@linux.dev>
InteractScreen in chat.rs owns conversation/autonomous/tools panes,
textarea, input history, scroll state. App is now just shared state
(status, sampling params, agent_state, channel_status, idle_info).
Event loop holds InteractScreen separately for UiMessage routing.
Overlay screens (F2-F5) in screens vec. F-key switching preserves
state across screen changes.
handle_ui_message moved from App to InteractScreen.
handle_key split: global keys on App, screen keys in tick().
draw dispatch eliminated — each screen draws itself.
Co-Authored-By: Kent Overstreet <kent.overstreet@linux.dev>
Create overlay screens vec (ConsciousScreen, SubconsciousScreen,
UnconsciousScreen, ThalamusScreen). F-keys switch active_screen.
Screen tick() called during render phase with pending key event.
Screen actions (Switch, Hotkey) applied after draw.
Interact (F1) still draws via App::draw_main(). Overlay screens
draw via ScreenView::tick(). State persists across switches.
Co-Authored-By: Kent Overstreet <kent.overstreet@linux.dev>
Convert F2-F5 screens to ScreenView trait with tick() method.
Each screen owns its view state (scroll, selection, expanded).
State persists across screen switches.
- ThalamusScreen: owns sampling_selected, scroll
- ConsciousScreen: owns scroll, selected, expanded
- SubconsciousScreen: owns selected, log_view, scroll
- UnconsciousScreen: owns scroll
Removed from App: Screen enum, debug_scroll, debug_selected,
debug_expanded, agent_selected, agent_log_view, sampling_selected,
set_screen(), per-screen key handling, draw dispatch.
App now only draws the interact (F1) screen. Overlay screens are
drawn by the event loop via ScreenView::tick. F-key routing and
screen instantiation to be wired in event_loop next.
InteractScreen (state-driven, reading from agent entries) is the
next step — will eliminate the input display race condition.
Co-Authored-By: Kent Overstreet <kent.overstreet@linux.dev>
SlashCommand registry with name, help, and handler as inline
closures in commands(). Single source of truth — send_help reads
it, dispatch_command looks up by name. No separate named functions.
run() takes &Mind instead of individual handles. Dispatch reduced
to: quit check, command lookup, or submit as input.
Co-Authored-By: Kent Overstreet <kent.overstreet@linux.dev>