Skip to content

Cap MessageText so a long question cannot overrun the model's window #1943

Closed

Description

@MODSetter

The gap

docs/architecture/chat.md lists this under Known gaps:

budget.py prices the question at 1,024 tokens and says MessageText enforces that, but MessageText has no length limit, so a long question can push a turn past the model's window.

Why it matters

The whole budget is built on four parts adding up to the model's window: the system prompt, the excerpts, the question and the answer reserve. History is whatever is left. Three of the four are real caps. The question is not — MessageText sets a minimum length and nothing else, so a pasted page of text walks straight past its 1,024-token share.

What the user sees is not an error. llama.cpp stops generating wherever the window runs out, so a long question buys a reply that is cut off mid-sentence, or on a tight window no reply at all. The budget's own comment says this is handled at the wire, which is the kind of wrong that survives review because it reads as a fact.

Where

What done looks like

  • A message longer than the question's share is refused at the wire with a 422, before any model is resolved or any retrieval runs.
  • You pick the bound and you defend it in the PR. A character cap derived from 1,024 tokens is the obvious reading, but note the tension: the same cap applies to a remote model with a 128k window, where the question would have fit fine. Choosing one global cap over a window-aware one is a real decision, and a one-line comment saying which you chose and why is what the next reader needs.
  • The frontend composer should not let someone type past the limit and only find out on send. chat-composer.tsx is where that lives; if you add a message there, it is an interface string.
  • Delete the Known gaps line from docs/architecture/chat.md.

What not to change

This gap is listed in two docs. docs/architecture/local-models/fit.md carries the same line under its own Known gaps, and its budget table describes the question's 1,024 as "A share, not a limit (see Known gaps)". Both that Known gaps line and that table note have to change in this PR too, or the docs will contradict each other the day it merges. Do not open a second issue for the fit.md line; it is this one.

How to verify

surfsense_local/backend/tests/unit/chat/test_budget.py holds the budget's unit tests and is where the "the question cannot exceed its share" test belongs. The 422 belongs in tests/integration/chat/test_chat.py. Write the failing test first.

cd surfsense_local/backend
uv run pytest -m unit
uv run pytest -m integration -k chat
uv run ruff check .
# from the repo root, because the PR edits two docs
cd ../.. && python scripts/check_docs.py

Before you start

Read CONTRIBUTING.md, the chat doc and the budget section of the fit doc. If you add a composer message, interface strings in surfsense_local follow the translate skill described in AGENTS.md. Open your PR against dev, not main. Comment here to claim this.

Activity

  1. added
    bugSomething isn't working
    help wantedExtra attention is needed
    desktopSurfSense desktop app (surfsense_local)
    on Sep 28, 2026
  2. ybai08 commented on Sep 29, 2026

    @ybai08
    Contributor

    I'd like to take this one.

    Plan: failing tests first, a test_budget.py case that the question cannot exceed its 1,024-token share and a test_chat.py case that an oversized message is refused with 422 before any model or retrieval runs. Then bound MessageText with a character cap derived from QUESTION_TOKENS through the existing _tokens() ratio, so the comment in budget.py becomes true, and mirror the limit in chat-composer.tsx so the composer says so before send. Both Known gaps lines (chat.md, fit.md) and the fit table note go in the same PR, against dev.

  3. added a commit that references this issue on Sep 29, 2026
    b69a6ca
  4. MODSetter commented on Sep 29, 2026

    @MODSetter
    OwnerAuthor

    Fixed by #2053, fix(local): cap a chat question at its share of the window, by @ybai08. Merged into dev.

    Closing by hand rather than by keyword: Fixes #1943 only auto-closes on a merge to the default branch, and this repo merges through dev (CONTRIBUTING), so this would have stayed open and claimable while the work was already done. It reaches main with the next release.

    Nothing left to pick up here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingdesktopSurfSense desktop app (surfsense_local)help wantedExtra attention is needed

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions