Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
132 commits
Select commit Hold shift + click to select a range
2ccc89c
fix(api): remove unused namespace module
senamakel Oct 4, 2026
9b505a4
fix(tinymemory-api): correct test assertion for namespace lookup
senamakel Oct 4, 2026
1c94c56
fix(consolidate): handle empty write batch in consolidation
senamakel Oct 4, 2026
42a4079
test(consolidate): add tests for memory consolidation logic
senamakel Oct 4, 2026
757b6e9
feat(api): add store_with and consolidate to MemoryEngine
senamakel Oct 4, 2026
e67577b
feat(api): add consolidation field to engine descriptors
senamakel Oct 4, 2026
1728e29
fix(conformance): update reference implementation to match engine beh…
senamakel Oct 4, 2026
c7d29f1
chore(tinymemory-api): add mod_tests module for explore
senamakel Oct 4, 2026
cee37e9
fix(conformance): handle empty input in distil reference implementation
senamakel Oct 4, 2026
408950b
fix(conformance): handle empty reference in distil
senamakel Oct 4, 2026
b298ff5
fix(conformance): add missing module documentation
senamakel Oct 4, 2026
8230354
fix(conformance): add lifecycle checks to conformance test suite
senamakel Oct 4, 2026
47981cc
chore(conformance): fix indentation in namespace test comment
senamakel Oct 4, 2026
84b6099
feat(tests): add consolidation conformance tests
senamakel Oct 4, 2026
833468b
fix(engine): handle missing descriptor in store lookup
senamakel Oct 4, 2026
3e86a44
fix(engine): handle empty scope in consolidation
senamakel Oct 4, 2026
c7db7b8
fix(consolidate): handle empty consolidation set gracefully
senamakel Oct 4, 2026
cc75b77
fix(cortex/testing): remove duplicate route registration
senamakel Oct 4, 2026
6384a32
fix(cortex): correct test module path for direct and consolidate tests
senamakel Oct 4, 2026
1a6cfb8
test(cortex-engine): clone engine before store_with in test
senamakel Oct 4, 2026
631def4
feat(cortex): add source scope to namespace documentation and test
senamakel Oct 4, 2026
a0a90fa
fix(render): handle empty recall context in template rendering
senamakel Oct 4, 2026
e6649ec
fix(render): handle empty recall results gracefully
senamakel Oct 4, 2026
45ee82c
fix(recall): correct test assertion for render output
senamakel Oct 4, 2026
0c6d347
fix(recall): handle empty gather results gracefully
senamakel Oct 4, 2026
2ee7e93
fix(recall): handle empty gather set without panic
senamakel Oct 4, 2026
500f147
fix(context): handle missing compile context gracefully
senamakel Oct 4, 2026
c1a82be
feat(recall): add concurrent holistic recall module
senamakel Oct 4, 2026
6102fc7
fix(tests): add missing ListRequest and RecallRequest imports
senamakel Oct 4, 2026
4a78959
fix(consolidate): derive Hash on ConsolidateRequest
senamakel Oct 4, 2026
108a63e
test(layout): add missing import for SourceKind in layout tests
senamakel Oct 4, 2026
fae7c06
chore(tinymemory-tools): add background module
senamakel Oct 4, 2026
33818a8
fix(background): clarify deduplication mechanism in doc comment
senamakel Oct 4, 2026
c9d02f6
feat(recall): deduplicate items across sections and add timestamps
senamakel Oct 4, 2026
e6cf5d3
fix(lifecycle): handle untracked file in lifecycle module
senamakel Oct 4, 2026
96887a0
feat(tinymemory-tools): add lifecycle module and background/brain tes…
senamakel Oct 4, 2026
0b33be4
feat(recall): deduplicate items across sections without hiding answer…
senamakel Oct 4, 2026
ccd550c
chore: reformat long function signatures and expressions across multi…
senamakel Oct 4, 2026
7cce299
fix(lifecycle): use is_multiple_of for turn index check
senamakel Oct 4, 2026
8751682
fix(brain): handle missing memory file on startup
senamakel Oct 4, 2026
a3b8246
chore(tinymemory-integrations): add initial crate structure
senamakel Oct 4, 2026
2c02383
feat(cortex): add lifecycle test module
senamakel Oct 4, 2026
3d480f7
chore(examples): add onboarding and refund policy fixture files
senamakel Oct 4, 2026
3efbf48
feat(tinymemory-tools): add agent loop and brain examples
senamakel Oct 4, 2026
786dc45
feat(recall): improve context selection and document display
senamakel Oct 4, 2026
499ce70
feat(examples): add cortex agent example
senamakel Oct 4, 2026
d5ca3e6
chore: reformat code and adjust example turn indices
senamakel Oct 4, 2026
505acf7
feat(tinymemory-integrations): add live cortex lifecycle test
senamakel Oct 4, 2026
e7baa92
chore(scripts): add agent lifecycle live test to cortexdb-live.sh
senamakel Oct 4, 2026
8b83fe5
docs(specs): add agent memory specification
senamakel Oct 4, 2026
bdde707
docs(agent-memory): add initial design document for agent memory
senamakel Oct 4, 2026
06cce6d
fix(recall): strip duplicate heading from document bullet text
senamakel Oct 4, 2026
6bae4c5
docs(architecture): add lifecycle documentation
senamakel Oct 4, 2026
6d7d44b
feat(docs): document the agent memory lifecycle and brain feature
senamakel Oct 4, 2026
d6fc144
docs(tinymemory-tools): document agent lifecycle and holistic recall
senamakel Oct 4, 2026
363fbc4
test: reformat long assertions in recall test for readability
senamakel Oct 4, 2026
1749b07
fix(docs): remove unnecessary path qualification in doc comment
senamakel Oct 4, 2026
7f88126
fix(lifecycle): use fully qualified path for WaitFor in doc comment
senamakel Oct 4, 2026
cea1470
feat(agent): add memory evaluation example
senamakel Oct 4, 2026
59240d8
feat(tinymemory-integrations): add memory evaluation scenario example
senamakel Oct 4, 2026
b572308
feat(score): add memory evaluation scoring example
senamakel Oct 4, 2026
7ac2f43
fix(score): correct score calculation for memory evaluation
senamakel Oct 4, 2026
5f01546
chore(examples): remove unused inspect example
senamakel Oct 4, 2026
4399055
feat(tinymemory-integrations): add memory evaluation example
senamakel Oct 4, 2026
4f9ba3d
feat(tinymemory-integrations): add memory_eval example
senamakel Oct 4, 2026
b250e1e
feat(tinymemory-integrations): add chrono, serde, and serde_json depe…
senamakel Oct 4, 2026
a9a6abf
chore(memory_eval): narrow visibility of example types and functions
senamakel Oct 4, 2026
732feb3
fix(agent): extract "not in memory" string into a constant
senamakel Oct 4, 2026
1bd8815
fix(agent): use as_ref to avoid moving score in closure
senamakel Oct 4, 2026
82e1dec
fix(integrations): correct memory evaluation example to use proper LL…
senamakel Oct 4, 2026
55ef43e
feat(score): add memory evaluation scoring example
senamakel Oct 4, 2026
5147876
feat(integrations): add memory evaluation example
senamakel Oct 4, 2026
7b3e90b
feat(memory_eval): add model answer column and show both phases in su…
senamakel Oct 4, 2026
cfcabb3
refactor(memory_eval): extract eval runner into a struct
senamakel Oct 4, 2026
659c2f5
refactor(example): convert settle to a method on Eval
senamakel Oct 4, 2026
da491d2
chore: files changed crates/tinymemory-api/src/conformance/reference/…
senamakel Oct 4, 2026
c408bf3
test(background): replace map-based assertion with explicit receipt c…
senamakel Oct 4, 2026
21303c3
feat(cortex): handle synchronous build responses from CortexDB v0.10
senamakel Oct 4, 2026
7a452a8
fix(cortex): parallelise scope recall in fetch to reduce latency
senamakel Oct 4, 2026
77e0120
feat(recall): prefix conversation bullets with timestamp when available
senamakel Oct 4, 2026
3daccfd
feat(memory_eval): track built beliefs and pass probe questions to in…
senamakel Oct 4, 2026
110df45
chore(scripts): add memory evaluation script
senamakel Oct 4, 2026
d013bb3
chore(scripts): make memory-eval.sh executable
senamakel Oct 4, 2026
5dbafcb
docs(consolidate): document synchronous belief builds and read visibi…
senamakel Oct 4, 2026
aa92161
feat(lifecycle): add timestamp display for turn bullets
senamakel Oct 4, 2026
676eea8
fix(lifecycle): sample every dropped turn in the gist
senamakel Oct 4, 2026
db9df44
docs(architecture): clarify recall_for_compaction turn selection and …
senamakel Oct 4, 2026
382f816
fix(test): correct expected job outcome for direct wire in lifecycle …
senamakel Oct 4, 2026
059fa07
docs(evals): add README for evaluation framework
senamakel Oct 4, 2026
e1358e2
docs(evals): document the agent memory eval and add evals directory t…
senamakel Oct 4, 2026
c14985f
feat(memory_eval): add accept field to Probe for alternative correct …
senamakel Oct 4, 2026
5db58ce
feat(score): support accepted wordings in probe grading
senamakel Oct 4, 2026
5522004
docs(agent-memory): add documentation for agent memory evaluation
senamakel Oct 4, 2026
0fd4ee9
docs(evals): update agent-memory evaluation numbers
senamakel Oct 4, 2026
e421743
docs(agent-memory): add evaluation script and results reference to plan
senamakel Oct 4, 2026
a48001f
fix(scripts): include port in docker compose project name
senamakel Oct 4, 2026
aedbfd5
chore: files changed crates/tinymemory-integrations/examples/memory_e…
senamakel Oct 4, 2026
9bf47ad
chore: files changed crates/tinymemory-integrations/examples/memory_e…
senamakel Oct 4, 2026
ad42b85
chore: files changed crates/tinymemory-integrations/examples/memory_e…
senamakel Oct 4, 2026
0919865
chore: files changed crates/tinymemory-integrations/examples/memory_e…
senamakel Oct 4, 2026
0722161
chore: files changed crates/tinymemory-integrations/examples/memory_e…
senamakel Oct 4, 2026
35bcade
chore: files changed docs/evals/README.md,scripts/memory-eval.sh
senamakel Oct 4, 2026
a96cb03
feat(memory_eval): increase max_tokens and disable reasoning for LLM …
senamakel Oct 4, 2026
c281f22
fix(example): increase token limit and adjust reasoning config for GL…
senamakel Oct 4, 2026
112a5d3
feat(memory_eval): wait for enrichment queue to drain instead of fixe…
senamakel Oct 4, 2026
1dce71b
docs(evals): document longer enrich-wait and model compatibility
senamakel Oct 4, 2026
137ae87
chore: files changed crates/tinymemory-integrations/examples/memory_e…
senamakel Oct 4, 2026
a3a059e
feat(consolidate): add belief reading API
senamakel Oct 4, 2026
196228b
feat(conformance): add beliefs conformance checks to lifecycle suite
senamakel Oct 4, 2026
2a82861
test(conformance): add belief fault cases to conformance reference
senamakel Oct 4, 2026
be8ee0f
feat(recall): interleave engine beliefs with stored learnings in fetc…
senamakel Oct 4, 2026
2bfddaa
test(recall): add test harness and integration tests for belief mergi…
senamakel Oct 4, 2026
52ad8a5
chore: files changed crates/tinymemory-integrations/src/cortex/testin…
senamakel Oct 4, 2026
37a9427
chore: files changed crates/tinymemory-integrations/src/cortex/descri…
senamakel Oct 4, 2026
47cc1b1
chore: files changed crates/tinymemory-integrations/src/cortex/engine…
senamakel Oct 4, 2026
c2b510c
feat(cortex): add beliefs endpoint to CortexEngine
senamakel Oct 4, 2026
98d9b23
fix(cortex): reorder imports to use tinymemory_api re-exported chrono…
senamakel Oct 4, 2026
a9c4ccd
test: simplify belief equality assertion in scope test
senamakel Oct 4, 2026
4a04199
test(consolidate): correct expected built count in test assertion
senamakel Oct 4, 2026
12d2756
chore: files changed crates/tinymemory-integrations/src/cortex/lifecy…
senamakel Oct 4, 2026
ddbb641
docs(cortex): clarify how built beliefs are read back
senamakel Oct 4, 2026
03efaba
docs(architecture, specs): document how built beliefs reach the Learn…
senamakel Oct 4, 2026
d7ef4c1
chore: files changed crates/tinymemory-api/src/conformance/reference/…
senamakel Oct 4, 2026
2012b65
chore: files changed crates/tinymemory-api/src/query/mod.rs,crates/ti…
senamakel Oct 4, 2026
fc59be9
chore: files changed crates/tinymemory-integrations/src/cortex/engine…
senamakel Oct 4, 2026
de482f7
chore: files changed crates/tinymemory-tools/src/recall/gather.rs,cra…
senamakel Oct 4, 2026
824915a
chore: files changed crates/tinymemory-tools/src/recall/mod_tests.rs
senamakel Oct 4, 2026
ab2dead
chore: files changed crates/tinymemory-tools/src/recall/mod_tests.rs
senamakel Oct 4, 2026
583c36b
chore: files changed crates/tinymemory-integrations/src/cortex/engine…
senamakel Oct 4, 2026
08677db
chore: files changed docs/integration.md
senamakel Oct 4, 2026
5b56a96
chore: files changed README.md,docs/README.md,docs/evals/agent-memory.md
senamakel Oct 4, 2026
5cd0235
chore: files changed docs/architecture/cortex-wire.md,docs/architectu…
senamakel Oct 4, 2026
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
3 changes: 2 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ package it holds. There are exactly three, one per part of the memory layer:
metadata, namespaces, errors, and (feature `conformance`) the suite every
engine must pass. No I/O.
- `crates/tinymemory-tools` — the **agent tool spec**: `MemoryTools` over any
engine, and the `context.md` compiler.
engine, holistic recall and the `context.md` compiler, and the agent memory
lifecycle (`AgentMemory`, `Brain`, `MemoryLayout`, background jobs).
- `crates/tinymemory-integrations` — the **integrations**: the CortexDB engine
and its registry, documents, sources, safety and the legacy v1 import, each
a module behind a feature.
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: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ resolver = "2"
# - `tinymemory-api`: the core contract (`MemoryEngine`, items, metadata,
# namespaces, errors) and, behind `conformance`, the suite every engine must
# pass.
# - `tinymemory-tools`: the agent-facing tool spec over any engine, and the
# `context.md` compiler.
# - `tinymemory-tools`: the agent-facing tool spec over any engine, holistic
# recall and the `context.md` compiler, and the agent memory lifecycle.
# - `tinymemory-integrations`: everything that talks to the outside world —
# the CortexDB engine and its registry, document conversion, source readers,
# safety scrubbing and the legacy v1 import — each behind a feature.
Expand Down
31 changes: 29 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ namespace tree that keeps one tenant's or agent's memory apart from another's.

- **Specified behaviour:** [`docs/specs/memory-v2.md`](docs/specs/memory-v2.md)
is the source of truth for what the system does and why.
- **Integrating an agent:** [`docs/integration.md`](docs/integration.md) is
the guide for a host wiring its agent loop to the memory API.
- **How it is built:** [`docs/architecture/README.md`](docs/architecture/README.md)
has one page per concern: the contract, operations, namespaces, the CortexDB
engine, the agent tools, the integrations and the test strategy.
Expand All @@ -33,7 +35,9 @@ crates/
│ an in-memory reference engine
├── tinymemory-tools/ the agent surface over any engine: `MemoryTools` (seven
│ model-callable tools with JSON Schemas and host-fixed
│ scoping) and the `context.md` compiler
│ scoping), holistic recall and the `context.md` compiler,
│ and the agent lifecycle (`AgentMemory`, `Brain`,
│ `MemoryLayout`, background jobs)
└── tinymemory-integrations/ everything that touches the outside world, one module
per feature: `cortex` (+ `registry`, `config`),
`documents`, `sources`, `safety`, `import`
Expand All @@ -58,11 +62,12 @@ else on request, so a host pays only for what it uses.
| `cortex` (default) | `cortex`, `registry`, `config` | `CortexEngine` over both wires, `list_engines`, `build_engine`, `EngineCredential`, `MemoryConfig` |
| `documents` | `documents` | Format sniffing and conversion to markdown, producing `StoreItem::Document` |
| `documents-office` | `documents::OfficeConverter` | PDF, DOCX, PPTX and XLSX to markdown (implies `documents`) |
| `brain` | `brain` | Files into `tinymemory_tools::BrainDocument`s, by the source type their format implies (implies `documents`) |
| `sources` | `sources` | Folder, file and conversation readers, Composio normalisers (implies `documents`) |
| `sources-network` | `sources::fetch` and the network readers | GitHub, RSS and web-page readers and `fetch_url`, behind the SSRF guard (implies `sources`) |
| `safety` | `safety` | Secret and PII scrubbing of a `StoreItem` |
| `legacy-import` | `import` | Migrating a v1 (embedded TinyCortex) workspace into any engine |
| `full` | all of the above | `cortex`, `documents-office`, `sources-network`, `safety`, `legacy-import` |
| `full` | all of the above | `cortex`, `documents-office`, `brain`, `sources-network`, `safety`, `legacy-import` |

