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>
This commit is contained in:
parent
bf1fa62d14
commit
850008ece7
3 changed files with 56 additions and 6 deletions
|
|
@ -1 +1 @@
|
||||||
{"sessionId":"463c6050-b49f-4509-9d4b-4596af79a90e","pid":11339,"acquiredAt":1775649730868}
|
{"sessionId":"463c6050-b49f-4509-9d4b-4596af79a90e","pid":61703,"acquiredAt":1775698574304}
|
||||||
|
|
@ -110,9 +110,39 @@ impl AutoAgent {
|
||||||
|
|
||||||
pub async fn run(
|
pub async fn run(
|
||||||
&mut self,
|
&mut self,
|
||||||
_bail_fn: Option<&(dyn Fn(usize) -> Result<(), String> + Sync)>,
|
bail_fn: Option<&(dyn Fn(usize) -> Result<(), String> + Sync)>,
|
||||||
) -> Result<String, String> {
|
) -> Result<String, String> {
|
||||||
Err("standalone agent run not yet migrated to completions API".to_string())
|
let config = crate::config::get();
|
||||||
|
let base_url = config.api_base_url.as_deref().unwrap_or("");
|
||||||
|
let api_key = config.api_key.as_deref().unwrap_or("");
|
||||||
|
let model = config.api_model.as_deref().unwrap_or("");
|
||||||
|
if base_url.is_empty() || model.is_empty() {
|
||||||
|
return Err("API not configured (no base_url or model)".to_string());
|
||||||
|
}
|
||||||
|
let client = super::api::ApiClient::new(base_url, api_key, model);
|
||||||
|
|
||||||
|
// Load system prompt + identity from config
|
||||||
|
let cli = crate::user::CliArgs::default();
|
||||||
|
let (app, _) = crate::config::load_app(&cli)
|
||||||
|
.map_err(|e| format!("config: {}", e))?;
|
||||||
|
let (system_prompt, personality) = crate::config::reload_for_model(
|
||||||
|
&app, &app.prompts.other,
|
||||||
|
).map_err(|e| format!("config: {}", e))?;
|
||||||
|
|
||||||
|
let agent = Agent::new(
|
||||||
|
client, system_prompt, personality,
|
||||||
|
app, String::new(),
|
||||||
|
None,
|
||||||
|
super::tools::ActiveTools::new(),
|
||||||
|
).await;
|
||||||
|
{
|
||||||
|
let mut st = agent.state.lock().await;
|
||||||
|
st.provenance = format!("standalone:{}", self.name);
|
||||||
|
st.tools = self.tools.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut backend = Backend(agent);
|
||||||
|
self.run_with_backend(&mut backend, bail_fn).await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Run forked using a shared agent Arc. The UI can lock the same
|
/// Run forked using a shared agent Arc. The UI can lock the same
|
||||||
|
|
@ -254,15 +284,35 @@ pub fn run_one_agent(
|
||||||
defs::run_agent(store, &def, effective_count, &Default::default())?
|
defs::run_agent(store, &def, effective_count, &Default::default())?
|
||||||
};
|
};
|
||||||
|
|
||||||
// Filter tools based on agent def
|
// Filter tools based on agent def, add filesystem output tool
|
||||||
let all_tools = super::tools::memory_and_journal_tools();
|
let all_tools = super::tools::memory_and_journal_tools();
|
||||||
let effective_tools: Vec<super::tools::Tool> = if def.tools.is_empty() {
|
let mut effective_tools: Vec<super::tools::Tool> = if def.tools.is_empty() {
|
||||||
all_tools.to_vec()
|
all_tools.to_vec()
|
||||||
} else {
|
} else {
|
||||||
all_tools.into_iter()
|
all_tools.into_iter()
|
||||||
.filter(|t| def.tools.iter().any(|w| w == &t.name))
|
.filter(|t| def.tools.iter().any(|w| w == &t.name))
|
||||||
.collect()
|
.collect()
|
||||||
};
|
};
|
||||||
|
effective_tools.push(super::tools::Tool {
|
||||||
|
name: "output",
|
||||||
|
description: "Produce a named output value for passing between steps.",
|
||||||
|
parameters_json: r#"{"type":"object","properties":{"key":{"type":"string","description":"Output name"},"value":{"type":"string","description":"Output value"}},"required":["key","value"]}"#,
|
||||||
|
handler: std::sync::Arc::new(|_agent, v| Box::pin(async move {
|
||||||
|
let key = v["key"].as_str()
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("output requires 'key'"))?;
|
||||||
|
if key.starts_with("pid-") || key.contains('/') || key.contains("..") {
|
||||||
|
anyhow::bail!("invalid output key: {}", key);
|
||||||
|
}
|
||||||
|
let value = v["value"].as_str()
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("output requires 'value'"))?;
|
||||||
|
let dir = std::env::var("POC_AGENT_OUTPUT_DIR")
|
||||||
|
.map_err(|_| anyhow::anyhow!("no output directory set"))?;
|
||||||
|
let path = std::path::Path::new(&dir).join(key);
|
||||||
|
std::fs::write(&path, value)
|
||||||
|
.map_err(|e| anyhow::anyhow!("writing output {}: {}", path.display(), e))?;
|
||||||
|
Ok(format!("{}: {}", key, value))
|
||||||
|
})),
|
||||||
|
});
|
||||||
let n_steps = agent_batch.steps.len();
|
let n_steps = agent_batch.steps.len();
|
||||||
|
|
||||||
// Guard: reject oversized first prompt
|
// Guard: reject oversized first prompt
|
||||||
|
|
|
||||||
|
|
@ -449,7 +449,7 @@ async fn run(
|
||||||
use clap::{Parser, Subcommand};
|
use clap::{Parser, Subcommand};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug, Default)]
|
||||||
#[command(name = "consciousness", about = "Substrate-independent AI agent")]
|
#[command(name = "consciousness", about = "Substrate-independent AI agent")]
|
||||||
pub struct CliArgs {
|
pub struct CliArgs {
|
||||||
/// Select active backend ("anthropic" or "openrouter")
|
/// Select active backend ("anthropic" or "openrouter")
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue