@factlayer/adapter-cognee 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -0
- package/dist/client.d.ts +2 -0
- package/dist/client.js +32 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +3 -0
- package/dist/mapper.d.ts +6 -0
- package/dist/mapper.js +83 -0
- package/dist/scan.d.ts +8 -0
- package/dist/scan.js +17 -0
- package/dist/types.d.ts +12 -0
- package/dist/types.js +1 -0
- package/package.json +25 -0
package/README.md
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# @factlayer/adapter-cognee
|
|
2
|
+
|
|
3
|
+
A [FactLayer](https://github.com/Rubber-Duck-Screaming/factlayer) adapter for [Cognee](https://www.cognee.ai): pulls a dataset's ingested source records, maps them to FactLayer facts, persists them locally (keyed by Cognee's own data id), and checks each one for freshness.
|
|
4
|
+
|
|
5
|
+
Cognee is different from Mem0 and Zep in one important way: it's self-hosted, not a hosted service you just read from. *You* run the Cognee instance and configure your own LLM provider (any OpenAI-compatible endpoint) to do the ingestion and knowledge-graph extraction. This adapter doesn't provide an LLM and doesn't run extraction itself — it only reads records you've already fed into your own Cognee instance, tracking the freshness of that ingested source material.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
bun add @factlayer/adapter-cognee
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { createCogneeClient, scanCognee } from "@factlayer/adapter-cognee";
|
|
17
|
+
|
|
18
|
+
const client = await createCogneeClient({
|
|
19
|
+
llmModel: "your-model-id",
|
|
20
|
+
llmApiKey: process.env.YOUR_LLM_KEY,
|
|
21
|
+
// llmEndpoint: process.env.YOUR_LLM_ENDPOINT, // only needed for a non-OpenAI, OpenAI-compatible provider
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
// datasetId is Cognee's own dataset UUID, not the dataset name you passed
|
|
25
|
+
// to Cognee's add()/remember() calls -- look it up via the underlying
|
|
26
|
+
// @cognee/cognee-ts SDK's datasets.list() if you only have the name.
|
|
27
|
+
const results = await scanCognee(client, datasetId);
|
|
28
|
+
// each result: { fact, result: { status: "fresh" | "needs-verification", ... } }
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
See the [main repo](https://github.com/Rubber-Duck-Screaming/factlayer) for the full architecture, the freshness model, and how this adapter fits alongside `@factlayer/core`, `@factlayer/cli`, and `@factlayer/mcp-server`.
|
package/dist/client.d.ts
ADDED
package/dist/client.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Cognee, init } from "@cognee/cognee-ts";
|
|
2
|
+
let initialized = false;
|
|
3
|
+
// Boots the Rust async runtime that every Cognee pipeline call depends on
|
|
4
|
+
// (Cognee's own usage notes call this out as required once per process,
|
|
5
|
+
// before any async op). Guarded here rather than left to callers, so
|
|
6
|
+
// createCogneeClient can be called more than once -- e.g. once per scan --
|
|
7
|
+
// without re-initializing the runtime.
|
|
8
|
+
function ensureInit() {
|
|
9
|
+
if (!initialized) {
|
|
10
|
+
init();
|
|
11
|
+
initialized = true;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
// Wraps the real @cognee/cognee-ts Cognee class so it satisfies the minimal
|
|
15
|
+
// CogneeClient shape scanCognee() depends on. Keeping scan.ts decoupled
|
|
16
|
+
// from the real SDK class is what lets the automated test suite inject a
|
|
17
|
+
// fake client instead of making real (LLM/embedding-backed) calls (see
|
|
18
|
+
// scan.test.ts). This file is exercised only by a manual smoke test
|
|
19
|
+
// against a real Cognee instance, not by `bun test`.
|
|
20
|
+
//
|
|
21
|
+
// Async (unlike adapter-mem0/adapter-zep's sync client factories) because
|
|
22
|
+
// Cognee's own docs require warm() -- which builds the embedding/LLM
|
|
23
|
+
// engines and resolves the default user -- to run once after construction
|
|
24
|
+
// and before any other call.
|
|
25
|
+
export async function createCogneeClient(settings) {
|
|
26
|
+
ensureInit();
|
|
27
|
+
const client = new Cognee(settings);
|
|
28
|
+
await client.warm();
|
|
29
|
+
return {
|
|
30
|
+
listData: (datasetId) => client.datasets.listData(datasetId),
|
|
31
|
+
};
|
|
32
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export type { CogneeClient, CogneeDataRecord } from "./types.ts";
|
|
2
|
+
export { toFact } from "./mapper";
|
|
3
|
+
export type { CogneeFact } from "./mapper.ts";
|
|
4
|
+
export { scanCognee } from "./scan";
|
|
5
|
+
export type { ScannedFact } from "./scan.ts";
|
|
6
|
+
export { createCogneeClient } from "./client";
|
package/dist/index.js
ADDED
package/dist/mapper.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Fact } from "@factlayer/core";
|
|
2
|
+
import type { CogneeDataRecord } from "./types.ts";
|
|
3
|
+
export interface CogneeFact extends Fact {
|
|
4
|
+
importanceWeight?: number | null;
|
|
5
|
+
}
|
|
6
|
+
export declare function toFact(record: CogneeDataRecord, now?: number): Promise<CogneeFact>;
|
package/dist/mapper.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// Design note: Cognee's search()/recall() -- its extracted knowledge-graph
|
|
2
|
+
// facts -- carry no timestamp fields in @cognee/cognee-ts's current types
|
|
3
|
+
// (CogneeSearchItem is just `{ id, score, payload }`; CogneeRecallItem is
|
|
4
|
+
// just `{ source, content, score }`), making them unusable as a freshness
|
|
5
|
+
// source. This adapter instead uses datasets.listData(datasetId), which
|
|
6
|
+
// returns pre-extraction raw ingested records (CogneeDataRecord) that do
|
|
7
|
+
// carry real timestamps. This means the Cognee adapter tracks freshness of
|
|
8
|
+
// ingested source material, not extracted graph facts -- a real scope
|
|
9
|
+
// difference from adapter-mem0 and adapter-zep, documented here rather
|
|
10
|
+
// than hidden.
|
|
11
|
+
//
|
|
12
|
+
// A second consequence of that same choice: CogneeDataRecord has no inline
|
|
13
|
+
// text-content field. `name` looked like a plausible stand-in at first, but
|
|
14
|
+
// live output against a real Cognee instance confirmed it's actually an
|
|
15
|
+
// internal hash-based filename (e.g.
|
|
16
|
+
// "text_b746e53fca48a4c932a25ac28fbdb0c2"), not the fact's content -- using
|
|
17
|
+
// it as `text` would have made the adapter report gibberish instead of
|
|
18
|
+
// facts. The real content lives at `raw_data_location`, a `file://` URI
|
|
19
|
+
// pointing at a local file on disk, so toFact() reads that file directly.
|
|
20
|
+
//
|
|
21
|
+
// Assumption, not a guarantee: this only works when the FactLayer process
|
|
22
|
+
// can read Cognee's local storage on the same filesystem -- true for our
|
|
23
|
+
// current self-hosted setup (both run on one machine), but it would break
|
|
24
|
+
// if Cognee and FactLayer ever ran on separate machines without a shared
|
|
25
|
+
// mount. Documented here rather than silently assumed.
|
|
26
|
+
import { readFile } from "node:fs/promises";
|
|
27
|
+
import { classify } from "@factlayer/core";
|
|
28
|
+
const FILE_URI_PREFIX = "file://";
|
|
29
|
+
// Cognee's raw_data_location is a `file://` URI, but on Windows the path
|
|
30
|
+
// after the scheme keeps its native backslashes instead of being
|
|
31
|
+
// forward-slash-encoded like a spec-compliant file URI would be (a real
|
|
32
|
+
// one is "file:///C:/Users/...", three slashes, forward-slash-separated;
|
|
33
|
+
// Cognee's own output is "file://C:\\Users\\...\\text_....txt", two
|
|
34
|
+
// slashes, backslash-separated). Parsing that with `new URL()` /
|
|
35
|
+
// `fileURLToPath()` would treat "C:" as the URL's host and silently drop
|
|
36
|
+
// it from the resulting path, corrupting the drive letter. So this strips
|
|
37
|
+
// the "file://" prefix as a plain string instead of URL-parsing it, and
|
|
38
|
+
// hands the remainder to fs as-is -- Node's fs accepts either slash
|
|
39
|
+
// direction on Windows.
|
|
40
|
+
function fileUriToPath(uri) {
|
|
41
|
+
return uri.startsWith(FILE_URI_PREFIX) ? uri.slice(FILE_URI_PREFIX.length) : uri;
|
|
42
|
+
}
|
|
43
|
+
function toMs(value, fallback) {
|
|
44
|
+
if (!value)
|
|
45
|
+
return fallback;
|
|
46
|
+
const ms = Date.parse(value);
|
|
47
|
+
return Number.isNaN(ms) ? fallback : ms;
|
|
48
|
+
}
|
|
49
|
+
// Converts a Cognee data record (a pre-extraction ingested item, not an
|
|
50
|
+
// extracted graph fact -- see the module comment above) into a factlayer
|
|
51
|
+
// Fact.
|
|
52
|
+
//
|
|
53
|
+
// - storedAt comes from created_at (when the item was ingested).
|
|
54
|
+
// - lastVerifiedAt comes from updated_at, falling back to created_at when
|
|
55
|
+
// updated_at is null. Deliberately NOT last_accessed: that field is an
|
|
56
|
+
// access-frequency signal (how often Cognee's own code read the record),
|
|
57
|
+
// not a truth-verification signal -- treating "read recently" as "still
|
|
58
|
+
// true" is exactly the mistake factlayer exists to catch.
|
|
59
|
+
// - text is read from the file at raw_data_location (see the module
|
|
60
|
+
// comment above for why, and the same-filesystem assumption this
|
|
61
|
+
// depends on) -- NOT record.name, which is an internal hash-based
|
|
62
|
+
// filename, not fact content.
|
|
63
|
+
// - category always comes from classify() on that file content -- Cognee
|
|
64
|
+
// doesn't expose a comparable freeform category on data records.
|
|
65
|
+
// - expiresAt is always null: CogneeDataRecord has no expiration-style
|
|
66
|
+
// field, so check() always falls through to the category half-life math.
|
|
67
|
+
// - importanceWeight passes through record.importance_weight as-is
|
|
68
|
+
// (undocumented and nullable in Cognee's own types) for callers who want
|
|
69
|
+
// it, without factoring into check() at all.
|
|
70
|
+
export async function toFact(record, now = Date.now()) {
|
|
71
|
+
const text = (await readFile(fileUriToPath(record.raw_data_location), "utf-8")).trim();
|
|
72
|
+
const storedAt = toMs(record.created_at, now);
|
|
73
|
+
const lastVerifiedAt = toMs(record.updated_at, storedAt);
|
|
74
|
+
return {
|
|
75
|
+
id: record.id,
|
|
76
|
+
text,
|
|
77
|
+
category: await classify(text),
|
|
78
|
+
storedAt,
|
|
79
|
+
lastVerifiedAt,
|
|
80
|
+
expiresAt: null,
|
|
81
|
+
importanceWeight: record.importance_weight,
|
|
82
|
+
};
|
|
83
|
+
}
|
package/dist/scan.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { CheckResult } from "@factlayer/core";
|
|
2
|
+
import type { CogneeFact } from "./mapper.ts";
|
|
3
|
+
import type { CogneeClient } from "./types.ts";
|
|
4
|
+
export interface ScannedFact {
|
|
5
|
+
fact: CogneeFact;
|
|
6
|
+
result: CheckResult;
|
|
7
|
+
}
|
|
8
|
+
export declare function scanCognee(client: CogneeClient, datasetId: string, now?: number): Promise<ScannedFact[]>;
|
package/dist/scan.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { addFact, check } from "@factlayer/core";
|
|
2
|
+
import { toFact } from "./mapper";
|
|
3
|
+
// Pulls raw ingested records for a dataset via datasets.listData() -- see
|
|
4
|
+
// mapper.ts's design note for why this is source material, not extracted
|
|
5
|
+
// graph facts -- maps each to a Fact, persists it locally (upserted via
|
|
6
|
+
// addFact, keyed by Cognee's own data id so the two systems share one id
|
|
7
|
+
// space), and runs factlayer's check() on it. Never writes anything back to
|
|
8
|
+
// Cognee itself -- only to factlayer's local store, which is what lets
|
|
9
|
+
// mark_verified act on Cognee-sourced facts afterward.
|
|
10
|
+
export async function scanCognee(client, datasetId, now = Date.now()) {
|
|
11
|
+
const records = await client.listData(datasetId);
|
|
12
|
+
return Promise.all(records.map(async (record) => {
|
|
13
|
+
const fact = await toFact(record, now);
|
|
14
|
+
await addFact(fact);
|
|
15
|
+
return { fact, result: check(fact, now) };
|
|
16
|
+
}));
|
|
17
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface CogneeDataRecord {
|
|
2
|
+
id: string;
|
|
3
|
+
name: string;
|
|
4
|
+
raw_data_location: string;
|
|
5
|
+
created_at: string;
|
|
6
|
+
updated_at: string | null;
|
|
7
|
+
last_accessed: string | null;
|
|
8
|
+
importance_weight: number | null;
|
|
9
|
+
}
|
|
10
|
+
export interface CogneeClient {
|
|
11
|
+
listData(datasetId: string): Promise<CogneeDataRecord[]>;
|
|
12
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@factlayer/adapter-cognee",
|
|
3
|
+
"version": "0.7.0",
|
|
4
|
+
"description": "Cognee adapter for FactLayer: scans, persists, and checks freshness of Cognee-ingested source records.",
|
|
5
|
+
"keywords": ["ai", "agent-memory", "llm", "freshness", "cognee", "factlayer"],
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "dist/index.js",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
|
+
"files": ["dist"],
|
|
10
|
+
"publishConfig": {
|
|
11
|
+
"access": "public"
|
|
12
|
+
},
|
|
13
|
+
"scripts": {
|
|
14
|
+
"test": "bun test",
|
|
15
|
+
"build": "tsc -p tsconfig.json",
|
|
16
|
+
"prepublishOnly": "bun run ../../scripts/check-publish-ready.ts"
|
|
17
|
+
},
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@factlayer/core": "0.6.0",
|
|
20
|
+
"@cognee/cognee-ts": "^0.2.0"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"typescript": "^5.6.3"
|
|
24
|
+
}
|
|
25
|
+
}
|