Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ The goal is a trustworthy substrate for coding agents that can:
## Features

- **Local-first storage** — SQLite plus `log.jsonl` under a configurable memory directory.
- **Append-first writes** — durable claims and hyperedges are appended to JSONL before SQLite insertion.
- **Append-first writes** — durable claims and hyperedges are appended to JSONL before SQLite insertion, and lifecycle transitions (retire, contradict, candidate staging, consolidation supersede/decay/merge) are logged the same way so replay reconstructs them.
- **Structured claims** — memories are stored as `(subject, predicate, object)` claims with source references, confidence, status, and agent provenance.
- **First-class hyperedges** — n-ary memories can be stored with predicate, provenance, confidence, source references, status, timestamps, and role/entity participants.
- **Privacy gate** — token/path/entropy checks run before writes; rejected content is not persisted.
Expand Down Expand Up @@ -155,7 +155,7 @@ Current CLI commands:
| `contradict` | Record a contradiction for a claim id and optional replacement claim. |
| `consolidate` | Consolidate active duplicates/conflicts and apply confidence decay. |
| `vacuum` | Run `VACUUM` (and optional analysis). |
| `replay` | Rebuild SQLite from the append-only logs. |
| `replay` | Rebuild SQLite from the append-only logs (strict by default; `--lenient` quarantines invalid lines and continues). |

## Server and MCP Usage

Expand Down
29 changes: 24 additions & 5 deletions crates/aver-cli/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use std::path::PathBuf;

use aver_core::{AgentKind, ObservationRelevance, ScopeWalk, Store, replay, vacuum};
use aver_core::{
AgentKind, ObservationRelevance, ReplayMode, ScopeWalk, Store, replay_with_mode, vacuum,
};
use clap::{Parser, Subcommand};

#[derive(Debug, Parser)]
Expand Down Expand Up @@ -192,6 +194,10 @@ enum Command {
/// Allow overwriting an existing populated db.sqlite.
#[arg(long)]
force: bool,
/// Quarantine invalid log lines and continue instead of aborting
/// (disaster recovery; strict by default).
#[arg(long)]
lenient: bool,
},
}

Expand All @@ -216,16 +222,29 @@ fn main() -> anyhow::Result<()> {
}
return Ok(());
}
Command::Replay { force } => {
let report = replay(&cli.memory_dir, *force)?;
Command::Replay { force, lenient } => {
let mode = if *lenient {
ReplayMode::Lenient
} else {
ReplayMode::Strict
};
let report = replay_with_mode(&cli.memory_dir, *force, mode)?;
println!(
"replay: claims={} hyperedges={} events={} observations={} files={}",
"replay: claims={} hyperedges={} events={} observations={} lifecycle={} files={} quarantined={}",
report.claims,
report.hyperedges,
report.events,
report.observations,
report.files_walked
report.lifecycle,
report.files_walked,
report.quarantined.len()
);
for quarantined in &report.quarantined {
eprintln!(
"quarantined: {}:{}: {}",
quarantined.path, quarantined.line, quarantined.error
);
}
return Ok(());
}
_ => {}
Expand Down
54 changes: 54 additions & 0 deletions crates/aver-cli/tests/replay.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
use assert_cmd::prelude::*;
use std::fmt::Write as _;
use std::process::Command;

fn aver(memory_dir: &std::path::Path) -> Command {
let mut cmd = Command::cargo_bin("aver").unwrap();
cmd.arg("--memory-dir").arg(memory_dir);
cmd
}

#[test]
fn replay_strict_fails_and_lenient_quarantines() {
let dir = tempfile::tempdir().unwrap();
let memory = dir.path();

let valid_line = |id: i64, subject: &str| {
format!(
r#"{{"kind":"add_claim","ts":1,"claim_id":{id},"subject":"{subject}","predicate":"depends_on","object":"o","source":"s","agent_id":"local","agent_kind":"HUMAN","confidence":0.95,"provenance":"USER_ASSERTED","scope":"global"}}"#
)
};
let mut log = String::new();
writeln!(log, "{}", valid_line(1, "good-one")).unwrap();
log.push_str("{\"kind\":\"no_such_kind\",\"ts\":1}\n");
writeln!(log, "{}", valid_line(2, "good-two")).unwrap();
std::fs::write(memory.join("log.jsonl"), log).unwrap();

// Default (strict) replay aborts on the unknown kind.
aver(memory).arg("replay").assert().failure();

// --lenient quarantines the bad line and rebuilds the rest.
let output = aver(memory)
.args(["replay", "--lenient"])
.assert()
.success()
.get_output()
.clone();
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
stdout.contains("quarantined=1"),
"expected quarantine count in output: {stdout}"
);
assert!(
stdout.contains("claims=2"),
"expected both valid claims applied: {stdout}"
);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("quarantined:") && stderr.contains("no_such_kind"),
"expected quarantine diagnostics on stderr: {stderr}"
);

// The rebuilt store opens and serves the two surviving claims.
aver(memory).arg("status").assert().success();
}
Loading
Loading