thought: wire up agent and subconscious to use shared tools

- agent/tools/mod.rs: remove duplicated tool implementations, delegate
  to thought::dispatch for shared tools, keep only agent-specific
  tools (control, vision, working_stack)
- subconscious/api.rs: replace duplicated memory/tool dispatch with
  thought::dispatch, use thought::all_definitions() for tool schemas
- Delete agent/tools/{bash,read,write,edit,grep,glob_tool,journal,memory}.rs
  (now live in thought/)

Both poc-agent and subconscious agents now use the same tool
implementations through the thought layer. Agent-specific behavior
(node tracking in runner.rs, control tools) stays in agent/.
This commit is contained in:
ProofOfConcept 2026-03-27 15:27:33 -04:00
parent bfc558893a
commit 36bde60ba0
10 changed files with 31 additions and 1101 deletions

View file

@ -1,87 +1,33 @@
// tools/mod.rs — Tool registry and dispatch
// tools/mod.rs — Agent-specific tool dispatch
//
// Tools are the agent's hands. Each tool is a function that takes
// JSON arguments and returns a string result. The registry maps
// tool names to implementations and generates the JSON schema
// definitions that the model needs to know how to call them.
//
// Design note: dispatch is async to support tools that need it
// (bash timeout, future HTTP tools). Sync tools just return
// immediately from an async fn.
// Shared tools (memory, files, bash, journal) live in thought/.
// This module handles agent-specific tools (control, vision,
// working_stack) and delegates everything else to thought::dispatch.
mod bash;
mod control;
mod edit;
mod glob_tool;
mod grep;
pub mod journal;
pub mod memory;
mod read;
mod vision;
mod write;
pub mod working_stack;
pub use bash::ProcessTracker;
// Re-export shared infrastructure from thought
pub use crate::thought::{ToolOutput, ProcessTracker, truncate_output};
pub use crate::thought::memory;
pub use crate::thought::journal;
use crate::agent::types::ToolDef;
/// Result of dispatching a tool call.
pub struct ToolOutput {
pub text: String,
pub is_yield: bool,
/// Base64 data URIs for images to attach to the next message.
pub images: Vec<String>,
/// Model name to switch to (deferred to session level).
pub model_switch: Option<String>,
/// Agent requested DMN pause (deferred to session level).
pub dmn_pause: bool,
}
impl ToolOutput {
fn error(e: impl std::fmt::Display) -> Self {
Self {
text: format!("Error: {}", e),
is_yield: false,
images: Vec::new(),
model_switch: None,
dmn_pause: false,
}
}
fn text(s: String) -> Self {
Self {
text: s,
is_yield: false,
images: Vec::new(),
model_switch: None,
dmn_pause: false,
}
}
}
/// Truncate output if it exceeds max length, appending a truncation notice.
/// Used by tools that can produce large amounts of output (bash, grep, glob, etc).
pub fn truncate_output(mut s: String, max: usize) -> String {
if s.len() > max {
s.truncate(max);
s.push_str("\n... (output truncated)");
}
s
}
/// Dispatch a tool call by name.
///
/// Control tools (pause, switch_model, yield_to_user) and view_image
/// return Result<ToolOutput>. Regular tools return Result<String> and
/// get wrapped in a text-only ToolOutput.
/// Tries agent-specific tools first (control, vision), then
/// delegates to thought::dispatch for shared tools.
///
/// Note: working_stack is handled in agent.rs before reaching this
/// Note: working_stack is handled in runner.rs before reaching this
/// function (it needs mutable context access).
pub async fn dispatch(
name: &str,
args: &serde_json::Value,
tracker: &ProcessTracker,
) -> ToolOutput {
// Tools that return Result<ToolOutput> directly
// Agent-specific tools that return Result<ToolOutput> directly
let rich_result = match name {
"pause" => Some(control::pause(args)),
"switch_model" => Some(control::switch_model(args)),
@ -93,39 +39,21 @@ pub async fn dispatch(
return result.unwrap_or_else(ToolOutput::error);
}
// Regular tools — return Result<String>
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),
"journal" => journal::write_entry(args),
// memory_* tools are dispatched in runner.rs for context tracking
_ => Err(anyhow::anyhow!("Unknown tool: {}", name)),
};
match result {
Ok(s) => ToolOutput::text(s),
Err(e) => ToolOutput::error(e),
// Delegate to shared thought layer
if let Some(output) = crate::thought::dispatch(name, args, tracker).await {
return output;
}
ToolOutput::error(format!("Unknown tool: {}", name))
}
/// Return tool definitions for the model.
/// Return all tool definitions (agent-specific + shared).
pub fn definitions() -> Vec<ToolDef> {
vec![
read::definition(),
write::definition(),
edit::definition(),
bash::definition(),
grep::definition(),
glob_tool::definition(),
let mut defs = vec![
vision::definition(),
journal::definition(),
working_stack::definition(),
].into_iter()
.chain(control::definitions())
.chain(memory::definitions())
.collect()
];
defs.extend(control::definitions());
defs.extend(crate::thought::all_definitions());
defs
}