Commit graph

880 commits

Author SHA1 Message Date
Kent Overstreet
e6f4e9ae04 Remove dead AutoAgent.outputs field
Outputs now go directly to Subconscious.state via the tool closure.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-08 20:45:05 -04:00
Kent Overstreet
e106b90a71 Fix collect_results: read outputs from self.state, not auto.outputs
The output tool closure writes directly to Subconscious.state,
so auto.outputs is always empty. collect_results now reads surface,
reflection, and thalamus keys from self.state.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-08 20:43:24 -04:00
Kent Overstreet
dd85a56902 Output tool via Arc<Mutex<Subconscious>> closure — complete
ToolHandler is now Arc<dyn Fn(...)> supporting closures that capture
state. The output tool is created during init_output_tool() as a
closure capturing Arc<Mutex<Subconscious>>, writing directly to
Subconscious.state. No more POC_AGENT_OUTPUT_DIR filesystem hack.

- All tool handlers wrapped in Arc::new()
- Tool is Clone (not Copy) — .copied() → .cloned()
- Subconscious wrapped in Arc<Mutex<>> on Mind
- Dead filesystem-based output() function removed
- memory_tools returns 11 items (output removed from static list)

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-08 20:41:42 -04:00
Kent Overstreet
daba424a46 WIP: Fix Arc::new() wrapping on tool handlers — some import/paren issues remain
Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-08 20:38:42 -04:00
Kent Overstreet
12798eeae2 WIP: Output tool via Arc<Mutex<Subconscious>>, ToolHandler to Arc<dyn Fn>
- ToolHandler changed to Arc<dyn Fn(...)> (supports closures)
- Subconscious wrapped in Arc<Mutex<>> on Mind
- init_output_tool() pushes output tool closure capturing the Arc
- Output removed from static memory_tools()
- Most tool handlers wrapped in Arc::new() but some have paren issues

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-08 20:37:19 -04:00
Kent Overstreet
d167b11283 Revert output tool hacks (AST scanning + silent success)
These were wrong approaches — replacing with proper closure-based
output tool that writes directly to shared Subconscious state.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-08 20:07:20 -04:00
Kent Overstreet
68fbcc351f output() tool: don't error when no output dir (forked agents)
Forked agents don't have POC_AGENT_OUTPUT_DIR set. The output tool
now returns success regardless — forked agents extract output values
from the AST via run_with_backend. Subprocess agents still write
to disk when the dir is set.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-08 19:51:44 -04:00
Kent Overstreet
33ed54396c Fix output() tool for forked agents: extract from AST after tool turns
The old dispatch_tools intercepted output() calls and stored them in
auto.outputs. The new Agent::turn() dispatches normally, so output()
was hitting the filesystem path (which fails without POC_AGENT_OUTPUT_DIR).

Now run_with_backend scans the conversation AST after each tool turn
and extracts output() call arguments into auto.outputs. collect_results
in dmn.rs reads these to surface memories and inject reflections.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-08 19:45:18 -04:00
Kent Overstreet
fba8fcc587 Fix UTF-8 slicing panics: use floor_char_boundary for all truncation
Byte-position truncation (&s[..s.len().min(N)]) panics when position
N lands inside a multi-byte character. Fixed in parser debug logging,
API error messages, oneshot response logging, and CLI agent display.

Also fixed tool dispatch permissions (removed global fallback).

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-08 19:35:01 -04:00
Kent Overstreet
1776222b07 Fix tool permissions: remove global fallback in dispatch_with_agent
When an agent context is present, only dispatch tools in the agent's
tool list. The global fallback was bypassing per-agent tool
restrictions — a subconscious agent could call bash, edit, or any
tool even if its .agent file only allowed memory tools.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-08 19:19:05 -04:00
Kent Overstreet
d451b69196 Fix XML tool call parsing: try JSON parse for parameter values
Parameter values like ["key1", "key2"] were being wrapped as strings
instead of parsed as JSON arrays. Tools expecting array arguments
(like memory_search) got a string containing the array literal.

Now tries serde_json::from_str first, falls back to String.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-08 18:52:10 -04:00
Kent Overstreet
785dea9b9b Update EBNF grammar comment for tool_result format
Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-08 18:43:50 -04:00
Kent Overstreet
8e5747ff43 Fix tool result format: Qwen expects <tool_response> in user role
Qwen's chat template renders tool results as:
  <|im_start|>user\n<tool_response>\n{content}\n</tool_response><|im_end|>

We were rendering as:
  <|im_start|>tool\n{content}<|im_end|>

The model never saw <|im_start|>tool in training, so it ignored our
tool results and looped retrying the same call. Found by comparing
our tokenization against vLLM's /tokenize endpoint with chat messages.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-08 18:42:47 -04:00
Kent Overstreet
8bf6753949 Debug: add context token count to parser log, fix compact() tool defs
compact() was clearing tool definitions from the system section on
startup — now leaves system section untouched (set once by new()).
Added context token count to parser done log for diagnosing the
subconscious agent loop issue.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-08 17:57:10 -04:00
Kent Overstreet
fc75b181cf Fix: compact() was clearing tool definitions from system section
compact() cleared and rebuilt the system section but only pushed the
system prompt — tool definitions were lost. Since new() sets up the
system section correctly (prompt + tools), compact() now only reloads
identity and journal, leaving system untouched.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-08 17:48:10 -04:00
Kent Overstreet
d4d661df5b Parser debug logging to /tmp/poc-{agent_name}.log
Logs full response text when no tool calls detected, tool call
bodies when found. Per-agent log files for debugging subconscious
agent parsing issues.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-08 17:39:55 -04:00
Kent Overstreet
473909db47 Add parser debug logging (POC_DEBUG=1)
Logs full text length, <tool_call> tag count, and tool call details
on stream completion. Helps diagnose parsing issues with subconscious
agents.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-08 17:38:02 -04:00
Kent Overstreet
119dc8c146 Store trimmed text in Content and Thinking nodes
Was checking trim but storing untrimmed. Now stores the trimmed
version — no leading/trailing whitespace in the AST.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-08 17:25:47 -04:00
Kent Overstreet
01bbc39a31 Drop whitespace-only content nodes from parser output
Content between tags (e.g. newlines between </think> and <tool_call>)
was creating empty Content nodes. Now trimmed before creating the node.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-08 17:21:34 -04:00
Kent Overstreet
1b6664ee1c Fix: skip empty CoT nodes, expand AST children in conscious screen, timestamps
Parser skips Thinking nodes that are just whitespace. Conscious screen
now shows assistant children (Content, Thinking, ToolCall) as nested
tree items via recursive node_to_view. Nodes get timestamped in
push_node and on assistant branch creation.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-08 17:18:48 -04:00
Kent Overstreet
5ec2ff95d8 Fix parser: re-encode tokens instead of tracking model IDs through tag splits
The parser can't reliably split model-produced token IDs at tag
boundaries (<think>, <tool_call>) because BPE tokens can span across
tags. Instead, each leaf gets re-encoded from its text content via
the local tokenizer. This gives clean token boundaries aligned with
semantic structure — better for budgeting and potentially for the
model during fine-tuning.

Also skip serializing token_ids to conversation log (they're cached
state, recomputed on construction).

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-08 17:08:42 -04:00
Kent Overstreet
88ac5e10ce Log completed assistant node after parser finishes
The parser mutates the AST directly but doesn't write to the
conversation log. The turn loop now logs the completed assistant
branch after the parser handle resolves successfully.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-08 16:58:35 -04:00
Kent Overstreet
5f5a8a807c Fix chat display: restore incremental sync with change detection
sync_from_agent now detects changed entries by comparing token counts
(cheap proxy for content changes during streaming). Changed entries
get popped and re-pushed. Extracted push_routed/pop_routed helpers.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-08 16:55:18 -04:00
Kent Overstreet
31e813f57d Fix status bar: show per-section budget breakdown
Budget display shows: sys 12% id 5% jnl 8% conv 40% = 15K/24K
Old conversation log entries silently skipped (journal has context).

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-08 16:53:23 -04:00
Kent Overstreet
9c0533966a Batch tool result application: single lock for remove + log + push
apply_tool_results() collects all results, then does one state lock
(remove from active_tools + write to log) and one context lock (push
all nodes). Eliminates redundant per-result locking.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-08 16:48:05 -04:00
Kent Overstreet
31a41fa042 ActiveTools wrapper: replace SharedActiveTools Arc<Mutex<Vec>>
New ActiveTools struct with proper methods: push, remove, abort_all,
take_finished, take_foreground, iter, len. Lives directly on AgentState,
no separate Arc<Mutex> needed.

TUI reads active tools through agent.state.try_lock(). Turn loop uses
helpers instead of manual index iteration.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-08 16:45:56 -04:00
Kent Overstreet
9c9618d034 WIP: ActiveTools wrapper type, removing SharedActiveTools
New ActiveTools struct with proper methods: push, remove,
take_finished, take_foreground, iter, len. Turn loop uses
helpers instead of manual index iteration.

Removing SharedActiveTools (Arc<Mutex<Vec>>) — active tools
live directly in AgentState. A few UI callers still need
updating.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-08 16:41:14 -04:00
Kent Overstreet
14fd8c9b90 Clean up warnings: StreamToken pub, dead oneshot code, SkipIndex
Made StreamToken pub (was pub(crate), needed by context.rs).
Removed dead API_CLIENT, get_client, sampling/priority fields
from oneshot. Suppressed pre-existing SkipIndex warning in learn.rs.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-08 16:35:57 -04:00
Kent Overstreet
2c401e24d6 Parser consumes stream directly, yields tool calls via channel
ResponseParser::run() spawns a task that reads StreamTokens, parses
into the AST (locking context per token), and sends PendingToolCalls
through a channel. Returns (tool_rx, JoinHandle<Result>) — the turn
loop dispatches tool calls and awaits the handle for error checking.

Token IDs from vLLM are accumulated alongside text and stored directly
on AST leaves — no local re-encoding on the response path.

The turn loop no longer matches on individual stream events. It just
reads tool calls and dispatches them.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-08 16:32:00 -04:00
Kent Overstreet
0b9813431a Agent/AgentState split complete — separate context and state locks
Agent is now Arc<Agent> (immutable config). ContextState and AgentState
have separate tokio::sync::Mutex locks. The parser locks only context,
tool dispatch locks only state. No contention between the two.

All callers migrated: mind/, user/, tools/, oneshot, dmn, learn.
28 tests pass, zero errors.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-08 15:47:21 -04:00
Kent Overstreet
1d61b091b0 WIP: Agent/AgentState — 36 errors remaining, all .lock() → .state.lock() or .context.lock()
Bulk replaced Arc<Mutex<Agent>> with Arc<Agent> across all files.
Fixed control.rs, memory.rs tool handlers. Fixed oneshot Backend.
Remaining errors are all agent.lock() → agent.state.lock() or
agent.context.lock() in mind/, user/, and a few in mod.rs.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-08 15:40:36 -04:00
Kent Overstreet
e73135a8d0 WIP: Agent/AgentState split — core methods migrated
turn(), push_node(), assemble_prompt_tokens(), compact(),
restore_from_log(), load_startup_journal(), apply_tool_result()
all use separate context/state locks. ToolHandler signature
updated to Arc<Agent>.

Remaining: tool handlers, control.rs, memory.rs, digest.rs,
and all outer callers (mind, user, learn, oneshot, dmn).

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-08 15:39:03 -04:00
Kent Overstreet
7fe4584ba0 WIP: Agent/AgentState split — struct defined, 80+ errors remaining
Split Agent into immutable Agent (behind Arc) and mutable AgentState
(behind its own Mutex). ContextState has its own Mutex on Agent.
Activities moved to AgentState. new() and fork() rewritten.

