The tuning values on this page are configured in the in-app App Settings screen
(sidebar footer, sliders icon) and stored in app_settings.jsonc -- not in .env.
A value left in .env is ignored and named once in the startup log; run
chatwalaau settings migrate --write to carry an existing configuration across.
Feature enable-gates and secrets stay in .env.
See Configuration -> Application settings.
Models & Reasoning
Chat models are configured exclusively through the Model Offering Catalog
(model_offerings.jsonc). The legacy per-provider variables
(AZURE_OPENAI_MODELS, ANTHROPIC_MODELS, OPENAI_MODELS, FOUNDRY_MODELS,
MODEL_MAX_CONTEXT_TOKENS, ANTHROPIC_HOSTING, and the per-provider chat
endpoint/key variables) have been removed. The *_MODELS snippets shown
below in the per-provider sections describe what each offering needs -- put
those values on an offering in the catalog instead. The shared Azure credential
variables and OPENAI_API_KEY are retained.
Multi-model switching
Author several offerings in model_offerings.jsonc and switch between them
mid-conversation:
- A model selector appears above the chat input (hidden when only one model is configured)
- Per-session model selection persists across reloads
- Regenerate with a different model -- click the chevron on Regenerate
- Each assistant message shows which model generated it
- All models share the same Tools, Skills, and MCP integrations
Per-model context-window limits are set by each offering's context_window
field (defaulting to 128000 when unset). The context-window progress bar above
the input updates automatically when you switch models.
Model Offering Catalog
Every chat model is managed from a single JSONC file. Point
MODEL_OFFERINGS_FILE at it (default model_offerings.jsonc, resolved against
the working directory); it is the single source of truth for model routing.
A non-demo deployment with no catalog boots normally with a startup warning (so
the App Settings screen stays reachable to fix it); chat is unavailable and
returns a message pointing at chatwalaau models add / the App Settings screen
the moment you try it (DEMO_MODE=true runs without one).
Each offering describes one model:
{
"auth_profiles": { "gateway": { "api_key_env": "GATEWAY_API_KEY" } },
"offerings": [
// The default chat model (at least one chat offering is required).
{
"id": "gpt-5.2",
"provider": "azure-openai",
"model_ref": "gpt-5.2", // the real deployment name
"endpoint": "${AZURE_OPENAI_ENDPOINT}",
"default": true,
"context_window": 400000
},
// Claude via an Anthropic-compatible gateway (per-offering hosting).
{
"id": "claude-opus-4-8",
"provider": "anthropic",
"hosting": "foundry",
"model_ref": "claude-opus-4-8",
"base_url": "https://my-gateway.example.com/anthropic",
"auth_profile": "gateway"
},
// A non-OpenAI Foundry family: turn off the OpenAI reasoning controls.
{
"id": "deepseek-v4",
"provider": "foundry",
"model_ref": "my-deepseek-deployment",
"endpoint": "${FOUNDRY_PROJECT_ENDPOINT}",
"family": "bare"
},
// Optional: at most one embeddings, one image and one live offering.
{
"id": "embeddings",
"provider": "azure-openai",
"model_ref": "text-embedding-3-small",
"endpoint": "${AZURE_OPENAI_ENDPOINT}",
"operations": ["embeddings"]
}
]
}
Why it helps:
- Gateways. One endpoint that fronts several model families becomes several
offerings sharing an
endpoint/base_url-- no new provider to configure. - Mixed hostings. Direct Claude and Foundry-hosted Claude can coexist in one
instance (per-offering
hosting), which the single globalANTHROPIC_HOSTINGcould not express. - One required chat model, everything else optional. At least one
chatoffering is required;embeddings,imageandliveofferings are optional. With several chat models, thedefault: trueone (or the first) is used when no model is specified.
Rules of thumb:
- Secrets never live in the file. Reference an environment variable by name
(
api_key_env) or a namedauth_profilesentry, or rely on the shared Entra ID credential lanes.${VAR}inendpoint/base_urlis filled from the environment. familyoverrides the per-model options a gateway model advertises:openai-reasoning,anthropic-adaptive, orbare(no generation controls -- use this for non-reasoning gateway models). A family must match its provider:azure-openai/openai/foundrytakeopenai-reasoningorbare, andanthropictakesanthropic-adaptiveorbare; any other pairing is rejected at startup. Omitfamilyto let the provider infer it.- A malformed catalog fails startup fast with a message naming the offending offering, so a typo never silently drops your models.
DEMO_MODEalways uses the legacy lane. The legacy*_MODELSvariables are now deprecated in favor of the catalog (still supported when no catalog is present).- Catalog invariants (enforced by the CLI, the GUI, and the server): at least
one
chatoffering and exactly one default among them; unique ids; at most oneembeddings, oneimageand oneliveoffering; aliveoffering uses theazure-openaiprovider and serves no other operation;hostingapplies to theanthropicprovider only. Aliveoffering is the GPT-Live voice deployment that enables the Live conversation.
When a deployment cannot serve a feature
Some features are supplied by the model's deployment, and whether they work depends on how that deployment was created -- not on the model.
Claude on Microsoft Foundry: pick the right hosting option
Foundry offers two hosting options, chosen when you create the deployment. Choosing "Default settings" in the portal picks Hosted on Azure, which is the restricted one:
| Hosted on Azure | Hosted on Anthropic | |
|---|---|---|
| Where inference runs | Azure infrastructure | Anthropic infrastructure |
| Hosted web search | ❌ Not supported | ✅ Supported |
| Structured output | ❌ Not supported | ✅ Supported |
Requests that use an unsupported feature are rejected with 400 by design. The
symptom looks like this, and every turn fails:
web search not supported in your workspace
The fix is to recreate the deployment, not to change any setting in ChatWalaʻau:
- Foundry portal → model catalog → your Claude model → Deploy → Custom settings
- Expand Model version settings and pick the version labeled Hosted on Anthropic
- Note the deployment name and point the offering's
model_refat it
Inside one Foundry resource the endpoint URL and authentication are unchanged -- only the deployment name changes.
Anthropic publishes three web search tool versions. All three are server-side tools, so none of them works on a Hosted-on-Azure deployment, and the two newer ones also require code execution — which is unsupported there too.
If you must stay on a restricted deployment
Tell the offering what its deployment cannot serve, and ChatWalaʻau stops sending it. In App Settings, or directly in the file:
{
"id": "claude-opus-5-foundry",
"provider": "anthropic",
"model_ref": "claude-opus-5",
"hosting": "foundry",
"base_url": "https://my-aifoundry.services.ai.azure.com/anthropic",
"capabilities": {
"web_search": false,
"native_structured_output": false,
"mcp": false,
"skills": false,
"image_generation": false
}
}
The settings behave differently, and the difference matters:
| Setting | What "Not available" does |
|---|---|
| Web search | Removes the capability. There is no substitute. The web-search guidance is also removed from the system prompt, so the agent will not claim it can search — it will not invent citations. |
| Structured output | Turns the feature off for this model. A run-target that has an output schema configured falls back to a plain answer on it, and the built-in agent's card says so. There is no degraded path: the forced-tool-use fallback this project once shipped was never compatible with the agent framework and was removed in v0.125.0. |
| MCP | Removes every MCP tool for this model — both stdio and streamable-HTTP servers — and the MCP guidance with them. Your MCP servers keep running and other models keep their tools; this changes what this model is offered. |
| Skills | Removes Agent Skills for this model: no skill is advertised and load_skill / read_skill_resource / run_skill_script are absent. Skills Management and the skills on disk are untouched. |
| Image generation | Removes image_generate / image_edit for this chat model. The image offering itself, and other chat models, are unaffected. |
| Function calling | Shown as Always available and cannot be turned off — see below. |
Structured output and web search cannot share a turn
This is a provider restriction, not a ChatWalaʻau setting: the Responses API rejects a
request that carries both a JSON output format and the hosted web search tool
(Web Search cannot be used with JSON mode.). Rather than fail the turn, ChatWalaʻau
drops web search for any turn that asks for structured output.
You are told where it happens, so a search that stops working has a reason:
- the ChatWalaʻau Core card and the Custom agent editor both say "Web search is dropped while structured output is on" while a schema is set
- the Prompt Dump's tool surface lists
web_searchasEXCLUDEDwith the reasonstructured output
Turn structured output off -- clear the format on the Core card, or remove the
outputSchema from a Custom agent -- and web search comes back on the next turn.
Why Function calling cannot be turned off
MCP tools, the three skill tools and the two image tools all reach a provider as function tools. A model that could not call functions could not carry any of them, so a catalog able to say "function calling off, MCP on" would be describing something that cannot exist. Rather than validating that combination, ChatWalaʻau removes it: the row is visible so the whole vocabulary is in one place, and it is fixed at available.
Register only models that support function calling as chat offerings. Writing
"function_calling": false in the file is rejected at load with an error that says so.
A capability you do not write means enabled. An offering with no capabilities
block, a key you left on Default, an unknown model, no catalog at all — every one of
those resolves to "available". Only an explicit Not available on this deployment
withholds anything, which is why adding these settings changed nothing about how your
existing offerings behave.
Nothing disappears silently: a withheld tool appears in the prompt dump as excluded
with the reason provider capability, the Built-in agent card names what the selected
model withholds, and the server logs one line naming the offering.
The same rule covers the one absence that is nobody's setting. Hosted web search is
dropped whenever structured output is on, because the provider rejects the pair -- so
it is listed as excluded with the reason structured output, which points at the
run-target's own card rather than at this catalog.
Leave everything on Default unless a request actually fails because of it. Default means "available", which is how every offering behaved before these settings existed.
Register the deployment under the native foundry provider instead — its
platform-supplied web search is unaffected by the Anthropic restriction. The trade-off
is that generation options there are detected by deployment name, so a Claude
deployment shows none. Recreating it as Hosted on Anthropic is usually better.
The order models appear in
The order of the chat offerings in the file is the order the model selector shows. Put your most-used model first and it appears first.
The default: true flag decides only which model is preselected for a new chat.
It does not move that model to the top of the list -- a default sitting third in
the file appears third, and is still the one preselected.
Before v0.106.0 the default model was always hoisted to the top of the selector, regardless of where it sat in the file. If your default is not already first, the dropdown order will look different after upgrading -- the default now appears where it actually is. Nothing about which model answers has changed. To get the old look, drag it to the top in App Settings.
Authoring offerings (CLI and GUI)
You do not have to hand-write the JSONC. The catalog is operator-configurable two
ways, and both read and write the same MODEL_OFFERINGS_FILE, so you can mix and
match. list still reflects a running server; the authoring commands and the screen
below manage the file itself.
CLI (offline, no server required). The chatwalaau models commands read and
write the local catalog file, creating it if it does not exist:
# Interactive wizard: author and append one offering
$ chatwalaau models add
Provider [azure-openai / anthropic / openai / foundry]: anthropic
Model ref: claude-opus-4-8
Operation [chat / embeddings / image / live]: chat
Offering id: claude-opus-4-8
Default chat model? [y/N]: y
Base URL: https://api.anthropic.com
Hosting [direct / foundry]: direct
Family (optional):
Context window (optional): 200000
API key environment variable name: ANTHROPIC_API_KEY
Wrote model_offerings.jsonc (1 offering).
# Edit or remove an existing offering by id
$ chatwalaau models edit claude-opus-4-8
$ chatwalaau models remove claude-opus-4-8
# List the models a running server is serving (read-only, unchanged)
$ chatwalaau models list
The wizard prompts only for the name of the API-key environment variable
(api_key_env), never the secret value. chatwalaau init can also run this "set up
your first model" step right after it writes .env (skip it with
chatwalaau init --no-model, or in any non-interactive run), so you can configure a
model before or after init.
GUI (App Settings screen). Open the App Settings screen from the gear/sliders icon in the chat sidebar footer (next to the info icon). The left pane is a searchable settings list (with a refresh); the right pane is the Model Offering Catalog editor. Compose offerings grouped by operation -- Chat (at least one; exactly one default), Embeddings (0-1), Image (0-1), and Live (0-1). Each referenced environment variable shows a detected / not set indicator, so you can confirm the key is present without ever seeing its value. Save applies immediately via in-process hot reload -- no restart -- and a progress indicator shows while the change is applied.
Each offering shows as a compact row (id, provider, model, default) that expands on click when you want to edit it. Drag the grip handle to reorder -- that order is exactly what the chat model selector shows.
Saving from the GUI writes clean JSON, so hand-written comments in
model_offerings.jsonc are not preserved. (Named auth_profiles entries are
preserved.) CLI edits take effect the next time the server starts; GUI saves
hot-reload the running server in place. No new environment variable is introduced --
both paths use MODEL_OFFERINGS_FILE (default model_offerings.jsonc).
Task model assignments
Besides the chat models you pick in the selector, ChatWalaʻau runs a few background helper models -- to generate a chat's title, extract user memory, curate agent memory, summarize a Teams meeting, and turn a natural-language question into an ontology SPARQL query -- and, since v0.169.0, to run the tasks a Live conversation delegates. Each of these is assigned to one of your existing chat offerings, so it inherits that offering's provider, endpoint, and credentials automatically.
- GUI. In the App Settings screen, the Task model assignments section lists each task with a dropdown of your chat offerings plus Follow session / default.
- CLI.
chatwalaau models role listshows the tasks and current assignments;chatwalaau models role set <role> <offering-id>assigns one andchatwalaau models role clear <role>unsets it. - File. They live in an optional top-level
rolesblock inmodel_offerings.jsonc, e.g."roles": { "session_title": "gpt-4o-mini" }.
Leaving a task unassigned uses the chat's own model (then the catalog default), which
is the default behavior (for Live delegation, the selected agent's own model). The
role keys are session_title, user_memory_extraction, agent_memory_curation,
meeting_summary, ontology_nl, and live_delegation.
Since v0.109.0 these assignments replaced the dedicated SESSION_TITLE_MODEL,
USER_MEMORY_EXTRACTION_MODEL, AGENT_MEMORY_CURATION_MODEL,
TEAMS_MEETING_SUMMARY_MODEL, and ONTOLOGY_NL_MODEL environment variables, which
were removed. A leftover variable is ignored (with a startup advisory naming the
successor role).
Anthropic (Claude) provider
Claude models are offerings in the Model Offering Catalog
with "provider": "anthropic". They appear in the same selector as every other model
and can be picked per turn. Each offering chooses its own hosting, so direct and
Foundry-hosted Claude models can sit side by side.
Direct hosting (Anthropic public API) -- the key lives in an environment variable the offering names:
{
"id": "claude-opus-4-8",
"provider": "anthropic",
"hosting": "direct", // the default when omitted
"model_ref": "claude-opus-4-8",
"api_key_env": "ANTHROPIC_API_KEY" // or an auth_profile
// "base_url": "https://your-gateway.example.com" // optional proxy
}
Foundry hosting (Anthropic on Microsoft Foundry) -- the offering carries the full
Anthropic-on-Foundry URL in base_url:
{
"id": "claude-opus-5",
"provider": "anthropic",
"hosting": "foundry",
"model_ref": "claude-opus-5", // the Foundry deployment name
"base_url": "https://my-aifoundry.services.ai.azure.com/anthropic"
// Auth A -- API key: "api_key_env": "MY_FOUNDRY_KEY"
// Auth B -- Entra ID: omit api_key_env; the shared Azure credential is used
// (AZURE_CREDENTIAL_MODE = cli | managed-identity | default)
}
base_url is https://<resource>.services.ai.azure.com/anthropic -- built from the
Azure AI Services resource name, not the Foundry project URL
(.../api/projects/...). endpoint is not used on this hosting: since v0.163.0 an
offering that sets it, or that lacks base_url, is rejected when the catalog is saved
or loaded, with a message saying what to change. For Entra ID auth, do not point
api_key_env at a token -- it is sent verbatim as the api-key header and returns
HTTP 401.
ANTHROPIC_HOSTING, ANTHROPIC_MODELS, ANTHROPIC_FOUNDRY_RESOURCE,
ANTHROPIC_FOUNDRY_BASE_URL and ANTHROPIC_FOUNDRY_API_KEY are no longer read --
the catalog replaced them. Because the Anthropic libraries underneath still look
for the three ANTHROPIC_FOUNDRY_* names, ChatWalaʻau removes them from its own
process at startup (unless your catalog references one by name) and logs a warning
for each: delete them from .env.
Anthropic requires max_tokens on every request as a hard output cap; its floor is the
Anthropic max tokens setting in App Settings.
Hosted web search works out of the box for Claude (web_search_20250305). Every
other agent feature works on either provider as long as the model supports tool
calling. Speech-to-text, text-to-speech, image generation, and RAG embedding run
on their own dedicated Azure models, independent of the chat provider.
OpenAI (direct) provider
Enable models from OpenAI's own public API alongside Azure OpenAI and Anthropic --
they appear in the same selector and can be picked per turn. OpenAI is disabled by
default; leaving OPENAI_MODELS unset is a no-op.
Authentication is by API key only:
OPENAI_MODELS=gpt-5.1
OPENAI_API_KEY=sk-...
# OPENAI_BASE_URL=https://your-gateway.example.com # optional, OpenAI-compatible gateways
The current release supports reasoning models (for example gpt-5.x / o-series).
They behave exactly like Azure OpenAI reasoning models -- same reasoning-effort
control, hosted web search (country-scoped via WEB_SEARCH_COUNTRY),
and structured output. Non-reasoning models (gpt-4o / gpt-4.1)
are planned for a later release.
Microsoft Foundry provider
Enable reasoning models deployed in a Microsoft Foundry project alongside the
other providers -- they appear in the same selector and can be picked per turn.
Foundry is disabled by default; leaving FOUNDRY_MODELS unset is a no-op.
FOUNDRY_MODELS=gpt-5.1
FOUNDRY_PROJECT_ENDPOINT=https://<resource>.services.ai.azure.com/api/projects/<project>
Authentication is by Entra ID only, reusing the same Azure credential setup as
Azure OpenAI: AZURE_CREDENTIAL_MODE (cli via az login, managed-identity on
Azure-hosted compute, or default) plus the AZURE_TENANT_ID pin. There is no
API-key option -- Foundry project endpoints authenticate with Entra ID; grant the
signed-in identity a Foundry data-plane role (for example Azure AI User) on the
project resource. AZURE_OPENAI_API_KEY does not apply to this provider.
Any Foundry chat-model deployment can be listed, and the generation controls match the model family: OpenAI reasoning deployments (gpt-5.x / o-series names) expose the same reasoning-effort control as Azure OpenAI reasoning models, while other families (for example DeepSeek) show no generation controls -- requests to them stay free of parameters those models reject. Web search (with citations) and structured output work across families.
This provider ("provider": "foundry") serves models natively from a Foundry
project endpoint. It is separate from the Anthropic provider's Foundry
hosting ("provider": "anthropic", "hosting": "foundry"), which serves Claude
through the Anthropic route and takes a base_url instead.
Model ids must be unique across all providers (AZURE_OPENAI_MODELS,
ANTHROPIC_MODELS, OPENAI_MODELS, FOUNDRY_MODELS); a collision is rejected at
startup. The model selector lists Azure first, then Anthropic, then OpenAI, then
Foundry, and the default model is the first configured Azure model when any is set.
Generation options (set on the agent)
How hard a model reasons is part of the agent, not of each message. Every run-target fixes its own model and reasoning effort, and they reach the provider once, when the agent is built:
| Run-target | Where you set the model and effort |
|---|---|
| Built-in ChatWalaʻau Core agent | Declarative Agents & Workflows -> the Core card (model, reasoning effort, structured output). On a phone: the agent button above the message box (model + effort). |
| Custom Prompt agent | The agent's detail screen, or model.id / model.options.effort in its YAML |
| Harness agent | The harness detail screen, or model.id / model.options.effort in its YAML |
| Workflow agent node | The Prompt agent the node references |
The available effort levels and the default are served by the backend
(GET /api/model, the model_options catalog), so a surface renders only what the
selected model advertises and a model that accepts no generation options renders no
control. There is no environment variable for them; the Built-in agent's selection
is stored in application settings and applied without a restart.
The Built-in agent answers chat, Microsoft Teams, the CLI, the OpenAI-compatible API and the background lanes (chat titles, user / agent memory, meeting minutes, Ontology). Changing its model or effort changes all of them at once. The surfaces that change it say so before applying.
Because the default effort is xhigh, short background work -- a chat title above all
-- is generated at high effort unless you act. If that matters, select a Prompt agent
whose effort is medium, or set the Built-in agent to medium and raise it per agent
where it earns its cost.
Reasoning effort
Sets how hard the model reasons. One ladder for every reasoning provider:
| Provider | Levels | Default | Mechanism |
|---|---|---|---|
| Azure OpenAI (gpt-6 / gpt-5.x) | low, medium, high, xhigh, max | xhigh | reasoning.effort |
| OpenAI (direct, gpt-5.x / o-series) | low, medium, high, xhigh, max | xhigh | reasoning.effort |
| Microsoft Foundry (gpt-5.x / o-series deployments) | low, medium, high, xhigh, max | xhigh | reasoning.effort |
| Anthropic (Claude) | low, medium, high, xhigh, max | xhigh | adaptive thinking + output_config.effort |
A model whose catalog family is bare accepts no reasoning options, so no control is
offered for it.
What follows the effort automatically
Effort is the only generation option you choose. Everything else is derived from it, identically on both reasoning families:
| Derived from the effort | low | medium | high | xhigh | max |
|---|---|---|---|---|---|
Verbosity (OpenAI family, text.verbosity) | low | medium | high | high | high |
| Max output tokens (thinking and answer) | 16000 | 32000 | 48000 | 64000 | 96000 |
Fixed and not selectable: the OpenAI reasoning summary (detailed) and Anthropic's
thinking mode and display (adaptive, summarized; no budget_tokens).
Two consequences worth knowing:
- Verbosity is no longer independent. "Reason hard but answer briefly" cannot be expressed -- ask for brevity in the prompt instead.
loweffort caps the output at 16000 tokens. A very long single answer can stop there; raise the effort for long-form work.
Both supported model families are reasoning / adaptive-thinking models. Claude
rejects temperature, top_p and top_k with HTTP 400, and the OpenAI reasoning
models do not accept them either. The options catalog therefore does not offer sampling
parameters. The mechanism is general, though: a future non-reasoning model can advertise
temperature / top_p and the matching control appears automatically.
Prompt caching (input-token cost)
Every model call re-sends a large, stable prefix -- the system prompt plus the full tool schemas. On a long turn (especially a coding tool loop, where one message fans out into many sequential model calls) that prefix is billed again and again. Prompt caching marks the stable prefix as cacheable so it is billed once and re-read cheaply on the following calls. It is output-transparent: the model's replies are identical -- only billing and latency change.
It is on by default and provider-agnostic:
- Anthropic (Claude): the backend injects
cache_controlbreakpoints on the system block (which also covers the tool definitions) and the last few conversation messages, so in an agentic/coding tool loop the conversation tail -- tool results, file contents, reasoning -- re-reads at a large discount instead of being re-billed at full price on every model call. - Azure OpenAI: prompt caching is automatic for prefixes of ~1024 tokens or more; nothing to configure.
For very long tool loops, history compaction shifts the conversation prefix and
lowers the conversation cache hit-rate. If caching is your priority, raise
Compaction: message groups kept in App Settings -> Chat & session (keeps a
wider stable window) and/or set ANTHROPIC_PROMPT_CACHE_TTL=1h (survives
multi-minute pauses). Note the two compaction budgets are coupled by 2 x K < N,
so a larger tool-call budget needs a larger message budget as well.
# Master toggle (default true). Set false to disable caching entirely.
PROMPT_CACHE_ENABLED=true
# Anthropic cache lifetime: 5m (default) or 1h (extended cache). Unknown -> 5m.
ANTHROPIC_PROMPT_CACHE_TTL=5m
When the active provider reports cache usage, the per-message token readout
includes cache_read_input_tokens / cache_write_input_tokens, so you can see the
savings directly.
For the saving across a whole turn -- which is what matters on a coding turn, where
one request fans out into many model calls -- click the in / out counts under the
assistant message. The detail shows the turn's cumulative cache reads and writes
alongside the input that was billed at the full rate, so the ratio between them is
the caching benefit.
If credit usage still feels high on Claude, also lower the reasoning effort on
the agent that answers (above). The default is xhigh, which spends a lot of
output tokens thinking on every step; medium or low is plenty for many coding
edits -- and for short background work such as chat titles.
Structured output (JSON)
Constrain the assistant's answer to JSON -- optionally to a specific JSON Schema -- so the reply is machine-readable instead of prose. It works across all base providers (Azure OpenAI, Anthropic, and OpenAI) and is off unless an agent asks for it: when it is off, requests are unchanged.
Where it is configured. On the agent, not per message (since v0.165.0): the
Built-in ChatWalaʻau Core card in Declarative Agents & Workflows, or
outputSchema in a custom Prompt agent's YAML. While it is on, every answer comes
back as JSON, rendered as a copyable code block. Two modes:
- Generic JSON -- just toggle on; the model returns a well-formed JSON object.
- Explicit schema -- open the small editor (the
+schema/editbutton) and paste a JSON Schema. The answer is constrained to your schema. An empty or invalid schema falls back to generic JSON.
The controls are hidden for any model whose offering declares Structured output as
Not available, and the Core card names structured output among what that model
withholds -- so an absent control is never left looking like a feature this build does
not have. A run-target that already had a schema configured answers in plain text on
such a model; it is not silently presented as a structured answer.
The provider rejects a request that carries both, so ChatWalaʻau removes hosted web search for any turn that asks for JSON. The Core card, the Custom agent editor and the Prompt Dump all say so. See Structured output and web search cannot share a turn.
What "no schema" means depends on the model
Turning the toggle on without writing a schema falls back to a default schema, and that default is a property of the provider:
| Provider | Default when no schema is supplied |
|---|---|
| Azure OpenAI / OpenAI | Any JSON object ({"type": "object", "additionalProperties": true}) |
| Anthropic (Claude) | {"answer": "..."} -- an object with a single answer string |
The difference is not a ChatWalaʻau choice: Anthropic's structured outputs require
additionalProperties: false on every object, do not allow it to be omitted, and offer
no way to request free-form JSON, so "any JSON object" cannot be expressed there.
Rather than refuse the feature, those models fall back to a minimal valid shape -- and
the schema editor states the exact shape you will get, so it is never a surprise.
Supply your own schema whenever the shape matters. An explicit schema always overrides the default, on every provider.
A schema that satisfies the strict rules works on every provider:
{
"type": "object",
"properties": { "answer": { "type": "string" } },
"required": ["answer"],
"additionalProperties": false
}
Every array needs "items", every object needs "additionalProperties": false, and
every declared property must appear in "required". The editor lists any violation
with the path of the offending sub-schema before you send.
Web search and structured output can't be used together (the providers reject that combination), so while structured output is on, web search is skipped for that turn. Other tools (coding, weather, and so on) keep working.
Strict, native structured output (the default for the shipped models) guarantees the shape on the provider side, so a separate validation step is unnecessary. ChatWalaʻau still does a light, non-blocking check and shows a small JSON badge on the message -- amber if the output could not be parsed (for example a very long answer that was cut off). It never blocks or rewrites the answer.
Over the API. The OpenAI-compatible Responses API accepts the standard
text.format field, so your existing OpenAI SDK code works unchanged -- see
OpenAI-compatible API.
Reliability: transient provider errors
Model providers occasionally return a transient server error (HTTP 5xx) -- "The server had an error processing your request." In a coding session one question fans out into many sequential model calls, so the chance of hitting one at least once per turn is higher.
ChatWalaʻau handles this for you:
- Automatic retry before any output. If the error happens before the answer has started streaming, the backend resends the request automatically (a small, bounded number of attempts with a short backoff). This is safe because nothing has been shown or saved yet, so the model simply restarts the turn. While it retries, the chat shows a brief "Temporary server error -- retrying..." notice so the run reads as in progress, not stalled.
- No silent duplication. If the error happens after the answer has begun streaming, it is not retried (a retry could duplicate output). You get a clear "temporary server error -- please resend" message; just send again, or start a new chat if the conversation is very long.
A connection that dies mid-answer counts as retryable too (v0.143.1). If the provider accepts the request, starts streaming, and the connection is then closed before the answer finishes, that is a network interruption -- not a problem with your conversation. It follows the same rule as a transient server error: retried automatically if nothing had appeared yet, reported clearly if part of the answer was already on screen. Nothing is charged for an answer that never completed, and no tool runs twice. Before v0.143.1 this was reported as an internal error.
Errors that a retry would not fix are reported distinctly and are never retried:
| Condition | What you see | What to do |
|---|---|---|
| Out of credits / quota | A billing message naming the provider | Add credits / raise the quota, or switch to another configured model |
| Rate limited (429) | A rate-limit message | Wait a moment and retry, lower the reasoning effort, or raise the deployment quota |
| Transient 5xx after output | "temporary server error -- please resend" | Resend; start a new chat if the conversation is very long |
| Connection dropped mid-answer (v0.143.1) | "The connection to the model provider dropped part-way through the answer" | Resend. If it keeps happening at roughly the same point each time, ask your operator to check for an idle timeout on the path to the provider |