> ## Documentation Index
> Fetch the complete documentation index at: https://docs.pool.day/llms.txt
> Use this file to discover all available pages before exploring further.

# Classify by a rule

> Write a rule in plain words and get the captures that belong to it, scored on the device first.

## One call

```swift theme={null}
let grant = try await pool.authorization.requestAccess(
    AccessRequest(data: [.products], operations: [.query], purpose: "Find the sneakers you saved")
)

let result = try await pool.context.classify(rule: "sneakers I might buy", using: grant)
for member in result.members {
    print(member.asset.locator, member.probability, member.decidedBy)
}
```

A rule is one sentence about things, places or moments, up to 500 characters: "receipts from restaurants", "concert tickets", "recipes with chicken". The answer is every capture the grant covers that is likely to belong, most likely first.

| Field | Meaning |
| - | - |
| `members` | Captures at or above `threshold`, each with a `probability` from 0 to 1 |
| `member.decidedBy` | `.onDevice` or `.cloud`: which stage settled this capture |
| `member.reasons` | Why it matched: `looksLike`, `text`, `label`, `entity` |
| `member.excerpt` | The matching text, only when the grant includes `.recognizedText` |
| `usedImageModel` | False until the engine's models are installed. Text and labels answer before that. |
| `isCalibrated` | True: on-device probabilities are fitted to the cloud judge. See [Probabilities](#probabilities). |
| `cloudDecided` | How many captures cloud assist settled in this call |
| `cloudRetryAfter` | Seconds to wait when cloud assist asked for a pause |
| `coverage`, `asOf` | How much of the library was read, and when, as on every result |

`threshold` defaults to 0.5 and `limit` to 100.

## How a capture is scored

**Stage one runs on the device, always.** PoolKit compares each image with the rule, using the same matcher that sorts captures into pools in the Pool app, and adds what it read: matching text, labels and named things. Nothing leaves the phone.

**Stage two is cloud assist, and it is optional.** It runs only when your configuration and the grant both allow [cloud assist](https://docs.pool.day/cloud-assist.md). PoolKit then sends the captures it is unsure about, those with a probability between 0.3 and 0.8, to Pool's judge.

* At most 48 captures per call, the most uncertain first.
* For each one: the text PoolKit read, its labels and the names of things on it, under a reference made up for the request. Never an image, never a photo identifier.
* A verdict replaces the on-device probability and the member is marked `.cloud`. When the judge cannot tell from the text, the on-device result stands.
* When the judge asks to wait, the on-device result stands and `cloudRetryAfter` says when to try again.

Four captures judged count as one unit. See the [Service API](https://docs.pool.day/service.md).

## Keep a rule live

```swift theme={null}
for try await update in pool.context.observeClassification(rule: "sneakers I might buy", using: grant) {
    render(update.members)
}
```

The first update is the current set. A new one arrives when indexing changes who belongs. A capture goes to the judge at most once per stream. This call needs the `observe` operation. To be told about each new match as an event, use [Signals](https://docs.pool.day/signals.md).

## Probabilities

On-device probabilities are fitted to the cloud judge's verdicts, so a capture at 0.7 is one the judge would call a member about 7 times in 10. The fit was made on a small test library (344 judged captures over 8 rules), so read values between 0.2 and 0.8 as approximate, and pick your threshold on your own content.

Words alone are weak evidence. Without the image model, a capture that holds every word of the rule scores about 0.35, under the default threshold of 0.5. If you classify before the models are installed, lower the threshold or allow cloud assist.

With cloud assist, captures between 0.3 and 0.8 go to the judge, and a `.cloud` member carries the judge's own probability.

## Rules PoolKit refuses

| Rule | Error |
| - | - |
| Empty, or longer than 500 characters | `PoolError.invalidRequest` |
| About health, religion, sexuality, ethnicity, politics, children, finances or credentials | `PoolError.unsupportedQuery` |
| Asks who someone is, or sorts people by how they look | `PoolError.unsupportedQuery` |

Rules about things pass: "race cars" and "face cream" are fine. Captures that Pool marks as sensitive are never scored, whatever the rule says.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.