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.
Files changed (163) hide show
  1. package/.turbo/turbo-build.log +4 -5
  2. package/.turbo/turbo-check-types.log +1 -1
  3. package/CHANGELOG.md +36 -0
  4. package/dist/agent/__tests__/concurrency.test.js +1 -1
  5. package/dist/agent/__tests__/run.test.js +1 -1
  6. package/dist/{types/agent.d.ts → agent/types.d.ts} +2 -2
  7. package/dist/agent/types.d.ts.map +1 -0
  8. package/dist/agent.d.ts +36 -4
  9. package/dist/agent.d.ts.map +1 -1
  10. package/dist/agent.js +58 -0
  11. package/dist/api/models/thread.d.ts +1 -1
  12. package/dist/api/resources/threads/threads.d.ts +1 -1
  13. package/dist/api/resources/threads/threads.d.ts.map +1 -1
  14. package/dist/api/resources/threads/threads.js +1 -1
  15. package/dist/api/resources/threads/types.d.ts +2 -2
  16. package/dist/api/resources/threads/types.d.ts.map +1 -1
  17. package/dist/context.d.ts +4 -4
  18. package/dist/context.d.ts.map +1 -1
  19. package/dist/guardrail.d.ts +2 -2
  20. package/dist/index.d.ts +5 -3
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +4 -3
  23. package/dist/internal.d.ts +2 -2
  24. package/dist/internal.js +1 -1
  25. package/dist/kernl/index.d.ts +1 -1
  26. package/dist/kernl/index.d.ts.map +1 -1
  27. package/dist/kernl/index.js +0 -1
  28. package/dist/kernl/kernl.d.ts +7 -18
  29. package/dist/kernl/kernl.d.ts.map +1 -1
  30. package/dist/kernl/kernl.js +29 -29
  31. package/dist/kernl/types.d.ts +91 -0
  32. package/dist/kernl/types.d.ts.map +1 -0
  33. package/dist/lib/error.d.ts +2 -2
  34. package/dist/lifecycle.d.ts +2 -2
  35. package/dist/memory/codec.d.ts +32 -0
  36. package/dist/memory/codec.d.ts.map +1 -0
  37. package/dist/memory/codec.js +97 -0
  38. package/dist/memory/codecs/domain.d.ts +34 -0
  39. package/dist/memory/codecs/domain.d.ts.map +1 -0
  40. package/dist/memory/codecs/domain.js +99 -0
  41. package/dist/memory/codecs/identity.d.ts +12 -0
  42. package/dist/memory/codecs/identity.d.ts.map +1 -0
  43. package/dist/memory/codecs/identity.js +17 -0
  44. package/dist/memory/codecs/index.d.ts +31 -0
  45. package/dist/memory/codecs/index.d.ts.map +1 -0
  46. package/dist/memory/codecs/index.js +39 -0
  47. package/dist/memory/codecs/tpuf.d.ts +38 -0
  48. package/dist/memory/codecs/tpuf.d.ts.map +1 -0
  49. package/dist/memory/codecs/tpuf.js +90 -0
  50. package/dist/memory/encoder.d.ts +29 -0
  51. package/dist/memory/encoder.d.ts.map +1 -0
  52. package/dist/memory/encoder.js +45 -0
  53. package/dist/memory/handle.d.ts +89 -0
  54. package/dist/memory/handle.d.ts.map +1 -0
  55. package/dist/memory/handle.js +128 -0
  56. package/dist/memory/index.d.ts +12 -0
  57. package/dist/memory/index.d.ts.map +1 -0
  58. package/dist/memory/index.js +7 -0
  59. package/dist/memory/indexes.d.ts +91 -0
  60. package/dist/memory/indexes.d.ts.map +1 -0
  61. package/dist/memory/indexes.js +7 -0
  62. package/dist/memory/memory.d.ts +51 -0
  63. package/dist/memory/memory.d.ts.map +1 -0
  64. package/dist/memory/memory.js +107 -0
  65. package/dist/memory/schema.d.ts +41 -0
  66. package/dist/memory/schema.d.ts.map +1 -0
  67. package/dist/memory/schema.js +112 -0
  68. package/dist/memory/store.d.ts +36 -0
  69. package/dist/memory/store.d.ts.map +1 -0
  70. package/dist/memory/store.js +4 -0
  71. package/dist/memory/types.d.ts +250 -0
  72. package/dist/memory/types.d.ts.map +1 -0
  73. package/dist/memory/types.js +4 -0
  74. package/dist/storage/base.d.ts +6 -1
  75. package/dist/storage/base.d.ts.map +1 -1
  76. package/dist/storage/in-memory.d.ts +24 -2
  77. package/dist/storage/in-memory.d.ts.map +1 -1
  78. package/dist/storage/in-memory.js +131 -0
  79. package/dist/storage/thread.d.ts +1 -1
  80. package/dist/thread/__tests__/integration.test.js +1 -1
  81. package/dist/thread/__tests__/mock.d.ts +1 -1
  82. package/dist/thread/__tests__/namespace.test.js +1 -1
  83. package/dist/thread/__tests__/thread.test.js +1 -1
  84. package/dist/thread/thread.d.ts +2 -2
  85. package/dist/thread/thread.d.ts.map +1 -1
  86. package/dist/{types/thread.d.ts → thread/types.d.ts} +2 -2
  87. package/dist/thread/types.d.ts.map +1 -0
  88. package/dist/thread/utils.d.ts +2 -2
  89. package/dist/thread/utils.d.ts.map +1 -1
  90. package/package.json +4 -2
  91. package/src/{types/agent.ts → agent/types.ts} +1 -1
  92. package/src/agent.ts +78 -2
  93. package/src/api/__tests__/threads.test.ts +2 -2
  94. package/src/api/models/thread.ts +1 -1
  95. package/src/api/resources/threads/events.ts +1 -1
  96. package/src/api/resources/threads/threads.ts +2 -2
  97. package/src/api/resources/threads/types.ts +2 -2
  98. package/src/context.ts +6 -136
  99. package/src/guardrail.ts +2 -2
  100. package/src/index.ts +35 -6
  101. package/src/internal.ts +2 -2
  102. package/src/kernl/index.ts +8 -0
  103. package/src/{kernl.ts → kernl/kernl.ts} +40 -28
  104. package/src/kernl/types.ts +106 -0
  105. package/src/lib/error.ts +2 -2
  106. package/src/lifecycle.ts +2 -2
  107. package/src/memory/codecs/domain.ts +115 -0
  108. package/src/memory/codecs/identity.ts +28 -0
  109. package/src/memory/codecs/index.ts +61 -0
  110. package/src/memory/codecs/tpuf.ts +115 -0
  111. package/src/memory/encoder.ts +56 -0
  112. package/src/memory/handle.ts +189 -0
  113. package/src/memory/index.ts +49 -0
  114. package/src/memory/indexes.ts +108 -0
  115. package/src/memory/memory.ts +143 -0
  116. package/src/memory/schema.ts +142 -0
  117. package/src/memory/store.ts +47 -0
  118. package/src/memory/types.ts +282 -0
  119. package/src/storage/__tests__/in-memory.test.ts +1 -1
  120. package/src/storage/base.ts +7 -1
  121. package/src/storage/in-memory.ts +170 -2
  122. package/src/storage/thread.ts +1 -1
  123. package/src/thread/__tests__/integration.test.ts +1 -1
  124. package/src/thread/__tests__/mock.ts +1 -1
  125. package/src/thread/__tests__/thread.test.ts +1 -1
  126. package/src/thread/thread.ts +2 -2
  127. package/src/{types/thread.ts → thread/types.ts} +1 -1
  128. package/src/thread/utils.ts +2 -2
  129. package/tsconfig.tsbuildinfo +1 -0
  130. package/dist/api/__tests__/cursor-page.test.d.ts +0 -2
  131. package/dist/api/__tests__/cursor-page.test.d.ts.map +0 -1
  132. package/dist/api/__tests__/cursor-page.test.js +0 -414
  133. package/dist/api/__tests__/offset-page.test.d.ts +0 -2
  134. package/dist/api/__tests__/offset-page.test.d.ts.map +0 -1
  135. package/dist/api/__tests__/offset-page.test.js +0 -510
  136. package/dist/api/pagination/base.d.ts +0 -48
  137. package/dist/api/pagination/base.d.ts.map +0 -1
  138. package/dist/api/pagination/base.js +0 -45
  139. package/dist/api/pagination/cursor.d.ts +0 -44
  140. package/dist/api/pagination/cursor.d.ts.map +0 -1
  141. package/dist/api/pagination/cursor.js +0 -52
  142. package/dist/api/pagination/offset.d.ts +0 -42
  143. package/dist/api/pagination/offset.d.ts.map +0 -1
  144. package/dist/api/pagination/offset.js +0 -55
  145. package/dist/kernl/threads.d.ts +0 -110
  146. package/dist/kernl/threads.d.ts.map +0 -1
  147. package/dist/kernl/threads.js +0 -126
  148. package/dist/kernl.d.ts +0 -60
  149. package/dist/kernl.d.ts.map +0 -1
  150. package/dist/kernl.js +0 -113
  151. package/dist/types/agent.d.ts.map +0 -1
  152. package/dist/types/kernl.d.ts +0 -42
  153. package/dist/types/kernl.d.ts.map +0 -1
  154. package/dist/types/thread.d.ts.map +0 -1
  155. package/src/api/__tests__/cursor-page.test.ts +0 -512
  156. package/src/api/__tests__/offset-page.test.ts +0 -624
  157. package/src/api/pagination/base.ts +0 -79
  158. package/src/api/pagination/cursor.ts +0 -86
  159. package/src/api/pagination/offset.ts +0 -89
  160. package/src/types/kernl.ts +0 -51
  161. /package/dist/{types/agent.js → agent/types.js} +0 -0
  162. /package/dist/{types/kernl.js → kernl/types.js} +0 -0
  163. /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 { KernlOptions } from "@/types/kernl";
12
- import type { ThreadExecuteResult, ThreadStreamEvent } from "@/types/thread";
13
- import type { AgentResponseType } from "@/types/agent";
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/agent";
13
- import { TextResponse } from "@/types/thread";
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 "./types/agent";
9
- import { TextResponse } from "./types/thread";
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
+ }