Providers and credentials
Recommended native provider
myopenai is an ordinary provider id. Declare its endpoint, native transport, models, and default models in zuno.json:
{
"model": "myopenai/primary-model",
"small_model": "myopenai/fast-model",
"provider": {
"myopenai": {
"name": "My OpenAI gateway",
"transport": "openai",
"surface": "responses",
"env": ["MYOPENAI_API_KEY"],
"options": {
"baseURL": "https://gateway.example.com/v1"
},
"headers": {"X-Tenant": "tenant-a"},
"models": {
"primary-model": {
"name": "Primary model",
"reasoning": true,
"tool_call": true,
"limit": {
"context": 200000,
"output": 32000
}
},
"fast-model": {
"name": "Fast model",
"tool_call": true,
"limit": {
"context": 128000,
"output": 16000
}
}
}
}
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
The checked starter file is examples/config/zuno.json. transport names a native Rust wire implementation; it is not the provider type, provider identity, authentication method, or concrete endpoint. surface selects responses, chat, or messages; an OpenAI-wire gateway should normally declare "transport": "openai", "surface": "responses". A model may override the provider default under models.<id>.provider.surface. Use openai-compatible only when a gateway's behavior differs from OpenAI. Neither transport loads npm packages, starts Node, or runs an AI SDK.
Provider-level headers are defaults for every configured model. A model may add or replace them under provider.<id>.models.<model>.headers; model-level headers win when the same name is present. A trusted runtime component may also attach request-local headers, which are applied last. This is the intended extension point for gateway routing, tenant, feature, and vendor-version headers, so those values do not need to be hard-coded in a Rust provider.
Do not use configurable headers to reproduce another product's OAuth identity or service authorization. Authorization, Content-Type, and Accept belong to provider authentication and transport framing; native OpenAI ChatGPT OAuth also owns its account and residency headers. Store bearer credentials through the provider login flow or declared environment variables instead.
First-run initialization
Zuno has no Node-based configuration generator. Initialize a checkout from the checked starter, edit the endpoint and model ids, then store the credential through the native CLI:
install -d -m 700 "${XDG_CONFIG_HOME:-$HOME/.config}/zuno"
install -m 600 examples/config/zuno.json "${XDG_CONFIG_HOME:-$HOME/.config}/zuno/zuno.json"
$EDITOR "${XDG_CONFIG_HOME:-$HOME/.config}/zuno/zuno.json"
printf '%s' "$MYOPENAI_API_KEY" | zuno providers login --provider myopenai
zuno debug config
zuno models myopenai --verbose2
3
4
5
6
When Zuno is installed without a source checkout, create the same zuno.json directly under the configuration root. Interactive API-key login disables terminal echo; piped login reads standard input. In either case, the key does not need to appear in shell history.
Login methods
zuno auth is an alias of zuno providers. List the methods implemented for one provider before logging in:
zuno auth methods openai
zuno auth methods myopenai2
In a terminal, a bare login opens a searchable provider picker. It includes the official OpenAI integration and configured providers whose resolved model route has a real credential consumer. This includes a configured Bedrock provider, whose stored key is used as an Amazon Bedrock bearer token. Catalog-only entries and historical credential ids are not login choices. The same picker can create an OpenAI Responses provider, one Amazon Bedrock provider, or an OpenAI-compatible Chat Completions provider when no matching configuration exists:
zuno auth loginUse the arrow keys or type to filter, then press Enter. If the selected provider has several authentication methods, Zuno opens a second picker for the method. Escape or Ctrl+C cancels either picker. Non-interactive invocations still require an explicit provider so scripts cannot hang on a prompt.
A URL login (zuno auth login https://gateway.example.com) runs a command chosen by that host and asks for confirmation first; see the providers login reference for the prompt and --trust-remote-command.
The built-in openai provider supports three methods:
# Select browser OAuth, device-code OAuth, or API key interactively.
zuno auth login openai
# ChatGPT Plus/Pro in the local browser.
zuno auth login openai --method chatgpt-browser
# ChatGPT Plus/Pro on a headless or remote host.
zuno auth login openai --method chatgpt-device
# OpenAI Platform API key.
printf '%s' "$OPENAI_API_KEY" | zuno auth login openai --method api-key2
3
4
5
6
7
8
9
10
11
A configured provider id such as myopenai receives only the API-key method when its resolved native transport consumes that credential:
printf '%s' "$MYOPENAI_API_KEY" | zuno auth login myopenaiAny configured provider with a bedrock, bedrock-mantle, or bedrock-runtime model route receives the bedrock-bearer-token method:
zuno auth methods amazon-bedrock
printf '%s' "$AWS_BEARER_TOKEN_BEDROCK" |
zuno auth login amazon-bedrock --method bedrock-bearer-token2
3
The command first explains the authentication priority. A bearer token from AWS_BEARER_TOKEN_BEDROCK or zuno auth login takes precedence over the AWS credential chain. Without a bearer token, Zuno uses the configured profile, access keys, IAM role, EKS IRSA/web identity, container credentials, or IMDS through the AWS SDK.
Interactive setup writes the generated provider to the global zuno.json before storing its credential. An arbitrary or credential-only id such as kiro-auth is still rejected before Zuno reads standard input or writes auth.json.
Using transport: "openai" does not grant a custom provider OpenAI's ChatGPT OAuth flow. The id openai owns that login, refresh protocol, ChatGPT endpoint rewrite, and account header. A custom OAuth provider needs its own registered login method and request-side consumer; an OAuth-shaped JSON object alone is not treated as a complete integration.
Credential storage
Credentials created by zuno auth login are stored by provider id in $XDG_DATA_HOME/zuno/auth.json (normally ~/.local/share/zuno/auth.json) with mode 0600 on Unix. ZUNO_AUTH_CONTENT can replace credential reads with a JSON object for ephemeral or managed environments. A credential file that was emptied by an interrupted write still reads, reporting the damage, and the next write replaces it; an entry Zuno cannot decode is preserved rather than deleted by an unrelated login.
Credential precedence is:
provider.<id>.options.apiKey, including an explicitly empty string;- the matching entry in
auth.json; - the first non-empty variable declared by
provider.<id>.env; - no credential.
Bedrock is intentionally more specific:
AWS_BEARER_TOKEN_BEDROCK;- non-empty
provider.<id>.options.apiKey; - the matching API credential stored by
zuno auth login; - the AWS SDK credential chain.
Only the exact bearer variable is treated as a bearer token. AWS_PROFILE, AWS_REGION, AWS_ACCESS_KEY_ID, AWS_WEB_IDENTITY_TOKEN_FILE, container credential variables, and IAM role metadata stay with the AWS SDK and are never copied into auth.json.
Putting apiKey in zuno.json is supported but exposes a secret to configuration backups and source control, so the credential store or an injected ZUNO_AUTH_CONTENT is preferable.
An environment key is consumed directly and is not copied into auth.json. This is why a provider can already be authenticated even when the user never ran a Zuno login command. zuno auth list prints the active credential kinds, storage path, and matching environment variable names without printing secret values. A stored credential with no current login-capable provider route is retained and labelled orphan so it can be removed with zuno auth logout.
ChatGPT OAuth stores an access token, refresh token, expiry, and account id in the same file. Before a request, Zuno refreshes a token that is near expiry and persists the rotated tokens unless credentials came from ZUNO_AUTH_CONTENT.
OpenAI API key versus ChatGPT OAuth
These are separate authentication products:
- An OpenAI Platform API key is sent to the configured OpenAI API endpoint as a bearer credential.
- ChatGPT OAuth signs into a ChatGPT subscription, requires the Responses surface, sends requests to the ChatGPT Codex backend, and includes the selected ChatGPT account id when available.
Selecting --method api-key never invokes ChatGPT login. Selecting either ChatGPT method never treats the resulting access token as a Platform API key.
Codex and Claude Code product subagents are a separate capability. They inherit the native command's existing login and never appear in zuno auth login; Zuno does not copy their credentials into auth.json or select their models. See Codex and Claude Code product agents.
How myopenai is called
The request path is native Rust:
zuno-configparses and mergesprovider.myopenai.zuno-llmresolves the model catalog and builds a typed providerSpec.zuno-cliresolves the explicitsurface; a custom OpenAI base URL is carried by the native compatible provider with OpenAI request semantics.- The provider builds Responses or Chat Completions JSON for that surface, applies request-local reasoning controls, then sends the request with
reqwest. zuno-llmparses SSE framing and the provider crate translates chunks into shared stream events consumed by the engine.
The openai-compatible transport is implemented separately by zuno-provider-compatible and defaults to /chat/completions; rule-driven compatible providers may select /responses. anthropic, Bedrock Converse, and the Google transports use separate native protocols. Bedrock Mantle and Bedrock Runtime reuse the same typed OpenAI Responses request builder and SSE decoder as the native OpenAI adapter, but authenticate with AWS credentials.
For foreground Responses requests, the engine supplies a private typed routing context containing the durable root or child session identity. The official OpenAI adapter and the compatible adapter used for a custom OpenAI baseURL project it as metadata.zuno_session_id; tool continuations reuse it, while title, summary, compaction, learning extraction, and Council calls are isolated. The field is reserved against extraBody and request-parameter overrides and is never copied into model input, instructions, headers, or tool definitions. Chat Completions and Messages surfaces ignore this routing context.
Supported configuration values are openai, openai-compatible, openrouter, anthropic, bedrock, bedrock-mantle, bedrock-runtime, google, google-vertex, and google-vertex-anthropic. Provider configuration has no npm field.
Important options include:
| Key | Meaning |
|---|---|
provider surface | Concrete API shape: responses, chat, or messages |
provider headers | Default extra HTTP headers for every model in the provider |
provider retry | Zuno-owned same-request retry policy; never forwarded upstream |
model headers | Per-model additions and overrides applied after provider headers |
baseURL or option endpoint | API base URL; option endpoint wins when both are set |
apiKey | config-local credential, preferred over the credential store |
timeout | whole-request timeout in milliseconds, or false |
headerTimeout | response-header timeout in milliseconds, or false |
chunkTimeout | maximum gap between streamed chunks in milliseconds |
maxTokens, temperature, topP, toolChoice | generation controls forwarded by the native provider |
toolCalls | Bedrock Converse only: true or false for every model this provider entry serves, overriding the built-in Converse tool-use table |
modelCapabilities | Bedrock Converse only: <model id>.tool_calls decides exactly that model id and nothing else |
responsesTextBlocks | Responses text projection: multiple by default, or single for gateways that expose only one upstream text field |
reasoningReplay | off by default, or encrypted for a Responses endpoint that seals reasoning |
reasoningReplayMaxAge | oldest sealed reasoning envelope Zuno still replays, in milliseconds |
extraBody | additional request fields after protected fields are assembled |
timeout, headerTimeout, and chunkTimeout are read only by the OpenAI-compatible transports, which default to a 330-second response-header deadline and a 120-second streamed-chunk idle allowance. The native openai, anthropic, google, and native Bedrock providers ignore all three. Each applies a fixed 330-second response-header deadline, no whole-request deadline, and the shared 300-second stream idle allowance that ZUNO_STREAM_IDLE_TIMEOUT_SECS overrides for every provider, so setting these keys on a native provider block does not change its deadlines.
Provider retry recovery is configured separately from transport timeouts:
{
"provider": {
"kiro-local": {
"retry": {
"max_attempts": 3,
"recovery_window_ms": 660000,
"initial_delay_ms": 2000,
"max_delay_ms": 30000,
"jitter_percent": 20
}
}
}
}2
3
4
5
6
7
8
9
10
11
12
13
max_attempts includes the initial call. That initial call remains governed by the transport. recovery_window_ms begins only after it returns a retryable failure, then bounds rollback, backoff, and all replacement attempts. Defaults are three attempts, a 180-second recovery window, 2-second initial delay, 30-second maximum delay, and 20 percent jitter. Retry settings are provider scoped, frozen with the resolved session model, and never enter SDK options or request JSON.
The main conversation, delegated turns, restored sessions and internal requests all retain this policy from the complete resolved model. It must not silently fall back to 180 seconds while internal requests use a configured value such as 660 seconds. Restart the relevant Zuno/ACP process after changing configuration. Retry deadline errors preserve the last captured, redacted HTTP status, provider code, request ID and reason when available; these are diagnostics, not permission to replay a side effect or an instruction to change accounts.
Compatible-provider diagnostics also retain the observed client phase: response_headers while waiting for HTTP headers, stream_idle when a local stream idle timer expires, request_budget when the local whole-request budget expires, or unknown when none of those boundaries is established. Wire error codes and early EOF do not prove an upstream execution stage. Transport body-read failures retain any received HTTP status and request ID, including HTTP 200; failures before headers leave those facts absent. Phase and bounded, redacted causes survive snapshotting without changing retry classification or deadlines.
Amazon Bedrock Responses and Converse
Zuno keeps the three AWS transports explicit:
| Transport | Registry factory | Endpoint | SigV4 service | Model id example |
|---|---|---|---|---|
bedrock-mantle | amazon-bedrock | https://bedrock-mantle.{region}.api.aws/openai/v1/responses | bedrock-mantle | openai.gpt-5.6-sol |
bedrock-runtime | amazon-bedrock-runtime | https://bedrock-runtime.{region}.amazonaws.com/openai/v1/responses | bedrock | global.openai.gpt-5.6-sol |
bedrock | amazon-bedrock-converse | https://bedrock-runtime.{region}.amazonaws.com/model/{model}/converse-stream | bedrock | us.anthropic.claude-opus-5 |
Mantle and Runtime always use the Responses surface and text/event-stream. They send store: false unless the provider explicitly overrides store, so Zuno's durable session remains the authoritative history. Converse sends the AWS JSON request body and decodes binary Amazon EventStream frames; the model id is carried only in the URI path.
The catalog resolves global.openai.*, us.openai.*, and us-gov.openai.* inference-profile ids to bedrock-runtime even when the upstream catalog supplies generic Bedrock package metadata. That protocol decision happens before model selection and provider construction, so these models never enter the Converse factory. A model whose metadata explicitly selects Mantle remains on bedrock-mantle. An explicit per-model override that routes one of these inference profiles to bedrock or bedrock-mantle fails before provider construction and names the required bedrock-runtime transport.
Amazon Bedrock API keys use Authorization: Bearer. When one is supplied by AWS_BEARER_TOKEN_BEDROCK, provider.<id>.options.apiKey, or zuno auth login, Zuno does not load or sign with AWS credentials. Without a bearer token, the request uses SigV4 and the AWS SDK credential chain. AWS documents the wire contract in Amazon Bedrock API keys.
The configured region wins over AWS_REGION, AWS_DEFAULT_REGION, and the profile's region. Zuno delegates credential resolution and SigV4 signing to aws-config and aws-sigv4, following the same structure as Codex's codex-aws-auth: a configured profile is resolved through the SDK's profile-only provider so ambient access-key variables cannot override it. Without one, the AWS SDK default chain owns environment credentials, shared profiles, credential_process, IAM Identity Center, web identity, container credentials, IMDS, refresh, and expiration.
This is a complete Mantle configuration using the same provider/model pair as Codex:
{
"model": "amazon-bedrock/openai.gpt-5.6-sol",
"provider": {
"amazon-bedrock": {
"name": "Amazon Bedrock Mantle",
"transport": "bedrock-mantle",
"surface": "responses",
"whitelist": ["openai.gpt-5.6-sol"],
"options": {
"profile": "us",
"region": "us-east-2",
"reasoningReplay": "encrypted"
},
"models": {
"openai.gpt-5.6-sol": {
"name": "GPT-5.6 Sol",
"reasoning": true,
"tool_call": true,
"temperature": false,
"limit": {"context": 272000, "output": 65536},
"variants": {
"low": {"reasoningEffort": "low"},
"medium": {"reasoningEffort": "medium"},
"high": {"reasoningEffort": "high"},
"xhigh": {"reasoningEffort": "xhigh"},
"max": {"reasoningEffort": "max"}
}
}
}
}
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
Mantle validates its published region set before opening a request. Runtime and Converse let AWS report regional or entitlement availability. An OpenAI GPT model sent to Converse is rejected locally with the two Responses provider choices, before credentials are resolved or a billable request is signed.
responsesTextBlocks: "single" is a compatibility declaration, not a model capability inferred from a provider id. It keeps Zuno's durable prompt parts typed, but joins their text projections with one blank line immediately before building the compatible Responses request. Inline images remain separate content blocks. Use it only when the target endpoint rejects more than one input_text block for a message; standards-compatible endpoints should keep the default multiple behavior. Do not use it with the 2026-08-28 kiro-provider build: that provider now concatenates consecutive all-text blocks byte-for-byte with no separator, while this option intentionally inserts a blank line.
Tool definitions and toolChoice
The tools a turn may call are locked by the turn loop, not by provider options, and every native provider sends that per-turn set. options.tools is for tools the endpoint runs on your behalf — Anthropic's web_search_20250305, the Vertex Anthropic equivalents — which the turn loop cannot express.
When a turn carries a locked set, a configured entry is kept only if it is such a server-run tool (recognised by carrying no input_schema) and its name is not already taken by a locked tool, because two entries sharing one name is itself a permanent 400. A configured custom declaration is superseded: tool dispatch is checked against the locked set, so a second, differing declaration of the same tool surface could only invite a call Zuno would refuse. When a turn carries no locked set — a title, summary, or compaction request — options.tools is sent unchanged.
Historical tool input is separate from the tools currently available for execution. Ordinary Agent requests preserve native history when the effective surface is known to be Responses, or when resolved options explicitly set reasoningReplay: "encrypted". Surface precedence is explicit ResolvedModel.surface > adapter-resolved Capabilities.default_surface > Spec.surface. With no explicit model surface, the Spec is a fallback only when the adapter still reports Default (unknown); it cannot override an already resolved adapter surface. If all remain Default, the surface stays unknown rather than being guessed. Without a model override, fixed compatible profiles ignore conflicting Spec declarations: xai stays Responses with Spec.surface = Chat, while openrouter stays Chat with Spec.surface = Responses. OpenAI resolves to Responses even when both model and Spec surfaces are Default. The Provider trait still has three methods and this capability declaration adds no user configuration. Existing calls and results retain their original content, call ids, raw argument strings, order, and boundaries even if a tool is missing, permissions are narrowed, a hook removes its declaration, or its schema changes structurally. They are not converted into inert text, and old Goal, Plan, or Todo state-tool interactions are not removed by AuthoritativeState history policy. The Responses rule does not require encrypted replay; reasoningReplay: "off" still sends no sealed reasoning.
Preserving history neither expands the current tool list nor restores permissions or re-executes old calls. Engine tool dispatch uses the same snapshot narrowed by prepare_request that the provider receives, not the wider pre-hook registry. New calls remain subject to that snapshot, schema validation, permissions, and risk gates. Other protocols retain the existing declaration fallback. Tool-free internal compaction keeps its own tool-independent projection: historical interactions become bounded explanatory text, without sealed reasoning.
toolChoice is sent only when the body actually carries the tool it names. {"type": "tool", "name": "…"} on the Anthropic and Vertex Anthropic surfaces, and a single-function restriction on Gemini, are permanent 400s when the named tool is absent from the request, so an unsatisfiable choice is dropped rather than turned into a failed turn. The model then chooses freely — on Gemini that is the AUTO mode applied when no toolConfig is sent — and can never be pushed at a different tool than the one you asked for.
Bedrock token accounting
Converse and Anthropic Invoke report disjoint input buckets: inputTokens / input_tokens is uncached input, alongside cache-read and cache-write tokens. The whole prompt is their sum. Bedrock OpenAI Responses uses a different contract: its cache details are already included in input_tokens.
Zuno preserves the provider counts and their accounting mode, then derives the latest whole prompt and cumulative disjoint usage for clients and budgets. Output includes reasoning on the provider wire; a reported thinking breakdown is split from visible output without adding it twice. An absent breakdown remains unreported, not estimated from the visible text.
The live TUI retains partial fields and replaces the current request's usage snapshot. Only a new request adds another charge; repeated usage events do not add the prompt or reasoning again. Retry rollback restores the prior snapshot, and durable usage replaces the live projection on resume.
Invoke usage in message_start and message_delta updates one request snapshot. Content-filtered responses retain reported usage and are terminal refusals, rather than empty answers to retry automatically. These corrections apply to new requests; existing stored receipts are not guessed or rewritten.
Bedrock tool definitions per model
Bedrock is one endpoint in front of every vendor's models, and Converse answers a toolConfig for a family that does not implement tool use with a ValidationException — a permanent failure, not a retryable one. Zuno therefore sends tool definitions only for the families Bedrock documents Converse tool use for: anthropic.claude (except anthropic.claude-v2 and anthropic.claude-instant), amazon.nova-micro, amazon.nova-lite, amazon.nova-pro, amazon.nova-premier, cohere.command-r, meta.llama3-1, meta.llama3-2, meta.llama3-3, meta.llama4, mistral.mistral-large, mistral.mistral-small, mistral.pixtral-large, and ai21.jamba-1-5. The match is a substring of the lowercased model id, so a cross-region prefix (us.anthropic.claude-…) and an inference-profile ARN carrying the model id resolve like the bare id.
Anything else — a provisioned-throughput ARN, a model newer than this build — is sent with no tool definitions, which is what earlier releases sent for every Bedrock model. Two options override that:
toolCalls: truesends tool definitions for every model this provider entry serves.toolCalls: falsesends them for none and also withdraws the provider's tool-call capability, so the turn loop stops assembling a tool set the wire will not carry.modelCapabilities: {"<model id>": {"tool_calls": true}}decides exactly that model id and leaves every other model totoolCallsor the table above. The key is the full model id as configured, in the same spelling the OpenAI-compatible provider already reads.
bedrock-mantle and bedrock-runtime post an OpenAI Responses body instead of a Converse body, so the Converse table does not describe them. Their per-turn locked tool snapshot is serialized as Responses function tools; a configured tool with the same name is superseded by the locked declaration.
Encrypted reasoning replay
reasoningReplay: "encrypted" declares that the endpoint seals reasoning. Zuno then adds include: ["reasoning.encrypted_content"] to every Responses request for that provider, persists each sealed reasoning item as its own durable part, and replays those items verbatim in the position the model produced them, each immediately before the output it explains. This is an endpoint capability, not a provider identity: the official Responses API and a loopback gateway declare it the same way.
Whether the option is honored depends on where the request lands. The catalog openai provider needs no declaration at all: its transport comes from the catalog and its surface stays the OpenAI adapter's default, which is Responses. A gateway that supplies its endpoint through options.baseURL or options.endpoint must declare both "transport": "openai" and "surface": "responses", because an omitted surface resolves to Chat Completions as soon as a provider option carries an endpoint, and openai-compatible resolves its surface from provider-id rules rather than from what you declared. Either of those would ask for sealed reasoning and never receive it, with nothing on the wire to say so.
Config validation rejects the routing it can prove wrong and names the offending key path: a transport other than openai, a surface other than responses, or a custom endpoint with no surface declared anywhere. It does not demand redundant declarations from a provider that already resolves to Responses. A models.<id>.provider.surface or .transport override is checked per model, since a model's own routing is what decides its requests, and so is a models.<id>.options.reasoningReplay.
A sealed envelope is bound to the model that minted it and expires upstream. Zuno replays one only to the same catalog provider and model, and only while it is newer than reasoningReplayMaxAge. Anything else leaves the provider request while the durable row keeps its ciphertext, so switching model mid-session degrades quality instead of failing the turn. Set the age limit to the endpoint's own validity window, for example 86400000 for 24 hours; omit it to replay every stored envelope. Title, summary, compaction, and every other auxiliary request run on a different model and never carry a sealed envelope.
Replay also has to match what the endpoint fingerprints. A tool call is replayed with the provider's own arguments bytes rather than a re-serialization of the parsed value, because key order and spacing are part of what was signed. A sealed item whose step produced no following output, such as an interrupted step or one that spent its whole output allowance on reasoning, is withheld instead of being sent alone, which a Responses endpoint refuses, and it is counted as withheld rather than as a replay. The item id is not echoed: a replayed item carries type, summary, encrypted_content, and status.
A dedicated sealed-history hash guard reuses the shared Responses maximal assistant-group boundaries to protect bound output, item order, and input boundaries across request hooks. A hook cannot inject or alter adjacent unsealed assistant content inside a protected group; unsealed ordinary prose outside it retains the existing behavior. Once eligible sealed replay is assembled, the guard also pins the request's model and surface across hooks. Request-parameter overrides of input, messages, or model are rejected before dispatch; they cannot replace the protected transcript or route through another path. Changes to the current tool list are not a reason to rewrite bound historical output. Zuno preserves that history without reopening tools, adding user settings or a database migration, or changing the original durable records.
If text_complete rewrites freshly completed provider-sealed output, Zuno restores the original text and returns a Hook error. The mismatched text is not persisted, and the error is not automatically retried. The sealed-history guard uses hash-only snapshots and never prints capsule contents.
This protection does not change existing replay eligibility or withholding rules. Provider/model matching, reasoningReplayMaxAge, reasoningReplay: "off", and withholding for ambiguous replay groups still apply, as does withholding a sealed item with no following output. Preserving historical tool interactions is not a promise to send every stored reasoning envelope.
For an error such as 400 reasoning_replay_context_mismatch, retain the original history and redacted diagnostics and investigate the replay content and binding. Blind retries, globally disabling or deleting reasoning, or switching accounts to bypass validation are not the recovery procedure. Kiro Provider v3.1.1 retains its replay-binding checks; existing valid Zuno Responses configurations need no new setting for this fix. The rejection does not become retryable under this policy. Successful recovery of a real session requires separate verification and cannot be inferred from configuration or version numbers.
Kiro v3.1.1 returns missing_tool_declaration when preserved history references a tool absent from the current tool list. Use the v3.1.2 historical-tool-scope fix, which separates historical validation and current tool authorization without a new Zuno configuration value. Old namespace/custom history without a provable alias binding still fails as missing_historical_tool_binding instead of guessing an identity. Do not expose denied tools again or delete sealed reasoning to bypass these checks; validate reduced-tool replay against the actual endpoint before reporting a real session as recovered.
Autonomous Goal continuations preserve the input boundary as well as the output order. Zuno stores a prompt-receipt reference on the first assistant row of the new turn and resolves the receipt's actual post-hook developer suffix into a structured ResponsesInputBoundary attached to the later request message. A shared Responses cursor emits those standard items between adjacent assistant responses for OpenAI, OpenAI-compatible, and Bedrock Mantle/Runtime requests. Chat Completions, Anthropic Messages, Bedrock Converse, and every request with an empty boundary keep their ordinary message projection. Older rows recover the receipt from their provider-request event; the stable runtime-policy prefix is removed before projection. This keeps each encrypted envelope paired with the output fingerprint of the one turn that minted it without inventing a user message or tool result.
An old export or compaction summary may have no recoverable receipt. In that case Zuno withholds the sealed capsules only from the ambiguous assistant-output group and keeps the rest of the transcript. A final provider-side validator refuses any malformed group left over and reports message indexes without including replay-token bytes.
The default off is a request that carries neither include nor any sealed item, including envelopes an earlier session stored while the option was encrypted. Native historical tool replay and assistant-item ordering apply to every Responses provider regardless of reasoningReplay: a turn that wrote text and then called a tool sends the text item before the function call. That ordering is what a sealing endpoint validates.
reasoningReplayMaxAge without reasoningReplay: "encrypted" is rejected at config time as well. Do not add reasoningSummary to a sealing endpoint, which rejects reasoning.summary.
The envelope is opaque provider ciphertext, and Zuno treats it as session content rather than as a secret it can redact: it is stored in the reasoning part's metadata.providerReasoning, returned by the HTTP messages endpoint, and forwarded on a stream event that carries it in full — spelled provider.reasoning.item to an SSE client and provider_reasoning_item in zuno run --json, with the ciphertext under encryptedContent in both. Zuno never sends it to a model other than the one that sealed it, and the session.provider.request event records counts only. Anyone who can read a session's messages or event stream can read its envelopes.
Each foreground request records reasoningReplay, replayedReasoningCapsules, withheldReasoningCapsules, restoredReasoningReplayBoundaries, and withheldAmbiguousReasoningCapsules on its session.provider.request event, which is how you confirm replay is actually happening. The replayed count is what reaches the wire; every envelope history offered that this request did not send is counted as withheld, whether another model minted it, it aged out, or nothing followed it. The restored-boundary count reports older assistant rows whose developer suffix came from their durable prompt receipt; no token or prompt text is copied into the event. The ambiguous count is the subset withheld because no exact boundary survived.
Model options.reasoningEffort is the default when neither the live session nor the selected agent chooses a level. On the Responses surface, options.reasoningSummary is lowered beside it as reasoning: { effort, summary }; Chat Completions receives only reasoning_effort because it has no reasoning-summary request field. Omit reasoningSummary for a compatible endpoint that rejects reasoning.summary; Zuno does not silently strip an explicitly requested control based on the provider id.
Run zuno models myopenai --verbose to inspect resolved models and zuno debug config to confirm the merged provider block without opening the credential file.
Network proxies
Zuno applies one process-wide environment proxy contract to every in-process outbound HTTP client used by a session: model providers, provider login and OAuth, remote MCP, model and skill catalogs, remote instruction files, webfetch, and web_search.
Set the standard variables before starting Zuno:
export HTTP_PROXY=http://127.0.0.1:1080
export HTTPS_PROXY=http://127.0.0.1:1080
export ALL_PROXY=socks5h://127.0.0.1:1080
export NO_PROXY=127.0.0.1,localhost,::1,.internal.example
zuno2
3
4
5
The lowercase aliases http_proxy, https_proxy, all_proxy, and no_proxy are also accepted. Scheme-specific variables take precedence over ALL_PROXY; NO_PROXY bypasses every configured proxy for matching destinations. HTTP, HTTPS CONNECT, SOCKS4, SOCKS5, and SOCKS5-with-proxy-DNS URLs are supported. Restart Zuno after changing these variables because connection pools capture the proxy policy when each client is constructed.
webfetch applies the same route without delegating target DNS authority to the proxy. For every original URL and redirect, Zuno resolves and validates all target addresses locally, then sends an already-validated IP through the selected HTTP, HTTPS, SOCKS4, or SOCKS5 route while retaining the original Host header and TLS SNI. If the configured proxy is unavailable, the request fails; it never silently retries direct.
Two environment variables select direct routing for a matching public target:
NO_PROXY(orno_proxy) bypasses the configured proxy for the destinations it matches.REQUEST_METHODdisables the proxy environment entirely. A CGI-style host maps an inboundProxy:request header ontoHTTP_PROXY(httpoxy), so whenREQUEST_METHODis present Zuno ignoresHTTP_PROXY,HTTPS_PROXY,ALL_PROXY, andNO_PROXYfor the whole process and routes every public request directly. UnsetREQUEST_METHODif you export it for unrelated reasons and still want proxy routing.
Some establishment failures are answers rather than weather, and webfetch fails them once instead of retrying on backoff: proxy CONNECT status 401, 403, 404, 407, 501, and any status outside 408, 429, and the remaining 5xx; SOCKS5 replies other than 0x01, 0x03, 0x04, 0x05, and 0x06; SOCKS4 replies other than 0x5b; and every certificate or protocol-level TLS rejection, including a plaintext captive-portal answer on port 443. A 403 or 404, and SOCKS5 0x02 or 0x08, name one destination, so the remaining validated addresses are still attempted -- but the denial still decides the request if none of them connects.
Establishment is bounded twice. One validated address may spend 10 seconds on TCP, proxy negotiation, and the TLS handshake; one hop's whole address walk -- the original URL, and again each redirect it follows -- may spend 30 seconds and at most 8 validated addresses, so a large DNS answer cannot extend it. Waiting for response headers is not part of that budget: the caller's own request timeout owns it. Every other Zuno HTTP client carries a 30-second connect default that an individual provider may tighten.
A walk those ceilings cut short is decided, not deferred. When the eight-address cap or the sequence budget stops the walk before any address accepted or denied the request, the request fails as a permanent transport error for that resolution -- gave up after N of M validated addresses, naming how much of the answer was walked -- and is not retried on backoff; a fresh request re-resolves the name and walks again. Earlier releases reported that truncation as a retryable timeout. The same DNS answer reproduces the same truncated walk, so retrying it would hand the length of the retry loop to whoever authors the answer.
Commands started by shell tools, formatters, language servers, and local MCP servers inherit the Zuno process environment. Their explicit environment configuration may override individual proxy variables.
Amazon Bedrock model requests continue through Zuno's shared route-aware HTTP client, so a region that is reachable only through a gateway needs no Bedrock-specific proxy option:
HTTPS_PROXY=http://127.0.0.1:1080 zunoFor a direct-versus-proxied comparison, add the resolved Bedrock hostname to NO_PROXY, for example bedrock-runtime.us-east-2.amazonaws.com or bedrock-mantle.us-east-2.api.aws. Credential discovery is owned by the AWS SDK rather than Zuno's model-request client. Static shared profiles need no credential network request; IAM Identity Center, STS, web identity, container, and IMDS behavior follows the selected AWS SDK release and its configuration.
The ownership and extension contract is specified in Provider authentication.