---
title: Evidence and classification
description: How agents send evidence as exact excerpts, how Felspar reads numbers, dates and text, and what "needs evidence" means.
sidebar:
  order: 2
---

Agents never tell Felspar the answer. They send what their sources say, word for word, and Felspar decides what that means under your policy.

## Excerpts

An **excerpt** is the shortest passage from a source that answers a question, with where it came from:

```json
{
  "text": "Give your users wallets with three lines of code.",
  "source": {
    "class": "company-site",
    "id": "https://tidewell.example",
    "locator": "https://tidewell.example/#hero",
    "observedAt": "2026-10-21T09:00:00Z"
  }
}
```

| Field | Meaning |
| --- | --- |
| `text` | The source's exact words. At most 2,000 characters. Never a label or a summary. |
| `source.class` | The kind of source, one the question accepts (see below). |
| `source.id` | Which source: a URL, a record ID, a ticket number. |
| `source.locator` | Where in the source: a URL with a section anchor, a field name. Optional, but give each distinct passage its own locator. |
| `source.observedAt` | When the evidence was true, in ISO 8601. For a web page, when the agent read it. For a record, its own last-updated time. Never a date printed on the page, such as a copyright year. |
| `source.revision`, `source.sensitivity` | Optional. A revision of the source, and a sensitivity label. |

A question can take one excerpt, or a list of up to three from different places. The same words can answer several questions; send them under each one.

## Source classes

A pack lists the kinds of evidence it accepts, such as `company-site`, `crm-record` or `carrier-tracking`. Each question accepts some of them. An excerpt whose class the question doesn't accept is refused with `source_class_not_accepted`. The job's brief lists the accepted classes for each question.

## Freshness

A pack sets how old evidence may be, such as 90 days. Age is counted from each excerpt's `observedAt` to the time of the decision. Evidence older than that doesn't count, and the result says the case is `stale`. The result's `explanation.evidenceFreshUntil` says when the evidence behind a decision stops being fresh. The [pre-action gate](/guides/pre-action-gate) refuses to act on a decision past that time.

## How Felspar reads each question

| Question type | How it is read | Model? |
| --- | --- | --- |
| Number | Read exactly from the text. "1,200 employees" is 1200. | No |
| Date | Read exactly from the text, as a calendar date. | No |
| Answer choice | Classified against the pack's definitions for each answer. | Yes, bounded |
| Yes/no | Classified the same way, when the policy defines yes and no. | Yes, bounded |

A number or date question may also take a typed value, `{ "value": 1200, "source": { ... } }`, when the agent already has it from a structured record. Answer-choice and yes/no questions always need text: a typed label such as `"payments_api"` is refused with `label_needs_mapping`.

When a number or date can't be read exactly, the question stays open with a reason: `value_not_found`, `value_ambiguous` (more than one candidate), `unit_mismatch` or `not_integer`.

## Classification

For answer-choice and yes/no questions, Felspar's managed classifier reads one excerpt and the question's released definition: the meaning, each answer with its inclusions, exclusions and examples, and always a "cannot determine" option. It returns how likely each answer is.

Felspar accepts the leading answer only if its probability is at or above the pack's **threshold**. Otherwise the question stays open. The reasons you may see on an excerpt are:

| Reason | Meaning |
| --- | --- |
| `probability_below_threshold` | The best answer wasn't likely enough. Usually the text is vague, or the definitions need clearer wording. |
| `probability_tie` | Two answers were equally likely. |
| `normalization_abstained` | The classifier chose "cannot determine": the text doesn't say. |
| `classification_unsupported` | The pack has no classification material for this question. |
| `classification_failed` | Classification didn't finish in time. Retrying may help. |
| `classification_disabled` | Classification is temporarily switched off. The question stays open; try again later. |
| `classification_ceiling_reached` | The workspace or caller reached its daily classification ceiling. |

Every result shows how each excerpt was read, under `result.interpretation`: the method, the value, the leading answer, its probability, the threshold, close alternatives, and whether the answer came from cache.

### What keeps classification bounded

- It only chooses among answers your policy defines. It can't invent an outcome.
- It only runs for questions the decision needs, given the answers known so far.
- Each excerpt is answered at most once per job. Identical requests are answered from a cache for 30 days. The cache is keyed by a fingerprint of the request, not by the text.
- A job's classification has a 10-second deadline. Anything unanswered by then is `classification_failed`.
- Each job, each workspace and each caller has a ceiling on classification work per day.
- Replay never calls a model. Verifying a receipt rebuilds the decision from the recorded classifications.

### Writing definitions that classify well

Classification quality depends mostly on how the policy words each answer. In short: one judgment per question; describe each answer by what the text says, never by an absence; give every common kind of text its own answer; include a "none of these" answer; prefer several positively worded answers over yes/no; and test both sides. See [Draft a pack](/guides/draft-a-pack#writing-answers-felspar-can-classify).

## Gaps, conflicts and "needs evidence"

When Felspar can't decide, it says why instead of guessing. The decision's status is one of:

| Status | Meaning |
| --- | --- |
| `ready` | A rule matched. There is an outcome. |
| `gap` | A question the decision needs has no accepted answer. |
| `stale` | The only evidence for a question is too old. |
| `conflict` | Evidence for a question disagrees. |
| `review_required` | The pack requires a person to review. |
| `blocked` | The decision can't proceed. |

`result.explanation.needs` lists each open question with its accepted source classes, maximum age, and how each excerpt was read. `result.retry` turns that into instructions for the agent. See [Retries and attempts](/guides/retries-and-attempts).

## What Felspar keeps

Felspar keeps the excerpts, every classification and the result with the case, for 30 days, so a receipt can be verified and backtests can re-decide real cases. See [Security, privacy and data](/security#what-felspar-stores).
