ulysses/src
nsgds 7ae6133d7f
fix(agent): don't let a materialized default budget defeat context-window scaling (#4122)
* fix(agent): don't let a materialized default budget defeat context scaling

#1230 scales agent_input_token_budget to the model's context window unless
the user explicitly set a budget, detected via is_setting_overridden(). But
the settings-save path materializes every DEFAULT_SETTINGS key into
settings.json (load_settings merges defaults; handlers persist the merged
dict), so the persisted default 6000 reads as "overridden" and the budget
code takes the min(6000, ctx) branch — silently re-capping long-context
models at 6000 for anyone who has ever saved a setting. This reintroduces
the exact regression #1170/#1230 set out to fix.

Add is_setting_customized() (saved value != default) and gate the scaling
on it instead of mere presence. A persisted default is not a user choice.

is_setting_overridden has exactly one consumer (this budget path), so the
change is contained. Tests cover the materialized-default regression, a
deliberately-chosen budget still being honoured, and the absent-key case.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(agent): rework context-budget fix per review (#4122)

Address RaresKeY's review:

P2 (explicitness): is_setting_customized treated a saved value equal to the
default as "not explicit", which ALSO blocked a user from deliberately pinning
the default budget. Reframe the default value itself as the AUTO sentinel —
agent_input_token_budget == DEFAULT_BUDGET means "scale to the model's context
window", any other value is an explicit cap. A materialized default still reads
as auto (fixing the original regression), and any non-default value the user
chooses is now honoured. Drop the now-unused is_setting_customized helper.

P2 (fallback context): auto-scaling trusted get_context_length() even when it
returned only the bare DEFAULT_CONTEXT fallback (no endpoint-reported / known
window), over-allocating on self-hosted/proxy setups. Add get_context_length_known()
(also returns whether the window was actually discovered); the budget block
passes 0 when unknown so auto-scaling stays conservative instead of inflating to
an unproven window.

hard_max stays auto-only — a deliberate explicit budget wins (#1190); kept that
contract and answered the reviewer's question rather than silently reversing it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test(agent): lock the materialized-default budget regression (review on #4121)

Per WGlynn's review on the issue: add an end-to-end regression that saves an
UNRELATED setting (which makes the settings-save path materialize the budget
default into settings.json) and asserts the budget still auto-scales rather than
re-reading as an explicit 6000 cap — locking the exact reopening shut.

To make the test bite the production decision (not just re-derive it), extract
`budget_is_explicit()` into src/context_budget.py and use it from the agent loop.
It keys off value-vs-default (the default is the auto sentinel), NOT settings
presence — which is the whole point, since the save path materializes defaults.

Note: after this PR's rework, is_setting_overridden has ZERO production callers,
so the merged-dict materialization smell can't reach any setting through a
presence check today (WGlynn's durability concern).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(agent): bind the budget context window to its own provenance (review #4122)

RaresKeY caught a correctness bug in the fallback-context guard: stream_agent_loop
kept only the `known` flag from get_context_length_known() and budgeted off the
passed-in `context_length`, which can come from a *different* lookup. Two failures:
- local endpoints are re-queried, so the passed value can be a stale DEFAULT_CONTEXT
  fallback while the fresh probe proves the real (smaller) served context — we'd
  scale off the stale value;
- callers that don't pass context_length (scheduled tasks, teacher escalation,
  skill test runs, bg_monitor) were capped at 6000 even when a long window is
  discoverable.

Extract budget_context_for_model() which returns the freshly-probed window when
known else 0, binding the flag to the value it proves; the agent loop uses it.
Regression tests cover the stale-fallback, no-arg-caller, and probe-error paths.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(agent): fix stale budget comments + tighten to the contract (review #4122)

- settings.py: an explicit budget is clamped to the window only — hard_max is
  auto-only (#1190); drop the incorrect "and to hard_max".
- is_setting_overridden docstring: drop the stale "adaptive budgets" example;
  point value-sensitive callers at context_budget.budget_is_explicit.
- Tighten the budget-block comments to the contract (default = auto sentinel,
  non-default = explicit cap, hard_max = auto-only ceiling).

Comment/docstring-only; no behaviour change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(agent): correct budget issue citations (#1190 → merged #1230/#1273)

The context-budget contract (auto-sentinel, explicit budgets honoured,
hard_max auto-only) merged via #1230#1190 was the earlier, closed,
superseded PR. Re-point the contract comments at #1230 (the live source,
already cited for the auto-sentinel two lines up in settings.py).

The configurable hard_max setting (`agent_input_token_hard_max`) was a
reviewer requirement first raised on #1190, omitted from the merged #1230,
and actually added in #1273 — credit #1273 for it and correct the test
comment's history (it previously implied this PR completed the requirement).

Comment/docstring-only; no behaviour change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 15:17:28 +09:00
..
agent_tools feat(agent): confine agent file/shell tools to a selectable workspace (#3665) 2026-06-11 18:17:54 +02:00
search refactor(search): make src analytics a service shim (#2264) 2026-06-04 18:57:24 +02:00
action_intents.py fix(agent): honor explicit web search requests 2026-06-15 15:02:10 +09:00
agent_loop.py fix(agent): don't let a materialized default budget defeat context-window scaling (#4122) 2026-06-15 15:17:28 +09:00
agent_runs.py Handle incomplete detached agent streams 2026-06-01 16:54:11 +09:00
ai_interaction.py fix: session context drifting — messages leaking between chats (#135) (#267) 2026-06-09 14:12:52 +01:00
api_key_manager.py fix(security): restrict API-key encryption key file to 0o600 2026-06-15 15:00:11 +09:00
app_helpers.py fix: inside_base_dir raises TypeError on a non-string path instead of failing closed (#1619) 2026-06-03 09:00:04 +09:00
app_initializer.py feat(memory): add provider interface (#72) 2026-06-04 16:26:11 +01:00
assistant_log.py Odysseus v1.0 2026-05-31 23:58:26 +09:00
auth_helpers.py fix(auth): gate api tokens from user routes (#2992) 2026-06-07 12:55:01 +02:00
bg_jobs.py refactor(constants): single source of truth for data dir (#3368) 2026-06-08 09:58:52 +02:00
bg_monitor.py fix: drop thinking deltas from background agent loops 2026-06-15 15:03:09 +09:00
builtin_actions.py fix(email): scope learned sender signatures by owner (#3724) 2026-06-11 13:26:59 +02:00
builtin_mcp.py fix(mcp): detect npx cache entries before probing (#4034) 2026-06-15 15:14:48 +09:00
caldav_sync.py fix(caldav): skip the prune when any object fails to parse (#3454) 2026-06-08 18:59:14 +02:00
caldav_writeback.py fix(caldav): disable redirects on the sync/write-back DAVClient (SSRF) (#2663) 2026-06-07 05:05:24 +01:00
chat_handler.py Scope vision model resolution by owner (#3009) 2026-06-07 12:39:02 +02:00
chat_helpers.py fix(chat): keep balanced trailing ')' when extracting URLs (#3406) 2026-06-08 21:33:29 +02:00
chat_processor.py fix(chat): stabilize system prompt, sequence memory extraction, and send stable session id to preserve KV cache (#3360) 2026-06-09 22:46:54 +01:00
chatgpt_subscription.py Fix model endpoint route test regressions 2026-06-09 10:16:38 +09:00
chroma_client.py fix: ChromaDB unreachable blocks app startup for 30-60s (#326) (#476) 2026-06-01 22:22:41 +09:00
cleanup_service.py Replace cleanup service datetime.utcnow calls (#1494) 2026-06-03 14:14:27 +09:00
config.py refactor(constants): single source of truth for data dir (#3368) 2026-06-08 09:58:52 +02:00
constants.py refactor(constants): single source of truth for data dir (#3368) 2026-06-08 09:58:52 +02:00
context_budget.py fix(agent): don't let a materialized default budget defeat context-window scaling (#4122) 2026-06-15 15:17:28 +09:00
context_compactor.py fix: session context drifting — messages leaking between chats (#135) (#267) 2026-06-09 14:12:52 +01:00
cookbook_serve_lifecycle.py fix(cookbook): preserve state during lifecycle tick 2026-06-15 15:07:03 +09:00
copilot.py feat(provider): add GitHub Copilot provider with device-flow auth (#1480) 2026-06-04 21:13:14 +02:00
database.py Odysseus v1.0 2026-05-31 23:58:26 +09:00
deep_research.py fix(research): track analyzed URLs separately (#3125) 2026-06-10 12:08:22 +01:00
document_actions.py fix: document tidy crashes on a duplicate with NULL timestamps (#1772) 2026-06-03 13:23:01 +09:00
document_processor.py fix(documents): restore PDF library metadata and preview (#2483) 2026-06-07 23:23:27 +02:00
email_thread_parser.py Ignore non-string email thread bodies (#1654) 2026-06-03 14:06:31 +09:00
embedding_lanes.py fix(embeddings): survive numpy embeddings when restoring a reset lane (#3410) 2026-06-09 10:40:17 +02:00
embeddings.py refactor(constants): single source of truth for data dir (#3368) 2026-06-08 09:58:52 +02:00
endpoint_resolver.py fix(models): probe /v1/models for path-less LM Studio endpoints 2026-06-15 15:09:50 +09:00
event_bus.py refactor(constants): single source of truth for data dir (#3368) 2026-06-08 09:58:52 +02:00
exceptions.py Odysseus v1.0 2026-05-31 23:58:26 +09:00
generated_images.py refactor(constants): single source of truth for data dir (#3368) 2026-06-08 09:58:52 +02:00
goal_based_extractor.py fix(deep-research): wrap fetched webpage content in untrusted-context sandbox 2026-06-06 03:37:10 -06:00
integrations.py fix(integrations): truncate api_call JSON lists with sentinel instead of mid-string cut (#3540) 2026-06-09 22:34:08 +01:00
llm_core.py fix(llm): make connect timeout configurable 2026-06-15 15:11:38 +09:00
markitdown_runtime.py fix: is_markitdown_format crashes on a non-string path (#1618) 2026-06-03 09:00:10 +09:00
mcp_manager.py feat: Add plan mode to the chat agent (#638) 2026-06-05 16:32:25 +02:00
mcp_oauth.py feat(mcp): add Streamable HTTP transport with OAuth 2.0 (#1033) 2026-06-05 02:40:52 +02:00
memory.py refactor(memory): canonicalize memory imports (#50) 2026-06-04 05:31:15 +01:00
memory_provider.py feat(memory): add provider interface (#72) 2026-06-04 16:26:11 +01:00
memory_vector.py fix: split Chroma embedding lanes (#3046) 2026-06-06 03:17:19 -06:00
model_context.py fix(agent): don't let a materialized default budget defeat context-window scaling (#4122) 2026-06-15 15:17:28 +09:00
model_discovery.py fix(startup): ping real endpoints in warmup/keepalive (#3641) 2026-06-10 19:21:45 +02:00
optional_deps.py fix(image): patch realesrgan torchvision compatibility (#4110) 2026-06-15 15:16:41 +09:00
pdf_form_doc.py refactor(tools): extract document tools to handle registry (#3666) 2026-06-10 10:41:52 +02:00
pdf_forms.py Odysseus v1.0 2026-05-31 23:58:26 +09:00
pdf_runtime.py Show a clear message when PyMuPDF is missing 2026-06-01 18:27:17 +09:00
personal_docs.py Skip malformed personal keyword index rows 2026-06-03 13:42:05 +09:00
preset_manager.py fix(presets): persist presets atomically to avoid corruption on crash (#2169) 2026-06-08 19:16:37 +02:00
prompt_security.py fix(security): harden untrusted_context_message against delimiter spoofing (#3086) 2026-06-07 22:15:50 +01:00
rag_manager.py refactor(constants): single source of truth for data dir (#3368) 2026-06-08 09:58:52 +02:00
rag_singleton.py refactor(constants): single source of truth for data dir (#3368) 2026-06-08 09:58:52 +02:00
rag_vector.py refactor(constants): single source of truth for data dir (#3368) 2026-06-08 09:58:52 +02:00
rate_limiter.py Odysseus v1.0 2026-05-31 23:58:26 +09:00
readiness.py feat: add /api/ready readiness probe (DB, data dir, local-first) (#1200) 2026-06-02 23:33:22 +09:00
request_models.py Odysseus v1.0 2026-05-31 23:58:26 +09:00
research_handler.py fix(research): migrate active task owners on rename (#3618) 2026-06-11 01:17:02 +02:00
research_utils.py Treat non-string research summaries as low quality 2026-06-03 13:42:24 +09:00
secret_storage.py refactor(constants): single source of truth for data dir (#3368) 2026-06-08 09:58:52 +02:00
service_health.py feat(diagnostics): add consolidated service health endpoint for degraded-state reporting (#964) 2026-06-09 16:00:24 +01:00
session_actions.py fix(sessions): keep fresh chats during auto tidy (#1871) 2026-06-09 01:06:20 +01:00
session_search.py fix(search): batch FTS hit lookups into one query (N+1) (#3909) 2026-06-11 16:31:54 +02:00
settings.py fix(agent): don't let a materialized default budget defeat context-window scaling (#4122) 2026-06-15 15:17:28 +09:00
settings_scrub.py fix(settings): scrub camelCase secret keys (#3707) 2026-06-11 12:53:33 +02:00
task_endpoint.py Harden session endpoint owner scope (#1308) 2026-06-03 02:40:22 +09:00
task_scheduler.py fix: drop thinking deltas from background agent loops 2026-06-15 15:03:09 +09:00
teacher_escalation.py fix: drop thinking deltas from background agent loops 2026-06-15 15:03:09 +09:00
text_helpers.py fix: normalize Gemma 4 thought-channel output (#2224) 2026-06-04 19:26:58 +02:00
tls_overrides.py Support extra CA bundle for private-CA LLM providers (#769) 2026-06-04 13:18:50 +01:00
tool_execution.py feat(agent): confine agent file/shell tools to a selectable workspace (#3665) 2026-06-11 18:17:54 +02:00
tool_implementations.py fix(calendar): treat same-day list_events range as full day 2026-06-15 15:09:19 +09:00
tool_index.py fix(agent): honor explicit web search requests 2026-06-15 15:02:10 +09:00
tool_parsing.py fix(agent): stop treating illustrative Markdown fences as tool calls for native function-calling models (#3356) 2026-06-08 22:25:28 +02:00
tool_policy.py fix(agent): enforce guide-only tool policy (#3088) 2026-06-06 18:48:24 -06:00
tool_schemas.py feat(agent): confine agent file/shell tools to a selectable workspace (#3665) 2026-06-11 18:17:54 +02:00
tool_security.py fix(agent): honor auth-disabled tool access after setup 2026-06-15 15:01:48 +09:00
tool_utils.py refactor(tools): consolidate duplicated _truncate and get_mcp_manager into src/tool_utils (#3478) 2026-06-09 01:05:30 +02:00
topic_analyzer.py Topics: hydrate session history before analysis 2026-06-02 20:44:27 +09:00
upload_handler.py fix(uploads): migrate upload ownership on rename (#3617) 2026-06-11 16:01:04 +02:00
upload_limits.py refactor(uploads): centralize upload byte-limits in upload_limits.py (#3364) (#3518) 2026-06-09 01:24:30 +02:00
url_safety.py fix: check_outbound_url crashes on a truthy non-string URL (#1623) 2026-06-03 08:59:49 +09:00
url_security.py Reapply "Merge branch 'main' of github.com:pewdiepie-archdaemon/odysseus" 2026-06-03 22:47:00 +09:00
user_time.py fix(chat): stabilize system prompt, sequence memory extraction, and send stable session id to preserve KV cache (#3360) 2026-06-09 22:46:54 +01:00
visual_report.py security: sanitize rendered research-report HTML (#364) 2026-06-04 13:42:49 +01:00
webhook_manager.py fix(webhooks): keep references to in-flight delivery tasks (#3859) 2026-06-11 15:53:52 +02:00
youtube_handler.py fix(youtube): consolidate duplicate handler 2026-06-15 15:03:41 +09:00