Applying Jev (System One Decision Model) to LEO Customer 360

Detailed design: concept, flows, rollout, and comparisons

Design report · LEO CDP leo-customer360 · 2026-10-03 Builds on the-customer360-model-layer.md (§1.2, §2.5, §2.6, §7), which argued why a System One / Jev layer belongs beside the LLM. This document specifies how to wire it in.

⚠️ Provenance caveat. Every Jev performance, cost, and calibration figure (70–500 ms, $0.042/M, “output free”, “calibrated”, “zero type errors”) is a single-source vendor claim (TypeSafe.ai) and is marked [unverified]. This design is therefore gated: nothing in Phase 2+ ships before the Phase 1 calibration POC passes (§9).

Assumption. “JEV (decision model)” is read as System One / Jev as used in the model-layer paper. Interface details of the vendor API are assumed (typed function call returning {value, confidence}); confirm against the real API before Phase 0 completes.

1. Executive summary

ItemDecision
What Jev is forFast, typed decisions with a calibrated confidence (classify, route, match, rerank)
What Jev is not forFree text: SQL, copy, narratives, transliteration stay on the LLM
Where it plugs inA new structured_decision engine family in cdp_ai_agents, called via a thin client inside customer360-agent
First use caseIdentity-resolution gray-zone adjudication (labels already exist, fully human-gated)
Killer use caseReal-time next-best-action rerank in the request path
GateCalibration (ECE), latency p95, cost / 1k decisions, precision/recall at the gate
FallbackKeep the LLM structured-output path; Jev is behind a feature flag per agent row

2. Concept

2.1 Two reasoning modes, one decision rule

An LLM deliberates in prose (seconds, per-token cost, uncalibrated “I’m 90% sure”). A System One model decides in one pass and returns a typed value plus a confidence that is claimed to be calibrated. The whole design reduces to one routing rule: route by output shape.

flowchart LR
    J["Model-touching job"] --> Q{"Output shape?"}
    Q -- "free text / SQL / copy" --> LLM["LLM gpt-5.6\n(System 2, seconds)"]
    Q -- "typed value + confidence\n(label, bool, pick-1-of-k)" --> JEV{{"Jev\n(System 1, sub-second)"}}
    Q -- "numeric score with history" --> ML["Own trained model\n(xgboost / lightgbm)"]
    Q -- "similarity / recall" --> VEC["pgvector"]

2.2 The confidence gate (the load-bearing idea)

Jev’s value is not the answer, it is that p can be thresholded. Two thresholds lo < hi split every decision into three lanes. Thresholds come from the POC reliability curve (§9), never from the vendor.

flowchart LR
    IN["Typed request"] --> JEV{{"Jev"}}
    JEV --> OUT["{ value, confidence p }"]
    OUT --> G{"p vs thresholds"}
    G -- "p >= hi" --> A["Lane A: act / recommend\n(low-risk: auto; high-risk: 1-click confirm)"]
    G -- "lo < p < hi" --> B["Lane B: human queue\n(+ LLM writes the reason)"]
    G -- "p <= lo" --> C["Lane C: deterministic default\n(no-op / rules result)"]
    A & B & C --> LOG["decision log\n(for ECE re-measurement)"]

2.3 Engine comparison

DimensionLLM (gpt-5.6)Jev (System One)Numeric ML (lead/churn/clv/cx)pgvector
OutputFree textTyped value + confidenceNumberRanked ids
Latency3–329 s (observed in paper)70–500 ms [unverified]ms (batch)ms
Cost shapePer output tokenNear-zero output [unverified]Fixed infraFixed infra
ConfidenceSelf-reported, uncalibratedCalibrated [unverified]Model-specific, calibrated by usDistance, not probability
Needs training dataNoNo (zero-shot)YesNo
Explains itselfYes (prose)Weakly (one-liner)Via reason codes (LLM)No
Best atSQL, copy, narrativeMatch, route, classify, rerankScoring at scaleRecall
RiskParse failures, driftVendor lock-in, unproven calibrationStale modelPoor semantics on short text

3. Target architecture

3.1 Where Jev sits

customer360-agent today exposes /plan/email, /plan/segment, /plan/zalo over LiteLLM (ai_providers/provider.py). LiteLLM is chat-shaped; a Jev call is a typed function call. So Jev gets a sibling client, not a LiteLLM route.

flowchart TB
    subgraph Callers
      BE["customer360-backend\n(Dagster jobs)"]
      API["customer360-api"]
      EVT["customer360-event-api\n(request path)"]
    end
    subgraph AGENT["customer360-agent (FastAPI)"]
      direction TB
      EP["/decide/{agent_code}  NEW"]
      PL["/plan/* (existing)"]
      REG["agent registry reader"]
      subgraph Clients
        LL["ai_providers (LiteLLM)"]
        JC["jev_client  NEW"]
      end
      GATE["confidence gate + schema validator  NEW"]
      LOGW["decision logger  NEW"]
    end
    DB[("Postgres\ncdp_ai_agents\ncdp_ai_decision_log NEW")]
    VEND["Jev endpoint\n(vendor or self-hosted)"]
    OAI["LLM provider"]

    BE & API & EVT --> EP
    BE & API --> PL
    EP --> REG --> DB
    EP --> JC --> VEND
    PL --> LL --> OAI
    JC --> GATE --> LOGW --> DB

3.2 Components and change footprint

ComponentChangeTypeSize
customer360-database/database-schema.sqlAdd 'structured_decision' to model_type CHECK; add cdp_ai_decision_logModify + new tableS
customer360-database/init-cdp-ai-agents.sqlSeed Jev agent rows (§5)SeedS
customer360-agent/src/jev_client/Typed client, timeout, retry, circuit breakerNew packageM
customer360-agent/src/app.pyPOST /decide/{agent_code}New routeS
customer360-agent/src/decision/Per-agent request/response Pydantic schemas, gate, loggerNew packageM
customer360-agent/src/leo_customer360_agent/client.pydecide() method for callersModifyS
customer360-daoAiDecisionLog modelNew modelS
customer360-backend/identity_resolutionGray-zone hook (Phase 1)ModifyM
customer360-backend/ai_agents_runnersNBA job (Phase 2)Fill handler behind the master workflowM
customer360-agent/tests/Contract tests with recorded Jev fixturesNewM

3.3 Request contract (POST /decide/{agent_code})

// request
{
  "tenant_id": "…",                    // overwritten server-side from auth, never trusted
  "input": { /* agent-specific, schema-validated, PII-redacted */ },
  "options": { "timeout_ms": 800, "mode": "shadow|live" }
}
// response
{
  "decision_id": "uuid",
  "agent_code": "identity_adjudicator",
  "instruction_version": 3,
  "value": { "match": true },          // typed per agent
  "confidence": 0.93,
  "lane": "A|B|C",                     // computed by gate, thresholds from registry row
  "engine": "jev",
  "latency_ms": 212,
  "degraded": false                    // true if fallback (LLM/rules) answered
}

3.4 Registry extension

Column / valueBeforeAfter
model_type CHECKclassification, regression, clustering, rules_engine, generative_llm+ structured_decision
model_nameopenai/gpt-5.6-…, xgboost-…+ jev (or self-hosted tag)
thresholds lo/hin/astored per agent (reuse the existing config/variables column of the row; confirm column during Phase 0 — no new column unless none fits)
instruction_versionper prompt revisionreused as the schema/threshold revision for Jev agents

4. Use-case flows

4.1 Identity gray-zone adjudication (Phase 1, first)

Rules stay in the driver’s seat. Jev only sees pairs the deterministic matcher cannot settle. Jev never merges; merges stay reversible via cdp_profile_merge_history.

sequenceDiagram
    autonumber
    participant IR as identity_resolution job
    participant AG as customer360-agent /decide
    participant JV as Jev
    participant LM as LLM
    participant DB as Postgres
    participant HU as Reviewer

    IR->>IR: rule predicates on candidate pair
    alt clear match / clear distinct
        IR->>DB: auto-merge / new master (+ merge_history)
    else gray zone
        IR->>AG: pair features (PII-redacted)
        AG->>JV: typed request
        JV-->>AG: {match, p}
        AG->>DB: write decision_log
        alt p >= hi
            AG-->>IR: recommend merge
            IR->>HU: 1-click confirm queue
        else lo < p < hi
            AG->>LM: explain pair for reviewer
            LM-->>AG: reason text
            AG-->>IR: needs review + reason
            IR->>HU: full review queue
        else p <= lo
            AG-->>IR: keep distinct
        end
        HU->>DB: confirm/reject -> merge_history (label for ECE)
    end

