# Close the loop

> An outcome recorded against a context run changes what future compiles rank first, without editing a single observation.

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

---
The agent acts outside Titen. What comes back is one small fact about the pack it was given:
did any of it help? That signal is what separates a memory store that accumulates from one
that gets better at choosing.

## Record an outcome

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

```ts
await titen.feedback(ctx.context_id, {
  outcome: 'useful',
  claim_id: ctx.items[0]?.claim_id,
});
```

</figure>

```sh
curl -X POST http://127.0.0.1:8787/v1/context/ctx_5aa8109e55ed41b49cb75f48868cd289/feedback \
  -H "authorization: Bearer $TITEN_API_KEY" \
  -H 'content-type: application/json' \
  -d '{"outcome":"useful","claim_id":"claim_d5ce4657d6b947aa8ade42271b4176f5"}'
```

```json
{ "data": { "feedback_id": "fb_759a0268e3a84d3b86d07b6590156714",
    "context_id": "ctx_5aa8109e55ed41b49cb75f48868cd289",
    "claim_id": "claim_d5ce4657d6b947aa8ade42271b4176f5",
    "outcome": "useful", "recorded_at": "2026-07-30T10:47:47.575Z" },
  "meta": { "request_id": "req_…", "replayed": false } }
```

## Five outcomes

<div class="table-wrap">

| Outcome | Effect on utility | When to send it |
| --- | --- | --- |
| `used` | positive | The item made it into the prompt and the action succeeded. |
| `useful` | positive | The item changed what the agent did, for the better. |
| `irrelevant` | negative | Eligible and correct, but not about this task. |
| `incorrect` | negative | The claim is wrong. |
| `harmful` | negative | Acting on it caused damage. |

</div>

Anything else is refused rather than coerced:

```json
{ "error": { "code": "VALIDATION_ERROR",
    "message": "Field \"outcome\" must be one of: used, useful, irrelevant, incorrect, harmful." },
  "meta": { "request_id": "req_e5d3a330e24343dfaf34c45d35955bbc" } }
```

An optional `reason_code`, up to 64 characters, rides along for your own evaluation queries.
`incorrect` and `harmful` are signals about a *selection*, not lifecycle commands: retiring a
wrong claim is a separate, authorized decision. See [claim lifecycle](/docs/claim-lifecycle).

## claim_id decides what moves

<div class="callout">
<strong>Run-level feedback moves nothing</strong>

Omit `claim_id` and the outcome is recorded against the whole run. Utility is counted per
claim, so only feedback that names a `claim_id` affects ranking. Name the claim to close the
loop; omit it when you are grading the pack as a whole.
</div>

A `claim_id` must be one the run actually returned. A claim that was never in the pack is a
non-disclosing `404`, so feedback cannot be used to probe which claims exist:

```json
{ "error": { "code": "NOT_FOUND", "message": "Resource was not found." },
  "meta": { "request_id": "req_a4adc0eb7c74469ebe1af88bd8c00b08" } }
```

Retries are idempotent two ways: an `Idempotency-Key` header, or a `client_mutation_id` in
the body. Either one returns the original `feedback_id` with `meta.replayed: true`.

## How utility feeds back into ranking

Positive and negative counts are read at compile time from the feedback rows themselves.
There is no cached utility score to drift out of date. The `utility` component works out to:

<div class="rules">

<p>Fewer than three signals for the claim: <code>0.5</code>, the neutral value.</p>
<p>Three or more: <code>(net + 1) / 2</code>, where <code>net</code> is <code>(positive − negative) / total</code>. All-positive gives 1, all-negative gives 0.</p>
<p>The component is weighted <code>0.10</code> in the final score, so it reorders neighbours and cannot outvote relevance or trust.</p>

</div>

A ranking signal that moved on a single vote is a poisoning surface: anything that can call
this route once could promote its own memory. Requiring three makes that attack cost more,
and slows promotion for everyone equally.

Live, on the same claim, before and after three `useful` outcomes:

<div class="table-wrap">

| | relevance | trust | recency | utility | conflict | score |
| --- | --- | --- | --- | --- | --- | --- |
| before | 1 | 0.666667 | 1 | **0.5** | 1 | **0.795** |
| after | 1 | 0.666667 | 1 | **1** | 1 | **0.84** |

</div>

Its statement, trust, confidence, evidence links and status are byte-identical; only the
score moved.

## What feedback may never do

<div class="rules">

<p>Rewrite observation content. Evidence is append-only, and an outcome is not evidence.</p>
<p>Raise trust. Trust comes from a credential's authority over its evidence, never from a popularity count.</p>
<p>Turn repeated opinion into fact. A claim voted useful a hundred times is still a claim, with the same <code>trust</code> and the same sources.</p>
<p>Cross a tenant, subject or visibility boundary, or delete canonical evidence on its own.</p>

</div>

Negative feedback is kept for evaluation even after its claim is revoked, so the system can
learn that a selection was bad without losing the record of what it selected.

## Next

<div class="cards">

<a href="/docs/claim-lifecycle"><strong>Claim lifecycle</strong><span>The authorized way to retire a claim that feedback says is wrong.</span></a>
<a href="/docs/compile"><strong>Compile context</strong><span>Where the utility component lands in the score.</span></a>
<a href="/docs/errors"><strong>Errors</strong><span>Every code this route can return.</span></a>

</div>