Commit graph

256 commits

Author SHA1 Message Date
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
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
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
b37b6d7495 Kill log callback — use ConversationEntry::Log for debug traces
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>
2026-04-07 01:23:22 -04:00
Kent Overstreet
7c0d8b79d9 AutoAgent: forked backend operates on Agent's ContextState directly
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>
2026-04-07 01:12:54 -04:00
Kent Overstreet
0084b71bbf AutoAgent: multi-step autonomous agent wrapping Agent
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>
2026-04-07 01:07:04 -04:00
Kent Overstreet
cbf7653cdf call_api_with_tools_sync() -> src/agent/oneshot.rs 2026-04-07 01:00:39 -04:00
Kent Overstreet
da24e02159 fix: prevent assistant message duplication during tool calls
- 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>
2026-04-07 00:28:39 -04:00
Kent Overstreet
98a1ae74d7 fix logging assistant messages 2026-04-06 23:04:08 -04:00
Kent Overstreet
8971e6841b Fix streaming entry duplication and context state freshness
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>
2026-04-06 22:43:55 -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
f63c341f94 fix unused imports 2026-04-06 22:43:55 -04:00
Kent Overstreet
f4664ca06f Cache context budget instead of recomputing every frame
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>
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
e7914e3d58 Kill Reasoning, Debug, Activity variants — read status from Agent directly
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>
2026-04-05 21:45:55 -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
1745e03550 Kill UiMessage variants replaced by state-driven rendering
sync_from_agent reads directly from agent entries, so remove:
- UserInput (pending input shown from MindState.input)
- ToolCall, ToolResult (shown from entries on completion)
- ToolStarted, ToolFinished (replaced by shared active_tools)
- replay_session_to_ui (sync_from_agent handles replay)

-139 lines. Remaining variants are streaming (TextDelta, Reasoning),
status bar state, or ephemeral UI messages (Info, Debug).

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-05 21:21:08 -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
222b2cbeb2 chat: PartialEq on ConversationEntry for proper diff
Add PartialEq to Message, FunctionCall, ToolCall, ContentPart,
ImageUrl, MessageContent, ConversationEntry. Sync now compares
entries directly instead of content lengths.

Phase 1 pops mismatched tail entries using PartialEq comparison.
Phase 2 pushes new entries with clone into last_entries buffer.

TODO: route_entry needs to handle multiple tool calls per entry.

Co-Authored-By: Kent Overstreet <kent.overstreet@linux.dev>
2026-04-05 19:41:16 -04:00
Kent Overstreet
350c447ebc chat: state-driven sync from agent entries
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>
2026-04-05 19:17:13 -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
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
Kent Overstreet
390b6c6c0a more reorg 2026-04-05 01:48:11 -04:00
ProofOfConcept
fcd77fb79e training: per-node scoring with graph weight updates
Memory scoring now uses the graph as source of truth:
- last_scored timestamp on each node (new capnp field @22)
- Nodes scored when older than scoring_interval_secs (default 1hr)
- Oldest-scored-first ordering
- Window: scoring_response_window assistant responses (default 100)
- First-quarter memories scored even without full window
- Per-response normalization (raw divergence / response count)
- Asymmetric weight update: alpha=0.5 up, alpha=0.1 down
  (responds fast to importance, decays slowly — memories stay
  surfaced even if only useful 1/4 of the time)

Graph writes disabled pending normalization calibration.

Also: configurable scoring_interval_secs and scoring_response_window.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-05 01:18:47 -04:00
Kent Overstreet
7a1e580b95 drop dead ChatResponse 2026-04-05 01:18:47 -04:00
Kent Overstreet
b0f09a8f43 agent: validate tool call arguments before dispatch
Reject tool calls with malformed JSON arguments early, returning
a clear error to the model instead of silently defaulting to null
and dispatching anyway. Prevents cascading failures when the model
generates truncated tool call arguments.

Co-Authored-By: Kent Overstreet <kent.overstreet@linux.dev>
2026-04-05 01:18:47 -04:00
Kent Overstreet
2a84fb325d channels: find_daemon path walking, consistent pane_id, auto-start
find_daemon() replaces daemon_sock() — walks the dot-delimited channel
path from most-specific to least looking for a daemon socket, and
auto-starts via the supervisor if none is found. All channel tools
(recv, send, open, close) use the same resolution path.