Human decisions flow back as labels, so the reliability curve keeps updating for free.

4.2 Real-time next-best-action (Phase 2)

flowchart LR
    REQ["page load / live event"] --> RC["pgvector recall\ntop-k (k≈20)"]
    RC --> DEC["/decide/nba_reranker"]
    DEC --> JEV{{"Jev rerank\n+ pick content, channel, timing"}}
    JEV --> GATE{"p >= hi?"}
    GATE -- yes --> NBA["serve NBA"]
    GATE -- no --> RULE["rules / top-1 recall fallback"]
    DEC -. "timeout 300 ms" .-> RULE
    NBA --> LOG["decision_log + impression event"]
    RULE --> LOG
    LOG --> AB["A/B vs rules-only (engagement lift)"]

Hard rule: the request path always has a deterministic fallback; Jev can only improve a response, never block it.

4.3 Event routing and PII / data-quality classification (Phase 3)

flowchart LR
    EV["inbound event / new source column"] --> BATCH["micro-batch (≤ N)"]
    BATCH --> DEC["/decide/event_router\n/decide/pii_classifier"]
    DEC --> JEV{{"Jev typed label + p"}}
    JEV -- "p >= hi" --> APPLY["apply routing / tag column"]
    JEV -- "else" --> Q["review queue (data steward)"]
    APPLY --> MAP["field-mapping proposal (never auto-write to golden record)"]

4.4 Cold-start scoring (Phase 4)

stateDiagram-v2
    [*] --> ColdStart: no labeled history
    ColdStart: Jev provisional calibrated score
    ColdStart --> Trained: enough labels, model trained
    Trained: numeric model (xgboost/lightgbm)
    Trained --> [*]
    note right of ColdStart
        cdp_ai_agents row: model_name = jev
    end note
    note right of Trained
        one-row change: model_name = xgboost-…
    end note

4.5 Use-case comparison: LLM-only (today) vs Jev-assisted

Use caseToday / LLM-onlyWith JevExpected gain [unverified]Risk if Jev is wrong
Identity gray zoneNot wired; would need an LLM call with self-reported confidenceCalibrated p drives 3 lanesTrustable threshold; lower reviewer loadMis-triage → human still decides; merge reversible
Next-best-actionImpossible in request path (seconds)Sync rerank on top-kPersonalize on page loadFalls back to rules; no outage
Event routingRules onlyTyped label for ambiguous eventsFewer mis-routed eventsWrong route → reprocess; low blast radius
PII classificationManual / regexTyped label + pFaster source onboardingMissed PII → mitigated by steward review + regex floor
Cold-start scoringNo score until model trainedProvisional scoreScore on day 1Poor score → flagged provisional, not used for sends
Compliance pre-flightManualTyped checks + LLM narrativeFaster reviewHuman sign-off retained

5. Agent catalog (registry rows to add)

agent_codemodel_typemodel_nameInput (typed)Output (typed)ModePhase
identity_adjudicatorstructured_decisionjevpair feature vector (names, phones, emails, addr, DOB — redacted/tokenized){match: bool} + pbatch, human-gated1
nba_rerankerstructured_decisionjevprofile/persona features + top-k candidate ids{content_id, channel, send_window} + psync, ≤300 ms2
event_routerstructured_decisionjevevent envelope (type, source, props keys){route: enum} + pmicro-batch3
pii_classifierstructured_decisionjevcolumn name + sample stats (no raw values){pii_class: enum} + pbatch3
cold_start_scorerstructured_decisionjevprofile features{tier: enum} + pbatch4
compliance_checkerstructured_decisionjevtemplate + segment + consent flags{check→pass/fail} + pon-demand4

6. Data model additions

erDiagram
    cdp_ai_agents ||--o{ cdp_ai_decision_log : "agent_code"
    cdp_ai_decision_log {
        uuid decision_id PK
        uuid tenant_id
        varchar agent_code FK
        int instruction_version
        varchar engine "jev | llm | rules (fallback)"
        jsonb input_ref "ids/hashes only, no raw PII"
        jsonb output_value
        numeric confidence
        char lane "A | B | C"
        int latency_ms
        boolean degraded
        varchar mode "shadow | live"
        varchar outcome "confirmed | rejected | null"
        timestamptz created_at
        timestamptz outcome_at
    }
    cdp_profile_merge_history }o--|| cdp_ai_decision_log : "decision_id (optional)"

cdp_ai_decision_log is the single table behind every eval in §9. outcome is back-filled from reviewer action or downstream event. Apply the same tenant RLS policy pattern as customer360-database/legacy-upgrade.sql (historical step 001).


7. Guardrails (specific to Jev)

#GuardrailMechanism
1Tenant isolationtenant_id injected from auth server-side; ignored if present in input
2Jev proposes, code disposeslane computed by our gate; Jev never writes golden-record tables
3PII minimizationFeatures are tokenized/hashed or derived (e.g. similarity scores) before leaving; prefer self-hosted Jev for identity
4Prompt/input injectionRecord text only in typed fields, length-capped, enum-validated outputs; reject out-of-schema responses
5Timeout + circuit breakerPer-agent timeout_ms; N consecutive failures → open breaker → fallback
6Deterministic fallbackEvery agent defines a rules/LLM fallback; degraded=true is logged
7Shadow mode firstmode=shadow records Jev’s answer but acts on the fallback
8Kill switchFlip model_name back to LLM/rules in the registry row — no deploy
9Calibration driftNightly ECE job on labeled outcomes; alert when ECE exceeds budget; thresholds revert to conservative
10Vendor claims unverifiedNo SLA, cost, or accuracy figure in this doc is design input until measured

8. Rollout plan and progress tracking

8.1 Roadmap

gantt
    title Jev adoption roadmap (indicative; start after sign-off)
    dateFormat  YYYY-MM-DD
    axisFormat  %b %d
    section Phase 0 Foundation
    Schema + registry + decision_log     :p0a, 2026-10-12, 5d
    jev_client + /decide + contract tests :p0b, after p0a, 8d
    section Phase 1 POC (gate)
    Build labeled gray-zone set          :p1a, 2026-10-12, 7d
    Shadow run identity_adjudicator      :p1b, after p0b, 10d
    ECE / latency / cost / PR report     :p1c, after p1b, 4d
    GATE decision                        :milestone, after p1c, 0d
    section Phase 2 Real-time
    nba_reranker shadow + A/B            :p2, after p1c, 21d
    section Phase 3 High volume
    event_router + pii_classifier        :p3, after p2, 14d
    section Phase 4 Fallbacks
    cold_start_scorer + compliance       :p4, after p3, 14d

8.2 Phase table

PhaseGoalDeliverablesExit criteriaGated by
0 FoundationPlumbing, no behavior changeCHECK + table + seed rows, jev_client, /decide, fixturesContract tests green; /decide returns fallback when Jev is off—
1 POCProve or kill calibrationLabeled gray-zone set, shadow run, report§9 thresholds metPhase 0
2 Real-timeNBA in request pathnba_reranker, A/B harnessp95 ≤ 300 ms and engagement lift > 0 vs rulesPhase 1 pass
3 High volumeRouting + PIItwo agents livePrecision ≥ target; steward queue load acceptablePhase 1 pass
4 FallbacksCold-start + compliancetwo agents liveProvisional score flagged; human sign-off keptPhase 1 pass

8.3 Progress tracker (update in-place as work lands)

WorkstreamStatusOwnerNotes
Vendor API confirmed (typed call, auth, self-host option)⬜ not started—blocks Phase 0 jev_client
Schema: structured_decision + cdp_ai_decision_log⬜—
Seed rows for Jev agents⬜—
jev_client + breaker + fixtures⬜—
/decide/{agent_code} + gate + logger⬜—
Labeled gray-zone dataset from cdp_profile_merge_history⬜—check label quality / class balance
Shadow run + POC report⬜—
Go / no-go decision⬜—

Legend: ⬜ not started · 🟨 in progress · 🟩 done · 🟥 blocked.


9. Evaluation and the adoption gate

9.1 Metrics

QuestionMetricMethodPass threshold (proposed, tune with stakeholders)
Calibrated on our data?ECE + reliability diagramBin p vs observed accuracy on labeled gray-zone pairsECE ≤ 0.05 and monotone curve
Fast enough for request path?Latency p50 / p95, warm + coldEnd-to-end from customer360-agentp95 ≤ 300 ms (NBA budget)
Cheaper than the LLM path?Cost / 1k decisionsSame batch through LLM structured call≥ 5× cheaper, else benefit is calibration only
Good enough at the gate?Precision / recall at hi, loAgainst human labels, gray-zone slice onlyPrecision ≥ 0.95 at hi with useful coverage
Robust to our language?Same metrics on VN-name/address sliceSlice by script/diacriticsNo slice worse than overall by > 2× ECE

9.2 Go / no-go

flowchart TD
    S["Shadow run complete"] --> C{"ECE within budget?"}
    C -- no --> K1["KILL Jev layer\nkeep LLM; record why"]
    C -- yes --> L{"p95 latency + cost\nbeat LLM by needed margin?"}
    L -- "no, but calibration good" --> K2["Adopt for batch confidence-gating only\n(skip real-time NBA)"]
    L -- yes --> P{"Precision at hi OK on VN slice?"}
    P -- no --> K3["Adopt with higher hi / narrower scope"]
    P -- yes --> GO["GO: Phase 2+"]

9.3 Success scorecard per use case

Use casePrimary KPIBaselineTargetSource
Identity adjudicationReviewer minutes per 100 pairsmeasured in POCdown, with equal merge precisiondecision_log + merge_history
NBAClick/engagement liftrules-onlypositive, significanttracking events A/B
Event routingMis-route ratecurrentdowndecision_log outcome
PII classificationRecall of true PII columnsregex baseline≥ baseline + steward-validatedsteward confirmations
Cold-startDays to first usable scoremodel-train lead time→ ~0scoring job

10. Risks and alternatives

RiskLikelihoodImpactMitigation
Calibration does not hold on VN dataMediumHigh (kills premise)POC gate; keep LLM path
Vendor lock-in / pricing changeMediumMediumRegistry swap (model_name); fallback always defined; self-host preference
PII exposure to external vendorMediumHighTokenized features only; self-host for identity
Latency claims miss in our networkMediumMediumMeasure p95 in POC; NBA falls back to rules
Over-rotation of generation work onto JevLowMediumRoute-by-output-shape rule (§2.1) enforced in review
Label leakage / bias in gray-zone setMediumMediumHold-out by time; audit class balance

Alternatives considered

OptionProsConsVerdict
A. Jev behind /decide (this design)Calibrated gate, fast, reversible per-rowVendor claims unprovenChosen, gated
B. LLM structured output onlyZero new infraUncalibrated confidence; seconds of latency; per-token costFallback / baseline
C. Train own classifier for each decisionFull control, calibratableNeeds labels per task; not zero-shot; ML ops costUse where labels already exist (scoring)
D. Small open-source local model + calibration layer (e.g. temperature scaling)Self-hosted, no PII leavesWe own calibration + servingViable alternative if Jev fails the vendor/PII bar; same /decide contract
E. Do nothingNo riskReal-time NBA and trusted gating stay out of reachRejected

Because the contract is /decide/{agent_code} with a registry-selected engine, options B and D drop in without caller changes — which is the main reason the design is cheap to reverse.


11. Open questions

  1. Vendor API shape: is it a typed function call with schema upload, and does it return a probability or a score?
  2. Self-hosted Jev (or equivalent) availability and licensing for PII-heavy agents.
  3. Which existing registry column holds per-agent config (lo/hi, timeout_ms)? Confirm against database-schema.sql before adding any column.
  4. Size and class balance of labeled gray-zone pairs in cdp_profile_merge_history.
  5. Who owns the reviewer queue UI for lanes A/B (customer360-frontend)?

12. Conclusion

Jev is a narrow, optional engine: it earns a seat only for typed, confidence-gated, latency- or cost-sensitive decisions. The design keeps its blast radius small — a new structured_decision registry family, one /decide endpoint, one decision-log table — and makes the whole bet falsifiable by one POC (identity gray zone) before anything real-time depends on it. If it passes, LEO gains a fast calibrated decision layer at the marginal cost of a schema and a row per job; if it fails, the same contract hosts an LLM or self-calibrated local model with no caller changes.