Decision log¶
Audience: operators reconstructing why the router / profile / gate picked what it picked, without re-reading four modules of routing code.
Overview¶
Every routing / criterion-profile / gate-fire decision in Bernstein writes one append-only JSONL record to .sdd/runtime/decisions.jsonl.
Source:
src/bernstein/core/observability/decision_log.pysrc/bernstein/cli/commands/decisions_cmd.py
The log is append-only, concurrent-safe (in-process lock + append-mode fd; cross-process safety from the kernel for the small line sizes written here), and malformed-line tolerant on read.
Record schema (schema_version: 1)¶
| Field | Use |
|---|---|
ts | Float epoch seconds |
decision_id | dec-<uuid> |
kind | model_route, mode_profile, criterion_profile, gate_fire |
chosen | Winner id |
alternatives | [{"id", "score", "reason"}] (truncated at MAX_ALTERNATIVES) |
confidence | 0.0 - 1.0 |
rationale | Human-readable summary |
parent_decision_id | Parent decision id (for chained routing) |
policy_path | Ordered list of policies that fired |
winner_score | Numeric score for the chosen alternative |
inputs | Free-form {"task_id": ..., ...} |
CLI¶
bernstein decisions tail [-n N] [--path PATH]
bernstein decisions search [--kind KIND] [--since DURATION] [--path PATH]
--kindfilters to one decision kind (e.g.model_route).--sinceaccepts30s,15m,2h,1d.--pathoverrides the default.sdd/runtime/decisions.jsonl.
Both commands render a Rich table with ts, kind, chosen, confidence, and a trimmed rationale. Use redirection / shell tooling on the raw JSONL when you need scriptable output.
Examples¶
Last 50 routing decisions:
Look at every gate that fired in the last hour:
Pipe to jq for richer analysis:
jq -c 'select(.kind == "model_route")' .sdd/runtime/decisions.jsonl \
| jq -s 'group_by(.chosen) | map({model: .[0].chosen, n: length})'
Disabling¶
Set BERNSTEIN_DECISION_LOG=0 in the orchestrator environment. The single record_decision entry point becomes a no-op; routing paths remain unchanged. The CLI subcommands keep working against any existing log file.
Bounded payloads¶
alternatives is truncated to MAX_ALTERNATIVES entries before serialisation; a routing bug that explores 10,000 candidates cannot fill the disk through this channel.
Troubleshooting¶
bernstein decisions tail is empty. Either the log was disabled via BERNSTEIN_DECISION_LOG=0, or no routing has happened yet in this workdir. Run a small task first, then re-tail.
Concurrent writers from a multi-process run. Cross-process append safety relies on the kernel's atomicity guarantee for sub-PIPE_BUF writes. If you are sharing a log across hosts (NFS), wrap the runs with separate log paths and merge offline.
Malformed line warnings. A partial line (machine killed mid-write) will be skipped by iter_records with a debug log. Trim the trailing broken line by hand if it bothers your downstream tooling.