Commit graph

902 commits

Author SHA1 Message Date
ProofOfConcept
c53c4f9071 Replace push() with explicit push_log() and push_no_log()
No implicit auto-logging. Call sites choose:
- push_log: new conversation entries (user messages, tool results,
  surfaced memories, assistant responses)
- push_no_log: system prompt, identity, journal, restore from log,
  compact reload, tests

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-09 01:10:40 -04:00
ProofOfConcept
6529aba069 Fix UI lag: try_lock on unconscious mutex, don't re-log restored nodes
The unconscious trigger holds the tokio mutex during heavy sync work
(store load, graph build, agent creation), blocking the UI tick which
needs the same lock for snapshots. Fix: try_lock in the UI — skip
the update if the trigger is running.

Also: restore_from_log was re-logging every restored node back to the
log file via push()'s auto-log. Added push_no_log() for restore path.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-09 01:07:55 -04:00
ProofOfConcept
b7e053edc3 Defer graph health computation to first trigger, not startup
Loading 23K nodes + building graph was blocking consciousness startup.
Now computed on first trigger cycle (runs async from mind loop).

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-09 01:05:08 -04:00
ProofOfConcept
0d40f27098 Fix F3 context pane for unconscious agents
read_sections and draw_context now use selected_agent() which maps the
selected index to either a subconscious forked_agent or an unconscious
agent Arc. Context title uses selected_agent_name for both types.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-09 01:03:16 -04:00
ProofOfConcept
dc07c92b28 Unconscious agents: persistent AutoAgent, shared Agent Arc for UI
- AutoAgent stored on UnconsciousAgent, swapped out for runs, restored
  on completion (same pattern as subconscious agents)
- Agent Arc created before spawn and stored on UnconsciousAgent so
  the TUI can lock it to read conversation context live
- run_shared() method on AutoAgent for running with a pre-created Agent
- Default tools: memory_tools (not memory_and_journal_tools)
- trigger/spawn_agent made async for Agent::new()

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-09 01:00:48 -04:00
ProofOfConcept
5b75ad3553 Toggle on immediately spawns the agent if not running
Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-09 00:53:54 -04:00
ProofOfConcept
c73f037265 Spacebar toggle for all agents, persist to config, scan agent directory
- Scan agents directory for all .agent files instead of hardcoded list
- Persist enabled state to ~/.consciousness/agent-enabled.json
- Spacebar on F3 agent list toggles selected agent on/off
- Both subconscious and unconscious agents support toggle
- Disabled agents shown dimmed with "off" indicator
- New agents default to disabled (safe default)

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-09 00:51:10 -04:00
ProofOfConcept
7aba17e5f0 Compute graph health in consciousness, rename F4 to hippocampus
Graph health stats (alpha, gini, cc, episodic ratio, consolidation
plan) now computed directly by the unconscious module on startup and
every 10 minutes, instead of fetching from the poc-memory daemon.

F4 screen renamed to hippocampus, stripped down to just the health
gauges — daemon task list removed (agents now shown on F3).

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-09 00:45:26 -04:00
ProofOfConcept
1df49482fd Add enabled toggle to AutoAgent, simplify unconscious scheduling
- AutoAgent.enabled: universal toggle for any auto agent
- Subconscious: should_trigger checks auto.enabled
- Unconscious: simplified from consolidation-plan-driven budgets to
  simple loop with cooldown. Static agent list, max 2 concurrent.
- TUI: unconscious agents shown in F3 subconscious screen under
  separator, with enabled/running/runs display

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-09 00:41:18 -04:00
ProofOfConcept
ddfdbe6cb1 Move conversation_log from AgentState to ContextState
The log records what goes into context, so it belongs under the context
lock. push() now auto-logs conversation entries, eliminating all the
manual lock-state-for-log, drop, lock-context-for-push dances.