Dependency weight per feature is tabulated in
[`crates/tinymemory-integrations/README.md`](crates/tinymemory-integrations/README.md).
Expand Down Expand Up @@ -136,6 +141,28 @@ credential, and a credentialed cleartext endpoint that is not loopback.
To compile a `context.md` for the start of a session, call
`tinymemory_tools::context::compile(&*engine, &ContextSpec::default())`.

### The agent lifecycle

For memory around every turn, use `tinymemory_tools::AgentMemory`. It
implements a standard layout: a global brain of documents by source type,
each agent's conversations, and shared learnings. Call it at each point of
the agent loop:

```rust,ignore
let memory = AgentMemory::new(engine.clone(), MemoryLayout::default(), "support-01")?;
let turn = memory.pre_turn(PreTurn::new("thread-1", 0, user_text)).await?; // log + recall, no indexing wait
let reply = llm.generate(&turn.pack.markdown, user_text).await;
let report = memory.post_turn(PostTurn::new("thread-1", 1, reply)).await?; // log
for job in report.jobs { queue.push(job) } // belief builds, off the turn

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

priority high critique confident

Execute background jobs so beliefs reach the store

The example queues report.jobs but never runs the queue or the AgentMemory::background() runner. A host following this integration guide can complete every turn while belief-building jobs remain unprocessed, so distilled beliefs are not persisted into the engine. This leaves the earlier high-severity persistence issue unresolved; show the queue worker consuming these jobs (or invoke the background runner) before presenting the lifecycle as complete.

[RULE] unprocessed-background-jobs ·

```

`Brain` ingests documents (with `tinymemory_integrations::brain::brain_document`
converting PDFs and markdown first). `start_session` and
`recall_for_compaction` cover session resume and prompt truncation. Run
`cargo run -p tinymemory-tools --example agent_loop` for the whole loop
offline, and see
[`docs/architecture/lifecycle.md`](docs/architecture/lifecycle.md).

## Engines

| Id | What | Fetch modes |
Expand Down
2 changes: 1 addition & 1 deletion crates/tinymemory-api/src/conformance/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,5 @@ pub mod reference;
mod suite;

pub use error::{Error, Result};
pub use reference::{REFERENCE_ENGINE_ID, ReferenceEngine};
pub use reference::{CONSOLIDATED_TAG, REFERENCE_ENGINE_ID, ReferenceEngine};
pub use suite::run;
88 changes: 88 additions & 0 deletions crates/tinymemory-api/src/conformance/reference/distil.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
//! The reference engine's consolidation: a toy belief per source item.
//!
//! Real engines distil beliefs with a model. The reference engine needs only
//! something deterministic and obvious: each document or conversation an
//! [`ConsolidateRequest`] admits yields one [`LearningKind::Fact`] — the
//! first sentence of the document's prose, or of the conversation's first
//! user turn — stored at the source item's own node with the source's metadata, a
//! `consolidated` tag, and the source's id as evidence. Consolidating twice is
//! a replay, so it never duplicates a belief.

use crate::{ConsolidateRequest, DocumentBody, ItemKind, LearningKind, Role, StoreItem};

/// Tag on every belief the reference engine distils.
pub const CONSOLIDATED_TAG: &str = "consolidated";

/// Confidence of a distilled belief: a first sentence is a weak signal.
const BELIEF_CONFIDENCE: f32 = 0.5;

/// Longest statement a belief keeps, in characters.
const MAX_STATEMENT_CHARS: usize = 240;

/// The beliefs `request` distils from `items`, in item order.
pub(super) fn distil(items: &[StoreItem], request: &ConsolidateRequest) -> Vec<StoreItem> {
let kinds = request.admitted_kinds();
items
.iter()
.filter(|item| item.kind() != ItemKind::Learning && kinds.contains(&item.kind()))
.filter(|item| request.reach.admits(&item.meta().namespace))
.filter_map(|item| {
let statement = first_sentence(&source_text(item)?)?;
let mut meta = item.meta().clone();
if !meta.tags.iter().any(|tag| tag == CONSOLIDATED_TAG) {
meta.tags.push(CONSOLIDATED_TAG.to_string());
}
Some(StoreItem::Learning {
text: statement,
kind: LearningKind::Fact,
confidence: BELIEF_CONFIDENCE,
evidence: Some(item.fingerprint()),
meta,
})
})
.collect()
}

/// The text a belief is drawn from: a document's body, or a conversation's
/// first user turn.
fn source_text(item: &StoreItem) -> Option<String> {
match item {
StoreItem::Document {
body: DocumentBody::Text(text),
..
} => Some(text.clone()),
StoreItem::Conversation { turns, .. } => turns
.iter()
.find(|turn| turn.role == Role::User)
.map(|turn| turn.text.clone()),
_ => None,
}
}

/// The first sentence of `text`'s prose, whitespace collapsed; markdown
/// headings are skipped unless they are all there is. `None` when nothing is
/// left.
fn first_sentence(text: &str) -> Option<String> {
let (headings, prose): (Vec<&str>, Vec<&str>) = text
.lines()
.map(str::trim)
.filter(|line| !line.is_empty())
.partition(|line| line.starts_with('#'));
let lines = if prose.is_empty() { headings } else { prose };
let collapsed = lines
.iter()
.map(|line| line.trim_start_matches('#'))
.flat_map(|line| line.split_whitespace())
.collect::<Vec<_>>()
.join(" ");
let end = collapsed
.char_indices()
.find(|(_, c)| matches!(c, '.' | '!' | '?'))
.map_or(collapsed.len(), |(index, c)| index + c.len_utf8());
let sentence: String = collapsed[..end].chars().take(MAX_STATEMENT_CHARS).collect();
(!sentence.is_empty()).then_some(sentence)
}

#[cfg(test)]
#[path = "distil_tests.rs"]
mod tests;
73 changes: 73 additions & 0 deletions crates/tinymemory-api/src/conformance/reference/distil_tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
//! The reference engine's toy belief distillation.

use super::*;
use crate::{MemoryMeta, Namespace, Reach, Turn};

fn at(namespace: Namespace) -> MemoryMeta {
MemoryMeta {
namespace,
..MemoryMeta::default()
}
}

#[test]
fn distils_the_first_sentence_of_documents_and_user_turns() {
let items = vec![
StoreItem::document(
"# Refunds\n\nRefunds take five days. Ask support.",
at(Namespace::source("markdown")),
),
StoreItem::Conversation {
turns: vec![
Turn::new(Role::Assistant, "Hello!"),
Turn::new(Role::User, "I live in Lagos. What is the weather?"),
],
meta: at(Namespace::agent("support")),
},
StoreItem::learning(
"already a belief",
LearningKind::Fact,
0.9,
at(Namespace::ROOT),
),
];
let beliefs = distil(
&items,
&ConsolidateRequest::new(Reach::subtree(Namespace::ROOT)),
);
let texts: Vec<String> = beliefs.iter().map(StoreItem::render_text).collect();
assert_eq!(texts, ["Refunds take five days.", "I live in Lagos."]);
let StoreItem::Learning { evidence, meta, .. } = &beliefs[1] else {
panic!("a belief is a learning");
};
assert_eq!(evidence.as_deref(), Some(items[1].fingerprint().as_str()));
assert_eq!(meta.namespace, Namespace::agent("support"));
assert_eq!(meta.tags, [CONSOLIDATED_TAG]);
}

#[test]
fn honours_the_reach_and_the_kinds() {
let items = vec![
StoreItem::document("Pdf fact.", at(Namespace::source("pdf"))),
StoreItem::document("Notion fact.", at(Namespace::source("notion"))),
];
let pdf_only = ConsolidateRequest::new(Reach::exact(Namespace::source("pdf")));
assert_eq!(distil(&items, &pdf_only).len(), 1);
let conversations_only =
ConsolidateRequest::new(Reach::subtree(Namespace::ROOT)).kinds([ItemKind::Conversation]);
assert!(distil(&items, &conversations_only).is_empty());
}

#[test]
fn skips_blank_text_and_caps_long_sentences() {
assert_eq!(first_sentence(" \n# \n"), None);
assert_eq!(
first_sentence("# Only a title").as_deref(),
Some("Only a title")
);
let long = "word ".repeat(200);
assert_eq!(
first_sentence(&long).map(|s| s.chars().count()),
Some(MAX_STATEMENT_CHARS)
);
}
49 changes: 44 additions & 5 deletions crates/tinymemory-api/src/conformance/reference/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,22 @@
//! It is the suite's calibration subject: a failure against it means the
//! assertion is wrong, not the engine. It serves every fetch mode, using a
//! trivial keyword scorer and a deterministic toy vector (see `score`), and
//! answers recall by quoting its best hybrid hits.
//! answers recall by quoting its best hybrid hits. It consolidates on demand,
//! distilling one toy belief per source item (see `distil`), so a host's
//! whole memory lifecycle runs offline against it.

mod distil;
mod score;

use std::sync::Mutex;

pub use distil::CONSOLIDATED_TAG;

use crate::{
Citation, EngineDescriptor, EngineHealth, Error, FetchMode, FetchPage, FetchRequest,
ForgetReport, ForgetTarget, Hit, ItemId, ListPage, ListRequest, MemoryEngine, MetaFilter,
RecallAnswer, RecallRequest, Result, StoreItem, StoreReceipt,
Citation, ConsolidateReceipt, ConsolidateRequest, ConsolidateStatus, Consolidation,
EngineDescriptor, EngineHealth, Error, FetchMode, FetchPage, FetchRequest, ForgetReport,
ForgetTarget, Hit, ItemId, ListPage, ListRequest, MemoryEngine, MetaFilter, RecallAnswer,
RecallRequest, Result, StoreItem, StoreReceipt,
};
use async_trait::async_trait;

Expand Down Expand Up @@ -47,6 +53,7 @@ impl ReferenceEngine {
needs_key: false,
default_endpoint: None,
fetch_modes: FetchMode::ALL.to_vec(),
consolidation: Consolidation::OnDemand,
},
items: Mutex::new(Vec::new()),
}
Expand Down Expand Up @@ -163,7 +170,11 @@ impl MemoryEngine for ReferenceEngine {
req.validate()?;
let hits = self.ranked(&req.query, req.mode, &req.filter)?;
let (hits, next_cursor) = page(hits, req.cursor.as_deref(), req.limit)?;
Ok(FetchPage { hits, next_cursor })
Ok(FetchPage {
hits,
next_cursor,
beliefs: Vec::new(),
})
}

async fn store(&self, item: StoreItem) -> Result<StoreReceipt> {
Expand Down Expand Up @@ -194,6 +205,34 @@ impl MemoryEngine for ReferenceEngine {
})
}

/// Distils one belief per admitted document or conversation, at once:
/// the build is [`ConsolidateStatus::Completed`] on return.
async fn consolidate(&self, req: ConsolidateRequest) -> Result<ConsolidateReceipt> {
req.validate()?;
let mut items = self.items()?;
let beliefs = distil::distil(&items, &req);
let mut nodes: Vec<&crate::Namespace> = Vec::new();
for belief in &beliefs {
if !nodes.contains(&&belief.meta().namespace) {
nodes.push(&belief.meta().namespace);
}
}
let scopes = nodes.len() * req.admitted_kinds().len();
let built = beliefs.len();
for belief in beliefs {
let id = belief.fingerprint();
if !items.iter().any(|held| held.fingerprint() == id) {
items.push(belief);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

priority high critique confident

Remove derived beliefs when forgetting their source

Generated beliefs are stored as ordinary StoreItems, but forget only removes items matching the requested source IDs or filter. For example, consolidating a document and then forgetting that document removes the source while leaving its LearningKind::Fact belief searchable and listable, even though the API contract says forgetting the items a belief was built from removes the belief. Track the source evidence when forgetting, or otherwise remove dependent consolidated beliefs in the same operation.

[RULE] orphaned-derived-data ·

}
}
Ok(ConsolidateReceipt {
status: ConsolidateStatus::Completed,
jobs: Vec::new(),
scopes,
built: Some(built),
})
}

async fn list(&self, req: ListRequest) -> Result<ListPage> {
req.validate()?;
let matching: Vec<Hit> = self
Expand Down
4 changes: 3 additions & 1 deletion crates/tinymemory-api/src/conformance/suite/checks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,15 @@ pub(super) async fn all(ctx: &Ctx<'_>) -> Result<()> {
super::explore::explore(ctx).await?;
super::explore::get(ctx).await?;
super::bulk::store_many(ctx).await?;
super::lifecycle::store_with(ctx).await?;
fetch_filters(ctx).await?;
unsupported_modes(ctx).await?;
super::namespaces::namespaces(ctx).await?;
empty_forget(ctx).await?;
forget_by_id(ctx).await?;
forget_by_filter(ctx).await?;
recall(ctx).await
recall(ctx).await?;
super::lifecycle::consolidate(ctx).await
}

/// Forgets everything the run stored and checks it is gone.
Expand Down
Loading
Loading