poc-memory: POC_MEMORY_DRY_RUN=1 for agent testing
All mutating commands (write, delete, rename, link-add, journal write, used, wrong, not-useful, gap) check POC_MEMORY_DRY_RUN after argument validation but before mutation. If set, process exits silently — agent tool calls are visible in the LLM output so we can see what it tried to do without applying changes. Read commands (render, search, graph link, journal tail) work normally in dry-run mode so agents can still explore the graph. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
2ab9b78363
commit
7e131862d6
4 changed files with 18 additions and 0 deletions
|
|
@ -134,6 +134,7 @@ pub fn cmd_triangle_close(min_degree: usize, sim_threshold: f32, max_per_hub: us
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn cmd_link_add(source: &str, target: &str, reason: &[String]) -> Result<(), String> {
|
pub fn cmd_link_add(source: &str, target: &str, reason: &[String]) -> Result<(), String> {
|
||||||
|
super::check_dry_run();
|
||||||
let mut store = store::Store::load()?;
|
let mut store = store::Store::load()?;
|
||||||
let source = store.resolve_key(source)?;
|
let source = store.resolve_key(source)?;
|
||||||
let target = store.resolve_key(target)?;
|
let target = store.resolve_key(target)?;
|
||||||
|
|
|
||||||
|
|
@ -176,6 +176,7 @@ pub fn cmd_journal_write(text: &[String]) -> Result<(), String> {
|
||||||
if text.is_empty() {
|
if text.is_empty() {
|
||||||
return Err("journal-write requires text".into());
|
return Err("journal-write requires text".into());
|
||||||
}
|
}
|
||||||
|
super::check_dry_run();
|
||||||
let text = text.join(" ");
|
let text = text.join(" ");
|
||||||
|
|
||||||
let timestamp = crate::store::format_datetime(crate::store::now_epoch());
|
let timestamp = crate::store::format_datetime(crate::store::now_epoch());
|
||||||
|
|
|
||||||
|
|
@ -9,3 +9,10 @@ pub mod agent;
|
||||||
pub mod admin;
|
pub mod admin;
|
||||||
pub mod journal;
|
pub mod journal;
|
||||||
pub mod misc;
|
pub mod misc;
|
||||||
|
|
||||||
|
/// Exit silently if POC_MEMORY_DRY_RUN=1.
|
||||||
|
pub fn check_dry_run() {
|
||||||
|
if std::env::var("POC_MEMORY_DRY_RUN").map_or(false, |v| v == "1" || v == "true") {
|
||||||
|
std::process::exit(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ pub fn cmd_used(key: &[String]) -> Result<(), String> {
|
||||||
if key.is_empty() {
|
if key.is_empty() {
|
||||||
return Err("used requires a key".into());
|
return Err("used requires a key".into());
|
||||||
}
|
}
|
||||||
|
super::check_dry_run();
|
||||||
let key = key.join(" ");
|
let key = key.join(" ");
|
||||||
let mut store = store::Store::load()?;
|
let mut store = store::Store::load()?;
|
||||||
let resolved = store.resolve_key(&key)?;
|
let resolved = store.resolve_key(&key)?;
|
||||||
|
|
@ -38,6 +39,7 @@ pub fn cmd_used(key: &[String]) -> Result<(), String> {
|
||||||
|
|
||||||
pub fn cmd_wrong(key: &str, context: &[String]) -> Result<(), String> {
|
pub fn cmd_wrong(key: &str, context: &[String]) -> Result<(), String> {
|
||||||
let ctx = if context.is_empty() { None } else { Some(context.join(" ")) };
|
let ctx = if context.is_empty() { None } else { Some(context.join(" ")) };
|
||||||
|
super::check_dry_run();
|
||||||
let mut store = store::Store::load()?;
|
let mut store = store::Store::load()?;
|
||||||
let resolved = store.resolve_key(key)?;
|
let resolved = store.resolve_key(key)?;
|
||||||
store.mark_wrong(&resolved, ctx.as_deref());
|
store.mark_wrong(&resolved, ctx.as_deref());
|
||||||
|
|
@ -71,6 +73,8 @@ pub fn cmd_not_relevant(key: &str) -> Result<(), String> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn cmd_not_useful(key: &str) -> Result<(), String> {
|
pub fn cmd_not_useful(key: &str) -> Result<(), String> {
|
||||||
|
// no args to validate
|
||||||
|
super::check_dry_run();
|
||||||
let mut store = store::Store::load()?;
|
let mut store = store::Store::load()?;
|
||||||
let resolved = store.resolve_key(key)?;
|
let resolved = store.resolve_key(key)?;
|
||||||
// Same as wrong but with clearer semantics: node content is bad, edges are fine.
|
// Same as wrong but with clearer semantics: node content is bad, edges are fine.
|
||||||
|
|
@ -84,6 +88,7 @@ pub fn cmd_gap(description: &[String]) -> Result<(), String> {
|
||||||
if description.is_empty() {
|
if description.is_empty() {
|
||||||
return Err("gap requires a description".into());
|
return Err("gap requires a description".into());
|
||||||
}
|
}
|
||||||
|
super::check_dry_run();
|
||||||
let desc = description.join(" ");
|
let desc = description.join(" ");
|
||||||
let mut store = store::Store::load()?;
|
let mut store = store::Store::load()?;
|
||||||
store.record_gap(&desc);
|
store.record_gap(&desc);
|
||||||
|
|
@ -146,6 +151,7 @@ pub fn cmd_node_delete(key: &[String]) -> Result<(), String> {
|
||||||
if key.is_empty() {
|
if key.is_empty() {
|
||||||
return Err("node-delete requires a key".into());
|
return Err("node-delete requires a key".into());
|
||||||
}
|
}
|
||||||
|
super::check_dry_run();
|
||||||
let key = key.join(" ");
|
let key = key.join(" ");
|
||||||
let mut store = store::Store::load()?;
|
let mut store = store::Store::load()?;
|
||||||
let resolved = store.resolve_key(&key)?;
|
let resolved = store.resolve_key(&key)?;
|
||||||
|
|
@ -156,6 +162,8 @@ pub fn cmd_node_delete(key: &[String]) -> Result<(), String> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn cmd_node_rename(old_key: &str, new_key: &str) -> Result<(), String> {
|
pub fn cmd_node_rename(old_key: &str, new_key: &str) -> Result<(), String> {
|
||||||
|
// args are positional, always valid if present
|
||||||
|
super::check_dry_run();
|
||||||
let mut store = store::Store::load()?;
|
let mut store = store::Store::load()?;
|
||||||
let old_resolved = store.resolve_key(old_key)?;
|
let old_resolved = store.resolve_key(old_key)?;
|
||||||
store.rename_node(&old_resolved, new_key)?;
|
store.rename_node(&old_resolved, new_key)?;
|
||||||
|
|
@ -286,6 +294,7 @@ pub fn cmd_write(key: &[String]) -> Result<(), String> {
|
||||||
if content.trim().is_empty() {
|
if content.trim().is_empty() {
|
||||||
return Err("No content on stdin".into());
|
return Err("No content on stdin".into());
|
||||||
}
|
}
|
||||||
|
super::check_dry_run();
|
||||||
|
|
||||||
let mut store = store::Store::load()?;
|
let mut store = store::Store::load()?;
|
||||||
let key = store.resolve_key(&raw_key).unwrap_or(raw_key);
|
let key = store.resolve_key(&raw_key).unwrap_or(raw_key);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue