feat(compaction): add fallbackModel to retry compaction on quota/rate-limit errors#33396
feat(compaction): add fallbackModel to retry compaction on quota/rate-limit errors#33396iamcobolt wants to merge 16 commits intoopenclaw:mainfrom
Conversation
session model's thinking level. On channels with strict reply windows (Discord 30s, Telegram 240s), extended thinking during compaction can race against the channel timeout and trigger a stale-response loop (see openclaw#25272). Disabling thinking for the summarization turn eliminates this race without affecting the primary conversation model. New config key `agents.defaults.compaction.thinking` allows opting in: { "agents": { "defaults": { "compaction": { "thinking": "low" } } } } When thinking is enabled and a compaction run times out, the runner retries once without thinking automatically. The before_compaction hook is guarded to fire exactly once per compaction event (not once per attempt), keeping plugin integrations consistent.
Greptile SummaryThis PR adds Key changes:
Confidence Score: 4/5
Last reviewed commit: 8f68d36 |
| const prior = await sanitizeSessionHistory({ | ||
| messages: session.messages, | ||
| modelApi: model.api, | ||
| modelId, | ||
| provider, | ||
| allowedToolNames, | ||
| config: params.config, | ||
| sessionManager, | ||
| sessionId: params.sessionId, | ||
| policy: transcriptPolicy, | ||
| }); |
There was a problem hiding this comment.
sanitizeSessionHistory uses stale original model identity on fallback
model.api, modelId, and provider are closed over from the outer scope (lines 265-266, 282) and always refer to the original (primary) model. When a fallback candidate from a different provider is being used (e.g., falling back from anthropic/claude-sonnet-4-6 to openai/gpt-4o-mini), sanitizeSessionHistory still receives the original provider's API type and identifiers. Because this function applies provider-specific message sanitization logic (e.g., filtering tool-result blocks that are illegal for a given model API), using the wrong modelApi/provider/modelId can produce incorrectly sanitized messages that the fallback model then rejects or silently mishandles.
The current candidate's values should be passed instead:
| const prior = await sanitizeSessionHistory({ | |
| messages: session.messages, | |
| modelApi: model.api, | |
| modelId, | |
| provider, | |
| allowedToolNames, | |
| config: params.config, | |
| sessionManager, | |
| sessionId: params.sessionId, | |
| policy: transcriptPolicy, | |
| }); | |
| const prior = await sanitizeSessionHistory({ | |
| messages: session.messages, | |
| modelApi: currentModel.api, | |
| modelId: cand.model, | |
| provider: cand.provider, | |
| allowedToolNames, | |
| config: params.config, | |
| sessionManager, | |
| sessionId: params.sessionId, | |
| policy: transcriptPolicy, | |
| }); |
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agents/pi-embedded-runner/compact.ts
Line: 663-673
Comment:
`sanitizeSessionHistory` uses stale original model identity on fallback
`model.api`, `modelId`, and `provider` are closed over from the outer scope (lines 265-266, 282) and always refer to the *original* (primary) model. When a fallback candidate from a different provider is being used (e.g., falling back from `anthropic/claude-sonnet-4-6` to `openai/gpt-4o-mini`), `sanitizeSessionHistory` still receives the original provider's API type and identifiers. Because this function applies provider-specific message sanitization logic (e.g., filtering tool-result blocks that are illegal for a given model API), using the wrong `modelApi`/`provider`/`modelId` can produce incorrectly sanitized messages that the fallback model then rejects or silently mishandles.
The current candidate's values should be passed instead:
```suggestion
const prior = await sanitizeSessionHistory({
messages: session.messages,
modelApi: currentModel.api,
modelId: cand.model,
provider: cand.provider,
allowedToolNames,
config: params.config,
sessionManager,
sessionId: params.sessionId,
policy: transcriptPolicy,
});
```
How can I resolve this? If you propose a fix, please make it concise.There was a problem hiding this comment.
Fixed in commit fcc5f17 — sanitizeSessionHistory now uses currentModel.api, cand.model, and cand.provider so sanitization uses the active fallback's provider rules.
| log.debug( | ||
| `[compaction-diag] start runId=${runId} sessionKey=${params.sessionKey ?? params.sessionId} ` + | ||
| `diagId=${diagId} trigger=${trigger} provider=${provider}/${modelId} ` + | ||
| `attempt=${attempt} maxAttempts=${maxAttempts} ` + | ||
| `pre.messages=${preMetrics.messages} pre.historyTextChars=${preMetrics.historyTextChars} ` + | ||
| `pre.toolResultChars=${preMetrics.toolResultChars} pre.estTokens=${preMetrics.estTokens ?? "unknown"}`, | ||
| ); |
There was a problem hiding this comment.
Diagnostic log reports original model instead of active fallback candidate
The provider and modelId logged on line 731 are the original session values (lines 265–266) and remain unchanged when a fallback model is active. When a fallback fires (line 576–840), the log line will incorrectly attribute the compaction run to the primary model, making it difficult to correlate actual execution with logged diagnostics.
Replace provider with cand.provider and modelId with cand.model in the diagnostic log template on line 731 to reflect the model actually being used.
Note: The same issue appears on line 785 in the end diagnostic log—update that as well.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agents/pi-embedded-runner/compact.ts
Line: 729-735
Comment:
Diagnostic log reports original model instead of active fallback candidate
The `provider` and `modelId` logged on line 731 are the original session values (lines 265–266) and remain unchanged when a fallback model is active. When a fallback fires (line 576–840), the log line will incorrectly attribute the compaction run to the primary model, making it difficult to correlate actual execution with logged diagnostics.
Replace `provider` with `cand.provider` and `modelId` with `cand.model` in the diagnostic log template on line 731 to reflect the model actually being used.
Note: The same issue appears on line 785 in the end diagnostic log—update that as well.
How can I resolve this? If you propose a fix, please make it concise.There was a problem hiding this comment.
Fixed in commit fcc5f17 — both [compaction-diag] start and end log lines inside the outer loop now use cand.provider/cand.model. The fail() log before the loop intentionally keeps provider/modelId since no candidate is active at that point.
Additional Comments (1)
Prompt To Fix With AIThis is a comment left during a code review.
Path: src/agents/pi-embedded-runner/compact.ts
Line: 547-551
Comment:
`transcriptPolicy` not re-resolved for fallback model candidates
`transcriptPolicy` is computed once from the original `model.api`, `provider`, and `modelId` (lines 547–551), then reused across all iterations of the outer model-fallback loop (line 576–840). The policy controls `validateGeminiTurns`, `validateAnthropicTurns`, `repairToolUseResultPairing`, and other provider-specific behaviour. When a fallback candidate belongs to a different provider (e.g., the primary is `anthropic` and the fallback is `openai`), the provider-specific turn validation will never run with the correct rules — because `validateGeminiTurns` or `validateAnthropicTurns` were computed for the wrong provider. Validation mismatches can silently pass malformed message sequences to the fallback model.
`transcriptPolicy` should be re-derived inside the outer loop using `currentModel.api`, `cand.provider`, and `cand.model` — at minimum for `candidateIdx > 0`:
```suggestion
for (let candidateIdx = 0; candidateIdx < allCandidates.length; candidateIdx++) {
// ... [model switching code] ...
// Re-resolve transcript policy per candidate
const transcriptPolicy = resolveTranscriptPolicy({
modelApi: currentModel.api,
provider: cand.provider,
modelId: cand.model,
});
```
How can I resolve this? If you propose a fix, please make it concise. |
… per fallback candidate; fix diag log model identity
|
Thanks for the thorough review — all three issues were valid. Fixed in the follow-up commit (
|
|
Tracked in discussion #33431 — split out from #33005, which originally proposed a general |
|
@greptile-apps review |
| // Explicit "provider/model" string | ||
| const resolved = resolveModelRefFromString({ | ||
| raw: fallbackModel, | ||
| defaultProvider, | ||
| aliasIndex, | ||
| }); | ||
| return resolved && !isCurrent(resolved.ref) ? [resolved.ref] : []; |
There was a problem hiding this comment.
Silent failure when explicit fallbackModel string can't be resolved
If a user sets fallbackModel to a string that resolveModelRefFromString can't parse (e.g. a bare model name like "claude-haiku-4-5" that isn't in the alias index, or a typo like "anthropic/claude-haiku4-5"), resolved is null and the function silently returns []. The user gets no fallback and no diagnostic that their configuration is being ignored — they'll only notice when they hit a real quota event and nothing retries.
Consider emitting a log.warn here, or alternatively inside compact.ts after building allCandidates, when fallbackModel is configured but the candidates list ends up empty:
// Explicit "provider/model" string
const resolved = resolveModelRefFromString({
raw: fallbackModel,
defaultProvider,
aliasIndex,
});
if (!resolved) {
// Optionally: consumers should warn, but a note here aids debugging
return [];
}
return !isCurrent(resolved.ref) ? [resolved.ref] : [];At minimum, the test suite would benefit from an additional case — e.g. fallbackModel: "not-a-valid-model-string" — to document and pin this silent-no-op behaviour.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agents/pi-embedded-runner/compaction-fallback.ts
Line: 56-62
Comment:
**Silent failure when explicit `fallbackModel` string can't be resolved**
If a user sets `fallbackModel` to a string that `resolveModelRefFromString` can't parse (e.g. a bare model name like `"claude-haiku-4-5"` that isn't in the alias index, or a typo like `"anthropic/claude-haiku4-5"`), `resolved` is `null` and the function silently returns `[]`. The user gets no fallback and no diagnostic that their configuration is being ignored — they'll only notice when they hit a real quota event and nothing retries.
Consider emitting a `log.warn` here, or alternatively inside `compact.ts` after building `allCandidates`, when `fallbackModel` is configured but the candidates list ends up empty:
```ts
// Explicit "provider/model" string
const resolved = resolveModelRefFromString({
raw: fallbackModel,
defaultProvider,
aliasIndex,
});
if (!resolved) {
// Optionally: consumers should warn, but a note here aids debugging
return [];
}
return !isCurrent(resolved.ref) ? [resolved.ref] : [];
```
At minimum, the test suite would benefit from an additional case — e.g. `fallbackModel: "not-a-valid-model-string"` — to document and pin this silent-no-op behaviour.
How can I resolve this? If you propose a fix, please make it concise.There was a problem hiding this comment.
Fixed in commit fce6f25 — added an optional warn callback to resolveCompactionFallbackCandidates. When an explicit string can't be resolved, it calls warn() with a message explaining the expected format. The call site in compact.ts passes log.warn so the misconfiguration is visible in the gateway log at startup.
| .union([z.literal("strict"), z.literal("off"), z.literal("custom")]) | ||
| .optional(), | ||
| identifierInstructions: z.string().optional(), | ||
| fallbackModel: z.string().optional(), |
There was a problem hiding this comment.
Missing .describe() on fallbackModel schema field
The sibling thinking field (lines 87–100) includes a .describe(...) string that documents its semantics and allowed values. fallbackModel is validated as a plain z.string().optional() with no description, which means generated JSON Schema / config-validation output won't explain the three accepted values ("off", "fallback", or "provider/model"). For consistency and discoverability, add a .describe():
| fallbackModel: z.string().optional(), | |
| fallbackModel: z.string().optional().describe( | |
| '"off" (default) disables fallback. "fallback" uses the agents.defaults.model.fallbacks chain. An explicit "provider/model" string targets a specific model.', | |
| ), |
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/config/zod-schema.agent-defaults.ts
Line: 109
Comment:
**Missing `.describe()` on `fallbackModel` schema field**
The sibling `thinking` field (lines 87–100) includes a `.describe(...)` string that documents its semantics and allowed values. `fallbackModel` is validated as a plain `z.string().optional()` with no description, which means generated JSON Schema / config-validation output won't explain the three accepted values (`"off"`, `"fallback"`, or `"provider/model"`). For consistency and discoverability, add a `.describe()`:
```suggestion
fallbackModel: z.string().optional().describe(
'"off" (default) disables fallback. "fallback" uses the agents.defaults.model.fallbacks chain. An explicit "provider/model" string targets a specific model.',
),
```
How can I resolve this? If you propose a fix, please make it concise.Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
There was a problem hiding this comment.
Fixed in commit fce6f25 — added .describe() to fallbackModel documenting the three accepted values ("off", "fallback", "provider/model") and their semantics, consistent with the sibling thinking field.
…en fallbackModel placeholder
… off/fallback; remove dead UI code
|
Latest push — schema simplification + UI cleanup:
|
|
@greptile-apps review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0c3fa46b39
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const nextKeyInfo = await getApiKeyForModel({ | ||
| model: nextModel, | ||
| cfg: params.config, | ||
| agentDir, | ||
| }); |
There was a problem hiding this comment.
Preserve authProfileId when resolving fallback model credentials
The primary compaction model resolves credentials with profileId: params.authProfileId, but the fallback path drops profileId when calling getApiKeyForModel. In sessions that rely on an auth-profile override (for example, provider-scoped profile keys), fallback candidates can be skipped or fail auth even though valid profile credentials exist, so the new quota fallback behavior silently does not work in those runs.
Useful? React with 👍 / 👎.
| fallbackModel: z | ||
| .union([z.literal("off"), z.literal("fallback")]) | ||
| .optional() |
There was a problem hiding this comment.
Accept explicit model refs in fallbackModel config schema
This schema only allows "off" | "fallback", but resolveCompactionFallbackCandidates includes an explicit "provider/model" branch (and tests for it), so users configuring an explicit fallback model will hit config validation errors instead of getting fallback behavior. That makes one supported fallback mode unreachable through normal validated config loading.
Useful? React with 👍 / 👎.
| // Explicit "provider/model" string | ||
| const resolved = resolveModelRefFromString({ | ||
| raw: fallbackModel, | ||
| defaultProvider, | ||
| aliasIndex, | ||
| }); | ||
| if (!resolved) { | ||
| params.warn?.( | ||
| `[compaction] fallbackModel "${fallbackModel}" could not be resolved — expected "off", "fallback", or a "provider/model" string; no fallback will be used`, | ||
| ); | ||
| return []; | ||
| } | ||
| return !isCurrent(resolved.ref) ? [resolved.ref] : []; |
There was a problem hiding this comment.
The explicit "provider/model" string handling at lines 58–70 is unreachable dead code. The TypeScript type (line 298 of types.agent-defaults.ts) and Zod schema (lines 104–105 of zod-schema.agent-defaults.ts) have been narrowed to only allow "off" | "fallback". Any explicit string like "openai/gpt-4o-mini" will be rejected at config-validation time and never reach this function.
The commit f077c9b5f ("narrow fallbackModel to off/fallback") narrowed the schema but did not remove this dead code path. The function documentation (lines 12–17) still mentions returning candidates "for 'fallback' or an explicit string", which is now misleading.
Either remove this dead branch (and the corresponding tests at compaction-fallback.test.ts:76–92), or widen the type/schema to actually support explicit strings if the feature is intended for future use.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agents/pi-embedded-runner/compaction-fallback.ts
Line: 58-70
Comment:
The explicit `"provider/model"` string handling at lines 58–70 is unreachable dead code. The TypeScript type (line 298 of `types.agent-defaults.ts`) and Zod schema (lines 104–105 of `zod-schema.agent-defaults.ts`) have been narrowed to only allow `"off" | "fallback"`. Any explicit string like `"openai/gpt-4o-mini"` will be rejected at config-validation time and never reach this function.
The commit `f077c9b5f` ("narrow fallbackModel to off/fallback") narrowed the schema but did not remove this dead code path. The function documentation (lines 12–17) still mentions returning candidates "for 'fallback' or an explicit string", which is now misleading.
Either remove this dead branch (and the corresponding tests at compaction-fallback.test.ts:76–92), or widen the type/schema to actually support explicit strings if the feature is intended for future use.
How can I resolve this? If you propose a fix, please make it concise.| it("explicit provider/model string returns that candidate", () => { | ||
| const cfg = { | ||
| agents: { defaults: { compaction: { fallbackModel: "openai/gpt-4o-mini" } } }, | ||
| } as unknown as OpenClawConfig; | ||
| expect(resolveCompactionFallbackCandidates({ cfg, ...current })).toEqual([ | ||
| { provider: "openai", model: "gpt-4o-mini" }, | ||
| ]); | ||
| }); | ||
|
|
||
| it("explicit string matching the current model returns []", () => { | ||
| const cfg = { | ||
| agents: { | ||
| defaults: { compaction: { fallbackModel: "anthropic/claude-sonnet-4-6" } }, | ||
| }, | ||
| } as unknown as OpenClawConfig; | ||
| expect(resolveCompactionFallbackCandidates({ cfg, ...current })).toEqual([]); | ||
| }); |
There was a problem hiding this comment.
These test cases exercise an explicit "provider/model" string path that cannot be reached in production due to schema validation constraints. The Zod schema only allows "off" or "fallback", rejecting any explicit string before it reaches resolveCompactionFallbackCandidates().
These tests use as unknown as OpenClawConfig to bypass the type system and inject values the schema forbids. They pass at the unit level but give false confidence — there is no production path where a user's explicit string config survives validation and executes this code.
Remove these tests if the explicit-string option is not intended to be exposed, or widen the type/schema to make the feature actually available.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agents/pi-embedded-runner/compaction-fallback.test.ts
Line: 76-92
Comment:
These test cases exercise an explicit `"provider/model"` string path that cannot be reached in production due to schema validation constraints. The Zod schema only allows `"off"` or `"fallback"`, rejecting any explicit string before it reaches `resolveCompactionFallbackCandidates()`.
These tests use `as unknown as OpenClawConfig` to bypass the type system and inject values the schema forbids. They pass at the unit level but give false confidence — there is no production path where a user's explicit string config survives validation and executes this code.
Remove these tests if the explicit-string option is not intended to be exposed, or widen the type/schema to make the feature actually available.
How can I resolve this? If you propose a fix, please make it concise.There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 253c80847b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (nextKeyInfo.apiKey) { | ||
| nextAuth.setRuntimeApiKey(cand.provider, nextKeyInfo.apiKey); | ||
| } |
There was a problem hiding this comment.
Exchange Copilot token before applying fallback credentials
In the fallback-candidate auth path, nextKeyInfo.apiKey is written directly to runtime auth storage, but GitHub Copilot requires exchanging the GitHub token for a short-lived Copilot API token first (the primary path in this same function does that). If a quota error triggers fallback to a github-copilot/* model, compaction will retry with the wrong credential type and fail authentication instead of recovering.
Useful? React with 👍 / 👎.
| tools: builtInTools, | ||
| customTools, |
There was a problem hiding this comment.
Rebuild provider-sanitized tools for each fallback candidate
The fallback loop reuses the same builtInTools/customTools for every candidate, but those tools are prepared once using the original provider. When fallback switches providers (notably to google-gemini-cli), Gemini-specific schema sanitization is not re-applied, so cross-provider fallback can fail with tool-schema validation errors instead of succeeding after a quota/rate-limit retry.
Useful? React with 👍 / 👎.
…pt to diag logs; redesign PR screenshots
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8f68d364ab
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| config: params.config, | ||
| // Outer loop: model fallback on quota/rate-limit errors. | ||
| // Inner loop: thinking retry on timeout (at most 2 attempts per candidate). | ||
| for (let candidateIdx = 0; candidateIdx < allCandidates.length; candidateIdx++) { |
There was a problem hiding this comment.
Increase lock budget for nested compaction retry loops
This new outer candidateIdx loop plus inner thinkAttempt loop allows a single compaction request to run multiple full compactWithSafetyTimeout calls (300s each), but the lock budget is still computed from a single timeout window (resolveSessionLockMaxHoldFromTimeout), so long fallback/thinking-retry runs can exceed the hold limit and trigger watchdog force-release while this function is still mutating the same session via SessionManager. In sessions that hit repeated timeouts or multiple quota fallbacks, that can permit concurrent writers on the same transcript and corrupt ordering/state; the lock hold time should be scaled to worst-case retries (or refreshed between attempts).
Useful? React with 👍 / 👎.
|
Addressed the unreachable code concern in commit 8f68d36: Dead explicit-string branch removed — Dead tests removed — the two test cases covering the unreachable explicit-string path (
@greptile-apps review |
…isclosure requirements" This reverts commit 51238c0.
|
Superseded by #34569 — reissued as a clean single commit on a proper feature branch ( |
Summary
Companion to #33296, implementing the second half of the compaction override work proposed in #33005.
Adds
agents.defaults.compaction.fallbackModel— an opt-in knob that retries compaction with a different model when the primary fails due to quota or rate-limit errors (HTTP 429 / 402 billing). Auth errors and timeouts use their own retry mechanisms and are unaffected.Two states:
"off"(default) — no change in behaviour"fallback"— tries each model in theagents.defaults.model.fallbackschain in order, skipping the current modelNew file
compaction-fallback.ts+ 8 unit tests. Can be merged before or after #33296.Schema
agents.defaults.compaction.fallbackModel:"off"(default) |"fallback"Renders as a two-button segmented control in the config UI, with a help tooltip explaining the retry semantics.
UI — Before / After
Before — 3-button composite with conditional free-text input (
off · fallback · custom+ text field when custom is selected):After — clean 2-button segmented control with help tooltip:
Change Type (select all)
Scope (select all touched areas)
Linked Issue/PR
Key Changes
compaction-fallback.ts(new) —resolveCompactionFallbackCandidates()resolves the ordered candidate list from config; filters out the current model to avoid a no-op retrycompaction-fallback.test.ts(new) — 8 unit tests covering allresolveCompactionFallbackCandidatesstatescompact.ts— outer model-fallback loop wrapping the thinking-retry inner loop;transcriptPolicyandsanitizeSessionHistoryre-resolved per candidate (provider-specific flags correct for cross-provider fallbacks);before_compactionhook guarded byhookFired(fires exactly once per compaction event); rebuilds extension factories + resource loader per candidatecompaction-overrides.ts—resolveCompactionThinkLevelupdated to acceptsessionThinkLevelparam;"on"inherits the session model's level at call timetypes.agent-defaults.ts—fallbackModel?: "off" | "fallback";thinking?: "off" | "on"zod-schema.agent-defaults.ts— both fields narrowed to their 2-option union with.describe()schema.help.ts— help tooltip forfallbackModelschema.labels.ts— UI labels forthinkingandfallbackModeldocs/gateway/configuration-reference.md+CHANGELOG.md— updatedNotes for Reviewers
rate_limit/billingerrors with remaining candidates and falls through to the next; after exhausting all candidates the last quota error is re-thrown.before_compactionhook: guarded byhookFiredso it fires exactly once per compaction event regardless of model or thinking retry depth.transcriptPolicyandsanitizeSessionHistoryre-resolved per candidate inside the outer loop — provider-specific flags (validateGeminiTurns,validateAnthropicTurns, etc.) are correct for cross-provider fallbacks. (Addresses Greptile's transcriptPolicy review comment; resolved in follow-up commitfcc5f17.)Security Impact
Repro + Verification
Steps
agents.defaults.compaction.fallbackModel: "fallback"with a fallbacks chain in configExpected
Compaction retries on the first available fallback model and completes successfully.
Evidence
resolveCompactionFallbackCandidatescasesHuman Verification
pnpm tsgoclean,pnpm checkpasses"off","fallback"with empty fallbacks list,"fallback"filtering current model,hookFiredguard across model and thinking retry loopsCompatibility / Migration
fallbackModelis optional; default is"off"(no change in behaviour)agents.defaults.compaction.fallbackModel: "off"