diff --git a/docs/models.md b/docs/models.md
index 2827beda..c482cda2 100644
--- a/docs/models.md
+++ b/docs/models.md
@@ -108,12 +108,19 @@ For any model not matching the table, specify `Provider`, `Endpoint`, and `ApiKe
{
"modelId": "anthropic.claude-sonnet-4-6-20250929-v1:0",
"endpoint": "http://localhost:3000/api/openai/v1",
- "apiKeyEnvVar": "OPENWEBUI_API_KEY"
+ "apiKeyEnvVar": "OPENWEBUI_API_KEY",
+ "replContextBudget": 400000
}
```
Set this file via `fuseraft repl` or `fuseraft models` (the setup wizard runs automatically on first use) or edit it directly. Run `fuseraft models` to see all models available from the configured provider, or use `/models` inside a REPL session for the same list.
+### `replContextBudget` — REPL working-context override
+
+The REPL trims conversation history against a working-context-token budget (`ctx.ContextTokenBudget`, shown in `/context`), separate from `MaxContextTokens` above. By default this budget comes from a per-model-family heuristic (150K for 1M/128K+-class frontier models like `claude-*`/`gemini-*`/`grok-*`/`gpt-5*`, 100K for ~128K-class models like `gpt-4*`/`mistral-*`/`deepseek-*`, 80K otherwise) — deliberately conservative, since the REPL's char-based token estimate doesn't account for tool-schema tokens.
+
+Set `replContextBudget` in `~/.fuseraft/config` (a positive integer, in tokens) to override that heuristic for every model used in the REPL session, regardless of family. Leave it unset (or `0`) to keep the built-in heuristic. This is REPL-only and does not affect `MaxContextTokens` above (a separate per-agent hard ceiling enforced before each API call in non-REPL agent/orchestration contexts), nor the unrelated `ContextBudget` YAML block used in `orchestration.yaml` (warn/cutover/tool-result trimming for multi-agent orchestration runs) — the similarly-named `replContextBudget` field intentionally carries the `Repl` prefix to keep the two apart.
+
### OS keychain fallback
If an agent model has neither `ApiKey` nor `ApiKeyEnvVar` set after global defaults are applied, fuseraft retrieves the key stored in the OS keychain (set via `fuseraft key set` or the REPL wizard) and injects it as a literal `ApiKey`. This means the full auth resolution order for any agent model is:
diff --git a/src/Cli/Commands/Repl/ModelContextWindow.cs b/src/Cli/Commands/Repl/ModelContextWindow.cs
index 2c95dff7..0ecce616 100644
--- a/src/Cli/Commands/Repl/ModelContextWindow.cs
+++ b/src/Cli/Commands/Repl/ModelContextWindow.cs
@@ -35,8 +35,15 @@ internal static class ModelContextWindow
/// so both bare model IDs (e.g. claude-sonnet-4-6) and provider-prefixed deployment
/// IDs (e.g. Bedrock's anthropic.claude-sonnet-4-6-20250929-v1:0) resolve correctly.
///
- internal static int GetBudget(string? modelId)
+ /// The model ID whose family determines the heuristic budget.
+ ///
+ /// User-configured override ().
+ /// When positive, takes precedence over the per-family heuristic below.
+ ///
+ internal static int GetBudget(string? modelId, int? overrideBudget = null)
{
+ if (overrideBudget is > 0) return overrideBudget.Value;
+
if (string.IsNullOrWhiteSpace(modelId)) return DefaultBudget;
if (LargeFamilyMarkers.Any(m => modelId.Contains(m, StringComparison.OrdinalIgnoreCase)))
diff --git a/src/Cli/Commands/Repl/ReplSessionContext.cs b/src/Cli/Commands/Repl/ReplSessionContext.cs
index 682ed187..2aee84bc 100644
--- a/src/Cli/Commands/Repl/ReplSessionContext.cs
+++ b/src/Cli/Commands/Repl/ReplSessionContext.cs
@@ -49,15 +49,17 @@ public string ModelId
set
{
_modelId = value;
- ContextTokenBudget = ModelContextWindow.GetBudget(value);
+ ContextTokenBudget = ModelContextWindow.GetBudget(value, UserCfg?.ReplContextBudget);
}
}
// Working token budget for history trimming (TrimHistory) and the /context, /compact,
- // and context-warning displays — derived from ModelId so a large-context model isn't
- // held to the same ceiling as a small-context local model. Recomputed automatically
- // whenever ModelId is (re)assigned, including on /provider setup, /model switch, and
- // session resume.
+ // and context-warning displays — derived from ModelId (and UserCfg.ReplContextBudget, if set)
+ // so a large-context model isn't held to the same ceiling as a small-context local model.
+ // Recomputed automatically whenever ModelId is (re)assigned, including on /provider setup,
+ // /model switch, and session resume. Relies on UserCfg already being current at that point
+ // — the constructor below sets UserCfg before ModelId for this reason, and every later
+ // reassignment site that changes both (e.g. /provider setup) must preserve that order.
public int ContextTokenBudget { get; private set; } = ModelContextWindow.DefaultBudget;
public ModelConfig ModelConfig { get; set; }
@@ -164,9 +166,9 @@ public ReplSessionContext(
Cwd = cwd;
SessionId = sessionId;
StartedAt = startedAt;
+ UserCfg = userCfg;
ModelId = modelId;
ModelConfig = modelConfig;
- UserCfg = userCfg;
Client = client;
Factory = factory;
KeyStore = keyStore;
diff --git a/src/Core/Models/Config/UserConfig.cs b/src/Core/Models/Config/UserConfig.cs
index 663f590f..76ff4f9d 100644
--- a/src/Core/Models/Config/UserConfig.cs
+++ b/src/Core/Models/Config/UserConfig.cs
@@ -19,6 +19,18 @@ public sealed class UserConfig
[JsonPropertyName("skillCuration")]
public SkillCurationConfig? SkillCuration { get; set; }
+ ///
+ /// Overrides the REPL's heuristic working-context-token budget ()
+ /// used for history trimming and the /context, /compact, and context-warning displays.
+ /// REPL-only — unrelated to the orchestration-level ContextBudgetConfig
+ /// (warn/cutover/tool-result trimming for agent orchestration runs); the similar name is
+ /// coincidental, hence the Repl prefix here to keep the two unambiguous.
+ /// Applies to every model used in the REPL session, regardless of model family. Null or
+ /// <= 0 falls back to the built-in per-family heuristic.
+ ///
+ [JsonPropertyName("replContextBudget")]
+ public int? ReplContextBudget { get; set; }
+
// Never written to disk — populated at runtime from the OS keychain.
[JsonIgnore]
public string ApiKey { get; set; } = string.Empty;
diff --git a/src/Infrastructure/Storage/UserConfigStore.cs b/src/Infrastructure/Storage/UserConfigStore.cs
index 8770a4c0..be698825 100644
--- a/src/Infrastructure/Storage/UserConfigStore.cs
+++ b/src/Infrastructure/Storage/UserConfigStore.cs
@@ -34,10 +34,11 @@ public static (UserConfig? Config, string? LegacyKey) Load()
var config = new UserConfig
{
- ModelId = onDisk.ModelId ?? string.Empty,
- Endpoint = onDisk.Endpoint ?? string.Empty,
- Provider = onDisk.Provider ?? string.Empty,
- ApiKeyEnvVar = onDisk.ApiKeyEnvVar ?? string.Empty,
+ ModelId = onDisk.ModelId ?? string.Empty,
+ Endpoint = onDisk.Endpoint ?? string.Empty,
+ Provider = onDisk.Provider ?? string.Empty,
+ ApiKeyEnvVar = onDisk.ApiKeyEnvVar ?? string.Empty,
+ ReplContextBudget = onDisk.ReplContextBudget,
};
return (config, onDisk.ApiKey ?? legacyKeyFile);
}
@@ -67,10 +68,11 @@ public static void Save(UserConfig config)
Directory.CreateDirectory(ConfigDir);
var onDisk = new OnDiskConfig
{
- ModelId = config.ModelId,
- Endpoint = config.Endpoint,
- Provider = config.Provider,
- ApiKeyEnvVar = config.ApiKeyEnvVar,
+ ModelId = config.ModelId,
+ Endpoint = config.Endpoint,
+ Provider = config.Provider,
+ ApiKeyEnvVar = config.ApiKeyEnvVar,
+ ReplContextBudget = config.ReplContextBudget,
};
File.WriteAllText(ConfigPath, JsonSerializer.Serialize(onDisk, JsonOptions));
}
@@ -91,6 +93,9 @@ private sealed class OnDiskConfig
[JsonPropertyName("apiKeyEnvVar")]
public string? ApiKeyEnvVar { get; set; }
+ [JsonPropertyName("replContextBudget")]
+ public int? ReplContextBudget { get; set; }
+
// Present only in configs created before keychain support was added.
[JsonPropertyName("apiKey")]
public string? ApiKey { get; set; }