Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
0ad0bf3
feat(config): context-budget kernel — per-model floors, resolver, cei…
Calmingstorm Aug 17, 2026
8bef6ec
fix(config): ceiling migration — one-time atomic rewrite, R2 primary …
Calmingstorm Aug 17, 2026
97e43e5
fix(config): harden legacy ceiling migration contract
Calmingstorm Aug 17, 2026
1d1c449
Merge pull request #270 from Calmingstorm/feat/context-budgets-p1-kernel
Calmingstorm Aug 18, 2026
4611cbc
feat(config): out-of-the-box defaults mirror the reference deployment
Calmingstorm Aug 18, 2026
0caff16
fix(setup): scaffold emits the reference-deployment primary model
Calmingstorm Aug 18, 2026
d04008f
feat(llm): provider truth — server usage echoes + opaque account keys
Calmingstorm Aug 18, 2026
f49674c
Merge pull request #271 from Calmingstorm/feat/context-budgets-defaults
Calmingstorm Aug 18, 2026
93489b7
feat(agents,chat): activate per-model context budgets (campaign phase 3)
Calmingstorm Aug 18, 2026
35cd3d7
fix(llm): harden account-key establishment — race, totality, material…
Calmingstorm Aug 18, 2026
48051ab
fix(llm): close account-key file race edges
Calmingstorm Aug 18, 2026
57196a3
Merge pull request #272 from Calmingstorm/feat/context-budgets-p2-pro…
Calmingstorm Aug 18, 2026
487b60c
Merge campaign tip (phase 2) into phase-3 branch
Calmingstorm Aug 18, 2026
cefd0a4
fix(agents,chat): freeze generation identity; gate collisions; defer …
Calmingstorm Aug 18, 2026
b099709
fix: freeze generation identity across retries
Calmingstorm Aug 18, 2026
c5e9ee0
Merge pull request #273 from Calmingstorm/feat/context-budgets-p3-act…
Calmingstorm Aug 18, 2026
4c27e4d
feat(compressor): surface-aware boundary — replay elision behind the …
Calmingstorm Aug 18, 2026
d926604
feat(loops): emergency overflow recovery + first-ever soft compaction
Calmingstorm Aug 18, 2026
c62b0d0
feat(chat): emergency overflow recovery with the durable six-step seq…
Calmingstorm Aug 18, 2026
a6c6f1a
test(recovery): chat/loop rescue batteries + boundary report truth
Calmingstorm Aug 18, 2026
68f81d6
test,fix: cover recovery guard branches; type the loop boundary
Calmingstorm Aug 18, 2026
b88e1d5
fix(recovery): resolve all six review round-1 blockers
Calmingstorm Aug 18, 2026
0490bc1
fix(recovery): close phase 4 round 3 blockers
Calmingstorm Aug 18, 2026
82615bb
Merge pull request #274 from Calmingstorm/feat/context-budgets-p4-rec…
Calmingstorm Aug 18, 2026
9565508
feat(context): passive window observer + downward-only clamps (phase 5)
Calmingstorm Aug 18, 2026
38f7b8e
fix(context): close phase 5 observer review blockers
Calmingstorm Aug 18, 2026
87602fa
test(context): make raw auto ceiling regression pin bite
Calmingstorm Aug 18, 2026
515af78
Merge pull request #275 from Calmingstorm/feat/context-budgets-p5-obs…
Calmingstorm Aug 18, 2026
5aaa547
feat(webui): add context budget controls
Calmingstorm Aug 18, 2026
b090b4f
Merge pull request #276 from Calmingstorm/feat/context-budgets-p6-webui
Calmingstorm Aug 18, 2026
bba2e7d
fix: close context-budget integration seams
Calmingstorm Aug 18, 2026
2167e11
Merge pull request #278 from Calmingstorm/feat/context-budgets-integr…
Calmingstorm Aug 18, 2026
a295b43
fix context budget integration seams
Calmingstorm Aug 18, 2026
78caf75
Merge pull request #279 from Calmingstorm/feat/context-budgets-integr…
Calmingstorm Aug 18, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 9 additions & 7 deletions config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,21 @@ openai_codex:
enabled: true
# ChatGPT-account Codex backend only supports the GPT-5 family
# (gpt-4o et al return "model not supported" via this auth path).
model: gpt-5.5
model: gpt-5.6-sol
# Reasoning effort sent with every main-provider request:
# none | low | medium | high | xhigh | max (backend default: medium).
# "max" is gpt-5.6-family only — config load rejects a known-incompatible
# model/effort pair (e.g. gpt-5.5 + max) rather than 400 on every request.
reasoning_effort: medium
reasoning_effort: xhigh
# Effort for spawned-agent iterations only. Unset/null = inherit
# reasoning_effort ("none" is a real effort level, not inherit). Set to
# "auto" to let the spawner choose the effort per spawn from the catalogue.
# Read at call time — live changes reach in-flight agents next iteration.
agent_reasoning_effort: null
agent_reasoning_effort: auto
# Model for spawned-agent iterations only. Unset/null = inherit model; a
# specific model = fixed; "auto" = let the spawner choose per spawn.
# Read at call time — live changes reach in-flight agents next iteration.
agent_model: null
agent_model: auto
credentials_path: ./data/codex_auth.json
# Streaming transport timeouts. request_timeout_seconds is a generous
# whole-request backstop (long high-effort reasoning turns stream past
Expand All @@ -46,15 +46,17 @@ openai_codex:
keepalive_timeout: 30
context_compression:
enabled: true
max_context_chars: 750000
# null = auto: the ceiling derives from the active model's input budget
# (a number only lowers the derived target, never raises it)
max_context_chars: null
keep_recent_iterations: 30
# Auxiliary model: when enabled, a cheaper Codex model runs the background
# jobs (compaction, reflection, consolidation, background follow-up) with
# automatic fallback to the primary model on error. It shares the main Codex
# OAuth credentials; only the model differs. Editable live from the WebUI.
auxiliary:
enabled: false
model: gpt-5.6-luna
enabled: true
model: gpt-5.6-terra

ollama:
enabled: false
Expand Down
46 changes: 35 additions & 11 deletions coverage-baseline.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,17 @@
"percent": 100.0,
"statements": 30
},
"src/config/migrations.py": {
"covered": 294,
"missing": 0,
"percent": 100.0,
"statements": 294
},
"src/config/schema.py": {
"covered": 628,
"covered": 706,
"missing": 1,
"percent": 99.84,
"statements": 629
"percent": 99.86,
"statements": 707
},
"src/constants.py": {
"covered": 17,
Expand Down Expand Up @@ -413,6 +419,12 @@
"percent": 100.0,
"statements": 12
},
"src/llm/account_key.py": {
"covered": 115,
"missing": 0,
"percent": 100.0,
"statements": 115
},
"src/llm/auxiliary.py": {
"covered": 65,
"missing": 0,
Expand All @@ -437,6 +449,12 @@
"percent": 93.28,
"statements": 402
},
"src/llm/context_budget.py": {
"covered": 45,
"missing": 0,
"percent": 100.0,
"statements": 45
},
"src/llm/context_compressor.py": {
"covered": 165,
"missing": 18,
Expand Down Expand Up @@ -852,10 +870,10 @@
"statements": 45
},
"src/tools/autonomous_loop.py": {
"covered": 155,
"missing": 83,
"percent": 65.13,
"statements": 238
"covered": 197,
"missing": 72,
"percent": 73.23,
"statements": 269
},
"src/tools/branch_freshness.py": {
"covered": 99,
Expand Down Expand Up @@ -1254,10 +1272,10 @@
"statements": 281
},
"src/web/api/llm_admin.py": {
"covered": 510,
"covered": 625,
"missing": 3,
"percent": 99.42,
"statements": 513
"percent": 99.52,
"statements": 628
},
"src/web/api/observability.py": {
"covered": 216,
Expand Down Expand Up @@ -1312,5 +1330,11 @@
"missing": 27,
"percent": 88.98,
"statements": 245
},
"src/llm/window_observer.py": {
"covered": 308,
"missing": 39,
"percent": 88.76,
"statements": 347
}
}
}
25 changes: 18 additions & 7 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,10 @@ tools:
```yaml
openai_codex:
enabled: true
model: gpt-5.5 # ChatGPT subscription path
reasoning_effort: medium # none | low | medium | high | xhigh | max
agent_reasoning_effort: null # spawned agents; null = inherit, "auto" = per-spawn choice
agent_model: null # spawned agents; null = inherit, "auto" = per-spawn choice
model: gpt-5.6-sol # ChatGPT subscription path
reasoning_effort: xhigh # none | low | medium | high | xhigh | max
agent_reasoning_effort: auto # spawned agents; "auto" = per-spawn choice, null = inherit
agent_model: auto # spawned agents; "auto" = per-spawn choice, null = inherit
credentials_path: ./data/codex_auth.json
request_timeout_seconds: 3600 # whole-request backstop; long reasoning turns stream past 10 min
stream_stall_timeout_seconds: 180 # fail fast when no stream bytes arrive for this long
Expand All @@ -82,13 +82,24 @@ openai_codex:
max_delay: 30.0
context_compression:
enabled: true
max_context_chars: 48000
max_context_chars: null # null = auto (model-derived ceiling); a number only lowers it
keep_recent_iterations: 3
# Per-model usable-input-budget overrides (tokens, 50192-2000000). Empty =
# built-in known-safe floors. Consumed by the context-budget resolver.
context_budget_overrides: {}
# Working-set policy: percent of the effective budget compaction targets
# (30-100). Never reduces budgets at or below 272K tokens.
context_utilization: 60
auxiliary: # cheaper model for background jobs
enabled: false
model: gpt-5.6-luna
enabled: true
model: gpt-5.6-terra
```

A persisted `max_context_chars: 750000` from the pre-campaign default is
migrated to auto once (a provenance marker under `data/` records it, and one
warning names the marker); saving the compression settings afterwards makes
any explicit value — including 750000 — stick permanently.

Reasoning effort `max` is served only by the gpt-5.6 family (sol/terra/luna);
gpt-5.5 rejects it per-request. Odin refuses a known-incompatible model/effort
pair everywhere it can be introduced — config load, the admin API, per-spawn
Expand Down
13 changes: 12 additions & 1 deletion scripts/check-config-center-ui2.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -332,11 +332,13 @@ const providerForm = {
retry: { max_retries: 9, base_delay: 4, max_delay: 40 },
connection_pool: { max_connections: 19, keepalive_timeout: 41 },
context_compression: { enabled: false, max_context_chars: 123456, keep_recent_iterations: 11 },
context_budget_overrides: { 'gpt-5.6-sol': 800000 },
context_utilization: 72,
timeout: 777,
};
const expectedPayloadKeys = new Map([
[codexBasicPayload, ['agent_model', 'agent_reasoning_effort', 'enabled', 'model', 'reasoning_effort']],
[codexAdvancedPayload, ['connection_pool', 'context_compression', 'request_timeout_seconds', 'retry', 'stream_stall_timeout_seconds']],
[codexAdvancedPayload, ['connection_pool', 'context_budget_overrides', 'context_compression', 'context_utilization', 'request_timeout_seconds', 'retry', 'stream_stall_timeout_seconds']],
[ollamaBasicPayload, ['base_url', 'enabled', 'max_tokens', 'model']],
[ollamaAdvancedPayload, ['timeout']],
[kimiBasicPayload, ['enabled', 'max_tokens', 'model']],
Expand All @@ -359,6 +361,15 @@ assert.match(llm, /saveCodexConfig\(\)[\s\S]*codexBasicPayload\(codexForm\.value
assert.match(llm, /saveOllamaConfig\(\)[\s\S]*ollamaBasicPayload\(ollamaForm\.value/, 'Ollama basic auto-save does not use its field-only payload');
assert.match(llm, /saveKimiConfig\(\)[\s\S]*kimiBasicPayload\(kimiForm\.value/, 'Kimi basic auto-save does not use its field-only payload');
assert.match(llm, /saveCodexAdvancedConfig\(\)[\s\S]*codexAdvancedPayload\(codexForm\.value\)/, 'Codex explicit Advanced save does not use its field-only payload');
assert.match(llm, /<strong>Context budgets<\/strong>/, 'Codex Advanced panel lost the Context budgets table');
assert.match(llm, /api\.get\('\/api\/context\/windows'\)/, 'Context budgets do not load backend derivation truth');
assert.match(llm, /api\.post\('\/api\/context\/windows\/clear'/, 'Context budgets lost account-scoped clamp clearing');
assert.match(llm, /formatContextCeiling\(llmStatus\.codex\.effective_context_compression\?\.max_context_chars\)/, 'Context-compression status lost truthful automatic-ceiling formatting');
assert.doesNotMatch(llm, /effective_context_compression\?\.max_context_chars\s*\|\|\s*0/, 'Automatic context ceiling regressed to 0 characters');
assert.match(llm, /details\.effective\?\.effective_budget/, 'effective budget is recomputed or not data-bound');
assert.match(llm, /details\.effective\?\.primary_chars/, 'resulting target is recomputed or not data-bound');
assert.doesNotMatch(llm, /921601|917506|270001|262146|124001/, 'browser duplicated the backend context-budget catalog');
assert.match(llm, /enabled: false, model: 'gpt-5\.6-sol', reasoning_effort: 'xhigh', agent_reasoning_effort: 'auto', agent_model: 'auto'/, 'LLM owner-page fallback defaults drifted from the schema');
assert.match(llm, /saveOllamaAdvancedConfig\(\)[\s\S]*ollamaAdvancedPayload\(ollamaForm\.value\)/, 'Ollama explicit Advanced save does not use its field-only payload');
assert.match(llm, /saveKimiAdvancedConfig\(\)[\s\S]*kimiAdvancedPayload\(kimiForm\.value\)/, 'Kimi explicit Advanced save does not use its field-only payload');
for (const provider of ['Codex', 'Ollama', 'Kimi']) {
Expand Down
77 changes: 76 additions & 1 deletion scripts/check-config-save-boundaries.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ const llmState = {
retry: { max_retries: 3, base_delay: 1, max_delay: 30 },
connection_pool: { max_connections: 10, keepalive_timeout: 30 },
context_compression: { enabled: true, max_context_chars: 750000, keep_recent_iterations: 30 },
effective_context_compression: { enabled: false, max_context_chars: null, keep_recent_iterations: 30 },
context_compression_pending_restart: true,
},
ollama: {
configured: true,
Expand All @@ -54,6 +56,18 @@ const llmState = {
},
auxiliary: { enabled: false, model: 'gpt-5.6-luna' },
};
const contextWindowsState = {
utilization: 60,
max_context_chars: null,
models: {
'gpt-5.6-sol': {
floor: 921601, override: null, active_clamp: null, provenance: 'built-in', clamp_expires_at: null,
configured: { effective_budget: 921601, primary_chars: 1277400 },
effective: { effective_budget: 921601, primary_chars: 1277400 },
},
},
clamps: [{ account_key: 'a'.repeat(32), model: 'gpt-5.6-sol', value: 300000, expires_at: '2026-08-19T12:00:00Z' }], evidence: { version: 1, accounts: {} },
};
const globalConfig = {
discord: {
allowed_users: ['441'],
Expand Down Expand Up @@ -107,14 +121,22 @@ globalThis.fetch = async (path, options = {}) => {
return response({ error: 'injected save failure' }, failureStatus);
}

if (method === 'POST' && path === '/api/context/windows/clear') return response({ cleared: 1 });
if (path === '/api/llm/status') return response(llmState);
if (path === '/api/context/windows') return response(contextWindowsState);
if (path === '/api/codex/status') return response({ configured: true, accounts: [] });
if (path === '/api/ollama/status') return response({ configured: true, model: llmState.ollama.model, health: { healthy: true } });
if (path === '/api/ollama/models') return response({ active_model: llmState.ollama.model, models: [{ name: 'llama3', size: 10 }, { name: 'qwen', size: 20 }] });
if (path === '/api/kimi/status') return response({ configured: true, model: llmState.kimi.model, health: { healthy: true } });
if (path === '/api/kimi/models') return response({ models: ['kimi-k2', 'kimi-next'] });
if (method === 'PUT' && /^\/api\/llm\/(codex|ollama|kimi)\/config$/.test(path)) {
Object.assign(providerConfig(path), body);
if (path.includes('/codex/')) {
if ('context_budget_overrides' in body) {
contextWindowsState.models['gpt-5.6-sol'].override = body.context_budget_overrides['gpt-5.6-sol'] ?? null;
}
if ('context_utilization' in body) contextWindowsState.utilization = body.context_utilization;
}
return response({ status: 'updated' });
}
if (path === '/api/discord/guilds') {
Expand Down Expand Up @@ -152,8 +174,49 @@ console.warn = message => {
};

const { default: LLMConfigPage } = await import('../ui/js/pages/llm-config.js');
assert.match(
LLMConfigPage.template,
/formatContextCeiling\(llmStatus\.codex\.effective_context_compression\?\.max_context_chars\)/,
'pending-restart template does not consume the truthful ceiling formatter',
);
assert.doesNotMatch(
LLMConfigPage.template,
/effective_context_compression\?\.max_context_chars\s*\|\|\s*0/,
'pending-restart template renders automatic context as zero characters',
);
const llm = LLMConfigPage.setup();
await llm.fetchAll();
assert.equal(llm.contextBudgetRows.value[0].primaryChars, 1277400, 'Context target did not come from GET /api/context/windows');
assert.equal(llm.formatContextCeiling(null), 'automatic (model-derived)', 'automatic runtime ceiling rendered as a numeric zero');
assert.equal(llm.formatContextCeiling(500000), '500,000 characters', 'explicit runtime ceiling lost its unit/value');
const lateContextRefresh = defer('GET /api/context/windows');
const contextRefresh = llm.fetchContextWindows();
await Promise.resolve();
llm.setContextOverride('gpt-5.6-sol', { target: { value: '800000' } });
llm.setContextUtilization({ target: { value: '72' } });
lateContextRefresh.resolve();
await contextRefresh;
assert.equal(llm.codexForm.value.context_budget_overrides['gpt-5.6-sol'], 800000, 'late context-window GET erased an unsaved override');
assert.equal(llm.codexForm.value.context_utilization, 72, 'late context-window GET erased unsaved utilization');
contextWindowsState.models['gpt-5.6-sol'].effective.primary_chars = 111111;
const olderWindows = defer('GET /api/context/windows');
const olderWindowsRequest = llm.fetchContextWindows();
await Promise.resolve();
const newerWindowsRequest = llm.fetchContextWindows();
await newerWindowsRequest;
assert.equal(llm.contextBudgetRows.value[0].primaryChars, 111111, 'newest context-window response did not render');
contextWindowsState.models['gpt-5.6-sol'].effective.primary_chars = 222222;
olderWindows.resolve();
await olderWindowsRequest;
assert.equal(llm.contextBudgetRows.value[0].primaryChars, 111111, 'older context-window response overwrote newer derivation truth');
contextWindowsState.models['gpt-5.6-sol'].effective.primary_chars = 1277400;
const clearBefore = requests.length;
await llm.clearContextClamp(contextWindowsState.clamps[0]);
const clearRequest = requests.slice(clearBefore).find(request => request.method === 'POST' && request.path === '/api/context/windows/clear');
assert.deepEqual(clearRequest?.body, { account_key: 'a'.repeat(32), model: 'gpt-5.6-sol' }, 'clamp clear lost account/model scope');
assert.equal(requests.slice(clearBefore).some(request => request.method === 'GET' && request.path === '/api/context/windows'), true, 'clamp clear did not refresh derivation truth');
assert.equal(llm.codexForm.value.context_budget_overrides['gpt-5.6-sol'], 800000, 'clamp clear erased an unsaved override');
assert.equal(llm.codexForm.value.context_utilization, 72, 'clamp clear erased unsaved utilization');

const providerCases = [
{
Expand All @@ -162,7 +225,7 @@ const providerCases = [
changeBasic: () => { llm.codexForm.value.model = 'gpt-5.6-sol'; },
save: llm.saveCodexConfig,
saveAdvanced: llm.saveCodexAdvancedConfig,
advancedKeys: ['request_timeout_seconds', 'stream_stall_timeout_seconds', 'retry', 'connection_pool', 'context_compression'],
advancedKeys: ['request_timeout_seconds', 'stream_stall_timeout_seconds', 'retry', 'connection_pool', 'context_compression', 'context_budget_overrides', 'context_utilization'],
serverAdvanced: () => llmState.codex.request_timeout_seconds,
draftAdvanced: () => llm.codexForm.value.request_timeout_seconds,
oldValue: 3600,
Expand Down Expand Up @@ -222,6 +285,18 @@ for (const testCase of providerCases) {
);
assert.equal(testCase.serverAdvanced(), testCase.draftValue, `${testCase.name} explicit Advanced save did not update server state`);
}
assert.equal(llmState.codex.context_budget_overrides['gpt-5.6-sol'], 800000, 'Context override was not persisted by the Advanced save');
assert.equal(llmState.codex.context_utilization, 72, 'Context utilization was not persisted by the Advanced save');
assert.equal(llm.contextPolicyDirty.value, false, 'successful unchanged Advanced save did not clear context-policy dirty state');
const postSaveRefresh = defer('GET /api/context/windows');
const postSaveRequest = llm.fetchContextWindows();
await Promise.resolve();
llm.setContextOverride('gpt-5.6-sol', { target: { value: '810000' } });
llm.setContextUtilization({ target: { value: '73' } });
postSaveRefresh.resolve();
await postSaveRequest;
assert.equal(llm.codexForm.value.context_budget_overrides['gpt-5.6-sol'], 810000, 'late post-save GET erased a newer override');
assert.equal(llm.codexForm.value.context_utilization, 73, 'late post-save GET erased newer utilization');


// A response that finishes after a newer edit must not repopulate either axis
Expand Down
Loading
Loading