# Consolidate claims

> A claim is a compact conclusion that names the observations behind it, created deterministically today with no model in the path.

Section: The memory loop · Source: https://titen.dev/docs/consolidate
Derived from: https://github.com/RamaAditya49/titen/blob/main/docs/architecture/memory-model.md

---
An observation says what happened. A claim says what someone concluded, and points at the
evidence. Keeping the two apart is what lets a compiled pack carry conclusions that stay
checkable: every claim resolves back to observations through
[`GET /v1/claims/:id/evidence`](/docs/claim-lifecycle).

## Create a claim

<figure class="code"><figcaption>consolidate.ts<b>typescript</b></figcaption>

```ts
const result = await titen.consolidate('user_rama', [
  {
    kind: 'procedural',
    statement: 'Deploy smoke must pass before release.',
    confidence: 0.95,
    sources: [{ observation_id: obs.observation_id, relation: 'supports' }],
  },
]);
// result.claims[0].claim_id → "claim_f3963d7b876143f5bfc8230db2757067"
// result.model_used         → false
```

</figure>

```sh
curl -X POST http://127.0.0.1:8787/v1/consolidations \
  -H "authorization: Bearer $TITEN_API_KEY" \
  -H 'content-type: application/json' \
  -d '{"subject_id":"user_rama","claims":[{
       "kind":"procedural","statement":"Deploy smoke must pass before release.",
       "confidence":0.95,
       "sources":[{"observation_id":"obs_ed91a2d0f66143fbba66c932494d5e64","relation":"supports"}]}]}'
```

One request carries up to 50 claims, each with up to 20 sources and a statement up to 4000
characters.

## Kinds

The kind is a typed field because pack diversity reads it: at most three items of one kind
enter a compiled pack.

<div class="table-wrap">

| Kind | What it holds |
| --- | --- |
| `semantic_fact` | A fact about the subject that is not tied to one moment. |
| `episodic_event` | Something that happened, as a conclusion rather than raw evidence. |
| `preference` | What the subject wants. |
| `procedural` | Guidance for how work should be done. |
| `decision` | A choice that was made, and therefore something a later decision can supersede. |
| `relationship` | A link between two subjects, backed by evidence. |

</div>

## Sources carry the relation

Every source names an observation and how it relates to the claim: `supports`,
`contradicts` or `qualifies`. At least one `supports` is mandatory.

```json
{ "error": { "code": "VALIDATION_ERROR",
    "message": "A claim needs at least one supporting source." },
  "meta": { "request_id": "req_e23da3bea5924b3f99cf84cf856e72ed" } }
```

That rule is what keeps the store from accumulating free-floating assertions.

An unknown source id, a foreign tenant's observation and someone else's `private` record
all return the same non-disclosing `404 NOT_FOUND`.

## Trust and visibility are inherited

Omit `trust` and the claim takes the highest trust among its **supporting** sources. Omit
`visibility` and it takes the narrowest visibility across **all** its sources, including
the ones that contradict or qualify. Supplying either field can only narrow the result:

```json
{ "error": { "code": "VALIDATION_ERROR",
    "message": "Claim trust may not exceed the trust of its supporting evidence." },
  "meta": { "request_id": "req_baae3681b26d44fe9ebd10f1b264bb3d" } }
```

Consolidation therefore cannot launder trust: evidence enters with a ceiling from its
credential, and the claim above it inherits that ceiling.

`confidence` is separate and defaults to `0.8`: how sure the author is, greater than 0 and
at most 1. It never substitutes for trust. Both appear in a compiled pack, and
[ranking](/docs/compile) uses them for different things.

## Contradiction produces a dispute, not a winner

Attach a `contradicts` source and the claim commits with `status: "disputed"`:

```json
{ "data": { "subject_id": "user_docs_loop", "project_id": null, "model_used": false,
    "claims": [{ "claim_id": "claim_d5ce4657d6b947aa8ade42271b4176f5",
      "kind": "procedural", "status": "disputed", "trust": "verified",
      "visibility": "team", "confidence": 0.9,
      "valid_from": "2026-07-30T10:47:30.946Z", "valid_to": null,
      "evidence_ids": ["obs_c98879c3adc44f6a9b2227b821a033bd",
                       "obs_bc4984ed9153467eb41b4753c5c96d6c"] }] },
  "meta": { "request_id": "req_…", "replayed": false, "model": "disabled" } }
```

The claim is still retrievable. It arrives in a context pack with `status: "disputed"`, a
`conflict` score component of 1 and an entry in the pack's `conflicts` array. The
disagreement is the part an agent needs to ask a better question. Resolving it is an
explicit [lifecycle transition](/docs/claim-lifecycle) that records who decided and why.

## No model is involved

`model_used: false` in `data` and `model: "disabled"` in `meta` are literal. Consolidation
today is deterministic, and no model call happens on this route.

<div class="callout">
<strong>Planned: model-driven extraction</strong>

Proposing claims from eligible observations with a model is on the roadmap. It changes what
Titen *proposes*, not what it *accepts*. Every proposed claim would still pass the same
schema, scope, evidence, trust and temporal validation as a hand-written one, and a model
could not invent a source identifier, raise trust above its evidence, delete evidence, or
resolve a dispute. Deterministic rules run first, and a model returning invalid sources
commits nothing.
</div>

## Next

<div class="cards">

<a href="/docs/compile"><strong>Compile context</strong><span>Scope, rank and pack claims under a token budget.</span></a>
<a href="/docs/claim-lifecycle"><strong>Claim lifecycle</strong><span>Supersede, revoke, expire, and why the evidence survives.</span></a>
<a href="/docs/data-model"><strong>Data model</strong><span>The claim and claim-source tables behind this route.</span></a>

</div>