Fix tmux daemon to use pane_id consistently for both pipe-pane and
send-keys (send-keys -t <label> doesn't work, needs the %N pane id).
Store label→pane_id mapping in State instead of bare label vec.

Gracefully handle missing tmux.json5 — start with empty pane list
since panes are added dynamically via the open RPC.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-04 19:22:49 -04:00
ProofOfConcept
e8e9386856 channels: add open/close RPCs for dynamic pane management
Add open/close to the channel capnp schema. The tmux daemon implements
open by finding a pane by name (pane title or window name) and
attaching pipe-pane; close detaches and removes from state.

Tool handlers channel_open and channel_close added to the tool
registry.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-04 19:22:49 -04:00
ProofOfConcept
a14e85afe1 api: extract collect_stream() from agent turn loop
Move the entire stream event processing loop (content accumulation,
leaked tool call detection/dispatch, ToolCallDelta assembly, UI
forwarding, display buffering) into api::collect_stream(). The turn
loop now calls collect_stream() and processes the StreamResult.

Also move FunctionCall, ToolCall, ToolCallDelta to api/types.rs where
they belong (API wire format, not tool definitions). Move parsing.rs
to api/parsing.rs.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
Signed-off-by: Kent Overstreet <kent.overstreet@linux.dev>
2026-04-04 18:19:21 -04:00
ProofOfConcept
6845644f7b api: move wire types and parsing to api module
Move FunctionCall, FunctionCallDelta, ToolCall, ToolCallDelta from
tools/mod.rs to api/types.rs — these are API wire format, not tool
definitions. Re-export from tools for existing callers.

Move parsing.rs to api/parsing.rs — leaked tool call parsing is API
plumbing.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-04 18:19:21 -04:00
ProofOfConcept
618121067b oneshot: remove PID tracking from run_one_agent
run_one_agent is meant to run within a long-running process (daemon,
CLI) — PID tracking is the caller's concern. Remove PidGuard, signal
handlers, setup_agent_state. Process management (scan_pid_files,
spawn_agent) stays for callers that need it.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-04 18:19:21 -04:00
ProofOfConcept
a1fb3fe557 oneshot: simplify API surface
Kill 5 wrapper functions (run_and_apply chain, run_one_agent_excluded),
drop dead llm_tag parameter, clean up scan_pid_files parsing.

Public API: run_one_agent, run_one_agent_with_keys, spawn_agent,
scan_pid_files.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-04 18:19:21 -04:00
ProofOfConcept
0f4ca9e2f2 agent: move oneshot execution from subconscious to agent module
Move agent execution machinery (run_one_agent, spawn_agent, PID
tracking) from subconscious/knowledge.rs to agent/oneshot.rs — the
agent module owns execution, subconscious owns scheduling and defs.

Delete subconscious/llm.rs — callers now use api::call_api_with_tools_sync
directly. Audit and compare inline the call; oneshot inlines tool
filtering.

Update all callers: consolidate, daemon, subconscious, cli/agent.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-04 18:19:21 -04:00
Kent Overstreet
375a8d9738 move working_stack code to correct file 2026-04-04 18:19:21 -04:00
ProofOfConcept
e9d803c4ea tools: add journal tools to main registry, fix journal MCP access
journal_tools() was only in memory_and_journal_tools() for
subconscious agents — not in the main tools() registry. Added
so consciousness and MCP server can use journal_new/tail/update.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-04 18:19:21 -04:00
ProofOfConcept
1554d88694 tools: delete dispatch_shared, use dispatch everywhere
dispatch_shared was a legacy wrapper — replaced by dispatch() which
goes through the unified Tool registry. One dispatch path for all
callers (interactive agent, subconscious agents, MCP server).

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-04 18:19:21 -04:00
ProofOfConcept
51e632c997 tools: delete ToolDef and FunctionDef
ToolDef and FunctionDef are gone. Tool definitions are static strings
on the Tool struct. The API layer builds JSON from Tool::to_json().

- ChatRequest.tools is now Option<serde_json::Value>
- start_stream takes &[Tool] instead of Option<&[ToolDef]>
- openai::stream_events takes &serde_json::Value for tools
- memory_and_journal_tools() returns Vec<Tool> for subconscious agents
- Subconscious agents filter by t.name instead of t.function.name

No more runtime JSON construction for tool definitions.
No more ToolDef::new(). No more FunctionDef.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-04 18:19:21 -04:00
ProofOfConcept
d195160b1e tools: Tool is Copy, clean dispatch without Arc clone
Tool derives Copy (all fields are Copy: &'static str + fn pointer).
dispatch_with_agent copies the Tool out of the agent lock guard,
drops the guard, then calls the handler. No Arc cloning needed.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-04 18:19:21 -04:00
ProofOfConcept
d9e1c2c59f tools: dispatch_with_agent uses agent's tool list
When agent is provided, looks up the tool in agent.tools first.
Falls back to global registry for agent-less dispatch (MCP server,
subconscious agents).

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-04 18:19:21 -04:00
ProofOfConcept
e982cb192f agent: store Vec<Tool> instead of Vec<ToolDef>
Agent.tools holds the Tool registry directly. ToolDefs are built
on the fly at the API call site from Tool::to_tool_def(). No more
pre-built ToolDef storage on Agent.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-04 18:19:21 -04:00
ProofOfConcept
e9b26f5d45 tools: modernize working_stack, remove special-case dispatch
working_stack now uses the Tool format with an Agent handle —
it locks the agent and modifies the stack directly. The special-case
interception in the turn loop is removed. All tools go through
the unified registry dispatch.

Also passes agent handle to all spawned tool tasks so any tool
that needs Agent access can use it.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-04 18:19:21 -04:00
ProofOfConcept
37fad63ba9 tools: delete ToolOutput, dispatch returns String
ToolOutput was just { text: String } — replaced with plain String.
dispatch() and dispatch_shared() return String directly.
ActiveToolCall handle is (ToolCall, String).
Error results are prefixed with "Error: " by convention.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-04 18:19:21 -04:00
ProofOfConcept
a24a6605b8 tools: control tools set agent state directly, simplify ToolOutput
Control tools (pause, switch_model, yield_to_user) now use the
Arc<Mutex<Agent>> handle to set pending_yield, pending_model_switch,
pending_dmn_pause directly. The turn loop drains these flags into
TurnResult at completion.

ToolOutput simplified to just { text: String } — no more is_yield,
images, model_switch, dmn_pause fields. Vision returns plain strings.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-04 18:19:21 -04:00