Skip to main content

jevlikedecisions

One request, one or more decision backends

Python, CLI, HTTP, and MCP use the same request fields. Existing callers keep passing one configured name or alias in backend. A caller selects the configured pair with a configured combined-selection alias (both or combined in the packaged configuration), or passes a list of names/aliases from Python.

Every response exposes status, arms, intent, and via; each arm contains either result or an error whose type and message come from that arm's actual exception. Single responses retain their existing top-level fields. Combined arms run concurrently, and any arm failure makes the combined status failed without promoting the survivor. Before any arm executes, the complete, untruncated request is built and counted for every selected wire shape and checked against the smallest selected ceiling; refusal at that shared ceiling executes zero arms. Arms keep separate audit rows and ledger IDs linked by one request digest. Combined execution costs the sum of its calls; its only extra product is agreement/disagreement.

jevlikedecisions.decide(...) is one typed decision entrypoint over a family of provider adapters. Shared evidence is sent once, in full, and responses are checked against both the provider shape and the originating questions. Backend selection is mandatory: no call silently falls back to the deterministic mock_jevlike backend.

Installation and spend-gate setup

Python 3.11 or newer is required.

Install the package and its sole runtime dependency, llm-spendguard, with pip:

python -m pip install jevlikedecisions

Pending first release: The package is not yet published on PyPI. Until the first release is published, clone this repository, change into the clone, and run python -m pip install .. Delete this note when publishing the first release.

Before making a model call, arm the spend gate in the virtual environment. This is required: jevlikedecisions deliberately refuses to call a model from an interpreter where spendguard is not enforcing. With the target virtual environment activated, run:

spendguard install-hook --venv "$VIRTUAL_ENV" --no-pkg

Temporary upstream workaround: --no-pkg avoids a FileNotFoundError for pip-installed, non-editable llm-spendguard environments where install-hook otherwise looks for a repository pyproject.toml. Remove this flag and note after that upstream defect is fixed.

Verify the activated interpreter:

spendguard doctor

The successful verdict line is ENFORCING HERE: 🟢 YES — calls from this interpreter are gated.

Temporary upstream workaround: spendguard doctor may currently exit non-zero after printing that successful verdict, with ValueError: Pricing returned no token cost. Read the verdict line rather than the exit code; remove this note after that upstream defect is fixed.

If gate setup is skipped, decide() refuses model calls through require_enforcing_spendguard.

Request and question schema

Every surface accepts the same JSON object:

{
  "input_parts": ["complete evidence"],
  "questions": [{"name": "safe", "type": "predicate", "description": "Is it safe?"}],
  "backend": "mock_jevlike",
  "intent": "documented-example",
  "prompt_cache_key": null
}

intent and backend are required. Every decision result identifies backend, provider, model, route, via, ledger_id, usage, cost_complete, and known_input_cost_floor_usd. Inspect route to distinguish in-process and metered results. cost_complete says whether the cost is fully settled; when a provider omits output-token usage, a metered result has cost_complete: false, no ledger id, and only the known input-cost floor. In-process results are settled at zero cost. Every question has name, type, and description. type is predicate, choice, or score. A choice also has choices, whose entries contain value and description. A score also has levels, whose entries contain label and description. Library validation is authoritative for every surface.

Decision suitability

assess_decision_suitability asks one configured classification question through decide() and returns the same contract through Python, CLI suitability, HTTP /suitability, and MCP assess_decision_suitability. Its bounded shapes are derived from the intersection of every configured backend's declared question_types, so a framing is backend-independent. In the shipped configuration these are predicate; choice with one or more caller-declared {value, description} entries; and score with caller-declared {label, description} levels within the configured minimum and maximum. A fourth configured outcome identifies prompts that require unbounded generation.

The result uses decision_shape for the selected question type, required_declarations for the schema the caller still owes, decide_request for a complete request that can be sent to decide(), and question_type_disagreement for a nullable object containing classified_question_type and supplied_question_type. On disagreement, the complete paid decision and suitability classification remain available, decide_request stays null, and required_declarations states what a conforming question for the classified shape requires. out_of_scope_explanation explains why an unsuitable prompt does not fit, and general_llm_generation_pointer remains present for work outside the bounded engine. The old reframing field is not part of this contract because an already-valid request is not a reframing, while an unsuitable prompt needs an explanation and declaration requirements rather than a fabricated request.

The optional candidate_question is the no-generation boundary. When supplied, it must already be a complete valid question of a type supported by every configured backend; malformed candidates are refused before decide() at zero spend. It is included whole in the same paid classification request and, when its type agrees with the selected shape, is returned unchanged inside decide_request. A post-call type disagreement is a reported finding, not a refusal. When the candidate is omitted, prose may still be classified as a bounded shape, but decide_request is null and required_declarations tells the caller what to supply. The capability never extracts choices or score levels from prose and never invents them. The zero-spend estimate accepts the same optional question, so it serializes and counts the same assessment request the paid path would submit.

Live suitability acceptance uses this capability directly through either Python or MCP with the same request fields. MCP tool names are resolved from the capability registry, and the measurement consumes the returned suitability contract without reconstructing or supplying any field it checks.

Backend names and aliases come from the package configuration. An alias is accepted only at selection; estimates, approvals, receipts, audit evidence, and returned provenance always use the canonical backend name. Omitting backend never selects a default and produces a typed DecisionUnavailable refusal.

Canonical backend Accepted aliases Provider Model Usability and precondition
openai_decisions openai openai gpt-6-luna Usable only with OPENAI_API_KEY, an enforcing spendguard interpreter, and a matching estimate approval. Proven live: 53 validated attempts over 2026-11-09 to 2026-11-10 totalling 1,607,127 input tokens, every one metered, cost-complete and carrying a ledger id. The provider reports an explicit zero output tokens on every one of them, so no output-side volume has been observed and output cost cannot be reconciled from provider-supplied usage. A further 39 attempts were rejected with HTTP 400 for the same cause as the typesafe_jev rejections below: a score question carrying 11 levels against this provider's maximum of 10, which the packaged limit now prevents locally.
typesafe_jev jev typesafe jev-1.14.0 Usable only with TYPESAFE_API_KEY, an enforcing spendguard interpreter, and a matching estimate approval. Input costs $0.042 per 1M tokens and output is free, as documented at docs.typesafe.ai/models; the wire schema is documented at docs.typesafe.ai/api/system-one. Proven live: 33 validated attempts on 2026-11-09 totalling 1,663,893 input and 517,177 output tokens, every one metered, cost-complete and carrying a ledger id. A further 45 attempts were rejected by the real endpoint with HTTP 422, every one for the same cause: a score question carrying 11 criteria against the provider's maximum of 10. That is a closed schema-discovery episode, not ongoing instability — the packaged maximum_score_levels is now 10, and an 11-level question is refused locally by validate_decision_questions before any request is built or any money is spent.
mock_jevlike none mock deterministic Usable in-process and never selected implicitly. Predicate probability is exactly 0.5; choice selects the first option with probability/confidence 1.0 and all others 0.0; score selects the first level with score 0.0, probability/confidence 1.0, and all other levels 0.0; reported input/output tokens are both 0.

Estimate, approve, then spend

The shipped configuration contains exactly three backends: metered typesafe_jev and openai_decisions, plus the in-process mock_jevlike test double. Their only transport kinds are http and in_process. A backend that declares the removed subscription transport is refused when configuration loads; subscription-lane LLM access belongs in llm-spendguard.

Choice questions may declare "calibrated_choice_distribution": "required" or "calibrated_choice_distribution": "omitted". Omitting the declaration preserves the current required contract: choice, probabilities for every declared choice in order, and confidence. Declaring omitted explicitly requests only name, type, and choice; returning either probabilities or confidence is a named nonconformance, even if null. Unknown or malformed declarations are refused with the permitted values from the packaged choice_distribution_contract configuration beside limits. Mixed declarations in one call are supported. The shared validator enforces the declared answer fields across Python, CLI, HTTP, and MCP. Score questions are unchanged.

Self-reported confidence is not calibrated. In the measured eight-question probe with twenty declared choices per question, gpt-6-luna returned top probability/confidence 1.0 on every item (entropy approximately zero; effective support 1.00), while glm-5.3 returned top probability/confidence 0.99 on every item (entropy 0.0629; effective support 1.06). Those degenerate vectors do not establish calibrated uncertainty. At the working shape of 32 questions × 29 choices, the measured output grew from 2,149 to 23,270 tokens. The declaration allows callers to omit that unvalidated distribution rather than pay for it. The field describes the requested distribution contract; it does not certify calibration when required.

Paid decisions fail closed. First call decide(..., estimate_only=True): it counts the complete provider request, prices it through spendguard.pricing, spends $0, and persists a matching approval. Only a later identical decide(...) may reach the gated metered transport. Provider usage remains authoritative for the real receipt. Configure approval storage with JEVLIKEDECISIONS_APPROVAL_DIR, approval signing with JEVLIKEDECISIONS_APPROVAL_INTEGRITY_KEY_FILE, receipt storage with JEVLIKEDECISIONS_RECEIPT_DIR, and an alternate package configuration with JEVLIKEDECISIONS_CONFIG. The legacy by-value integrity variable is retained only to produce a migration refusal. The integrity file must contain a non-whitespace UTF-8 key and permit no non-owner access.

Run callers under an enforcing llm-spendguard interpreter: the gate supplies model credentials from its governed keys file before this package starts. The packaged backend key_env declarations currently name OPENAI_API_KEY for openai_decisions and TYPESAFE_API_KEY for typesafe_jev; jevlikedecisions has no default-path credential loader. For a repository-specific fallback only, set the environment variable named by credential_fallback_file_env (JEVLIKEDECISIONS_KEYS_FILE in the packaged configuration). If unset, its configured default is .jevlikedecisions/jdlkeys.env beneath the caller's home directory. An absent fallback is normal. The file may supply only names declared by backend key_env, never overwrites a name already supplied by the gate or caller, must permit no non-owner access, and must not have a replaceable non-sticky ancestor.

Provider requests have a socket bound. Python, CLI, HTTP, and MCP callers may override it per call with request_timeout_seconds; omitting it on any surface uses the positive finite decision_request_timeout_seconds value from decision configuration. The packaged 300-second value is a conservative ceiling, not a measured bound: historical decide rows contain no latency distribution. It is intentionally well above a plausible healthy decision and must be re-derived after this release records request latency.

The decision audit store uses SQLite WAL journaling and the configured busy timeout on every connection opened for attempts, outcomes, queries, and wire-evidence reads. WAL permits readers to continue while a writer commits; SQLite still serializes writers. An append returns succeeded after a durable database write, retries busy/locked contention for the configured attempt count and backoff, then atomically spools the complete row and returns pending; malformed data, non-contention storage errors, or spool failure return failed. The first append creates the store with owner-only permissions using one atomic O_CREAT open; later appends do not change an existing store's mode. Opening an existing rollback-journal store switches its persistent journal setting to WAL and may create -wal and -shm sidecar files. This does not rewrite the table schema or make the database incompatible, and it is reversible by setting PRAGMA journal_mode=DELETE after all connections have closed and the WAL has been checkpointed.

Python, CLI, HTTP, and MCP

Python:

from jevlikedecisions import decide

estimate = decide(input_parts, questions, backend=backend, intent=intent, estimate_only=True)
result = decide(
    input_parts, questions, backend=backend, intent=intent,
    request_timeout_seconds=request_timeout_seconds,
)

CLI input comes from a JSON file or stdin. estimate prints the estimate and its approval_path; decide prints answers and receipt fields. The backend value accepts either a canonical name or an alias reported by backends.

jevlikedecisions backends
jevlikedecisions estimate --backend "$JEVLIKEDECISIONS_BACKEND" request.json
jevlikedecisions decide --backend "$JEVLIKEDECISIONS_BACKEND" --request-timeout-seconds "$JEVLIKEDECISIONS_REQUEST_TIMEOUT_SECONDS" request.json

The CLI flag fills a missing request backend; if both the flag and JSON field are present they must agree or the command fails. CLI exit codes follow sysexits: 0 (EX_OK) for success, 64 (EX_USAGE) for malformed input, 65 (EX_DATAERR) for a typed decision refusal, and 70 (EX_SOFTWARE) for an internal fault. The CLI reports every failure as TypeName: message on stderr; catching internal faults gives automation a stable code without hiding the fault.

The standard-library HTTP server exposes POST /backends, POST /estimate, and POST /decide; its backend field accepts the same config-derived canonical names and aliases, and /decide accepts the optional numeric request_timeout_seconds field. HTTP 400 identifies a malformed request, 422 identifies a typed decision refusal, 503 specifically identifies DecisionUnavailable, and 500 identifies an internal fault. Keeping unavailable decisions at 503 preserves the established retry signal, while the JSON error field retains the exact typed refusal. Set JEVLIKEDECISIONS_HTTP_PORT; optionally set JEVLIKEDECISIONS_HTTP_HOST. With no JEVLIKEDECISIONS_OPERATOR_TOKEN it binds only to loopback. When that token is set, callers must send it as an operator bearer token. A paid caller must successfully call /estimate before /decide with the identical request. Both HTTP listeners derive their maximum request-body size from four UTF-8 bytes for every token in the largest configured backend input context. A declared body above that ceiling is refused with HTTP 413 before the body is read; the capability listener uses its normal error/message JSON shape and the MCP listener uses JSON-RPC error JSON.

jevlikedecisions-http --host "$JEVLIKEDECISIONS_HTTP_HOST" --port "$JEVLIKEDECISIONS_HTTP_PORT"

The package's sole runtime dependency is llm-spendguard; its MCP surface is a standard-library JSON-RPC stdio server. The list_decision_backends, estimate_decision, and make_decision tools expose the same selection contract and send evidence whole. Enumeration is derived from configuration and reports canonical name, aliases, provider, model, usability, and every missing precondition; both decision tools accept exactly those canonical names and aliases. make_decision also exposes estimate_mode with the same decide semantics as the other surfaces and accepts optional numeric request_timeout_seconds. The approval from the estimate tool must exist before a paid call through the decision tool.

jevlikedecisions-mcp

The same registry-derived MCP tools are also available as a long-running, JSON-only MCP Streamable HTTP service at MCP_HTTP_ENDPOINT_PATH in mcp_http_transport.py; this is a transport for the stdio MCP surface, not a separate capability surface. Set JEVLIKEDECISIONS_MCP_HTTP_PORT; optionally set JEVLIKEDECISIONS_MCP_HTTP_HOST. With no JEVLIKEDECISIONS_MCP_HTTP_OPERATOR_TOKEN it binds only to loopback. When that token is set, the host is required and callers must send the token as an operator bearer token. Both MCP processes derive launch readiness from the environment-requirement reviews on every MCP-exposed capability and refuse to serve if any review is UNREVIEWED or any required configured variable is unset. Behavioral startup tests exercise that refusal independently for the stdio and Streamable HTTP processes before either process can serve requests. With the packaged metered backends, set the variables configured by approval_directory_env, approval_integrity_key_file_env, decision_audit_store_path_env, decision_audit_spool_path_env, and receipt_directory_env. Backend credential availability remains the separate per-backend status returned by list_decision_backends; the optional credential fallback file remains optional. Errors name variables only, never their values or resolved paths.

jevlikedecisions-mcp-http --host "$JEVLIKEDECISIONS_MCP_HTTP_HOST" --port "$JEVLIKEDECISIONS_MCP_HTTP_PORT"

On macOS, scripts/generate_mcp_http_launchd_agent.py generates a launchd user-agent plist from the repository without putting a platform-specific entry point in the wheel. The port, approval directory, audit store path, audit spool path, and receipt directory are required. A wrong record location works silently and forks the decision history, so the generator will not guess any of these four locations. The executable defaults to jevlikedecisions-mcp-http beside the Python interpreter running the generator; the owner and home come from the current environment, and the label, plist destination, and per-tool log paths derive from them. The generated daemon environment uses the four operator-supplied record locations and defaults only the approval_integrity.key owner-only key-file path. A wrong key-file path fails loudly and closed. The bind host defaults through the server's existing unauthenticated loopback resolution. Every derived value has an explicit override flag, and the plist contains only the key file path, never the key value. Generate and load an agent like this:

python scripts/generate_mcp_http_launchd_agent.py --port "$JEVLIKEDECISIONS_MCP_HTTP_PORT" \
  --approval-directory "$JEVLIKEDECISIONS_APPROVAL_DIRECTORY" \
  --audit-store-path "$JEVLIKEDECISIONS_AUDIT_STORE_PATH" \
  --audit-spool-path "$JEVLIKEDECISIONS_AUDIT_SPOOL_PATH" \
  --receipt-directory "$JEVLIKEDECISIONS_RECEIPT_DIRECTORY"
launchctl load "$HOME/Library/LaunchAgents/com.$USER.jevlikedecisions-mcp-http.plist"
launchctl kickstart -k "gui/$(id -u)/com.$USER.jevlikedecisions-mcp-http"

Use kickstart -k to restart the loaded label; do not load a second copy. This daemon is not a singleton: another process on the same port fails to bind, and KeepAlive then retries it at the configured ThrottleInterval. Without JEVLIKEDECISIONS_MCP_HTTP_OPERATOR_TOKEN, the daemon binds loopback only. A coding tool's HTTP MCP server entry should point to http://127.0.0.1:<port>/mcp, replacing <port> with the required operator-selected port.

Paid live-proof utility

scripts/run_paid_decision_live_proof.py preserves the reproducible paid-call proof harness in the repository. It takes a configuration path rather than embedding one; examples/paid_decision_live_proof.example.json shows the input shape. By default it estimates every configured backend, prints the per-backend and total token/USD estimates, and exits without making a paid call. After reviewing that complete estimate, rerun with the explicit --authorize-paid-calls flag to execute every configured proof. Its records include reported-output-token status, cost completeness, and the known input-cost floor alongside the ledger and receipt evidence.

PYTHONPATH=src python scripts/run_paid_decision_live_proof.py examples/paid_decision_live_proof.example.json
PYTHONPATH=src python scripts/run_paid_decision_live_proof.py examples/paid_decision_live_proof.example.json --authorize-paid-calls

assert_all_decisions_via_jevlikedecisions(...) cross-checks package-owned receipts against spendguard rows by ledger_id and fails closed when there are no auditable receipts. Spendguard's calls table has no endpoint, route, or via columns, so this proves receipt-backed package calls; it cannot prove that no outside caller contacted a Decisions endpoint.

Capability documentation verdict maintenance

Capability documentation verdicts accumulate by exact (subject, sha256(documentation)) pairs. The documentation judge normally evaluates only pairs absent from the schema-current record, retains byte-identical entries with their original per-entry instrument provenance and timestamp, and reports every stale pair it prunes. Use --force-full-rejudge when the judging instrument changes, including its model, prompt, sample count, or independent-run count; that mode replaces every current pair rather than retaining evidence produced by a different instrument. Older record schemas are refused and must be migrated explicitly before judging.

Troubleshooting

A framework Python without a usable certificate bundle may need SSL_CERT_FILE set to the path of an installed CA bundle, such as the one supplied by certifi, before a real HTTPS call. Never put keys or secret values in configuration or examples.

anyjev remains a named v1 schema gap: its authoritative independent wire contract was unavailable, so no fields were invented. It can be added as another adapter without changing decide().

General LLM generation

This package wraps the jev and OpenAI Decisions APIs only. General text generation was removed; use llm-spendguard for general LLM calls.

Metadata

Release files for jevlikedecisions 0.6.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for jevlikedecisions 0.6.0
File Size Uploaded Provenance Actions
jevlikedecisions-0.6.0.tar.gz 3.3 MB With provenance Details for jevlikedecisions-0.6.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for jevlikedecisions 0.6.0
File Interpreter ABI Platform Actions
jevlikedecisions-0.6.0-py3-none-any.whl Python 3 none any Details for jevlikedecisions-0.6.0-py3-none-any.whl

Total release size: 3.4 MB

Release files / jevlikedecisions-0.6.0.tar.gz

Download URL jevlikedecisions-0.6.0.tar.gz
Size 3.3 MB
Tags Source
SHA-256 checksum
How to use checksums
eca283d0f0a09ec75d169850468dd672d9dd1d041571c6ee21dc76a9b0e93ddb
BLAKE2b-256 checksum
How to use checksums
4979db4dff812d81d9e1923e164ae4f749efaf368f278d14238050e81af5ee36
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 10, 2026.

Transparency log

Release files / jevlikedecisions-0.6.0-py3-none-any.whl

Download URL jevlikedecisions-0.6.0-py3-none-any.whl
Size 95.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
a93fdc2d4f1fe5de99ba17ee4c9dc0d8a8779e0ae436cec509bd5b2f4c12c79f
BLAKE2b-256 checksum
How to use checksums
9183f3010e5088c1b82fd80c227d37efa3a47cb6851e7a313a20604e57b625aa
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 10, 2026.

Transparency log

Security for jevlikedecisions 0.6.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

Both files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for jevlikedecisions-0.6.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 10, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page