@ponraaj/dimple-sdk 0.0.0-alpha.11 → 0.0.0-alpha.13

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/dist/dimple.d.ts CHANGED
@@ -55,6 +55,12 @@ export interface IndexOptions {
55
55
  readonly namespace?: string;
56
56
  /** Embed + topic-route every unit (default true). */
57
57
  readonly embed?: boolean;
58
+ /** Write the units and hand every one that needs a vector to the durable
59
+ * `memory.embed.batch` job instead of embedding inline: units are stored
60
+ * (and lexically searchable) when `index` returns, vectors + topic routing
61
+ * land whenever a worker drains the queue. Ignored when `embed` is
62
+ * `false`. */
63
+ readonly deferEmbed?: boolean;
58
64
  /** Cap for the whole-file memory content (default 64 KiB). */
59
65
  readonly maxFileBytes?: number;
60
66
  /** Cap for each extracted chunk's content (default 8 KiB). */
@@ -83,6 +89,10 @@ export interface IndexResult {
83
89
  readonly skipped: number;
84
90
  /** Files skipped because the extension is not indexable. */
85
91
  readonly unsupported: number;
92
+ /** `memory.embed.batch` jobs enqueued by the deferred path (`deferEmbed`) —
93
+ * units stored without a vector, counted after the enqueue. Absent in the
94
+ * synchronous path, where every unit is embedded before returning. */
95
+ readonly pendingEmbeddings?: number;
86
96
  readonly commit: string | null;
87
97
  /** Files that could not be walked/read/parsed/extracted/written. */
88
98
  readonly failed: readonly {
package/dist/errors.js CHANGED
@@ -33,7 +33,7 @@ import { Effect as Effect39, Layer as Layer15, Schedule as Schedule2, Schema as
33
33
 
34
34
  // ../store/src/store.ts
35
35
  import { Config as Config4, Effect as Effect25, Layer as Layer10, Schema as Schema23 } from "effect";
36
- import { eq as eq8, and as and8, desc as desc2, asc as asc5, inArray as inArray4, ne as ne4, or, isNull as isNull2, like, sql as sql7 } from "drizzle-orm";
36
+ import { eq as eq8, and as and8, desc as desc2, asc as asc5, inArray as inArray4, ne as ne4, or, isNull as isNull2, like, sql as sql8 } from "drizzle-orm";
37
37
 
38
38
  // ../store/src/db-backend.ts
39
39
  import { Context as Context2 } from "effect";
@@ -840,6 +840,43 @@ var IndexConfig = Schema8.Struct({
840
840
  maxNeighbors: Schema8.optional(Schema8.Number),
841
841
  searchL: Schema8.optional(Schema8.Number)
842
842
  });
843
+ var ThreadCount = Schema8.Number.pipe(Schema8.refine((n) => Number.isInteger(n) && n >= 0 && n <= 1024, {
844
+ identifier: "ThreadCount"
845
+ }));
846
+ var SessionOptions = Schema8.Struct({
847
+ intraOpNumThreads: Schema8.optional(ThreadCount),
848
+ interOpNumThreads: Schema8.optional(ThreadCount),
849
+ executionMode: Schema8.optional(Schema8.Literals(["sequential", "parallel"])),
850
+ graphOptimizationLevel: Schema8.optional(Schema8.Literals(["disabled", "basic", "extended", "all", "layout"])),
851
+ enableCpuMemArena: Schema8.optional(Schema8.Boolean),
852
+ enableMemPattern: Schema8.optional(Schema8.Boolean),
853
+ gpuMemLimit: Schema8.optional(Schema8.Number),
854
+ arenaExtendStrategy: Schema8.optional(Schema8.Literals([0, 1]))
855
+ });
856
+ var mergeSessionOptions = (global, model) => {
857
+ const pick = (key) => model?.[key] ?? global?.[key];
858
+ const intraOpNumThreads = pick("intraOpNumThreads");
859
+ const interOpNumThreads = pick("interOpNumThreads");
860
+ const executionMode = pick("executionMode");
861
+ const graphOptimizationLevel = pick("graphOptimizationLevel");
862
+ const enableCpuMemArena = pick("enableCpuMemArena");
863
+ const enableMemPattern = pick("enableMemPattern");
864
+ const gpuMemLimit = pick("gpuMemLimit");
865
+ const arenaExtendStrategy = pick("arenaExtendStrategy");
866
+ const empty = intraOpNumThreads === undefined && interOpNumThreads === undefined && executionMode === undefined && graphOptimizationLevel === undefined && enableCpuMemArena === undefined && enableMemPattern === undefined && gpuMemLimit === undefined && arenaExtendStrategy === undefined;
867
+ if (empty)
868
+ return;
869
+ return {
870
+ ...intraOpNumThreads !== undefined ? { intraOpNumThreads } : undefined,
871
+ ...interOpNumThreads !== undefined ? { interOpNumThreads } : undefined,
872
+ ...executionMode !== undefined ? { executionMode } : undefined,
873
+ ...graphOptimizationLevel !== undefined ? { graphOptimizationLevel } : undefined,
874
+ ...enableCpuMemArena !== undefined ? { enableCpuMemArena } : undefined,
875
+ ...enableMemPattern !== undefined ? { enableMemPattern } : undefined,
876
+ ...gpuMemLimit !== undefined ? { gpuMemLimit } : undefined,
877
+ ...arenaExtendStrategy !== undefined ? { arenaExtendStrategy } : undefined
878
+ };
879
+ };
843
880
  var EmbeddingModelConfig = Schema8.Struct({
844
881
  id: Schema8.String,
845
882
  provider: Schema8.String,
@@ -877,14 +914,13 @@ var EmbeddingModelConfig = Schema8.Struct({
877
914
  pooling: Schema8.optional(Schema8.Literals(["mean", "cls", "none"])),
878
915
  normalize: Schema8.optional(Schema8.Boolean),
879
916
  cacheDir: Schema8.optional(Schema8.String),
880
- sessionOptions: Schema8.optional(Schema8.Struct({
881
- gpuMemLimit: Schema8.optional(Schema8.Number),
882
- arenaExtendStrategy: Schema8.optional(Schema8.Literals([0, 1]))
883
- })),
917
+ sessionOptions: Schema8.optional(SessionOptions),
918
+ wasm: Schema8.optional(Schema8.Struct({ numThreads: Schema8.optional(ThreadCount) })),
884
919
  autoInstall: Schema8.optional(Schema8.Boolean)
885
920
  });
886
921
  var EmbeddingConfig = Schema8.Struct({
887
922
  defaultModel: Schema8.String,
923
+ sessionOptions: Schema8.optional(SessionOptions),
888
924
  models: Schema8.Array(EmbeddingModelConfig)
889
925
  });
890
926
  var MaxAttempts = Schema8.Number.pipe(Schema8.refine((n) => Number.isInteger(n) && n > 0 && n <= 100, {
@@ -2736,8 +2772,8 @@ var resolveTopics = Effect11.fn("topics.resolveTopics")(function* (queryVector,
2736
2772
  });
2737
2773
 
2738
2774
  // ../store/src/topics/lifecycle.ts
2739
- import { Clock as Clock3, Effect as Effect20 } from "effect";
2740
- import { and as and3, eq as eq3, inArray, ne } from "drizzle-orm";
2775
+ import { Clock as Clock3, Cause as Cause3, Effect as Effect20, Exit as Exit6 } from "effect";
2776
+ import { and as and3, eq as eq3, inArray, ne, sql as sql3 } from "drizzle-orm";
2741
2777
 
2742
2778
  // ../workflow/src/queue.ts
2743
2779
  import { Effect as Effect12, Schema as Schema13 } from "effect";
@@ -2820,6 +2856,16 @@ var MemoryEmbed = JobDefinition.make("memory.embed", {
2820
2856
  error: Schema13.String,
2821
2857
  idempotencyKey: (p) => `embed:${p.memoryId}:${p.modelKey}`
2822
2858
  });
2859
+ var MemoryEmbedBatch = JobDefinition.make("memory.embed.batch", {
2860
+ payload: {
2861
+ memoryIds: Schema13.Array(Schema13.String),
2862
+ modelKey: Schema13.String,
2863
+ nonce: Schema13.String
2864
+ },
2865
+ success: Schema13.Void,
2866
+ error: Schema13.String,
2867
+ idempotencyKey: (p) => `embed-batch:${p.modelKey}:${p.nonce}`
2868
+ });
2823
2869
  var TopicMerge = JobDefinition.make("topic.merge", {
2824
2870
  payload: { topicId: Schema13.String, modelKey: Schema13.String },
2825
2871
  success: Schema13.Void,
@@ -2877,6 +2923,7 @@ var AuditWrite = JobDefinition.make("audit.write", {
2877
2923
  var queues = [
2878
2924
  TopicSplit,
2879
2925
  MemoryEmbed,
2926
+ MemoryEmbedBatch,
2880
2927
  TopicMerge,
2881
2928
  TopicRebalance,
2882
2929
  ConceptCluster,
@@ -3703,46 +3750,136 @@ var setActiveModel = Effect20.fn("topics.setActiveModel")(function* (model, conf
3703
3750
  }).pipe(routingFailure3("topics.setActiveModel.deactivate"));
3704
3751
  return { routed, alreadyInTree, deactivated };
3705
3752
  });
3753
+ var embedOneMemory = (deps, payload, ctx) => Effect20.gen(function* () {
3754
+ const envelope = payload;
3755
+ const model = deps.getModelConfig(envelope.modelKey);
3756
+ if (!model) {
3757
+ return yield* Effect20.fail(new JobFailure({
3758
+ jobId: ctx.jobId,
3759
+ message: `memory.embed: unknown model "${envelope.modelKey}"`
3760
+ }));
3761
+ }
3762
+ const content = yield* deps.getMemoryContent(envelope.memoryId).pipe(Effect20.catchTag("Store.NotFound", () => Effect20.succeed(undefined)), Effect20.mapError((e) => new JobFailure({ jobId: ctx.jobId, message: `memory.embed: load content: ${e}` })));
3763
+ if (content === undefined) {
3764
+ yield* Effect20.logDebug("embed.stale-reoffer").pipe(Effect20.annotateLogs({
3765
+ scope: "@dimple/store",
3766
+ event: "embed.stale-reoffer",
3767
+ memoryId: envelope.memoryId,
3768
+ modelKey: envelope.modelKey
3769
+ }));
3770
+ return;
3771
+ }
3772
+ const vector = yield* deps.embedOne(content, model).pipe(Effect20.mapError((e) => new JobFailure({ jobId: ctx.jobId, message: `memory.embed: embed: ${e}` })));
3773
+ const backend = yield* Service2;
3774
+ const db = backend.db;
3775
+ const existing = yield* Effect20.tryPromise({
3776
+ try: () => db.select({ memoryId: topicMemberships.memoryId }).from(topicMemberships).where(and3(eq3(topicMemberships.memoryId, envelope.memoryId), eq3(topicMemberships.modelKey, model.id))),
3777
+ catch: (cause) => new StoreError({ message: `memory.embed: membership check: ${cause}` })
3778
+ }).pipe(Effect20.mapError((e) => new JobFailure({ jobId: ctx.jobId, message: `memory.embed: membership check: ${e}` })));
3779
+ yield* ensureTopicTree(model, deps.config).pipe(Effect20.mapError((e) => new JobFailure({ jobId: ctx.jobId, message: `memory.embed: seed tree: ${e}` })));
3780
+ yield* backend.ensureVectorTable(model).pipe(Effect20.mapError((e) => new JobFailure({ jobId: ctx.jobId, message: `memory.embed: vector table: ${e}` })));
3781
+ yield* backend.insertVector(envelope.memoryId, [...vector], model).pipe(Effect20.mapError((e) => new JobFailure({ jobId: ctx.jobId, message: `memory.embed: insert vector: ${e}` })));
3782
+ yield* Effect20.tryPromise({
3783
+ try: () => db.update(memories).set({ embedding: [...vector], embeddingModel: model.id }).where(eq3(memories.id, envelope.memoryId)),
3784
+ catch: (cause) => new StoreError({ message: `memory.embed: stamp embedding: ${cause}` })
3785
+ }).pipe(Effect20.mapError((e) => new JobFailure({ jobId: ctx.jobId, message: `memory.embed: stamp embedding: ${e}` })));
3786
+ if (existing.length === 0) {
3787
+ yield* routeInsert(envelope.memoryId, [...vector], model, deps.config).pipe(Effect20.mapError((e) => new JobFailure({ jobId: ctx.jobId, message: `memory.embed: route: ${e}` })));
3788
+ }
3789
+ yield* Effect20.logInfo("embed.completed").pipe(Effect20.annotateLogs({
3790
+ scope: "@dimple/store",
3791
+ event: "embed.completed",
3792
+ memoryId: envelope.memoryId,
3793
+ modelKey: envelope.modelKey,
3794
+ reroute: existing.length === 0
3795
+ }));
3796
+ return;
3797
+ });
3706
3798
  function memoryEmbedHandler(deps) {
3799
+ const run = (payload, ctx) => embedOneMemory(deps, payload, ctx);
3800
+ return deps.backend ? (payload, ctx) => run(payload, ctx).pipe(Effect20.provideService(Service2, deps.backend)) : run;
3801
+ }
3802
+ var logBatchCompleted = (modelKey, embedded, failed, ctx) => Effect20.logInfo("embed.batch.completed").pipe(Effect20.annotateLogs({
3803
+ scope: "@dimple/store",
3804
+ event: "embed.batch.completed",
3805
+ jobId: ctx.jobId,
3806
+ modelKey,
3807
+ embedded,
3808
+ failed
3809
+ }));
3810
+ var batchFailure = (ctx, failed, total, first) => Effect20.fail(new JobFailure({
3811
+ jobId: ctx.jobId,
3812
+ message: `memory.embed.batch: ${failed}/${total} failed — ${first}`
3813
+ }));
3814
+ function memoryEmbedBatchHandler(deps) {
3707
3815
  const run = (payload, ctx) => Effect20.gen(function* () {
3708
- const envelope = payload;
3709
- const model = deps.getModelConfig(envelope.modelKey);
3816
+ const model = deps.getModelConfig(payload.modelKey);
3710
3817
  if (!model) {
3711
3818
  return yield* Effect20.fail(new JobFailure({
3712
3819
  jobId: ctx.jobId,
3713
- message: `memory.embed: unknown model "${envelope.modelKey}"`
3820
+ message: `memory.embed: unknown model "${payload.modelKey}"`
3714
3821
  }));
3715
3822
  }
3716
- const content = yield* deps.getMemoryContent(envelope.memoryId).pipe(Effect20.catchTag("Store.NotFound", () => Effect20.succeed(undefined)), Effect20.mapError((e) => new JobFailure({ jobId: ctx.jobId, message: `memory.embed: load content: ${e}` })));
3717
- if (content === undefined) {
3823
+ const backend = yield* Service2;
3824
+ const db = backend.db;
3825
+ let failed = 0;
3826
+ let first;
3827
+ const embedded = [];
3828
+ const stale = [];
3829
+ yield* Effect20.forEach(payload.memoryIds, (memoryId) => Effect20.gen(function* () {
3830
+ const content = yield* deps.getMemoryContent(memoryId).pipe(Effect20.catchTag("Store.NotFound", () => Effect20.succeed(undefined)), Effect20.mapError((e) => new JobFailure({ jobId: ctx.jobId, message: `memory.embed: load content: ${e}` })));
3831
+ if (content === undefined) {
3832
+ stale.push(memoryId);
3833
+ return;
3834
+ }
3835
+ const vector = yield* deps.embedOne(content, model).pipe(Effect20.mapError((e) => new JobFailure({ jobId: ctx.jobId, message: `memory.embed: embed: ${e}` })));
3836
+ embedded.push({ memoryId, vector });
3837
+ }).pipe(Effect20.exit, Effect20.tap((exit) => {
3838
+ if (Exit6.isSuccess(exit))
3839
+ return Effect20.void;
3840
+ failed += 1;
3841
+ first ??= Cause3.pretty(exit.cause);
3842
+ return Effect20.void;
3843
+ })), { concurrency: 1, discard: true });
3844
+ for (const memoryId of stale) {
3718
3845
  yield* Effect20.logDebug("embed.stale-reoffer").pipe(Effect20.annotateLogs({
3719
3846
  scope: "@dimple/store",
3720
3847
  event: "embed.stale-reoffer",
3721
- memoryId: envelope.memoryId,
3722
- modelKey: envelope.modelKey
3848
+ memoryId,
3849
+ modelKey: payload.modelKey
3723
3850
  }));
3851
+ }
3852
+ if (embedded.length === 0) {
3853
+ yield* logBatchCompleted(payload.modelKey, 0, failed, ctx);
3854
+ if (failed > 0) {
3855
+ return yield* batchFailure(ctx, failed, payload.memoryIds.length, first);
3856
+ }
3724
3857
  return;
3725
3858
  }
3726
- const vector = yield* deps.embedOne(content, model).pipe(Effect20.mapError((e) => new JobFailure({ jobId: ctx.jobId, message: `memory.embed: embed: ${e}` })));
3727
- const backend = yield* Service2;
3728
- const db = backend.db;
3729
- const existing = yield* Effect20.tryPromise({
3730
- try: () => db.select({ memoryId: topicMemberships.memoryId }).from(topicMemberships).where(and3(eq3(topicMemberships.memoryId, envelope.memoryId), eq3(topicMemberships.modelKey, model.id))),
3731
- catch: (cause) => new StoreError({ message: `memory.embed: membership check: ${cause}` })
3732
- }).pipe(Effect20.mapError((e) => new JobFailure({ jobId: ctx.jobId, message: `memory.embed: membership check: ${e}` })));
3733
3859
  yield* ensureTopicTree(model, deps.config).pipe(Effect20.mapError((e) => new JobFailure({ jobId: ctx.jobId, message: `memory.embed: seed tree: ${e}` })));
3734
3860
  yield* backend.ensureVectorTable(model).pipe(Effect20.mapError((e) => new JobFailure({ jobId: ctx.jobId, message: `memory.embed: vector table: ${e}` })));
3735
- yield* backend.insertVector(envelope.memoryId, [...vector], model).pipe(Effect20.mapError((e) => new JobFailure({ jobId: ctx.jobId, message: `memory.embed: insert vector: ${e}` })));
3736
- if (existing.length === 0) {
3737
- yield* routeInsert(envelope.memoryId, [...vector], model, deps.config).pipe(Effect20.mapError((e) => new JobFailure({ jobId: ctx.jobId, message: `memory.embed: route: ${e}` })));
3861
+ const ids = embedded.map((e) => e.memoryId);
3862
+ const members = yield* Effect20.tryPromise({
3863
+ try: () => db.select({ memoryId: topicMemberships.memoryId }).from(topicMemberships).where(and3(eq3(topicMemberships.modelKey, model.id), inArray(topicMemberships.memoryId, ids))),
3864
+ catch: (cause) => new StoreError({ message: `memory.embed: membership check: ${cause}` })
3865
+ }).pipe(Effect20.mapError((e) => new JobFailure({ jobId: ctx.jobId, message: `memory.embed: membership check: ${e}` })));
3866
+ const memberSet = new Set(members.map((r) => r.memoryId));
3867
+ const entries = embedded.map((e) => ({ memoryId: e.memoryId, vector: [...e.vector] }));
3868
+ yield* backend.insertVectors(entries, model).pipe(Effect20.mapError((e) => new JobFailure({ jobId: ctx.jobId, message: `memory.embed: insert vectors: ${e}` })));
3869
+ yield* Effect20.tryPromise({
3870
+ try: () => db.transaction(async (tx) => {
3871
+ await tx.run(sql3`UPDATE ${memories} SET embedding = CASE id ${sql3.join(entries.map((entry) => sql3`WHEN ${entry.memoryId} THEN ${new Float32Array(entry.vector).buffer}`), sql3` `)} END, embedding_model = ${model.id} WHERE id IN (${sql3.join(entries.map((entry) => sql3`${entry.memoryId}`), sql3`, `)})`);
3872
+ }),
3873
+ catch: (cause) => new StoreError({ message: `memory.embed: stamp embeddings: ${cause}` })
3874
+ }).pipe(Effect20.mapError((e) => new JobFailure({ jobId: ctx.jobId, message: `memory.embed: stamp embeddings: ${e}` })));
3875
+ const fresh = entries.filter((entry) => !memberSet.has(entry.memoryId));
3876
+ if (fresh.length > 0) {
3877
+ yield* routeInsertMany(fresh, model, deps.config).pipe(Effect20.mapError((e) => new JobFailure({ jobId: ctx.jobId, message: `memory.embed: route: ${e}` })));
3878
+ }
3879
+ yield* logBatchCompleted(payload.modelKey, embedded.length, failed, ctx);
3880
+ if (failed > 0) {
3881
+ return yield* batchFailure(ctx, failed, payload.memoryIds.length, first);
3738
3882
  }
3739
- yield* Effect20.logInfo("embed.completed").pipe(Effect20.annotateLogs({
3740
- scope: "@dimple/store",
3741
- event: "embed.completed",
3742
- memoryId: envelope.memoryId,
3743
- modelKey: envelope.modelKey,
3744
- reroute: existing.length === 0
3745
- }));
3746
3883
  return;
3747
3884
  });
3748
3885
  return deps.backend ? (payload, ctx) => run(payload, ctx).pipe(Effect20.provideService(Service2, deps.backend)) : run;
@@ -3750,19 +3887,19 @@ function memoryEmbedHandler(deps) {
3750
3887
 
3751
3888
  // ../store/src/topics/jobs.ts
3752
3889
  import { Clock as Clock4, Effect as Effect21 } from "effect";
3753
- import { and as and4, asc as asc3, eq as eq4, inArray as inArray2, sql as sql4 } from "drizzle-orm";
3890
+ import { and as and4, asc as asc3, eq as eq4, inArray as inArray2, sql as sql5 } from "drizzle-orm";
3754
3891
 
3755
3892
  // ../store/src/topics/topic-vectors.ts
3756
- import { sql as sql3 } from "drizzle-orm";
3893
+ import { sql as sql4 } from "drizzle-orm";
3757
3894
  var topicVectorTableName2 = (model) => `topic_vec_${model.dimensions}_${model.id.replace(/[^a-zA-Z0-9_]/g, "_")}`;
3758
3895
  var deleteTopicVectorRow = async (runner, model, topicKey) => {
3759
3896
  const table = topicVectorTableName2(model);
3760
- await runner.run(sql3`DELETE FROM ${sql3.raw(table)} WHERE topic_key = ${topicKey}`);
3897
+ await runner.run(sql4`DELETE FROM ${sql4.raw(table)} WHERE topic_key = ${topicKey}`);
3761
3898
  };
3762
3899
  var writeTopicVector = async (runner, model, topicKey, ls) => {
3763
3900
  const table = topicVectorTableName2(model);
3764
- await runner.run(sql3`
3765
- INSERT OR REPLACE INTO ${sql3.raw(table)} (topic_key, sum_vector, model_key)
3901
+ await runner.run(sql4`
3902
+ INSERT OR REPLACE INTO ${sql4.raw(table)} (topic_key, sum_vector, model_key)
3766
3903
  VALUES (${topicKey}, vector32(${JSON.stringify([...ls])}), ${model.id})
3767
3904
  `);
3768
3905
  };
@@ -3853,7 +3990,7 @@ var splitTopic = Effect21.fn("TopicSplit.splitTopic")(function* (topicKey, model
3853
3990
  }).onConflictDoUpdate({
3854
3991
  target: [topicGrowthState.topicKey, topicGrowthState.modelKey],
3855
3992
  set: {
3856
- splitRejectedCount: sql4`${topicGrowthState.splitRejectedCount} + 1`,
3993
+ splitRejectedCount: sql5`${topicGrowthState.splitRejectedCount} + 1`,
3857
3994
  splitDeferred: 1
3858
3995
  }
3859
3996
  });
@@ -4543,7 +4680,7 @@ var staleNoOp = (notFound, model) => Effect22.gen(function* () {
4543
4680
 
4544
4681
  // ../store/src/topics/observability.ts
4545
4682
  import { Effect as Effect23 } from "effect";
4546
- import { and as and6, desc, eq as eq6, inArray as inArray3, ne as ne3, sql as sql5 } from "drizzle-orm";
4683
+ import { and as and6, desc, eq as eq6, inArray as inArray3, ne as ne3, sql as sql6 } from "drizzle-orm";
4547
4684
  var DEFAULT_MAX_LEAF_ITEMS3 = 128;
4548
4685
  var DEFAULT_SPLIT_DEFERRED_HARD_LIMIT2 = 512;
4549
4686
  var DEFAULT_CHANGE_LOG_LIMIT = 50;
@@ -4587,7 +4724,7 @@ var topicMetrics = Effect23.fn("topics.topicMetrics")(function* (modelKey, confi
4587
4724
  }
4588
4725
  leafMemberCounts.sort((a, b) => a - b);
4589
4726
  const growth = yield* Effect23.tryPromise({
4590
- try: () => db.select({ total: sql5`coalesce(sum(${topicGrowthState.splitRejectedCount}), 0)` }).from(topicGrowthState).where(eq6(topicGrowthState.modelKey, modelKey)),
4727
+ try: () => db.select({ total: sql6`coalesce(sum(${topicGrowthState.splitRejectedCount}), 0)` }).from(topicGrowthState).where(eq6(topicGrowthState.modelKey, modelKey)),
4591
4728
  catch: (cause) => cause
4592
4729
  }).pipe(routingFailure4("topics.topicMetrics.growth"));
4593
4730
  const splitRejectedTotal = Number(growth[0]?.total ?? 0);
@@ -4643,7 +4780,7 @@ var topicHealth = Effect23.fn("topics.topicHealth")(function* (modelKey, config)
4643
4780
  catch: (cause) => cause
4644
4781
  }).pipe(routingFailure4("topics.topicHealth.topics"));
4645
4782
  const countRows = yield* Effect23.tryPromise({
4646
- try: () => db.select({ topicKey: topicMemberships.topicKey, count: sql5`count(*)` }).from(topicMemberships).where(eq6(topicMemberships.modelKey, modelKey)).groupBy(topicMemberships.topicKey),
4783
+ try: () => db.select({ topicKey: topicMemberships.topicKey, count: sql6`count(*)` }).from(topicMemberships).where(eq6(topicMemberships.modelKey, modelKey)).groupBy(topicMemberships.topicKey),
4647
4784
  catch: (cause) => cause
4648
4785
  }).pipe(routingFailure4("topics.topicHealth.memberships"));
4649
4786
  const membershipCount = new Map(countRows.map((r) => [r.topicKey, Number(r.count)]));
@@ -4668,7 +4805,7 @@ var topicHealth = Effect23.fn("topics.topicHealth")(function* (modelKey, config)
4668
4805
 
4669
4806
  // ../store/src/graph/observability.ts
4670
4807
  import { Effect as Effect24 } from "effect";
4671
- import { and as and7, eq as eq7, isNull, sql as sql6 } from "drizzle-orm";
4808
+ import { and as and7, eq as eq7, isNull, sql as sql7 } from "drizzle-orm";
4672
4809
  var graphCoverage = Effect24.fn("GraphCoverage.run")(function* (modelKey) {
4673
4810
  const backend = yield* Service2;
4674
4811
  const db = backend.db;
@@ -4682,7 +4819,7 @@ var graphCoverage = Effect24.fn("GraphCoverage.run")(function* (modelKey) {
4682
4819
  const coveredMemories = totalMemories - isolatedMemories;
4683
4820
  const coveragePercent = totalMemories === 0 ? 0 : Math.round(coveredMemories / totalMemories * 1000) / 10;
4684
4821
  const pendingRows = yield* Effect24.tryPromise({
4685
- try: () => db.select({ count: sql6`count(*)` }).from(artifactEdges).where(and7(eq7(artifactEdges.status, "pending"), isNull(artifactEdges.expiredAt))),
4822
+ try: () => db.select({ count: sql7`count(*)` }).from(artifactEdges).where(and7(eq7(artifactEdges.status, "pending"), isNull(artifactEdges.expiredAt))),
4686
4823
  catch: (cause) => new StoreError({ message: `graphCoverage: pending: ${cause}` })
4687
4824
  });
4688
4825
  const pendingCandidates = totalMemories === 0 ? 0 : Number(pendingRows[0]?.count ?? 0);
@@ -5303,7 +5440,7 @@ var makeStore = (backend, modelConfig, topicsOptions, graphConfig, auditOptions,
5303
5440
  const relRows = await tx.select({ parentKey: topicRelations.parentKey }).from(topicRelations).where(and8(eq8(topicRelations.childKey, membership.topicKey), eq8(topicRelations.modelKey, membership.modelKey), eq8(topicRelations.relationKind, "parent_child")));
5304
5441
  let ancestorKey = relRows[0]?.parentKey;
5305
5442
  while (ancestorKey) {
5306
- await tx.update(topics).set({ memberCount: sql7`max(0, ${topics.memberCount} - 1)`, updatedAt: Date.now() }).where(and8(eq8(topics.topicKey, ancestorKey), eq8(topics.modelKey, membership.modelKey)));
5443
+ await tx.update(topics).set({ memberCount: sql8`max(0, ${topics.memberCount} - 1)`, updatedAt: Date.now() }).where(and8(eq8(topics.topicKey, ancestorKey), eq8(topics.modelKey, membership.modelKey)));
5307
5444
  const up = await tx.select({ parentKey: topicRelations.parentKey }).from(topicRelations).where(and8(eq8(topicRelations.childKey, ancestorKey), eq8(topicRelations.modelKey, membership.modelKey), eq8(topicRelations.relationKind, "parent_child")));
5308
5445
  ancestorKey = up[0]?.parentKey;
5309
5446
  }
@@ -5597,7 +5734,7 @@ var makeStore = (backend, modelConfig, topicsOptions, graphConfig, auditOptions,
5597
5734
  const now = Date.now();
5598
5735
  yield* Effect25.tryPromise({
5599
5736
  try: () => db.transaction(async (tx) => {
5600
- await tx.run(sql7`UPDATE ${memories2} SET embedding = CASE id ${sql7.join(entries.map((entry) => sql7`WHEN ${entry.id} THEN ${new Float32Array(entry.embedding).buffer}`), sql7` `)} END, embedding_model = ${modelName} WHERE id IN (${sql7.join(entries.map((entry) => entry.id), sql7`, `)})`);
5737
+ await tx.run(sql8`UPDATE ${memories2} SET embedding = CASE id ${sql8.join(entries.map((entry) => sql8`WHEN ${entry.id} THEN ${new Float32Array(entry.embedding).buffer}`), sql8` `)} END, embedding_model = ${modelName} WHERE id IN (${sql8.join(entries.map((entry) => entry.id), sql8`, `)})`);
5601
5738
  await tx.insert(memoryEvents2).values(entries.map((entry) => ({
5602
5739
  memoryId: entry.id,
5603
5740
  eventType: "embedding_updated",
@@ -5646,7 +5783,7 @@ var makeStore = (backend, modelConfig, topicsOptions, graphConfig, auditOptions,
5646
5783
  });
5647
5784
  const findStale = Effect25.fn("Store.findStale")(function* (model) {
5648
5785
  const rows = yield* Effect25.tryPromise({
5649
- try: () => db.select({ id: memories2.id }).from(memories2).where(and8(eq8(memories2.state, "active"), sql7`(embedding IS NULL OR embedding_model IS NULL OR embedding_model != ${model})`)),
5786
+ try: () => db.select({ id: memories2.id }).from(memories2).where(and8(eq8(memories2.state, "active"), sql8`(embedding IS NULL OR embedding_model IS NULL OR embedding_model != ${model})`)),
5650
5787
  catch: storeError("Store.findStale")
5651
5788
  });
5652
5789
  return rows.map((row) => row.id);
@@ -6284,6 +6421,7 @@ __export(exports_topics, {
6284
6421
  radiusOf: () => radiusOf,
6285
6422
  mergeOrBorrow: () => mergeOrBorrow,
6286
6423
  memoryEmbedHandler: () => memoryEmbedHandler,
6424
+ memoryEmbedBatchHandler: () => memoryEmbedBatchHandler,
6287
6425
  lloydRefine: () => lloydRefine,
6288
6426
  isActive: () => isActive,
6289
6427
  farthestPair: () => farthestPair,
@@ -6358,7 +6496,7 @@ __export(exports_graph, {
6358
6496
 
6359
6497
  // ../store/src/graph/concepts.ts
6360
6498
  import { Clock as Clock6, Effect as Effect27 } from "effect";
6361
- import { and as and9, asc as asc6, eq as eq9, isNotNull, sql as sql8 } from "drizzle-orm";
6499
+ import { and as and9, asc as asc6, eq as eq9, isNotNull, sql as sql9 } from "drizzle-orm";
6362
6500
  var DEFAULT_CLUSTER_K = 8;
6363
6501
  var DEFAULT_CLUSTER_MIN_SIZE = 3;
6364
6502
  var DEFAULT_CLUSTER_PASSES = 2;
@@ -6551,7 +6689,7 @@ var persistClusters = (db, members, config, now) => Effect27.tryPromise({
6551
6689
  }).onConflictDoUpdate({
6552
6690
  target: concepts.id,
6553
6691
  set: {
6554
- occurrenceCount: sql8`${concepts.occurrenceCount} + 1`,
6692
+ occurrenceCount: sql9`${concepts.occurrenceCount} + 1`,
6555
6693
  lastSeen: now
6556
6694
  }
6557
6695
  });
@@ -6565,7 +6703,7 @@ var persistClusters = (db, members, config, now) => Effect27.tryPromise({
6565
6703
  };
6566
6704
  })).onConflictDoUpdate({
6567
6705
  target: [memoryConcepts.conceptId, memoryConcepts.memoryId],
6568
- set: { confidence: sql8`excluded.confidence`, assignedAt: now }
6706
+ set: { confidence: sql9`excluded.confidence`, assignedAt: now }
6569
6707
  });
6570
6708
  conceptCount++;
6571
6709
  annotated += memberIndices.length;
@@ -6601,7 +6739,7 @@ function conceptClusterHandler(deps) {
6601
6739
  }
6602
6740
  // ../store/src/graph/dream.ts
6603
6741
  import { Clock as Clock7, Effect as Effect28 } from "effect";
6604
- import { and as and10, asc as asc7, eq as eq10, gt, inArray as inArray5, isNull as isNull3, sql as sql9 } from "drizzle-orm";
6742
+ import { and as and10, asc as asc7, eq as eq10, gt, inArray as inArray5, isNull as isNull3, sql as sql10 } from "drizzle-orm";
6605
6743
  var dreamStateDefaults = {
6606
6744
  minHours: 24,
6607
6745
  minNewMemories: 20,
@@ -6732,7 +6870,7 @@ var runDream = Effect28.fn("MemoryDream.run")(function* (model, classify) {
6732
6870
  };
6733
6871
  }
6734
6872
  const sinceCountRows = yield* Effect28.tryPromise({
6735
- try: () => db.select({ count: sql9`count(*)` }).from(memories).where(gt(memories.createdAt, lastDreamAt)),
6873
+ try: () => db.select({ count: sql10`count(*)` }).from(memories).where(gt(memories.createdAt, lastDreamAt)),
6736
6874
  catch: (cause) => new StoreError({ message: `memory.dream: count new: ${cause}` })
6737
6875
  });
6738
6876
  const memoriesSinceWatermark = Number(sinceCountRows[0]?.count ?? 0);
@@ -6911,7 +7049,7 @@ var runDream = Effect28.fn("MemoryDream.run")(function* (model, classify) {
6911
7049
  if (outcome.inserted)
6912
7050
  created++;
6913
7051
  }
6914
- const countRows = await tx.select({ count: sql9`count(*)` }).from(memories);
7052
+ const countRows = await tx.select({ count: sql10`count(*)` }).from(memories);
6915
7053
  const totalMemories = Number(countRows[0]?.count ?? 0);
6916
7054
  await tx.insert(dreamState).values({
6917
7055
  modelKey: model.id,
@@ -6950,7 +7088,7 @@ function graphDreamHandler(deps) {
6950
7088
  }
6951
7089
  // ../store/src/graph/repair.ts
6952
7090
  import { Clock as Clock8, Effect as Effect29 } from "effect";
6953
- import { and as and11, eq as eq11, isNotNull as isNotNull2, lt, or as or2, sql as sql10 } from "drizzle-orm";
7091
+ import { and as and11, eq as eq11, isNotNull as isNotNull2, lt, or as or2, sql as sql11 } from "drizzle-orm";
6954
7092
  var DEFAULT_EXPIRED_GRACE_MS = 7 * 24 * 60 * 60 * 1000;
6955
7093
  var runRepair = (db, includePurgeExpired, expiredGraceMs, now) => Effect29.tryPromise({
6956
7094
  try: () => db.transaction(async (tx) => {
@@ -6962,15 +7100,15 @@ var runRepair = (db, includePurgeExpired, expiredGraceMs, now) => Effect29.tryPr
6962
7100
  edgeKind: artifactEdges.edgeKind,
6963
7101
  fromMemoryId: artifactEdges.fromMemoryId,
6964
7102
  toMemoryId: artifactEdges.toMemoryId,
6965
- fromMissing: sql10`NOT EXISTS (
7103
+ fromMissing: sql11`NOT EXISTS (
6966
7104
  SELECT 1 FROM memories WHERE memories.id = ${artifactEdges.fromMemoryId}
6967
7105
  )`,
6968
- toMissing: sql10`NOT EXISTS (
7106
+ toMissing: sql11`NOT EXISTS (
6969
7107
  SELECT 1 FROM memories WHERE memories.id = ${artifactEdges.toMemoryId}
6970
7108
  )`
6971
- }).from(artifactEdges).where(or2(sql10`NOT EXISTS (
7109
+ }).from(artifactEdges).where(or2(sql11`NOT EXISTS (
6972
7110
  SELECT 1 FROM memories WHERE memories.id = ${artifactEdges.fromMemoryId}
6973
- )`, sql10`NOT EXISTS (
7111
+ )`, sql11`NOT EXISTS (
6974
7112
  SELECT 1 FROM memories WHERE memories.id = ${artifactEdges.toMemoryId}
6975
7113
  )`));
6976
7114
  for (const row of orphanRows) {
@@ -6993,12 +7131,12 @@ var runRepair = (db, includePurgeExpired, expiredGraceMs, now) => Effect29.tryPr
6993
7131
  const staleRows = await tx.select({
6994
7132
  conceptId: memoryConcepts.conceptId,
6995
7133
  memoryId: memoryConcepts.memoryId,
6996
- memoryMissing: sql10`NOT EXISTS (
7134
+ memoryMissing: sql11`NOT EXISTS (
6997
7135
  SELECT 1 FROM memories WHERE memories.id = ${memoryConcepts.memoryId}
6998
7136
  )`
6999
- }).from(memoryConcepts).where(or2(sql10`NOT EXISTS (
7137
+ }).from(memoryConcepts).where(or2(sql11`NOT EXISTS (
7000
7138
  SELECT 1 FROM concepts WHERE concepts.id = ${memoryConcepts.conceptId}
7001
- )`, sql10`NOT EXISTS (
7139
+ )`, sql11`NOT EXISTS (
7002
7140
  SELECT 1 FROM memories WHERE memories.id = ${memoryConcepts.memoryId}
7003
7141
  )`));
7004
7142
  for (const row of staleRows) {
@@ -7321,16 +7459,32 @@ var importFromDisk = () => new Promise((resolve, reject) => {
7321
7459
  reject(cause);
7322
7460
  }
7323
7461
  });
7324
- var buildSessionOptions = (opts) => ({
7325
- executionProviders: [
7326
- {
7327
- name: "cuda",
7328
- device_id: 0,
7329
- ...opts.gpuMemLimit !== undefined ? { gpu_mem_limit: opts.gpuMemLimit } : undefined,
7330
- ...opts.arenaExtendStrategy !== undefined ? { arena_extend_strategy: opts.arenaExtendStrategy } : undefined
7331
- }
7332
- ]
7333
- });
7462
+ var buildSessionOptions = (opts) => {
7463
+ const cuda = opts.gpuMemLimit !== undefined || opts.arenaExtendStrategy !== undefined;
7464
+ return {
7465
+ enableCpuMemArena: opts.enableCpuMemArena ?? false,
7466
+ ...opts.intraOpNumThreads !== undefined ? { intraOpNumThreads: opts.intraOpNumThreads } : undefined,
7467
+ ...opts.interOpNumThreads !== undefined ? { interOpNumThreads: opts.interOpNumThreads } : undefined,
7468
+ ...opts.executionMode !== undefined ? { executionMode: opts.executionMode } : undefined,
7469
+ ...opts.graphOptimizationLevel !== undefined ? { graphOptimizationLevel: opts.graphOptimizationLevel } : undefined,
7470
+ ...opts.enableMemPattern !== undefined ? { enableMemPattern: opts.enableMemPattern } : undefined,
7471
+ ...cuda ? {
7472
+ executionProviders: [
7473
+ {
7474
+ name: "cuda",
7475
+ device_id: 0,
7476
+ ...opts.gpuMemLimit !== undefined ? { gpu_mem_limit: opts.gpuMemLimit } : undefined,
7477
+ ...opts.arenaExtendStrategy !== undefined ? { arena_extend_strategy: opts.arenaExtendStrategy } : undefined
7478
+ }
7479
+ ]
7480
+ } : undefined
7481
+ };
7482
+ };
7483
+ var applyWasmOptions = (env, wasm) => {
7484
+ if (wasm?.numThreads === undefined)
7485
+ return;
7486
+ env.backends.onnx.wasm.numThreads = wasm.numThreads;
7487
+ };
7334
7488
  var defaults2 = {
7335
7489
  modelId: "Xenova/all-MiniLM-L6-v2",
7336
7490
  device: "cpu",
@@ -7372,10 +7526,11 @@ var make6 = (options) => {
7372
7526
  }));
7373
7527
  if (options.cacheDir)
7374
7528
  transformers.env.cacheDir = options.cacheDir;
7529
+ applyWasmOptions(transformers.env, options.wasm);
7375
7530
  pipe = yield* Effect32.tryPromise(() => transformers.pipeline("feature-extraction", options.modelId ?? defaults2.modelId, {
7376
7531
  device: options.device ?? defaults2.device,
7377
7532
  dtype: options.dtype ?? defaults2.dtype,
7378
- ...options.sessionOptions !== undefined ? { sessionOptions: buildSessionOptions(options.sessionOptions) } : undefined
7533
+ session_options: buildSessionOptions(options.sessionOptions ?? {})
7379
7534
  })).pipe(Effect32.mapError((cause) => new EmbeddingError({ message: `load model: ${cause}` })));
7380
7535
  return pipe;
7381
7536
  });
@@ -7676,7 +7831,7 @@ var configured8 = (options, deps) => make({
7676
7831
  deps
7677
7832
  });
7678
7833
  // ../llm/src/llm.ts
7679
- import { Cause as Cause3, Effect as Effect36, Option as Option12, Schema as Schema28 } from "effect";
7834
+ import { Cause as Cause4, Effect as Effect36, Option as Option12, Schema as Schema28 } from "effect";
7680
7835
  import {
7681
7836
  generateText as generateTextCall,
7682
7837
  NoSuchToolError,
@@ -7733,7 +7888,7 @@ var makeLLM = (model, defaults3 = {}) => {
7733
7888
  ...input.system !== undefined ? { system: input.system } : undefined,
7734
7889
  ...callOptions(input)
7735
7890
  }).then((result) => result.text),
7736
- catch: (cause) => new LLMError({ message: "LLM text generation failed", cause: Cause3.die(cause) })
7891
+ catch: (cause) => new LLMError({ message: "LLM text generation failed", cause: Cause4.die(cause) })
7737
7892
  });
7738
7893
  yield* Effect36.logDebug("llm.generate-text").pipe(Effect36.annotateLogs({
7739
7894
  scope: "@dimple/llm",
@@ -7785,7 +7940,7 @@ ${schemaJson}`,
7785
7940
  return Promise.reject(extracted.error);
7786
7941
  return input.schema.parse(extracted.value);
7787
7942
  },
7788
- catch: (cause) => new LLMError({ message: "LLM structured generation failed", cause: Cause3.die(cause) })
7943
+ catch: (cause) => new LLMError({ message: "LLM structured generation failed", cause: Cause4.die(cause) })
7789
7944
  });
7790
7945
  yield* Effect36.logDebug("llm.generate-object").pipe(Effect36.annotateLogs({
7791
7946
  scope: "@dimple/llm",
@@ -7897,7 +8052,8 @@ var embedderFromConfig = (model) => model.provider === "transformers" ? Effect39
7897
8052
  ...model.normalize !== undefined ? { normalize: model.normalize } : undefined,
7898
8053
  ...model.cacheDir !== undefined ? { cacheDir: model.cacheDir } : undefined,
7899
8054
  ...model.autoInstall !== undefined ? { autoInstall: model.autoInstall } : undefined,
7900
- ...model.sessionOptions !== undefined ? { sessionOptions: model.sessionOptions } : undefined
8055
+ ...model.sessionOptions !== undefined ? { sessionOptions: model.sessionOptions } : undefined,
8056
+ ...model.wasm !== undefined ? { wasm: model.wasm } : undefined
7901
8057
  })) : model.provider === "openai" ? model.apiKey === undefined ? Effect39.fail(new EmbeddingError({ message: `OpenAI model ${model.id} requires an apiKey` })) : Effect39.succeed(makeHostedEmbeddingModel({
7902
8058
  modelId: model.id,
7903
8059
  model: model.model,
@@ -7987,6 +8143,13 @@ var layer13 = (options) => {
7987
8143
  config: topicConfig,
7988
8144
  backend
7989
8145
  }));
8146
+ yield* register("memory.embed.batch", exports_topics.memoryEmbedBatchHandler({
8147
+ getModelConfig,
8148
+ embedOne: (text4, model) => options.embedderFor(model).pipe(Effect39.flatMap((m) => m.embedSingle(text4))),
8149
+ getMemoryContent: (memoryId) => store2.get(memoryId).pipe(Effect39.map((m) => m.content)),
8150
+ config: topicConfig,
8151
+ backend
8152
+ }));
7990
8153
  yield* register("concept.cluster", exports_graph.conceptClusterHandler({ getModelConfig, config: graphConfig, backend }));
7991
8154
  if (options.llm !== undefined) {
7992
8155
  yield* register("memory.dream", exports_graph.graphDreamHandler({
@@ -8062,6 +8225,13 @@ var layerConfig7 = (options) => Layer15.unwrap(Effect39.gen(function* () {
8062
8225
  });
8063
8226
  }
8064
8227
  const llm = cfg.config.llm;
8228
+ const models2 = embedding.models.map((model) => {
8229
+ const sessionOptions = mergeSessionOptions(embedding.sessionOptions, model.sessionOptions);
8230
+ return {
8231
+ ...model,
8232
+ ...sessionOptions !== undefined ? { sessionOptions } : undefined
8233
+ };
8234
+ });
8065
8235
  return layer13({
8066
8236
  storeUrl: cfg.config.storeUrl,
8067
8237
  ...cfg.config.defaultK !== undefined ? { defaultK: cfg.config.defaultK } : undefined,
@@ -8069,7 +8239,7 @@ var layerConfig7 = (options) => Layer15.unwrap(Effect39.gen(function* () {
8069
8239
  ...cfg.config.defaultTtlSecs !== undefined ? { defaultTtlSecs: cfg.config.defaultTtlSecs } : undefined,
8070
8240
  jobsUrl,
8071
8241
  ...options?.migrationFolder !== undefined ? { migrationFolder: options.migrationFolder } : undefined,
8072
- models: embedding.models,
8242
+ models: models2,
8073
8243
  defaultModelId: embedding.defaultModel,
8074
8244
  embedderFor: embedderFromConfig,
8075
8245
  ...cfg.config.topics !== undefined ? { topics: { config: cfg.config.topics } } : undefined,