pleach
Operate

Runtime inspector

`inspectRuntime()` — the typed read-only introspection surface for which `contribute*` hooks a constructed `SessionRuntime` actually has wired.

Runtime inspector is one surface in the observability thematic island — siblings of observability (the orientation page), OTel spans, and lineage.

inspectRuntime(runtime) returns a typed snapshot of which contribute* hooks the plugins on a constructed SessionRuntime actually implement. Use it to answer one question: of the 38 known hooks, which ones a registered plugin covers, which ones are unwired, and which ones still use the deprecated bare-property form slated for removal.

The hook count is ~45: 41 modern contribute* hooks (per the audit:plugin-contract-completeness baseline — 38 wired end-to-end and 3 baselined awaiting consumer) plus the soft-deprecated bare-property forms (tools, events, customEventTypes, batchingHints) the inspector still inventories so it can flag plugins that haven't migrated. The 5 hard-deprecated lifecycle hooks (onSessionCreated, onToolCompleted, onMessageAdded, queryExtensions, sandboxAvailability) have been retired. The list is a static snapshot in the inspector source; new hooks append to it when they land in HarnessPlugin.

The inspector is read-only. It doesn't mutate the runtime, doesn't register plugins, and doesn't trigger any contribute* hook — it walks the registered plugin list and checks which properties are defined as functions. Safe to call from a startup probe.

import { inspectRuntime } from "@pleach/core/inspector";
import type {
  InspectionReport,
  CapabilityReport,
  PluginReport,
  CapabilityDescriptor,
} from "@pleach/core/inspector";
Subpath@pleach/core/inspectorSourcesrc/inspector/

What it returns

InspectionReport carries the runtime identity, a per-plugin contributions map, and one row per known hook.

FieldTypePurpose
runtime.tenantIdstringThe tenant the runtime is bound to
runtime.userIdstring | undefinedThe user, when one is bound
runtime.runtimeMode"interactive" | "deterministic" | undefinedThe runtime's resolved mode
pluginsreadonly PluginReport[]Per-plugin contributions, in registration order
capabilitiesreadonly CapabilityReport[]One row per known hook

CapabilityReport

Each capability row reports the status of one contribute* hook across all registered plugins.

FieldTypeMeaning
namestringThe hook name (matches the HarnessPlugin method)
deprecatedbooleantrue for hooks slated for removal per the retirement table
status"wired" | "deprecated-wired" | "unwired"The discriminator (see below)
wiredCountnumberNumber of plugins implementing the hook
implementorsreadonly string[]Plugin names that implement it, in registration order
descriptionstringOne-line description suitable for runtime.help-style surfaces

The status discriminator:

  • "wired" — at least one registered plugin implements the modern hook.
  • "deprecated-wired" — at least one plugin still uses the deprecated form. Migration to the modern hook is recommended; the codemod at scripts/codemods/pleach-plugin-modernize.mjs rewrites the four bare-property forms (see Host adapter).
  • "unwired" — no plugin implements the hook; the capability is unavailable on this runtime.

"deprecated-wired" is the new fact. It's the row the inspector flags so a host that constructed a runtime out of older plugins can spot the migration debt without reading source.

PluginReport

FieldTypeMeaning
namestringPlugin name as registered
contributionsreadonly string[]contribute* hook names this plugin implements
deprecatedContributionsreadonly string[]Subset of contributions that map to deprecated hooks

Calling it

import { SessionRuntime } from "@pleach/core";
import { inspectRuntime } from "@pleach/core/inspector";

const runtime = new SessionRuntime({
  plugins: [compliancePlugin, gatewayPlugin, myPlugin],
  userId: "user_123",
});

const report = inspectRuntime(runtime);

for (const row of report.capabilities) {
  if (row.status === "deprecated-wired") {
    console.warn(
      `[migrate] ${row.name} — implementors: ${row.implementors.join(", ")}`,
    );
  }
}

for (const plugin of report.plugins) {
  if (plugin.deprecatedContributions.length > 0) {
    console.warn(
      `[plugin:${plugin.name}] still on deprecated: ${plugin.deprecatedContributions.join(", ")}`,
    );
  }
}

The call is synchronous. It reads the runtime's plugin registry without firing any seam.

What the inventory covers

The current inventory breaks down as:

  • 41 modern contribute* hooks — the active surface tracked by audit:plugin-contract-completeness. Examples: contributePrompts, contributeRuntimeAwarePrompts, contributeSafetyPolicies, contributeFabricationDetectors, contributeFabricationDetectorRules, contributeFinalizationPasses, contributeSynthesisDirectiveBlocks, contributeIntentClassifiers, contributeIntentToolMap, contributeTools, contributeToolCouplingHints, contributeStreamObservers, contributeFabricationGuard, contributeCitationRuleSet, contributeChatManifestProvider, contributeRetryPolicy, contributeContinuationPolicy, contributeFamilyPivot, contributeFamilyExhaustedSurface, contributeMiddleware, contributeRuntimeAwareMiddleware, contributeSandboxBridge, contributeStreamEventHandlersAdapters, contributeGuestDeniedTools, contributePreserveDataRefFields, contributeHallucinatedToolDetectors, contributeStreamChunkHandlers, contributeDataChannelRefetch, contributeArtifactCacheReader, contributeGetDataHandlerFactory, contributeStructurePrefetcher, contributeEntityNameCounter, contributeGarbledOutputRecorder, contributeContinuationShadowResolver, contributeInterruptUIHandlers, contributeCitationEntityExtractor, contributeDetectionRules, contributePromptHints, contributeObserverConsumers. 38 are wired end-to-end; 3 are baselined awaiting their consumer per the audit baseline.
  • 4 soft-deprecated bare-property formstools, events, customEventTypes, batchingHints. Retired in 1.2.0; the inspector flags them so plugins migrate. Run audit:harness-plugin-deprecated-usage:strict to surface novel uses.

The 5 hard-deprecated lifecycle hooks (onSessionCreated, onToolCompleted, onMessageAdded, queryExtensions, sandboxAvailability) have been retired and no longer appear on the HarnessPlugin type.

The source of truth for the list is the KNOWN_CAPABILITIES constant in src/inspector/index.ts. The drift policy is documented inline: when a new hook lands in HarnessPlugin, the maintainer appends a CapabilityDescriptor so the inspector picks it up.

Capability breadcrumbs (separate from the inspector)

A separate runtime surface — PluginManager itself — emits a [Pleach:capability-not-contributed] console line on first call when a collector for one of 5 named capabilities aggregates empty. The breadcrumb is dedup-keyed on ${sessionId}:${capability} so it fires once per session per capability, not per turn.

The 5 capabilities that emit the breadcrumb:

CapabilityHook
Stream observerscontributeStreamObservers
Safety policiescontributeSafetyPolicies
Fabrication detectorscontributeFabricationDetectors
Tool-coupling hintscontributeToolCouplingHints
Intent-tool mapcontributeIntentToolMap

Payload shape:

console.log("[Pleach:capability-not-contributed]", {
  capability: "contributeStreamObservers",
  sessionId: "sess_018f...",
  hint: "register a HarnessPlugin that implements contributeStreamObservers() to provide this capability",
});

The inspector and the breadcrumb cover the same ground from different angles. The inspector is a synchronous pull — inspectRuntime(runtime) tells you the state right now. The breadcrumb is an asynchronous push — it surfaces at first-call time, dedup'd per session. Use the inspector at startup; rely on the breadcrumb in dev to catch a plugin you thought you'd registered but didn't.

Pairing with the coverage audit

audit:plugin-contract-completeness is the upstream CI gate that asserts every contribute* hook on HarnessPlugin has a paired PluginManager.collectX accessor AND at least one substrate consumer site. Today's baseline reports 41 hooks — 38 wired end-to-end, 3 baselined awaiting consumer (contributeBatchingHints on the 1.2.0 retirement track, contributeEventTypes collector-bypass per doc 50 §14.4 G5, and contributeMiddleware Stage 2 consumer rewires in flight). 0 novel dead hooks.

The audit's hook count and the inspector's KNOWN_CAPABILITIES count don't collide — the audit covers what plugins can contribute and the substrate will read; the inspector covers what a specific runtime construction actually has. The audit answers a substrate question; the inspector answers a host question.

Where to go next

On this page