Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 99 additions & 0 deletions src/core/tdai-core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,37 @@ export interface TdaiCoreOptions {
instanceId?: string;
}

export interface TdaiDiagnostics {
dataDir: string;
store: {
vectorStore: boolean;
embeddingService: boolean;
l0Count: number | null;
l1Count: number | null;
countError?: string;
};
scheduler: {
enabled: boolean;
started: boolean;
queues: ReturnType<MemoryPipelineManager["getQueueSizes"]> | null;
};
checkpoint: {
totalProcessed: number;
l0ConversationsCount: number;
totalMemoriesExtracted: number;
memoriesSinceLastPersona: number;
scenesProcessed: number;
};
sessions: Array<{
sessionKey: string;
trackedInMemory: boolean;
bufferedMessages: number;
schedulerState: ReturnType<MemoryPipelineManager["getSessionState"]> | null;
checkpointRunnerState: unknown;
checkpointPipelineState: unknown;
}>;
}

// ============================
// TdaiCore
// ============================
Expand Down Expand Up @@ -363,6 +394,74 @@ export class TdaiCore {
await this.scheduler.flushSession(sessionKey);
}

/**
* Read-only diagnostics for Gateway/CLI observability.
*
* This intentionally avoids starting the scheduler by itself: diagnostics
* should report current state, not create new pipeline timers.
*/
async getDiagnostics(): Promise<TdaiDiagnostics> {
await this.storeReady?.catch(() => {});

const checkpoint = new CheckpointManager(this.dataDir, this.logger);
const cp = await checkpoint.read();
const scheduler = this.scheduler;

let l0Count: number | null = null;
let l1Count: number | null = null;
let countError: string | undefined;
if (this.vectorStore) {
try {
l0Count = await this.vectorStore.countL0();
l1Count = await this.vectorStore.countL1();
} catch (err) {
countError = err instanceof Error ? err.message : String(err);
}
}

const sessionKeys = new Set<string>([
...Object.keys(cp.runner_states ?? {}),
...Object.keys(cp.pipeline_states ?? {}),
...(scheduler?.getSessionKeys() ?? []),
]);

const sessions = Array.from(sessionKeys).sort().map((sessionKey) => {
const schedulerState = scheduler?.getSessionState(sessionKey) ?? null;
return {
sessionKey,
trackedInMemory: schedulerState !== null,
bufferedMessages: scheduler?.getBufferedMessageCount(sessionKey) ?? 0,
schedulerState,
checkpointRunnerState: cp.runner_states?.[sessionKey] ?? null,
checkpointPipelineState: cp.pipeline_states?.[sessionKey] ?? null,
};
});

return {
dataDir: this.dataDir,
store: {
vectorStore: !!this.vectorStore,
embeddingService: !!this.embeddingService,
l0Count,
l1Count,
...(countError ? { countError } : {}),
},
scheduler: {
enabled: !!scheduler,
started: this.isSchedulerStarted(),
queues: scheduler?.getQueueSizes() ?? null,
},
checkpoint: {
totalProcessed: cp.total_processed,
l0ConversationsCount: cp.l0_conversations_count,
totalMemoriesExtracted: cp.total_memories_extracted,
memoriesSinceLastPersona: cp.memories_since_last_persona,
scenesProcessed: cp.scenes_processed,
},
sessions,
};
}

// ============================
// Accessors (for migration bridge)
// ============================
Expand Down
9 changes: 9 additions & 0 deletions src/gateway/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
* POST /search/conversations — L0 conversation search
* POST /session/end — Session end + flush
* POST /seed — Batch seed historical conversations (L0 → L1)
* GET /diagnostics/sessions — Read-only session/pipeline diagnostics
*
* Built with Node.js native `http` module — no Express/Fastify dependency.
* Designed to run as a managed sidecar alongside Hermes.
Expand Down Expand Up @@ -37,6 +38,7 @@ import type {
SessionEndResponse,
SeedRequest,
SeedResponse,
DiagnosticsResponse,
GatewayErrorResponse,
} from "./types.js";
import type { Logger } from "../core/types.js";
Expand Down Expand Up @@ -272,6 +274,8 @@ export class TdaiGateway {
return await this.handleSessionEnd(req, res);
case "POST /seed":
return await this.handleSeed(req, res);
case "GET /diagnostics/sessions":
return await this.handleDiagnosticsSessions(res);
default:
sendError(res, 404, `Not found: ${method} ${pathname}`);
}
Expand Down Expand Up @@ -368,6 +372,11 @@ export class TdaiGateway {
sendJson(res, 200, response);
}

private async handleDiagnosticsSessions(res: http.ServerResponse): Promise<void> {
const response: DiagnosticsResponse = await this.core.getDiagnostics();
sendJson(res, 200, response);
}

private async handleRecall(req: http.IncomingMessage, res: http.ServerResponse): Promise<void> {
const body = await parseJsonBody<RecallRequest>(req);

Expand Down
35 changes: 35 additions & 0 deletions src/gateway/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,41 @@ export interface HealthResponse {
};
}

// ============================
// /diagnostics/sessions
// ============================

export interface DiagnosticsResponse {
dataDir: string;
store: {
vectorStore: boolean;
embeddingService: boolean;
l0Count: number | null;
l1Count: number | null;
countError?: string;
};
scheduler: {
enabled: boolean;
started: boolean;
queues: unknown;
};
checkpoint: {
totalProcessed: number;
l0ConversationsCount: number;
totalMemoriesExtracted: number;
memoriesSinceLastPersona: number;
scenesProcessed: number;
};
sessions: Array<{
sessionKey: string;
trackedInMemory: boolean;
bufferedMessages: number;
schedulerState: unknown;
checkpointRunnerState: unknown;
checkpointPipelineState: unknown;
}>;
}

// ============================
// /recall
// ============================
Expand Down