# Facet accessors (/docs/facets)



The runtime exposes its capabilities through **facets** — small named
accessors that group related methods by domain. `runtime.tenant`
gathers every tenant-scoping primitive; `runtime.events` gathers
every event-log primitive; `runtime.spans` gathers the in-process
OTel span surface. The grouping is the contract. The methods inside
each facet are the implementation.

The design choice is to make the runtime **discoverable by
construction**. A human reading `runtime.<TAB>` in an IDE sees a
short list of named domains, not a flat dump of methods. An LLM
agent introspecting the runtime to plan a next action reads the
same short list. Both audiences route their queries through the
facet name (`spans`, `events`, `tenant`) and arrive at the right
method.

> **Facets are a thematic island.** Not one of the [six cluster
> triplets](/docs/concept-clusters#the-six-cluster-triplets) — a
> facet is a surface pattern (typed accessors grouped by domain),
> not a three-concept cluster. See [What lives outside the cluster
> pattern](/docs/concept-clusters#what-lives-outside-the-cluster-pattern).

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

## The facet inventory [#the-facet-inventory]

Facets live on two host objects: the `SessionRuntime` your host
code constructs, and the `TurnOrchestrator` handle each turn
receives. The per-turn graph also exposes a `runtime.graph.*`
namespace that mirrors the facet pattern for graph-layer-only
helpers.

### On `SessionRuntime` [#on-sessionruntime]

| Facet                    | What it carries                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `runtime.sessions`       | Session lifecycle — `create`, `resume`, `find`, `save`, `delete` (returns a `SessionDeleteReceipt`), `updateProviderModel`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| `runtime.checkpoints`    | Checkpoint surface — `rollback`, `list` (async-iterable of summaries)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| `runtime.tenant`         | `id` + `subId` properties. See [Tenant facet](/docs/tenant-facet)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| `runtime.sync`           | `execute` (callable directly — `runtime.sync(sessionId, opts)` delegates), `resolveConflict` (returns a `SyncConflictResolveReceipt` — `resolved`, `sessionId`, `conflictId`, `resolution`, `resolvedAt`; **FSL build: last-writer-wins — logs the resolution + returns `resolved: true` with NO CRDT merge or state mutation; enterprise builds perform the merge**), `subscribeToStream`                                                                                                                                                                                                                                                                 |
| `runtime.diagnostics`    | `checkReadiness()` — mailbox, history, perf-state report                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| `runtime.plugins`        | 25+ `collectX` accessors (fabrication detectors, finalization passes, synthesis directive blocks, detection rules, intent classifiers, tool-coupling hints, intent-tool map, sandbox bridge, interrupt UI handlers, citation rule set, chat manifest provider, fabrication guard, guest denied tools, entity name counter, structure prefetcher, artifact cache reader, get-data handler factory, stream observers, ...) plus `listAvailablePromptContributions`, `listAvailableSafetyPolicies`, `getActiveSafetyPolicies`, `getSafetyRegistry`, `registerExtension`, `getPluginManager`. The canonical surface for reading what plugins have contributed. |
| `runtime.prompts`        | `list`, `get`, `getAll`, `listByOrigin`, `listByPlugin`, `listByAnnotation`, `count` over registered prompt contributions                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| `runtime.safety`         | `listActivePolicies`, `listAvailablePolicies`, `getRegistry`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| `runtime.tools`          | `list`, `get`, `listByApprovalRequired`, `listByAnnotation`, `count` over registered tools. (`listByCallClass` is **deprecated** — it returns `[]` unconditionally because `ToolDefinitionLite` carries no `callClass` field; use `list()` + your own call-class mapping.)                                                                                                                                                                                                                                                                                                                                                                                 |
| `runtime.observerRouter` | Host-side stream observer registration — `register`, `unregister` (returns `ObserverUnregisterReceipt`), enumeration helpers                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| `runtime.degradation`    | `getDegradedModels`, `isModelDegraded`, `getDegradedModelRecord` (returns receipt), `getPlanningContext`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| `runtime.timeTravel`     | `api` — the `TimeTravelAPI` property (lazily constructed; `undefined` when the runtime has no checkpointer) for state snapshot get/list, fork, revert                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| `runtime.dev`            | DevTools-only surface — store + stream-manager accessors, `onRepetitionGuard`, `createTimestamp`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| `runtime.async`          | `spawn`, `spawnStream`, `getResult`, `getResultWithContext`, plus task-manager/subagent-manager accessors                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| `runtime.interrupts`     | `resolve` (returns `InterruptResolveReceipt`), `manager` (the `InterruptManager` property, `undefined` when none is attached)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| `runtime.adapter`        | `get` / `set` the model adapter; `getCapabilityContextForTool`, `getCapabilityRegistry`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| `runtime.events`         | Event log read surface — `iterate({chatId, fromSequenceNumber?})` paginated async-iterable + `fold(projection)` GraphProjection reducer; extends the live event-bus (`on` / `once` / `off`). See [Event log projections](/docs/event-log-projections)                                                                                                                                                                                                                                                                                                                                                                                                      |
| `runtime.spans`          | In-process OTel span surface — `start`, `flush`, `shutdown`, `inFlightCount`, `isShutdown`, `snapshot`. See [OTel observability](/docs/otel-observability). **Wire `config.otelExporter` or spans are dropped** — the default is a `NoopOtelExporter` that accepts every span and discards it on `flush` (`inFlightCount`/`isShutdown`/`snapshot` still track accurately).                                                                                                                                                                                                                                                                                 |
| `runtime.observe`        | `record(row)` — emit one `ObserveRow` to the registered observe destination; silent no-op when none is installed (host call sites stay guard-free). Shares the dispatch slot `@pleach/observe`'s `init()` writes to, so one `init()` activates both the top-level `recordCall` path and `runtime.observe.record`. See [Observe](/docs/observe)                                                                                                                                                                                                                                                                                                             |
| `runtime.probes`         | `emit(label, payload)` plus a TS-typed `typed<K>(key, payload)` over the augmentable `ProbesRegistry`. Emits a probe into the per-runtime registry aggregated by `collectProbes` (the `contributeProbes` plugin hook); no-ops when no plugin contributes a probe at that label                                                                                                                                                                                                                                                                                                                                                                             |
| `runtime._internal`      | Substrate-only escape hatches. Two sub-facets: `_internal.observerRouter` (local-flag/observer-halt bookkeeping per messageId) and `_internal.lifecycle` (seam prompt resolution, job-dispatch/complete notifications). Off-limits to consumer code.                                                                                                                                                                                                                                                                                                                                                                                                       |

Several facets return **receipts** rather than bare booleans —
`sessions.delete`, `sync.resolveConflict`, `observerRouter.unregister`,
`degradation.getDegradedModelRecord`, `interrupts.resolve`. The
receipt shape carries enough context (resolution timestamps,
the resolved id, the resolution decision) to be logged or asserted
against without a follow-up read.

`runtime.graph.*` is **not** a facet on `SessionRuntime`. It is the
graph-layer-only namespace assembled inside the per-turn graph
itself; the K.1 ladder carved `graph.recovery`, `graph.heuristics`,
`graph.config`, and `graph._internal.builders` into the graph
facets tree. The per-turn graph constructs these; the runtime does
not re-expose them.

Alongside those, the graph namespace exposes **one read-only
introspection facet per stage** — `graph.anchor`, `graph.toolLoop`,
`graph.synthesize`, and `graph.postTurn`, mirroring the four-stage
lattice. Each carries the compiled-graph nodes whose `stageId`
matches that stage (a frozen `nodes` array in deterministic
registration order) plus `getNode(id)` for a stage-scoped lookup.
`graph.synthesize` additionally exposes `getSeamIdentity()` — the
reader for the singleton synthesize seam's `nodeId`. The graph builder
holds no reference to the per-runtime seam holder, so this reader
always returns `undefined` today; a future layer may inject a live
seam-identity reader from `SessionRuntime`.

| Graph stage facet  | Stage         | Carries                                     |
| ------------------ | ------------- | ------------------------------------------- |
| `graph.anchor`     | `anchor-plan` | `nodes`, `getNode(id)`                      |
| `graph.toolLoop`   | `tool-loop`   | `nodes`, `getNode(id)`                      |
| `graph.synthesize` | `synthesize`  | `nodes`, `getNode(id)`, `getSeamIdentity()` |
| `graph.postTurn`   | `post-turn`   | `nodes`, `getNode(id)`                      |

### On `TurnOrchestrator` [#on-turnorchestrator]

The per-turn orchestrator handle each turn receives. `TurnOrchestrator`
was renamed from `OrchestratorClient` under D-PO-3; the old name
survives as a `@deprecated` path re-export until `@pleach/core@2.0.0`.
Facets here expose what's in scope for the current turn — config,
history, tools, model resolution.

| Facet                    | What it carries                                                                                                                                                                                                                                                                                       |
| ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `client.config`          | Turn config inspection — `get`, `update`, `getBackend`                                                                                                                                                                                                                                                |
| `client.history`         | Message history reader/writer — `get`, `load`, `clear`, `add`                                                                                                                                                                                                                                         |
| `client.context`         | Per-turn context — `setArtifacts`, `setJobHistorySummary`, `clearArtifactsByJobId`, `setLastUserMessageFileRefs`                                                                                                                                                                                      |
| `client.tools`           | Tool execution surface — `getResolved`, `resolveForCurrentTurn`, `getPreResolvedIntent`, `getCurrentIntent`, `getPendingJobs`                                                                                                                                                                         |
| `client.model`           | Model selection + fallback — `selectForQuery`, `getLastSelection`, `registerPreHook`, `registerPostHook`, `setOnProviderAttempt`                                                                                                                                                                      |
| `client.prompts`         | Composed-prompt surface — `buildSystemPrompt`, `getGraphConfig`, `drainPendingSystemNotices`                                                                                                                                                                                                          |
| `client._internal.graph` | INTERNAL composite for the 7 `*Public` / `*ForGraph` graph-internal cohort — `executeTool`, `applyPreModelTransforms`, `runPostModelHooks`, `getAuthToken`, `getThinkingConfig`, `getFallbackConfig`, `recordToolOutcomes`, `getConversationHistory`. Substrate-only; not on the user-facing surface. |

The flat methods that existed before the facet carve — `getConfig`,
`getHistory`, `selectModelForQuery`, `buildSystemPrompt`, and the
rest — remain on the class with `@deprecated` JSDoc pointing at the
facet equivalent. They forward to the same implementation; the
deprecation cycle is the contract.

## Why facets, not flat methods [#why-facets-not-flat-methods]

A flat namespace with fifty methods is searchable only if you
already know what you're looking for; a facet surface is searchable
by **typing the concern**. `runtime.tenant.<TAB>` lists every
tenant-scoping primitive; `runtime.events.<TAB>` lists every
event-log primitive; `runtime.spans.<TAB>` lists every in-process
span accessor. The grouping IS the documentation — a new
contributor reads the facet names first, picks the one that names
their concern, and the IDE shows them every primitive inside. The
same property holds for an LLM-driven coding agent reading the
runtime's type surface to plan its next action: a handful of facet
names with one-line descriptions costs far less token budget than
fifty method signatures with overlapping naming.

The flat methods (`runtime.createSession()`, `client.getConfig()`)
remain available as forwarders for back-compat. The facet path is
canonical; the flat surface is a migration aid.

## Surface stability [#surface-stability]

A facet groups methods that share a domain. Refactors inside that
domain — renaming a private helper, swapping the internal store,
adding a new method to the group — leave the facet shape unchanged.
Consumer code that destructures the facet keeps working:

```typescript
const { id, subId } = runtime.tenant;
const { iterate, fold } = runtime.events;
```

The facet shape is the contract; the methods inside are the
implementation. Code that pins to a facet sees fewer breakage
events at minor bumps than code that pins to individual methods,
because the substrate can churn underneath the facet without
changing the facet's exported shape.

## What's internal — don't use [#whats-internal--dont-use]

Two namespaces are off-limits to consumer code:

* `runtime._internal.*` — substrate-only escape hatches with two
  sub-facets (`observerRouter` for local-flag/halt bookkeeping,
  `lifecycle` for seam prompt resolution + job notifications).
  Subject to change without a deprecation cycle. The leading
  underscore is the signal; treat the field as if it weren't
  exported.
* `TurnOrchestrator._internal.graph` — same rules. The composite
  holds the 7 `*Public` / `*ForGraph` graph-internal methods the
  per-turn graph consumes. The public surface for graph inspection
  is `runtime.graph.*` inside the graph layer, not `_internal.graph`
  on the orchestrator. Consumer code reading it will break the next
  time the snapshot shape changes.

Code that touches `_internal` is breakage waiting to happen at the
next patch release. CI gates (see below) flag consumer imports of
`_internal` symbols so the breakage surfaces in CI, not at runtime.

## CI gates enforcing facet coverage [#ci-gates-enforcing-facet-coverage]

Three audit gates ride on the facet surface. Each gate runs in
upstream CI; all three are **WARN-DRIFT** today (non-blocking) and
will promote to strict per D-AGENT-8 once in-tree migration of the
last flat-method call sites finishes.

### `audit:facet-coverage` [#auditfacet-coverage]

Asserts every public capability on `SessionRuntime` has a facet
entry. Adding a new public method without placing it under a
facet — or under the explicit "stays flat" allowlist — surfaces in
the report. The intent is to keep the surface organized by domain as
the runtime grows; new capabilities land under the facet that names
their concern.

### `audit:orchestrator-facet-coverage` [#auditorchestrator-facet-coverage]

The same gate, scoped to `TurnOrchestrator`. Adding a new field on
the client handle without a facet placement (or an `_internal`
prefix) surfaces in the report.

### `audit:graph-facet-coverage` [#auditgraph-facet-coverage]

The same gate, scoped to the `runtime.graph.*` sub-tree. The graph
namespace is fast-growing as the per-turn graph absorbs more
substrate logic; the gate keeps the sub-tree organized by domain
(`recovery`, `heuristics`, `config`, `_internal.builders`, plus the
per-stage introspection facets `anchor` / `toolLoop` / `synthesize`
/ `postTurn`) rather than letting every new helper land at the top
level.

## Deprecation contract for flat methods [#deprecation-contract-for-flat-methods]

Flat methods (e.g. `runtime.createSession()`, `client.getConfig()`)
ship with `@deprecated` JSDoc that points at the facet equivalent
(`runtime.sessions.create()`, `client.config.get()`). IDEs render
the strike-through; TypeScript surfaces the deprecation in the
language service. The lifecycle:

1. **Minor bump.** Adds the facet. Marks the flat method
   `@deprecated` with a pointer at the facet.
2. **Next major.** Removes the flat method. The facet is the only
   path.

Flat methods don't drop in minor versions. The deprecation cycle
is the contract — consumer code that ignores the `@deprecated`
warning still works through the minor series, and the next major
is the announced breakage window. The `OrchestratorClient` →
`TurnOrchestrator` rename follows the same shape: the old name is a
path re-export until `@pleach/core@2.0.0`.

## Migrating to the facet path [#migrating-to-the-facet-path]

The rename is mechanical — the flat method's name appears verbatim
inside the facet, prefixed with the facet accessor.

```typescript
// Flat (back-compat forwarder)
const id = await runtime.createSession({ provider });
const messages = client.getHistory();
const model = client.selectModelForQuery(userMessage);

// Canonical (facet path)
const id = await runtime.sessions.create({ provider });
const messages = client.history.get();
const model = client.model.selectForQuery(userMessage);

// Facet-first from the start
for await (const row of runtime.events.iterate({ chatId })) {
  // ...
}
```

The last block uses a facet that was facet-first from the start.
Mixed codebases are expected during the migration window; the
deprecated JSDoc on the flat methods is what flags the lines that
still need rewriting.

A codemod that walks the AST and rewrites every flat-method call
into its facet equivalent is straightforward — the rename map is
1-to-1. Hosts running large migrations should write the codemod
once and run it across the consumer tree rather than rewriting
call sites by hand.

## Where to go next [#where-to-go-next]

<Cards>
  <Card title="SessionRuntime" href="/docs/session-runtime" description="The class your host constructs — the host object every facet hangs off." />

  <Card title="Tenant facet" href="/docs/tenant-facet" description="`runtime.tenant.id` and the multi-tenant scoping surface." />

  <Card title="OTel observability" href="/docs/otel-observability" description="`runtime.spans.snapshot()` and the in-process span surface facets route through." />

  <Card title="Event log projections" href="/docs/event-log-projections" description="`runtime.events.iterate` / `fold` — the read surface on the per-session event log." />
</Cards>
