SemDecide

September 16, 2026 · View on GitHub

Typed semantic decisions for Unix pipelines and CI, powered by TypeSafe AI Jev.

SemDecide is grep for meaning and jq for judgment. Pipe in text or JSONL and get a predicate, route, score, filtered stream, and stable process exit code without writing prompt, parsing, retry, or confidence-handling glue.

printf '%s' 'Login from a new country, followed by payout changes.' \
  | semdecide is 'This describes a plausible account takeover'
TRUE probability=0.860 threshold=0.700

Why

Exact string rules cannot express concepts such as:

  • “This customer is likely to churn.”
  • “This change introduces a breaking API behavior.”
  • “This record may describe account takeover.”
  • “This response is grounded and complete.”

General-purpose LLM calls can make those judgments, but they are awkward to place in shell scripts. They generate text that must be parsed, are comparatively slow and expensive, and often hide uncertainty.

Jev returns typed decisions instead. SemDecide gives those decisions predictable Unix input, output, limits, retries, schemas, and exit behavior.

Install

SemDecide requires Python 3.10 or newer.

pipx install https://github.com/sharziki/semdecide/releases/download/v0.2.1/semdecide-0.2.1-py3-none-any.whl
# or, with uv
uv tool install https://github.com/sharziki/semdecide/releases/download/v0.2.1/semdecide-0.2.1-py3-none-any.whl

SemDecide is not yet published on PyPI. The commands above install the signed-off GitHub release artifact directly.

From a source checkout:

python3 -m venv .venv
.venv/bin/pip install -e .

Set a TypeSafe AI API key:

export TYPESAFE_API_KEY='...'

SemDecide also reads ~/.config/typesafe/credentials.env:

TYPESAFE_API_KEY='...'

Keep that file mode 600. Do not pass credentials as command-line arguments.

Commands

is: semantic predicates

cat incident.txt | semdecide is \
  'This describes a security-sensitive incident requiring human attention'

Use --json for a stable machine interface:

cat incident.txt | semdecide is 'This is an account-takeover signal' --json
{
  "schema_version": "1",
  "command": "is",
  "verdict": "true",
  "probability": 0.86,
  "confidence": null,
  "threshold": 0.7,
  "model": "jev-1.13.0",
  "usage": {
    "input_tokens": 120,
    "output_tokens": 8
  }
}

Configure local policy with --threshold and --uncertainty-margin. SemDecide does not silently force borderline results into true or false.

choose: route among named options

cat ticket.txt | semdecide choose \
  'Which queue should receive this ticket?' \
  --option support='ordinary product support' \
  --option security='possible security incident' \
  --option billing='billing or payment issue' \
  --json

The result preserves the selected option, every option probability, and Jev confidence. --min-confidence controls whether the process exits as uncertain.

score: ordered semantic rubrics

cat answer.txt | semdecide score \
  --criterion 'How trustworthy is this answer?' \
  --level 'unsafe or misleading' \
  --level 'mostly correct but incomplete' \
  --level 'correct, grounded, and complete' \
  --json

filter: semantic JSONL filtering

cat tickets.jsonl | semdecide filter \
  'The record indicates urgent churn risk or active customer impact' \
  --field text

Input order is preserved. Matching records receive _semdecide metadata:

{"id":42,"text":"We will cancel unless today's outage is fixed.","_semdecide":{"schema_version":"1","probability":0.9,"threshold":0.7,"model":"jev-1.13.0"}}

Use --raw to emit the original records without metadata.

guard: an opinionated agent-safety recipe

The original REFLEX action firewall now ships as one recipe built on SemDecide's primitives:

semdecide guard \
  --action 'Delete the production customer database' \
  --context 'No exact approval or backup exists' \
  --json

Jev evaluates narrow signals such as authorization, destructiveness, ambiguity, secret exposure, and consequence. Deterministic local code returns allow, escalate, or block.

semdecide check remains a deprecated compatibility alias for one minor release. A temporary reflex executable alias also remains for users of the prototype.

Input and output

Text commands accept exactly one of:

  • stdin
  • --text '...'
  • --file path

SemDecide rejects empty, binary, invalid UTF-8, oversized, and malformed structured input before calling Jev.

Useful controls:

--max-input-bytes   cap submitted input, default 1,000,000
--timeout           per-attempt provider timeout, default 10 seconds
--retries           transient retry count, default 2 and maximum 5
--quiet             emit no output and use only the exit code
--json              stable JSON output for is, choose, and score

filter accepts JSONL objects and additionally supports --max-records, --field, and --raw.

With --json, parsing and runtime failures are emitted as a versioned JSON error object on stderr. Provider details are intentionally redacted. --quiet suppresses both normal output and diagnostics, leaving only the exit code.

Exit codes

Semantic commands follow grep-like behavior while keeping uncertainty and provider failure distinct:

CodeMeaning
0true, selected, scored, or at least one definite filter match
1false or no filter matches
2invalid local input or command usage
3uncertain result under the configured margin or confidence floor
4provider, authentication, timeout, or invalid-response failure

The guard recipe retains its compatibility contract:

CodeMeaning
0allow
10escalate
20block
2invalid local input

Provider failures in guard fail closed to escalate.

Safety and privacy

Input evaluated by SemDecide is sent to TypeSafe AI. Do not submit material your data policy forbids sending to that provider.

SemDecide is not an authorization system, sandbox, security proof, or tool executor. Semantic decisions can be wrong. Keep deterministic permission checks around money, credentials, production infrastructure, private data, and irreversible operations.

SemDecide is an independent open-source project and is not an official TypeSafe AI product.

Development

python3 -m venv .venv
.venv/bin/pip install -e .
.venv/bin/python -m unittest discover -s tests -v
.venv/bin/python -m compileall -q src tests

The runtime is dependency-free. Live-provider tests are acceptance checks, not part of the deterministic default suite.

See:

License

MIT