ConnectIntelligence layer

Integrating the intelligence layer

The intelligence service is internal / M2M only (not a public/partner API). Integration is two-sided and deliberately low-friction:

  1. Pull a ranking from one versioned contract — POST /v1/recommendations/rank.
  2. 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" and policy.version tell you exactly what produced the order — surface it for transparency.
  • effect_size / confidence are 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:

EventBecomes the outcomeEmitted by
order.placed.v1commerce outcome (purchase, value)commerce
clinical.biomarker.added.v1biomarker-delta outcome (the clinical feedback wire)clinical
user.erasure_requested.v1GDPR erasure of the entity’s ledger + featuresidentity

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

ScopeUse
write:intelligencecall /v1/recommendations/rank (it logs a decision)
read:intelligenceread meta-metrics / decisions / features
admin:intelligencegovernance (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.