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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,13 @@ the end of this file for historical context.
- Did not claim the unrelated `longmemory` PyPI project. New Python installs use
`longmemory-sdk`.

### Fixes

- Embedded project-scoped writes. `longmemory_ingest` with a `project_id`,
`longmemory_remember_decision` and connector imports go through
`applyImportPlan`, which stored every node without a semantic vector, so
those memories never received a vector score in recall (#216).

## [1.0.0] - 2026-08-31

LongMemory 1.0 is a ground-up architecture rewrite rather than an incremental
Expand Down
14 changes: 14 additions & 0 deletions benchmarks/src/check.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import { create_named_embedding_provider } from '../../src/core/embeddings/provi
import { prepare_evidence_query, evidence_support, evidence_adjustment, order_evidence, query_calendar_window, calendar_relevance } from '../../src/core/recall/rerank.js';
import { associative_recall } from '../../src/core/recall/associative_recall.js';
import { answer_from_evidence, type evidence_reader_model } from '../../src/answering/evidence_reader.js';
import { createProjectMemory as create_project_memory } from '../../src/core/project/project_memory.js';

const directory = temp(join(tmpdir(), 'longmemory-bench-check-'));
const memory = create_memory({ embedding_dimension: 2 });
Expand Down Expand Up @@ -451,6 +452,19 @@ try {
else process.env[key!] = value;
}
}
{
// Project writes and decisions go through applyImportPlan, not ingest(); they must still be embedded.
const embedded = create_memory({ embedding_dimension: 4, embedding_provider: { embed: () => [0.5, 0.5, 0.5, 0.5] } });
try {
const manager = await create_project_memory({ memory: embedded, tenant_id: 'default', project_id: 'check-project', name: 'check' });
const id = await manager.ingestProjectEvent('check-project', { kind: 'decision', text: 'Staging credentials rotate every 45 days.' });
const node = (await embedded.explain(id)).node;
assert.ok(node);
assert.equal(node.vectors.semantic?.length, 4);
} finally {
await embedded.close();
}
}
if (!process.argv.includes('--unit-only')) {
const smoke = await run_benchmark({ providers: ['longmemory'], datasets: ['smoke'], cutoffs: [5], output_dir: join(directory, 'smoke'), resume: false });
assert.equal(smoke.report.providers[0].failed_questions, 0);
Expand Down
7 changes: 7 additions & 0 deletions src/core/create_memory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -550,6 +550,12 @@ export function create_memory(config: memory_config = {}): long_memory {
throw new Error(`connector node ${node.key} is missing source identity, recorded_at, or provenance`);
}
}
// The import transaction is synchronous, so embed before entering it, as ingest() does.
const vectors = new Map<(typeof plan.nodes_to_create)[number], number[] | null>();
for (const node of plan.nodes_to_create) {
const language = detect_language(node.content).language ?? cfg.default_language;
vectors.set(node, await embed(cfg.embedding_provider, multilingual_embeddings, embedding_cache, node.content, language, 'document'));
}
return import_transaction().run(() => {
const before_nodes = new Set(engine.graph.node_list().map((node) => node.id));
const before_edges = new Set(engine.graph.edge_list().map((edge) => edge.id));
Expand Down Expand Up @@ -611,6 +617,7 @@ export function create_memory(config: memory_config = {}): long_memory {
id: node.id,
user_id: cfg.user_id,
text: node.content,
vector: vectors.get(node),
at: node.recorded_at,
observed_at: node.observed_at,
valid_from: node.valid_from,
Expand Down