# Reference

Everything the `thinkthen` command takes and prints. Read this page before you write a pipeline, and hand it to an agent. [How answers work](/reference/answers/) says when an answer is yes, no, not sure, or a number.

## The ten functions

| Function | What it does | Kind of answer | Requests |
| --- | --- | --- | --- |
| [`decide`](/functions/decide/) | Answer one yes or no question about the evidence. | Yes or no | One request for one piece of evidence. One request for each record in a stream. |
| [`choose`](/functions/choose/) | Pick one option from your list. | Pick one | One request for one piece of evidence. One request for each record in a stream. |
| [`tag`](/functions/tag/) | Name every label that fits. | Yes or no, per label | Every label rides in one request. One request for one piece of evidence, whatever the label count. |
| [`score`](/functions/score/) | Place the evidence on a scale you name. | Place on a scale | One request for one piece of evidence. One request for each record in a stream. |
| [`filter`](/functions/filter/) | Keep the records where the answer is yes. | Yes or no, per record | One request for each record. |
| [`rank`](/functions/rank/) | Sort records by how likely the answer is yes. | Yes or no, per record | One request for each record. --top trims the printed list and saves nothing. |
| [`find`](/functions/find/) | Pick the one line that best answers a question. | Pick one line of the evidence | It sends one request for the whole document. |
| [`annotate`](/functions/annotate/) | Fill out a form for every record. | Every kind of answer at once | It sends one request for each record and each part the questions read. |
| [`recognize`](/functions/recognize/) | Find every name in the evidence and say what kind it is. | Pick one, per word | It runs three steps. It finds the names. It labels each name with one of your kinds. This step works like choose. When you name a relation, it relates the names. --plan prints the first request it would send. |
| [`relate`](/functions/relate/) | Find relationships among named entities. | Yes or no per pair, or pick one | It reads one complete set of up to 255 entities and asks a yes/no question per allowed pair and rule. A single rule asks one choice per source instead, with none of these as an option. --plan prints the first request it would send. |

## Arguments and thresholds

A threshold is a cut on the quantity each function names. You set it; the tool never reports one. A cut is one number. Only `decide` takes a band, written `LOW:HIGH`. A recognized name’s printed strength is a computed product, not a probability.

| Function | Positional arguments | Threshold | Default |
| --- | --- | --- | --- |
| `decide` | none | A cut or a band. decide is the one function that takes both. | `0.5` |
| `choose` | 2 to 255 options | A cut only. A band is a usage error. | `none`; the winning label comes back |
| `tag` | 1 to 20 labels | A cut only, applied to each label on its own. A band is refused. | `0.5` |
| `score` | 2 to 10 levels, lowest first | None. score has no threshold. | `n/a` |
| `filter` | none | A cut only. A band is exit 2 from the command line and exit 5 from a question file. | `0.5` |
| `rank` | none | None. rank refuses a threshold on the command line and in a question file. | `n/a` |
| `find` | 2 to 255 lines or records, or 2 to 254 with --none | None. | `n/a` |
| `annotate` | a question set file | Each question carries its own. A top-level threshold applies to every decide question that names none. | per question |
| `recognize` | optional kinds | A cut on printed name strength; a separate relation cut applies to edges. | `0.5` |
| `relate` | relation rules | A cut on each edge’s probability of yes. | `0.5` |

With p as the probability of yes:

| Form | Accepted | Yes | No | Not sure |
| --- | --- | --- | --- | --- |
| none given |  | p ≥ 0.5 | p < 0.5 | never |
| `--threshold T` | 0 < T ≤ 1 | p ≥ T | p < T | never |
| `--threshold LOW:HIGH` | 0 ≤ LOW < HIGH ≤ 1 | p ≥ HIGH | p < LOW | LOW ≤ p < HIGH |

The high side is inclusive. A probability exactly at `LOW` is not sure. A cut of 0 is refused. A band low of 0 is accepted. `--threshold 0.5` and no threshold name the same rule. A percent such as `90`, a reversed band, an empty side, and a number that is not finite are all usage errors before any request.

The cut applies to the probability of yes on `decide` and each record under `filter`, the winning option’s probability on `choose`, each label’s independent yes probability under `tag`, each name’s printed strength under `recognize`, and each relation edge’s yes probability under `recognize` or `relate`. The [threshold contract](https://github.com/botassembly/thinkthen/blob/main/specification/threshold.md) defines every boundary.

## Global flags

Every function accepts all of these. The [settings page](/install/settings/) lists every setting on every surface, with its default.

| Flag | Takes | What it does | Default |
| --- | --- | --- | --- |
| `--plan` | none | Checks the whole input, prints the first request it would send and a summary line, and sends nothing. It needs no key. | `off` |
| `--url BASE` | an address | The server's base address. The tool posts to BASE/systemone. It outranks THINKTHEN_BASE_URL. | `https://api.typesafe.ai/v1` |
| `--model NAME` | a name that is not blank and holds no control character or white space but a plain space. Space around it is dropped | The model the request carries. This is how a run is pinned to one version. | `jev-1.13.0` |
| `--timeout SECONDS` | 1 to 86400 whole seconds | Covers one attempt from connect to last byte. Other values exit 2. | `30` |
| `--max-retries N` | a whole number of 0 or more | How many times a retried status is sent again. A transport failure is never sent again. | `3` |
| `--record DIR` | a folder | Calls the backend and writes each exchange into DIR. The folder is created when absent. | `off` |
| `--replay DIR` | a folder | Answers from DIR alone. No connection and no key. A missing entry is exit 5. | `off` |
| `--cache DIR` | a folder | Exactly --record DIR --replay DIR. Beside either of those it is a usage error. | `off` |
| `--no-cache` | none | Turns off the answer cache for one run, both lookup and writing. It never turns off usage counting. | on, in the default answer cache folder. SQL extensions: off unless THINKTHEN_CACHE or the SQL setting names a folder |
| `--profile FILE` | a readable profile file, or version-one JSON text in SQL | Applies local byte and question limits before a request leaves, and names the profile in use. It selects neither address nor model. | `none` |
| `--input FILE` | a readable file | Reads the evidence from a file instead of standard input. It names a path. It never names a framing. | standard input |

`--jobs N` sets the throttle. The throttle is the most requests in flight at once. The flag is not global. It runs from 1 to 32, defaults to 4, and it belongs to record mode. `annotate` also accepts it on one document, and `relate` on its one set of names. Every other function refuses it outside record mode.

TypeSafe documents a limit of 1,200 requests a minute. The [records specification](https://github.com/botassembly/thinkthen/blob/main/specification/records.md) holds the measured rates. `--jobs` caps the requests in flight. It sets no limit on requests a minute. The rate depends on how fast replies come back. On short records, the default of 4 can pass that limit. In a [review](https://github.com/botassembly/thinkthen/blob/main/sdlc/issues/closed/2026-09-26-architect-review-07-throughput-limits-cost.md), the default sent 1,519 requests a minute to TypeSafe. Against a local test server with 100 ms replies, `--jobs 3` sent 1,285 a minute. Set `requests_per_minute` on the backend, or `THINKTHEN_REQUESTS_PER_MINUTE`, to stay under the limit.

Function-only flags: `--threshold`, `--true TEXT` and `--false TEXT` (`decide`, `filter`, `rank`), `--quiet` (`decide` and `choose`, one document only), `--raw` (`choose` only, refused under CSV and TSV), `--option LABEL=DESCRIPTION`, `--label LABEL=DESCRIPTION`, `--options POINTER` (`choose`, and it needs `--jsonl`), `--top N` (`rank`, a whole number of 1 or more), `--none` (`find`).

## Framing a stream

The four framing flags are mutually exclusive. The tool never guesses a framing from a filename.

| Flag | One record is |
| --- | --- |
| `none` | The whole input is one text document and one record. |
| `--lines` | Each line is one text record. A trailing newline ends the last record. |
| `--jsonl` | Each line is one JSON value and one record. No blank lines. |
| `--csv` | The first logical row is a header. Each later row becomes one JSON object of string cells. |
| `--tsv` | The CSV rules, with a tab delimiter. |

| Function | What it accepts |
| --- | --- |
| `decide`, `choose`, `tag`, `score`, `annotate` | One document by default. All four framing flags are accepted. |
| `filter`, `rank` | Lines by default, or JSON Lines when a pointer is given. All four framing flags are accepted. One document is not a stream. |
| `find` | --lines by default, or --jsonl. CSV and TSV are refused as unexpected arguments. |

### `--field POINTER`

A JSON Pointer, RFC 6901. It names the part of each record the model sees. Only the pointed value leaves the machine.

- Under `--jsonl`, `--csv`, or `--tsv` it reads the pointer in each record.
- With no framing flag it reads the whole input as one JSON value and takes the pointer inside it. There is no separate JSON framing flag.
- On `filter` and `rank`, a pointer with no framing flag reads JSON Lines instead. A question file's `on` does the same.
- Under `--lines` it is a usage error. A text line has no members.
- It may repeat. Several pointers build an evidence object, each member keyed by the last part of its pointer. Two members that would share one key are a usage error.
- A pointer that finds nothing is an input error for that record, exit 2, before any request for it.
- `$.body`, `#/id`, a wildcard, and a negative index are refused, and the message names RFC 6901.
- A pointed value that is not a string is serialized as compact JSON and sent as text. With no `--field`, a JSONL, CSV, or TSV record is serialized whole.

### CSV and TSV

Both require a header. Every cell becomes a JSON string, including an empty cell and text that looks like a number, a boolean, a null, an array, or an object. Header order becomes key order. A header name may hold whitespace, but it may not be blank, hold a control character, or exactly duplicate another. One UTF-8 byte order mark is ignored at the start of the first header name and nowhere else. Every data row must carry the header's field count. A header with no data rows is a successful empty dataset and sends no request. Empty input is exit 2. Every output from CSV or TSV input is JSONL.

A record is capped at 16 MiB of encoded bytes. `find` reads at most 16 MiB of input in all. Invalid UTF-8 in a record is exit 5. An empty document is a usage error. An empty line or JSONL stream exits 0 with no output and no request.

## Limits

One whole request carries at most about 64,000 tokens, the questions included. The evidence in one request must fit in about 32,000 tokens. TypeSafe published both numbers on 2026-09-19 for its hosted service. The [records specification](https://github.com/botassembly/thinkthen/blob/main/specification/records.md) lists them. They belong to the backend and not to the tool.

Evidence past the token limit is refused and the command exits 4. Under `annotate` the questions and the evidence count together. A long question set can pass the limit on evidence that fits on its own. Fewer questions per file is the answer.

## What one row looks like

| Function | Standard output |
| --- | --- |
| `decide` | true, false, or null. |
| `choose` | A JSON string, or null. --raw prints the bare label. |
| `tag` | A JSON array of every label that reaches the bar, including []. |
| `score` | A JSON number. |
| `find` | The selected line or JSONL record as it arrived. Nothing when --none wins or ties. |
| `filter` | Each kept line or JSONL record as it arrived, in input order. A kept table row prints as compact JSON. |
| `rank` | Every line or record again, most likely yes first. With a saved score question, rank @FILE puts the highest score first. |
| `annotate` | One JSON object per record. |

In record mode, a `decide`, `choose`, `tag`, or `score` row is `{"input":RECORD,"value":ANSWER}`, in that key order. `filter`, `rank`, and `find` keep returning records. Every result is compact and sits on one line.

`annotate` in record mode adds one top-level field per question to an object record. A line, a JSON scalar, or a JSON array keeps its parsed record under `input` and its named answers under `value`. CSV and TSV rows are objects, and they stay flat.

## Exit codes

| Code | What it means |
| --- | --- |
| 0 | The command finished. On a single piece of evidence, decide answers yes. |
| 1 | A single piece of evidence under decide answers no. No other function exits 1. |
| 2 | A usage error or an input error. The failing record sent nothing. |
| 3 | A single piece of evidence under decide or choose is not sure. Under find --none, nothing fits. |
| 4 | The backend failed, or sent a reply the adapter refused. Also a key that is unset or blank at any address other than localhost, 127.0.0.1 or [::1], evidence past the token limit, and a critical line in a check report. |
| 5 | A local failure: a file or a saved answer. Also an unreadable question file, a band in a question file for choose, tag, or filter, and a threshold in a rank question file. |
| 6 | annotate or relate only. The run finished with at least one valid and one failed question. It prints no diagnostic. annotate marks each failed question in its result. relate prints only the edges that succeeded. |
| 7 | annotate --jsonl --details --batch 1 --on-error continue finished, and at least one record had a missing-pointer error row. When a run has both failed questions and such rows, 7 wins over 6. |
| 70 | A defect in the tool. Its message begins "defect:". |
| 130, 143 | SIGINT or SIGTERM stopped the command. It ends by that signal, and the shell reports 128 plus the signal number. |

Code 8 is reserved and unused. In record mode the exit code reports the run and never one record's answer. A completed run exits 0 unless `annotate` exits 6 or 7, or `relate` exits 6. A valid answer on standard output can sit beside exit 1, 3, 6, or 7.

## The environment

The [Configuration page](/install/configuration/#environment) lists every variable ThinkThen reads.

With no backend named, the key comes from `THINKTHEN_API_KEY`. `--backend`, `THINKTHEN_BACKEND` or the configuration file's `backend` names a backend. A named backend reads only its own key variables, and the first one that is not blank wins:

- `liquid` reads `LIQUIDAI_API_KEY`, then `LIQUID_API_KEY`.
- `ollama` reads `OLLAMA_API_KEY`.
- `typesafe` reads `TYPESAFE_API_KEY`.

The command line comes first, then the environment, then the configuration file. The first of these that names a backend or an address decides. So `THINKTHEN_BASE_URL` in the shell outranks the configuration file's `backend`, and the key then comes from `THINKTHEN_API_KEY`. With both `--backend` and `--url`, the backend's key variables apply at that address.

With the key unset or blank, a request to `localhost`, `127.0.0.1` or `[::1]` goes out with no key and no `Authorization` header. At any other address, the command exits 4. The [Backends page](/install/backends/) gives the rest of the key rules.

## The cache

[Recording and the answer cache](/reference/recording/) covers the answer cache, the cache key, recording and replay, and pruning. The [usage totals](/install/configuration/#usage-totals) and [`thinkthen status`](/install/configuration/#status) are on the Configuration page. `status --json` prints one `thinkthen.status/2` object. The [recording specification](https://github.com/botassembly/thinkthen/blob/main/specification/recording.md#the-options) lists its fields.

*The schema names version 2. No backend is named, so the key would come from THINKTHEN_API_KEY.*

```
thinkthen status --json |
jq '{schema, backend}'
```

*Output*

```
{
  "schema": "thinkthen.status/2",
  "backend": {
    "name": null,
    "url": "https://api.typesafe.ai/v1/systemone",
    "url_source": "built_in",
    "model": "jev-1.13.0",
    "model_source": "built_in",
    "key_variable": "THINKTHEN_API_KEY",
    "api_key_set": false
  }
}
```

*exit 0*

## The configuration file

The [Configuration page](/install/configuration/#configuration-file) gives its path, its fields, and the rules ThinkThen reads it by.

A list given on the command line replaces the file's whole list. The two never merge. The [settings page](/install/settings/) gives the order in which values win.

## Question files

A question file holds one saved question. Write `@FILE` in place of the question words. A question set for `annotate` holds many questions. The [question file page](/functions/question-file/) carries the keys and the exit codes.

[Take the tutorial](/learn/tutorial/) · [See every function](/functions/) · [Test it before you trust it](/trust/)

On GitHub: [github.com/botassembly/thinkthen](https://github.com/botassembly/thinkthen)
