feat(memory): surface llama.cpp diagnostics

This commit is contained in:
Vincent Koc
2026-07-10 21:14:56 -07:00
committed by Vincent Koc
parent 4b0e5f5461
commit 85a96409f1
13 changed files with 426 additions and 8 deletions
+28 -3
View File
@@ -14,6 +14,7 @@ import { afterEach, describe, expect, it, vi } from "vitest";
const memoryHostEmbeddingMocks = vi.hoisted(() => ({
createLocalEmbeddingProvider: vi.fn(),
}));
const LOCAL_EMBEDDING_RUNTIME_FACTS = Symbol.for("openclaw.localEmbeddingRuntimeFacts");
vi.mock("openclaw/plugin-sdk/memory-core-host-engine-embeddings", () => ({
createLocalEmbeddingProvider: memoryHostEmbeddingMocks.createLocalEmbeddingProvider,
@@ -59,7 +60,13 @@ describe("llama.cpp provider plugin", () => {
it("adapts the worker-backed local embedding provider", async () => {
const close = vi.fn();
memoryHostEmbeddingMocks.createLocalEmbeddingProvider.mockResolvedValue({
const getRuntimeFacts = vi.fn(() => ({
engine: "llama.cpp" as const,
state: "ready" as const,
backend: "metal" as const,
buildType: "prebuilt" as const,
}));
const workerProvider = {
id: "local",
model: DEFAULT_LLAMA_CPP_EMBEDDING_MODEL,
maxInputTokens: 2048,
@@ -67,7 +74,11 @@ describe("llama.cpp provider plugin", () => {
embedBatchInputs: vi.fn(async () => [[0.3, 0.4]]),
embedBatch: vi.fn(async () => [[1, 0]]),
close,
};
Object.defineProperty(workerProvider, LOCAL_EMBEDDING_RUNTIME_FACTS, {
value: getRuntimeFacts,
});
memoryHostEmbeddingMocks.createLocalEmbeddingProvider.mockResolvedValue(workerProvider);
const abortController = new AbortController();
const result = await llamaCppEmbeddingProviderAdapter.create({
@@ -89,6 +100,20 @@ describe("llama.cpp provider plugin", () => {
expect(provider.model).toBe(DEFAULT_LLAMA_CPP_EMBEDDING_MODEL);
expect(provider.maxInputTokens).toBe(2048);
const adaptedGetRuntimeFacts = Reflect.get(provider, LOCAL_EMBEDDING_RUNTIME_FACTS);
if (typeof adaptedGetRuntimeFacts !== "function") {
throw new Error("expected llama.cpp runtime facts carrier");
}
expect(adaptedGetRuntimeFacts()).toEqual({
engine: "llama.cpp",
state: "ready",
backend: "metal",
buildType: "prebuilt",
});
expect(result.runtime?.cacheKeyData).toEqual({
provider: "local",
model: DEFAULT_LLAMA_CPP_EMBEDDING_MODEL,
});
expect(close).toHaveBeenCalledTimes(1);
expect(memoryHostEmbeddingMocks.createLocalEmbeddingProvider).toHaveBeenCalledWith(
{
@@ -104,9 +129,9 @@ describe("llama.cpp provider plugin", () => {
nodeLlamaCppImportUrl: expect.stringContaining("node-llama-cpp"),
},
);
const workerProvider =
const createdWorkerProvider =
await memoryHostEmbeddingMocks.createLocalEmbeddingProvider.mock.results[0].value;
expect(workerProvider.embedBatchInputs).toHaveBeenCalledWith([{ text: "doc" }], {
expect(createdWorkerProvider.embedBatchInputs).toHaveBeenCalledWith([{ text: "doc" }], {
signal: abortController.signal,
});
});
+17 -1
View File
@@ -28,6 +28,7 @@ type LlamaCppEmbeddingProviderRuntimeOptions = {
};
const LLAMA_CPP_EMBEDDING_PROVIDER_ID = "local";
const LOCAL_EMBEDDING_RUNTIME_FACTS = Symbol.for("openclaw.localEmbeddingRuntimeFacts");
export const DEFAULT_LLAMA_CPP_EMBEDDING_MODEL =
"hf:ggml-org/embeddinggemma-300m-qat-q8_0-GGUF/embeddinggemma-300m-qat-Q8_0.gguf";
const DEFAULT_LLAMA_CPP_EMBEDDING_MODEL_CACHE_FILE_NAME =
@@ -159,8 +160,18 @@ function resolveNodeLlamaCppImportUrl(): string {
return pathToFileURL(requireFromPlugin.resolve("node-llama-cpp")).href;
}
function copyLocalRuntimeFacts(source: object, target: object): void {
const getRuntimeFacts = Reflect.get(source, LOCAL_EMBEDDING_RUNTIME_FACTS);
if (typeof getRuntimeFacts === "function") {
Object.defineProperty(target, LOCAL_EMBEDDING_RUNTIME_FACTS, {
enumerable: false,
value: getRuntimeFacts,
});
}
}
function adaptMemoryEmbeddingProvider(provider: MemoryEmbeddingProvider): EmbeddingProvider {
return {
const adapted: EmbeddingProvider = {
id: LLAMA_CPP_EMBEDDING_PROVIDER_ID,
model: provider.model,
maxInputTokens: provider.maxInputTokens,
@@ -180,6 +191,8 @@ function adaptMemoryEmbeddingProvider(provider: MemoryEmbeddingProvider): Embedd
},
close: provider.close,
};
copyLocalRuntimeFacts(provider, adapted);
return adapted;
}
export async function createLlamaCppMemoryEmbeddingProvider(
@@ -198,6 +211,9 @@ export async function createLlamaCppMemoryEmbeddingProvider(
);
const identifiedProvider =
identity.model === provider.model ? provider : { ...provider, model: identity.model };
if (identifiedProvider !== provider) {
copyLocalRuntimeFacts(provider, identifiedProvider);
}
return {
provider: identifiedProvider,
runtime: createLlamaCppEmbeddingProviderRuntime(identity),
+81
View File
@@ -74,6 +74,36 @@ type MemoryManagerPurpose = Parameters<typeof getMemorySearchManager>[0]["purpos
type MemorySourceName = "memory" | "sessions";
type LlamaCppRuntimeStatus = {
state?: string;
backend?: string;
buildType?: string;
deviceNames?: string[];
memory?: {
totalBytes: number;
usedBytes: number;
freeBytes: number;
unifiedBytes: number;
observedAtMs: number;
};
offload?: {
supported: boolean;
offloadedLayers?: number;
totalLayers?: number;
};
context?: {
requestedSize: number | "auto";
};
loadError?: string;
};
function readLlamaCppRuntimeStatus(
status: ReturnType<MemoryManager["status"]>,
): LlamaCppRuntimeStatus | null {
const runtime = asRecord(asRecord(status.custom)?.llamaCppRuntime);
return runtime?.engine === "llama.cpp" ? (runtime as LlamaCppRuntimeStatus) : null;
}
function formatMemoryIndexIdentityWarning(
status: ReturnType<MemoryManager["status"]>,
agentId: string,
@@ -96,6 +126,20 @@ function formatMemoryIndexIdentityWarning(
};
}
function formatRuntimeBytes(bytes: number): string {
if (bytes < 1024) {
return `${bytes} B`;
}
const units = ["KB", "MB", "GB", "TB"];
let value = bytes / 1024;
let unit = units[0];
for (let index = 1; index < units.length && value >= 1024; index += 1) {
value /= 1024;
unit = units[index];
}
return `${value >= 10 ? value.toFixed(0) : value.toFixed(1)} ${unit}`;
}
type SourceScan = {
source: MemorySourceName;
totalFiles: number | null;
@@ -910,6 +954,43 @@ export async function runMemoryStatus(opts: MemoryCommandOptions) {
lines.push(`${label("Embeddings error")} ${warn(embeddingProbe.error)}`);
}
}
const llamaCppRuntime = opts.deep ? readLlamaCppRuntimeStatus(status) : null;
if (llamaCppRuntime) {
const runtime = llamaCppRuntime;
const backend = runtime.backend ?? "unknown";
const build = runtime.buildType ? ` (${runtime.buildType})` : "";
lines.push(`${label("llama.cpp")} ${info(backend)}${muted(build)}`);
if (runtime.deviceNames?.length) {
lines.push(`${label("Devices")} ${info(runtime.deviceNames.join(", "))}`);
}
if (runtime.memory) {
const unified =
runtime.memory.unifiedBytes > 0
? ` · ${formatRuntimeBytes(runtime.memory.unifiedBytes)} unified`
: "";
lines.push(
`${label("VRAM snapshot")} ${info(`${formatRuntimeBytes(runtime.memory.usedBytes)} used · ${formatRuntimeBytes(runtime.memory.freeBytes)} free · ${formatRuntimeBytes(runtime.memory.totalBytes)} total${unified}`)} ${muted(`(${new Date(runtime.memory.observedAtMs).toISOString()})`)}`,
);
}
if (runtime.offload) {
const layers =
typeof runtime.offload.offloadedLayers === "number" &&
typeof runtime.offload.totalLayers === "number"
? `${runtime.offload.offloadedLayers}/${runtime.offload.totalLayers} layers`
: runtime.offload.supported
? "supported"
: "unsupported";
lines.push(`${label("GPU offload")} ${info(layers)}`);
}
if (runtime.context) {
lines.push(
`${label("Requested context")} ${info(`${runtime.context.requestedSize} tokens`)}`,
);
}
if (runtime.loadError) {
lines.push(`${label("llama.cpp error")} ${warn(runtime.loadError)}`);
}
}
const identityWarning = formatMemoryIndexIdentityWarning(status, agentId);
if (identityWarning) {
lines.push(`${label("Index identity")} ${warn(identityWarning.reason)}`);
+46 -1
View File
@@ -472,6 +472,14 @@ describe("memory cli", () => {
provider: "auto",
requestedProvider: "auto",
vector: { enabled: true },
custom: {
llamaCppRuntime: {
engine: "llama.cpp",
state: "ready",
backend: "metal",
buildType: "prebuilt",
},
},
}),
close,
});
@@ -483,6 +491,7 @@ describe("memory cli", () => {
expect(probeEmbeddingAvailability).not.toHaveBeenCalled();
expectLogged(log, "Provider: auto");
expectLogged(log, "Vector store: unknown");
expectNotLogged(log, "llama.cpp:");
expect(close).toHaveBeenCalled();
});
@@ -589,7 +598,35 @@ describe("memory cli", () => {
probeVectorStoreAvailability,
probeVectorAvailability,
probeEmbeddingAvailability,
status: () => makeMemoryStatus({ files: 1, chunks: 1 }),
status: () =>
makeMemoryStatus({
files: 1,
chunks: 1,
custom: {
llamaCppRuntime: {
engine: "llama.cpp",
state: "ready",
backend: "metal",
buildType: "prebuilt",
deviceNames: ["Apple M4 Max"],
memory: {
totalBytes: 64 * 1024 ** 3,
usedBytes: 8 * 1024 ** 3,
freeBytes: 56 * 1024 ** 3,
unifiedBytes: 64 * 1024 ** 3,
observedAtMs: Date.parse("2026-07-10T12:00:00.000Z"),
},
offload: {
supported: true,
offloadedLayers: 20,
totalLayers: 24,
},
context: {
requestedSize: 4096,
},
},
},
}),
close,
});
@@ -600,6 +637,14 @@ describe("memory cli", () => {
expect(probeVectorAvailability).toHaveBeenCalled();
expect(probeEmbeddingAvailability).toHaveBeenCalled();
expectLogged(log, "Embeddings: ready");
expectLogged(log, "llama.cpp: metal (prebuilt)");
expectLogged(log, "Devices: Apple M4 Max");
expectLogged(
log,
"VRAM snapshot: 8.0 GB used · 56 GB free · 64 GB total · 64 GB unified (2026-07-10T12:00:00.000Z)",
);
expectLogged(log, "GPU offload: 20/24 layers");
expectLogged(log, "Requested context: 4096 tokens");
expect(close).toHaveBeenCalled();
});
@@ -37,6 +37,7 @@ type CreateEmbeddingProviderOptions = MemoryEmbeddingProviderCreateOptions & {
const DEFAULT_MEMORY_EMBEDDING_PROVIDER = "openai";
const LOCAL_LLAMA_CPP_PROVIDER_ID = "local";
const LOCAL_EMBEDDING_RUNTIME_FACTS = Symbol.for("openclaw.localEmbeddingRuntimeFacts");
function createMissingLlamaCppProviderError(): Error {
return new Error(
@@ -52,7 +53,7 @@ function createMissingLlamaCppProviderError(): Error {
function adaptGenericEmbeddingProvider(
provider: GenericEmbeddingProvider,
): MemoryEmbeddingProvider {
return {
const adapted: MemoryEmbeddingProvider = {
id: provider.id,
model: provider.model,
...(typeof provider.maxInputTokens === "number"
@@ -75,6 +76,14 @@ function adaptGenericEmbeddingProvider(
}),
...(provider.close ? { close: provider.close } : {}),
};
const getRuntimeFacts = Reflect.get(provider, LOCAL_EMBEDDING_RUNTIME_FACTS);
if (typeof getRuntimeFacts === "function") {
Object.defineProperty(adapted, LOCAL_EMBEDDING_RUNTIME_FACTS, {
enumerable: false,
value: getRuntimeFacts,
});
}
return adapted;
}
function adaptGenericRuntime(
@@ -2158,6 +2158,62 @@ describe("memory index", () => {
}
});
it("exposes already-created local runtime facts without probing embeddings", async () => {
const cfg = createCfg({});
const { getRequiredMemoryIndexManager } = await import("./test-manager-helpers.js");
const manager = await getRequiredMemoryIndexManager({
cfg,
agentId: "main",
purpose: "status",
});
try {
const getRuntimeFacts = vi.fn(() => ({
engine: "llama.cpp" as const,
state: "ready" as const,
backend: "cuda" as const,
buildType: "prebuilt" as const,
deviceNames: ["NVIDIA Test GPU"],
offload: {
supported: true,
offloadedLayers: 24,
totalLayers: 24,
},
context: {
requestedSize: 4096,
},
}));
const provider = {
id: "local",
model: "test-model.gguf",
embedQuery: vi.fn(async () => [1, 0, 0, 0]),
embedBatch: vi.fn(async (texts: string[]) => texts.map(() => [1, 0, 0, 0])),
};
Object.defineProperty(provider, Symbol.for("openclaw.localEmbeddingRuntimeFacts"), {
value: getRuntimeFacts,
});
const fields = manager as unknown as {
provider: typeof provider | null;
};
fields.provider = provider;
expect(manager.status().custom?.llamaCppRuntime).toMatchObject({
state: "ready",
backend: "cuda",
deviceNames: ["NVIDIA Test GPU"],
offload: {
offloadedLayers: 24,
totalLayers: 24,
},
context: {
requestedSize: 4096,
},
});
expect(getRuntimeFacts).toHaveBeenCalledTimes(1);
} finally {
await manager.close?.();
}
});
it("keeps metadata after unchanged in-place force reindex", async () => {
const cfg = createCfg({});
const manager = await getFreshManager(cfg);
@@ -69,6 +69,17 @@ import {
type MemoryReadonlyRecoveryState,
} from "./manager-sync-control.js";
import { applyTemporalDecayToHybridResults } from "./temporal-decay.js";
const LOCAL_EMBEDDING_RUNTIME_FACTS = Symbol.for("openclaw.localEmbeddingRuntimeFacts");
function getLocalEmbeddingRuntimeFacts(provider: EmbeddingProvider | null): unknown {
if (!provider) {
return undefined;
}
const getRuntimeFacts = Reflect.get(provider, LOCAL_EMBEDDING_RUNTIME_FACTS);
return typeof getRuntimeFacts === "function" ? getRuntimeFacts() : undefined;
}
const SNIPPET_MAX_CHARS = 700;
const VECTOR_TABLE = MEMORY_INDEX_VECTOR_TABLE;
const FTS_TABLE = MEMORY_INDEX_FTS_TABLE;
@@ -1206,6 +1217,7 @@ export class MemoryIndexManager extends MemoryManagerEmbeddingOps implements Mem
lastProvider: this.batchFailureLastProvider,
},
custom: {
llamaCppRuntime: getLocalEmbeddingRuntimeFacts(this.provider),
searchMode: providerInfo.searchMode,
providerState: this.providerLifecycle,
providerUnavailableReason: this.providerUnavailableReason,
@@ -233,6 +233,28 @@ describe("probeGatewayMemoryStatus", () => {
});
});
it("carries last-known llama.cpp facts from the gateway", async () => {
callGateway.mockResolvedValue({
embedding: { ok: true },
embeddingRuntime: {
engine: "llama.cpp",
state: "ready",
backend: "metal",
buildType: "prebuilt",
},
});
await expect(probeGatewayMemoryStatus({ cfg })).resolves.toMatchObject({
checked: true,
ready: true,
runtimeFacts: {
state: "ready",
backend: "metal",
buildType: "prebuilt",
},
});
});
it("treats outer gateway timeouts as inconclusive (skipped: false)", async () => {
// A transport timeout must NOT be treated as a skipped probe. It is a real
// diagnostic signal and the renderer should warn for key-optional providers.
+6 -1
View File
@@ -9,7 +9,10 @@ import {
isGatewayCredentialsRequiredError,
} from "../gateway/call.js";
import { isGatewaySecretRefUnavailableError } from "../gateway/credentials.js";
import type { DoctorMemoryStatusPayload } from "../gateway/server-methods/doctor.js";
import type {
DoctorMemoryEmbeddingRuntimePayload,
DoctorMemoryStatusPayload,
} from "../gateway/server-methods/doctor.js";
import { collectChannelStatusIssues } from "../infra/channels-status-issues.js";
import { formatErrorMessage } from "../infra/errors.js";
import type { RuntimeEnv } from "../runtime.js";
@@ -26,6 +29,7 @@ type GatewayMemoryProbe = {
checked: boolean;
ready: boolean;
error?: string;
runtimeFacts?: DoctorMemoryEmbeddingRuntimePayload;
/**
* True when the probe was intentionally skipped by the gateway (probe: false
* path). Distinct from checked: false caused by a network timeout or
@@ -170,6 +174,7 @@ export async function probeGatewayMemoryStatus(params: {
checked: gatewayChecked,
ready: payload.embedding.ok,
error: payload.embedding.error,
...(payload.embeddingRuntime ? { runtimeFacts: payload.embeddingRuntime } : {}),
skipped: !gatewayChecked,
};
} catch (err) {
+40
View File
@@ -266,6 +266,46 @@ describe("noteMemorySearchHealth", () => {
expect(note).not.toHaveBeenCalled();
});
it("reports last-known llama.cpp runtime facts from the gateway", async () => {
resolveMemorySearchConfig.mockReturnValue({
provider: "local",
local: {},
remote: {},
});
await noteMemorySearchHealth(cfg, {
gatewayMemoryProbe: {
checked: true,
ready: true,
runtimeFacts: {
engine: "llama.cpp",
state: "ready",
backend: "cuda",
buildType: "prebuilt",
deviceNames: ["NVIDIA Test GPU"],
offload: {
supported: true,
offloadedLayers: 24,
totalLayers: 24,
},
context: {
requestedSize: 4096,
},
},
},
});
expect(note).toHaveBeenCalledWith(
[
"llama.cpp runtime: cuda, prebuilt",
"Devices: NVIDIA Test GPU",
"GPU offload: 24/24 layers",
"Requested context: 4096 tokens",
].join("\n"),
"Memory search",
);
});
it("does not warn when local provider readiness probe was intentionally skipped", async () => {
resolveMemorySearchConfig.mockReturnValue({
provider: "local",
+18
View File
@@ -23,6 +23,7 @@ import {
} from "../agents/model-auth.js";
import { formatCliCommand } from "../cli/command-format.js";
import type { OpenClawConfig } from "../config/types.openclaw.js";
import type { DoctorMemoryEmbeddingRuntimePayload } from "../gateway/server-methods/doctor.js";
import { formatErrorMessage } from "../infra/errors.js";
import {
checkQmdBinaryAvailability,
@@ -63,6 +64,19 @@ type MemoryEmbeddingProviderDoctorMetadata = {
autoSelectPriority?: number;
};
function formatLocalRuntimeDoctorNote(facts: DoctorMemoryEmbeddingRuntimePayload): string {
const backend = facts.backend ?? "unknown";
const build = facts.buildType ? `, ${facts.buildType}` : "";
const devices = facts.deviceNames?.length ? `\nDevices: ${facts.deviceNames.join(", ")}` : "";
const offload =
typeof facts.offload?.offloadedLayers === "number" &&
typeof facts.offload.totalLayers === "number"
? `\nGPU offload: ${facts.offload.offloadedLayers}/${facts.offload.totalLayers} layers`
: "";
const context = facts.context ? `\nRequested context: ${facts.context.requestedSize} tokens` : "";
return `llama.cpp runtime: ${backend}${build}${devices}${offload}${context}`;
}
const BUNDLED_MEMORY_EMBEDDING_PROVIDER_DOCTOR_METADATA: MemoryEmbeddingProviderDoctorMetadata[] = [
{
providerId: "github-copilot",
@@ -419,6 +433,7 @@ export async function noteMemorySearchHealth(
ready: boolean;
error?: string;
skipped?: boolean;
runtimeFacts?: DoctorMemoryEmbeddingRuntimePayload;
};
noteFn?: typeof note;
includeWorkspaceMemoryHealth?: boolean;
@@ -514,6 +529,9 @@ export async function noteMemorySearchHealth(
if (provider === "local") {
const suggestedRemoteProvider = resolveSuggestedRemoteMemoryProvider();
if (opts?.gatewayMemoryProbe?.runtimeFacts?.state === "ready") {
noteFn(formatLocalRuntimeDoctorNote(opts.gatewayMemoryProbe.runtimeFacts), "Memory search");
}
if (opts?.gatewayMemoryProbe?.checked && opts.gatewayMemoryProbe.ready) {
return;
}
+55
View File
@@ -322,6 +322,61 @@ describe("doctor.memory.status", () => {
});
});
it("returns llama.cpp runtime facts created by the deep embedding probe", async () => {
const close = vi.fn().mockResolvedValue(undefined);
let probed = false;
getMemorySearchManager.mockResolvedValue({
manager: {
status: () => ({
provider: "local",
...(probed
? {
custom: {
llamaCppRuntime: {
engine: "llama.cpp",
state: "ready",
backend: "cuda",
buildType: "prebuilt",
deviceNames: ["NVIDIA Test GPU"],
offload: {
supported: true,
offloadedLayers: 24,
totalLayers: 24,
},
context: {
requestedSize: 4096,
},
},
},
}
: {}),
}),
probeEmbeddingAvailability: vi.fn(async () => {
probed = true;
return { ok: true };
}),
close,
},
});
const respond = vi.fn();
await invokeDoctorMemoryStatus(respond, { params: { probe: true } });
expect(respondPayload(respond).embeddingRuntime).toMatchObject({
state: "ready",
backend: "cuda",
deviceNames: ["NVIDIA Test GPU"],
offload: {
offloadedLayers: 24,
totalLayers: 24,
},
context: {
requestedSize: 4096,
},
});
expect(close).toHaveBeenCalled();
});
it("does not live-probe embedding readiness by default", async () => {
const close = vi.fn().mockResolvedValue(undefined);
const probeEmbeddingAvailability = vi.fn().mockResolvedValue({ ok: true });
+35 -1
View File
@@ -124,9 +124,34 @@ export type DoctorMemoryStatusPayload = {
checkedAtMs?: number;
cacheExpiresAtMs?: number;
};
embeddingRuntime?: DoctorMemoryEmbeddingRuntimePayload;
dreaming?: DoctorMemoryDreamingPayload;
};
export type DoctorMemoryEmbeddingRuntimePayload = {
engine: "llama.cpp";
state: "ready" | "failed";
backend?: "metal" | "cuda" | "vulkan" | "cpu";
buildType?: "localBuild" | "prebuilt";
deviceNames?: string[];
memory?: {
totalBytes: number;
usedBytes: number;
freeBytes: number;
unifiedBytes: number;
observedAtMs: number;
};
offload?: {
supported: boolean;
offloadedLayers?: number;
totalLayers?: number;
};
context?: {
requestedSize: number | "auto";
};
loadError?: string;
};
export type DoctorMemoryDreamDiaryPayload = {
agentId: string;
found: boolean;
@@ -727,11 +752,14 @@ export const doctorHandlers: GatewayRequestHandlers = {
}
try {
const status = manager.status();
let status = manager.status();
const shouldProbe = shouldProbeMemoryEmbeddings(params);
let embedding = shouldProbe
? await manager.probeEmbeddingAvailability()
: (manager.getCachedEmbeddingAvailability?.() ?? SKIPPED_MEMORY_EMBEDDING_PROBE);
if (shouldProbe) {
status = manager.status();
}
if (!embedding.ok && !embedding.error) {
embedding = { ok: false, error: "memory embeddings unavailable" };
}
@@ -774,6 +802,12 @@ export const doctorHandlers: GatewayRequestHandlers = {
agentId,
provider: status.provider,
embedding,
embeddingRuntime: (() => {
const runtime = asOptionalRecord(asOptionalRecord(status.custom)?.llamaCppRuntime);
return runtime?.engine === "llama.cpp"
? (runtime as DoctorMemoryEmbeddingRuntimePayload)
: undefined;
})(),
dreaming: {
...dreamingConfig,
...storeStats,