Skip to content

[AI-stack E9] @netscript/ai: OTel GenAI/MCP semconv telemetry adapter (./otel) #248

Description

@rickylabs

Add an @netscript/ai/otel adapter that maps GenAI/MCP calls to OTel GenAI (gen_ai.*) and MCP (mcp.*) semantic-convention spans via an injected TelemetryPort, without pulling telemetry into core.

Context

Part of epic #238 · cluster ENGINE · wave v1 · depends-on E1

E9 sits downstream of the core engine contract (E1) and is the single sanctioned exception to the "core has zero @netscript/* runtime deps" rule: it is scoped now so the port shape in E1 is designed with an observability seam from day one, rather than retrofitted after adapters/fresh-ai land.

Problem / motivation

@netscript/ai core has no tracing story, and the only prior art in-repo (eis-chat) has no GenAI/MCP semconv spans at all — calls are opaque in traces. Left unaddressed, either (a) adapters/providers each hand-roll ad-hoc spans with inconsistent attribute names, or (b) core absorbs a direct @netscript/telemetry dependency to "just wire it in," breaking the zero-dep core invariant every other @netscript/ai slice depends on. This slice closes that gap once, behind a port, so instrumentation is opt-in and centralized.

Scope

Ships

  • packages/ai/otel/mod.ts (or @netscript/ai ./otel subpath entry) exporting a TelemetryPort-conformant adapter, e.g. createOtelTelemetryAdapter(...).
  • Span mapping for GenAI calls (gen_ai.system, gen_ai.request.model, gen_ai.usage.input_tokens, gen_ai.usage.output_tokens, etc.) sourced from the real usage event (per ratified usage decision), not estimated token counts.
  • Span mapping for MCP tool/resource invocations (mcp.tool.name, mcp.method.name, etc.) where core exposes MCP call boundaries.
  • Import of @netscript/telemetry confined to this subpath only.
  • Wiring notes/tests showing the adapter satisfies the TelemetryPort contract defined in E1.

Out of scope

  • Defining the TelemetryPort interface itself and the injection point in the engine — owned by E1.
  • In-process vs. gateway execution modes — owned by the ENGINE slice(s) implementing --gateway.
  • Provider-specific span enrichment (e.g. Anthropic-only attributes) — owned by each provider subpath slice (e.g. @netscript/ai/anthropic).
  • fresh/ai or fresh-ui consumption of traces — out of this cluster entirely.

Public surface

  • Subpath: @netscript/ai/otel
  • File: packages/ai/otel/mod.ts (with @module JSDoc, <=20 exports)
  • Primary export: createOtelTelemetryAdapter(options): TelemetryPort (name may be finalized against E1's port signature, but must remain the sole factory export for this subpath)
  • No changes to @netscript/ai's root/core exports; core continues to accept TelemetryPort only as an injected dependency.

Acceptance & fitness gates

  • F-3: layering respects domain -> ports -> application -> adapters; deno task arch:check passes; only packages/ai/otel/** imports @netscript/telemetry, verified by grep/arch:check.
  • F-5: ./otel mod.ts has @module JSDoc and public-surface JSDoc; <=20 exports.
  • F-6 (gate:jsr): packages/ai/otel publishes via deno task publish:dry-run WITHOUT --allow-slow-types.
  • F-13: adapter lifecycle (span start/end, flush/shutdown) has explicit runtime declarations; no implicit background timers.
  • Unit test asserts GenAI span attributes are populated from the real finish/usage event, not a chars/4 estimate.
  • Unit test asserts core (@netscript/ai without ./otel) has zero transitive dependency on @netscript/telemetry (import-graph check).

Dependencies

Depends on E1 (GitHub numbers tracked in the #238 DAG checklist). Not blocked by #239.

Size

M

Part of #238

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions