| 264 | // dropped content - the stock-Ollama small-num_ctx front-truncation that |
| 265 | // eats the system prompt first, which the >=95% tripwire above structurally |
| 266 | // cannot see when the configured window is larger than the served one. |
| 267 | budgetFloorWarned bool |
| 268 | ctxTruncationWarned bool |
| 269 | // lastPromptEstimate is the packer's char/4 token estimate of the request |
| 270 | // most recently sent, compared in applyDone against the server-reported |
| 271 | // prompt_tokens to detect silent server-side truncation. |
| 272 | lastPromptEstimate int |
| 273 | // turnActed is false while a turn has done nothing but read_file - a |
| 274 | // question answered out of the codebase, with no artifact that could be |
| 275 | // falsely called green. Deliberately "not read_file" rather than |
| 276 | // "write_file or edit_file": a file built with a bash heredoc, a `sed -i`, |
| 277 | // or an `npm init` is just as much an artifact, and telling those apart |
| 278 | // would mean classifying shell commands. Reset in endTurn. |
| 279 | turnActed bool |
| 280 | |
| 281 | // Empty-reply nudge, the third soft backstop. The two above catch doing-too- |
| 282 | // much; this catches a turn ending with nothing said and nothing called. A |
| 283 | // clean finish always carries a summary and a continuing turn always carries a |
| 284 | // tool call, so an empty newest assistant message is always an anomaly: the |
| 285 | // model stopped mid-task, or (on a thinking model) its tool call streamed into |
| 286 | // the reasoning channel and was dropped before reaching us, the dominant |
| 287 | // silent-death we'd otherwise end on with no warning. One re-prompt to re-issue |
| 288 | // or finish; emptyNudged bounds CONSECUTIVE empties to a single retry - a |
| 289 | // round that issues a tool call re-arms it (see handleStreamClosed), so a |
| 290 | // flaky stream earns a fresh re-prompt per stall while a server that |
| 291 | // deterministically swallows every call can't loop. Reset in endTurn. |
| 292 | emptyNudged bool |
| 293 | |
| 294 | // Finish re-grounding nudge, the fourth soft backstop. The three above catch |
| 295 | // doing-too-much (failure, runaway) and stopping-with-nothing-said (empty). |
| 296 | // This catches the false-green finish: a turn that did real work ending with a |
| 297 | // confident summary for something it never actually ran. When a substantial |
| 298 | // turn (llmRounds >= verifyNudgeMinRounds, or toolRounds >= verifyNudgeMinCalls) |
| 299 | // is about to finish with a clean, |
| 300 | // non-empty reply, one re-prompt makes the model re-walk the original request |
| 301 | // and run the check that proves each runnable part, or mark it unverified |
| 302 | // honestly, instead of dressing up a brace-count or an HTTP 200 as proof. A |
| 303 | // nudge, never a hard yield; verifyNudged latches it to once per turn. Reset in |
| 304 | // endTurn. |
| 305 | verifyNudged bool |
| 306 | |
| 307 | // liveContextSize is the per-profile, runtime-only context window the |
| 308 | // server reports via X-Context-Window. Seeded by Probe at activation and |
| 309 | // refreshed on every chat EventDone, so a server-side change applies on the |
| 310 | // next prompt without a restart. Authoritative for cloud profiles (whose |
| 311 | // on-disk ContextSize is intentionally empty); for user-managed profiles |
| 312 | // it's empty and packing falls back to Profile.ContextSize. Never persisted. |
| 313 | liveContextSize map[string]int |
| 314 | } |