diff --git a/src/mirror-cli/schemas.ts b/src/mirror-cli/schemas.ts index 7a4a659b05..4f1a834dff 100644 --- a/src/mirror-cli/schemas.ts +++ b/src/mirror-cli/schemas.ts @@ -286,7 +286,7 @@ export const MIRROR_CLI_COMMAND_HELP: MirrorCliCommandHelp[] = [ description: "Verify canonical lore files against a lore manifest.", args: [], options: [ - "--manifest : lore manifest path (default: /manifest.json)", + "--manifest : lore manifest path (default: /manifest.json)", "--dir : canonical lore directory (default: MIRROR_LORE_DIR or ./lore-scrolls)", "--json: emit stable machine-readable JSON", ], diff --git a/src/mirror-gateway/chat_route_runtime.ts b/src/mirror-gateway/chat_route_runtime.ts index ed203bb2d4..de9b92c761 100644 --- a/src/mirror-gateway/chat_route_runtime.ts +++ b/src/mirror-gateway/chat_route_runtime.ts @@ -3,7 +3,7 @@ import { buildHttpChatAdapterEnvelope, type MirrorAdapterResponseEnvelope, } from "../mirror-adapters/index.js"; -import { incrementMetric, logMirrorEvent } from "../mirror-observability/index.js"; +import type { MirrorObservabilityContext } from "../mirror-observability/index.js"; import type { MirrorPolicyContext } from "../mirror-policy/index.js"; import { withMirrorCorrelation } from "../mirror-runtime/index.js"; @@ -22,6 +22,7 @@ type ExecuteMirrorGatewayChatRouteOptions = { executeAdapterRequest: ( envelope: ReturnType, ) => Promise; + observability: Pick; onRuntimeEvent?: (type: string, payload?: Record) => void; readIngressAdapterDescriptor: ReadIngressAdapterDescriptor; readIngressRoutePath: (req: express.Request) => string; @@ -48,8 +49,8 @@ export async function executeMirrorGatewayChatRoute( const adapterDescriptor = options.readIngressAdapterDescriptor( options.readIngressRoutePath(req), ); - incrementMetric("chat_requests"); - logMirrorEvent("chat.pipeline", { route: "mirror.chat" }); + options.observability.incrementMetric("chat_requests"); + options.observability.logEvent("chat.pipeline", { route: "mirror.chat" }); options.onRuntimeEvent?.( "chat.started", withMirrorCorrelation( diff --git a/src/mirror-gateway/mirror_gateway.ts b/src/mirror-gateway/mirror_gateway.ts index 152dc6d202..4c73736e07 100644 --- a/src/mirror-gateway/mirror_gateway.ts +++ b/src/mirror-gateway/mirror_gateway.ts @@ -14,6 +14,7 @@ import { type MirrorAdapterRequestEnvelope, type MirrorAdapterResponseEnvelope, } from "../mirror-adapters/index.js"; +import type { MirrorObservabilityContext } from "../mirror-observability/index.js"; import { buildMirrorChatPolicyTarget, buildMirrorAdapterPolicyTarget, @@ -185,7 +186,14 @@ function buildAdapterPolicyContext(envelope: MirrorAdapterRequestEnvelope): Mirr export function createMirrorGateway( basePath = "/mirror", - options: { providerPlane?: MirrorProviderPlane; policy?: MirrorPolicyEngine } = {}, + options: { + observability?: Pick< + MirrorObservabilityContext, + "incrementMetric" | "incrementToolExecution" | "logEvent" + >; + providerPlane?: MirrorProviderPlane; + policy?: MirrorPolicyEngine; + } = {}, ): MirrorGateway { const registry = createMirrorToolRegistry(getMirrorNativeSkillTools()); const actionRuntime = createMirrorActionRuntime( @@ -309,6 +317,7 @@ export function createMirrorGateway( } const handlers = createMirrorGatewayHandlers(toolRegistry, { + observability: options.observability, providerPlane: options.providerPlane, executeAdapterRequest: executeAdapterRequestInternal, }); diff --git a/src/mirror-gateway/routes.ts b/src/mirror-gateway/routes.ts index 6dff14114e..ae258e2cbc 100644 --- a/src/mirror-gateway/routes.ts +++ b/src/mirror-gateway/routes.ts @@ -4,7 +4,10 @@ import { buildHttpToolAdapterEnvelope, type MirrorAdapterResponseEnvelope, } from "../mirror-adapters/index.js"; -import { incrementMetric, logMirrorEvent } from "../mirror-observability/index.js"; +import { + getCurrentMirrorObservabilityContext, + type MirrorObservabilityContext, +} from "../mirror-observability/index.js"; import { type MirrorPolicyContext } from "../mirror-policy/index.js"; import { type MirrorProviderConfig, type MirrorProviderPlane } from "../mirror-provider/index.js"; import { resolveMirrorTraceId } from "../mirror-runtime/index.js"; @@ -83,6 +86,10 @@ export type MirrorGatewayHandlers = { export function createMirrorGatewayHandlers( registry = createMirrorToolRegistry(getMirrorNativeSkillTools()), options: { + observability?: Pick< + MirrorObservabilityContext, + "incrementMetric" | "incrementToolExecution" | "logEvent" + >; provider?: MirrorProviderConfig; providerPlane?: MirrorProviderPlane; onRuntimeEvent?: (type: string, payload?: Record) => void; @@ -93,6 +100,23 @@ export function createMirrorGatewayHandlers( ) => Promise; }, ): MirrorGatewayHandlers { + const observability = + options.observability ?? + ({ + incrementMetric(name, amount) { + getCurrentMirrorObservabilityContext().incrementMetric(name, amount); + }, + incrementToolExecution(toolName) { + getCurrentMirrorObservabilityContext().incrementToolExecution(toolName); + }, + logEvent(event, fields) { + getCurrentMirrorObservabilityContext().logEvent(event, fields); + }, + } satisfies Pick< + MirrorObservabilityContext, + "incrementMetric" | "incrementToolExecution" | "logEvent" + >); + function buildPolicyContext( req: express.Request, body: Record, @@ -140,30 +164,30 @@ export function createMirrorGatewayHandlers( function recordWorkspaceToolObservability(toolName: string): void { if (toolName.startsWith("mirror.task.")) { - incrementMetric("workspace_events"); - incrementMetric("task_operations"); - logMirrorEvent("workspace.task", { tool: toolName }); + observability.incrementMetric("workspace_events"); + observability.incrementMetric("task_operations"); + observability.logEvent("workspace.task", { tool: toolName }); return; } if (toolName.startsWith("mirror.reminder.")) { - incrementMetric("workspace_events"); - incrementMetric("reminder_operations"); - logMirrorEvent("workspace.reminder", { tool: toolName }); + observability.incrementMetric("workspace_events"); + observability.incrementMetric("reminder_operations"); + observability.logEvent("workspace.reminder", { tool: toolName }); return; } if (toolName.startsWith("mirror.heartbeat.")) { - incrementMetric("workspace_events"); - incrementMetric("heartbeat_operations"); - logMirrorEvent("workspace.heartbeat", { tool: toolName }); + observability.incrementMetric("workspace_events"); + observability.incrementMetric("heartbeat_operations"); + observability.logEvent("workspace.heartbeat", { tool: toolName }); return; } if (toolName.startsWith("mirror.monk.")) { - incrementMetric("workspace_events"); - incrementMetric("monk_actions"); - logMirrorEvent("workspace.monk", { tool: toolName }); + observability.incrementMetric("workspace_events"); + observability.incrementMetric("monk_actions"); + observability.logEvent("workspace.monk", { tool: toolName }); } } @@ -206,6 +230,7 @@ export function createMirrorGatewayHandlers( { buildPolicyContext, executeAdapterRequest: options.executeAdapterRequest, + observability, onRuntimeEvent: options.onRuntimeEvent, readIngressAdapterDescriptor, readIngressRoutePath, @@ -231,6 +256,7 @@ export function createMirrorGatewayHandlers( { buildPolicyContext, executeAdapterRequest: options.executeAdapterRequest, + observability, onRuntimeEvent: options.onRuntimeEvent, readIngressAdapterDescriptor, readIngressRoutePath, diff --git a/src/mirror-gateway/tool_route_runtime.ts b/src/mirror-gateway/tool_route_runtime.ts index 223e26cd53..614db608c4 100644 --- a/src/mirror-gateway/tool_route_runtime.ts +++ b/src/mirror-gateway/tool_route_runtime.ts @@ -4,7 +4,7 @@ import { buildHttpToolAdapterEnvelope, type MirrorAdapterResponseEnvelope, } from "../mirror-adapters/index.js"; -import { incrementToolExecution, logMirrorEvent } from "../mirror-observability/index.js"; +import type { MirrorObservabilityContext } from "../mirror-observability/index.js"; import type { MirrorPolicyContext } from "../mirror-policy/index.js"; import { withMirrorCorrelation } from "../mirror-runtime/index.js"; import type { MirrorSkillTool } from "../mirror/skills/index.js"; @@ -25,6 +25,7 @@ type ExecuteMirrorGatewayToolRouteOptions = { executeAdapterRequest: ( envelope: ReturnType, ) => Promise; + observability: Pick; onRuntimeEvent?: (type: string, payload?: Record) => void; readIngressAdapterDescriptor: ReadIngressAdapterDescriptor; readIngressRoutePath: (req: express.Request) => string; @@ -78,9 +79,9 @@ export async function executeMirrorGatewayToolRoute( }, ), ); - incrementToolExecution(options.tool.metadata.name); - logMirrorEvent("tool.execution", { tool: options.tool.metadata.name }); - logMirrorEvent("action.execution", { action: options.tool.metadata.name }); + options.observability.incrementToolExecution(options.tool.metadata.name); + options.observability.logEvent("tool.execution", { tool: options.tool.metadata.name }); + options.observability.logEvent("action.execution", { action: options.tool.metadata.name }); options.onRuntimeEvent?.( "action.execution.started", withMirrorCorrelation( diff --git a/src/mirror-service/mirror_service.ts b/src/mirror-service/mirror_service.ts index 8951968094..77c3c47c4c 100644 --- a/src/mirror-service/mirror_service.ts +++ b/src/mirror-service/mirror_service.ts @@ -98,6 +98,7 @@ export async function startMirrorService( const actionRuntime = gateway.actionRuntime; const handlers = createMirrorGatewayHandlers(gateway.registry, { + observability, providerPlane, onRuntimeEvent: daemon.publishRuntimeEvent, executeAdapterRequest: async (envelope) => await runtimeHost.executeAdapterRequest(envelope), diff --git a/src/mirror-service/runtime_host.ts b/src/mirror-service/runtime_host.ts index cefab49be7..f404169101 100644 --- a/src/mirror-service/runtime_host.ts +++ b/src/mirror-service/runtime_host.ts @@ -116,7 +116,10 @@ export async function createMirrorRuntimeHost( lifecycle, providerPlane, }); - const gateway = createMirrorGateway("/mirror", { providerPlane }); + const gateway = createMirrorGateway("/mirror", { + observability: daemon.getObservability(), + providerPlane, + }); const syncManager = createMirrorSyncManager({ nodeId: config.nodeId, loreDir: config.loreDir, diff --git a/src/mirror/telemetry_tail/cli.ts b/src/mirror/telemetry_tail/cli.ts index 165f51f87e..a0bb4b2477 100644 --- a/src/mirror/telemetry_tail/cli.ts +++ b/src/mirror/telemetry_tail/cli.ts @@ -402,7 +402,7 @@ export function registerMirrorTelemetryCli(program: Command): void { mirror .command("verify-lore") .description("Compatibility wrapper for mirror verify-lore") - .option("--manifest ", "Lore manifest path (default: /manifest.json)") + .option("--manifest ", "Lore manifest path (default: /manifest.json)") .option("--dir ", "Canonical lore directory (default: MIRROR_LORE_DIR or ./lore-scrolls)") .option("--json", "Output machine-readable JSON", false) .action(async (opts: { manifest?: string; dir?: string; json?: boolean }) => { diff --git a/src/mirrordaemon/debug_api.ts b/src/mirrordaemon/debug_api.ts index e41723ff79..04a4722f70 100644 --- a/src/mirrordaemon/debug_api.ts +++ b/src/mirrordaemon/debug_api.ts @@ -3,7 +3,7 @@ import { buildDebugSnapshot } from "./runtime_state.js"; export function getMirrordaemonDebugState( daemon: Mirrordaemon, - params: { port?: number; baseUrl?: string | null; peersKnown?: number } = {}, + params: { port?: number; baseUrl?: string | null } = {}, ) { return buildDebugSnapshot(daemon, params); } diff --git a/src/mirrordaemon/runtime_state.ts b/src/mirrordaemon/runtime_state.ts index f4e0575dfb..77ec57eed5 100644 --- a/src/mirrordaemon/runtime_state.ts +++ b/src/mirrordaemon/runtime_state.ts @@ -190,7 +190,6 @@ export function buildHealthSummary( ): MirrordaemonHealthSummary { const runtime = buildRuntimeSummary(daemon, overrides); const boot = daemon.getBootSnapshot(); - const metrics = daemon.getObservability().getMetrics(); return { ...runtime, @@ -211,7 +210,7 @@ export function buildHealthSummary( fallback_available: boot.readiness.provider.fallback_available, }, sync: { - peers_known: overrides.peers?.length ?? metrics.gauges.peers_known ?? 0, + peers_known: overrides.peers?.length ?? 0, }, observability: { metrics_available: true, @@ -222,7 +221,7 @@ export function buildHealthSummary( export function buildDebugSnapshot( daemon: Mirrordaemon, - overrides: RuntimeStateOverrides & { peersKnown?: number } = {}, + overrides: RuntimeStateOverrides = {}, ): MirrordaemonDebugSnapshot { const observability = daemon.getObservability(); return { diff --git a/test/mirror-help-surface-boundary.test.ts b/test/mirror-help-surface-boundary.test.ts index 404f40ea6f..e7310492ae 100644 --- a/test/mirror-help-surface-boundary.test.ts +++ b/test/mirror-help-surface-boundary.test.ts @@ -32,5 +32,8 @@ describe("mirror standalone help surface", () => { expect(schemaSource).toContain('command: "status"'); expect(schemaSource).toContain('command: "verify-lore"'); expect(schemaSource).toContain('command: "sync"'); + expect(schemaSource).toContain( + "--manifest : lore manifest path (default: /manifest.json)", + ); }); });