Skip to main content
When the same application serves an agent experience graph, call mountExperienceGraph(app, options) immediately after express() and before any global app.use(...) middleware. Its fail-closed startup check allows earlier route-specific layers only when they cannot shadow the graph namespace.
This one middleware instruments eligible JSON and server-rendered HTML responses and mounts the company-owned same-origin consent and answer relay. The customer installs nothing. Epode failures preserve the original response. Use customer.context.get, customer.personalization.decide, and customer.outcomes.track as shown in the quickstart. The same enrichment request records the final HTTP status, product-handler duration, optional product-issued journey, and optional bounded runtime label. Never populate sessionRef or runtimeHint from query parameters, agent input, or other unverified values. The selected response waits at most timeoutMs (250 ms by default) for the enrichment request. A timeout or failure returns the original JSON unchanged. For routes without a stable company identity, use customer.contextFor(request); it validates the agent-returned Epode-Context-Interaction header so context is available only on the immediate retry of the same interaction. authenticate runs only on included business routes. The adapter bypasses it only for the exact POST relay paths, which authenticate with their short-lived aqr1_ capability. Do not mount rejecting global product auth above the Epode middleware.

Server-rendered HTML

Bounded successful HTML strings on included routes receive a non-executable <meta name="epode-customer-context"> marker containing base64url JSON. This preserves the page’s CSP and visible content. Streams, buffers, documents over maxHtmlBytes (512 KiB by default), and pages that already own the marker remain unchanged. Mutated pages become private, no-store; stale length, digest, and ETag headers are removed or regenerated. Run the anonymous-to-known retail example · Run the anonymous advertising example

Legacy structured outcome feedback

Install

Node 20 or newer is required.

Configure once

Run product authentication and authorized tenant selection first, then register Epode before the included route handlers. Identity accessors only read state your product already verified or owns.
No route wrapper or response call is required. The middleware derives the normalized operation from the Express route.

Cached APIs

The default cacheMode: "safe" does not silently turn a response with public, max-age, s-maxage, immutable, or stale-while-revalidate into a private response. It skips those responses and logs one configuration warning. If ordinary callers share a CDN response but feedback-aware agent callers can opt in, use request mode:
The agent caller sends Agent-Feedback-Request: 1. Only that request receives feedback metadata and becomes private, no-store; the public response keeps its existing cache policy. The SDK also adds Vary: Agent-Feedback-Request, so a CDN cannot replay the ordinary cached body to an opted-in caller. Use cacheMode: "private" only when disabling shared caching on every included response is intentional. Eligible ordinary 2xx GET and HEAD responses also carry a same-path-and-query Link marker, allowing an installed Epode Companion to make one authenticated opt-in refetch without changing the public body. * matches one path segment (/docs/* matches /docs/guide). Use ** for any depth (/docs/** also matches /docs/platform/authentication).

Async jobs

Instrument the result, not every polling response:
Creation responses, in-progress polls, and failures remain untouched. The terminal result asks for one assessment after the agent can judge the experience. authorizedCrawl.id must be the server-loaded job the authenticated caller is allowed to access—not a raw header, query value, or unverified body field.

Server-rendered HTML

Use the same middleware and include the HTML route:
Eligible HTML receives an embedded <script id="agent-feedback" type="application/json"> contract. It does not change the visible page.

Verify

The doctor checks shape preservation, the scoped capability, trusted submission origin, and the company-side opportunity without creating feedback. In ask modes, it validates consent without claiming approval. Use a real customer-agent task to prove confirmation and reporting.

Serverless and edge-hosted Node

Keep the middleware object outside the request handler when the platform reuses isolates. If the platform provides waitUntil, attach feedback.flush() to that lifecycle hook after the response is committed:
Do not await telemetry before sending the product response. On platforms with no background lifecycle at all, opportunity telemetry is best effort and may be dropped when an isolate freezes; feedback reports still use their independent signed submission path.

Customer-agent coverage

Your middleware creates the HTTP feedback contract; it cannot make an independent customer agent act on that contract. For reliable Codex and Claude Code handling, customers install Epode Companion once for all Epode-instrumented products. This is a user-side plugin, not another company SDK or product key. Generic agents without the Companion can still use the API normally, but feedback remains best effort.

What is not instrumented

Errors, redirects, health and metrics routes, assets, streams, binary responses, and routes outside include remain untouched. JSON arrays and scalar values use response headers instead of being wrapped. View the runnable Express example View the cached search API example