From 21ee330548077aa6238830c2ba7b37ac1cec38e7 Mon Sep 17 00:00:00 2001 From: TerminallyLazy Date: Wed, 8 Jul 2026 20:40:16 -0400 Subject: [PATCH 1/4] Design performance and harness certification --- ...erformance-harness-certification-design.md | 242 ++++++++++++++++++ 1 file changed, 242 insertions(+) create mode 100644 docs/superpowers/specs/2026-07-09-tree-ring-performance-harness-certification-design.md diff --git a/docs/superpowers/specs/2026-07-09-tree-ring-performance-harness-certification-design.md b/docs/superpowers/specs/2026-07-09-tree-ring-performance-harness-certification-design.md new file mode 100644 index 0000000..a7d57c3 --- /dev/null +++ b/docs/superpowers/specs/2026-07-09-tree-ring-performance-harness-certification-design.md @@ -0,0 +1,242 @@ +# Tree Ring Performance And Harness Certification Design + +## Status + +Approved direction: performance and harness certification before adding broader +features. Tree Ring Memory should keep the Rust-native CLI small and fast while +making agent-harness compatibility provable through repeatable local checks. + +## Intent + +Recent verification showed that core recall is healthy, installer behavior is +working in isolated project and global installs, and the generated +agent-awareness files give real harnesses usable instructions. It also exposed +two gaps that should be fixed before claiming the app is smooth at scale: + +- bulk import and seed writes slow down at larger memory counts +- harness proof is spread across manual commands instead of one repeatable + certification surface + +This change should make Tree Ring easier to trust. A maintainer should be able +to run one certification command or script and get install size, storage size, +recall latency, import throughput, adapter behavior, and harness bridge status +from the same checkout. + +## Current Baseline + +Measured on the current Rust-native v0.11 checkout: + +- release binary: about 5.8 MB +- project install with init: about 5.9 MB +- 10k in-process recall: max about 6.3 ms +- 30k in-process recall: max about 14.3 ms +- 50k in-process recall: max about 22.1 ms +- 10k CLI subprocess recall: p95 about 7.5 ms to 12.4 ms depending on query +- 50k performance smoke: recall stayed fast, but insert throughput fell below + the existing 500 inserts/s threshold +- Agent Zero plugin tests pass when run with the expected Python path +- Agent Zero maintenance commands work on a host when + `TREE_RING_MEMORY_DATA_DIR` points at a writable data root + +## Goals + +- Add a repeatable certification workflow for installer, size, recall, import, + adapter, and harness bridge checks. +- Improve bulk JSONL import so larger seed or benchmark loads do not write one + event at a time. +- Preserve current recall speed and privacy defaults. +- Distinguish project-local harness detection from user-home harness detection. +- Keep certification output machine-readable and human-readable. +- Keep Tree Ring framework-agnostic: no Agent Zero core changes, no Codex-only + assumptions, no hidden daemon, and no background transcript capture. +- Clean up recently touched code where it removes obvious noise without changing + behavior. + +## Non-Goals + +- Do not add vector search in this change. +- Do not add remote sync, cloud storage, or hosted telemetry. +- Do not auto-write global harness configuration. +- Do not turn TUI event-stream pulses into durable memory. +- Do not replace DOX, Revolve, Agent Zero, Codex, Claude Code, OpenCode, Goose, + Hermes, or Pi; Tree Ring remains a local CLI memory lifecycle layer they can + call deliberately. + +## Considered Approaches + +### 1. Certification Script Plus Targeted Import Fix + +Add a repo-owned certification script under `scripts/` that drives the existing +CLI and examples, emits metrics JSON, and fails on defined thresholds. Pair it +with a targeted SQLite import batching fix. + +Trade-off: this is the smallest useful change and keeps the product model +unchanged. It does not create a new long-lived CLI subcommand yet. + +### 2. New `tree-ring certify` CLI Subcommand + +Add a first-class Rust CLI command that runs the certification checks itself. + +Trade-off: this is polished for users, but it grows the public command surface +before the checks have stabilized. It also pulls installer and host-harness +concerns into the binary. + +### 3. External Benchmark-Only Validation + +Focus only on external benchmark repositories and published harness PRs. + +Trade-off: useful for credibility, but it does not protect this repository from +regressions and cannot verify local install, generated files, or host/container +harness behavior. + +Recommended approach: start with approach 1. After the script and thresholds +settle, consider promoting the stable subset into `tree-ring certify`. + +## Design + +### Certification Workflow + +Add `scripts/certify-tree-ring.sh`. + +Default checks: + +- `cargo fmt --check` +- `cargo test --locked` +- `cargo clippy --locked --all-targets` +- `cargo build --release --locked` +- release binary size check +- isolated project install with `--source`, `--project`, `--init`, and + `--no-animation` +- isolated global install with `--source`, `--global`, and `--no-onboarding` +- JSON CLI smoke for `init`, `remember`, `evidence`, `recall`, and `audit` +- DOX dry-run/write recall smoke +- Revolve dry-run/write recall smoke +- integration scan smoke with temporary Codex, Claude Code, Agent Zero, DOX, + Revolve, Goose, and OpenCode markers +- recall benchmark at 10k and 30k memories +- optional extended recall benchmark at 50k memories +- optional Agent Zero plugin smoke when a plugin checkout path is supplied + +The script should emit: + +- concise console output for humans +- `target/tree-ring-certification/metrics.json` +- `target/tree-ring-certification/summary.md` + +The summary should include install size, storage size, insert throughput, +recall avg/max/p95 where available, command versions, and pass/fail status. + +### Bulk Storage Throughput + +Bulk memory loading has two paths that matter: + +- `SQLiteMemoryStore::put_many`, used by benchmarks and internal batch writes +- `SQLiteMemoryStore::import_jsonl`, used by CLI imports and harness adapters + +Both paths should be measured by certification. `import_jsonl` currently checks +and writes events one by one. Replace that path with a transaction-oriented +import: + +1. Decode and normalize JSONL exactly as today. +2. Query existing ids in batches. +3. Split events into insert, replace, and skipped duplicate sets. +4. Write inserts and replacements through `put_many` inside one transaction. +5. Apply supersession updates after rows are present, preserving current + behavior. +6. Keep dry-run behavior unchanged. + +The implementation must preserve sensitivity checks, secret blocking, +duplicate skipping, replace semantics, and supersession behavior. + +If `put_many` still falls below the 500 inserts/s threshold at 50k records after +the import change, optimize the shared SQLite write path rather than hiding the +failure in the benchmark. + +### Harness Detection Reporting + +Extend integration scan reporting so each marker records its origin: + +- `project` +- `home` + +The existing `detected` and `available` statuses can remain, but JSON output +should make it clear whether a harness is proven for the current repository or +only known from the user's home environment. + +This avoids overstating readiness when a project has no local Claude, Codex, +OpenCode, Goose, or Agent Zero bridge files but the user's machine has global +configuration. + +### Harness Smoke Matrix + +Certification should exercise harnesses at the level Tree Ring actually owns: + +- Codex/Gemini-style: generated `.tree-ring/SKILL.md` and + `.tree-ring/CLI.md` are present and contain recall/remember guidance. +- Claude Code: scanner detects `.claude`/`CLAUDE.md` markers and gives a + bridge next step. +- Agent Zero: optional plugin smoke runs syntax checks, tests, and maintenance + commands with `TREE_RING_MEMORY_DATA_DIR` when a checkout is available. +- DOX: `dox sync --dry-run` previews source-linked memories and write mode + stores retrievable memories. +- Revolve: `revolve sync --dry-run` previews evidence memories and write mode + stores retrievable memories. +- OpenCode/Goose/Hermes/Pi: scanner detects markers and reports explicit next + steps without modifying those frameworks. + +### Code Simplification + +Keep cleanup narrowly scoped to files touched by this work or surfaced by the +current verification: + +- remove duplicate `welcome_logo_frame` branches in `welcome.rs` +- remove or test the unused `RingMarkFrame::layer_at` +- derive simple defaults for `MaintenanceFtsReport` and `MemoryReview` +- simplify filter placeholder construction in SQLite code +- avoid broader ring renderer refactors unless needed for tests + +These changes should not alter user-visible behavior. + +## Error Handling + +- Certification must fail with a clear section name and preserve the metrics + gathered before failure. +- Temporary install roots must be cleaned up on success and failure. +- Host Agent Zero checks must not write to `/a0`; they must use + `TREE_RING_MEMORY_DATA_DIR`. +- Missing optional harness checkouts should be reported as skipped, not failed. +- `cargo clippy` warnings caused by known cleanup items should be fixed before + certification is considered green. + +## Acceptance Criteria + +1. `scripts/certify-tree-ring.sh` runs from the repo root and produces metrics + JSON plus a markdown summary. +2. Certification passes on the default local suite. +3. The release binary remains under 8 MB for the current macOS ARM64 build. +4. Project install with init remains under 8 MB before user memories are added. +5. 10k and 30k recall max latency remain under 50 ms in release mode. +6. 50k recall max latency remains under 100 ms in the optional extended mode. +7. 10k JSONL CLI import completes at or above 1,500 events/s on the + certification host. +8. Optional 50k bulk storage smoke completes at or above 500 inserts/s, or the + certification report fails with the measured throughput. +9. Integration scan JSON distinguishes project markers from home markers. +10. DOX and Revolve dry-run/write smokes store retrievable source-linked memory. +11. Optional Agent Zero plugin smoke passes when the checkout path is supplied. +12. `cargo fmt --check`, `cargo test --locked`, `cargo clippy --locked + --all-targets`, and `git diff --check` pass. + +## Documentation + +Update README or architecture docs only after implementation proves the final +commands and thresholds. The docs should state measured local results with the +machine/date context, not broad benchmark claims. + +The certification summary is the primary evidence artifact. Marketing or launch +materials should link to evidence and avoid unsupported leaderboard claims. + +## Open Follow-Up + +After the script and import fix land, decide whether the stable certification +subset should become a user-facing `tree-ring certify` command. From 78c236b976db133576ffdf42cba9a2fe5a60f3c4 Mon Sep 17 00:00:00 2001 From: TerminallyLazy Date: Wed, 8 Jul 2026 22:22:48 -0400 Subject: [PATCH 2/4] Plan performance and harness certification --- ...rness-certification-implementation-plan.md | 159 ++++++++++++++++++ 1 file changed, 159 insertions(+) create mode 100644 docs/superpowers/plans/2026-07-09-tree-ring-performance-harness-certification-implementation-plan.md diff --git a/docs/superpowers/plans/2026-07-09-tree-ring-performance-harness-certification-implementation-plan.md b/docs/superpowers/plans/2026-07-09-tree-ring-performance-harness-certification-implementation-plan.md new file mode 100644 index 0000000..993f14e --- /dev/null +++ b/docs/superpowers/plans/2026-07-09-tree-ring-performance-harness-certification-implementation-plan.md @@ -0,0 +1,159 @@ +# Tree Ring Performance And Harness Certification Implementation Plan + +## Task 1: Add Bulk Storage Performance Tests + +Files: + +- `crates/tree-ring-memory-sqlite/src/lib.rs` +- `crates/tree-ring-memory-sqlite/examples/performance_smoke.rs` + +Work: + +- Add focused tests around duplicate handling, replacement, and supersession for + bulk import so later refactors are protected. +- Keep the existing performance smoke but make its output easy for automation + to parse. +- Preserve the current recall latency assertions. +- Keep the 50k path as an extended check so default certification remains + practical. + +Checks: + +```bash +cargo test -p tree-ring-memory-sqlite import_jsonl +cargo run --release -p tree-ring-memory-sqlite --example performance_smoke -- 10000 +cargo run --release -p tree-ring-memory-sqlite --example performance_smoke -- 30000 +``` + +## Task 2: Batch JSONL Import + +Files: + +- `crates/tree-ring-memory-sqlite/src/lib.rs` + +Work: + +- Add an internal helper that fetches existing memory ids in chunks. +- Refactor `SQLiteMemoryStore::import_jsonl` to split normalized events into + inserts, replacements, and skipped duplicates before writing. +- Write inserts and replacements through transaction-backed batch paths instead + of calling `put` for every event. +- Preserve dry-run behavior, secret blocking, duplicate skipping, + `--replace-existing`, and supersession application. +- If `put_many` remains the bottleneck for larger stores, profile and optimize + the shared write path without changing storage semantics. + +Checks: + +```bash +cargo test -p tree-ring-memory-sqlite import_jsonl +cargo test -p tree-ring-memory-sqlite put_many +cargo run --release -p tree-ring-memory-sqlite --example performance_smoke -- 30000 +``` + +## Task 3: Report Harness Marker Origin + +Files: + +- `crates/tree-ring-memory-cli/src/integrations.rs` +- `crates/tree-ring-memory-cli/src/main.rs` + +Work: + +- Extend `AgentIntegration` marker output so each marker includes its origin: + `project` or `home`. +- Keep the existing human-readable scan useful, but make JSON output explicit + enough for certification. +- Preserve existing integration ids and statuses. +- Add tests proving project-only, home-only, and mixed marker cases. + +Checks: + +```bash +cargo test -p tree-ring-memory-cli integrations +./target/release/tree-ring --json integrations scan --source-root . +``` + +## Task 4: Add Certification Script + +Files: + +- `scripts/certify-tree-ring.sh` + +Work: + +- Add a POSIX shell script that runs from the repo root. +- Build release once, then reuse the binary for CLI smokes. +- Write outputs under `target/tree-ring-certification/`. +- Emit `metrics.json` and `summary.md`. +- Measure release binary size, project install size, CLI import throughput, and + recall timing. +- Run isolated project and global installer checks. +- Run JSON CLI smoke for `init`, `remember`, `evidence`, `recall`, and `audit`. +- Run DOX and Revolve dry-run/write smokes. +- Run integration-scan smoke with temporary harness markers. +- Support optional environment variables: + - `TREE_RING_CERT_EXTENDED=1` for the 50k benchmark + - `TREE_RING_AGENT_ZERO_ROOT=/path/to/a0-ready-test` for Agent Zero plugin + checks + +Checks: + +```bash +sh scripts/certify-tree-ring.sh +TREE_RING_CERT_EXTENDED=1 sh scripts/certify-tree-ring.sh +TREE_RING_AGENT_ZERO_ROOT=/Users/lazy/a0-ready-test sh scripts/certify-tree-ring.sh +``` + +## Task 5: Apply Code-Simplifier Cleanup + +Files: + +- `crates/tree-ring-memory-cli/src/welcome.rs` +- `crates/tree-ring-memory-cli/src/ring_mark.rs` +- `crates/tree-ring-memory-core/src/maintenance.rs` +- `crates/tree-ring-memory-core/src/models.rs` +- `crates/tree-ring-memory-sqlite/src/lib.rs` + +Work: + +- Remove the duplicate `welcome_logo_frame` branch. +- Remove or test `RingMarkFrame::layer_at`. +- Derive simple defaults where Clippy already identified manual equivalents. +- Simplify SQLite placeholder construction. +- Avoid broader renderer refactors unless a test requires them. + +Checks: + +```bash +cargo clippy --locked --all-targets +cargo test --locked +``` + +## Task 6: Document And Run Final Certification + +Files: + +- `README.md` +- `docs/architecture/rust-core-status.md` +- `docs/superpowers/specs/2026-07-09-tree-ring-performance-harness-certification-design.md` + +Work: + +- Document the certification script only after it works. +- Record current measured results with date and machine context. +- Avoid marketing claims or external benchmark claims that are not directly + proved by the certification output. +- Update architecture status to mention project-vs-home marker reporting and + the certification evidence artifact. + +Final checks: + +```bash +cargo fmt --check +cargo test --locked +cargo clippy --locked --all-targets +cargo build --release --locked +sh scripts/certify-tree-ring.sh +git diff --check +``` From 3e4ab1bfabd522b1fd88ecfd3107d7b38fc30d9c Mon Sep 17 00:00:00 2001 From: TerminallyLazy Date: Wed, 8 Jul 2026 22:43:52 -0400 Subject: [PATCH 3/4] Add performance and harness certification --- README.md | 26 +- .../tree-ring-memory-cli/src/integrations.rs | 109 ++++++- crates/tree-ring-memory-cli/src/main.rs | 5 +- crates/tree-ring-memory-cli/src/ring_mark.rs | 72 +++-- crates/tree-ring-memory-cli/src/tui/render.rs | 8 +- crates/tree-ring-memory-cli/src/welcome.rs | 2 - .../src/import_export.rs | 4 +- .../tree-ring-memory-core/src/maintenance.rs | 14 +- crates/tree-ring-memory-core/src/models.rs | 13 +- crates/tree-ring-memory-sqlite/src/lib.rs | 78 ++++- docs/architecture/rust-core-status.md | 27 +- scripts/certify-tree-ring.sh | 276 ++++++++++++++++++ 12 files changed, 555 insertions(+), 79 deletions(-) create mode 100755 scripts/certify-tree-ring.sh diff --git a/README.md b/README.md index a690b51..97154cf 100644 --- a/README.md +++ b/README.md @@ -260,7 +260,9 @@ tree-ring integrations scan --source-root . It looks for local markers for DOX, Revolve, Codex, Claude Code, Agent Zero/A0, Goose, OpenCode, Hermes, and Pi, then suggests next steps without editing those -tools' configuration. +tools' configuration. JSON output records whether each marker came from the +project or from the user's home configuration, so harness readiness is not +overstated when only global config exists. Agent-mediated bridge linking is the planned next step after read-only discovery. The design keeps `.tree-ring` as the canonical memory root while @@ -276,8 +278,8 @@ OpenCode, or other agent configuration. `tree_ring_memory_export` header with schema and plugin version metadata; each remaining line is a `memory_event` envelope. The command excludes sensitive and superseded memories unless `--include-sensitive` or `--include-superseded` is -set. Import validates all events, skips duplicate ids by default, and replaces -existing ids only with `--replace-existing`. +set. Import validates all events, batches writes through SQLite, skips duplicate +ids by default, and replaces existing ids only with `--replace-existing`. `tree-ring audit` is non-mutating. It reports deterministic local findings for stale expiry, sensitive retention, low-confidence durable memory, supersession @@ -362,6 +364,7 @@ cargo run -p tree-ring-memory-cli -- dox sync --help cargo run -p tree-ring-memory-cli -- revolve sync --help cargo run -p tree-ring-memory-cli -- integrations scan --help cargo run --release -p tree-ring-memory-sqlite --example performance_smoke -- 1000 +sh scripts/certify-tree-ring.sh sh scripts/package-release.sh ``` @@ -370,6 +373,23 @@ asserts nonempty recalls, emits a `METRICS_JSON=` line, and uses conservative local thresholds of at least 500 inserts/sec and max recall latency of 250 ms for the synthetic workload. +`scripts/certify-tree-ring.sh` runs the fuller local certification suite: +formatting, tests, Clippy, release build, isolated project/global installs, CLI +JSON smokes, DOX/Revolve adapter smokes, integration-scan origin checks, import +throughput, and 10k/30k recall timing. It writes +`target/tree-ring-certification/summary.md` and `metrics.json`. + +Latest local certification run, generated at `2026-07-09T02:42:24Z` with +Agent Zero plugin smoke enabled: + +- Release binary: 6,104,272 bytes. +- Project install with init: 6,032 KB. +- Global install: 5,988 KB. +- CLI import: 10,000 memories in 5 seconds, about 2,000/sec. +- 10k recall max: 6.740 ms. +- 30k recall max: 14.705 ms. +- Agent Zero plugin smoke: passed. + `scripts/package-release.sh` builds the Rust CLI in release mode, creates a platform tarball under `dist/`, and writes a SHA-256 checksum file. Tag pushes run the release artifact workflow for Linux and macOS. diff --git a/crates/tree-ring-memory-cli/src/integrations.rs b/crates/tree-ring-memory-cli/src/integrations.rs index 85a98b6..8edf143 100644 --- a/crates/tree-ring-memory-cli/src/integrations.rs +++ b/crates/tree-ring-memory-cli/src/integrations.rs @@ -15,10 +15,23 @@ pub struct AgentIntegration { pub name: &'static str, pub status: IntegrationStatus, pub confidence: f64, - pub markers: Vec, + pub markers: Vec, pub next_step: &'static str, } +#[derive(Debug, Clone, PartialEq, Eq, Serialize)] +pub struct IntegrationMarker { + pub path: String, + pub origin: MarkerOrigin, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)] +#[serde(rename_all = "snake_case")] +pub enum MarkerOrigin { + Project, + Home, +} + #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)] #[serde(rename_all = "snake_case")] pub enum IntegrationStatus { @@ -28,12 +41,24 @@ pub enum IntegrationStatus { pub fn scan_integrations(root: &Path) -> IntegrationScanReport { let home = env::var_os("HOME").map(PathBuf::from); + scan_integrations_with_home(root, home.as_deref()) +} + +pub fn format_markers(markers: &[IntegrationMarker]) -> String { + markers + .iter() + .map(|marker| format!("{}:{}", marker.origin.as_str(), marker.path)) + .collect::>() + .join(", ") +} + +fn scan_integrations_with_home(root: &Path, home: Option<&Path>) -> IntegrationScanReport { let mut integrations = vec![ detect( "dox", "DOX / AGENTS.md", root, - home.as_deref(), + home, &["AGENTS.md"], &[], "Run `tree-ring dox sync --source-root . --dry-run`, then sync when the preview looks right.", @@ -42,7 +67,7 @@ pub fn scan_integrations(root: &Path) -> IntegrationScanReport { "revolve", "Revolve", root, - home.as_deref(), + home, &["revolve", ".revolve"], &[], "Run `tree-ring revolve sync --source-root revolve --dry-run` to import promoted lessons, scars, and seeds.", @@ -51,7 +76,7 @@ pub fn scan_integrations(root: &Path) -> IntegrationScanReport { "codex", "Codex", root, - home.as_deref(), + home, &[".codex", "AGENTS.md"], &[".codex"], "Reference `.tree-ring/SKILL.md` and `.tree-ring/CLI.md` from project guidance.", @@ -60,7 +85,7 @@ pub fn scan_integrations(root: &Path) -> IntegrationScanReport { "claude-code", "Claude Code", root, - home.as_deref(), + home, &[".claude", "CLAUDE.md"], &[".claude"], "Reference `.tree-ring/SKILL.md` from `CLAUDE.md` or `.claude` project instructions.", @@ -69,7 +94,7 @@ pub fn scan_integrations(root: &Path) -> IntegrationScanReport { "agent-zero", "Agent Zero / A0", root, - home.as_deref(), + home, &["usr/plugins", "a0", "agent-zero", ".a0"], &[".a0"], "Use the generated skill/CLI guidance, or bridge via an Agent Zero plugin without modifying core code.", @@ -78,7 +103,7 @@ pub fn scan_integrations(root: &Path) -> IntegrationScanReport { "goose", "Goose", root, - home.as_deref(), + home, &[".goose", "goosehints"], &[".goose"], "Add Tree Ring Memory recall and remember commands to Goose project instructions.", @@ -87,7 +112,7 @@ pub fn scan_integrations(root: &Path) -> IntegrationScanReport { "opencode", "OpenCode", root, - home.as_deref(), + home, &[".opencode", "opencode.json", "opencode.toml"], &[".opencode"], "Reference the Tree Ring CLI from OpenCode project configuration or instructions.", @@ -96,7 +121,7 @@ pub fn scan_integrations(root: &Path) -> IntegrationScanReport { "hermes", "Hermes", root, - home.as_deref(), + home, &[".hermes", "hermes.toml"], &[".hermes"], "Reference Tree Ring Memory as a local CLI memory lifecycle layer.", @@ -105,7 +130,7 @@ pub fn scan_integrations(root: &Path) -> IntegrationScanReport { "pi", "Pi", root, - home.as_deref(), + home, &[".pi", "pi.toml"], &[".pi"], "Use the generated portable skill and CLI reference as project instructions.", @@ -129,6 +154,15 @@ pub fn scan_integrations(root: &Path) -> IntegrationScanReport { } } +impl MarkerOrigin { + pub fn as_str(self) -> &'static str { + match self { + Self::Project => "project", + Self::Home => "home", + } + } +} + fn detect( id: &'static str, name: &'static str, @@ -142,18 +176,28 @@ fn detect( for marker in project_markers { let path = root.join(marker); if path.exists() { - markers.push(path.display().to_string()); + markers.push(IntegrationMarker { + path: path.display().to_string(), + origin: MarkerOrigin::Project, + }); } } if let Some(home) = home { for marker in home_markers { let path = home.join(marker); if path.exists() { - markers.push(path.display().to_string()); + markers.push(IntegrationMarker { + path: path.display().to_string(), + origin: MarkerOrigin::Home, + }); } } } - markers.sort(); + markers.sort_by(|left, right| { + left.path + .cmp(&right.path) + .then_with(|| left.origin.as_str().cmp(right.origin.as_str())) + }); markers.dedup(); let confidence = if markers.is_empty() { 0.0 @@ -197,6 +241,37 @@ mod tests { assert!(detected(&report, "claude-code")); } + #[test] + fn marks_project_and_home_marker_origins() { + let project = tempdir().unwrap(); + let home = tempdir().unwrap(); + fs::write(project.path().join("CLAUDE.md"), "# Claude instructions").unwrap(); + fs::create_dir_all(project.path().join(".codex")).unwrap(); + fs::create_dir_all(home.path().join(".claude")).unwrap(); + + let report = scan_integrations_with_home(project.path(), Some(home.path())); + + let claude = integration(&report, "claude-code"); + assert!(claude + .markers + .iter() + .any(|marker| marker.origin == MarkerOrigin::Project + && marker.path.ends_with("CLAUDE.md"))); + assert!(claude + .markers + .iter() + .any(|marker| marker.origin == MarkerOrigin::Home && marker.path.ends_with(".claude"))); + + let codex = integration(&report, "codex"); + assert!(codex.markers.iter().any( + |marker| marker.origin == MarkerOrigin::Project && marker.path.ends_with(".codex") + )); + assert!(!codex + .markers + .iter() + .any(|marker| marker.origin == MarkerOrigin::Home)); + } + #[test] fn unavailable_integrations_still_include_onboarding_next_steps() { let dir = tempdir().unwrap(); @@ -217,4 +292,12 @@ mod tests { integration.id == id && integration.status == IntegrationStatus::Detected }) } + + fn integration<'a>(report: &'a IntegrationScanReport, id: &str) -> &'a AgentIntegration { + report + .integrations + .iter() + .find(|integration| integration.id == id) + .unwrap() + } } diff --git a/crates/tree-ring-memory-cli/src/main.rs b/crates/tree-ring-memory-cli/src/main.rs index 405934c..636ccb4 100644 --- a/crates/tree-ring-memory-cli/src/main.rs +++ b/crates/tree-ring-memory-cli/src/main.rs @@ -1095,7 +1095,10 @@ fn print_integration_report( integration.name, integration.status, integration.confidence ); if !integration.markers.is_empty() { - println!(" markers: {}", integration.markers.join(", ")); + println!( + " markers: {}", + integrations::format_markers(&integration.markers) + ); } println!(" next: {}", integration.next_step); } diff --git a/crates/tree-ring-memory-cli/src/ring_mark.rs b/crates/tree-ring-memory-cli/src/ring_mark.rs index 02e13ee..ef7a577 100644 --- a/crates/tree-ring-memory-cli/src/ring_mark.rs +++ b/crates/tree-ring-memory-cli/src/ring_mark.rs @@ -65,7 +65,8 @@ impl RingMarkFrame { } } - pub fn layer_at(&self, col: usize, row: usize) -> Option { + #[cfg(test)] + fn layer_at(&self, col: usize, row: usize) -> Option { if col >= self.width || row >= self.height { return None; } @@ -181,18 +182,37 @@ struct SubPixelHit { brightness: u8, } +#[derive(Clone, Copy, Debug)] +struct SamplingGeometry { + width: usize, + height: usize, + phase: f64, +} + +#[derive(Clone, Copy, Debug)] +struct SubPixelSample { + col: usize, + row: usize, + sub_x: usize, + sub_y: usize, +} + fn sample_tree_rings( width: usize, height: usize, frame: usize, activity: RingMarkActivity, ) -> Vec { - let phase = frame as f64 * 0.22; + let geometry = SamplingGeometry { + width, + height, + phase: frame as f64 * 0.22, + }; let mut cells = Vec::with_capacity(width * height); for row in 0..height { for col in 0..width { - cells.push(sample_cell(col, row, width, height, phase, activity)); + cells.push(sample_cell(col, row, geometry, activity)); } } @@ -202,9 +222,7 @@ fn sample_tree_rings( fn sample_cell( col: usize, row: usize, - width: usize, - height: usize, - phase: f64, + geometry: SamplingGeometry, activity: RingMarkActivity, ) -> RingMarkCell { let mut pattern = 0u8; @@ -213,9 +231,13 @@ fn sample_cell( let mut hits = 0usize; for sub_y in 0..2 { for sub_x in 0..2 { - if let Some(hit) = - sample_subpixel(col, row, sub_x, sub_y, width, height, phase, activity) - { + let sample = SubPixelSample { + col, + row, + sub_x, + sub_y, + }; + if let Some(hit) = sample_subpixel(sample, geometry, activity) { pattern |= quadrant_bit(sub_x, sub_y); brightness_sum += hit.brightness as usize; layer_counts[pulse_index(hit.layer)] += 1; @@ -236,37 +258,45 @@ fn sample_cell( } fn sample_subpixel( - col: usize, - row: usize, - sub_x: usize, - sub_y: usize, - width: usize, - height: usize, - phase: f64, + sample: SubPixelSample, + geometry: SamplingGeometry, activity: RingMarkActivity, ) -> Option { - let x = normalized_x(col, sub_x, width); - let y = normalized_y(row, sub_y, height); + let x = normalized_x(sample.col, sample.sub_x, geometry.width); + let y = normalized_y(sample.row, sample.sub_y, geometry.height); let radius = (x * x + y * y).sqrt(); if radius > 1.06 { return None; } let angle = normalize_angle(y.atan2(x)); - if let Some(hit) = scar_hit(x, y, radius, phase, activity.layer(RingMarkLayer::Scar)) { + if let Some(hit) = scar_hit( + x, + y, + radius, + geometry.phase, + activity.layer(RingMarkLayer::Scar), + ) { return Some(hit); } let mut best: Option<(f64, SubPixelHit)> = None; for band in BANDS { let layer_activity = activity.layer(band.layer); - let center = animated_radius(*band, phase, layer_activity); + let center = animated_radius(*band, geometry.phase, layer_activity); let half_width = band.half_width + layer_activity * 0.016; let distance = (radius - center).abs(); if distance > half_width { continue; } - let brightness = band_brightness(*band, angle, radius, phase, layer_activity, distance); + let brightness = band_brightness( + *band, + angle, + radius, + geometry.phase, + layer_activity, + distance, + ); let hit = SubPixelHit { layer: band.layer, brightness, diff --git a/crates/tree-ring-memory-cli/src/tui/render.rs b/crates/tree-ring-memory-cli/src/tui/render.rs index d67cd93..26b0cc2 100644 --- a/crates/tree-ring-memory-cli/src/tui/render.rs +++ b/crates/tree-ring-memory-cli/src/tui/render.rs @@ -354,7 +354,13 @@ fn render_detail(frame: &mut Frame<'_>, area: Rect, app: &App) { lines.push(Line::from(truncate(integration.next_step, 140))); if !integration.markers.is_empty() { lines.push(Line::from(Span::styled( - truncate(&format!("markers: {}", integration.markers.join(", ")), 140), + truncate( + &format!( + "markers: {}", + crate::integrations::format_markers(&integration.markers) + ), + 140, + ), theme::dim(), ))); } diff --git a/crates/tree-ring-memory-cli/src/welcome.rs b/crates/tree-ring-memory-cli/src/welcome.rs index 83b4db5..b2ea0a6 100644 --- a/crates/tree-ring-memory-cli/src/welcome.rs +++ b/crates/tree-ring-memory-cli/src/welcome.rs @@ -76,8 +76,6 @@ fn print_static_welcome( ) -> Result<(), String> { if color && animated { animate_logo(color)?; - } else if color { - print!("{}", welcome_logo_frame(1, color)); } else { print!("{}", welcome_logo_frame(1, color)); } diff --git a/crates/tree-ring-memory-core/src/import_export.rs b/crates/tree-ring-memory-core/src/import_export.rs index 3f00002..df171ae 100644 --- a/crates/tree-ring-memory-core/src/import_export.rs +++ b/crates/tree-ring-memory-core/src/import_export.rs @@ -137,7 +137,7 @@ mod tests { fn encodes_header_and_event_envelopes() { let event = MemoryEvent::new("Export this memory.", "lesson").unwrap(); - let jsonl = encode_jsonl(&[event.clone()], false).unwrap(); + let jsonl = encode_jsonl(std::slice::from_ref(&event), false).unwrap(); let lines: Vec<_> = jsonl.lines().collect(); assert_eq!(lines.len(), 2); @@ -150,7 +150,7 @@ mod tests { #[test] fn decodes_export_envelope_jsonl() { let event = MemoryEvent::new("Import this memory.", "lesson").unwrap(); - let jsonl = encode_jsonl(&[event.clone()], false).unwrap(); + let jsonl = encode_jsonl(std::slice::from_ref(&event), false).unwrap(); let decoded = decode_jsonl(&jsonl).unwrap(); diff --git a/crates/tree-ring-memory-core/src/maintenance.rs b/crates/tree-ring-memory-core/src/maintenance.rs index cf54093..ecd1407 100644 --- a/crates/tree-ring-memory-core/src/maintenance.rs +++ b/crates/tree-ring-memory-core/src/maintenance.rs @@ -112,7 +112,7 @@ pub struct MaintenanceAction { pub applied: bool, } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] pub struct MaintenanceFtsReport { pub memory_rows: usize, pub fts_rows: usize, @@ -121,18 +121,6 @@ pub struct MaintenanceFtsReport { pub repaired: bool, } -impl Default for MaintenanceFtsReport { - fn default() -> Self { - Self { - memory_rows: 0, - fts_rows: 0, - missing_fts_rows: 0, - orphan_fts_rows: 0, - repaired: false, - } - } -} - #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct MaintenanceReport { pub id: String, diff --git a/crates/tree-ring-memory-core/src/models.rs b/crates/tree-ring-memory-core/src/models.rs index 88beec0..ffb6b74 100644 --- a/crates/tree-ring-memory-core/src/models.rs +++ b/crates/tree-ring-memory-core/src/models.rs @@ -83,7 +83,7 @@ pub struct MemoryLink { pub target: String, } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] pub struct MemoryReview { #[serde(default)] pub needs_review: bool, @@ -95,17 +95,6 @@ pub struct MemoryReview { pub reviewed_by: Option, } -impl Default for MemoryReview { - fn default() -> Self { - Self { - needs_review: false, - review_reason: None, - reviewed_at: None, - reviewed_by: None, - } - } -} - #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct MemoryEvent { pub id: String, diff --git a/crates/tree-ring-memory-sqlite/src/lib.rs b/crates/tree-ring-memory-sqlite/src/lib.rs index 2aeec42..2b9b633 100644 --- a/crates/tree-ring-memory-sqlite/src/lib.rs +++ b/crates/tree-ring-memory-sqlite/src/lib.rs @@ -17,6 +17,7 @@ use tree_ring_memory_core::{ const WRITE_RETRY_ATTEMPTS: usize = 8; const WRITE_RETRY_INITIAL_DELAY_MS: u64 = 5; const WRITE_RETRY_MAX_DELAY_MS: u64 = 100; +const EXISTING_ID_QUERY_CHUNK: usize = 500; #[derive(Debug, Clone)] pub struct RecallResult { @@ -220,7 +221,7 @@ impl SQLiteMemoryStore { }; let mut statement = self .connection - .prepare(&sql) + .prepare(sql) .map_err(sqlite_error_from_rusqlite)?; let rows = statement .query_map([], event_from_row) @@ -446,22 +447,32 @@ impl SQLiteMemoryStore { return Ok(report); } + let ids = events + .iter() + .map(|event| event.id.clone()) + .collect::>(); + let mut known_ids = self.existing_memory_ids(&ids)?; let mut imported_events = Vec::new(); + let mut batch_events = Vec::new(); for event in events { - if self.get(&event.id)?.is_some() { + if known_ids.contains(&event.id) { if replace_existing { - self.put(&event)?; + batch_events.push(event.clone()); imported_events.push(event); report.replaced_count += 1; } else { report.skipped_duplicate_count += 1; } } else { - self.put(&event)?; + known_ids.insert(event.id.clone()); + batch_events.push(event.clone()); imported_events.push(event); report.inserted_count += 1; } } + if !batch_events.is_empty() { + self.put_many(&batch_events)?; + } for event in imported_events { self.apply_supersedes(&event)?; } @@ -713,6 +724,32 @@ impl SQLiteMemoryStore { Ok(()) } + fn existing_memory_ids(&self, ids: &[String]) -> TreeRingResult> { + let mut existing = HashSet::new(); + for chunk in ids.chunks(EXISTING_ID_QUERY_CHUNK) { + if chunk.is_empty() { + continue; + } + let placeholders = std::iter::repeat_n("?", chunk.len()) + .collect::>() + .join(", "); + let sql = format!("SELECT id FROM memories WHERE id IN ({placeholders})"); + let mut statement = self + .connection + .prepare(&sql) + .map_err(sqlite_error_from_rusqlite)?; + let rows = statement + .query_map(params_from_iter(chunk.iter()), |row| { + row.get::<_, String>(0) + }) + .map_err(sqlite_error_from_rusqlite)?; + for row in rows { + existing.insert(row.map_err(sqlite_error_from_rusqlite)?); + } + } + Ok(existing) + } + fn fts_report(&self, repaired: bool) -> TreeRingResult { Ok(MaintenanceFtsReport { memory_rows: count_query(&self.connection, "SELECT count(*) FROM memories")?, @@ -1066,8 +1103,7 @@ fn push_in_filter( sql.push_str(column_name); sql.push_str(" IN ("); sql.push_str( - &std::iter::repeat("?") - .take(values.len()) + &std::iter::repeat_n("?", values.len()) .collect::>() .join(", "), ); @@ -1461,6 +1497,36 @@ mod tests { ); } + #[test] + fn import_jsonl_preserves_duplicate_order_within_one_file() { + let dir = tempdir().unwrap(); + let mut store = SQLiteMemoryStore::open(dir.path().join("memory.sqlite")).unwrap(); + let first = MemoryEvent::new("First duplicate import memory.", "lesson").unwrap(); + let mut second = first.clone(); + second.summary = "Second duplicate import memory.".to_string(); + let duplicate_jsonl = encode_jsonl(&[first.clone(), second.clone()], false).unwrap(); + + let skipped = store.import_jsonl(&duplicate_jsonl, false, false).unwrap(); + + assert_eq!(skipped.inserted_count, 1); + assert_eq!(skipped.replaced_count, 0); + assert_eq!(skipped.skipped_duplicate_count, 1); + assert_eq!( + store.get(&first.id).unwrap().unwrap().summary, + "First duplicate import memory." + ); + + let replaced = store.import_jsonl(&duplicate_jsonl, false, true).unwrap(); + + assert_eq!(replaced.inserted_count, 0); + assert_eq!(replaced.replaced_count, 2); + assert_eq!(replaced.skipped_duplicate_count, 0); + assert_eq!( + store.get(&first.id).unwrap().unwrap().summary, + "Second duplicate import memory." + ); + } + #[test] fn import_jsonl_reclassifies_sensitive_and_blocks_secrets() { let dir = tempdir().unwrap(); diff --git a/docs/architecture/rust-core-status.md b/docs/architecture/rust-core-status.md index 81b7d23..5858c0d 100644 --- a/docs/architecture/rust-core-status.md +++ b/docs/architecture/rust-core-status.md @@ -55,6 +55,15 @@ v0.11 Rust-native source adapters plus framework discovery. replacing DOX contracts or Revolve evidence records. - The Rust CLI and TUI include read-only agent-framework discovery for DOX, Revolve, Codex, Claude Code, Agent Zero/A0, Goose, OpenCode, Hermes, and Pi. + Integration scan output distinguishes project markers from user-home markers + so local harness readiness is explicit. +- JSONL import uses batched SQLite writes while preserving dry-run validation, + duplicate skipping, explicit replacement, secret blocking, and supersession + application. +- `scripts/certify-tree-ring.sh` provides a repeatable local certification + surface for formatting, tests, Clippy, release build, isolated installs, CLI + smokes, DOX/Revolve smokes, integration marker origins, import throughput, and + recall timing. - Project-local agent guidance is generated under `.tree-ring/AGENTS.md`, `.tree-ring/SKILL.md`, and `.tree-ring/CLI.md`. The current bridge-linking design keeps those files canonical, prefers project-level harness bridges, @@ -78,6 +87,7 @@ cargo run -p tree-ring-memory-cli -- dox sync --help cargo run -p tree-ring-memory-cli -- revolve sync --help cargo run -p tree-ring-memory-cli -- integrations scan --help cargo run --release -p tree-ring-memory-sqlite --example performance_smoke -- 1000 +sh scripts/certify-tree-ring.sh ``` ## Smoke Coverage @@ -98,12 +108,19 @@ cargo run --release -p tree-ring-memory-sqlite --example performance_smoke -- 10 conservative synthetic-workload thresholds of at least 500 inserts/sec and max recall latency of 250 ms. -Latest local smoke on July 5, 2026 with `--count 10000`: +Latest local certification run generated at `2026-07-09T02:42:24Z` with Agent +Zero plugin smoke enabled: -- Inserted 10,000 memories in 5,316.4 ms. -- Insert throughput: 1,881.0 inserts/sec. -- Recall average latency: 4.298 ms. -- Recall max latency: 6.813 ms. +- Release binary: 6,104,272 bytes. +- Project install with init: 6,032 KB. +- Global install: 5,988 KB. +- CLI import: 10,000 memories in 5 seconds, about 2,000/sec. +- 10k performance smoke: 2,059.6 inserts/sec, recall average 3.887 ms, + recall max 6.740 ms. +- 30k performance smoke: 667.8 inserts/sec, recall average 8.300 ms, recall + max 14.705 ms. +- Agent Zero plugin smoke: passed. +- Extended 50k smoke was skipped; enable it with `TREE_RING_CERT_EXTENDED=1`. ## Compatibility Rule diff --git a/scripts/certify-tree-ring.sh b/scripts/certify-tree-ring.sh new file mode 100755 index 0000000..2527263 --- /dev/null +++ b/scripts/certify-tree-ring.sh @@ -0,0 +1,276 @@ +#!/bin/sh +set -eu + +ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) +OUT_DIR="$ROOT/target/tree-ring-certification" +TMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/tree-ring-cert.XXXXXX") +BIN="$ROOT/target/release/tree-ring" +IMPORT_COUNT=${TREE_RING_CERT_IMPORT_COUNT:-10000} +EXTENDED=${TREE_RING_CERT_EXTENDED:-0} +AGENT_ZERO_ROOT=${TREE_RING_AGENT_ZERO_ROOT:-} + +cleanup() { + rm -rf "$TMP_DIR" +} +trap cleanup EXIT INT TERM + +log() { + printf '==> %s\n' "$*" +} + +fail() { + printf 'Tree Ring certification failed: %s\n' "$*" >&2 + exit 1 +} + +require_file() { + [ -f "$1" ] || fail "missing expected file: $1" +} + +run() { + log "$*" + "$@" +} + +run_logged() { + log "$*" + printf '==> %s\n' "$*" >> "$LOG" + "$@" >> "$LOG" 2>&1 || fail "command failed: $*" +} + +json_escape() { + printf '%s' "$1" | sed 's/\\/\\\\/g; s/"/\\"/g' +} + +size_bytes() { + wc -c < "$1" | tr -d ' ' +} + +size_kb_path() { + du -sk "$1" | awk '{print $1}' +} + +now_utc() { + date -u '+%Y-%m-%dT%H:%M:%SZ' +} + +generate_import_jsonl() { + output=$1 + count=$2 + index=0 + : > "$output" + while [ "$index" -lt "$count" ]; do + id=$(printf 'mem_cert_%06d' "$index") + if [ $((index % 50)) -eq 0 ]; then + ring="scar" + event_type="warning" + summary="Avoid stale deployment rollback cache failure $index." + tags='["deployment","cache"]' + else + ring="cambium" + event_type="lesson" + summary="Implementation note $index for subsystem $((index % 17))." + tags='["implementation"]' + fi + printf '{"id":"%s","created_at":"2026-07-05T00:00:00+00:00","updated_at":"2026-07-05T00:00:00+00:00","project":"certification","agent_profile":null,"scope":"project","ring":"%s","event_type":"%s","summary":"%s","details":"","source":{"type":"manual","ref":"certification","quote":""},"tags":%s,"salience":0.5,"confidence":0.5,"sensitivity":"normal","retention":"normal","expires_at":null,"supersedes":[],"superseded_by":null,"links":[],"review":{"needs_review":false,"review_reason":null,"reviewed_at":null,"reviewed_by":null}}\n' \ + "$id" "$ring" "$event_type" "$summary" "$tags" >> "$output" + index=$((index + 1)) + done +} + +extract_metrics_json() { + sed -n 's/^METRICS_JSON=//p' "$1" | tail -n 1 +} + +mkdir -p "$OUT_DIR" +SUMMARY="$OUT_DIR/summary.md" +METRICS="$OUT_DIR/metrics.json" +LOG="$OUT_DIR/certification.log" +: > "$LOG" + +log "certification output: $OUT_DIR" | tee -a "$LOG" + +run_logged cargo fmt --check +run_logged cargo test --locked +run_logged cargo clippy --locked --all-targets +run_logged cargo build --release --locked + +require_file "$BIN" +binary_bytes=$(size_bytes "$BIN") +[ "$binary_bytes" -le 8388608 ] || fail "release binary exceeds 8 MB: $binary_bytes bytes" + +project_root="$TMP_DIR/project-install" +mkdir -p "$project_root" +(cd "$project_root" && sh "$ROOT/install.sh" --project --init --source "$ROOT" --no-animation) \ + > "$OUT_DIR/project-install.out" 2> "$OUT_DIR/project-install.err" +project_install_kb=$(size_kb_path "$project_root/.tree-ring") +[ "$project_install_kb" -le 8192 ] || fail "project install exceeds 8 MB: ${project_install_kb}KB" +"$project_root/.tree-ring/bin/tree-ring" --root "$project_root/.tree-ring" --json recall "startup warnings" \ + > "$OUT_DIR/project-install-recall.json" + +global_root="$TMP_DIR/global-install" +mkdir -p "$global_root/home" +HOME="$global_root/home" SHELL=/bin/zsh sh "$ROOT/install.sh" --global --source "$ROOT" --no-onboarding \ + > "$OUT_DIR/global-install.out" 2> "$OUT_DIR/global-install.err" +global_install_kb=$(size_kb_path "$global_root/home/.local") +"$global_root/home/.local/bin/tree-ring" --version > "$OUT_DIR/global-version.out" +grep -F 'Tree Ring Memory PATH' "$global_root/home/.zshrc" > /dev/null \ + || fail "global install did not write PATH block" + +smoke_root="$TMP_DIR/cli-smoke/.tree-ring" +"$BIN" --root "$smoke_root" --json init > "$OUT_DIR/cli-init.json" +"$BIN" --root "$smoke_root" --json remember \ + "Use project-scoped recall before changing release behavior." \ + --event-type lesson --scope project --project certification --tag release \ + > "$OUT_DIR/cli-remember.json" +"$BIN" --root "$smoke_root" --json evidence \ + "Recall found release workflow guardrail in certification." \ + --outcome promoted --evidence-ref certification/harness --project certification --score 0.91 \ + > "$OUT_DIR/cli-evidence.json" +"$BIN" --root "$smoke_root" --json recall "release workflow guardrail" --project certification \ + > "$OUT_DIR/cli-recall.json" +"$BIN" --root "$smoke_root" --json audit --audit-type all > "$OUT_DIR/cli-audit.json" +grep -F 'release workflow guardrail' "$OUT_DIR/cli-recall.json" > /dev/null \ + || fail "CLI recall smoke did not return expected memory" + +adapter_root="$TMP_DIR/adapters" +mkdir -p "$adapter_root/sub" "$adapter_root/revolve" +printf '# Project Contract\n\n## Safety\nAlways run tests before shipping.\n' > "$adapter_root/AGENTS.md" +printf '# Nested Contract\n\n## Runtime\nUse local-first memory only.\n' > "$adapter_root/sub/AGENTS.md" +printf '# Eval\n\nOutcome: promoted\nEvidence: tests/run-42\nSummary: Recall bridge worked in certification.\n' \ + > "$adapter_root/revolve/result.md" +adapter_memory_root="$adapter_root/.tree-ring" +"$BIN" --root "$adapter_memory_root" --json dox sync --source-root "$adapter_root" --dry-run \ + > "$OUT_DIR/dox-dry-run.json" +"$BIN" --root "$adapter_memory_root" --json dox sync --source-root "$adapter_root" \ + > "$OUT_DIR/dox-write.json" +"$BIN" --root "$adapter_memory_root" --json recall "run tests" > "$OUT_DIR/dox-recall.json" +"$BIN" --root "$adapter_memory_root" --json revolve sync --source-root "$adapter_root/revolve" --dry-run \ + > "$OUT_DIR/revolve-dry-run.json" +"$BIN" --root "$adapter_memory_root" --json revolve sync --source-root "$adapter_root/revolve" \ + > "$OUT_DIR/revolve-write.json" +"$BIN" --root "$adapter_memory_root" --json recall "promoted evidence" > "$OUT_DIR/revolve-recall.json" +grep -F 'Always run tests' "$OUT_DIR/dox-recall.json" > /dev/null \ + || fail "DOX recall smoke did not return expected memory" +grep -F 'promoted evidence' "$OUT_DIR/revolve-recall.json" > /dev/null \ + || fail "Revolve recall smoke did not return expected memory" + +scan_root="$TMP_DIR/integration-scan" +scan_home="$TMP_DIR/integration-home" +mkdir -p "$scan_root/.codex" "$scan_root/.claude" "$scan_root/usr/plugins" \ + "$scan_root/revolve" "$scan_root/.opencode" "$scan_root/.goose" "$scan_home/.claude" +printf '# Agent contract\n' > "$scan_root/AGENTS.md" +printf '# Claude instructions\n' > "$scan_root/CLAUDE.md" +HOME="$scan_home" "$BIN" --json integrations scan --source-root "$scan_root" \ + > "$OUT_DIR/integrations-scan.json" +grep -F '"origin":"project"' "$OUT_DIR/integrations-scan.json" > /dev/null \ + || fail "integration scan did not include project-origin markers" +grep -F '"origin":"home"' "$OUT_DIR/integrations-scan.json" > /dev/null \ + || fail "integration scan did not include home-origin markers" + +import_root="$TMP_DIR/import-bench/.tree-ring" +import_jsonl="$TMP_DIR/import-bench.jsonl" +generate_import_jsonl "$import_jsonl" "$IMPORT_COUNT" +"$BIN" --root "$import_root" --json init > /dev/null +import_start=$(date +%s) +"$BIN" --root "$import_root" --json import "$import_jsonl" > "$OUT_DIR/import-bench.json" +import_end=$(date +%s) +import_seconds=$((import_end - import_start)) +[ "$import_seconds" -le 0 ] && import_seconds=1 +import_rate=$((IMPORT_COUNT / import_seconds)) +[ "$import_rate" -ge 1500 ] || fail "import throughput below 1500 events/s: $import_rate" + +perf_10k_out="$OUT_DIR/performance-10000.out" +perf_30k_out="$OUT_DIR/performance-30000.out" +run cargo run --release -p tree-ring-memory-sqlite --example performance_smoke -- 10000 \ + > "$perf_10k_out" 2>&1 +run cargo run --release -p tree-ring-memory-sqlite --example performance_smoke -- 30000 \ + > "$perf_30k_out" 2>&1 +perf_10k_json=$(extract_metrics_json "$perf_10k_out") +perf_30k_json=$(extract_metrics_json "$perf_30k_out") +[ -n "$perf_10k_json" ] || fail "missing 10k performance metrics" +[ -n "$perf_30k_json" ] || fail "missing 30k performance metrics" + +perf_50k_json=null +if [ "$EXTENDED" = "1" ]; then + perf_50k_out="$OUT_DIR/performance-50000.out" + run cargo run --release -p tree-ring-memory-sqlite --example performance_smoke -- 50000 \ + > "$perf_50k_out" 2>&1 + perf_50k_json=$(extract_metrics_json "$perf_50k_out") + [ -n "$perf_50k_json" ] || fail "missing 50k performance metrics" +fi + +agent_zero_status='"skipped"' +agent_zero_note='"TREE_RING_AGENT_ZERO_ROOT not set"' +if [ -n "$AGENT_ZERO_ROOT" ]; then + if [ ! -d "$AGENT_ZERO_ROOT/usr/plugins/tree_ring_memory" ]; then + fail "Agent Zero plugin checkout not found: $AGENT_ZERO_ROOT" + fi + if command -v node >/dev/null 2>&1; then + node --check "$AGENT_ZERO_ROOT/usr/plugins/tree_ring_memory/webui/memory-store.js" \ + > "$OUT_DIR/agent-zero-node-check.out" 2>&1 + else + fail "node is required for Agent Zero plugin check" + fi + if command -v python3 >/dev/null 2>&1; then + (cd "$AGENT_ZERO_ROOT" && PYTHONPATH="$AGENT_ZERO_ROOT" pytest -q -p no:cacheprovider usr/plugins/tree_ring_memory/tests) \ + > "$OUT_DIR/agent-zero-pytest.out" 2>&1 + az_data="$TMP_DIR/agent-zero-data" + (cd "$AGENT_ZERO_ROOT" && TREE_RING_MEMORY_DATA_DIR="$az_data" PYTHONPATH="$AGENT_ZERO_ROOT" python3 usr/plugins/tree_ring_memory/execute.py status) \ + > "$OUT_DIR/agent-zero-status.json" + (cd "$AGENT_ZERO_ROOT" && TREE_RING_MEMORY_DATA_DIR="$az_data" PYTHONPATH="$AGENT_ZERO_ROOT" python3 usr/plugins/tree_ring_memory/execute.py audit) \ + > "$OUT_DIR/agent-zero-audit.json" + (cd "$AGENT_ZERO_ROOT" && TREE_RING_MEMORY_DATA_DIR="$az_data" PYTHONPATH="$AGENT_ZERO_ROOT" python3 usr/plugins/tree_ring_memory/execute.py export) \ + > "$OUT_DIR/agent-zero-export.json" + else + fail "python3 is required for Agent Zero plugin check" + fi + agent_zero_status='"passed"' + agent_zero_note='"Agent Zero plugin smoke passed"' +fi + +created_at=$(now_utc) +cat > "$METRICS" < "$SUMMARY" < Date: Wed, 8 Jul 2026 23:11:31 -0400 Subject: [PATCH 4/4] Address certification review feedback --- crates/tree-ring-memory-cli/src/integrations.rs | 12 ++++-------- crates/tree-ring-memory-sqlite/src/lib.rs | 13 +++++-------- 2 files changed, 9 insertions(+), 16 deletions(-) diff --git a/crates/tree-ring-memory-cli/src/integrations.rs b/crates/tree-ring-memory-cli/src/integrations.rs index 8edf143..5031686 100644 --- a/crates/tree-ring-memory-cli/src/integrations.rs +++ b/crates/tree-ring-memory-cli/src/integrations.rs @@ -19,17 +19,17 @@ pub struct AgentIntegration { pub next_step: &'static str, } -#[derive(Debug, Clone, PartialEq, Eq, Serialize)] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize)] pub struct IntegrationMarker { pub path: String, pub origin: MarkerOrigin, } -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize)] #[serde(rename_all = "snake_case")] pub enum MarkerOrigin { - Project, Home, + Project, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)] @@ -193,11 +193,7 @@ fn detect( } } } - markers.sort_by(|left, right| { - left.path - .cmp(&right.path) - .then_with(|| left.origin.as_str().cmp(right.origin.as_str())) - }); + markers.sort(); markers.dedup(); let confidence = if markers.is_empty() { 0.0 diff --git a/crates/tree-ring-memory-sqlite/src/lib.rs b/crates/tree-ring-memory-sqlite/src/lib.rs index 2b9b633..f640bdb 100644 --- a/crates/tree-ring-memory-sqlite/src/lib.rs +++ b/crates/tree-ring-memory-sqlite/src/lib.rs @@ -452,29 +452,26 @@ impl SQLiteMemoryStore { .map(|event| event.id.clone()) .collect::>(); let mut known_ids = self.existing_memory_ids(&ids)?; - let mut imported_events = Vec::new(); let mut batch_events = Vec::new(); for event in events { if known_ids.contains(&event.id) { if replace_existing { - batch_events.push(event.clone()); - imported_events.push(event); + batch_events.push(event); report.replaced_count += 1; } else { report.skipped_duplicate_count += 1; } } else { known_ids.insert(event.id.clone()); - batch_events.push(event.clone()); - imported_events.push(event); + batch_events.push(event); report.inserted_count += 1; } } if !batch_events.is_empty() { self.put_many(&batch_events)?; - } - for event in imported_events { - self.apply_supersedes(&event)?; + for event in &batch_events { + self.apply_supersedes(event)?; + } } Ok(report) }