Let your shell branch on meaning
Shell scripts branch on exit codes, and ThinkThen 0.2.0 lets them branch on meaning. grep -q exits 0 when it finds a match and 1 when it doesn't, and decades of scripts are built on that, but grep matches characters and can't tell you whether a message asks for a refund.
ThinkThen gives the shell that missing test.
An if that reads meaning
Section titled “An if that reads meaning”Add --check to decide, and the answer becomes the exit code. Yes is 0:
printf '%s' 'Please refund my order.' | thinkthen decide --check 'Does the customer request a refund?'Output
true
No is 1:
printf '%s' 'Where is my parcel?' | thinkthen decide --check 'Does the customer request a refund?'Output
false
That's enough to write if printf '%s' "$message" | thinkthen decide --check -q '...'; then. With -q the command prints nothing and only the exit code speaks. Same shape as grep -q, a different kind of match.
Not sure is not no
Section titled “Not sure is not no”Here's where I part ways with grep. A model has a probability, not a match. At 0.51 you'd rather not act as if it said yes.
So ThinkThen has a third answer. Give it a low bar and a high bar with --low and --high, and an answer between them comes back as not sure, with its own exit code, 3:
printf '%s' 'I was charged twice for one invoice.' | thinkthen decide --check --low 0.2 --high 0.8 \ 'Does the customer request a refund?'Output
unsure
Now the script has three branches: act, skip, or ask a person. In my experience the third branch is what makes automation safe to turn on. You start with the bars far apart and a busy person, and you move them closer as you check the model against your own labels.
A failure is not an answer
Section titled “A failure is not an answer”The worst thing a classifier can do is fail quietly and look like a "no". A timeout reads as false, the script skips the refund, and nobody finds out.
ThinkThen keeps those apart. A line that can't be judged comes back marked failed, with its error code. The other lines still get answered, and the command exits 6 after it has written every answer. In broken.jsonl the second line is not JSON, and the third has no text:
{"id":"a1","text":"Please refund my order."}not json{"id":"a3"}if thinkthen decide --from jsonl --field /text --json \ 'Does the customer request a refund?' broken.jsonl \ > outcomes.jsonl; then echo "all records answered"else status=$? echo "stopped: thinkthen exited $status"fijq '{position, status, error: .error.code}' outcomes.jsonlOutput
stopped: thinkthen exited 6
{
"position": 1,
"status": "answered",
"error": null
}
{
"position": 2,
"status": "failed",
"error": "record.json"
}
{
"position": 3,
"status": "failed",
"error": "record.missing"
}
thinkthen: record.json: position 2 id none file broken.jsonl: the record is not JSON
thinkthen: record.missing: position 3 id a3 file broken.jsonl: the record holds nothing at /text
A failed line wins over every answer, so --check never turns a failure into a 0 or a 1. A wrong command line exits 2 before anything is judged. Each code means one thing, and Exit codes lists them all.
Why this matters for agents
Section titled “Why this matters for agents”An agent calling a tool is a shell script with opinions. It needs the same three things a script needs: a typed answer, a way to say "I'm not sure", and a failure that can't pass for an answer. ThinkThen gives an agent all three in one call, without asking it to parse prose.
Try it
Section titled “Try it”The quickstart installs ThinkThen and asks your first question, and Exit codes has the full table.