Chapter 05
SDK reference
The HolonographClient is the operator-facing surface end-to-end: one class you construct, one call flow — messages.create → handle → reportOutcome — and two auxiliary namespaces (contract, availability) for shape and gap declarations. This chapter documents every method.
Install
npm install @holonograph/client
# or
pnpm add @holonograph/client
# or
yarn add @holonograph/client
Single self-contained ES module, no runtime dependencies. Targets Node 18+ and any runtime with a global fetch. Fully typed — the package ships its own type declarations.
Construct a client
import { HolonographClient } from "@holonograph/client";
const client = new HolonographClient({
endpoint: "http://127.0.0.1:8080",
token: process.env.HOLONOGRAPH_TOKEN,
runMode: "production",
lensVersion: "lv_2026_07_01",
substrate: {
lensVersion: "lv_2026_07_01",
lightSourceIdentifier: "anthropic/claude/4",
runMode: "production",
provenance: "production",
operatorColumns: {}
}
});
Constructor options
| Option | Shape | Notes |
|---|---|---|
endpoint | string | URL where your running Holonograph lens is reachable. The README ships with http://127.0.0.1:8080 as its example host. |
token | string (opt) | Bearer token, when the lens requires one. |
runMode | enum (opt) | One of the canonical modes (production, test, eval, replay, local_dev). Sent as x-holonograph-run-mode on every request. Can be resolved per-call at request time — see Run modes. |
lensVersion | string | Identifier of the lens configuration this client should call under. Pinned into every evaluation event this client produces. |
substrate | object | The SubstrateSnapshot attached to every event. Carries lensVersion, lightSourceIdentifier, runMode, provenance, and operatorColumns — see below. |
The substrate object
| Field | Shape | Notes |
|---|---|---|
lensVersion | string | Which lens configuration was in effect. |
lightSourceIdentifier | string | Which model produced the output, as vendor/model/version. |
runMode | enum | Which run mode the evaluation ran under. |
provenance | string | Origin category of the run — see Substrate columns. |
operatorColumns | object | The operator-declared deployment-state columns, keyed by column id. For example: { prompt_hash: "v2-sha256-…", tool_schema_version: "3.1.0" }. |
The call flow
One evaluation is three calls: messages.create, then use .result, then .reportOutcome(...).
const handle = await client.messages.create({
surfaceId: "classify-intent",
messages: [{ role: "user", content: "why was I charged twice?" }],
tools: [ /* ... */ ]
});
const answer = handle.result; // read/act on the model's response
const isCorrect = grade(answer); // your evaluation logic
await handle.reportOutcome({
dimensions: [{
dimensionId: "correctness",
passed: isCorrect,
expected: "a correct answer to the user's question",
actual: summarize(answer)
}],
cohortTags: ["customer-tier=enterprise"]
});
messages.create() mediates the model call and returns a handle. handle.result is the response you use in your agent. handle.reportOutcome() closes the loop — it persists the evaluation event that binds this call into your history.
messages.create(req)
Every field on the request is optional except surfaceId and messages. The rest are passed through to the model when relevant, or interpreted by the lens.
| Field | Shape | Notes |
|---|---|---|
surfaceId | string (req) | The surface this call belongs to. Must exist on the active contract. |
messages[] | array (req) | The conversation turns. |
system | string (opt) | System prompt. |
systemOverride | string (opt) | Overrides the system prompt for this call only. |
additionalSkills | string[] (opt) | Extra skills to inject for this call. |
additionalContextTags | string[] (opt) | Extra context tags this call should carry. |
lightSourceIdOverride | string (opt) | Overrides the surface's default routing to a specific vendor. |
maxOutputTokens | number (opt) | Cap on the model's output length. |
temperature | number (opt) | Model sampling temperature. |
tools | array (opt) | Tool declarations for the model to invoke. |
toolChoice | object (opt) | Which tool the model must invoke, if any. |
cacheControl | object (opt) | Caching hints for the model provider. |
correlationId | string (opt) | Pass one in to bind this call to a prior call's correlation. Otherwise generated. |
The call handle
The object returned by messages.create().
| Member | Shape | Notes |
|---|---|---|
.result | object | The primary model's response. Use this as the answer in your agent. |
.observerRecords[] | array | Under multiplex routing, the observer vendors' captured records. Empty when not multiplexing. |
.reportOutcome(outcome) | method | Persist the evaluation event that closes this call. |
.gradeObserver(recordOrId, outcome) | method | Attach grades to a cross-vendor observer call before reporting, when the lens returned observer records. Call this before reportOutcome. |
reportOutcome(outcome)
| Field | Shape | Notes |
|---|---|---|
dimensions[] | array (req) | One entry per dimension declared on the surface. Each entry: { dimensionId, passed, expected, actual }. |
cohortTags[] | string[] (opt) | Discrete labels to slice on later. For diagnostic substrate columns, use the <column.id>=<value> form. |
sideEffects[] | array (opt) | Any downstream actions this call triggered — useful for correlated fixtures and lessons later. |
fixtureId | string (opt) | The fixture this call satisfied, when the call was run as part of a conformance check. |
Dimension entry shape
| Field | Shape | Notes |
|---|---|---|
dimensionId | string | Must match a dimension declared on the surface's contract. |
passed | boolean | Whether the dimension passed the evaluation. |
expected | string | What good looked like — the expected shape of the response. |
actual | string | What the model actually produced (or a short summary). |
Calling reportOutcome persists the evaluation event to Holonograph's substrate — one row, four layers bound (see The four-layer snapshot). What Holonograph does with that event afterwards is the subject of the analysis chapters.
Multiplex: grade the observers
When the surface's contract routes to more than one vendor, the primary's response comes back as handle.result and the observers' captured records come back as handle.observerRecords[]. Grade the observers before reportOutcome so all vendors' outcomes land in the same evaluation.
for (const rec of handle.observerRecords) {
await handle.gradeObserver(rec, {
dimensions: [{
dimensionId: "correctness",
passed: isCorrect(rec.result),
expected: "a correct answer to the user's question",
actual: summarize(rec.result)
}]
});
}
await handle.reportOutcome({
dimensions: [{
dimensionId: "correctness",
passed: isCorrect(handle.result),
expected: "a correct answer to the user's question",
actual: summarize(handle.result)
}]
});
See The multiplexer for how routings are declared.
Contract
contract.register(contract)
Registers or replaces the surface contract for this agent. Returns a ContractSummary reflecting the newly-active shape. See Surface contracts for the full contract shape and the summary fields.
const summary = await client.contract.register({
agentId: "billing-triage",
surfaces: [ /* ... */ ]
});
console.log(summary.surfaces[0].dimensionCount);
Availability
availability.mark(req, opts?)
Records that a surface is (or is not) available for evaluation over a given interval. Two modes:
- Single — one transition event: "this surface just became unavailable" or "…just came back." Pass
mode: "single"with atransition. - Paired — bound an interval you already know the extent of. Pass
mode: "paired"withunreachableFromandunreachableUntil. The response includes apairingreference so downstream analyses can bracket the interval cleanly.
await client.availability.mark({
mode: "single",
surfaceId: "classify-intent",
transition: "unavailable",
reason: "vendor-outage"
});
Response:
{ "eventIds": ["evt_..."], "pairing": null }
Headers & run mode resolution
The client sends two headers on every request:
Authorization: Bearer <token>— when the client was constructed with atoken.x-holonograph-run-mode: <mode>— from the client'srunMode, unless resolved per-call. A missing mode is rejected by the daemon by design (fail-closed).
For scenarios where the run mode is determined at request time (Express middleware, async work, custom resolution), see Run modes.
Escape hatch: callDirectly
For scenarios that need the full request shape — a bespoke bridge, a replay tool, or an integration test that wants to prod an edge of the wire — the client exposes a lower-level callDirectly method that skips the messages.create ergonomics and forwards the request as-authored.
const result = await client.callDirectly(rawRequest);
Prefer messages.create for anything user-facing. callDirectly is an escape hatch, not the everyday path.
Errors
Errors from the lens surface as HolonographHttpError, which carries the HTTP status, an error code, and any details the lens returned. There are additional typed error classes for the grading and availability flows so a catch block can discriminate on class.
import { HolonographClient, HolonographHttpError } from "@holonograph/client";
try {
await handle.reportOutcome(outcome);
} catch (err) {
if (err instanceof HolonographHttpError) {
console.error(err.status, err.code, err.details);
} else {
throw err;
}
}
The underlying wire shape every error carries:
{
"error": "human message",
"code": "error_code",
"details": { }
}
4xx covers validation, auth, and not-found. 5xx is internal. 501 means the feature isn't wired on the given deployment.
Advanced: HttpTransport
The client is built on an HttpTransport primitive that's exported for advanced use — swapping in a custom fetch, adding request interceptors, or running the transport under a different runtime harness. Most integrations never touch it. If yours does, the transport surface is fully typed; the tests in the package are the working reference.