brokerd: ask_memory, a built-in memory tool that memoryd runs
Co-Authored-By: ornith-1.5-35b-a3b <agent@wntrmute.dev>
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
//! path, a host or a URL is well formed. The module is pure: no I/O, no clock. Everything it reads
|
||||
//! was written by the model, so it is treated as hostile.
|
||||
|
||||
use bxcore::AskMemoryArgs;
|
||||
use bxcore::tools::{HttpFetchArgs, ReadFileArgs, ShellArgs, WriteFileArgs};
|
||||
use bxcore::{MemoryArgs, RecallArgs};
|
||||
use std::fmt;
|
||||
@@ -15,8 +16,8 @@ pub const MAX_URL: usize = 2048;
|
||||
/// Maximum length, in characters, of the detail an invalid call is denied with.
|
||||
pub const MAX_DETAIL: usize = 300;
|
||||
|
||||
/// The tools the broker can dispatch to: four that run in a container, and the two memory
|
||||
/// tools that `memoryd` runs (M5a spec, section 8).
|
||||
/// The tools the broker can dispatch to: four that run in a container, and the three memory
|
||||
/// tools that `memoryd` runs (M5a spec, section 8; M5c spec, section 9).
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ToolName {
|
||||
ReadFile,
|
||||
@@ -25,6 +26,7 @@ pub enum ToolName {
|
||||
HttpFetch,
|
||||
Recall,
|
||||
Memory,
|
||||
AskMemory,
|
||||
}
|
||||
|
||||
impl ToolName {
|
||||
@@ -45,6 +47,7 @@ impl ToolName {
|
||||
"http_fetch" => Some(ToolName::HttpFetch),
|
||||
"recall" => Some(ToolName::Recall),
|
||||
"memory" => Some(ToolName::Memory),
|
||||
"ask_memory" => Some(ToolName::AskMemory),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -58,12 +61,16 @@ impl ToolName {
|
||||
ToolName::HttpFetch => "http_fetch",
|
||||
ToolName::Recall => "recall",
|
||||
ToolName::Memory => "memory",
|
||||
ToolName::AskMemory => "ask_memory",
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether `memoryd` runs this tool rather than a container.
|
||||
pub fn is_memory(self) -> bool {
|
||||
matches!(self, ToolName::Recall | ToolName::Memory)
|
||||
matches!(
|
||||
self,
|
||||
ToolName::Recall | ToolName::Memory | ToolName::AskMemory
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,6 +91,7 @@ pub enum ToolArgs {
|
||||
HttpFetch(FetchUrl),
|
||||
Recall(RecallArgs),
|
||||
Memory(MemoryArgs),
|
||||
AskMemory(AskMemoryArgs),
|
||||
}
|
||||
|
||||
/// A URL that passed the checks, and its host. Only `parse` makes one, so the host policy matched
|
||||
@@ -128,6 +136,7 @@ impl ToolArgs {
|
||||
ToolArgs::HttpFetch(_) => ToolName::HttpFetch,
|
||||
ToolArgs::Recall(_) => ToolName::Recall,
|
||||
ToolArgs::Memory(_) => ToolName::Memory,
|
||||
ToolArgs::AskMemory(_) => ToolName::AskMemory,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,6 +176,9 @@ impl ToolArgs {
|
||||
ToolArgs::Memory(value) => {
|
||||
serde_json::to_string(value).unwrap_or_else(|_| "{}".to_string())
|
||||
}
|
||||
ToolArgs::AskMemory(value) => {
|
||||
serde_json::to_string(value).unwrap_or_else(|_| "{}".to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -284,6 +296,12 @@ pub fn parse(tool: ToolName, arguments: &str) -> Result<ToolArgs, ArgsError> {
|
||||
value.check().map_err(ArgsError::Shape)?;
|
||||
Ok(ToolArgs::Memory(value))
|
||||
}
|
||||
ToolName::AskMemory => {
|
||||
let value: AskMemoryArgs = serde_json::from_str(arguments)
|
||||
.map_err(|error| ArgsError::Shape(error.to_string()))?;
|
||||
value.check().map_err(ArgsError::Shape)?;
|
||||
Ok(ToolArgs::AskMemory(value))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
//! The built-in grants of `recall` and `memory` (M5a spec, section 8). Reading and writing memory
|
||||
//! is what an agent is, so no grant file can turn it off or put questions in front of it; the
|
||||
//! prompt is protected by `memoryd`, which lets only clean sessions edit identity.
|
||||
//! The built-in grants of `recall`, `memory` and `ask_memory` (M5a spec, section 8; M5c spec,
|
||||
//! section 9). Reading and writing memory is what an agent is, so no grant file can turn it off or
|
||||
//! put questions in front of it; the prompt is protected by `memoryd`, which lets only clean
|
||||
//! sessions edit identity.
|
||||
|
||||
use bxcore::{Grant, GrantProblem, sha256};
|
||||
|
||||
@@ -12,17 +13,23 @@ pub const RESERVED: &str = "builtin-";
|
||||
/// The built-in grants' text, hashed for the audit log like a grant file's bytes.
|
||||
pub const RECALL: &str = "tool = \"recall\"\nmode = \"auto\"\nmax_taint = \"secret\"\nresult_class = \"private\"\nuntrusted = false\n";
|
||||
pub const MEMORY: &str = "tool = \"memory\"\nmode = \"auto\"\nmax_taint = \"secret\"\nresult_class = \"private\"\nuntrusted = false\n";
|
||||
pub const ASK_MEMORY: &str = "tool = \"ask_memory\"\nmode = \"auto\"\nmax_taint = \"secret\"\nresult_class = \"private\"\nuntrusted = false\n";
|
||||
|
||||
/// The sentence for an owner's grant naming a memory tool.
|
||||
pub const BUILT_IN: &str = "the policy of recall and memory is built in; remove this grant";
|
||||
pub const BUILT_IN: &str =
|
||||
"the policy of recall, memory and ask_memory is built in; remove this grant";
|
||||
|
||||
/// The two built-in grants, `builtin-memory` and `builtin-recall`.
|
||||
/// The three built-in grants, `builtin-memory`, `builtin-recall` and `builtin-ask-memory`.
|
||||
pub fn grants() -> Result<Vec<LoadedGrant>, GrantProblem> {
|
||||
// For each (id, text) of [("builtin-memory", MEMORY), ("builtin-recall", RECALL)]:
|
||||
// For each (id, text) of `sources`:
|
||||
// `toml::from_str::<Grant>(text)` and `sha256(text.as_bytes())`, each error as a GrantProblem {
|
||||
// file: id, line: None, problem: e.to_string() }; build LoadedGrant { id, grant, sha256 }.
|
||||
// Collect into Result<Vec<LoadedGrant>, GrantProblem> (the first error wins).
|
||||
let sources: &[(&str, &str)] = &[("builtin-memory", MEMORY), ("builtin-recall", RECALL)];
|
||||
let sources: &[(&str, &str)] = &[
|
||||
("builtin-memory", MEMORY),
|
||||
("builtin-recall", RECALL),
|
||||
("builtin-ask-memory", ASK_MEMORY),
|
||||
];
|
||||
let mut out: Vec<LoadedGrant> = Vec::new();
|
||||
for (id, text) in sources {
|
||||
let problem = |error: String| GrantProblem {
|
||||
|
||||
@@ -124,7 +124,9 @@ impl Runner {
|
||||
crate::args::ToolName::Shell => self.shell_ms,
|
||||
crate::args::ToolName::HttpFetch => self.http_fetch_ms,
|
||||
// `memoryd` runs these; they never reach a container.
|
||||
crate::args::ToolName::Recall | crate::args::ToolName::Memory => self.read_file_ms,
|
||||
crate::args::ToolName::Recall
|
||||
| crate::args::ToolName::Memory
|
||||
| crate::args::ToolName::AskMemory => self.read_file_ms,
|
||||
};
|
||||
std::time::Duration::from_millis(ms)
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@ fn covers(args: &ToolArgs, grant: &Grant) -> Option<Option<String>> {
|
||||
}
|
||||
ToolArgs::Shell { cwd: Some(cwd), .. } => best_path(grant, cwd, true),
|
||||
// The memory tools take no constraints: their grants are built in (M5a spec, section 8).
|
||||
ToolArgs::Recall(_) | ToolArgs::Memory(_) => Some(None),
|
||||
ToolArgs::Recall(_) | ToolArgs::Memory(_) | ToolArgs::AskMemory(_) => Some(None),
|
||||
ToolArgs::HttpFetch(target) => {
|
||||
if grant
|
||||
.constraints
|
||||
|
||||
@@ -129,7 +129,7 @@ pub fn run(decision: Decision, runtime: &dyn Runtime) -> bxcore::ToolResponse {
|
||||
None,
|
||||
),
|
||||
ToolArgs::HttpFetch(_) => (Vec::new(), Some(decision.hosts().to_vec())),
|
||||
ToolArgs::Recall(_) | ToolArgs::Memory(_) => (Vec::new(), None),
|
||||
ToolArgs::Recall(_) | ToolArgs::Memory(_) | ToolArgs::AskMemory(_) => (Vec::new(), None),
|
||||
};
|
||||
let spec = RunSpec {
|
||||
session: decision.request().session.clone(),
|
||||
|
||||
Reference in New Issue
Block a user