Configuration reference
Status: draft. This page describes the current judgevet configuration, not the configuration contract of the official TypeSafe SDK.
Direct Python adapters
Both HTTPSystemOneAdapter and AsyncHTTPSystemOneAdapter take these constructor
arguments. Neither reads judgevet environment settings or installs logging.
| Argument | Default | Behavior |
|---|---|---|
api_key |
None |
Supply a key explicitly. None raises ValueError; this is not an authentication check. |
base_url |
None |
Uses https://api.typesafe.ai when omitted or empty. |
default_model |
"jev-latest" |
Used when system_one receives no model or an empty model string. |
gateway |
None |
Optional GatewayConfig; omission retains direct authentication and no metadata. |
redactor |
None |
Optional synchronous StateRedactor; transforms a private state copy before transmission. |
network |
None |
Optional NetworkConfig; omission retains HTTPX2 proxy and TLS defaults. |
retry |
None |
Optional RetryPolicy; omission uses RetryPolicy(), three attempts. |
spend_cap |
None |
Optional SpendCap; omission sends every attempt the retry policy permits. See spend cap. |
audit |
None |
Optional AuditSink; omission writes no record. See audit records. |
fingerprint_key |
None |
Optional bytes key; omission leaves state_fingerprint as None. See state fingerprint. |
transport |
None |
Optional HTTPX2 transport; use the sync or async transport type appropriate to the adapter. |
timeout_seconds |
30.0 |
HTTPX2 read, write and pool timeout in seconds; connect timeout is fixed at 5 seconds. Values at or below zero raise ValueError. |
These are judgevet defaults from the HTTP adapter. The service host and endpoint are documented by TypeSafe. Timeouts apply to HTTPX2 operations, not a guaranteed total wall-clock deadline. Retries are on by default: three attempts on a rate limit or a 5xx status. See retry limits for the opt-out. A timed-out request may still be running at the service.
The jev-latest alias follows the model the service currently serves.
VERIFIED_MODEL names the one model version that this repository's live tests
have exercised; the verification ledger records it. Pass either value as
model; the choice between the latest and the verified model belongs to the caller.
Direct constructors do not apply the CLI/MCP settings URL validator. Callers own URL selection and credential disclosure. HTTPX2 environment proxy and certificate settings can still affect transport; see transport limits. Use sync or async context managers to release the client. Changing the process environment does not reconfigure an existing adapter.
Application-owned providers
Import ProviderFactory and provider_scope from judgevet.providers.
Use provider_scope(port=application_port) to borrow an existing SystemOnePort.
Use provider_scope(factory=application_factory) to acquire a port through a
synchronous context manager. The factory takes no arguments and returns an
AbstractContextManager[SystemOnePort]. Applications bind their configuration
when they construct that factory.
Exactly one selection is required. Missing or conflicting selections raise
ValueError on scope entry, before acquisition. The helper has no hosted
default, discovery or fallback. It checks that the selected port exposes a
callable system_one without calling it. Invalid ports raise
ProviderUnavailableError.
Borrowed ports remain open after success or failure. Once factory entry returns,
the helper calls its exit method exactly once after success, invalid-port
validation, call or rendering failure, or BaseException interruption. The
factory receives the escaping exception for cleanup. Its suppression return
value cannot swallow that exception. If cleanup raises, the cleanup exception
propagates with the original failure as its exception context. The factory owns
rollback of resources allocated before failed entry; the helper cannot clean
resources it never acquired.
Applications own provider dependencies, inference, typed translation and safe
error messages. Missing optional support should raise an actionable
ProviderUnavailableError, such as an instruction to install the application's
provider extra. Known provider failures use the
neutral provider errors.
Unexpected errors propagate unchanged. Structural port conformance does not
install audit records, state redaction or spend caps; applications must apply
the existing mechanisms where their provider can honor them.
Async applications import AsyncProviderFactory and async_provider_scope from
judgevet.providers. Use async with async_provider_scope(port=application_port)
to borrow an existing AsyncSystemOnePort. Use
async with async_provider_scope(factory=application_factory) to acquire one.
That factory returns an AbstractAsyncContextManager[AsyncSystemOnePort].
The async scope keeps every rule above for selection, validation and exit.
Source: https://github.com/Alberto-Codes/judgevet/issues/251.
This library boundary adds no environment settings or provider discovery. Existing hosted entry points retain their configuration. Source: accepted ownership contract.
CLI and MCP settings
The composition roots read Settings
when they start. No judgevet configuration file or automatic .env loading is
configured. An external tool such as direnv can populate the process environment.
The nested settings use the JEV_ prefix and __ separator.
| Environment name | Compatibility alias | Default | Effect |
|---|---|---|---|
JEV_API__KEY |
TYPESAFE_API_KEY |
No key | Literal credential or an explicit !command source. |
JEV_API__KEY_FILE |
None | Unset | Regular UTF-8 file containing one credential token. |
JEV_API__KEY_COMMAND_TIMEOUT |
None | 5.0 |
Command deadline after process creation; finite and greater than zero. |
JEV_API__BASE_URL |
TYPESAFE_BASE_URL |
https://api.typesafe.ai |
Service base URL. |
JEV_API__DEFAULT_MODEL |
None | jev-latest |
Stored adapter default; entry-point call overrides are described below. |
JEV_API__TIMEOUT_SECONDS |
None | 30.0 |
Adapter timeout; must be greater than zero. |
JEV_API__AUTH_HEADER |
None | Authorization |
Credential field name. |
JEV_API__AUTH_SCHEME |
None | Bearer |
Prefix token; empty sends the bare credential. |
JEV_API__HEADERS |
None | {} |
JSON map of explicit string metadata. Values are literal. |
JEV_API__REQUEST_ID_HEADER |
None | Unset | Opt into forwarding the scoped request ID under this field. |
JEV_API__PROXY |
None | Unset | Explicit HTTPX2 proxy URL; overrides standard proxy routing. |
JEV_API__CA_BUNDLE |
None | Unset | PEM file replacing default trust roots; must load successfully. |
JEV_API__VERIFY |
None | true |
Verify certificate chains and hostnames. Disable only in controlled tests. |
JEV_API__MAX_ATTEMPTS |
None | 3 |
Total requests per call; must be a positive integer. 1 disables retries. |
JEV_API__RETRY_BASE_DELAY |
None | 0.5 |
Initial backoff ceiling in seconds. |
JEV_API__RETRY_MAX_DELAY |
None | 5.0 |
Maximum backoff ceiling in seconds. |
JEV_API__RETRY_TRANSPORT |
None | false |
Permit retries after transport failures. |
JEV_API__SPEND_MAX_ATTEMPTS |
None | Unset | Physical attempts one CLI process or MCP server may send, retries included; must be a positive integer. See spend cap. |
JEV_API__SPEND_MAX_INPUT_TOKENS |
None | Unset | Settled input tokens after which the cap refuses further attempts; must be a positive integer. See spend cap. |
JEV_API__AUDIT_PATH |
None | Unset | JSON Lines audit file the CLI and MCP entry points append to. See entry-point audit file. |
JEV_LOG__FORMAT |
None | auto |
auto, json or console logging format. |
JEV_LOG__LEVEL |
None | info |
debug, info, warning, error or critical, in lowercase. |
When both documented names for a key or base URL are set, the JEV_API__...
value wins. These values are not merged. Python callers can also construct
Settings with explicit values; those override environment values. Ordinary
library callers can bypass Settings entirely and pass constructor arguments.
Settings accepts HTTPS URLs and HTTP URLs whose hostname is localhost or
127.0.0.1. It rejects other schemes and remote plaintext HTTP. This validation
does not prove that a destination is trusted or reachable.
Entry-point overrides
The CLI's explicit --api-key is literal and overrides every settings source. Prefer an approved
environment or secret provider because command arguments may be visible to
other processes or stored in shell history. Invalid settings can still prevent
startup even when an option supplies another value.
The CLI always passes its --model value, whose default is jev-latest.
Consequently, JEV_API__DEFAULT_MODEL does not select the CLI's requested model.
The installed judgevet-mcp command sends JEV_API__DEFAULT_MODEL on every
tool call. The setting defaults to jev-latest. An application that passes a
model other than jev-latest to the MCP main function overrides the
setting. For a pinned CLI model, use the --model option.
This behavior comes from the CLI, policy invocation, MCP entry point and MCP tools.
Logging and diagnostics
Logs use stderr. auto selects console output when stderr is a terminal and
JSON otherwise. The log format is separate from CLI --json, which selects
answer and handled-error rendering. Use the documented lowercase settings;
unknown log levels are not accepted by the logging configuration.
The event reference defines exact built-in fields and caller correlation.
SecretStr masks configured keys, command specifications and resolved keys. The HTTP adapter
retains an unwrapped key. Redaction has limits: CLI error text, protocol errors
and arbitrary tracebacks are not universally scrubbed. Consult
credentials and
diagnostic disclosure
before logging or sharing output. For recovery steps, use
troubleshooting.
Retry limits
Pass retry=RetryPolicy(...) to either adapter. Import RetryPolicy from
judgevet. Its defaults are max_attempts=3, retry_base_delay=0.5,
retry_max_delay=5.0 and retry_transport=False. An adapter built without
retry uses RetryPolicy(). The CLI and MCP server share that default through
JEV_API__MAX_ATTEMPTS.
The default retries errors whose retryable property is true. This covers
HTTP 429 and every 500 to 599 status. Transport failures also require
retry_transport=True. To make one request per call, opt out:
from judgevet import RetryPolicy
single = RetryPolicy(max_attempts=1)
assert single.max_attempts == 1
Pass it as retry=RetryPolicy(max_attempts=1).
A retried 5xx can repeat a billed attempt. A spend cap counts
every attempt, including each retry, and bounds that cost.
A read or write failure can occur after the service accepted the request;
replaying it can duplicate a billed judgment. The library does not provide
an idempotency guarantee.
The attempt count includes the initial request. After failed attempt number
n, the delay ceiling is min(retry_max_delay, retry_base_delay * 2**(n-1)).
The actual delay is uniformly distributed between 75% and 100% of that ceiling.
Zero base delay or zero maximum delay disables waiting. Each delay must be
finite and nonnegative. Exhaustion re-raises the final error with its cause;
there is no delay after the final attempt.
The sync and async adapters use the same policy. Async retry waits use
asyncio.sleep and are cancellable.
Cancellation during a request or wait propagates without another attempt.
Authentication errors, request errors, malformed successful answers and
redirects do not trigger retries. The existing HTTP 408 mapping remains a
non-retryable request error. One terminal diagnostic describes the final
attempt of the logical call.
These limits bound attempts and backoff, not total wall-clock duration. HTTPX2 operation timeouts still apply to each attempt. Account for all attempts and waits when choosing a platform request deadline.
The official SDK retry reference
documents two retries by default, 0.5-second initial delay, a 5-second cap and
25% subtractive jitter. judgevet uses those defaults: three attempts in total.
It keeps transport retries opt-in and leaves HTTP 408 unretried. It does not
honour Retry-After or retry-after-ms headers. These are local policy choices, not live-service
observations. The error recipe shows a bounded call.
Spend cap
Pass spend_cap=SpendCap(...) to either adapter. Import SpendCap and
JevBudgetExceededError from judgevet. The cap is off by default. Its limits
are max_attempts and max_input_tokens. Each is None or a positive integer.
A None limit does not bound that counter.
The cap counts physical attempts, not logical calls. Before each attempt,
including every retry, the adapter claims one slot from the cap. The claim
fails when attempts already equal max_attempts, or when settled input tokens
already reach max_input_tokens. A failed claim raises
JevBudgetExceededError and sends nothing. That error is not retryable, so the
retry loop stops. It carries limit ("attempts" or "input_tokens"), cap
and spent. Its status_code is None.
After a successful attempt, the cap adds the response usage.input_tokens.
A failed attempt still counts as an attempt but settles zero tokens. The AWS SDK
retry quota also charges failed attempts.
Source: https://docs.aws.amazon.com/sdkref/latest/guide/feature-retry-behavior.html.
LiteLLM also raises a distinct budget error that carries the spend and the cap.
Source: https://docs.litellm.ai/docs/proxy/users.
Open question: a timed-out or 5xx attempt may still bill input tokens at the service. No call has shown whether it does. Settling zero for a failed attempt is a local choice, tracked on #56.
The counters never reset. Every adapter that receives the same SpendCap
object, sync or async, draws from the same counters. A threading.Lock guards
them, because an asyncio.Lock guards one event loop only. The lock is held
for the counter update alone, never across an await.
Source: https://docs.python.org/3/library/asyncio-sync.html.
The attempt bound is hard: concurrent callers cannot exceed it. The token bound
is checked before sending, and tokens settle after the response. Attempts in
flight when the bound is reached can therefore settle past it. Set
max_attempts as well when a hard ceiling matters. The cap has no currency
and no preflight size estimate.
The CLI and MCP entry points read JEV_API__SPEND_MAX_ATTEMPTS and
JEV_API__SPEND_MAX_INPUT_TOKENS. Both are unset by default, which sets no
cap. A value that is not a positive integer, including true, fails settings
validation. Each entry point builds one cap for its adapter. The cap spans one
CLI process, or the whole lifetime of one MCP server, and never resets. A
tripped cap makes the CLI exit 1 with Error: Spend cap reached: …. Every MCP
tool returns a tool result with isError true and text such as
Spend cap reached: attempts spent 2 of 2; restart the server to continue.
Every later call fails the same way until the server restarts. An explicit
provider selection bypasses these settings and sets no cap.
An application can also construct a capped HTTP adapter and pass it to
run_cli or create_mcp_server through the existing judgment port. The
offline fakes in judgevet.testing accept the same spend_cap= option and
claim, refuse and settle one attempt per call the same way; see
offline tests.
Audit records
Pass audit= to either HTTP adapter to receive one record per logical call.
Auditing is off by default. Import the structural AuditSink protocol and the
frozen JudgmentRecord from judgevet. The protocol defines one synchronous
method, record(record: JudgmentRecord) -> None. The caller owns where records
go, how long they stay and who reads them. JsonlAuditSink is the reference
sink; see JSONL sink.
See the protocol and the
record.
The adapter writes exactly one record when a logical call ends: after success, after an error, and after cancellation. Retries add no records. A rate limit followed by success writes one success record with the final status.
A record holds these fields:
| Field | Meaning | OpenTelemetry attribute |
|---|---|---|
schema_version |
Record layout version, 1 in this release. |
none |
timestamp |
Timezone-aware UTC time from the client clock when the call ended. | none |
outcome |
success, error or cancelled. |
none |
error_type |
Exception class name, or None on success. |
error.type |
status_code |
Final attempt's HTTP status, or None when no response arrived. |
none |
requested_model |
Effective requested model, including the adapter default. | gen_ai.request.model |
resolved_model |
Model the service reported; None unless the call succeeded. |
gen_ai.response.model |
questions |
Question id to question type name. | none |
answers |
Typed answers on success; None otherwise. |
none |
usage |
Token counts on success; None otherwise. |
gen_ai.usage.input_tokens, gen_ai.usage.output_tokens |
request_id |
Client-bound correlation from bind_request_id, or None. No service response identifier is read, because none has been observed. |
none |
state_fingerprint |
Keyed fingerprint of the state before redaction, or None without a fingerprint_key. See state fingerprint. |
none |
media_provenance |
Optional caller-built media fingerprints and declared revision identifiers; None by default. See src/judgevet/domain/audit.py and media provenance. |
none |
Source: https://opentelemetry.io/docs/specs/semconv/gen-ai/gen-ai-spans/.
The UTC timestamp follows the OWASP ASVS logging requirements.
Source: https://github.com/OWASP/ASVS/blob/master/5.0/en/0x25-V16-Security-Logging-and-Error-Handling.md.
The schema marker follows the CloudEvents specversion attribute.
Source: https://github.com/cloudevents/spec/blob/main/cloudevents/spec.md.
A record never holds the state, raw or redacted. It also never holds question instructions, criteria, credentials, headers or exception text. Source: https://cheatsheetseries.owasp.org/cheatsheets/Logging_Cheat_Sheet.html. Typed answers stay in the record. They are labels, booleans and probabilities, not generated prose. Question identifiers can contain customer data; choose identifiers that are safe to store. Probabilities are recorded as returned and are not claimed to be calibrated.
A sink failure never fails the call. The adapter catches an Exception from
the sink and returns the answer or re-raises the call's original error. It adds
the failure's class name as audit_error to the http.call event.
When logging is configured, the adapter also logs audit sink failed at error
level with the sink's class name and the traceback. The traceback can carry the
sink's own exception text. The record for that call is lost. This follows the OpenTelemetry error-handling
specification and the Python logging module.
Source: https://opentelemetry.io/docs/specs/otel/error-handling/.
Source: https://docs.python.org/3/library/logging.html#logging.Handler.handleError.
The async adapter calls the same synchronous method inline, so a slow sink
blocks the event loop. Async sinks are not supported. Every adapter that
receives the same sink writes to it; the sink owns its own thread safety.
CLI and MCP entry points do not set a sink. The offline fakes in
judgevet.testing accept the same audit= option and write one record per
call the same way, with status_code=None on success because no HTTP response
arrived.
JSONL sink
JsonlAuditSink(path, *, fsync=False) appends each record to one file as one
JSON line. Import it from judgevet. See the
module source.
Each line is one compact JSON object in UTF-8, ended by a newline. No byte
order mark is written.
Source: https://jsonlines.org/.
Source: https://github.com/ndjson/ndjson-spec.
Source: https://opentelemetry.io/docs/specs/otel/protocol/file-exporter/.
Keys follow the record's field order, and every line carries every field. A
None field is written as JSON null. Non-ASCII text is written as UTF-8,
not escaped.
timestampis RFC 3339 text fromdatetime.isoformat(), such as2026-09-30T12:00:00+00:00. Source: https://github.com/cloudevents/spec/blob/main/cloudevents/spec.md.- The OpenTelemetry logs data model uses epoch nanoseconds instead. This sink follows CloudEvents. Source: https://opentelemetry.io/docs/specs/otel/logs/data-model/.
answersuse the API wire shape, with atypekey per answer.- JSON object keys are strings. A Score answer's
legendandprobabilitiestherefore read back with string keys such as"1". usageis an object withinput_tokensandoutput_tokens.media_provenanceis an object; itsattachmentsare pairs of id and fingerprint.- The encoder refuses
NaNand infinity, because neither is valid JSON. Source: https://docs.python.org/3/library/json.html.
The constructor opens the file with O_WRONLY, O_CREAT and O_APPEND. The
sink requests mode 0o600 when it creates the file. The process umask can
only remove bits. The file is never wider than owner read and write. An
existing file keeps its mode.
Source: https://cheatsheetseries.owasp.org/cheatsheets/Logging_Cheat_Sheet.html.
A missing directory raises FileNotFoundError from the constructor, not on
the first call.
One sink is safe to share across threads. A lock serialises writes, and each
line reaches the file in one os.write call. On a local POSIX file,
O_APPEND makes the seek and the write one atomic step. That guarantee does
not hold on NFS with several appenders.
Source: https://man7.org/linux/man-pages/man2/open.2.html.
Python's FileHandler makes the same promise: thread-safe, not process-safe.
Source: https://docs.python.org/3/library/logging.handlers.html.
fsync=True flushes each line to storage before record returns. It is off
by default. Through the async adapter, each flush blocks the event loop.
Source: https://nvlpubs.nist.gov/nistpubs/legacy/sp/nistspecialpublication800-92.pdf.
close() closes the file once, and the context manager calls it. After
close(), record raises ValueError with the message audit sink is
closed. The sink swallows no error. The adapter catches it and reports
audit_error, as described above. Rotation and retention belong to the
caller.
Entry-point audit file
The CLI and MCP entry points read JEV_API__AUDIT_PATH. Unset installs no
sink, as with the spend cap. An empty value is a path, not an opt-out.
Each entry point opens one JsonlAuditSink on that path before any request.
It passes the sink as audit= and closes it after the adapter.
The judge command and the policy command each write one line per call.
One MCP server writes one line per tool call for its whole lifetime.
The directory must exist. The entry points do not set fsync or a
fingerprint_key.
A path that cannot be opened stops the entry point before any request.
The CLI exits 1 with Error: Cannot open the audit file set by JEV_API__AUDIT_PATH.
judgevet-mcp exits 1 with judgevet-mcp: audit sink failed (<TypeName>).
No record carries the key or the state. An explicit provider selection
bypasses this setting and installs no sink.
State fingerprint
Pass fingerprint_key= with audit= to link records of equal state without
storing the state. Each record then carries state_fingerprint. Without a key,
the field is None. The adapter computes this value:
HMAC-SHA-256(key, b"judgevet-state-v1\x00" + json.dumps(state, sort_keys=True, ensure_ascii=False, separators=(",", ":"), allow_nan=False).encode("utf-8"))
The value is 64 lowercase hex characters and is not truncated. The state is
the value passed to system_one, before any redaction.
The label and zero byte follow the labelled input of NIST SP 800-108.
Source: https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-108r1-upd1.pdf.
The serialization is compact sorted-key JSON as written, not RFC 8785; the v1
in the label versions it.
Source: https://www.rfc-editor.org/info/rfc8785/.
A string state and a list state never collide, because JSON quotes the string.
A state that is not JSON data, or that holds a non-finite number, raises
TypeError or ValueError and writes an error record. The fingerprint is
computed before the redactor runs, so the raw state must be JSON data even
when a redactor would remove the offending value.
The threat model has five parts:
- The caller holds the key. judgevet never logs the key, stores it on a record or puts it in an event or exception.
- The fingerprint must not reveal the state.
- Equal fingerprints under one key mean equal hashed bytes.
- The value is pseudonymous and reveals frequency: a common state produces a common fingerprint.
- A leaked key lets anyone confirm a guessed state.
Source: https://arxiv.org/pdf/1802.07975.
judgevet enforces no minimum key length. Use at least 32 bytes from
secrets.token_bytes(32). That length matches the 32-byte SHA-256 output.
RFC 2104 discourages an HMAC key shorter than the hash output.
Source: https://www.rfc-editor.org/rfc/rfc2104.
Rotating the key breaks linkage
between records written before and after the rotation. The offline fakes in
judgevet.testing accept the same fingerprint_key= option and compute the
same value.
Proxy and TLS configuration
Import NetworkConfig from judgevet and pass it as network= to either
adapter. Its defaults are proxy=None, ca_bundle=None, and verify=True.
The sync recipe shows explicit optional CA selection.
With no explicit proxy, HTTPX2 retains its standard HTTP_PROXY, HTTPS_PROXY,
ALL_PROXY, and NO_PROXY behavior. An explicit proxy selects routing even
when NO_PROXY would bypass an environment proxy. HTTPX2 often requires an
http:// proxy URL for an HTTPS destination; the destination TLS connection
then runs through a CONNECT tunnel. See HTTPX2 proxies
and environment variables.
By default, HTTPX2 verifies certificates and hostnames using OS roots through
truststore.
SSL_CERT_FILE or SSL_CERT_DIR can replace those roots. An explicit
ca_bundle takes precedence and creates a verifying Python SSL context.
It replaces the default roots; include every required root in that PEM file.
An empty path, unreadable file or malformed bundle fails adapter construction.
The adapter never disables verification after a CA loading error.
verify=False disables both chain and hostname checks. Use it only for
controlled tests with synthetic data. Supplying a CA bundle together with
verify=False raises ValueError. The default remains secure on the sync,
async, CLI, policy CLI and MCP paths. See HTTPX2 TLS configuration.
Containers use their own configured trust roots, not the host's store. Install the required CA roots in the image or select an explicit CA bundle. Caller-supplied transports own their own network and TLS behavior. Prefer the default transport when using these settings. Review transport and certificate limits before deployment. Changing settings does not reconfigure an existing client.
Credential sources
Settings construction reads configuration but does not open credential files or
run commands. settings.api.resolve_key() performs resolution when requested.
It returns a SecretStr, or None when no source is configured. The CLI,
policy CLI and MCP resolve once when constructing their adapter. Retries reuse
that credential. Reconstruct the adapter to pick up a rotated source.
The resolver selects the first available source in this order:
- An explicit argument to
resolve_key, including an explicit empty string. It is literal, even if it starts with!. - A nonempty configured key that does not start with
!. JEV_API__KEY_FILE, if configured.- A configured key starting with
!, interpreted as a command.
An empty configured key is absent. A selected source failure stops resolution;
it does not try lower-priority sources. Unselected files and commands perform
no IO. Existing key aliases retain their precedence. Direct HTTP adapter
api_key arguments remain literal and never execute commands.
For a mounted secret, set JEV_API__KEY_FILE to its path and leave literal
key variables unset. Relative paths use the process working directory.
Regular-file symlinks are supported; directories, pipes and devices are rejected.
File reads and command stdout accept at most 4096 bytes, including line endings.
The resolver removes trailing CR/LF and requires one printable ASCII token
without whitespace. Empty, oversized, malformed or multiline output fails with
ValueError. These are client input constraints, not a vendor key-format claim.
For a command, a value such as !op read 'op://vault/item/credential' names an
installed provider executable and its arguments. Command syntax uses
Python shlex tokenization
and direct argv execution.
Quotes group arguments. Pipes, redirection, substitutions and shell builtins
are not evaluated unless you explicitly name a shell executable. Executables
use the inherited PATH, environment and working directory. Use trusted,
noninteractive providers; stdin and stderr are discarded.
The default command deadline is five seconds after process creation. The resolver kills the command process group and reaps its direct child after success, failure or timeout. Cleanup allows one additional second for reaping. OS process creation itself has no portable deadline guarantee. Commands require POSIX process groups; unsupported platforms reject command resolution while literal and file sources remain available. Descendants that deliberately leave the process group are outside cleanup control. This feature is not a sandbox.
A nonzero exit, timeout, spawn failure or invalid output raises a generic
ValueError. Diagnostics omit command text and output. CLI source failures
produce a handled error; MCP uses its existing generic startup-failure path.
Keep the returned key wrapped until the adapter call expression, as in the
sync recipe. Resolution is synchronous; perform it
during startup before serving async requests. See
credential security for disclosure limits.
Gateway authentication and metadata
Both HTTP adapters accept gateway=GatewayConfig(...). Its defaults preserve
Authorization: Bearer <key>, direct TypeSafe routing and no custom metadata.
The gateway receives the supplied credential instead of a second TypeSafe key.
Gateway operators own upstream credential injection. See TypeSafe authentication.
auth_header chooses the credential field. auth_scheme chooses its token
prefix; an empty string sends a bare key. Apigee's example
uses x-apikey. Kong Key Auth
supports configured key names. Azure APIM
uses Ocp-Apim-Subscription-Key by default. These are vendor conventions,
not proof of compatibility with an untested deployment.
The following program uses a synthetic gateway URL and credential. Replace them with your approved destination and credential before calling a real gateway. Documentation checks run the exact program with a synthetic HTTP transport.
from judgevet import GatewayConfig, HTTPSystemOneAdapter, Noul, RequestMetadata
gateway = GatewayConfig(
auth_header="x-apikey",
auth_scheme="",
headers={"Example-Tenant": "synthetic", "Example-Route": "default"},
)
with HTTPSystemOneAdapter(
api_key="dummy-gateway-key",
base_url="https://gateway.example/organization/jev",
gateway=gateway,
) as adapter:
response = adapter.system_one(
state="Synthetic support ticket",
questions={"billing": Noul(instructions="Is this about billing?")},
metadata=RequestMetadata(headers={"example-route": "review"}),
)
print(response.answers["billing"])
Both trailing-slash forms of this base URL send to
/organization/jev/v1/systemone. URL path prefixes are separate from header
names and header values. Supply complete field names and values; no prefix
expansion occurs. Names compare case-insensitively. Per-call metadata replaces
matching defaults. Duplicates within one mapping are rejected. Configuration
and per-call maps are copied; later changes to the caller's dictionaries do not
affect requests. Each logical call snapshots merged fields once for every retry.
Concurrent calls do not change client-wide headers.
Names follow HTTP token syntax.
Values accept printable ASCII, including empty strings, without leading or
trailing spaces. Controls, tabs, DEL and non-ASCII fail with ValueError before
transmission. Local metadata limits are 32 fields, 128 bytes per name, 2048 bytes
per value and 8192 total name/value bytes after merging. These limits are
judgevet policy, not published vendor limits.
Metadata cannot set the selected authentication header, Authorization,
Proxy-Authorization, Host, Content-Type, Content-Length,
Content-Encoding, Transfer-Encoding, Connection, Keep-Alive, TE,
Trailer, Upgrade, Expect, Cookie or Set-Cookie. Authentication can use
Authorization or a custom token name, but cannot reuse another protected name
or traceparent, tracestate or baggage.
request_id_header forwards only the dedicated scoped request ID.
Without a binding it sends no field. It cannot use a protected or trace field,
and collision with explicit metadata raises ValueError. Arbitrary logging
context remains local. Explicit traceparent, tracestate and baggage values
are opaque caller text. Callers own their format and disclosure choices under
W3C Trace Context and
W3C Baggage. The client does not create spans,
extract context or claim tracing conformance.
CLI, policy CLI and MCP consume the gateway environment settings listed above.
For example, set JEV_API__AUTH_HEADER=x-apikey and JEV_API__AUTH_SCHEME to an
empty string. Supply JEV_API__HEADERS as a JSON string map. $NAME and
!command metadata values remain literal. Only existing credential sources
resolve files or commands. MCP configuration belongs to the host environment;
its tool schemas do not accept headers or authentication overrides.
Credentials and metadata reach the configured base URL on each attempt. Choosing another base URL changes their recipient. Redirects remain disabled, including same-origin redirects. A plain HTTP proxy can inspect fields. HTTPS CONNECT exposes the destination, and a TLS-terminating intermediary can inspect content. TLS verification defaults remain unchanged. Header values are omitted from configuration repr, validation messages and built-in events; arbitrary caller tracebacks remain outside that guarantee. Metadata is not a secret vault.
Gateway-owned errors use the existing status-based error mapping.
A gateway 429 remains retryable without TypeSafe JSON. Unknown JSON, HTML and
text error bodies are omitted from messages; recognized TypeSafe detail parsing
remains unchanged. A body without detail but with a nonempty string error
field adds that string to the message. Ollama documents that body shape; no
call here has observed it. See src/judgevet/adapters/outbound/http.py.
Source: https://docs.ollama.com/api/systemone.
Local loopback tests prove these client properties. They do
not verify a deployed gateway or unseen TypeSafe error bodies.
Caller-owned state redaction
Pass redactor= to either HTTP adapter to transform state before transmission.
Import the structural StateRedactor protocol from judgevet or
judgevet.ports. It defines one synchronous method,
redact(state: str | dict[str, Any] | list[Any]) -> str | dict[str, Any] | list[Any].
The client supplies no detection rules. The caller owns what to remove or retain.
See the protocol and
outbound preparation.
The following synthetic example replaces all state. It demonstrates the seam, not a useful judgment policy or a sensitive-data detector. Replace the callback, URL and credential with approved application choices for a real call. Documentation checks execute the exact program against an isolated wheel with synthetic HTTP responses.
from typing import Any
from judgevet import GatewayConfig, HTTPSystemOneAdapter, Noul, StateRedactor
class ReplaceState:
def redact(self, state: str | dict[str, Any] | list[Any]) -> str:
return "Caller-approved synthetic summary"
redactor: StateRedactor = ReplaceState()
with HTTPSystemOneAdapter(
api_key="dummy-gateway-key",
base_url="https://gateway.example/organization/jev",
gateway=GatewayConfig(auth_header="x-apikey", auth_scheme=""),
redactor=redactor,
) as adapter:
response = adapter.system_one(
state={"private_note": "Synthetic original content"},
questions={"billing": Noul(instructions="Is this about billing?")},
)
print(response.answers["billing"])
The adapter deep-copies ordinary JSON state before invoking the callback. Mutating that copy does not change caller-owned nested lists or dictionaries, including when the callback raises. The callback receives no questions, model, credentials or headers. Its return value must be a string, dictionary or list that can be serialized as finite JSON. Invalid output fails before transmission.
A configured redactor runs once per logical call, immediately before the shared outbound serializer. The adapter reuses the same immutable UTF-8 body on every retry. Changes to input or retained callback output after preparation cannot rewrite later attempts. Separate calls invoke the callback again. Copy failures, callback exceptions and serialization failures propagate before retry handling, with no request and no fallback to original state. Callback exceptions may carry sensitive content; arbitrary application tracebacks are not scrubbed.
The same synchronous method runs in async adapters. Keep it fast and nonblocking; async callbacks are not supported. Applications own synchronization for stateful callbacks shared across concurrent calls. Do not mutate inputs concurrently with preparation. A callback that changes external references or performs IO remains caller-owned code; copying is not a sandbox.
Omitting redactor, or passing None, preserves the existing state and HTTPX2
serialization path without copying. Gateway authentication, metadata and
correlation remain independent. State redaction does not redact question text
or header values. The egress contract
lists each transmitted channel and its limits.
CLI and MCP command entry points retain the default. There is no environment
setting, command option or MCP tool argument that loads Python redactor code.
An application can construct a redaction-enabled HTTP adapter and pass it to
run_cli or create_mcp_server through the existing judgment port.
Optional media provenance
JudgmentRecord.media_provenance defaults to None. Applications can call
judgevet.media_audit.media_provenance with an explicit nonempty bytes
fingerprint_key and attach the returned MediaProvenance to their existing
terminal record. Record schema version 1 remains unchanged. The helper emits
no audit events, observes no provider retries and estimates no usage.
The value contains ordered attachment IDs and keyed fingerprints, immutable question associations, a complete evidence fingerprint and a separate question snapshot fingerprint. It contains no image bytes or question text. Existing state fingerprints remain separate. Use opaque attachment IDs and revision identifiers; caller-supplied identifiers must not contain customer content.
Fingerprints use HMAC-SHA-256 with separate versioned labels for attachments, evidence and questions. Variable parts have eight-byte big-endian length prefixes. Evidence fingerprints cover exact bytes, MIME declarations, global image order, association order and required question IDs. Request fingerprints cover normalized question types, instructions and criteria. Invalid JSON snapshots fail without echoing their content.
Callers own key storage and rotation. Equal inputs under one key expose equality;
changing the key breaks that linkage. Optional prompt_revision,
provider_identity and preprocessing_revision are caller declarations and
default to None. They do not prove internal preprocessing. The existing
resolved_model field retains the provider-reported identity. Existing sink
failure containment, attempt claims and known-only usage settlement still apply.
Source: media provenance contract.