mirror of
https://github.com/trustgraph-ai/trustgraph.git
synced 2026-07-24 12:41:02 +02:00
Migrate knowledge core service to ref-backed Effect state
This commit is contained in:
parent
5979d38b99
commit
0da0df81c4
5 changed files with 976 additions and 642 deletions
|
|
@ -12,22 +12,22 @@ Verified source roots:
|
||||||
- Effect v4 subtree: `/home/elpresidank/YeeBois/projects/beep-effect2/.repos/effect-v4`
|
- Effect v4 subtree: `/home/elpresidank/YeeBois/projects/beep-effect2/.repos/effect-v4`
|
||||||
- Installed Effect beta used by this workspace: `ts/node_modules/effect`
|
- Installed Effect beta used by this workspace: `ts/node_modules/effect`
|
||||||
|
|
||||||
Current signal counts from `ts/packages` after the 2026-06-02 RAG and agent
|
Current signal counts from `ts/packages` after the 2026-06-02 KnowledgeCore
|
||||||
requestor bridge slice:
|
ref-backed state slice:
|
||||||
|
|
||||||
| Signal | Count |
|
| Signal | Count |
|
||||||
| --- | ---: |
|
| --- | ---: |
|
||||||
| `Effect.runPromise` | 198 |
|
| `Effect.runPromise` | 200 |
|
||||||
| `Map<` | 65 |
|
| `Map<` | 72 |
|
||||||
| `WebSocket` | 51 |
|
| `WebSocket` | 51 |
|
||||||
| `new Map` | 47 |
|
| `new Map` | 53 |
|
||||||
| `toPromiseRequestor` | 0 |
|
| `toPromiseRequestor` | 0 |
|
||||||
| `makeAsyncProcessor` | 19 |
|
| `makeAsyncProcessor` | 19 |
|
||||||
| `receive(` | 18 |
|
| `receive(` | 18 |
|
||||||
| `while (` | 12 |
|
| `while (` | 11 |
|
||||||
| `new Error` | 14 |
|
| `new Error` | 14 |
|
||||||
| `new Promise` | 10 |
|
| `new Promise` | 10 |
|
||||||
| `JSON.parse` | 8 |
|
| `JSON.parse` | 7 |
|
||||||
| `localStorage` | 9 |
|
| `localStorage` | 9 |
|
||||||
| `JSON.stringify` | 6 |
|
| `JSON.stringify` | 6 |
|
||||||
| `setTimeout` | 4 |
|
| `setTimeout` | 4 |
|
||||||
|
|
@ -135,7 +135,7 @@ Notes:
|
||||||
|
|
||||||
### 2026-06-02: RAG And Agent Requestor Bridge Slice
|
### 2026-06-02: RAG And Agent Requestor Bridge Slice
|
||||||
|
|
||||||
- Status: migrated, root-verified, and ready to commit.
|
- Status: migrated, root-verified, committed, and pushed.
|
||||||
- Completed:
|
- Completed:
|
||||||
- `ts/packages/flow/src/retrieval/graph-rag.ts` and
|
- `ts/packages/flow/src/retrieval/graph-rag.ts` and
|
||||||
`ts/packages/flow/src/retrieval/document-rag.ts` now accept
|
`ts/packages/flow/src/retrieval/document-rag.ts` now accept
|
||||||
|
|
@ -163,6 +163,39 @@ Notes:
|
||||||
- `cd ts && bun run build`
|
- `cd ts && bun run build`
|
||||||
- `cd ts && bun run test`
|
- `cd ts && bun run test`
|
||||||
|
|
||||||
|
### 2026-06-02: KnowledgeCore Ref-Backed State Slice
|
||||||
|
|
||||||
|
- Status: migrated and root-verified.
|
||||||
|
- Completed:
|
||||||
|
- `ts/packages/flow/src/cores/service.ts` now exposes a typed
|
||||||
|
`KnowledgeCoreService` instead of `AsyncProcessorRuntime & Record<string,
|
||||||
|
any>`.
|
||||||
|
- Runtime state now lives in
|
||||||
|
`SynchronizedRef<KnowledgeCoreServiceState>` with `kgCores`, `deCores`,
|
||||||
|
the request consumer, and response producer.
|
||||||
|
- Knowledge operations now have Effect-returning handlers with Promise
|
||||||
|
facades only on exported compatibility methods.
|
||||||
|
- Persistence now decodes legacy and current snapshot shapes with Effect
|
||||||
|
Schema and encodes JSON through Schema rather than raw
|
||||||
|
`JSON.parse`/`JSON.stringify` plus assertions.
|
||||||
|
- The consume loop now uses `Effect.whileLoop`; the remaining
|
||||||
|
`consumer.receive(2000)` call is a pubsub boundary for this service.
|
||||||
|
- The service exposes `runMain()` through `NodeRuntime.runMain`; legacy
|
||||||
|
`run()` uses `ManagedRuntime`, and `ts/scripts/run-knowledge.ts` delegates
|
||||||
|
to `runMain()`.
|
||||||
|
- `ts/packages/base/src/schema/messages.ts` now models legacy hyphenated
|
||||||
|
knowledge request/response aliases so the service can preserve the wire
|
||||||
|
shape without response type assertions.
|
||||||
|
- New knowledge-core tests cover ref-backed mutation, graph embedding alias
|
||||||
|
responses, concurrent state updates, and legacy persistence loading.
|
||||||
|
- Verification:
|
||||||
|
- `bun run --cwd ts/packages/base build`
|
||||||
|
- `bun run --cwd ts/packages/flow build`
|
||||||
|
- `bun run --cwd ts/packages/flow test`
|
||||||
|
- `cd ts && bun run check`
|
||||||
|
- `cd ts && bun run build`
|
||||||
|
- `cd ts && bun run test`
|
||||||
|
|
||||||
## Subagent Findings To Preserve
|
## Subagent Findings To Preserve
|
||||||
|
|
||||||
- MCP/workbench:
|
- MCP/workbench:
|
||||||
|
|
@ -172,8 +205,9 @@ Notes:
|
||||||
the client API is less Promise-first.
|
the client API is less Promise-first.
|
||||||
- MCP env is now Config-backed; continue that policy for future MCP settings.
|
- MCP env is now Config-backed; continue that policy for future MCP settings.
|
||||||
- Flow stateful services:
|
- Flow stateful services:
|
||||||
- Config service ref-backed state is complete. Librarian, cores, and
|
- Config service and KnowledgeCore service ref-backed state are complete.
|
||||||
flow-manager still have mutable poller service objects. These remain good
|
Librarian and flow-manager still have mutable poller service objects.
|
||||||
|
These remain good
|
||||||
candidates for `Context` services,
|
candidates for `Context` services,
|
||||||
scoped layers, `Ref`/`SynchronizedRef`, `Schedule`, and managed
|
scoped layers, `Ref`/`SynchronizedRef`, `Schedule`, and managed
|
||||||
persistence.
|
persistence.
|
||||||
|
|
@ -206,7 +240,6 @@ Notes:
|
||||||
|
|
||||||
- TrustGraph evidence:
|
- TrustGraph evidence:
|
||||||
- `ts/packages/flow/src/librarian/service.ts`
|
- `ts/packages/flow/src/librarian/service.ts`
|
||||||
- `ts/packages/flow/src/cores/service.ts`
|
|
||||||
- `ts/packages/flow/src/flow-manager/service.ts`
|
- `ts/packages/flow/src/flow-manager/service.ts`
|
||||||
- Effect primitives:
|
- Effect primitives:
|
||||||
- `Context`, `Layer.scoped`, `Ref`, `SynchronizedRef`, `Schedule`,
|
- `Context`, `Layer.scoped`, `Ref`, `SynchronizedRef`, `Schedule`,
|
||||||
|
|
@ -314,7 +347,7 @@ Notes:
|
||||||
|
|
||||||
## Recommended PR Order
|
## Recommended PR Order
|
||||||
|
|
||||||
1. Librarian, cores, or flow-manager scoped state migration.
|
1. Librarian or flow-manager scoped state migration.
|
||||||
2. Client RPC managed runtime/scoped layer cleanup.
|
2. Client RPC managed runtime/scoped layer cleanup.
|
||||||
3. Base processor registry and constructor shim redesign.
|
3. Base processor registry and constructor shim redesign.
|
||||||
4. Gateway RPC callback and client streaming completion cleanup.
|
4. Gateway RPC callback and client streaming completion cleanup.
|
||||||
|
|
|
||||||
|
|
@ -381,7 +381,9 @@ export const KnowledgeRequest = S.Struct({
|
||||||
collection: S.optionalKey(S.String),
|
collection: S.optionalKey(S.String),
|
||||||
triples: OptionalMutableArray(Triple),
|
triples: OptionalMutableArray(Triple),
|
||||||
graphEmbeddings: OptionalMutableArray(GraphEmbedding),
|
graphEmbeddings: OptionalMutableArray(GraphEmbedding),
|
||||||
|
"graph-embeddings": OptionalMutableArray(GraphEmbedding),
|
||||||
documentEmbeddings: S.optionalKey(DocumentEmbeddingsCore),
|
documentEmbeddings: S.optionalKey(DocumentEmbeddingsCore),
|
||||||
|
"document-embeddings": S.optionalKey(DocumentEmbeddingsCore),
|
||||||
});
|
});
|
||||||
export type KnowledgeRequest = typeof KnowledgeRequest.Type;
|
export type KnowledgeRequest = typeof KnowledgeRequest.Type;
|
||||||
|
|
||||||
|
|
@ -391,7 +393,9 @@ export const KnowledgeResponse = S.Struct({
|
||||||
eos: S.optionalKey(S.Boolean),
|
eos: S.optionalKey(S.Boolean),
|
||||||
triples: OptionalMutableArray(Triple),
|
triples: OptionalMutableArray(Triple),
|
||||||
graphEmbeddings: OptionalMutableArray(GraphEmbedding),
|
graphEmbeddings: OptionalMutableArray(GraphEmbedding),
|
||||||
|
"graph-embeddings": OptionalMutableArray(GraphEmbedding),
|
||||||
documentEmbeddings: S.optionalKey(DocumentEmbeddingsCore),
|
documentEmbeddings: S.optionalKey(DocumentEmbeddingsCore),
|
||||||
|
"document-embeddings": S.optionalKey(DocumentEmbeddingsCore),
|
||||||
});
|
});
|
||||||
export type KnowledgeResponse = typeof KnowledgeResponse.Type;
|
export type KnowledgeResponse = typeof KnowledgeResponse.Type;
|
||||||
|
|
||||||
|
|
|
||||||
192
ts/packages/flow/src/__tests__/knowledge-core-service.test.ts
Normal file
192
ts/packages/flow/src/__tests__/knowledge-core-service.test.ts
Normal file
|
|
@ -0,0 +1,192 @@
|
||||||
|
import {mkdtemp, rm} from "node:fs/promises";
|
||||||
|
import {tmpdir} from "node:os";
|
||||||
|
import {join} from "node:path";
|
||||||
|
import {Effect, SynchronizedRef} from "effect";
|
||||||
|
import {describe, expect, it} from "vitest";
|
||||||
|
import {
|
||||||
|
topics,
|
||||||
|
type BackendConsumer,
|
||||||
|
type BackendProducer,
|
||||||
|
type CreateConsumerOptions,
|
||||||
|
type CreateProducerOptions,
|
||||||
|
type KnowledgeRequest,
|
||||||
|
type KnowledgeResponse,
|
||||||
|
type Message,
|
||||||
|
type PubSubBackend,
|
||||||
|
type Triple,
|
||||||
|
} from "@trustgraph/base";
|
||||||
|
import {makeKnowledgeCoreService} from "../cores/service.js";
|
||||||
|
|
||||||
|
class NoopPubSub implements PubSubBackend {
|
||||||
|
readonly sentByTopic = new Map<string, Array<unknown>>();
|
||||||
|
|
||||||
|
async createProducer<T>(options: CreateProducerOptions<T>): Promise<BackendProducer<T>> {
|
||||||
|
return {
|
||||||
|
send: async (message) => {
|
||||||
|
const sent = this.sentByTopic.get(options.topic) ?? [];
|
||||||
|
sent.push(message);
|
||||||
|
this.sentByTopic.set(options.topic, sent);
|
||||||
|
},
|
||||||
|
flush: async () => undefined,
|
||||||
|
close: async () => undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async createConsumer<T>(_options: CreateConsumerOptions): Promise<BackendConsumer<T>> {
|
||||||
|
return {
|
||||||
|
receive: async () => null,
|
||||||
|
acknowledge: async (_message: Message<T>) => undefined,
|
||||||
|
negativeAcknowledge: async (_message: Message<T>) => undefined,
|
||||||
|
unsubscribe: async () => undefined,
|
||||||
|
close: async () => undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async close(): Promise<void> {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const sampleTriple: Triple = {
|
||||||
|
s: {type: "IRI", iri: "https://example.test/a"},
|
||||||
|
p: {type: "IRI", iri: "https://example.test/related"},
|
||||||
|
o: {type: "LITERAL", value: "alpha"},
|
||||||
|
};
|
||||||
|
|
||||||
|
const makeService = (dataDir: string, backend: PubSubBackend = new NoopPubSub()) =>
|
||||||
|
makeKnowledgeCoreService({
|
||||||
|
id: "knowledge-test",
|
||||||
|
manageProcessSignals: false,
|
||||||
|
pubsub: backend,
|
||||||
|
dataDir,
|
||||||
|
});
|
||||||
|
|
||||||
|
const seedResponseProducer = async (
|
||||||
|
backend: NoopPubSub,
|
||||||
|
service: ReturnType<typeof makeKnowledgeCoreService>,
|
||||||
|
) => {
|
||||||
|
const responseProducer = await backend.createProducer<KnowledgeResponse>({
|
||||||
|
topic: topics.knowledgeResponse,
|
||||||
|
});
|
||||||
|
await Effect.runPromise(
|
||||||
|
SynchronizedRef.update(service.state, (state) => ({
|
||||||
|
...state,
|
||||||
|
responseProducer,
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
describe("KnowledgeCoreService operations", () => {
|
||||||
|
it("stores knowledge cores through ref-backed state and preserves graph embedding aliases", async () => {
|
||||||
|
const dir = await mkdtemp(join(tmpdir(), "trustgraph-knowledge-service-"));
|
||||||
|
const backend = new NoopPubSub();
|
||||||
|
const service = makeService(dir, backend);
|
||||||
|
await seedResponseProducer(backend, service);
|
||||||
|
|
||||||
|
const request: KnowledgeRequest = {
|
||||||
|
operation: "put-kg-core",
|
||||||
|
user: "alice",
|
||||||
|
id: "core-a",
|
||||||
|
triples: [sampleTriple],
|
||||||
|
"graph-embeddings": [
|
||||||
|
{
|
||||||
|
entity: {type: "IRI", iri: "https://example.test/a"},
|
||||||
|
vectors: [[1, 2, 3]],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
await service.putKgCore(request, "put-1");
|
||||||
|
const state = await Effect.runPromise(SynchronizedRef.get(service.state));
|
||||||
|
const core = state.kgCores.get("alice:core-a");
|
||||||
|
|
||||||
|
await service.getKgCore({
|
||||||
|
operation: "get-kg-core",
|
||||||
|
user: "alice",
|
||||||
|
id: "core-a",
|
||||||
|
}, "get-1");
|
||||||
|
await rm(dir, {recursive: true, force: true});
|
||||||
|
|
||||||
|
expect(core?.triples).toEqual([sampleTriple]);
|
||||||
|
expect(core?.graphEmbeddings).toEqual([
|
||||||
|
{
|
||||||
|
entity: {type: "IRI", iri: "https://example.test/a"},
|
||||||
|
vectors: [[1, 2, 3]],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
expect(backend.sentByTopic.get(topics.knowledgeResponse)).toEqual([
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
triples: [sampleTriple],
|
||||||
|
eos: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
graphEmbeddings: [
|
||||||
|
{
|
||||||
|
entity: {type: "IRI", iri: "https://example.test/a"},
|
||||||
|
vectors: [[1, 2, 3]],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"graph-embeddings": [
|
||||||
|
{
|
||||||
|
entity: {type: "IRI", iri: "https://example.test/a"},
|
||||||
|
vectors: [[1, 2, 3]],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
eos: true,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("serializes concurrent mutations through ref-backed maps", async () => {
|
||||||
|
const dir = await mkdtemp(join(tmpdir(), "trustgraph-knowledge-service-"));
|
||||||
|
const backend = new NoopPubSub();
|
||||||
|
const service = makeService(dir, backend);
|
||||||
|
await seedResponseProducer(backend, service);
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
service.putKgCore({
|
||||||
|
operation: "put-kg-core",
|
||||||
|
user: "alice",
|
||||||
|
id: "core-b",
|
||||||
|
triples: [sampleTriple],
|
||||||
|
}, "put-a"),
|
||||||
|
service.putKgCore({
|
||||||
|
operation: "put-kg-core",
|
||||||
|
user: "alice",
|
||||||
|
id: "core-b",
|
||||||
|
triples: [
|
||||||
|
{
|
||||||
|
s: {type: "IRI", iri: "https://example.test/b"},
|
||||||
|
p: {type: "IRI", iri: "https://example.test/related"},
|
||||||
|
o: {type: "LITERAL", value: "beta"},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}, "put-b"),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const state = await Effect.runPromise(SynchronizedRef.get(service.state));
|
||||||
|
await rm(dir, {recursive: true, force: true});
|
||||||
|
|
||||||
|
expect(state.kgCores.get("alice:core-b")?.triples).toHaveLength(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("loads the legacy persisted knowledge shape with schema decoding", async () => {
|
||||||
|
const dir = await mkdtemp(join(tmpdir(), "trustgraph-knowledge-service-"));
|
||||||
|
const persistPath = join(dir, "knowledge-state.json");
|
||||||
|
await Bun.write(
|
||||||
|
persistPath,
|
||||||
|
JSON.stringify({
|
||||||
|
"alice:legacy": {
|
||||||
|
triples: [sampleTriple],
|
||||||
|
graphEmbeddings: [],
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
const service = makeService(dir);
|
||||||
|
|
||||||
|
await service.loadFromDisk();
|
||||||
|
const state = await Effect.runPromise(SynchronizedRef.get(service.state));
|
||||||
|
await rm(dir, {recursive: true, force: true});
|
||||||
|
|
||||||
|
expect(state.kgCores.get("alice:legacy")?.triples).toEqual([sampleTriple]);
|
||||||
|
});
|
||||||
|
});
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -7,9 +7,6 @@
|
||||||
* NATS_URL (default: nats://localhost:4222)
|
* NATS_URL (default: nats://localhost:4222)
|
||||||
* KNOWLEDGE_DATA_DIR (optional, e.g., ./data/knowledge)
|
* KNOWLEDGE_DATA_DIR (optional, e.g., ./data/knowledge)
|
||||||
*/
|
*/
|
||||||
import { run } from "../packages/flow/src/cores/service.js";
|
import {runMain} from "../packages/flow/src/cores/service.js";
|
||||||
|
|
||||||
run().catch((err) => {
|
runMain();
|
||||||
console.error("Knowledge core service failed:", err);
|
|
||||||
process.exit(1);
|
|
||||||
});
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue