Loader for ~/.pixir/config.json (ADR 0005 ergonomics).
Parses user-global knobs, ignores malformed values with warnings (never hard-fails
on a bad field), and resolves effective values with this precedence for each key.
Known max reasoning intent is retained with a compatibility warning when its
Config model/backend cannot honor it; runtime validates the final overrides:
config :pixir, :key(programmatic override)~/.pixir/config.json- built-in default
Model id keeps the Provider chain: config :pixir, :model → PIXIR_MODEL →
"model" in config.json → built-in default.
Legacy keys (model, models, context_windows) remain supported alongside the
expanded surface (permission_default, reasoning.effort, text.verbosity,
bash_timeout_ms, bash_timeout_max_ms, host_commands, max_retries,
stream_idle_timeout_ms, presenter_idle_timeout_ms, skills.user_scope,
compaction.tail_events).
Pixir has three independent clocks: stream_idle_timeout_ms bounds silence within a
Provider transport stream; presenter_idle_timeout_ms bounds silence between Session
events in the CLI one-shot/resume Conversation.await path (ACP does not read this key);
and bash_timeout_ms caps each bash tool call. The presenter default deliberately exceeds
the bash default plus cleanup grace so a timed-out tool can return its recoverable result
before the presenter gives up. Agents may raise a bash call's timeout_ms as high as
bash_timeout_max_ms (600 seconds by default), above the presenter default. At an
expired CLI deadline, a live registered bash process extends the next wait only by the
smaller of the presenter interval and its remaining effective timeout plus
kill-escalation grace; dead, closed, and over-cap process entries do not extend it.
bash_timeout_max_ms is an override cap, not a way to shorten the configured
default. The effective cap is never lower than bash_timeout_ms; when config asks
for a lower cap, load/1 reports a warning and raises the effective cap so the
default bash command remains executable.
compaction.native is a late-bound overlay preference (nil = no preference,
false = off, true = request on). Invalid values warn and stay nil so they
cannot force the overlay off.
Summary
Functions
Whether model-assisted compaction is enabled (default false).
Native OpenAI compaction overlay preference.
Anthropic models list from config.json only, or nil when absent/invalid.
Context-window overrides from config.json (model => positive integer).
Effective model when config.json is present; application and env precedence still applies.
Models list from config.json only, or nil when absent/invalid.
Resolved host-command boundary limits.
Load and resolve config.json.
Merge config defaults into Provider opts without clobbering explicit values.
UTC timestamp written by the last explicit model-catalog refresh, if present.
Resolved permission default (:auto, :ask, or :read_only).
Resolved presenter idle timeout in milliseconds.
Resolved reasoning effort, or nil to omit and let the model default.
Resolve one immutable model/backend snapshot from exactly one source-document read.
Resolve only the explicit Responses backend descriptor from one request snapshot.
Whether Skills discovery includes the user scope (default true).
Resolved text verbosity, or nil to omit and let the model default.
Known reasoning effort intent ids (legacy list API); not a model/backend capability grant.
Resolved hosted web search preference.
Types
Functions
@spec bash_timeout_max_ms(keyword()) :: pos_integer()
@spec bash_timeout_ms(keyword()) :: pos_integer()
Whether model-assisted compaction is enabled (default false).
Native OpenAI compaction overlay preference.
Absence (nil) means no preference; after Provider/backend resolve the
overlay defaults on for chatgpt_codex / API-key OpenAI. Explicit false
always wins. Explicit true still refuses open_responses and Anthropic.
Invalid values are ignored with a warning and stored as nil.
@spec compaction_tail_events(keyword()) :: pos_integer()
Anthropic models list from config.json only, or nil when absent/invalid.
@spec file_context_windows(keyword()) :: %{required(String.t()) => pos_integer()}
Context-window overrides from config.json (model => positive integer).
Effective model when config.json is present; application and env precedence still applies.
Models list from config.json only, or nil when absent/invalid.
@spec host_commands(keyword()) :: {:ok, %{required(String.t()) => non_neg_integer() | pos_integer()}}
Resolved host-command boundary limits.
@spec load(keyword()) :: load_result()
Load and resolve config.json.
Returns a JSON-serializable map with "path", "present", "effective", and
"warnings". Missing files yield defaults and an empty warning list.
@spec max_retries(keyword()) :: non_neg_integer()
Merge config defaults into Provider opts without clobbering explicit values.
UTC timestamp written by the last explicit model-catalog refresh, if present.
@spec permission_default(keyword()) :: Permissions.mode()
Resolved permission default (:auto, :ask, or :read_only).
@spec presenter_idle_timeout_ms(keyword()) :: pos_integer()
Resolved presenter idle timeout in milliseconds.
Pixir has three independent clocks: stream_idle_timeout_ms bounds silence within a
Provider transport stream; this key is the idle deadline between Session events in the
CLI one-shot/resume Conversation.await path (ACP does not read it); and
bash_timeout_ms caps each bash tool call. The 300-second default deliberately exceeds
the bash default plus cleanup grace so a timed-out tool returns before the CLI gives up.
Agents may raise a bash call's timeout_ms as high as bash_timeout_max_ms (600 seconds
by default), above this default. At each expired presenter deadline, a live registered
bash process extends the next wait only by the smaller of the presenter interval and
its remaining effective timeout plus kill-escalation grace; a dead, closed, or over-cap
process does not extend it.
Resolved reasoning effort, or nil to omit and let the model default.
Resolve one immutable model/backend snapshot from exactly one source-document read.
@spec responses_backend(keyword()) :: {:ok, :absent | Pixir.Providers.ResponsesBackend.t()} | {:error, map()}
Resolve only the explicit Responses backend descriptor from one request snapshot.
Whether Skills discovery includes the user scope (default true).
@spec stream_idle_timeout_ms(keyword()) :: non_neg_integer()
Resolved text verbosity, or nil to omit and let the model default.
@spec valid_reasoning_efforts() :: [String.t()]
Known reasoning effort intent ids (legacy list API); not a model/backend capability grant.
Resolved hosted web search preference.
This is not the runtime default. Absence (nil) means no preference; the
Provider/backend seam decides later. An explicit disable is false. A map
is an explicit enable (and may carry search policy fields). Invalid values
are ignored with a warning and stored as false so they cannot default-on.