- ContextState: new conversation_log field, Clone impl drops it
  (forked contexts don't log)
- push(): auto-logs Section::Conversation entries
- push_node, apply_tool_results, collect_results: all simplified
- collect_results: batch nodes under single context lock
- Assistant response logged under context lock after parse completes

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-09 00:32:32 -04:00
ProofOfConcept
d82a2ae90d Clean up mind loop: fix double locks, async agent triggers, input peek
- push_node: notify before dropping state lock instead of relocking
- Mind::run: single lock for timeout + turn_active + has_input;
  single lock for turn_handle + complete_turn
- Agent triggers (subconscious/unconscious) spawned as async tasks
  so they don't block the select loop
- has_pending_input() peek for DMN sleep guard — don't sleep when
  there's user input waiting
- unconscious: merge collect_results into trigger, single store load

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-09 00:21:46 -04:00
ProofOfConcept
0314619579 Add mind/unconscious.rs: standalone graph maintenance agents
Unconscious agents (organize, linker, distill, etc.) run independently
of the conversation context. They create fresh Agent instances, select
target nodes via their .agent file queries, and are scheduled by the
consolidation plan which analyzes graph health metrics.

Key differences from subconscious agents:
- No fork — standalone agents with fresh context
- Self-selecting — queries in .agent files pick target nodes
- Budget-driven — consolidation plan allocates runs per type
- Max 2 concurrent, 60s min interval between same-type runs

Wired into Mind event loop alongside subconscious trigger/collect.
TUI display not yet implemented.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-08 23:39:48 -04:00
ProofOfConcept
9704e7a698 Rename mind/dmn.rs to mind/subconscious.rs
The file contains both the DMN state machine and the subconscious agent
orchestration. Renaming to match the conceptual grouping — next step is
adding mind/unconscious.rs for the standalone graph maintenance agents
(organize, linker, etc.) that don't need conversation context.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-08 23:37:01 -04:00
ProofOfConcept
24b211dc35 Feed observe agents their recent writes to prevent duplicate nodes
Observe was creating byte-identical nodes under slightly different names
(e.g. april-8-evening-folded-presence, -presence-2, -folded-state)
because it had no visibility into its own prior writes across runs.

Query recent writes by provenance in trigger(), pass through
run_forked_shared/resolve_prompt as {{recently_written}}, and include
the list in the observe phase prompts so the agent knows what it
already recorded.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-08 23:27:12 -04:00
ProofOfConcept
44a0bc376a Forked agents: stop gracefully on context overflow instead of compacting
Subconscious agents (observe, etc.) fork the conscious agent's context
to share the KV cache prefix. When a multi-step agent fills the context
window, compacting blows the KV cache and evicts the step prompts,
leaving the model with no idea what it was doing.

Fix: forked agents set no_compact=true. On overflow, turn() returns the
error immediately (no compact+retry), and run_with_backend catches it
and returns Ok — the output tool has already written results to
Subconscious.state, so collect_results still picks them up.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-08 23:00:12 -04:00
Kent Overstreet
850008ece7 Implement standalone AutoAgent::run() for poc-hook agents
Creates an Agent from global config (API credentials, system prompt,
identity), overrides tools with the agent's tool set, and runs through
the standard Backend → run_with_backend → Agent::turn() path.

This enables poc-hook spawned agents (surface-observe, journal, etc.)
to work with the completions API instead of the deleted chat API.

Also added Default derive to CliArgs for config loading.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
Signed-off-by: Kent Overstreet <kent.overstreet@linux.dev>
2026-04-08 22:30:46 -04:00
Kent Overstreet
bf1fa62d14 Restore format_budget: window-based %, free%, colon format, .max(1)
Matched the old format_budget behavior: uses context_window as
denominator (not budget), shows free%, uses colon separators,
.max(1) for non-zero sections. Added mem% split.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-08 21:30:57 -04:00
Kent Overstreet
bbffc2213e Restore trim_conversation: dedup memories, evict to budget, snap boundary
Ported the old trim_entries logic to the new AstNode types:
- Phase 1: Dedup Memory nodes by key (keep last), drop DMN entries
- Phase 2: While over budget, evict lowest-scored memory (if memories
  > 50% of conv tokens) or oldest conversation entry
- Phase 3: Snap to User message boundary at start

Called from compact() which runs on startup and on /compact.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-08 21:27:16 -04:00
Kent Overstreet
7237baba11 Split memory vs conversation tokens in status bar budget
Memory nodes in the conversation section are now counted separately:
  sys X% id Y% jnl Z% mem W% conv V% = NK/MK

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-08 21:19:38 -04:00
Kent Overstreet
5c9590ada7 Custom Deserialize for NodeLeaf: recompute tokens on deserialization
token_ids are not serialized (serde skip), so deserialized nodes had
0 tokens. The custom Deserialize impl recomputes tokens from the body
text, restoring the invariant at the reconstruction boundary. No
separate recompute step needed.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-08 21:14:54 -04:00
Kent Overstreet
a09567849f Fix tmux pane injection: don't scan globally for claude panes
poc-daemon was using find_claude_pane() which scans ALL tmux panes
for a 'claude' process, potentially finding unrelated sessions.
Now only uses the pane ID set by poc-hook via the user/response
RPC calls. If no pane is set yet, injection is skipped.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-08 21:02:44 -04:00
Kent Overstreet
4db7eca275 Dedup surfaced memories: skip keys already in conversation context
collect_results now checks existing Memory nodes in the conversation
before surfacing. Prevents the same memory from being pushed every
time the surface agent runs.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-08 20:51:28 -04:00
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