DocsRun it11 min read

Command line#

Everything shad0w does from Python it also does from a terminal: start a project, import old answers, train, try, watch and serve. This page lists every command and every flag.

TL;DR

pip install "shad0wllm[compile]"
shad0w init                                     # shad0w.toml + a runnable app.py
python app.py "my card was stolen"              # your LLM answers; the answer is logged
shad0w status                                   # rows logged, certified share, next step
shad0w train --dir shad0w --question intent     # once enough answers are logged
shad0w try --bundle shad0w/intent/bundle "my card was stolen"

shad0w --help lists the commands; shad0w <command> --help lists a command's flags; shad0w --version prints the version.

Words used on this page. The table (or bundle, the folder that holds it): the small model shad0w trains from your LLM's answers. Certified: tested on answers it never trained on and allowed to serve only where it differs from your LLM on at most α. Log: the JSON Lines file of your LLM's answers that training reads.

Commands at a glance#

Command What it does
init Write a commented shad0w.toml and a runnable app.py
status One line per decision: rows logged, certified share, live agreement, next step
import Turn exported LLM request/response pairs into a decision's log
doctor Check Python, the C core, training deps, config, your API key, an upstream, a bundle and a log
config Print every effective setting and where it came from, or write a starter shad0w.toml
proxy OpenAI-compatible gateway: answers marked decisions from the table, forwards the rest
train Train and certify a table from logged answers
try See what the table answers for a message, how sure it is, how fast, and why
stats Summarise a log, or print a snapshot of a running proxy or server
watch Live counters from a running proxy or server
serve HTTP decisions from a bundle, with dashboard and metrics
decide One raw decision as JSON
bench Latency percentiles of a bundle
report Print a bundle's certificate
certify Re-certify a bundle on fresh answers it never trained on
calibrate Certify a bundle against human labels
compile Build a table from human labels or from unlabelled text
shadow Train with an explicit schema (the older form of train)

Settings that a command does not take as a flag come from SHAD0W_* variables, then shad0w.toml, then the defaults (Configuration). A flag always wins.

Start#

init#

Write a commented shad0w.toml (with llm and api_key_env) and a runnable app.py that uses decision(). Existing files are kept unless you pass --force.

Flag Default What it does
--dir . Folder to write into (created if missing)
--name intent The decision's name: a Python name, used for the variable, the folder and the log field
--llm openai/gpt-6-luna provider/model; the provider picks api_key_env (none for local servers such as ollama/...)
--options a card-support example (refund, lost_card, balance, other) Comma-separated option names, at least two
--force off Overwrite existing files
--files off Write the older starter instead: schema.json and teacher_log.jsonl for shad0w shadow
shad0w init --dir support --name ticket --options billing,tech,other
wrote support/shad0w.toml, support/app.py
next:
  cd support
  export OPENAI_API_KEY=sk-...      # your key; shad0w.toml names this variable
  python app.py "my card was stolen"
  shad0w status

status#

One line per decision folder: rows logged against min_rows, the certified share at α, live agreement on spot checks since the last training, and the next step.

Flag Default What it does
--folder the folder setting (shad0w) The decisions folder to scan
--config ./shad0w.toml Config file to read
intent    1,500/1,000 rows  certified 61.2% at alpha=0.05 (threshold 0.886)  live agreement 97.0% (n=33)  -> serving
ticket      240/1,000 rows  no table yet  -> log 760 more answers

import#

Turn exported request/response pairs into a decision's log, so training can start from answers you already paid for. Each line of the file is {"request": ..., "response": ...}; OpenAI Batch output works as is. Labels that match none of the options are skipped and counted. Rows are written with "source": "import".

Flag Default What it does
--from required openai-chat (chat completions) or openai-decisions (/v1/decisions)
--file required The JSON Lines export
--question required The decision's name: its folder and log field
--options the request's enum, or the answers seen Comma-separated options
--dir shad0w Decisions folder
--config ./shad0w.toml Config file to read
shad0w import --from openai-chat --file export.jsonl --question intent
export.jsonl: 1,500 answers imported into shad0w/intent/log.jsonl (0 exchanges without a usable answer, 0 with an unknown option)
next: shad0w train --dir shad0w --question intent

Check and configure#

doctor#

Checks what most often goes wrong and prints one line each: [ok], [--] (a warning) or [!!] (a problem). Exits 1 when any [!!] line is printed, so it works in CI. What each line means: Troubleshooting.

Flag Default What it does
--llm the llm setting provider/model whose API key should be set; prints where the key comes from, masked
--upstream — An OpenAI-compatible base URL to probe (<url>/models)
--bundle — A bundle to load; reports each question's options, threshold and α
--log — A log to count against min_rows
--config ./shad0w.toml Config file to read and validate
shad0w doctor --llm openai/gpt-6-luna --bundle shad0w/intent/bundle --log shad0w/intent/log.jsonl
[ok] Python 3.12.14                       shad0w 0.3.2
[ok] C core                               built in
[ok] training deps (scipy, scikit-learn)  installed
[--] torch (optional, faster training)    not installed: scipy solver is used
[ok] TOML config reader                   tomllib
[ok] Node (JavaScript package)            v24.20.0
[ok] config                               defaults; mode=serve alpha=0.05 audit_rate=0.01 canary=1.0
[ok] openai API key                       key: set via OPENAI_API_KEY (sk-…3f9a)
[ok] bundle question 'intent'             4 options, threshold 0.886, alpha 0.05
[ok] certificate.json                     present
[ok] log                                  1,500 rows (ready to train)

all good

config#

Without flags, prints every setting, its effective value and where it came from (default, env:SHAD0W_…, toml:…, toml:… [questions.X], configure()), then the config file in use and where the LLM key comes from (masked).

Flag Default What it does
--question — Also apply that question's [questions.<name>] section
--config ./shad0w.toml or $SHAD0W_CONFIG Config file to read (with --init: the file to write)
--init off Write a commented shad0w.toml listing every setting with its default and meaning; refuses to overwrite
--explain on The default action; accepted for clarity
shad0w config --question intent
shad0w config --init

Train and certify#

train#

Train and certify a table from logged answers. Questions and options are detected from the log unless you pass --schema (or a previous bundle's schema.json exists). When at least 100 spot-check rows exist, they are used to certify (why).

Flag Default What it does
--dir — The decision() / proxy layout: reads <dir>/<question>/log.jsonl, writes <dir>/<question>/bundle
--question the only one in --dir Which question to train
--log — A JSON Lines log (instead of --dir)
--out — Bundle folder to write (with --log)
--schema options seen in the log A schema.json naming each question's options
--alpha the alpha setting (0.05) Most disagreement allowed among table answers
--delta the delta setting (0.1) Chance the certificate itself is wrong
--min-rows the min_rows setting (1000) Refuse smaller logs (exit 1); 100 is the hard minimum
--max-mb the max_mb setting (none) Size budget per table, in MB
--rename OLD=NEW — Rename a label in the log while training; repeatable. Without --schema, the previous bundle's schema.json is reused with the names renamed
--gate off Keep the existing bundle unless the new one certifies at least 80% of its share (exit 3 when kept)
--teacher unspecified A name for your LLM, recorded in the certificate
--config ./shad0w.toml Config file to read (including [questions.<name>])
shad0w train --dir shad0w --question intent
shad0w train --log log.jsonl --out bundle --schema schema.json --alpha 0.02 --max-mb 1
shad0w train --log log.jsonl --out bundle --rename lost_card=card_lost   # the old schema is renamed for you

report#

Print a bundle's certificate as JSON: α, δ, the teacher, and per question the options, the number of rows, the threshold, the certified share and the calibration set used.

Flag Default What it does
--bundle required The bundle folder

certify#

Keep the table, and refresh its threshold and certificate on fresh answers it never trained on (for example last week's log).

Flag Default What it does
--bundle required The bundle to re-certify (rewritten in place)
--data required JSON Lines of fresh LLM answers (text plus one field per question)
--alpha the bundle's α Most disagreement allowed
--delta 0.1 Chance the certificate itself is wrong
--teacher the bundle's A name for your LLM
--force off Certify even with fewer than 100 rows (it then usually stops serving)

calibrate#

Certify against human labels (the truth) instead of your LLM. About 300 labels per question suffice.

Flag Default What it does
--bundle required The bundle (rewritten in place)
--data required JSON Lines of labelled examples, held out from training
--alpha the table's α Most error allowed
--force off Calibrate even with fewer than 100 labels per question

compile#

Build a table without an LLM log.

Flag Default What it does
--schema required schema.json naming the options
--data — JSON Lines of human labels
--unlabeled — A text file, one message per line: label-free mode, uncertified until calibrated (needs shad0wllm[logs])
--encoder bge-small Sentence encoder for label-free mode
--alpha 0.05 α recorded for later calibration
--out required Bundle folder to write

shadow#

Train with an explicit schema and data file: what train does under the hood, kept for older scripts.

Flag Default What it does
--schema required schema.json
--data required JSON Lines: text plus one field per question holding your LLM's answer
--teacher unspecified A name for your LLM
--alpha 0.05 Most disagreement allowed (this command does not read shad0w.toml)
--delta 0.1 Chance the certificate itself is wrong
--out required Bundle folder to write

Use a table#

try#

Shows, for each message: whether the table would answer, the answer, its confidence against the certified threshold, the time in µs, the reason, and the top three options. Without messages it reads them interactively (an empty line quits).

Flag Default What it does
--bundle required The bundle folder
--question the bundle's only question Which question to show
text … — One or more messages
my card was stolen
  ✓ table  lost_card  (confidence 0.962, needs 0.911)  7.7 µs
     certified: answered by the table
     top: lost_card 0.96, card_swallowed 0.03, other 0.01

decide#

Flag Default What it does
--bundle required The bundle folder
text required One message
--exposed off Treat the input as adversarial (also defer answers a few edits could flip)

Prints the raw decision for every question as JSON: choice, confidence, certified, flag, probabilities.

bench#

Flag Default What it does
--bundle required The bundle folder
--texts required A text file, one message per line

Prints {"n", "p50_us", "p99_us", "questions"} after a short warm-up.

serve#

HTTP decisions from a bundle, without an LLM: POST /v1/decide, /v1/decisions, /v1/systemone, /v1/playground, plus the dashboard at /, /v1/stats, /metrics and /v1/health. See HTTP. Off this machine, set an access token ($SHAD0W_PROXY_TOKEN, --token-env, --token-file); see Access token.

Flag Default What it does
--bundle required The bundle folder
--host 127.0.0.1 Address to listen on
--port 8010 Port
--token-env NAME / --token-file PATH $SHAD0W_PROXY_TOKEN Access token every route but /v1/health needs
--cost-per-call — Cost of one LLM call, for "money saved"
--llm-latency-ms — Assumed LLM latency, for "time saved"

proxy#

An OpenAI-compatible gateway in front of your LLM. Requests marked as decisions are answered from the table when it is certified; everything else is forwarded unchanged. Full guide: The proxy.

Flag Default What it does
--upstream https://api.openai.com/v1 The real API base URL (OpenAI, Groq, Ollama, vLLM, …)
--dir the folder setting (shad0w) Where logs and bundles live: <dir>/<question>/
--model — Upstream model for requests that use model="shad0w/<question>"
--api-key-env NAME forward each client's own key Send the key in this environment variable upstream
--api-key-file PATH forward each client's own key Send the key in this file upstream, re-read on every request (Docker / Kubernetes secrets). Not with --api-key-env
--host 127.0.0.1 Address to listen on
--port 8010 Port
--token-env NAME / --token-file PATH $SHAD0W_PROXY_TOKEN Access token every route but /v1/health needs; see Access token
--insecure-open off Allow a non-local --host with the proxy's own key and no token
--audit-rate the audit_rate setting (0.01) Share of decisions spot-checked against the LLM
--auto-train the auto_train setting (0, off) Retrain a question every N new LLM answers
--alpha the alpha setting (0.05) Most disagreement allowed
--delta the delta setting (0.1) Chance the certificate itself is wrong
--min-rows the min_rows setting (1000) Answers needed before auto-train runs
--mode the mode setting (serve) serve, shadow or off
--canary the canary setting (1.0) Share of certified answers the table may serve
--never-serve — Comma-separated labels that always go to the LLM
--min-confidence — Confidence floor on top of the certificate
--capture the capture setting (header,model,tools,decisions) How requests are recognised as decisions; add json_schema; repeatable
--timeout the timeout setting (120) Upstream timeout in seconds
--trace — JSON Lines file receiving every decision
--bundle Q=PATH <dir>/<Q>/bundle Use the bundle at PATH for question Q; repeatable
--schema — A schema.json naming each question's options
--cost-per-call — Cost of one LLM call, for "money saved"
--llm-latency-ms — Assumed LLM latency until measured, for "time saved"
--no-text off Keep request text out of the dashboard
--config ./shad0w.toml or $SHAD0W_CONFIG Config file to read
shad0w proxy --upstream https://api.openai.com/v1 --api-key-env OPENAI_API_KEY --mode shadow
shad0w proxy --api-key-file /run/secrets/openai --token-file /run/secrets/shad0w-token --host 0.0.0.0 --canary 0.1

On start it prints where the key comes from, masked: upstream key: set via OPENAI_API_KEY (sk-…3f9a).

Watch#

stats#

Flag Default What it does
--log — Summarise a log: answers per source and per option, and whether it is ready to train
--top 15 Options shown per question
--trace — Also summarise a trace file: share answered by the table
--url — Instead of --log: print one JSON snapshot from a running proxy or server (/v1/stats)

One of --log or --url is required.

watch#

Live counters in the terminal: decisions, share answered by the table, LLM calls, time and money saved, per-question latency and the live safety bound, and the latest decisions. Ctrl-C quits.

Flag Default What it does
--url http://127.0.0.1:8010 The running shad0w proxy or shad0w serve
--every 1.0 Refresh interval in seconds

Exit codes#

Code Meaning
0 Success
1 train: not enough logged answers yet. doctor: at least one [!!] line
2 A one-line shad0w: … error on stderr (missing bundle or log, missing training dependencies, invalid setting, bad flag); no traceback
3 train --gate kept the old table

Files#

shad0w.toml                              settings (optional)
shad0w/intent/log.jsonl                  {"text": "...", "intent": "lost_card", "source": "teacher", "ts": ...}  one per LLM answer
shad0w/intent/bundle/manifest.json       options, certified threshold, alpha, drift-guard state
shad0w/intent/bundle/intent.s0           the table (int8)
shad0w/intent/bundle/certificate.json    what was certified, on how much data, when
shad0w/intent/bundle/schema.json         the options (and descriptions) it was trained with

A typical table is about 1.5 MB on disk.