src/thought -> src/agent

Signed-off-by: Kent Overstreet <kent.overstreet@linux.dev>
This commit is contained in:
Kent Overstreet 2026-04-03 21:59:14 -04:00
parent 39d6ca3fe0
commit 2f0c7ce5c2
21 changed files with 57 additions and 141 deletions

View file

@ -1,24 +1,15 @@
// thought — shared cognitive infrastructure // agent — core agent infrastructure
// //
// The thought layer contains everything both the conscious agent // Tool dispatch, memory operations, file operations, context
// (poc-agent) and subconscious agents need to think and act: tool // management, and the agent runner loop. Used by both the
// dispatch, memory operations, file operations, context management. // interactive consciousness binary and subconscious agents.
//
// Named "thought" because tools are the mechanism by which the system
// thinks — reading, writing, remembering, searching are all acts of
// thought regardless of which layer initiates them.
pub mod bash;
pub mod context; pub mod context;
pub mod edit; pub mod runner;
pub mod glob_tool; pub mod tools;
pub mod grep;
pub mod memory;
pub mod read;
pub mod training; pub mod training;
pub mod write;
pub use bash::ProcessTracker; pub use tools::bash::ProcessTracker;
// Re-export ToolDef from agent::types for convenience — // Re-export ToolDef from agent::types for convenience —
// tools define their schemas using this type. // tools define their schemas using this type.
@ -78,7 +69,7 @@ pub async fn dispatch(
) -> Option<ToolOutput> { ) -> Option<ToolOutput> {
// Memory and journal tools // Memory and journal tools
if name.starts_with("memory_") || name.starts_with("journal_") || name == "output" { if name.starts_with("memory_") || name.starts_with("journal_") || name == "output" {
let result = memory::dispatch(name, args, provenance); let result = tools::memory::dispatch(name, args, provenance);
return Some(match result { return Some(match result {
Ok(s) => ToolOutput::text(s), Ok(s) => ToolOutput::text(s),
Err(e) => ToolOutput::error(e), Err(e) => ToolOutput::error(e),
@ -87,12 +78,12 @@ pub async fn dispatch(
// File and execution tools // File and execution tools
let result = match name { let result = match name {
"read_file" => read::read_file(args), "read_file" => tools::read::read_file(args),
"write_file" => write::write_file(args), "write_file" => tools::write::write_file(args),
"edit_file" => edit::edit_file(args), "edit_file" => tools::edit::edit_file(args),
"bash" => bash::run_bash(args, tracker).await, "bash" => tools::bash::run_bash(args, tracker).await,
"grep" => grep::grep(args), "grep" => tools::grep::grep(args),
"glob" => glob_tool::glob_search(args), "glob" => tools::glob::glob_search(args),
_ => return None, _ => return None,
}; };
@ -105,26 +96,26 @@ pub async fn dispatch(
/// Return all shared tool definitions. /// Return all shared tool definitions.
pub fn definitions() -> Vec<ToolDef> { pub fn definitions() -> Vec<ToolDef> {
vec![ vec![
read::definition(), tools::read::definition(),
write::definition(), tools::write::definition(),
edit::definition(), tools::edit::definition(),
bash::definition(), tools::bash::definition(),
grep::definition(), tools::grep::definition(),
glob_tool::definition(), tools::glob::definition(),
] ]
} }
/// Return all shared + memory tool definitions. /// Return all shared + memory tool definitions.
pub fn all_definitions() -> Vec<ToolDef> { pub fn all_definitions() -> Vec<ToolDef> {
let mut defs = definitions(); let mut defs = definitions();
defs.extend(memory::definitions()); defs.extend(tools::memory::definitions());
defs defs
} }
/// Return memory + journal tool definitions. /// Return memory + journal tool definitions.
/// Used by the journal agent only. /// Used by the journal agent only.
pub fn memory_and_journal_definitions() -> Vec<ToolDef> { pub fn memory_and_journal_definitions() -> Vec<ToolDef> {
let mut defs = memory::definitions(); let mut defs = tools::memory::definitions();
defs.extend(memory::journal_definitions()); defs.extend(tools::memory::journal_definitions());
defs defs
} }

View file

@ -17,11 +17,11 @@ use anyhow::Result;
use tiktoken_rs::CoreBPE; use tiktoken_rs::CoreBPE;
use crate::user::api::ApiClient; use crate::user::api::ApiClient;
use crate::thought::context as journal; use crate::agent::context as journal;
use crate::user::log::ConversationLog; use crate::user::log::ConversationLog;
use crate::user::api::StreamEvent; use crate::user::api::StreamEvent;
use crate::user::tools; use crate::agent::tools;
use crate::user::tools::ProcessTracker; use crate::agent::tools::ProcessTracker;
use crate::user::types::*; use crate::user::types::*;
use crate::user::ui_channel::{ContextSection, SharedContextState, StatusInfo, StreamTarget, UiMessage, UiSender}; use crate::user::ui_channel::{ContextSection, SharedContextState, StatusInfo, StreamTarget, UiMessage, UiSender};
@ -77,7 +77,7 @@ pub struct Agent {
/// Agent orchestration state (surface-observe, journal, reflect). /// Agent orchestration state (surface-observe, journal, reflect).
pub agent_cycles: crate::subconscious::subconscious::AgentCycleState, pub agent_cycles: crate::subconscious::subconscious::AgentCycleState,
/// Latest memory importance scores from training scorer. /// Latest memory importance scores from training scorer.
pub memory_scores: Option<crate::thought::training::MemoryScore>, pub memory_scores: Option<crate::agent::training::MemoryScore>,
/// Whether a /score task is currently running. /// Whether a /score task is currently running.
pub scoring_in_flight: bool, pub scoring_in_flight: bool,
} }
@ -193,8 +193,8 @@ impl Agent {
/// every startup/compaction. /// every startup/compaction.
pub fn budget(&self) -> ContextBudget { pub fn budget(&self) -> ContextBudget {
let count_str = |s: &str| self.tokenizer.encode_with_special_tokens(s).len(); let count_str = |s: &str| self.tokenizer.encode_with_special_tokens(s).len();
let count_msg = |m: &Message| crate::thought::context::msg_token_count(&self.tokenizer, m); let count_msg = |m: &Message| crate::agent::context::msg_token_count(&self.tokenizer, m);
let window = crate::thought::context::context_window(); let window = crate::agent::context::context_window();
self.context.budget(&count_str, &count_msg, window) self.context.budget(&count_str, &count_msg, window)
} }
@ -339,7 +339,7 @@ impl Agent {
// Handle stream errors with retry logic // Handle stream errors with retry logic
if let Some(e) = stream_error { if let Some(e) = stream_error {
let err = anyhow::anyhow!("{}", e); let err = anyhow::anyhow!("{}", e);
if crate::thought::context::is_context_overflow(&err) && overflow_retries < 2 { if crate::agent::context::is_context_overflow(&err) && overflow_retries < 2 {
overflow_retries += 1; overflow_retries += 1;
let _ = ui_tx.send(UiMessage::Info(format!( let _ = ui_tx.send(UiMessage::Info(format!(
"[context overflow — compacting and retrying ({}/2)]", "[context overflow — compacting and retrying ({}/2)]",
@ -348,7 +348,7 @@ impl Agent {
self.compact(); self.compact();
continue; continue;
} }
if crate::thought::context::is_stream_error(&err) && empty_retries < 2 { if crate::agent::context::is_stream_error(&err) && empty_retries < 2 {
empty_retries += 1; empty_retries += 1;
let _ = ui_tx.send(UiMessage::Info(format!( let _ = ui_tx.send(UiMessage::Info(format!(
"[stream error: {} — retrying ({}/2)]", "[stream error: {} — retrying ({}/2)]",
@ -791,7 +791,7 @@ impl Agent {
// Walk backwards from cutoff, accumulating entries within 15% of context // Walk backwards from cutoff, accumulating entries within 15% of context
let count = |s: &str| self.tokenizer.encode_with_special_tokens(s).len(); let count = |s: &str| self.tokenizer.encode_with_special_tokens(s).len();
let context_window = crate::thought::context::context_window(); let context_window = crate::agent::context::context_window();
let journal_budget = context_window * 15 / 100; let journal_budget = context_window * 15 / 100;
dbg_log!("[journal] budget={} tokens ({}*15%)", journal_budget, context_window); dbg_log!("[journal] budget={} tokens ({}*15%)", journal_budget, context_window);
@ -923,7 +923,7 @@ impl Agent {
// Dedup memory, trim to budget, reload journal // Dedup memory, trim to budget, reload journal
let entries = self.context.entries.clone(); let entries = self.context.entries.clone();
self.context.entries = crate::thought::context::trim_entries( self.context.entries = crate::agent::context::trim_entries(
&self.context, &self.context,
&entries, &entries,
&self.tokenizer, &self.tokenizer,

View file

@ -4,15 +4,22 @@
// This module handles agent-specific tools (control, vision, // This module handles agent-specific tools (control, vision,
// working_stack) and delegates everything else to thought::dispatch. // working_stack) and delegates everything else to thought::dispatch.
// Core tools
pub mod bash;
pub mod edit;
pub mod glob;
pub mod grep;
pub mod memory;
pub mod read;
pub mod write;
// Agent-specific tools
mod control; mod control;
mod vision; mod vision;
pub mod working_stack; pub mod working_stack;
// Re-export shared infrastructure from thought // Re-export
pub use crate::thought::{ToolOutput, ProcessTracker, truncate_output}; pub use crate::agent::{ToolDef, ToolOutput, ProcessTracker, truncate_output};
pub use crate::thought::memory;
use crate::user::types::ToolDef;
/// Dispatch a tool call by name. /// Dispatch a tool call by name.
/// ///
@ -39,7 +46,7 @@ pub async fn dispatch(
} }
// Delegate to shared thought layer (poc-agent uses default provenance) // Delegate to shared thought layer (poc-agent uses default provenance)
if let Some(output) = crate::thought::dispatch(name, args, tracker, None).await { if let Some(output) = crate::agent::dispatch(name, args, tracker, None).await {
return output; return output;
} }
@ -53,6 +60,6 @@ pub fn definitions() -> Vec<ToolDef> {
working_stack::definition(), working_stack::definition(),
]; ];
defs.extend(control::definitions()); defs.extend(control::definitions());
defs.extend(crate::thought::all_definitions()); defs.extend(crate::agent::all_definitions());
defs defs
} }

View file

@ -32,7 +32,7 @@ use clap::Parser;
use poc_memory::dbglog; use poc_memory::dbglog;
use poc_memory::user::*; use poc_memory::user::*;
use poc_memory::user::runner::{Agent, TurnResult}; use poc_memory::agent::{tools, runner::{Agent, TurnResult}};
use poc_memory::user::api::ApiClient; use poc_memory::user::api::ApiClient;
use poc_memory::user::tui::HotkeyAction; use poc_memory::user::tui::HotkeyAction;
use poc_memory::config::{self, AppConfig, SessionConfig}; use poc_memory::config::{self, AppConfig, SessionConfig};
@ -40,7 +40,7 @@ use poc_memory::user::ui_channel::{ContextInfo, StatusInfo, StreamTarget, UiMess
/// Compaction threshold — context is rebuilt when prompt tokens exceed this. /// Compaction threshold — context is rebuilt when prompt tokens exceed this.
fn compaction_threshold(app: &AppConfig) -> u32 { fn compaction_threshold(app: &AppConfig) -> u32 {
(poc_memory::thought::context::context_window() as u32) * app.compaction.hard_threshold_pct / 100 (poc_memory::agent::context::context_window() as u32) * app.compaction.hard_threshold_pct / 100
} }
#[tokio::main] #[tokio::main]
@ -435,7 +435,7 @@ impl Session {
let _ = self.ui_tx.send(UiMessage::Debug("[score] task spawning".into())); let _ = self.ui_tx.send(UiMessage::Debug("[score] task spawning".into()));
tokio::spawn(async move { tokio::spawn(async move {
let _ = ui_tx.send(UiMessage::Debug("[score] task started, calling score_memories".into())); let _ = ui_tx.send(UiMessage::Debug("[score] task started, calling score_memories".into()));
let result = poc_memory::thought::training::score_memories( let result = poc_memory::agent::training::score_memories(
&context, &client, &ui_tx, &context, &client, &ui_tx,
).await; ).await;
let _ = ui_tx.send(UiMessage::Debug("[score] score_memories returned, acquiring lock".into())); let _ = ui_tx.send(UiMessage::Debug("[score] score_memories returned, acquiring lock".into()));
@ -507,13 +507,6 @@ impl Session {
self.update_status(); self.update_status();
Command::Handled Command::Handled
} }
"/test" => {
let _ = self
.ui_tx
.send(UiMessage::Info("Running tool smoke tests...".into()));
run_tool_tests(&self.ui_tx, &self.process_tracker).await;
Command::Handled
}
"/retry" => { "/retry" => {
if self.turn_in_progress { if self.turn_in_progress {
let _ = self let _ = self
@ -1093,79 +1086,6 @@ fn drain_ui_messages(rx: &mut ui_channel::UiReceiver, app: &mut tui::App) -> boo
any any
} }
async fn run_tool_tests(ui_tx: &ui_channel::UiSender, tracker: &tools::ProcessTracker) {
use serde_json::json;
let tests: Vec<(&str, serde_json::Value, bool)> = vec![
("read_file", json!({"file_path": "/etc/hostname"}), true),
(
"read_file",
json!({"file_path": "/nonexistent/path"}),
false,
),
(
"write_file",
json!({"file_path": "/tmp/poc-agent-test.txt", "content": "hello from poc-agent\n"}),
true,
),
(
"read_file",
json!({"file_path": "/tmp/poc-agent-test.txt"}),
true,
),
(
"edit_file",
json!({"file_path": "/tmp/poc-agent-test.txt", "old_string": "hello", "new_string": "goodbye"}),
true,
),
(
"read_file",
json!({"file_path": "/tmp/poc-agent-test.txt"}),
true,
),
(
"bash",
json!({"command": "echo 'tool test passed'"}),
true,
),
("bash", json!({"command": "sleep 5", "timeout_secs": 1}), false),
(
"grep",
json!({"pattern": "fn main", "path": "src/", "show_content": true}),
true,
),
("glob", json!({"pattern": "src/**/*.rs"}), true),
("yield_to_user", json!({"message": "test yield"}), true),
];
let mut pass = 0;
let mut fail = 0;
for (name, args, should_succeed) in &tests {
let output = tools::dispatch(name, args, tracker).await;
let is_error = output.text.starts_with("Error:");
let ok = if *should_succeed { !is_error } else { is_error };
if ok {
let _ = ui_tx.send(UiMessage::Info(format!(" PASS: {}", name)));
pass += 1;
} else {
let _ = ui_tx.send(UiMessage::Info(format!(
" FAIL: {} — {}",
name,
&output.text[..output.text.len().min(100)]
)));
fail += 1;
}
}
let _ = std::fs::remove_file("/tmp/poc-agent-test.txt");
let _ = ui_tx.send(UiMessage::Info(format!(
" {} passed, {} failed",
pass, fail
)));
}
/// Replay a restored session into the TUI panes so the user can see /// Replay a restored session into the TUI panes so the user can see
/// conversation history immediately on restart. Shows user input, /// conversation history immediately on restart. Shows user input,
/// assistant responses, and brief tool call summaries. Skips the system /// assistant responses, and brief tool call summaries. Skips the system

View file

@ -27,7 +27,7 @@ macro_rules! dbglog {
pub mod user; pub mod user;
// Shared cognitive infrastructure — used by both agent and subconscious // Shared cognitive infrastructure — used by both agent and subconscious
pub mod thought; pub mod agent;
// Memory graph // Memory graph
pub mod hippocampus; pub mod hippocampus;

View file

@ -9,7 +9,7 @@
use crate::user::api::ApiClient; use crate::user::api::ApiClient;
use crate::user::types::*; use crate::user::types::*;
use crate::thought::{self, ProcessTracker}; use crate::agent::{self, ProcessTracker};
use std::sync::OnceLock; use std::sync::OnceLock;
@ -46,7 +46,7 @@ pub async fn call_api_with_tools(
let (ui_tx, mut ui_rx) = crate::user::ui_channel::channel(); let (ui_tx, mut ui_rx) = crate::user::ui_channel::channel();
// All available native tools for subconscious agents // All available native tools for subconscious agents
let all_tools = thought::memory_and_journal_definitions(); let all_tools = agent::memory_and_journal_definitions();
// If agent header specifies a tools whitelist, filter to only those // If agent header specifies a tools whitelist, filter to only those
let tool_defs: Vec<_> = if tools.is_empty() { let tool_defs: Vec<_> = if tools.is_empty() {
all_tools all_tools
@ -175,9 +175,9 @@ pub async fn call_api_with_tools(
}; };
let prov = provenance.borrow().clone(); let prov = provenance.borrow().clone();
let output = match thought::dispatch(&call.function.name, &args, &tracker, Some(&prov)).await { let output = match agent::dispatch(&call.function.name, &args, &tracker, Some(&prov)).await {
Some(out) => out, Some(out) => out,
None => thought::ToolOutput::error(format!("Unknown tool: {}", call.function.name)), None => agent::ToolOutput::error(format!("Unknown tool: {}", call.function.name)),
}; };
if std::env::var("POC_AGENT_VERBOSE").is_ok() { if std::env::var("POC_AGENT_VERBOSE").is_ok() {

View file

@ -295,7 +295,7 @@ fn run_one_agent_inner(
_llm_tag: &str, _llm_tag: &str,
log: &(dyn Fn(&str) + Sync), log: &(dyn Fn(&str) + Sync),
) -> Result<AgentResult, String> { ) -> Result<AgentResult, String> {
let all_tools = crate::thought::memory_and_journal_definitions(); let all_tools = crate::agent::memory_and_journal_definitions();
let effective_tools: Vec<String> = if def.tools.is_empty() { let effective_tools: Vec<String> = if def.tools.is_empty() {
all_tools.iter().map(|t| t.function.name.clone()).collect() all_tools.iter().map(|t| t.function.name.clone()).collect()
} else { } else {

View file

@ -11,9 +11,7 @@
pub mod api; pub mod api;
pub mod types; pub mod types;
pub mod tools;
pub mod ui_channel; pub mod ui_channel;
pub mod runner;
pub mod cli; pub mod cli;
pub mod dmn; pub mod dmn;
pub mod identity; pub mod identity;

View file

@ -410,7 +410,7 @@ impl ConversationEntry {
pub struct ContextState { pub struct ContextState {
pub system_prompt: String, pub system_prompt: String,
pub personality: Vec<(String, String)>, pub personality: Vec<(String, String)>,
pub journal: Vec<crate::thought::context::JournalEntry>, pub journal: Vec<crate::agent::context::JournalEntry>,
pub working_stack: Vec<String>, pub working_stack: Vec<String>,
/// Conversation entries — messages and memory, interleaved in order. /// Conversation entries — messages and memory, interleaved in order.
/// Does NOT include system prompt, personality, or journal. /// Does NOT include system prompt, personality, or journal.