Skip to content

Settings: two toggles that never saved, and the surfaces around them (v0.21.270) - #73

Merged
QR-Madness merged 1 commit into
masterfrom
feat/settings-surfaces
Aug 8, 2026
Merged

Settings: two toggles that never saved, and the surfaces around them (v0.21.270)#73
QR-Madness merged 1 commit into
masterfrom
feat/settings-surfaces

Conversation

@QR-Madness

Copy link
Copy Markdown
Owner

Wave 3.6. Starts with a live bug the refit found, then the write-up. 178 of 210 settings documented.

Two settings toggles did nothing

Settings → Images & Audio → "Enable audio input" and "Enable speech generation" never persisted. Confirmed against a running server before fixing:

POST /api/config/update  {"audio": {"input_enabled": false}}
→ {"status": "ok", "message": "Config updated and applied", "updated": []}
→ value unchanged on re-read

The screen POSTs {"audio": {...}}; no section named audio was declared, so config_update dropped it and reported success. The client toasted "saved" and the toggle reverted on reload.

The impact is real. Both flags are read — audio.input_enabled gates audio attachments (views.py:2946,4366), audio.speech_enabled gates the generate_speech tool (internal_tools.py:1328) — and both default true. So audio input and agent speech could not be turned off.

There was also no top-level audio root in DEFAULT_CONFIG at all; the read sites carried literal defaults. Declared properly now, back-compat safe.

The general form, made observable

This is the Images-bug shape for the third time. ADR-17 stops a declared section from silently dropping keys — nothing stopped a client writing to a root nobody declared.

An undeclared section is now named back to the caller as ignored and logged with the remedy. Dropping it is still correct (undeclared means read-only by design), but it announces itself instead of waiting to be found by hand:

POST {"images": {...}, "totally_made_up": {"x": 1}}
→ {"updated": ["images.enabled"], "ignored": ["totally_made_up"]}

Three regression tests: the audio round-trip, the ignored-report, and that a clean payload carries no ignored key.

The write-up — 26 keys

Ambassador (18) leads with the invariant that makes a read-broad agent safe: its belt executes no write. Reads run automatically; anything that would change state comes back as a proposal you confirm — including dispatch, which lands as your user turn, which is what keeps the audit trail honest about who started the work.

The aide swarm is explained as map-reduce — aides each condense one conversation, the Ambassador reduces — which is why a cross-conversation question is affordable rather than requiring every transcript in one prompt. Its eight tuning keys are documented because they're writable, not because they need attention.

The three voice keys are documented as what they are: fallbacks below the per-profile settings, which is why this screen has no control for them.

Images & Audio (8) distinguishes pictures out (generation) from pictures in (vision), and explains why the avatar model is a separate setting — avatars are generated rarely and looked at constantly. The shipped avatar template's circular safe zone is called out, because a rewrite that drops it produces clipped avatars.

Two field-kit items

  • PromptField takes the manifest contract — the last primitive without it. It needed the least: it already owns a reset that knows the shipped text and a default-vs-yours diff, so it takes binding alone (anchor + help) and keeps its own reset. The call site passes binding explicitly rather than spreading, so the manifest reset can't displace it.
  • vision.refeed_recent_turns gained a control beside the vision toggle it belongs with.

Verification

  • Backend suite 1246 OK (3 new); task test:sterile 1246 OK
  • Client 499 tests; tsc clean; ruff check api/ clean; pyright baseline 0
  • task docs:check green, zero warnings; production build OK
  • Live walk: toggled the previously-broken switch through the real UI — it persisted, survived a reload, showed as changed-from-default, and reset cleanly back to shipped. Ambassador 5 anchors, Images & Audio 8, all with help. Fresh tab, zero console errors.

Assisted-by: Opus 5

…them (v0.21.270)

**Settings → Images & Audio had two switches that did nothing.** *Enable audio
input* and *Enable speech generation* POSTed `{"audio": {...}}`; no section
named `audio` was declared, so `config_update` dropped it and answered
`{"status": "ok", … "updated": []}`. The client toasted success and the value
reverted on reload. Both flags are genuinely read — `audio.input_enabled` gates
attachments (views.py), `audio.speech_enabled` gates the `generate_speech` tool
— and both default true, so the user-visible effect was that audio input and
agent speech could not be turned off at all.

This is the Images-bug shape a third time. ADR-17 stops a *declared* section
from silently dropping keys; nothing stopped a client writing to a root nobody
declared. So alongside the fix, the general case is now observable: an
undeclared section is named back to the caller as `ignored` and logged with the
remedy. Dropping it is still correct — undeclared means read-only by design —
but it announces itself now instead of waiting to be found by hand.

Verified against a running server: the write persists, survives a reload, shows
as changed-from-default, and resets cleanly; an unknown root reports
`ignored: ["totally_made_up"]` rather than vanishing.

With that fixed, the write-up: 26 keys across Ambassador and Images & Audio.
The Ambassador's help leads with the invariant that makes a read-broad agent
safe — its belt executes no write; reads run automatically and anything that
would change state comes back as a proposal — and explains the aide swarm as
map-reduce (aides condense one conversation each; the Ambassador reduces),
which is why a cross-conversation question is affordable at all. The voice
settings are documented as what they are: fallbacks below the per-profile ones,
which is why this screen has no control for them.

`PromptField` takes the manifest contract — the last field-kit primitive
without it. It needed the least: it already owns a reset that knows the shipped
*text* and a default-vs-yours diff, so it takes `binding` alone (anchor + help)
and keeps its own reset. `vision.refeed_recent_turns` gained a control beside
the vision toggle it belongs with.

178 of 210 settings documented.

Assisted-by: Opus 5
@vercel

vercel Bot commented Aug 7, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
agentx-docs-site Ready Ready Preview Aug 7, 2026 9:25pm

@QR-Madness
QR-Madness merged commit 35a17bd into master Aug 8, 2026
4 checks passed
@QR-Madness
QR-Madness deleted the feat/settings-surfaces branch August 8, 2026 02:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant