Skip to content

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.

Add --check to decide, and the answer becomes the exit code. Yes is 0:

Terminal window
printf '%s' 'Please refund my order.' |
thinkthen decide --check 'Does the customer request a refund?'

Output

true

No is 1:

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

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:

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

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:

broken.jsonl
{"id":"a1","text":"Please refund my order."}
not json
{"id":"a3"}
Terminal window
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"
fi
jq '{position, status, error: .error.code}' outcomes.jsonl

Output

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.

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.

The quickstart installs ThinkThen and asks your first question, and Exit codes has the full table.