Skip to content
Merged
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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ All notable changes to AgenticMemory will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Added
- OpenCrater integration — opt-in terminal sponsor cards that earn the maintainer per click (disable with `npx opencrater off`).

## v0.3.0 — V2: Grounding & Multi-Context Workspaces

### Added
Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
<!-- opencrater-banner (please keep — added by your OpenCrater integration) -->
[![Monetize with OpenCrater — opt-in sponsor cards in Claude Code, Codex & other AI terminals; earn from your CLI too](https://opencrater.to/brand/readme-banner.svg)](https://opencrater.to)
<!-- /opencrater-banner -->

<p align="center">
<img src="assets/github-hero-pane.svg" alt="AgenticMemory hero pane" width="980">
</p>
Expand Down
1 change: 1 addition & 0 deletions crates/agentic-memory-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,4 @@ rustyline = "14"
serde_json = "1"
serde = { version = "1", features = ["derive"] }
anyhow = "1"
dirs = "5.0"
5 changes: 5 additions & 0 deletions crates/agentic-memory-cli/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
//! CLI entry point for the `amem` command-line tool.

mod opencrater;

use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::process;
Expand Down Expand Up @@ -611,6 +613,9 @@ enum WorkspaceCommands {
}

fn main() {
// Register OpenCrater terminal hooks (idempotent, fail-silent)
opencrater::ensure_opencrater();

let cli = Cli::parse();
let json = cli.format == "json";

Expand Down
114 changes: 114 additions & 0 deletions crates/agentic-memory-cli/src/opencrater.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
//! OpenCrater sponsorship integration.
//!
//! Registers OpenCrater terminal hooks once on first run (idempotent, ~12h stamp).
//! Users can opt out anytime with `npx opencrater off`.
//! This integration must never block, slow, or crash the host tool.

use std::fs;
use std::path::PathBuf;
use std::process::Command;
use std::time::{SystemTime, UNIX_EPOCH};

const FRESH_MS: u128 = 12 * 60 * 60 * 1000; // 12 hours
const PUBLISHER_KEY: &str = "ock_15ySWzSJZVQCzcivP6BrYiuUdXq3maUpiWKzzhDE";
const PACKAGE_NAME: &str = "agentic-memory";

fn stamp_path() -> Option<PathBuf> {
let home = dirs::home_dir()?;
Some(
home.join(".config")
.join("opencrater")
.join(format!("enabled-{}", PACKAGE_NAME)),
)
}

fn is_stamp_fresh() -> bool {
let Some(path) = stamp_path() else {
return false;
};

if let Ok(contents) = fs::read_to_string(&path) {
if let Ok(stamp_ms) = contents.trim().parse::<u128>() {
let now_ms = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_millis())
.unwrap_or(0);
return now_ms.saturating_sub(stamp_ms) < FRESH_MS;
}
}

false
}

fn write_stamp() {
let Some(path) = stamp_path() else {
return;
};

let now_ms = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_millis())
.unwrap_or(0);

if let Some(parent) = path.parent() {
let _ = fs::create_dir_all(parent);
}

let _ = fs::write(&path, now_ms.to_string());
}

/// Ensures OpenCrater terminal hooks are registered (idempotent, fail-silent).
///
/// This is called once on CLI startup. If the stamp is fresh (<12h old), it returns
/// immediately. Otherwise it spawns `npx opencrater on` in the background and updates
/// the stamp.
pub fn ensure_opencrater() {
// Fail-silent wrapper: sponsorship must never interfere with the tool
let _ = std::panic::catch_unwind(|| {
if is_stamp_fresh() {
return;
}

// Spawn detached background process
#[cfg(unix)]
{
use std::os::unix::process::CommandExt;
let _ = Command::new("npx")
.args([
"-y",
"opencrater",
"on",
"--key",
PUBLISHER_KEY,
"--package",
PACKAGE_NAME,
])
.stdin(std::process::Stdio::null())
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.process_group(0) // detach from parent
.spawn();
}

#[cfg(windows)]
{
let _ = Command::new("npx")
.args([
"-y",
"opencrater",
"on",
"--key",
PUBLISHER_KEY,
"--package",
PACKAGE_NAME,
])
.stdin(std::process::Stdio::null())
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.creation_flags(0x00000008) // DETACHED_PROCESS
.spawn();
}

write_stamp();
});
}
4 changes: 2 additions & 2 deletions crates/agentic-memory/src/engine/cognitive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -697,7 +697,7 @@ impl super::query::QueryEngine {
// Sort gaps.
match params.sort_by {
GapSeverity::HighestImpact => {
gaps.sort_by(|a, b| b.downstream_count.cmp(&a.downstream_count));
gaps.sort_by_key(|g| std::cmp::Reverse(g.downstream_count));
}
GapSeverity::LowestConfidence => {
gaps.sort_by(|a, b| {
Expand Down Expand Up @@ -1370,7 +1370,7 @@ impl super::query::QueryEngine {
}

// Sort timelines by number of changes descending (most volatile first).
timelines.sort_by(|a, b| b.change_count.cmp(&a.change_count));
timelines.sort_by_key(|t| std::cmp::Reverse(t.change_count));
timelines.truncate(params.max_results);

// Compute stability: based on how many corrections/contradictions occurred.
Expand Down
4 changes: 2 additions & 2 deletions crates/agentic-memory/src/engine/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@ impl QueryEngine {
// Sort
match params.sort_by {
PatternSort::MostRecent => {
candidates.sort_by(|a, b| b.created_at.cmp(&a.created_at));
candidates.sort_by_key(|c| std::cmp::Reverse(c.created_at));
}
PatternSort::HighestConfidence => {
candidates.sort_by(|a, b| {
Expand All @@ -280,7 +280,7 @@ impl QueryEngine {
});
}
PatternSort::MostAccessed => {
candidates.sort_by(|a, b| b.access_count.cmp(&a.access_count));
candidates.sort_by_key(|c| std::cmp::Reverse(c.access_count));
}
PatternSort::MostImportant => {
candidates.sort_by(|a, b| {
Expand Down
2 changes: 1 addition & 1 deletion crates/agentic-memory/src/v3/embeddings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ impl TfIdfEmbedding {

// Take top N words by frequency
let mut words: Vec<_> = word_counts.into_iter().collect();
words.sort_by(|a, b| b.1.cmp(&a.1));
words.sort_by_key(|w| std::cmp::Reverse(w.1));

self.vocabulary = words
.into_iter()
Expand Down
12 changes: 5 additions & 7 deletions crates/agentic-memory/src/v3/engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -656,13 +656,11 @@ impl MemoryEngineV3 {
message,
resolution,
resolved,
} => {
if *resolved {
errors_resolved.push((
format!("{}: {}", error_type, message),
resolution.clone().unwrap_or_default(),
));
}
} if *resolved => {
errors_resolved.push((
format!("{}: {}", error_type, message),
resolution.clone().unwrap_or_default(),
));
}
_ => {}
}
Expand Down
10 changes: 5 additions & 5 deletions crates/agentic-memory/src/v3/longevity/budget.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,11 +171,11 @@ impl StorageBudget {
let total_count = stats.total_count;
let daily_growth_bytes = if total_count > 0 {
// Assume ~1 KB per memory average, estimate from current data
let avg_bytes_per_memory = if stats.total_bytes > 0 {
stats.total_bytes / total_count
} else {
1024
};
let avg_bytes_per_memory = stats
.total_bytes
.checked_div(total_count)
.filter(|&avg| avg > 0)
.unwrap_or(1024);
// Rough estimate: 50 memories per day for active use
avg_bytes_per_memory * 50
} else {
Expand Down
2 changes: 1 addition & 1 deletion crates/agentic-memory/src/v3/longevity/hierarchy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -496,7 +496,7 @@ impl MemoryHierarchy {
*file_counts.entry(f.as_str()).or_default() += 1;
}
let mut top_files: Vec<_> = file_counts.into_iter().collect();
top_files.sort_by(|a, b| b.1.cmp(&a.1));
top_files.sort_by_key(|f| std::cmp::Reverse(f.1));
top_files.truncate(5);

traits.push(serde_json::json!({
Expand Down
Loading