kernl 0.6.1 → 0.6.3
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/.turbo/turbo-build.log +4 -5
- package/.turbo/turbo-check-types.log +1 -1
- package/CHANGELOG.md +36 -0
- package/dist/agent/__tests__/concurrency.test.js +1 -1
- package/dist/agent/__tests__/run.test.js +1 -1
- package/dist/{types/agent.d.ts → agent/types.d.ts} +2 -2
- package/dist/agent/types.d.ts.map +1 -0
- package/dist/agent.d.ts +36 -4
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +58 -0
- package/dist/api/models/thread.d.ts +1 -1
- package/dist/api/resources/threads/threads.d.ts +1 -1
- package/dist/api/resources/threads/threads.d.ts.map +1 -1
- package/dist/api/resources/threads/threads.js +1 -1
- package/dist/api/resources/threads/types.d.ts +2 -2
- package/dist/api/resources/threads/types.d.ts.map +1 -1
- package/dist/context.d.ts +4 -4
- package/dist/context.d.ts.map +1 -1
- package/dist/guardrail.d.ts +2 -2
- package/dist/index.d.ts +5 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -3
- package/dist/internal.d.ts +2 -2
- package/dist/internal.js +1 -1
- package/dist/kernl/index.d.ts +1 -1
- package/dist/kernl/index.d.ts.map +1 -1
- package/dist/kernl/index.js +0 -1
- package/dist/kernl/kernl.d.ts +7 -18
- package/dist/kernl/kernl.d.ts.map +1 -1
- package/dist/kernl/kernl.js +29 -29
- package/dist/kernl/types.d.ts +91 -0
- package/dist/kernl/types.d.ts.map +1 -0
- package/dist/lib/error.d.ts +2 -2
- package/dist/lifecycle.d.ts +2 -2
- package/dist/memory/codec.d.ts +32 -0
- package/dist/memory/codec.d.ts.map +1 -0
- package/dist/memory/codec.js +97 -0
- package/dist/memory/codecs/domain.d.ts +34 -0
- package/dist/memory/codecs/domain.d.ts.map +1 -0
- package/dist/memory/codecs/domain.js +99 -0
- package/dist/memory/codecs/identity.d.ts +12 -0
- package/dist/memory/codecs/identity.d.ts.map +1 -0
- package/dist/memory/codecs/identity.js +17 -0
- package/dist/memory/codecs/index.d.ts +31 -0
- package/dist/memory/codecs/index.d.ts.map +1 -0
- package/dist/memory/codecs/index.js +39 -0
- package/dist/memory/codecs/tpuf.d.ts +38 -0
- package/dist/memory/codecs/tpuf.d.ts.map +1 -0
- package/dist/memory/codecs/tpuf.js +90 -0
- package/dist/memory/encoder.d.ts +29 -0
- package/dist/memory/encoder.d.ts.map +1 -0
- package/dist/memory/encoder.js +45 -0
- package/dist/memory/handle.d.ts +89 -0
- package/dist/memory/handle.d.ts.map +1 -0
- package/dist/memory/handle.js +128 -0
- package/dist/memory/index.d.ts +12 -0
- package/dist/memory/index.d.ts.map +1 -0
- package/dist/memory/index.js +7 -0
- package/dist/memory/indexes.d.ts +91 -0
- package/dist/memory/indexes.d.ts.map +1 -0
- package/dist/memory/indexes.js +7 -0
- package/dist/memory/memory.d.ts +51 -0
- package/dist/memory/memory.d.ts.map +1 -0
- package/dist/memory/memory.js +107 -0
- package/dist/memory/schema.d.ts +41 -0
- package/dist/memory/schema.d.ts.map +1 -0
- package/dist/memory/schema.js +112 -0
- package/dist/memory/store.d.ts +36 -0
- package/dist/memory/store.d.ts.map +1 -0
- package/dist/memory/store.js +4 -0
- package/dist/memory/types.d.ts +250 -0
- package/dist/memory/types.d.ts.map +1 -0
- package/dist/memory/types.js +4 -0
- package/dist/storage/base.d.ts +6 -1
- package/dist/storage/base.d.ts.map +1 -1
- package/dist/storage/in-memory.d.ts +24 -2
- package/dist/storage/in-memory.d.ts.map +1 -1
- package/dist/storage/in-memory.js +131 -0
- package/dist/storage/thread.d.ts +1 -1
- package/dist/thread/__tests__/integration.test.js +1 -1
- package/dist/thread/__tests__/mock.d.ts +1 -1
- package/dist/thread/__tests__/namespace.test.js +1 -1
- package/dist/thread/__tests__/thread.test.js +1 -1
- package/dist/thread/thread.d.ts +2 -2
- package/dist/thread/thread.d.ts.map +1 -1
- package/dist/{types/thread.d.ts → thread/types.d.ts} +2 -2
- package/dist/thread/types.d.ts.map +1 -0
- package/dist/thread/utils.d.ts +2 -2
- package/dist/thread/utils.d.ts.map +1 -1
- package/package.json +4 -2
- package/src/{types/agent.ts → agent/types.ts} +1 -1
- package/src/agent.ts +78 -2
- package/src/api/__tests__/threads.test.ts +2 -2
- package/src/api/models/thread.ts +1 -1
- package/src/api/resources/threads/events.ts +1 -1
- package/src/api/resources/threads/threads.ts +2 -2
- package/src/api/resources/threads/types.ts +2 -2
- package/src/context.ts +6 -136
- package/src/guardrail.ts +2 -2
- package/src/index.ts +35 -6
- package/src/internal.ts +2 -2
- package/src/kernl/index.ts +8 -0
- package/src/{kernl.ts → kernl/kernl.ts} +40 -28
- package/src/kernl/types.ts +106 -0
- package/src/lib/error.ts +2 -2
- package/src/lifecycle.ts +2 -2
- package/src/memory/codecs/domain.ts +115 -0
- package/src/memory/codecs/identity.ts +28 -0
- package/src/memory/codecs/index.ts +61 -0
- package/src/memory/codecs/tpuf.ts +115 -0
- package/src/memory/encoder.ts +56 -0
- package/src/memory/handle.ts +189 -0
- package/src/memory/index.ts +49 -0
- package/src/memory/indexes.ts +108 -0
- package/src/memory/memory.ts +143 -0
- package/src/memory/schema.ts +142 -0
- package/src/memory/store.ts +47 -0
- package/src/memory/types.ts +282 -0
- package/src/storage/__tests__/in-memory.test.ts +1 -1
- package/src/storage/base.ts +7 -1
- package/src/storage/in-memory.ts +170 -2
- package/src/storage/thread.ts +1 -1
- package/src/thread/__tests__/integration.test.ts +1 -1
- package/src/thread/__tests__/mock.ts +1 -1
- package/src/thread/__tests__/thread.test.ts +1 -1
- package/src/thread/thread.ts +2 -2
- package/src/{types/thread.ts → thread/types.ts} +1 -1
- package/src/thread/utils.ts +2 -2
- package/tsconfig.tsbuildinfo +1 -0
- package/dist/api/__tests__/cursor-page.test.d.ts +0 -2
- package/dist/api/__tests__/cursor-page.test.d.ts.map +0 -1
- package/dist/api/__tests__/cursor-page.test.js +0 -414
- package/dist/api/__tests__/offset-page.test.d.ts +0 -2
- package/dist/api/__tests__/offset-page.test.d.ts.map +0 -1
- package/dist/api/__tests__/offset-page.test.js +0 -510
- package/dist/api/pagination/base.d.ts +0 -48
- package/dist/api/pagination/base.d.ts.map +0 -1
- package/dist/api/pagination/base.js +0 -45
- package/dist/api/pagination/cursor.d.ts +0 -44
- package/dist/api/pagination/cursor.d.ts.map +0 -1
- package/dist/api/pagination/cursor.js +0 -52
- package/dist/api/pagination/offset.d.ts +0 -42
- package/dist/api/pagination/offset.d.ts.map +0 -1
- package/dist/api/pagination/offset.js +0 -55
- package/dist/kernl/threads.d.ts +0 -110
- package/dist/kernl/threads.d.ts.map +0 -1
- package/dist/kernl/threads.js +0 -126
- package/dist/kernl.d.ts +0 -60
- package/dist/kernl.d.ts.map +0 -1
- package/dist/kernl.js +0 -113
- package/dist/types/agent.d.ts.map +0 -1
- package/dist/types/kernl.d.ts +0 -42
- package/dist/types/kernl.d.ts.map +0 -1
- package/dist/types/thread.d.ts.map +0 -1
- package/src/api/__tests__/cursor-page.test.ts +0 -512
- package/src/api/__tests__/offset-page.test.ts +0 -624
- package/src/api/pagination/base.ts +0 -79
- package/src/api/pagination/cursor.ts +0 -86
- package/src/api/pagination/offset.ts +0 -89
- package/src/types/kernl.ts +0 -51
- /package/dist/{types/agent.js → agent/types.js} +0 -0
- /package/dist/{types/kernl.js → kernl/types.js} +0 -0
- /package/dist/{types/thread.js → thread/types.js} +0 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { LanguageModel } from "@kernl-sdk/protocol";
|
|
2
|
+
import { resolveEmbeddingModel } from "@kernl-sdk/retrieval";
|
|
2
3
|
|
|
3
4
|
import { Agent } from "@/agent";
|
|
4
5
|
import { UnknownContext } from "@/context";
|
|
@@ -7,10 +8,17 @@ import type { Thread } from "@/thread";
|
|
|
7
8
|
import type { ResolvedAgentResponse } from "@/guardrail";
|
|
8
9
|
import { InMemoryStorage, type KernlStorage } from "@/storage";
|
|
9
10
|
import { RThreads } from "@/api/resources/threads";
|
|
11
|
+
import {
|
|
12
|
+
Memory,
|
|
13
|
+
MemoryByteEncoder,
|
|
14
|
+
MemoryIndexHandle,
|
|
15
|
+
buildMemoryIndexSchema,
|
|
16
|
+
} from "@/memory";
|
|
10
17
|
|
|
11
|
-
import type {
|
|
12
|
-
import type {
|
|
13
|
-
|
|
18
|
+
import type { ThreadExecuteResult, ThreadStreamEvent } from "@/thread/types";
|
|
19
|
+
import type { AgentResponseType } from "@/agent/types";
|
|
20
|
+
|
|
21
|
+
import type { KernlOptions } from "./types";
|
|
14
22
|
|
|
15
23
|
/**
|
|
16
24
|
* The kernl - manages agent processes, scheduling, and task lifecycle.
|
|
@@ -25,16 +33,43 @@ export class Kernl extends KernlHooks<UnknownContext, AgentResponseType> {
|
|
|
25
33
|
readonly storage: KernlStorage;
|
|
26
34
|
athreads: Map<string, Thread<any, any>> = new Map(); /* active threads */
|
|
27
35
|
|
|
28
|
-
private initPromise: Promise<void> | null = null;
|
|
29
|
-
|
|
30
36
|
// --- public API ---
|
|
31
37
|
readonly threads: RThreads; /* Threads resource */
|
|
38
|
+
readonly memories: Memory; /* Memory system */
|
|
32
39
|
|
|
33
40
|
constructor(options: KernlOptions = {}) {
|
|
34
41
|
super();
|
|
35
42
|
this.storage = options.storage?.db ?? new InMemoryStorage();
|
|
36
43
|
this.storage.bind({ agents: this.agents, models: this.models });
|
|
37
44
|
this.threads = new RThreads(this.storage.threads);
|
|
45
|
+
|
|
46
|
+
// initialize memory
|
|
47
|
+
const embeddingModel =
|
|
48
|
+
options.memory?.embeddingModel ?? "openai/text-embedding-3-small";
|
|
49
|
+
const embedder =
|
|
50
|
+
typeof embeddingModel === "string"
|
|
51
|
+
? resolveEmbeddingModel<string>(embeddingModel)
|
|
52
|
+
: embeddingModel;
|
|
53
|
+
const encoder = new MemoryByteEncoder(embedder);
|
|
54
|
+
|
|
55
|
+
const vector = options.storage?.vector;
|
|
56
|
+
const indexId = options.memory?.indexId ?? "memories_sindex";
|
|
57
|
+
const dimensions = options.memory?.dimensions ?? 1536;
|
|
58
|
+
const providerOptions = options.memory?.indexProviderOptions ?? { schema: "kernl" };
|
|
59
|
+
|
|
60
|
+
this.memories = new Memory({
|
|
61
|
+
store: this.storage.memories,
|
|
62
|
+
search:
|
|
63
|
+
vector !== undefined
|
|
64
|
+
? new MemoryIndexHandle({
|
|
65
|
+
index: vector,
|
|
66
|
+
indexId,
|
|
67
|
+
schema: buildMemoryIndexSchema({ dimensions }),
|
|
68
|
+
providerOptions,
|
|
69
|
+
})
|
|
70
|
+
: undefined,
|
|
71
|
+
encoder,
|
|
72
|
+
});
|
|
38
73
|
}
|
|
39
74
|
|
|
40
75
|
/**
|
|
@@ -59,7 +94,6 @@ export class Kernl extends KernlHooks<UnknownContext, AgentResponseType> {
|
|
|
59
94
|
async spawn<TContext, TResponse extends AgentResponseType>(
|
|
60
95
|
thread: Thread<TContext, TResponse>,
|
|
61
96
|
): Promise<ThreadExecuteResult<ResolvedAgentResponse<TResponse>>> {
|
|
62
|
-
await this.ensureInitialized();
|
|
63
97
|
this.athreads.set(thread.tid, thread);
|
|
64
98
|
try {
|
|
65
99
|
return await thread.execute();
|
|
@@ -76,7 +110,6 @@ export class Kernl extends KernlHooks<UnknownContext, AgentResponseType> {
|
|
|
76
110
|
async schedule<TContext, TResponse extends AgentResponseType>(
|
|
77
111
|
thread: Thread<TContext, TResponse>,
|
|
78
112
|
): Promise<ThreadExecuteResult<ResolvedAgentResponse<TResponse>>> {
|
|
79
|
-
await this.ensureInitialized();
|
|
80
113
|
this.athreads.set(thread.tid, thread);
|
|
81
114
|
try {
|
|
82
115
|
return await thread.execute();
|
|
@@ -93,7 +126,6 @@ export class Kernl extends KernlHooks<UnknownContext, AgentResponseType> {
|
|
|
93
126
|
async *spawnStream<TContext, TResponse extends AgentResponseType>(
|
|
94
127
|
thread: Thread<TContext, TResponse>,
|
|
95
128
|
): AsyncIterable<ThreadStreamEvent> {
|
|
96
|
-
await this.ensureInitialized();
|
|
97
129
|
this.athreads.set(thread.tid, thread);
|
|
98
130
|
try {
|
|
99
131
|
yield* thread.stream();
|
|
@@ -110,7 +142,6 @@ export class Kernl extends KernlHooks<UnknownContext, AgentResponseType> {
|
|
|
110
142
|
async *scheduleStream<TContext, TResponse extends AgentResponseType>(
|
|
111
143
|
thread: Thread<TContext, TResponse>,
|
|
112
144
|
): AsyncIterable<ThreadStreamEvent> {
|
|
113
|
-
await this.ensureInitialized();
|
|
114
145
|
this.athreads.set(thread.tid, thread);
|
|
115
146
|
try {
|
|
116
147
|
yield* thread.stream();
|
|
@@ -118,23 +149,4 @@ export class Kernl extends KernlHooks<UnknownContext, AgentResponseType> {
|
|
|
118
149
|
this.athreads.delete(thread.tid);
|
|
119
150
|
}
|
|
120
151
|
}
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* Ensure the underlying storage backend has been initialized.
|
|
124
|
-
*
|
|
125
|
-
* This is called lazily on first use so that callers do not need to worry
|
|
126
|
-
* about calling storage.init() themselves. Safe and idempotent to call
|
|
127
|
-
* multiple times; concurrent calls share the same initialization promise.
|
|
128
|
-
*/
|
|
129
|
-
private async ensureInitialized(): Promise<void> {
|
|
130
|
-
if (!this.initPromise) {
|
|
131
|
-
this.initPromise = this.storage.init().catch((error) => {
|
|
132
|
-
// allow a retry if initialization fails.
|
|
133
|
-
this.initPromise = null;
|
|
134
|
-
throw error;
|
|
135
|
-
});
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
await this.initPromise;
|
|
139
|
-
}
|
|
140
152
|
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { LanguageModel, EmbeddingModel } from "@kernl-sdk/protocol";
|
|
2
|
+
import { SearchIndex } from "@kernl-sdk/retrieval";
|
|
3
|
+
|
|
4
|
+
import { Agent } from "@/agent";
|
|
5
|
+
import { KernlStorage } from "@/storage";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Storage configuration for Kernl.
|
|
9
|
+
*/
|
|
10
|
+
export interface StorageOptions {
|
|
11
|
+
/**
|
|
12
|
+
* Relational database storage (threads, tasks, traces).
|
|
13
|
+
*/
|
|
14
|
+
db?: KernlStorage;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Vector search index for semantic memory search.
|
|
18
|
+
* Supports pgvector, Turbopuffer, etc.
|
|
19
|
+
*/
|
|
20
|
+
vector?: SearchIndex;
|
|
21
|
+
|
|
22
|
+
// Future storage layers (deferred):
|
|
23
|
+
// blob?: BlobStore;
|
|
24
|
+
// lake?: DataLake;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Memory system configuration.
|
|
29
|
+
*/
|
|
30
|
+
export interface MemoryOptions {
|
|
31
|
+
/**
|
|
32
|
+
* Embedding model for memory encoding.
|
|
33
|
+
*
|
|
34
|
+
* Can be:
|
|
35
|
+
* - A string like "openai/text-embedding-3-small" (resolved via provider registry)
|
|
36
|
+
* - An EmbeddingModel instance
|
|
37
|
+
*
|
|
38
|
+
* @default "openai/text-embedding-3-small"
|
|
39
|
+
*/
|
|
40
|
+
embeddingModel?: string | EmbeddingModel<string>;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Logical index ID used by the search backend.
|
|
44
|
+
* - For pgvector: becomes the table name (with schema from indexProviderOptions)
|
|
45
|
+
* - For Turbopuffer: becomes the namespace
|
|
46
|
+
* @default "kernl_memories_index"
|
|
47
|
+
*/
|
|
48
|
+
indexId?: string;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Backend-specific options passed to SearchIndex.createIndex().
|
|
52
|
+
* - For pgvector: { schema?: string } controls schema (default: "kernl")
|
|
53
|
+
* - For Turbopuffer: not used
|
|
54
|
+
*/
|
|
55
|
+
indexProviderOptions?: Record<string, unknown>;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Vector dimensions for embeddings.
|
|
59
|
+
* Only needed if embedding model doesn't provide this automatically.
|
|
60
|
+
* @default 1536
|
|
61
|
+
*/
|
|
62
|
+
dimensions?: number;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Similarity metric for vector search.
|
|
66
|
+
* @default "cosine"
|
|
67
|
+
*/
|
|
68
|
+
similarity?: "cosine" | "euclidean" | "dot_product";
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Configuration options for creating a Kernl instance.
|
|
73
|
+
*/
|
|
74
|
+
export interface KernlOptions {
|
|
75
|
+
/**
|
|
76
|
+
* Storage configuration for persisting threads, tasks, and traces.
|
|
77
|
+
*/
|
|
78
|
+
storage?: StorageOptions;
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Memory system configuration.
|
|
82
|
+
*/
|
|
83
|
+
memory?: MemoryOptions;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Agent registry interface.
|
|
88
|
+
*
|
|
89
|
+
* Satisfied by Map<string, Agent>.
|
|
90
|
+
*/
|
|
91
|
+
export interface AgentRegistry {
|
|
92
|
+
get(id: string): Agent<any> | undefined;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Model registry interface.
|
|
97
|
+
*
|
|
98
|
+
* Satisfied by Map<string, LanguageModel>.
|
|
99
|
+
* Key format: "provider/modelId"
|
|
100
|
+
*
|
|
101
|
+
* TODO: Create an exhaustive model registry in the protocol package
|
|
102
|
+
* with all supported models and their metadata.
|
|
103
|
+
*/
|
|
104
|
+
export interface ModelRegistry {
|
|
105
|
+
get(key: string): LanguageModel | undefined;
|
|
106
|
+
}
|
package/src/lib/error.ts
CHANGED
|
@@ -9,8 +9,8 @@ import { randomID } from "@kernl-sdk/shared/lib";
|
|
|
9
9
|
// import { SerializedThread } from "@/lib/serde/thread";
|
|
10
10
|
type SerializedThread = any;
|
|
11
11
|
|
|
12
|
-
import { AgentResponseType } from "@/types
|
|
13
|
-
import { TextResponse } from "@/types
|
|
12
|
+
import { AgentResponseType } from "@/agent/types";
|
|
13
|
+
import { TextResponse } from "@/thread/types";
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* Abstract base class for all `kernl` errors
|
package/src/lifecycle.ts
CHANGED
|
@@ -5,8 +5,8 @@ import { Context, UnknownContext } from "./context";
|
|
|
5
5
|
import { Tool } from "./tool";
|
|
6
6
|
import type { ToolCall } from "@kernl-sdk/protocol";
|
|
7
7
|
|
|
8
|
-
import { AgentResponseType } from "
|
|
9
|
-
import { TextResponse } from "
|
|
8
|
+
import { AgentResponseType } from "@/agent/types";
|
|
9
|
+
import { TextResponse } from "@/thread/types";
|
|
10
10
|
|
|
11
11
|
export type EventEmitterEvents = Record<string, any[]>;
|
|
12
12
|
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Domain-level memory codecs.
|
|
3
|
+
*
|
|
4
|
+
* Codecs for transforming between memory domain types and search/index formats.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { Codec, AsyncCodec } from "@kernl-sdk/shared/lib";
|
|
8
|
+
import type { Filter as SearchFilter } from "@kernl-sdk/retrieval";
|
|
9
|
+
|
|
10
|
+
import type {
|
|
11
|
+
MemoryFilter,
|
|
12
|
+
MemoryRecord,
|
|
13
|
+
MemoryRecordUpdate,
|
|
14
|
+
IndexMemoryRecord,
|
|
15
|
+
IndexMemoryRecordPatch,
|
|
16
|
+
MemoryByteCodec,
|
|
17
|
+
} from "../types";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Codec for converting MemoryFilter to retrieval Filter.
|
|
21
|
+
*
|
|
22
|
+
* - scope.namespace → namespace
|
|
23
|
+
* - scope.entityId → entityId
|
|
24
|
+
* - scope.agentId → agentId
|
|
25
|
+
* - collections → collection: { $in: [...] }
|
|
26
|
+
* - after/before → timestamp: { $gt/$lt }
|
|
27
|
+
*
|
|
28
|
+
* Content field filtering (text, metadata, kind) is not currently supported.
|
|
29
|
+
* Text relevance is handled via vector similarity in the query, not filters.
|
|
30
|
+
*/
|
|
31
|
+
export const MEMORY_FILTER: Codec<MemoryFilter, SearchFilter> = {
|
|
32
|
+
encode(mf: MemoryFilter): SearchFilter {
|
|
33
|
+
const sf: SearchFilter = {};
|
|
34
|
+
|
|
35
|
+
// scope
|
|
36
|
+
if (mf.scope?.namespace !== undefined) sf.namespace = mf.scope.namespace;
|
|
37
|
+
if (mf.scope?.entityId !== undefined) sf.entityId = mf.scope.entityId;
|
|
38
|
+
if (mf.scope?.agentId !== undefined) sf.agentId = mf.scope.agentId;
|
|
39
|
+
|
|
40
|
+
if (mf.collections && mf.collections.length > 0) {
|
|
41
|
+
sf.collection = { $in: mf.collections };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (mf.after !== undefined || mf.before !== undefined) {
|
|
45
|
+
const ts: { $gt?: number; $lt?: number } = {};
|
|
46
|
+
if (mf.after !== undefined) ts.$gt = mf.after;
|
|
47
|
+
if (mf.before !== undefined) ts.$lt = mf.before;
|
|
48
|
+
sf.timestamp = ts;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return sf;
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
decode(_filter: SearchFilter): MemoryFilter {
|
|
55
|
+
throw new Error("MEMORY_FILTER.decode not implemented");
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Create a codec for MemoryRecord -> IndexMemoryRecord.
|
|
61
|
+
*/
|
|
62
|
+
export function recordCodec(
|
|
63
|
+
bytecodec: MemoryByteCodec,
|
|
64
|
+
): AsyncCodec<MemoryRecord, IndexMemoryRecord> {
|
|
65
|
+
return {
|
|
66
|
+
async encode(record: MemoryRecord): Promise<IndexMemoryRecord> {
|
|
67
|
+
const indexable = await bytecodec.encode(record.content);
|
|
68
|
+
return {
|
|
69
|
+
id: record.id,
|
|
70
|
+
namespace: record.scope.namespace ?? null,
|
|
71
|
+
entityId: record.scope.entityId ?? null,
|
|
72
|
+
agentId: record.scope.agentId ?? null,
|
|
73
|
+
kind: record.kind,
|
|
74
|
+
collection: record.collection,
|
|
75
|
+
timestamp: record.timestamp,
|
|
76
|
+
createdAt: record.createdAt,
|
|
77
|
+
updatedAt: record.updatedAt,
|
|
78
|
+
...indexable,
|
|
79
|
+
};
|
|
80
|
+
},
|
|
81
|
+
async decode(): Promise<MemoryRecord> {
|
|
82
|
+
throw new Error("recordCodec.decode not implemented");
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Codec for converting MemoryRecordUpdate to IndexMemoryRecordPatch.
|
|
89
|
+
*
|
|
90
|
+
* Maps patchable fields from domain update to index patch format.
|
|
91
|
+
* wmem/smem are store-only fields and are not included.
|
|
92
|
+
* content changes require full re-index, not a patch.
|
|
93
|
+
*/
|
|
94
|
+
export const PATCH_CODEC: Codec<MemoryRecordUpdate, IndexMemoryRecordPatch> = {
|
|
95
|
+
encode(update: MemoryRecordUpdate): IndexMemoryRecordPatch {
|
|
96
|
+
const patch: IndexMemoryRecordPatch = { id: update.id };
|
|
97
|
+
|
|
98
|
+
if (update.scope?.namespace !== undefined)
|
|
99
|
+
patch.namespace = update.scope.namespace;
|
|
100
|
+
if (update.scope?.entityId !== undefined)
|
|
101
|
+
patch.entityId = update.scope.entityId;
|
|
102
|
+
if (update.scope?.agentId !== undefined)
|
|
103
|
+
patch.agentId = update.scope.agentId;
|
|
104
|
+
if (update.collection !== undefined) patch.collection = update.collection;
|
|
105
|
+
if (update.timestamp !== undefined) patch.timestamp = update.timestamp;
|
|
106
|
+
if (update.updatedAt !== undefined) patch.updatedAt = update.updatedAt;
|
|
107
|
+
if (update.metadata !== undefined) patch.metadata = update.metadata;
|
|
108
|
+
|
|
109
|
+
return patch;
|
|
110
|
+
},
|
|
111
|
+
|
|
112
|
+
decode(_patch: IndexMemoryRecordPatch): MemoryRecordUpdate {
|
|
113
|
+
throw new Error("PATCH_CODEC.decode not implemented");
|
|
114
|
+
},
|
|
115
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Identity codecs - pass through unchanged.
|
|
3
|
+
*
|
|
4
|
+
* Used for backends that support the full IndexMemoryRecord schema natively.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { Codec } from "@kernl-sdk/shared/lib";
|
|
8
|
+
import type { FieldSchema, SearchQuery, UnknownDocument } from "@kernl-sdk/retrieval";
|
|
9
|
+
|
|
10
|
+
import type { IndexMemoryRecord } from "../types";
|
|
11
|
+
|
|
12
|
+
export const IDENTITY_DOC: Codec<IndexMemoryRecord, UnknownDocument> = {
|
|
13
|
+
encode: (doc) => doc as unknown as UnknownDocument,
|
|
14
|
+
decode: (row) => row as unknown as IndexMemoryRecord,
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const IDENTITY_SCHEMA: Codec<
|
|
18
|
+
Record<string, FieldSchema>,
|
|
19
|
+
Record<string, FieldSchema>
|
|
20
|
+
> = {
|
|
21
|
+
encode: (schema) => schema,
|
|
22
|
+
decode: (schema) => schema,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export const IDENTITY_QUERY: Codec<SearchQuery, SearchQuery> = {
|
|
26
|
+
encode: (q) => q,
|
|
27
|
+
decode: (q) => q,
|
|
28
|
+
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memory codecs.
|
|
3
|
+
*
|
|
4
|
+
* Re-exports all memory codecs:
|
|
5
|
+
* - Domain codecs (MEMORY_FILTER, PATCH_CODEC, recordCodec)
|
|
6
|
+
* - Backend codecs (TPUF_*, IDENTITY_*)
|
|
7
|
+
* - Backend codec registry (getBackendCodecs)
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { Codec } from "@kernl-sdk/shared/lib";
|
|
11
|
+
import type {
|
|
12
|
+
FieldSchema,
|
|
13
|
+
SearchQuery,
|
|
14
|
+
UnknownDocument,
|
|
15
|
+
} from "@kernl-sdk/retrieval";
|
|
16
|
+
|
|
17
|
+
import type { IndexMemoryRecord } from "../types";
|
|
18
|
+
import { IDENTITY_DOC, IDENTITY_SCHEMA, IDENTITY_QUERY } from "./identity";
|
|
19
|
+
import { TPUF_DOC, TPUF_SCHEMA, TPUF_QUERY } from "./tpuf";
|
|
20
|
+
|
|
21
|
+
// re-exports
|
|
22
|
+
export { MEMORY_FILTER, PATCH_CODEC, recordCodec } from "./domain";
|
|
23
|
+
export { IDENTITY_DOC, IDENTITY_SCHEMA, IDENTITY_QUERY } from "./identity";
|
|
24
|
+
export { TPUF_DOC, TPUF_SCHEMA, TPUF_QUERY } from "./tpuf";
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Backend codec set.
|
|
28
|
+
*/
|
|
29
|
+
export interface AdapterCodecs {
|
|
30
|
+
doc: Codec<IndexMemoryRecord, UnknownDocument>;
|
|
31
|
+
schema: Codec<Record<string, FieldSchema>, Record<string, FieldSchema>>;
|
|
32
|
+
query: Codec<SearchQuery, SearchQuery>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Registry of backend codecs.
|
|
37
|
+
*/
|
|
38
|
+
export const ADAPTER_CODECS: Record<string, AdapterCodecs> = {
|
|
39
|
+
turbopuffer: { doc: TPUF_DOC, schema: TPUF_SCHEMA, query: TPUF_QUERY },
|
|
40
|
+
pgvector: {
|
|
41
|
+
doc: IDENTITY_DOC,
|
|
42
|
+
schema: IDENTITY_SCHEMA,
|
|
43
|
+
query: IDENTITY_QUERY,
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Default codecs (identity) for unknown backends.
|
|
49
|
+
*/
|
|
50
|
+
const DEFAULT_CODECS: AdapterCodecs = {
|
|
51
|
+
doc: IDENTITY_DOC,
|
|
52
|
+
schema: IDENTITY_SCHEMA,
|
|
53
|
+
query: IDENTITY_QUERY,
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Get codecs for a backend, falling back to identity.
|
|
58
|
+
*/
|
|
59
|
+
export function getAdapterCodecs(adapterId: string): AdapterCodecs {
|
|
60
|
+
return ADAPTER_CODECS[adapterId] ?? DEFAULT_CODECS;
|
|
61
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turbopuffer backend codecs.
|
|
3
|
+
*
|
|
4
|
+
* Turbopuffer constraints:
|
|
5
|
+
* - Exactly one ANN vector field named "vector" per namespace.
|
|
6
|
+
*
|
|
7
|
+
* Memory model:
|
|
8
|
+
* - IndexMemoryRecord has modality-specific vectors: tvec, ivec, avec, vvec.
|
|
9
|
+
*
|
|
10
|
+
* Mapping:
|
|
11
|
+
* - tvec (text embedding) → vector
|
|
12
|
+
* - ivec/avec/vvec are dropped (not indexed in Turbopuffer)
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import type { Codec } from "@kernl-sdk/shared/lib";
|
|
16
|
+
import type {
|
|
17
|
+
FieldSchema,
|
|
18
|
+
SearchQuery,
|
|
19
|
+
RankingSignal,
|
|
20
|
+
UnknownDocument,
|
|
21
|
+
} from "@kernl-sdk/retrieval";
|
|
22
|
+
|
|
23
|
+
import type { IndexMemoryRecord } from "../types";
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Turbopuffer document codec.
|
|
27
|
+
*
|
|
28
|
+
* Maps tvec → vector, drops ivec/avec/vvec.
|
|
29
|
+
*/
|
|
30
|
+
export const TPUF_DOC: Codec<IndexMemoryRecord, UnknownDocument> = {
|
|
31
|
+
encode(doc: IndexMemoryRecord): UnknownDocument {
|
|
32
|
+
const { tvec, ivec, avec, vvec, metadata, ...rest } = doc;
|
|
33
|
+
const row: UnknownDocument = {
|
|
34
|
+
...rest,
|
|
35
|
+
metadata: metadata as UnknownDocument["metadata"], // metadata is JSONObject | null, cast to FieldValue for UnknownDocument
|
|
36
|
+
};
|
|
37
|
+
if (tvec) row.vector = tvec;
|
|
38
|
+
return row;
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
decode(row: UnknownDocument): IndexMemoryRecord {
|
|
42
|
+
const { vector, ...rest } = row;
|
|
43
|
+
return {
|
|
44
|
+
...(rest as unknown as IndexMemoryRecord),
|
|
45
|
+
tvec: Array.isArray(vector) ? (vector as number[]) : undefined,
|
|
46
|
+
};
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Turbopuffer schema codec.
|
|
52
|
+
*
|
|
53
|
+
* Maps tvec → vector, drops ivec/avec/vvec fields.
|
|
54
|
+
*/
|
|
55
|
+
export const TPUF_SCHEMA: Codec<
|
|
56
|
+
Record<string, FieldSchema>,
|
|
57
|
+
Record<string, FieldSchema>
|
|
58
|
+
> = {
|
|
59
|
+
encode(schema: Record<string, FieldSchema>): Record<string, FieldSchema> {
|
|
60
|
+
const result: Record<string, FieldSchema> = {};
|
|
61
|
+
for (const [name, field] of Object.entries(schema)) {
|
|
62
|
+
if (name === "tvec") {
|
|
63
|
+
result.vector = field;
|
|
64
|
+
} else if (name === "ivec" || name === "avec" || name === "vvec") {
|
|
65
|
+
continue;
|
|
66
|
+
} else {
|
|
67
|
+
result[name] = field;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return result;
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
decode(): Record<string, FieldSchema> {
|
|
74
|
+
throw new Error("TPUF_SCHEMA.decode not implemented");
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Turbopuffer query codec.
|
|
80
|
+
*
|
|
81
|
+
* Maps tvec → vector in query signals, drops ivec/avec/vvec.
|
|
82
|
+
* Operates on normalized SearchQuery (after planQuery).
|
|
83
|
+
*
|
|
84
|
+
* Defaults include to true so memory searches return all document attributes.
|
|
85
|
+
*/
|
|
86
|
+
export const TPUF_QUERY: Codec<SearchQuery, SearchQuery> = {
|
|
87
|
+
encode(input: SearchQuery): SearchQuery {
|
|
88
|
+
// default include to true for memory search (tpuf only returns id + score without it)
|
|
89
|
+
const include = input.include ?? true;
|
|
90
|
+
|
|
91
|
+
if (!input.query) {
|
|
92
|
+
return { ...input, include };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const signals = input.query.map((signal: RankingSignal) => {
|
|
96
|
+
const { tvec, ivec, avec, vvec, ...rest } = signal as RankingSignal & {
|
|
97
|
+
tvec?: number[];
|
|
98
|
+
ivec?: number[];
|
|
99
|
+
avec?: number[];
|
|
100
|
+
vvec?: number[];
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
if (tvec) {
|
|
104
|
+
return { ...rest, vector: tvec };
|
|
105
|
+
}
|
|
106
|
+
return rest;
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
return { ...input, query: signals, include };
|
|
110
|
+
},
|
|
111
|
+
|
|
112
|
+
decode(): SearchQuery {
|
|
113
|
+
throw new Error("TPUF_QUERY.decode not implemented");
|
|
114
|
+
},
|
|
115
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MemoryByte encoder - converts MemoryByte to IndexableByte with embeddings.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { EmbeddingModel } from "@kernl-sdk/protocol";
|
|
6
|
+
|
|
7
|
+
import type { MemoryByte, IndexableByte, MemoryByteCodec } from "./types";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Encoder that converts MemoryByte to IndexableByte.
|
|
11
|
+
*
|
|
12
|
+
* Extracts canonical text from content and computes embeddings.
|
|
13
|
+
*/
|
|
14
|
+
export class MemoryByteEncoder implements MemoryByteCodec {
|
|
15
|
+
private readonly embedder: EmbeddingModel<string>;
|
|
16
|
+
|
|
17
|
+
constructor(embedder: EmbeddingModel<string>) {
|
|
18
|
+
this.embedder = embedder;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Encode a MemoryByte to IndexableByte.
|
|
23
|
+
* Extracts text and computes embeddings for each modality.
|
|
24
|
+
*/
|
|
25
|
+
async encode(byte: MemoryByte): Promise<IndexableByte> {
|
|
26
|
+
const text = byte.text;
|
|
27
|
+
const tvec = text ? await this.embed(text) : undefined;
|
|
28
|
+
|
|
29
|
+
// TODO: embed other modalities (image, audio, video)
|
|
30
|
+
// const ivec = byte.image ? await this.embedImage(byte.image) : undefined;
|
|
31
|
+
// const avec = byte.audio ? await this.embedAudio(byte.audio) : undefined;
|
|
32
|
+
// const vvec = byte.video ? await this.embedVideo(byte.video) : undefined;
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
text,
|
|
36
|
+
tvec,
|
|
37
|
+
metadata: byte.object ?? null,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Decode is not implemented - IndexableByte cannot be converted back to MemoryByte.
|
|
43
|
+
*/
|
|
44
|
+
async decode(_indexable: IndexableByte): Promise<MemoryByte> {
|
|
45
|
+
throw new Error("MemoryByteEncoder.decode not implemented");
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Embed a text string.
|
|
50
|
+
* Exposed for query embedding.
|
|
51
|
+
*/
|
|
52
|
+
async embed(text: string): Promise<number[]> {
|
|
53
|
+
const result = await this.embedder.embed({ values: [text] });
|
|
54
|
+
return result.embeddings[0] ?? [];
|
|
55
|
+
}
|
|
56
|
+
}
|