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 says when an answer is yes, no, not sure, or a number.
The ten functions
| Function | What it does | Kind of answer | Requests |
|---|---|---|---|
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 | Pick one option from your list. | Pick one | One request for one piece of evidence. One request for each record in a stream. |
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 | 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 | Keep the records where the answer is yes. | Yes or no, per record | One request for each record. |
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 | Pick the one line that best answers a question. | Pick one line of the evidence | It sends one request for the whole document. |
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 | 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 | 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 defines every boundary.
Global flags
Every function accepts all of these. The settings page 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 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, 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--tsvit 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
filterandrank, a pointer with no framing flag reads JSON Lines instead. A question file'sondoes the same. - Under
--linesit 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 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 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:
liquidreadsLIQUIDAI_API_KEY, thenLIQUID_API_KEY.ollamareadsOLLAMA_API_KEY.typesafereadsTYPESAFE_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 gives the rest of the key rules.
The cache
Recording and the answer cache covers the answer cache, the cache key, recording and replay, and pruning. The usage totals and thinkthen status are on the Configuration page. status --json prints one thinkthen.status/2 object. The recording specification lists its fields.
thinkthen status --json |
jq '{schema, backend}'{
"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
}
}The configuration file
The Configuration page 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 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 carries the keys and the exit codes.
Take the tutorial · See every function · Test it before you trust it
On GitHub: github.com/botassembly/thinkthen