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
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,25 @@
# Changelog

## [0.3.0] - 2026-02-03

### Changed
- **BREAKING**: Simplified API from 5 tools to 4 tools (removed `connections`)
- **BREAKING**: Simplified `store` from 9 parameters to 2 (`content`, `scope`)
- **BREAKING**: Simplified `recall` from 4 parameters to 2 (`query`, `limit`)
- **BREAKING**: Simplified `list` from 3 parameters to 2 (`limit`, `scope`)
- **BREAKING**: Removed fields from Item: `title`, `tags`, `source`, `metadata`, `expires_at`

### Added
- Auto-migration: existing databases automatically migrated to new schema on startup
- Schema versioning for future migrations

### Removed
- `connections` tool (graph is now internal infrastructure)
- Tag-based filtering (semantic search handles categorization)
- Item expiration (simplified lifecycle)
- Replace functionality (use `forget` + `store`)
- Related item linking on store (handled by auto-consolidation)

## [0.2.3] - 2026-02-03

### Added
Expand Down
32 changes: 18 additions & 14 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,12 @@ Sediment is a semantic memory system for AI agents, running as an MCP (Model Con

- **`mod.rs`** - Module exports
- **`server.rs`** - stdio JSON-RPC server with shared embedder, graph path, consolidation semaphore
- **`tools.rs`** - 5 MCP tools: `store`, `recall`, `list`, `forget`, `connections`
- **`tools.rs`** - 4 MCP tools: `store`, `recall`, `list`, `forget`
- **`protocol.rs`** - MCP protocol types and JSON-RPC handling

### Data Flow

1. **Store**: Content → Embedder (384-dim vector) → LanceDB storage → Graph node creation → Provenance metadata injection → Conflict detection → Consolidation queue → Auto-tag inference
1. **Store**: Content → Embedder (384-dim vector) → LanceDB storage → Graph node creation → Conflict detection → Consolidation queue
2. **Chunking**: Long content (>1000 chars) → Type-aware splitting → Individual chunk embeddings
3. **Recall**: Query → Embedder → Vector similarity search → Project boosting → Decay scoring → Trust-weighted re-ranking → Graph backfill → 1-hop graph expansion → Co-access suggestions → Cross-project flagging → Background consolidation + co-access recording
4. **Consolidation** (background): Queue candidates → >=0.95 similarity: merge (delete old, transfer edges, SUPERSEDES edge) → 0.85-0.95: link (RELATED edge)
Expand All @@ -74,33 +74,37 @@ Sediment is a semantic memory system for AI agents, running as an MCP (Model Con
- **Memory decay scoring**: Recall results re-ranked using freshness (30-day half-life) and access frequency (log-scaled). Tracked in SQLite sidecar since LanceDB is append-oriented.
- **Trust-weighted scoring**: `final_score = similarity * freshness * frequency * trust_bonus` where `trust_bonus = 1.0 + 0.05*ln(1+validation_count) + 0.02*edge_count`
- **Non-blocking intelligence**: All background tasks (consolidation, co-access tracking, clustering) run as fire-and-forget `tokio::spawn` tasks. Tool responses return immediately. `Semaphore(1)` prevents concurrent consolidation.
- **Auto-provenance**: Every store injects `metadata._provenance` with version, project_path, and supersedes chain
- **Lazy graph backfill**: Pre-existing items get graph nodes when they appear in recall results
- **Auto-tagging**: Items stored without tags inherit `auto:` prefixed tags from 2+ similar items sharing the same tag
- **Auto-migration**: Database schema is automatically migrated on startup when upgrading from older versions

## MCP Tools Reference

The 5-tool API is defined in `src/mcp/tools.rs`:
The 4-tool API is defined in `src/mcp/tools.rs`:

| Tool | Purpose |
|------|---------|
| `store` | Store content with optional title, tags, metadata, expiration, scope, replace, related |
| `recall` | Semantic search with decay scoring, trust weighting, graph expansion, co-access suggestions, cross-project flagging |
| `list` | List items by scope (project/global/all) with tag filtering |
| `store` | Store content with optional scope (project/global) |
| `recall` | Semantic search with decay scoring, trust weighting, graph expansion |
| `list` | List items by scope (project/global/all) |
| `forget` | Delete item by ID (removes from LanceDB and graph) |
| `connections` | Show full relationship graph for an item (RELATED, SUPERSEDES, CO_ACCESSED edges with content previews) |

### Store Parameters
- `content` (required), `title`, `tags`, `source`, `metadata`, `expires_at`, `scope` (project/global), `replace` (atomically replace item by ID), `related` (array of item IDs to link in graph)
- `content` (required) — The content to store
- `scope` (optional, default: "project") — Where to store: "project" or "global"

### Recall Parameters
- `query` (required) — Semantic search query
- `limit` (optional, default: 5) — Maximum number of results

### List Parameters
- `limit` (optional, default: 10) — Maximum number of results
- `scope` (optional, default: "project") — Which items to list: "project", "global", or "all"

### Recall Response Fields
- `results[]` — standard results with `similarity`, `related_ids`, optional `cross_project` + `project_path` flags
- `results[]` — standard results with `similarity`, `related_ids`, optional `cross_project` flag
- `graph_expanded[]` — 1-hop neighbors from graph not in original results (marked `graph_expanded: true`)
- `suggested[]` — items frequently co-recalled with top results (co-access count >= 3)

### Connections Response
- `item_id`, `connections[]` — each with `id`, `type` (related/supersedes/co_accessed), `strength`, optional `count`, `content_preview`

## SQLite Schema (access.db)

```sql
Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "sediment-mcp"
version = "0.2.4"
version = "0.3.0"
edition = "2024"
repository = "https://github.com/rendro/sediment"
homepage = "https://github.com/rendro/sediment"
Expand Down
27 changes: 13 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Combines vector search, a relationship graph, and access tracking into a unified

- **Single binary, zero config** — no Docker, no Postgres, no Qdrant. Just `sediment`.
- **Sub-16ms recall** — local embeddings and vector search at 100 items, no network round-trips.
- **5-tool focused API** — `store`, `recall`, `list`, `forget`, `connections`. That's it.
- **4-tool focused API** — `store`, `recall`, `list`, `forget`. That's it.
- **Works everywhere** — macOS (Intel + ARM), Linux x86_64. All data stays on your machine.

### Comparison
Expand All @@ -21,7 +21,7 @@ Combines vector search, a relationship graph, and access tracking into a unified
|---|---|---|---|
| Install | Single binary | Docker + Postgres + Qdrant | Python + pip |
| Dependencies | None | 3 services | Python runtime + deps |
| Tools | 5 | 10+ | 24 |
| Tools | 4 | 10+ | 24 |
| Embeddings | Local (all-MiniLM-L6-v2) | API-dependent | API-dependent |
| Graph features | Built-in | No | No |
| Memory decay | Built-in | No | No |
Expand Down Expand Up @@ -131,13 +131,12 @@ Go to **Settings > Tools > AI Assistant > MCP Servers**, click **+**, and add:

## Tools

| Tool | Description |
|------|-------------|
| `store` | Save content with optional title, tags, source, metadata, expiration, scope, replace, and related item links |
| `recall` | Search memories by semantic similarity with decay scoring, trust weighting, graph expansion, and co-access suggestions |
| `list` | List stored items by scope (project/global/all) with tag filtering |
| `forget` | Delete an item by ID (removes from vector store and graph) |
| `connections` | Show relationship graph for an item (related, supersedes, co-accessed edges) |
| Tool | Parameters | Description |
|------|------------|-------------|
| `store` | `content`, `scope?` | Save content to memory |
| `recall` | `query`, `limit?` | Search by semantic similarity |
| `list` | `limit?`, `scope?` | List stored items |
| `forget` | `id` | Delete an item by ID |

## CLI

Expand All @@ -164,19 +163,19 @@ All local, embedded, zero config:
- **Project scoping**: Automatic context isolation between projects. Same-project items get a similarity boost.
- **Relationship graph**: Items linked via RELATED, SUPERSEDES, and CO_ACCESSED edges. Recall expands results with 1-hop graph neighbors and co-access suggestions.
- **Background consolidation**: Near-duplicates (≥0.95 similarity) auto-merged; similar items (0.85–0.95) linked.
- **Auto-tagging**: Items without tags inherit tags from similar existing items.
- **Type-aware chunking**: Intelligent splitting for markdown, code, JSON, YAML, and plain text.
- **Conflict detection**: Items with ≥0.85 similarity flagged on store.
- **Cross-project recall**: Results from other projects flagged with provenance metadata.
- **Cross-project recall**: Results from other projects flagged.
- **Local embeddings**: all-MiniLM-L6-v2 via Candle (384-dim vectors, no API keys).
- **Model integrity**: SHA-256 verification of all model files on every load, pinned to a specific revision.
- **Auto-migration**: Database schema automatically migrated when upgrading from older versions.

### Security

- **Input bounds**: Content (1MB), queries (100KB), JSON-RPC lines (10MB), tags (50×200B), metadata (100KB).
- **Rate limiting**: 60 tool calls per minute.
- **Input bounds**: Content (1MB), queries (10KB), JSON-RPC lines (10MB).
- **Rate limiting**: 600 tool calls per minute.
- **SQL injection prevention**: Sanitized filter expressions for LanceDB; parameterized queries for SQLite.
- **Cross-project access control**: Replace, forget, and connections enforce project isolation. Cross-project content is redacted in recall results.
- **Cross-project access control**: Forget enforces project isolation. Cross-project content is flagged in recall results.
- **Error sanitization**: Internal errors logged to stderr; only generic messages returned to MCP clients.
- **Retry with backoff**: Transient failures retried with exponential backoff (3 attempts, 100ms–2s).

Expand Down
8 changes: 3 additions & 5 deletions benches/recall_bench.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,7 @@ async fn seed_database(n: usize, embedder: Arc<Embedder>) -> SeededDb {

for i in 0..n {
let content = generate_content(i);
let item = Item::new(&content)
.with_tags(vec![format!("bench-tag-{}", i % 5)])
.with_project_id("bench-project");
let item = Item::new(&content).with_project_id("bench-project");

let result = db.store_item(item).await.expect("store item");
let id = result.id.clone();
Expand Down Expand Up @@ -174,7 +172,7 @@ fn recall_benchmarks(c: &mut Criterion) {
enable_decay_scoring: false,
enable_background_tasks: false,
};
let filters = ItemFilters::new().with_min_similarity(0.3);
let filters = ItemFilters::new();
let _ = recall_pipeline(
&mut db, &tracker, &graph, query, 5, filters, &config,
)
Expand Down Expand Up @@ -210,7 +208,7 @@ fn recall_benchmarks(c: &mut Criterion) {
let tracker = AccessTracker::open(&access_path).unwrap();
let graph = GraphStore::open(&access_path).unwrap();
let config = RecallConfig::default();
let filters = ItemFilters::new().with_min_similarity(0.3);
let filters = ItemFilters::new();
let _ = recall_pipeline(
&mut db, &tracker, &graph, query, 5, filters, &config,
)
Expand Down
21 changes: 6 additions & 15 deletions src/consolidation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -315,21 +315,12 @@ async fn process_candidate(
tracing::warn!("add_related_edge failed: {}", e);
}

// Soft-delete: mark item as expired instead of hard-deleting.
// This allows recovery; expired items are excluded from search
// results by default but remain in the database.
let past = chrono::Utc::now() - chrono::Duration::seconds(1);
if let Err(e) = db.expire_item(&remove.id, past).await {
// expire_item is delete-then-insert, so a failure may mean the
// item was already deleted. Only attempt hard delete if the item
// still exists to avoid double-deleting.
warn!("expire_item failed ({}), checking if item still exists", e);
if let Ok(Some(_)) = db.get_item(&remove.id).await {
db.delete_item(&remove.id).await?;
if let Err(e2) = graph.remove_node(&remove.id) {
tracing::warn!("remove_node failed: {}", e2);
}
}
// Delete the duplicate item
if let Err(e) = db.delete_item(&remove.id).await {
warn!("delete_item failed: {}", e);
}
if let Err(e) = graph.remove_node(&remove.id) {
tracing::warn!("remove_node failed: {}", e);
}

Ok("merged".to_string())
Expand Down
Loading