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
// (poc-agent) and subconscious agents need to think and act: tool
// dispatch, memory operations, file operations, context management.
//
// 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.
// Tool dispatch, memory operations, file operations, context
// management, and the agent runner loop. Used by both the
// interactive consciousness binary and subconscious agents.
pub mod bash;
pub mod context;
pub mod edit;
pub mod glob_tool;
pub mod grep;
pub mod memory;
pub mod read;
pub mod runner;
pub mod tools;
pub mod training;
pub mod write;
pub use bash::ProcessTracker;
pub use tools::bash::ProcessTracker;
// Re-export ToolDef from agent::types for convenience —
// tools define their schemas using this type.
@ -78,7 +69,7 @@ pub async fn dispatch(
) -> Option<ToolOutput> {
// Memory and journal tools
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 {
Ok(s) => ToolOutput::text(s),
Err(e) => ToolOutput::error(e),
@ -87,12 +78,12 @@ pub async fn dispatch(
// File and execution tools
let result = match name {
"read_file" => read::read_file(args),
"write_file" => write::write_file(args),
"edit_file" => edit::edit_file(args),
"bash" => bash::run_bash(args, tracker).await,
"grep" => grep::grep(args),
"glob" => glob_tool::glob_search(args),
"read_file" => tools::read::read_file(args),
"write_file" => tools::write::write_file(args),
"edit_file" => tools::edit::edit_file(args),
"bash" => tools::bash::run_bash(args, tracker).await,
"grep" => tools::grep::grep(args),
"glob" => tools::glob::glob_search(args),
_ => return None,
};
@ -105,26 +96,26 @@ pub async fn dispatch(
/// Return all shared tool definitions.
pub fn definitions() -> Vec<ToolDef> {
vec![
read::definition(),
write::definition(),
edit::definition(),
bash::definition(),
grep::definition(),
glob_tool::definition(),
tools::read::definition(),
tools::write::definition(),
tools::edit::definition(),
tools::bash::definition(),
tools::grep::definition(),
tools::glob::definition(),
]
}
/// Return all shared + memory tool definitions.
pub fn all_definitions() -> Vec<ToolDef> {
let mut defs = definitions();
defs.extend(memory::definitions());
defs.extend(tools::memory::definitions());
defs
}
/// Return memory + journal tool definitions.
/// Used by the journal agent only.
pub fn memory_and_journal_definitions() -> Vec<ToolDef> {
let mut defs = memory::definitions();
defs.extend(memory::journal_definitions());
let mut defs = tools::memory::definitions();
defs.extend(tools::memory::journal_definitions());
defs
}

View file

@ -17,11 +17,11 @@ use anyhow::Result;
use tiktoken_rs::CoreBPE;
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::api::StreamEvent;
use crate::user::tools;
use crate::user::tools::ProcessTracker;
use crate::agent::tools;
use crate::agent::tools::ProcessTracker;
use crate::user::types::*;
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).
pub agent_cycles: crate::subconscious::subconscious::AgentCycleState,
/// 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.
pub scoring_in_flight: bool,
}
@ -193,8 +193,8 @@ impl Agent {
/// every startup/compaction.
pub fn budget(&self) -> ContextBudget {
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 window = crate::thought::context::context_window();
let count_msg = |m: &Message| crate::agent::context::msg_token_count(&self.tokenizer, m);
let window = crate::agent::context::context_window();
self.context.budget(&count_str, &count_msg, window)
}
@ -339,7 +339,7 @@ impl Agent {
// Handle stream errors with retry logic
if let Some(e) = stream_error {
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;
let _ = ui_tx.send(UiMessage::Info(format!(
"[context overflow — compacting and retrying ({}/2)]",
@ -348,7 +348,7 @@ impl Agent {
self.compact();
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;
let _ = ui_tx.send(UiMessage::Info(format!(
"[stream error: {} — retrying ({}/2)]",
@ -791,7 +791,7 @@ impl Agent {
// Walk backwards from cutoff, accumulating entries within 15% of context
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;
dbg_log!("[journal] budget={} tokens ({}*15%)", journal_budget, context_window);
@ -923,7 +923,7 @@ impl Agent {
// Dedup memory, trim to budget, reload journal
let entries = self.context.entries.clone();
self.context.entries = crate::thought::context::trim_entries(
self.context.entries = crate::agent::context::trim_entries(
&self.context,
&entries,
&self.tokenizer,

View file

@ -4,15 +4,22 @@
// This module handles agent-specific tools (control, vision,
// 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 vision;
pub mod working_stack;
// Re-export shared infrastructure from thought
pub use crate::thought::{ToolOutput, ProcessTracker, truncate_output};
pub use crate::thought::memory;
use crate::user::types::ToolDef;
// Re-export
pub use crate::agent::{ToolDef, ToolOutput, ProcessTracker, truncate_output};
/// Dispatch a tool call by name.
///
@ -39,7 +46,7 @@ pub async fn dispatch(
}
// 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;
}
@ -53,6 +60,6 @@ pub fn definitions() -> Vec<ToolDef> {
working_stack::definition(),
];
defs.extend(control::definitions());
defs.extend(crate::thought::all_definitions());
defs.extend(crate::agent::all_definitions());
defs
}

View file

@ -32,7 +32,7 @@ use clap::Parser;
use poc_memory::dbglog;
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::tui::HotkeyAction;
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.
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]
@ -435,7 +435,7 @@ impl Session {
let _ = self.ui_tx.send(UiMessage::Debug("[score] task spawning".into()));
tokio::spawn(async move {
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,
).await;
let _ = ui_tx.send(UiMessage::Debug("[score] score_memories returned, acquiring lock".into()));
@ -507,13 +507,6 @@ impl Session {
self.update_status();
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" => {
if self.turn_in_progress {
let _ = self
@ -1093,79 +1086,6 @@ fn drain_ui_messages(rx: &mut ui_channel::UiReceiver, app: &mut tui::App) -> boo
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
/// conversation history immediately on restart. Shows user input,
/// assistant responses, and brief tool call summaries. Skips the system

View file

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

View file

@ -9,7 +9,7 @@
use crate::user::api::ApiClient;
use crate::user::types::*;
use crate::thought::{self, ProcessTracker};
use crate::agent::{self, ProcessTracker};
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();
// 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
let tool_defs: Vec<_> = if tools.is_empty() {
all_tools
@ -175,9 +175,9 @@ pub async fn call_api_with_tools(
};
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,
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() {

View file

@ -295,7 +295,7 @@ fn run_one_agent_inner(
_llm_tag: &str,
log: &(dyn Fn(&str) + Sync),
) -> 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() {
all_tools.iter().map(|t| t.function.name.clone()).collect()
} else {

View file

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

View file

@ -410,7 +410,7 @@ impl ConversationEntry {
pub struct ContextState {
pub system_prompt: 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>,
/// Conversation entries — messages and memory, interleaved in order.
/// Does NOT include system prompt, personality, or journal.