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";@pleach/core/inspectorSourcesrc/inspector/What it returns
InspectionReport carries the runtime identity, a per-plugin
contributions map, and one row per known hook.
| Field | Type | Purpose |
|---|---|---|
runtime.tenantId | string | The tenant the runtime is bound to |
runtime.userId | string | undefined | The user, when one is bound |
runtime.runtimeMode | "interactive" | "deterministic" | undefined | The runtime's resolved mode |
plugins | readonly PluginReport[] | Per-plugin contributions, in registration order |
capabilities | readonly CapabilityReport[] | One row per known hook |
CapabilityReport
Each capability row reports the status of one contribute* hook
across all registered plugins.
| Field | Type | Meaning |
|---|---|---|
name | string | The hook name (matches the HarnessPlugin method) |
deprecated | boolean | true for hooks slated for removal per the retirement table |
status | "wired" | "deprecated-wired" | "unwired" | The discriminator (see below) |
wiredCount | number | Number of plugins implementing the hook |
implementors | readonly string[] | Plugin names that implement it, in registration order |
description | string | One-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 atscripts/codemods/pleach-plugin-modernize.mjsrewrites 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
| Field | Type | Meaning |
|---|---|---|
name | string | Plugin name as registered |
contributions | readonly string[] | contribute* hook names this plugin implements |
deprecatedContributions | readonly 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 byaudit: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 forms —
tools,events,customEventTypes,batchingHints. Retired in 1.2.0; the inspector flags them so plugins migrate. Runaudit:harness-plugin-deprecated-usage:strictto 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:
| Capability | Hook |
|---|---|
| Stream observers | contributeStreamObservers |
| Safety policies | contributeSafetyPolicies |
| Fabrication detectors | contributeFabricationDetectors |
| Tool-coupling hints | contributeToolCouplingHints |
| Intent-tool map | contributeIntentToolMap |
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
Plugin contract
`definePleachPlugin` and the capability map the inspector inventories.
Host adapter
`host.{strategies, modules, raw}` — the typed host carveout the inspector sits alongside.
DevTools
`window.__HARNESS_DEVTOOLS__` — the browser-side counterpart for in-flight session state.
Harness loop
The `harness` chatinit flag folds this report (capabilities wired n/total) into a per-turn TurnReport for self-tests + CI.
Subpath exports
`@pleach/core/inspector` — where the inspector ships from.
OTEL spans & exporter wiring
Four read-side span types — session.turn, llm.invocation, graph.stage, tool.execution — with parent-threading, auto-flush, the runtime.spans facet, and a minimal OTLP exporter wiring.
Playground & devtools
What ships today for inspecting sessions, walking checkpoints, and tailing the event log — plus the explicit trade we made on "hosted dashboard".