More
Settings by Mode - what actually applies where
Mode is not a property of a setting. Nothing in Bodega's settings store carries a code or chat field - mode is a request-scoped string derived from the session's type and threaded through one shared pipeline. So the honest answer to "does this setting apply in Chat Mode?" is decided by the code path that reads it, and for several settings that answer is the opposite of what the Settings panel's grouping suggests. This page lists the ones that surprise people, each with the file and line that decides it.
How to read this page
There are three ways a setting ends up mode-specific, and they are not equally trustworthy:
| Basis | What it means | How sure we are |
|---|---|---|
| Mode-gated | The read path contains an explicit mode === 'code' / 'chat' test. |
Certain - you can read the line. |
| Code-only in practice | Nothing in the read path mentions mode. The setting is reached only from code sessions because of what else has to be true (a panel is mounted, a write tool is available, a session was created as a code session). | Accurate today, but nothing enforces it. A future code path could reach it from chat and no test would fail. |
| Undetermined | Not audited. | Unknown - and the absence of a mode token proves nothing, because reachability-gating is invisible to exactly that search. |
The Settings panel shows a small badge on the nav entries that have been audited. No badge means "not audited", not "both modes".
QEL is not mode-gated (IterationFinalizer.ts:247 vs :327) - verification can run on a chat turn
This is the one that catches people, including us.
The QEL verification gate is IterationFinalizer.ts:247:
if ((isCreationVerify || isModOrFixVerify) && repairGateOpen && !input.isSidechat) {
There is no mode term in it. The gate is the contract classifier's verdict, plus a repair budget, plus a sidechat firewall. Eighty lines below, in the same function, auto-verify is mode-gated - IterationFinalizer.ts:327:
if (input.mode === 'code' && autoVerifyTaskEligible && input.projectPath && ...) {
Two adjacent gates in one function, one gated on mode and one not. The consequence: a Chat Mode turn whose contract is classified as creation, modification, or bug-fix enters the full QEL verification path, reading qel.contract_review, qel.semantic_judge, qel.execution_proofs, qel.modification_proofs, qel.code_review, qel.tsgo and qel.grader_model. The Settings panel presents QEL as one coherent feature in one section. It is not one thing.
What we cannot tell you: how often a chat turn is actually classified that way. That depends on the contract classifier's runtime behaviour and cannot be settled by reading code - it needs the gate instrumented with the mode and logged over real traffic, which has not been done. So treat the eight contract-hinged qel.* keys as: runs whenever the turn is classified as a build/fix task - usually, but not provably, Code Mode.
`qel.answer_judge` fires on the opposite condition to every other qel key
FinalResponseAssembly.ts:180:
if (!isGradeableTask && allToolCalls.length > 0 && finalContent && !params.signal?.aborted) {
Note the !. Every other qel.* key runs when the turn is a gradeable creation/modification/bug-fix task. The answer-grounding judge runs when it is not - a question-shaped turn that used tools. It runs in either mode. It is advisory: it appends at most one note and never blocks or repairs.
So the two halves of the QEL section are close to mutually exclusive on any given turn. Turning qel.answer_judge off does not quieten the rest of QEL, and turning the rest off does not quieten it.
Settings that are code-only in practice - and why that matters
These have no mode check anywhere in their read path. They are code-only because of what else has to be true. Filing them under "Code" as if a gate enforced it would be a claim no code implements:
qel.code_review- needs changed files, which needs write tools, which Chat Mode blocks atToolPreExecutionGuards.ts:147(if (mode === 'chat'), the non-native-tool-call guard) and at the tool-registration allowlistToolCallProcessor.ts:690-691. Code-only by tool availability.fleet.*andworktree.*- nomodetoken in their read path. They are code-only because fleet sessions are created as code sessions, not because anything checks.editor.*- UI settings (SettingDefaultsUi.ts:83-96) consumed by the code editor panel. Code-only by which panel is mounted.memory.auto_extract- one key, two read sites, different mode-relevance.MemoryExtractor.ts:202honours the off-switch with no mode test;MemoryExtractor.ts:206then returns early onmode !== 'code'for the decision-log writer only. The same toggle means different things four lines apart.
The risk here is real and has bitten this codebase before: IterationFinalizer.ts:230-236 records that the sidechat firewall had to be added explicitly, because "this verify() call is not a tool call and was previously ungated." Reachability is not a guarantee.
The rest of the surprising list
| # | Setting | What surprises | Where |
|---|---|---|---|
| 1 | llm.smart_auto vs llm.routing_rules |
Adjacent in the Routing section, opposite mode semantics. Smart Auto hard-returns null in chat; routing rules evaluate in both modes and take mode as a rule condition. |
IterationModelRouter.ts:211 vs :362 |
| 2 | llm.smart_auto |
Deliberately not project-overridable, while its neighbours llm.default_model / llm.code_model are. A .bodega/config.json setting it is silently ignored. |
ProjectConfigLoader.ts:18-27 (in-file rationale) |
| 3 | agent.dual_model |
A no-op in Chat Mode, with no marker in the UI. | ArchitectEditorCoordinator.ts:122 |
| 4 | skills.auto_activate |
Code-only. Slash-triggered skills are not - they work in both. One toggle, one section, two mode behaviours. | SkillExecutionCoordinator.ts:160 (auto) vs :150 (slash) |
| 5 | Project rules (.bodega-rules / CLAUDE.md) |
Weaker in Code Mode. Both modes inject the rules, but only chat gets the reinforcing "ACTIVE PROJECT CONTEXT" anchor sentence. The opposite of what anyone guesses. | ContextAssemblyService.ts:437,440 (both) vs :444-445 (chat only) |
| 6 | persona.system_prompt |
Honoured in both modes, but wrapped in materially different scaffolding per mode. Same custom prompt, different behaviour, no UI indication. | multiple prompt-assembly sites |
| 7 | browser.widened_enabled |
Read at tool registration - it gates preview_interaction's CORE-set membership and rewrites tool descriptions - and re-checked at dispatch, so turning it off mid-session does refuse the next widened navigation rather than waiting for the next turn. |
registration: ToolCallProcessor.ts:634 (read), :654 (CORE-set membership), :684 (description rewrite) · dispatch backstop: previewWidenedNavigate.ts:112-113 → browserAirGapGuard.checkBrowserWideningSettingEnabled (:103), ARCHITECTURE.md §12 layer 20 checkpoint (b) |
| 8 | The _isCodeMode parameter of ModeFirewall.shouldAllowTool |
Dead. preExecGates.ts:235 passes config.mode === 'code'; the firewall ignores it and says so in its own docstring. Reading the call site, you would conclude the firewall is mode-aware. It is not - the real split is the chat allowlist at ToolCallProcessor.ts:690-691. |
ModeFirewall.ts:258, :282 |
Deliberately not classified
loops, observatory, hooks, github, mcp, acp-agents, plugins and custom-agents were left unclassified rather than guessed. No mode token appears in their obvious read paths - but that is exactly the evidence that proves nothing, since reachability-gating is invisible to that search.
Frontend mode gates were also out of scope. If the renderer hides a control by session type, that is a third mode axis nobody has audited yet.
If you need a definitive answer for one of these, the honest method is to instrument the read site and watch real traffic - not to grep.
Mode is not the same axis as global-vs-project
Two different questions get confused with each other:
- "Does this setting apply in Chat Mode?" - this page.
- "Can a project's committed
.bodega/config.jsonchange it?" - a security boundary, covered on the Settings, Themes & Spending page under "What a project's committed config can and cannot change", and on the Workspace Trust page.
They are independent, and conflating them is dangerous in one specific direction: a setting being moved next to project-overridable settings in the UI is not a reason to make it project-overridable. The allowlist is append-only-with-review, independent of layout.
One behaviour worth repeating here because it interacts with routing: llm.routing_rules does not shadow your global rules - the loader concatenates project rules above them. Because rules are first-match, prepending is strictly more powerful than shadowing.
In the CLI
The bodega CLI reads the same settings through the same backend, so everything on this page applies there identically - the QEL gate, the routing split, the chat tool allowlist are all backend code shared by both clients. What the CLI does not yet have is this annotation in bodega config output; it prints values without saying which mode they reach. That is a known gap, not a difference in behaviour.
This page mirrors the in-app docs hub for app version 1.0.0-beta.35.1. Found something unclear or out of date? Tell us on Discord. New here? Download the free beta and follow along.