deepseek-ai/deepseek-harness · error
${name}: retainRatio and retainTokens are mutually exclusive
Error message
${name}: retainRatio and retainTokens are mutually exclusive What it means
Retention (the verbatim tail kept after compaction) has exactly one form per config scope: a fraction of the model's context window (retainRatio) or an absolute token count (retainTokens). resolveRetention() prefers retainTokens when both are present, so validatePolicy() rejects the combination at load instead of silently ignoring one setting. The rule applies to top-level defaults and to each modelPolicies entry separately.
Source
Thrown at packages/compaction/compaction-basic/src/config.ts:241
validatePolicy(source, name)
}
/** Validate the fields common to defaults and exact-target partial overrides. */
function validatePolicy(
config: CompactionPolicyConfig | Record<string, unknown>,
name: string,
): void {
const thresholdRatio = config.thresholdRatio
const retainRatio = config.retainRatio
const retainTokens = config.retainTokens
const maxTokens = config.maxTokens
const compactionRetries = config.compactionRetries
const maxOverflowRetries = config.maxOverflowRetries
if (thresholdRatio !== undefined) assertRatio(`${name}.thresholdRatio`, thresholdRatio)
if (retainRatio !== undefined) assertRatio(`${name}.retainRatio`, retainRatio)
if (retainTokens !== undefined) assertNonNegativeInteger(`${name}.retainTokens`, retainTokens)
if (retainRatio !== undefined && retainTokens !== undefined) {
throw new Error(`${name}: retainRatio and retainTokens are mutually exclusive`)
}
if (maxTokens !== undefined) assertPositiveInteger(`${name}.maxTokens`, maxTokens)
if (compactionRetries !== undefined) {
assertNonNegativeInteger(`${name}.compactionRetries`, compactionRetries)
}
if (maxOverflowRetries !== undefined) {
assertNonNegativeInteger(`${name}.maxOverflowRetries`, maxOverflowRetries)
}
validateSummarizationPair(config, name)
}
/** Require one scope to omit, clear, or replace the summarization target as a pair. */
function validateSummarizationPair(
config: CompactionPolicyConfig | Record<string, unknown>,
name: string,
): void {
const provider = config.summarizationProviderView on GitHub (pinned to b150a551b8)
Solutions
- Pick one retention form per scope: delete retainRatio or delete retainTokens
- For absolute budgets keep retainTokens and remove retainRatio; for window-relative budgets do the opposite
- If you want per-model absolutes plus a global ratio, keep them in different scopes — entry-level retainTokens overriding top-level retainRatio is valid
Example fix
# before — both forms in the same scope retainRatio: 0.1 retainTokens: 4000 # after — absolute budget only retainTokens: 4000
Defensive patterns
Strategy: validation
Validate before calling
for (const scope of [config, ...(config.modelPolicies ?? [])]) {
if (scope.retainRatio !== undefined && scope.retainTokens !== undefined) {
throw new Error('choose retainRatio OR retainTokens per config scope')
}
}
resolveConfig(config) Try / catch
try {
resolveConfig(config)
} catch (err) {
if ((err as Error).message.includes('mutually exclusive')) {
// The message names the scope: top-level 'BasicCompactionConfig' or 'modelPolicies[i]'.
throw new Error(
'retention conflict: keep exactly one of retainRatio / retainTokens in the named scope',
{ cause: err },
)
}
throw err
} Prevention
- Treat retention as one knob per block: fraction OR absolute tokens
- State the chosen form in your config template so editors do not add the second key
- A modelPolicies entry overriding the top-level form is legal — only same-scope duplicates fail
When it happens
Trigger: Both retainRatio and retainTokens set in the same scope: the top-level config block, or one modelPolicies entry. Cross-scope mixing is legal — an entry may set retainTokens while the top level sets retainRatio; the entry replaces the inherited retention.
Common situations: Tuning compaction and forgetting an old retainRatio is still present; copying a per-model entry that had retainTokens into a top-level block that already sets retainRatio; assuming the more specific value wins silently.
Related errors
- BasicCompactionConfig: auto must be a boolean
- BasicCompactionConfig: ${policy.target.provider}/${policy.ta
- ${name}: retainRatio (${retention.retainRatio}) must be less
- BasicCompactionConfig: modelPolicies must be an array
- BasicCompactionConfig: duplicate model policy for ${source.p
AI-assisted analysis of deepseek-ai/deepseek-harness@b150a551b8 (2026-08-24).
Data as JSON: /api/errors/3b3813543933f587.
Report an issue: GitHub.