From 359955f83875fd6894f6900a62681829cdf32283 Mon Sep 17 00:00:00 2001 From: Kent Overstreet Date: Mon, 13 Apr 2026 14:55:41 -0400 Subject: [PATCH] defs.rs: async conversion, remove block_in_place Convert resolve(), resolve_placeholders(), run_agent() to async. Use memory_render/memory_query directly with .await instead of block_in_place wrappers. Propagate async to callers: - config.rs: resolve(), load_session(), reload_for_model() - identity.rs: load_memory_files(), assemble_context_message() - oneshot.rs: run_one_agent() - prompts.rs: agent_prompt() Co-Authored-By: Proof of Concept --- opencode_session_id | 1 - src/agent/mod.rs | 2 +- src/agent/oneshot.rs | 8 ++--- src/cli/agent.rs | 4 +-- src/config.rs | 12 ++++---- src/mind/identity.rs | 14 ++++----- src/mind/unconscious.rs | 2 +- src/subconscious/defs.rs | 59 ++++++++++++++----------------------- src/subconscious/prompts.rs | 4 +-- src/user/mod.rs | 2 +- 10 files changed, 44 insertions(+), 64 deletions(-) delete mode 100644 opencode_session_id diff --git a/opencode_session_id b/opencode_session_id deleted file mode 100644 index 75fd64a..0000000 --- a/opencode_session_id +++ /dev/null @@ -1 +0,0 @@ -ses_2864fa54cffe2jLoh5grt8UixA diff --git a/src/agent/mod.rs b/src/agent/mod.rs index acf513c..52a4764 100644 --- a/src/agent/mod.rs +++ b/src/agent/mod.rs @@ -553,7 +553,7 @@ impl Agent { } pub async fn compact(&self) { - match crate::config::reload_for_model(&self.app_config, &self.prompt_file) { + match crate::config::reload_for_model(&self.app_config, &self.prompt_file).await { Ok(personality) => { let mut ctx = self.context.lock().await; // System section (prompt + tools) set by new(), don't touch it diff --git a/src/agent/oneshot.rs b/src/agent/oneshot.rs index 23033fe..e2f984e 100644 --- a/src/agent/oneshot.rs +++ b/src/agent/oneshot.rs @@ -260,7 +260,7 @@ impl AutoAgent { .map_err(|e| format!("config: {}", e))?; let personality = crate::config::reload_for_model( &app, &app.prompts.other, - ).map_err(|e| format!("config: {}", e))?; + ).await.map_err(|e| format!("config: {}", e))?; let agent = Agent::new( client, personality, @@ -381,7 +381,7 @@ pub struct AgentResult { /// Run an agent. If keys are provided, use them directly (bypassing the /// agent's query). Otherwise, run the query to select target nodes. -pub fn run_one_agent( +pub async fn run_one_agent( store: &mut Store, agent_name: &str, count: usize, @@ -406,7 +406,7 @@ pub fn run_one_agent( for step in &def.steps { let (prompt, extra_keys) = defs::resolve_placeholders( &step.prompt, store, keys, count, - ); + ).await; all_keys.extend(extra_keys); resolved_steps.push(prompts::ResolvedStep { prompt, @@ -420,7 +420,7 @@ pub fn run_one_agent( batch } else { let effective_count = def.count.unwrap_or(count); - defs::run_agent(store, &def, effective_count, &Default::default())? + defs::run_agent(store, &def, effective_count, &Default::default()).await? }; // Base memory tools + extras from agent def (matching unconscious.rs pattern) diff --git a/src/cli/agent.rs b/src/cli/agent.rs index 0ec0cf6..8cd7e92 100644 --- a/src/cli/agent.rs +++ b/src/cli/agent.rs @@ -46,7 +46,7 @@ pub async fn cmd_run_agent(agent: &str, count: usize, target: &[String], query: let mut store = store::Store::load()?; if let Err(e) = crate::agent::oneshot::run_one_agent( &mut store, agent, count, Some(&[key.clone()]), - ) { + ).await { println!("[{}] ERROR on {}: {}", agent, key, e); } } @@ -55,7 +55,7 @@ pub async fn cmd_run_agent(agent: &str, count: usize, target: &[String], query: let mut store = store::Store::load()?; crate::agent::oneshot::run_one_agent( &mut store, agent, count, None, - )?; + ).await?; } Ok(()) } diff --git a/src/config.rs b/src/config.rs index e2a59ca..3fb8135 100644 --- a/src/config.rs +++ b/src/config.rs @@ -506,7 +506,7 @@ pub struct ResolvedModel { impl AppConfig { /// Resolve the active backend and assemble prompts into a SessionConfig. - pub fn resolve(&self, cli: &crate::user::CliArgs) -> Result { + pub async fn resolve(&self, cli: &crate::user::CliArgs) -> Result { let cwd = std::env::current_dir().context("Failed to get current directory")?; let (api_base, api_key, model, prompt_file); @@ -536,7 +536,7 @@ impl AppConfig { let context_groups = get().context_groups.clone(); let (context_parts, config_file_count, memory_file_count) = - crate::mind::identity::assemble_context_message(&cwd, &prompt_file, self.memory_project.as_deref(), &context_groups)?; + crate::mind::identity::assemble_context_message(&cwd, &prompt_file, self.memory_project.as_deref(), &context_groups).await?; let session_dir = dirs::home_dir() .unwrap_or_else(|| PathBuf::from(".")) @@ -668,17 +668,17 @@ pub fn load_app(cli: &crate::user::CliArgs) -> Result<(AppConfig, Figment)> { } /// Load the full config: figment → AppConfig → resolve backend → assemble prompts. -pub fn load_session(cli: &crate::user::CliArgs) -> Result<(SessionConfig, Figment)> { +pub async fn load_session(cli: &crate::user::CliArgs) -> Result<(SessionConfig, Figment)> { let (app, figment) = load_app(cli)?; - let config = app.resolve(cli)?; + let config = app.resolve(cli).await?; Ok((config, figment)) } /// Re-assemble context for a specific model's prompt file. -pub fn reload_for_model(app: &AppConfig, prompt_file: &str) -> Result> { +pub async fn reload_for_model(app: &AppConfig, prompt_file: &str) -> Result> { let cwd = std::env::current_dir().context("Failed to get current directory")?; let context_groups = get().context_groups.clone(); - let (context_parts, _, _) = crate::mind::identity::assemble_context_message(&cwd, prompt_file, app.memory_project.as_deref(), &context_groups)?; + let (context_parts, _, _) = crate::mind::identity::assemble_context_message(&cwd, prompt_file, app.memory_project.as_deref(), &context_groups).await?; Ok(context_parts) } diff --git a/src/mind/identity.rs b/src/mind/identity.rs index e18cb4d..69b3959 100644 --- a/src/mind/identity.rs +++ b/src/mind/identity.rs @@ -7,6 +7,7 @@ use anyhow::Result; use std::path::{Path, PathBuf}; +use crate::agent::tools::memory::memory_render; use crate::config::{ContextGroup, ContextSource}; /// Read a file if it exists and is non-empty. @@ -71,7 +72,7 @@ fn find_context_files(cwd: &Path, prompt_file: &str) -> Vec { /// 2. Project dir (if set) /// 3. Global (~/.consciousness/) /// For journal source, loads recent journal entries. -fn load_memory_files(memory_project: Option<&Path>, context_groups: &[ContextGroup]) -> Vec<(String, String)> { +async fn load_memory_files(memory_project: Option<&Path>, context_groups: &[ContextGroup]) -> Vec<(String, String)> { let home = match dirs::home_dir() { Some(h) => h, None => return Vec::new(), @@ -94,12 +95,7 @@ fn load_memory_files(memory_project: Option<&Path>, context_groups: &[ContextGro ContextSource::Store => { // Load from the memory graph store via typed API for key in &group.keys { - let content = tokio::task::block_in_place(|| { - tokio::runtime::Handle::current().block_on( - crate::agent::tools::memory::memory_render(None, key, Some(true)) - ) - }); - if let Ok(c) = content { + if let Ok(c) = memory_render(None, key, Some(true)).await { if !c.trim().is_empty() { memories.push((key.clone(), c)); } @@ -141,7 +137,7 @@ fn load_memory_files(memory_project: Option<&Path>, context_groups: &[ContextGro } /// Context message: instruction files + memory files + manifest. -pub fn assemble_context_message(cwd: &Path, prompt_file: &str, memory_project: Option<&Path>, context_groups: &[ContextGroup]) -> Result<(Vec<(String, String)>, usize, usize)> { +pub async fn assemble_context_message(cwd: &Path, prompt_file: &str, memory_project: Option<&Path>, context_groups: &[ContextGroup]) -> Result<(Vec<(String, String)>, usize, usize)> { let mut parts: Vec<(String, String)> = vec![ ("Preamble".to_string(), "Everything below is already loaded — your identity, instructions, \ @@ -162,7 +158,7 @@ pub fn assemble_context_message(cwd: &Path, prompt_file: &str, memory_project: O } } - let memories = load_memory_files(memory_project, context_groups); + let memories = load_memory_files(memory_project, context_groups).await; let memory_count = memories.len(); for (name, content) in memories { parts.push((name, content)); diff --git a/src/mind/unconscious.rs b/src/mind/unconscious.rs index b6a2eac..b766532 100644 --- a/src/mind/unconscious.rs +++ b/src/mind/unconscious.rs @@ -265,7 +265,7 @@ pub async fn prepare_spawn(name: &str, mut auto: AutoAgent) -> Result = std::collections::HashSet::new(); let batch = match defs::run_agent( &store, &def, def.count.unwrap_or(5), &exclude, - ) { + ).await { Ok(b) => b, Err(e) => { dbglog!("[unconscious] {} query failed: {}", name, e); diff --git a/src/subconscious/defs.rs b/src/subconscious/defs.rs index 1f1f960..db47109 100644 --- a/src/subconscious/defs.rs +++ b/src/subconscious/defs.rs @@ -14,6 +14,7 @@ // // The query selects what to operate on; placeholders pull in context. +use crate::agent::tools::memory::memory_render; use crate::graph::Graph; use crate::store::Store; @@ -198,7 +199,7 @@ struct Resolved { /// Resolve a single {{placeholder}} by name. /// Returns the replacement text and any node keys it produced (for visit tracking). -fn resolve( +async fn resolve( name: &str, store: &Store, graph: &Graph, @@ -211,10 +212,13 @@ fn resolve( let mut text = String::new(); let mut result_keys = Vec::new(); for key in keys { - if let Some(r) = resolve_tool(&format!("memory_render {}", key)) { - if !text.is_empty() { text.push_str("\n\n---\n\n"); } - text.push_str(&format!("## {}\n\n{}", key, r.text)); - result_keys.push(key.clone()); + match memory_render(None, key, None).await { + Ok(c) if !c.trim().is_empty() => { + if !text.is_empty() { text.push_str("\n\n---\n\n"); } + text.push_str(&format!("## {}\n\n{}", key, c)); + result_keys.push(key.clone()); + } + _ => continue, } } if text.is_empty() { return None; } @@ -227,12 +231,7 @@ fn resolve( let mut result_keys = Vec::new(); for key in keys { - let content = tokio::task::block_in_place(|| { - tokio::runtime::Handle::current().block_on( - crate::agent::tools::memory::memory_render(None, key, None) - ) - }); - match content { + match memory_render(None, key, None).await { Ok(c) if !c.trim().is_empty() => { text.push_str(&format!("#### {}\n\n{}\n\n---\n\n", key, c)); result_keys.push(key.clone()); @@ -305,12 +304,7 @@ fn resolve( let mut keys = Vec::new(); for group in &cfg.context_groups { if !group.agent { continue; } - // Bridge sync→async using block_in_place (same as resolve_tool) - let entries = tokio::task::block_in_place(|| { - tokio::runtime::Handle::current().block_on( - crate::cli::node::get_group_content(group, &cfg) - ) - }); + let entries = crate::cli::node::get_group_content(group, &cfg).await; for (key, content) in entries { use std::fmt::Write; writeln!(text, "--- {} ({}) ---", key, group.label).ok(); @@ -366,7 +360,7 @@ fn resolve( // tool:NAME ARGS — run a tool call and include its output _ if name.starts_with("tool:") => { let spec = name[5..].trim(); - resolve_tool(spec) + resolve_tool(spec).await } // bash:COMMAND — run a shell command and include its stdout @@ -529,9 +523,8 @@ fn resolve_memory_ratio() -> String { pct, keys.len(), memory_bytes / 1024, transcript_size / 1024) } -/// Resolve a {{tool: name {args}}} placeholder by calling the tool -/// handler from the registry. Uses block_in_place to bridge sync→async. -fn resolve_tool(spec: &str) -> Option { +/// Resolve a {{tool: name {args}}} placeholder by calling the tool handler. +async fn resolve_tool(spec: &str) -> Option { // Parse "tool_name {json args}" or "tool_name arg" let (name, args) = match spec.find('{') { Some(i) => { @@ -552,13 +545,7 @@ fn resolve_tool(spec: &str) -> Option { let tools = crate::agent::tools::tools(); let tool = tools.iter().find(|t| t.name == name)?; - let result = tokio::task::block_in_place(|| { - tokio::runtime::Handle::current().block_on( - (tool.handler)(None, args.clone()) - ) - }); - - match result { + match (tool.handler)(None, args.clone()).await { Ok(text) => Some(Resolved { text, keys: vec![] }), Err(e) => { eprintln!("[defs] {{{{tool: {}}}}} failed: {}", name, e); @@ -569,7 +556,7 @@ fn resolve_tool(spec: &str) -> Option { /// Resolve all {{placeholder}} patterns in a prompt template. /// Returns the resolved text and all node keys collected from placeholders. -pub fn resolve_placeholders( +pub async fn resolve_placeholders( template: &str, store: &Store, keys: &[String], @@ -585,7 +572,7 @@ pub fn resolve_placeholders( let Some(rel_end) = result[start + 2..].find("}}") else { break }; let end = start + 2 + rel_end; let name = result[start + 2..end].trim().to_lowercase(); - match resolve(&name, store, &graph, keys, count) { + match resolve(&name, store, &graph, keys, count).await { Some(resolved) => { let len = resolved.text.len(); extra_keys.extend(resolved.keys); @@ -606,7 +593,7 @@ pub fn resolve_placeholders( /// Run a config-driven agent: query → resolve placeholders → prompt. /// `exclude` filters out nodes (and their neighborhoods) already being /// worked on by other agents, preventing concurrent collisions. -pub fn run_agent( +pub async fn run_agent( store: &Store, def: &AgentDef, count: usize, @@ -621,11 +608,9 @@ pub fn run_agent( } else { format!("{} | limit:{}", def.query, padded) }; - let result = tokio::task::block_in_place(|| { - tokio::runtime::Handle::current().block_on( - crate::agent::tools::memory::memory_query(None, &query, None) - ) - }).map_err(|e| e.to_string())?; + let result = crate::agent::tools::memory::memory_query(None, &query, None) + .await + .map_err(|e| e.to_string())?; let filtered: Vec = result.lines() .filter(|l| !l.is_empty() && *l != "no results") .map(|s| s.to_string()) @@ -650,7 +635,7 @@ pub fn run_agent( .replace("{agent_name}", &def.agent) .replace("{user_name}", &cfg.user_name) .replace("{assistant_name}", &cfg.assistant_name); - let (prompt, extra_keys) = resolve_placeholders(&template, store, &all_keys, count); + let (prompt, extra_keys) = resolve_placeholders(&template, store, &all_keys, count).await; all_keys.extend(extra_keys); resolved_steps.push(super::prompts::ResolvedStep { prompt, diff --git a/src/subconscious/prompts.rs b/src/subconscious/prompts.rs index cc9c3da..5faad24 100644 --- a/src/subconscious/prompts.rs +++ b/src/subconscious/prompts.rs @@ -212,8 +212,8 @@ pub fn format_health_section(store: &Store, graph: &Graph) -> String { } /// Generate a specific agent prompt with filled-in data. -pub fn agent_prompt(store: &Store, agent: &str, count: usize) -> Result { +pub async fn agent_prompt(store: &Store, agent: &str, count: usize) -> Result { let def = super::defs::get_def(agent) .ok_or_else(|| format!("Unknown agent: {}", agent))?; - super::defs::run_agent(store, &def, count, &Default::default()) + super::defs::run_agent(store, &def, count, &Default::default()).await } diff --git a/src/user/mod.rs b/src/user/mod.rs index 0a50a6e..b72d9d6 100644 --- a/src/user/mod.rs +++ b/src/user/mod.rs @@ -184,7 +184,7 @@ fn restore_terminal(terminal: &mut ratatui::Terminal Result<()> { - let (config, _figment) = crate::config::load_session(&cli)?; + let (config, _figment) = crate::config::load_session(&cli).await?; if config.app.debug { unsafe { std::env::set_var("POC_DEBUG", "1") };