Integrating the intelligence layer
The intelligence service is internal / M2M only (not a public/partner API). Integration is two-sided and deliberately low-friction:
- Pull a ranking from one versioned contract —
POST /v1/recommendations/rank. - Learn — the loop attributes outcomes from domain events your app already emits. No new producer code.
See the concept for why it’s a loop and the architecture for how it’s built.
1. Pull — POST /v1/recommendations/rank
The single serving contract every product surface calls, at every ★ stage. The ranker behind it changes (S0 rules → S5 model); the request/response shape never does — it’s a versioned @platform/contracts type with a conformance test, so advancing the model is a flag flip, not a breaking change.
Call it through the typed SDK with an M2M token carrying write:intelligence (or admin:intelligence):
import { M2MClient } from "@platform/client";
import { SERVICE_NAMES } from "@platform/contracts";
import { SCOPES } from "@platform/scopes";
import { createIntelligenceClient } from "@platform/sdk-intelligence";
const auth = new M2MClient({
identityUrl: process.env.IDENTITY_BASE_URL,
clientId: process.env.M2M_CLIENT_ID,
clientSecret: process.env.M2M_CLIENT_SECRET,
audience: SERVICE_NAMES.INTELLIGENCE,
scopes: [SCOPES.WRITE_INTELLIGENCE],
});
const intelligence = createIntelligenceClient({
baseUrl: process.env.INTELLIGENCE_SERVICE_URL ?? "http://intelligence:3000",
auth,
headers: { "x-source": "loop-health" },
});
const { data } = await intelligence.POST("/v1/recommendations/rank", {
body: {
entity_id: userId, // who we're ranking for (the canonical user uuid)
brand_id: "loop-health",
surface: "commerce.pdp", // where the rec is shown
candidates: [ // the items you want ranked
{ item_id: "prod-a", metadata: { title: "Magnesium" } },
{ item_id: "prod-b", metadata: { title: "Creatine" } },
],
context: { source: "homepage" }, // optional request-time features
max_results: 10,
},
});
// data.ranked -> [{ item_id, rank, score, effect_size, confidence, provenance, reason }]
// data.stage -> "S0" | … | "S5" (which ranker served)
// data.policy -> { kind, key, version } (provenance: exactly what served)
// data.decision_id -> the logged Decision Record (the loop's learning anchor)Why it writes as well as reads: every call logs a Decision Record (with the selection propensity) so the loop can learn from it later — that’s why the scope is write:intelligence. You don’t manage any of that; you pass context, you get a ranking.
Provenance + safety come back with every ranking
provenance: "rule" | "model" | "both"andpolicy.versiontell you exactly what produced the order — surface it for transparency.effect_size/confidenceare populated once a model serves (null at S0/S1).- The response is already guardrail-filtered — any candidate a safety/compliance rule blocks is dropped before you receive it (the guardrail gate runs server-side; see the concept). You never have to post-filter for safety.
2. Learn — emit your normal events
The loop’s Outcome Join attributes a later real-world result back to the decision that produced the rec — possibly weeks later. It consumes events apps already emit:
| Event | Becomes the outcome | Emitted by |
|---|---|---|
order.placed.v1 | commerce outcome (purchase, value) | commerce |
clinical.biomarker.added.v1 | biomarker-delta outcome (the clinical feedback wire) | clinical |
user.erasure_requested.v1 | GDPR erasure of the entity’s ledger + features | identity |
You add no producer code. If your surface already emits order.placed.v1, the loop already learns from it. (Attribution today is by entity + time window; a future enhancement lets you pass an explicit decision_id for exact attribution.)
The stage flag
feature.intelligence.stage (default S0) selects the ranker. It is the turn-on switch for the activation pathway — and the instant rollback: any stage is one flag flip back to the previous. Nothing in the product path changes until you advance it.
Scopes
| Scope | Use |
|---|---|
write:intelligence | call /v1/recommendations/rank (it logs a decision) |
read:intelligence | read meta-metrics / decisions / features |
admin:intelligence | governance (approve/reject/apply), registry, loop tick; satisfies the above |
What you don’t do
- You don’t compute rankings, log decisions, run guardrails, or manage model versions — the service owns all of it.
- You don’t get public/partner access — if that’s ever needed it’s a separate BFF + auth decision, not this service.