All callers need mechanical updates: agent.lock().await.field →
agent.state.lock().await.field or agent.context.lock().await.method.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-08 15:36:08 -04:00
Kent Overstreet
e587431f9a IT BUILDS: Full AST migration compiles — zero errors
All callers migrated from old context types to AstNode/ContextState.
Killed: Message, Role (api), ConversationEntry, ContextEntry,
ContextSection, working_stack, api/parsing.rs, api/types.rs,
api/openai.rs, context_old.rs.

Oneshot standalone path stubbed (needs completions API rewrite).
12 warnings remaining (dead code cleanup).

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-08 15:29:52 -04:00
Kent Overstreet
d0d876e067 WIP: Fix mind/, dmn, UI layer — 35 errors remaining
mind/mod.rs and mind/dmn.rs fully migrated to AST types.
user/context.rs, user/widgets.rs, user/chat.rs partially migrated.
Killed working_stack tool, tokenize_conv_entry, context_old.rs.

Remaining: learn.rs (22), oneshot.rs (5), subconscious.rs (3),
chat.rs (3), widgets.rs (1), context.rs (1).

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-08 15:24:49 -04:00
Kent Overstreet
bf3e2a9b73 WIP: Rename context_new → context, delete old files, fix UI layer
Renamed context_new.rs to context.rs, deleted context_old.rs,
types.rs, openai.rs, parsing.rs. Updated all imports. Rewrote
user/context.rs and user/widgets.rs for new types. Stubbed
working_stack tool. Killed tokenize_conv_entry.

Remaining: mind/mod.rs, mind/dmn.rs, learn.rs, chat.rs,
subconscious.rs, oneshot.rs.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-08 15:20:26 -04:00
Kent Overstreet
22146156d4 Collapse API layer: inline openai.rs, delete types.rs and parsing.rs
API is now two files: mod.rs (430 lines) and http.rs. Contains:
Usage, StreamToken, SamplingParams, ApiClient, stream_completions,
SseReader, send_and_check. Everything else is dead and gone.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-08 15:15:21 -04:00
Kent Overstreet
9bb626f18c Strip api/types.rs to just Usage
Killed Message, Role, ToolCall, FunctionCall, MessageContent,
ContentPart, ImageUrl — all dead. types.rs is now 8 lines.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-08 15:12:28 -04:00
Kent Overstreet
39e6ae350d Kill dead API types: ChatRequest, ChatCompletionChunk, Delta, streaming types
Removed all chat completions wire types that are no longer used:
ChatRequest, ReasoningConfig, ChatCompletionChunk, ChunkChoice,
Delta, FunctionCallDelta, ToolCallDelta, append_content, user_with_images.

Remaining types in api/types.rs are transitional (Message, ToolCall, etc.)
— they'll go away as outer callers migrate to AstNode.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-08 15:08:41 -04:00
Kent Overstreet
1e5cd0dd3f Kill dead API code: stream_events, parsing.rs, build_response_message, log_diagnostics
Deleted: api/parsing.rs entirely (parsing now in context_new.rs),
stream_events (chat completions path), collect_stream, build_response_message,
log_diagnostics, tools_to_json_str, start_stream, chat_completion_stream_temp.

API layer is now just: stream_completion (token IDs in/out), SseReader,
send_and_check, and types. Zero errors in api/.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-08 15:06:33 -04:00
Kent Overstreet
48db4a42cc WIP: Kill chat API path — StreamEvent, collect_stream, build_response_message
Removed start_stream, chat_completion_stream_temp, collect_stream,
StreamResult, build_response_message. All streaming goes through
stream_completion → StreamToken now. ConversationLog rewritten
for AstNode serialization.

