Skip to content

User .forge.toml [compact] settings silently ignored: agent-priority merge overrides user config (token_threshold uncontrollable) #3526

@akhil29

Description

@akhil29

Summary

A user's ~/.forge/.forge.toml [compact] settings (notably token_threshold) are silently discarded at runtime. No matter what value the user sets, the embedded default (token_threshold = 100000) is what actually takes effect. This makes compaction behavior uncontrollable via user config.

This is distinct from #3518 / #3519, which concern how the threshold is computed (context-window resolution and the min(token_threshold, 0.7 × window) cap). This bug is about config precedence — the user's value is never even read into the effective config, so those other fixes can't be tuned by users anyway.

Root cause

When the agent config is assembled, the agent's compact settings are merged on top of the workflow/user (.forge.toml) settings:

  • crates/forge_app/src/agent.rs:148merged_compact.merge(agent.compact.clone()) applies the agent's compact config over the user/workflow config, so the agent value wins.

  • This is even documented in the source comment at crates/forge_app/src/agent.rs:277-279:

    CURRENT BEHAVIOR: When agent has compact settings, they override workflow settings. This means user's .forge.toml compact settings are ignored if agent has ANY compact config.

Because the embedded default agent/config carries token_threshold = 100000 (crates/forge_config/.forge.toml:67), the user's value is always overridden.

Reproduction / Evidence

I built an instrumented binary from main with a debug print inside compaction_threshold (crates/forge_domain/src/agent.rs) that logs the resolved window, the configured threshold, and the effective threshold.

Steps:

  1. Set ~/.forge/.forge.toml[compact] token_threshold = 424242 (a distinctive sentinel value).
  2. Run a single turn through the instrumented binary on the vertex_ai_anthropic provider (model claude-opus-4-8).

Observed debug output:

[FORGE_COMPACT_DEBUG]
  model_id                   = claude-opus-4-8
  raw_context_length         = Some(1000000)
  resolved_context_window    = 1000000        # window correctly resolves to 1M
  percentage                 = 0.7
  context_window_threshold   = 700000         # 0.7 x 1M
  configured_token_threshold = Some(100000)   # <-- NOT 424242; user config discarded
  effective_token_threshold  = 100000         # min(100000, 700000)

I set 424242; the binary loaded 100000. The user-configured token_threshold is never applied.

Impact

  • Users cannot raise (or lower) token_threshold (or any [compact] field) via ~/.forge/.forge.toml.
  • On large-context models (e.g. Opus 4.x at 1M), the effective trigger is permanently pinned at 100K. Any turn whose prompt exceeds ~100K (e.g. a large tool/MCP output) triggers compaction, producing repeated per-turn compaction (a ~95K–100K "sawtooth") regardless of the user's intended threshold.
  • This also undermines the user-facing half of fix: handle large (1M) context windows for Opus 4.x and compaction #3519: even if the threshold computation is improved, users still can't configure the value because it's discarded before use.

Expected behavior

User-provided .forge.toml [compact] settings should take precedence over (or at least be merge-able with) the embedded/agent defaults. A user-set token_threshold should win over the embedded default. At minimum, the merge order in crates/forge_app/src/agent.rs:148 should not let an agent's default compact config silently override an explicit user value.

Suggested fix

  • Invert/adjust the merge precedence so user/workflow [compact] config overrides agent-default compact config (or only let agent compact override when it was explicitly set, not when it's the embedded default).
  • Location: crates/forge_app/src/agent.rs:148 (and the documented behavior at :277-279).

Environment

  • forge v2.13.11 (released binary reproduces the symptom; instrumented build from main produced the debug evidence above)
  • Provider: vertex_ai_anthropic, model claude-opus-4-8 (context_length 1,000,000 in provider.json)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions