Chapter 06
The mediating lens: placement & call flow
Holonograph's central affordance — the lens — is where it sits. This chapter is about the physical placement of the lens in your call path and the shape of a single call as it moves through it.
What it is
The lens is a bidirectional in-process mediator between your agent and the model. Your agent doesn't call the vendor directly; it calls POST /holonograph/messages on the local Holonograph daemon, and Holonograph forwards to the vendor on your behalf. Both directions of the exchange — request out and response back — pass through the same code path.
Practically, that placement gives you three things:
- One boundary, two directions. Every call has a single owning process that sees the wire before and after. Nothing is reconstructed from logs after the fact.
- Vendor-agnostic capture. The daemon speaks each vendor's protocol at the outbound edge, then hands your agent back a consistent shape at the inbound edge.
- No SDK version coupling in your code. When a vendor changes their SDK, the change is absorbed at the lens, not in every consumer.
Why it matters
The alternative — instrumenting the agent's code with logging calls — has two structural weaknesses. Log lines can only record what the developer thought to log, and the log format is coupled to the agent's version of the vendor SDK. Placement side-steps both. Because the lens owns the boundary, capture is a property of the architecture, not a discipline the developer must remember.
POST /holonograph/messages as your model provider's endpoint from the agent's point of view. The vendor call still happens — it just happens on the daemon's side of a stable, local wire.
How to use it
Direct HTTP
The endpoint accepts a shape closely mirroring a vendor's messages call, plus a required surfaceId that ties the call to your contract.
POST /holonograph/messages
Authorization: Bearer <token>
x-holonograph-run-mode: production
Content-Type: application/json
{
"surfaceId": "classify-intent",
"messages": [
{ "role": "user", "content": "why was I charged twice?" }
],
"system": "You are a billing triage assistant.",
"temperature": 0.2,
"maxOutputTokens": 512,
"tools": [ /* ... */ ],
"correlationId": "corr_01H..."
}
Response:
{
"callId": "call_01H...",
"correlationId": "corr_01H...",
"surfaceId": "classify-intent",
"lightSourceId": "anthropic/claude-sonnet-5",
"result": { /* the primary model's response */ },
"observerRecords": [ /* multiplex observers, if any */ ],
"gateOutcome": null,
"substituted": false
}
Via the SDK
Most integrations use the SDK's messages.create(), which wraps the endpoint and returns a call handle documented in SDK reference. The wire shape above is what the handle's .result is unpacked from.
const handle = await client.messages.create({
surfaceId: "classify-intent",
messages: [{ role: "user", content: "why was I charged twice?" }],
system: "You are a billing triage assistant.",
temperature: 0.2,
maxOutputTokens: 512
});
const answer = handle.result;
Overrides that ride along
Two request fields let you override the surface's contract for a single call, when you know what you're doing:
systemOverride— replaces the system prompt for this call only. Does not mutate the contract.lightSourceIdOverride— routes this call to a specific vendor instead of the surface's default routing. Useful for reproducing an issue against a known-good model.
Reading the output
The response carries five load-bearing fields:
| Field | Shape | What it tells you |
|---|---|---|
callId | string | The unique identifier of this call. Use it in a subsequent reportOutcome if you're not using the SDK handle. |
correlationId | string | Ties this call to the evaluation it belongs to. If you passed one in, it comes back unchanged; if not, one was generated. |
surfaceId | string | Echo of the surface this call was made against. |
lightSourceId | string | The vendor and model that actually produced the response (vendor/model/version). Reflects the effective routing after any override. |
result | object | The primary model's response, in a shape your agent uses. |
observerRecords | array | Under multiplex routing, the captured records from the observer vendors. Empty when not multiplexing. |
gateOutcome | object (opt) | Present when a gate fired (e.g. content-policy gate, cost cap). Null otherwise. |
substituted | boolean | True when the response was substituted (e.g. a cached hit, a canned refusal). Downstream analyses use this to distinguish substitute from live-generated. |
Reference
Request fields
| Field | Shape | Notes |
|---|---|---|
surfaceId | string (req) | Must exist on the active contract. |
messages[] | array (req) | The conversation turns. |
system | string (opt) | System prompt. |
systemOverride | string (opt) | Per-call system prompt override. |
additionalSkills | string[] (opt) | Extra skills to inject for this call. |
additionalContextTags | string[] (opt) | Extra context tags this call carries. |
lightSourceIdOverride | string (opt) | Overrides the surface's default routing. |
maxOutputTokens | number (opt) | Cap on the model's output length. |
temperature | number (opt) | Model sampling temperature. |
tools | array (opt) | Tool declarations. |
toolChoice | object (opt) | Which tool the model must invoke. |
cacheControl | object (opt) | Provider caching hints. |
correlationId | string (opt) | Pre-existing correlation id to bind this call to a prior evaluation. |