Remaining: openai.rs stream_events, mind/, user/, oneshot, learn.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-08 15:01:42 -04:00
Kent Overstreet
a68377907a WIP: Agent core migrated to AST types
agent/mod.rs fully uses AstNode/ContextState/PendingToolCall.
Killed: push_message, push_entry, append_streaming, finalize_streaming,
streaming_index, assemble_api_messages, age_out_images, working_stack,
context_sections, entries. ConversationLog rewritten for AstNode.

Remaining: api dead code (chat path), mind/, user/, oneshot, learn.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-08 14:59:38 -04:00
Kent Overstreet
9c79d7a037 WIP: Wiring context_new into agent — turn loop, StreamToken, dead code removal
Work in progress. New turn loop uses ResponseParser + StreamToken.
Killed StreamEvent, append_streaming, finalize_streaming, streaming_index,
assemble_api_messages, working_stack. Many methods still reference old
types — fixing next.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-08 14:55:10 -04:00
Kent Overstreet
648356ae40 ResponseParser mutates AST directly, returns PendingToolCalls
The parser takes &mut ContextState on feed()/finish() and pushes
completed children (content, thinking, tool calls) directly into
the assistant branch. Only PendingToolCall handles are returned
to the caller for dispatch — the caller no longer manages AST
mutation.

Tests verify by reading back from ContextState after parsing.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-08 14:33:57 -04:00
Kent Overstreet
6139d43942 ResponseParser returns children incrementally, add push_child/PendingToolCall
feed() now returns all completed children (not just tool calls) so the
caller can push them into the AST as they arrive. finish() returns
remaining buffered children. The caller manages the assistant branch.

Added ContextState::push_child() for appending to an existing branch,
PendingToolCall for ephemeral dispatch handles, and len() for section
size queries.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-08 14:26:53 -04:00
Kent Overstreet
9fb9c2b2cb Add serde derives to AST types, enable chrono serde feature
Prep for wiring context_new.rs into the codebase: AstNode, NodeLeaf,
NodeBody, Role all derive Serialize/Deserialize for conversation log
persistence.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-08 14:17:40 -04:00
Kent Overstreet
bb80225942 Recursive render_into/token_ids_into, compose from cached children
render_into(&mut String) and token_ids_into(&mut Vec<u32>) recurse
the tree extending the output in place. Branches emit their wrapping
(im_start/role/im_end) and recurse into children — same structure in
both methods. token_ids() now composes from cached leaf tokens instead
of re-encoding the full rendered string.

Killed the AstEvent/AstIter iterator experiment — explicit recursion
is cleaner for a tree walk that isn't truly flattening.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-08 14:00:42 -04:00
Kent Overstreet
942144949d Add Ast trait for render/token_ids/tokens
Implemented by both AstNode and ContextState, so anything that
needs "give me the prompt" can take impl Ast.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-08 13:39:05 -04:00
Kent Overstreet
f1397b7783 Redesign context AST: typed NodeBody, Role as grammar roles, tests
Role is now just System/User/Assistant — maps 1:1 to the grammar.
Leaf types are NodeBody variants: Content, Thinking, ToolCall,
ToolResult, Memory, Dmn, Log. Each variant renders itself; no Role
needed on leaves. AstNode is Leaf(NodeLeaf) | Branch{role, children}.
ContextState holds four Vec<AstNode> sections directly.

Moved tool call XML parsing from api/parsing.rs into context_new.rs
so all grammar knowledge lives in one place.

Tokenizer encode() now returns empty vec when uninitialized instead
of panicking, so tests work without the tokenizer file.

26 tests: XML parsing, incremental streaming (char-by-char feeds
found and fixed a lookahead bug), rendering for all node types,
tokenizer round-trip verification.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-08 13:35:04 -04:00
Kent Overstreet
6730d136d4 ContextState + private AstNode fields: enforce token_ids invariant
AstNode fields are now private with read-only accessors. All mutation
goes through ContextState methods (push, set_message, set_score, del)
which guarantee token_ids stays in sync with text on every leaf.

Also fix ResponseParser to use AstNode::tool_call() constructor,
widen parsing module visibility to pub(crate).

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-08 12:58:59 -04:00