ThinkThen

Reference

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

FunctionWhat it doesKind of answerRequests
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.

FunctionPositional argumentsThresholdDefault
decidenoneA cut or a band. decide is the one function that takes both.0.5
choose2 to 255 optionsA cut only. A band is a usage error.none; the winning label comes back
tag1 to 20 labelsA cut only, applied to each label on its own. A band is refused.0.5
score2 to 10 levels, lowest firstNone. score has no threshold.n/a
filternoneA cut only. A band is exit 2 from the command line and exit 5 from a question file.0.5
ranknoneNone. rank refuses a threshold on the command line and in a question file.n/a
find2 to 255 lines or records, or 2 to 254 with --noneNone.n/a
annotatea question set fileEach question carries its own. A top-level threshold applies to every decide question that names none.per question
recognizeoptional kindsA cut on printed name strength; a separate relation cut applies to edges.0.5
relaterelation rulesA cut on each edge’s probability of yes.0.5

With p as the probability of yes:

FormAcceptedYesNoNot sure
none givenp ≥ 0.5p < 0.5never
--threshold T0 < T ≤ 1p ≥ Tp < Tnever
--threshold LOW:HIGH0 ≤ LOW < HIGH ≤ 1p ≥ HIGHp < LOWLOW ≤ 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.

FlagTakesWhat it doesDefault
--plannoneChecks the whole input, prints the first request it would send and a summary line, and sends nothing. It needs no key.off
--url BASEan addressThe server's base address. The tool posts to BASE/systemone. It outranks THINKTHEN_BASE_URL.https://api.typesafe.ai/v1
--model NAMEa name that is not blank and holds no control character or white space but a plain space. Space around it is droppedThe model the request carries. This is how a run is pinned to one version.jev-1.13.0
--timeout SECONDS1 to 86400 whole secondsCovers one attempt from connect to last byte. Other values exit 2.30
--max-retries Na whole number of 0 or moreHow many times a retried status is sent again. A transport failure is never sent again.3
--record DIRa folderCalls the backend and writes each exchange into DIR. The folder is created when absent.off
--replay DIRa folderAnswers from DIR alone. No connection and no key. A missing entry is exit 5.off
--cache DIRa folderExactly --record DIR --replay DIR. Beside either of those it is a usage error.off
--no-cachenoneTurns 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 FILEa readable profile file, or version-one JSON text in SQLApplies local byte and question limits before a request leaves, and names the profile in use. It selects neither address nor model.none
--input FILEa readable fileReads 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.

FlagOne record is
noneThe whole input is one text document and one record.
--linesEach line is one text record. A trailing newline ends the last record.
--jsonlEach line is one JSON value and one record. No blank lines.
--csvThe first logical row is a header. Each later row becomes one JSON object of string cells.
--tsvThe CSV rules, with a tab delimiter.
FunctionWhat it accepts
decide, choose, tag, score, annotateOne document by default. All four framing flags are accepted.
filter, rankLines 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 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

FunctionStandard output
decidetrue, false, or null.
chooseA JSON string, or null. --raw prints the bare label.
tagA JSON array of every label that reaches the bar, including [].
scoreA JSON number.
findThe selected line or JSONL record as it arrived. Nothing when --none wins or ties.
filterEach kept line or JSONL record as it arrived, in input order. A kept table row prints as compact JSON.
rankEvery line or record again, most likely yes first. With a saved score question, rank @FILE puts the highest score first.
annotateOne 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

CodeWhat it means
0The command finished. On a single piece of evidence, decide answers yes.
1A single piece of evidence under decide answers no. No other function exits 1.
2A usage error or an input error. The failing record sent nothing.
3A single piece of evidence under decide or choose is not sure. Under find --none, nothing fits.
4The 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.
5A 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.
6annotate 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.
7annotate --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.
70A defect in the tool. Its message begins "defect:".
130, 143SIGINT 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:

  • 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 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.

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 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.