# Runtime inspector (/docs/runtime-inspector)



Runtime inspector is one surface in the **observability**
[thematic island](/docs/concept-clusters#what-lives-outside-the-cluster-pattern) —
siblings of [observability](/docs/observability) (the orientation
page), [OTel spans](/docs/otel-observability), and
[lineage](/docs/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.

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

<SourceMeta subpath="@pleach/core/inspector" source="{ label: &#x22;src/inspector/&#x22;, href: &#x22;https://github.com/pleachhq/core/tree/main/src/inspector&#x22; }" />

## What it returns [#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` [#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 at `scripts/codemods/pleach-plugin-modernize.mjs` rewrites the four bare-property forms (see [Host adapter](/docs/host-adapter#pleach-plugin-modernize-codemod)).
* `"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` [#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 [#calling-it]

```typescript
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 [#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 forms** — `tools`, `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) [#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:

```js
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 [#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 [#where-to-go-next]

<Cards>
  <Card title="Plugin contract" href="/docs/plugin-contract" description="`definePleachPlugin` and the capability map the inspector inventories." />

  <Card title="Host adapter" href="/docs/host-adapter" description="`host.{strategies, modules, raw}` — the typed host carveout the inspector sits alongside." />

  <Card title="DevTools" href="/docs/devtools" description="`window.__HARNESS_DEVTOOLS__` — the browser-side counterpart for in-flight session state." />

  <Card title="Harness loop" href="/docs/playground-and-devtools#the-harness-chatinit-flag" description="The `harness` chatinit flag folds this report (capabilities wired n/total) into a per-turn TurnReport for self-tests + CI." />

  <Card title="Subpath exports" href="/docs/subpath-exports" description="`@pleach/core/inspector` — where the inspector ships from." />
</Cards>
