MemMesh — persistent, self-improving memory for AI agents. Get started →
API ReferenceOutcomes & Calibration

Outcomes & Calibration API

This is the closed-loop learning surface — the decision → action → outcome causal chain plus the online calibration it drives. It is what keeps prediction confidence honest: recording an outcome re-weights the confidence of every pattern/prediction the decision was informed by (a Beta-Binomial posterior).

Decisions and outcomes are stored as decision / outcome memories, so retrieval, embeddings, and the GDPR subject cascade cover them for free. The surface is domain-agnostic: subject / decision / action / outcome / reward only, never a consumer-domain noun.

All routes are under /api/v1/projects/{projectId}/lattice.

Record a decision

POST /lattice/decisions

Log a choice an actor made, with provenance links to the patterns/predictions that informed it (credit assignment).

{
  "subject": { "kind": "customer", "externalId": "acct-42" },
  "actor": "policy:winback-v1",
  "decisionType": "outreach",
  "actionType": "send_message",
  "informedBy": [
    { "memoryId": "mem_pattern_…", "refType": "pattern", "weight": 1.0 }
  ],
  "status": "executed",
  "idempotencyKey": "winback-acct-42-2026-07-13"
}

refType is pattern | prediction | observation | collective. Re-sending the same idempotencyKey returns the existing record rather than creating a duplicate. The full DecisionRecord (with decisionId) comes back.

Record an outcome

POST /lattice/outcomes

Log the realized result. On write, it folds into the calibrated confidence of every ref the decision was informedBy.

{
  "decisionId": "dec_…",
  "subject": { "kind": "customer", "externalId": "acct-42" },
  "outcomeType": "conversion",
  "result": "success",
  "reward": 49.0,
  "attributionWindowSecs": 604800,
  "idempotencyKey": "outcome-dec-…"
}

result is success | failure | partial. The response surfaces exactly which refs moved:

{
  "outcomeId": "out_…",
  "updates": [
    {
      "refId": "mem_pattern_…",
      "refType": "pattern",
      "priorConfidence": 0.70,
      "posteriorConfidence": 0.74,
      "hits": 8,
      "misses": 3
    }
  ]
}

List outcomes

GET /lattice/outcomes?subjectKind=…&subjectExternalId=…&decisionType=…&actionType=…&limit=…

Lists recorded outcomes for a subject (omit the subject params for scope-wide), newest first. Each row is denormalized with the linked decision’s decisionType and actionType.

What worked

GET /lattice/effectiveness?groupBy=…&minSupport=…

Roll up “what worked” per group. groupBy is action_type | decision_type | policy | pattern_kind.

{
  "rows": [
    {
      "groupKey": "send_message",
      "n": 120,
      "successRate": 0.41,
      "avgReward": 18.7,
      "confidence": 0.40
    }
  ]
}

confidence is the Beta-Binomial posterior mean of the success rate; minSupport filters out thin groups.

Calibration report

GET /lattice/calibration?bucketCount=…

Read-only reliability report: active patterns bucketed by stated confidence, with the realized hit-rate of their past predictions per bucket. Answers “of the patterns we rate ~0.8, do ~80% of their predictions actually fire?”

{
  "buckets": [
    {
      "lower": 0.6, "upper": 0.8,
      "patterns": 14,
      "predictions": 52, "hits": 33, "misses": 19,
      "realizedHitRate": 0.63,
      "hasData": true
    }
  ],
  "totalPatterns": 41,
  "totalPredictions": 190
}

bucketCount defaults to 5, clamped to [1, 20]. hasData is false for a bucket where no prediction has been scored yet — don’t read realizedHitRate when it’s false.

Calibration is the read side; the decisions → outcomes loop above is the write side that feeds it. Together they are the “with confidence, honestly” half of the prediction trust layer.