Skip to content

[GUI] Agent turn is ended after 4 minutes while the local engine is still reading a long prompt (the watchdog ignores engine progress) #3623

Closed

Description

@tomamama022-art

Area

[GUI] (the desktop app)

Desktop app, Terminal or source?

Desktop app (Windows, Linux or Mac)

What happened

In an agent (coding assistant) chat with a local model, the turn was ended with:

The assistant stopped answering for 4 minutes, so I ended this turn. Anything it already finished is kept. Send your message again to go on.

The model had not stopped. The engine was reading a 59,091-token prompt. Only 6,154 tokens came from the prefix cache, and it had read 52,932 of the remaining 52,937 at 208 tok/s when the turn was cancelled at 255 s:

strata serve: prompt 59091 tokens = 6154 reused + 52932 of 52937 read in 254492 ms (208.0 tok/s), 0 generated in 0 ms (0.0 tok/s), drafts accepted 0 of 0, 4 checkpoints (cancelled)

The request just before it (85,480 tokens: 68,036 reused, 17,444 read in 95 s) finished normally. The engine was healthy. The cache miss is what made this read take longer than 4 minutes.

The app's progress lines for the request (one per 6,144-token chunk, about 29 s each)
[strata] reading the prompt: 59,091 tokens, 10 s so far
[strata] reading the prompt: 12,298 of 59,091 tokens, 28 s so far
...
[strata] reading the prompt: 55,306 of 59,091 tokens, 243 s so far
[strata] done: 0 tokens in 255 s (0.0 tok/s) (cancel, cancel=True)

Expected: while the engine is making progress on the prompt, the turn keeps waiting. It should only be ended when neither the Harness nor the engine has moved for the stall time.

Why it happens (from reading the code)

Two timers can end a healthy request, and neither sees the engine's keep-alives:

  1. The agent watchdog hears only the Harness. In apps/host/server/agent/agent-bridge.mjs, turn.watchdog ends the turn when Date.now() - turn.lastActivity >= stallMs. stallMs is 4 min, and there is no setting or environment variable for it. turn.lastActivity is updated only for Harness items (handle, fold). While the engine reads a prompt it sends only : keep-alive SSE comments, which the OpenAI client drops. So the Harness stays silent, and any prompt read longer than 4 minutes ends the turn.

    serve already handles this case: engine_silence_s is extended by prompt-progress lines (serve engine deadlocks in SleepConditionVariableSRW during a long streaming reply; stall watchdog never fires #481), and STRATA_ENGINE_STALL_S only ends an engine that also uses no CPU, disk or GPU (serve: an untimed read of the engine/vision pipe can hold the request turn forever (intermittent hang; the #481 lost-step class) #1317). The host also already polls the engine's progress every 2 s (/metrics → live.state, live.prompt_read, live.generated in native-control.mjs). The watchdog just does not use it.

  2. The Harness's LLM client has the same blind spot. @deepseek-ai/dsh-llm-pi-ai drops a request after streamIdleTimeoutMs (default 300000) without a stream event. The error is pi-ai stream idle timeout after 300000ms, code TIMEOUT. openai/core/streaming.js drops lines that start with :, so neither the queue beats nor the prompt-progress comments reset that timer. apps/host/server/agent/profile.mjs sets no value for local providers. With the defaults the watchdog fires first, so fixing (1) alone only moves the cut-off from 4 to 5 minutes. That is why both are in one report. It is the keep-alive-as-comment problem of serve: keep-alive is an SSE comment, so clients that drop comments still hit their stream idle timeout and retry #1951 / PR serve: the keep-alive while a prompt is read is a real SSE frame, not a comment #1953 (closed, not merged), but for Strata's own agent.

    In a 3.5 h run with the timeout raised to 15 min (patch below), the two helpers' sessions show 16 llm/retry events, all with pi-ai stream idle timeout after 900000ms (code TIMEOUT). Seven of the gaps between one retry and the next are 900–902 s, so the retried request timed out again after exactly 15 min. With one slot, a helper's request first waits behind the other agents' turns and then reads its own prompt, so it hears nothing for more than 15 min. For 15 of the 16 retries, the engine log shows the dropped request as (cancelled) with 0 tokens generated, 153 to 752 s into the read. Each was then sent again and read from the start, which took about 1.8 h of the 3.5 h of engine time. A fixed timeout does not fit this case, because the wait grows with the number of agents that share a slot.

Local patch I am running (works for me; not proposed as the fix)

Diff (hand-trimmed to the changed lines; not meant for git apply)
diff --git a/apps/host/server/agent/agent-bridge.mjs b/apps/host/server/agent/agent-bridge.mjs
--- a/apps/host/server/agent/agent-bridge.mjs
+++ b/apps/host/server/agent/agent-bridge.mjs
@@ export function createAgentBridge(...) @@
-export function createAgentBridge({ ..., expertSampling = () => null, stallMs = 4 * 60_000, stallToolMs = 20 * 60_000, ... }) {
+export function createAgentBridge({ ..., expertSampling = () => null, engineProgress = () => null, engineGraceMs = 30 * 60_000, stallMs = 4 * 60_000, stallToolMs = 20 * 60_000, ... }) {
@@ turn.watchdog = setInterval(() => { ... @@
       const limit = toolRunning ? stallToolMs : stallMs;
+      // A local engine reading a long prompt sends the Harness nothing for minutes. Its own progress
+      // (engineProgress: the host's /metrics poll) counts as activity, so only an engine that is idle or stuck lets the turn stall.
+      // It counts for at most engineGraceMs after the Harness was last heard: progress for another chat cannot keep a lost turn forever.
+      if (route.kind === 'local') {
+        if (turn.lastActivity !== turn.engineBump) turn.heard = turn.lastActivity;   // the Harness moved it since the engine last did
+        let mark = null;
+        try { mark = engineProgress(route.instanceId); } catch { /* no progress known: judged by the Harness alone */ }
+        if (mark != null && mark !== turn.engineMark && Date.now() - turn.heard < engineGraceMs) {
+          turn.engineMark = mark; turn.lastActivity = turn.engineBump = Date.now(); return;
+        }
+      }
       if (Date.now() - turn.lastActivity < limit) return;
diff --git a/apps/host/server/index.mjs b/apps/host/server/index.mjs
--- a/apps/host/server/index.mjs
+++ b/apps/host/server/index.mjs
@@ const agent = createAgentBridge({ @@
   expertSampling: () => samplingFromExpert(store.get().expert),
+  // How far a local engine has got (the 2 s /metrics poll), for the agent's stall watchdog. It moves while
+  // a long prompt is read, when the agent hears nothing; null when the engine is idle or says nothing.
+  engineProgress: instanceId => {
+    const live = native.readyInstances().find(i => i.id === instanceId)?.metrics?.live;
+    if (!live || live.state === 'idle' || live.state === 'unloaded') return null;
+    const slots = Array.isArray(live.slots) ? live.slots.map(s => `${s?.state}:${s?.prompt_read ?? ''}:${s?.generated ?? ''}`).join(',') : '';
+    return `${live.state}|${live.prompt_read ?? ''}|${live.generated ?? ''}|${slots}`;
+  },
diff --git a/apps/host/server/agent/profile.mjs b/apps/host/server/agent/profile.mjs
--- a/apps/host/server/agent/profile.mjs
+++ b/apps/host/server/agent/profile.mjs
@@ local.forEach((inst, index) => { ... providers[route] = { @@
       baseURL: inst.baseUrl,
       apiKeyEnv: keyEnv,
+      // A long prompt read sends only SSE comments, which do not reset the 300 s default idle timer.
+      // 60 min: with helpers taking turns on one slot, a request waits for the others and then reads its own prompt (15 min was
+      // exceeded 16 times in one run); a hung engine is ended by serve itself, and a lost turn by the bridge's watchdog.
+      streamIdleTimeoutMs: 60 * 60_000,

Validation (the patch applied to the installed 0.2.1 files on this PC)

Passed:

  • node --check with Strata's bundled Node (v24.12.0) on host/server/agent/agent-bridge.mjs, host/server/index.mjs and host/server/agent/profile.mjs: exit 0.
  • buildProfile() from the patched profile.mjs, called from a small script with one local instance: streamIdleTimeoutMs: 3600000 on strata-local and on all four strata-local~sN routes.
  • A script that takes the patched watchdog block and engineProgress from the installed files and runs them on a simulated clock: 11 of 11 cases pass (list below).
  • In the app, the 3.5 h agent run above (one parent and two helpers on this one-slot engine, watchdog patch with a 15 min streamIdleTimeoutMs): 23 prompt reads took 240 s or more, 11 of them finished normally (the longest 483 s), and the watchdog did not end the turn once.
  • In the app, the next run with 60 min (2.7 h of engine time, 52 requests): 6 prompt reads took 240 s or more and all finished, with 0 cancelled reads and 0 llm/retry. I turned on the engine's conversation cache (--conversation-cache-mib 4096) at the same time, which also shortens the waits.

Failed:

  • 15 min for streamIdleTimeoutMs was not enough: 16 llm/retry in the 3.5 h run, as described in (2).

Not run:

  • Strata 0.2.2 / engine 0.1.47, Linux and macOS.
  • apps/host/tests/agent-watchdog.test.mjs (it needs a built Harness in STRATA_HARNESS_DIR).
  • API-model routes in the app (covered only by the simulation).
  • The diff against main. It was made against the installed 0.2.1 files.
The 11 simulated-clock cases
  • Do not stall: a 12-minute prompt read followed by generating; batch slots that keep moving.
  • Stall at 240 s, as before: a read whose prompt_read never moves; a read with no progress line yet; an idle engine; an unloaded engine; missing metrics; an API route.
  • The cap: an engine that keeps moving while the Harness stays silent stalls at 2025 s (30 min + 4 min). An item from the Harness at 25 min restarts the cap (stall at 3525 s). Harness items every 10 min: no stall.
  • Known limit: prompt_read moves once per prompt chunk, so a single chunk that takes more than 4 minutes would still end the turn. Here chunks took about 29 s.
  • A test in the style of apps/host/tests/agent-watchdog.test.mjs could cover this: a scripted worker that stays silent for longer than stallMs while engineProgress keeps changing should not end the turn.

Smallest steps or command that reproduce it

Seen once. I have not re-run it on purpose yet.

  1. Desktop app, agent chat with the local model below, in a long session with many tool calls (the prompt had grown to ~59k tokens).
  2. Branch the chat and send a message in the branch. The prefix cache matched 6,154 tokens, so 52,937 tokens had to be read again at ~208 tok/s (about 254 s).
  3. At 255 s the turn ended with the message above. The engine log shows the read still progressing until the cancel.

The trigger (a long re-read after a cache miss) looks related to #3493. The failure itself (the agent watchdog) is separate.

Reproduction status

Reproduced locally

Strata version

Strata 0.2.1 (desktop app) · engine 0.1.46.1 (windows-x86_64-hip-rdna3) · DeepSeek Harness (@deepseek-ai/dsh) 0.2.1-alpha.3. I have not run 0.2.2. As of 2026-10-22, main still has stallMs = 4 * 60_000 with turn.lastActivity set only from Harness items, and profile.mjs sets no streamIdleTimeoutMs.

Graphics card, memory and system

AMD Ryzen AI MAX+ 395 with Radeon 8060S (gfx1151, HIP). The GPU is integrated: Windows reports 512 MiB of dedicated GPU memory (the BIOS carve-out), and the engine works from the 128 GB of unified memory (127.1 GiB visible to Windows). Windows 11 (build 26300).

Model, size and settings

Qwen3.8-Flash-Next UD-Q4_K_XL (Unsloth) · 131072 context (--kv int8 --kv-resident 32768) · pictures on · thinking Max (budget 24576 of max_tokens 32768) · parallel 1

Logs

The excerpts are above. Related: #481, #1317, #1619, #1951, PR #1953, #3493.


Investigated and drafted with Claude Code (code reading, log analysis and writing). The numbers and log lines come from the logs on my machine and were cross-checked against them.

Activity

  1. bghyt commented on Oct 21, 2026

    @bghyt

    I have the same issue, i gave it a prompt and it was doing work, after 4 mins of thinking it just stopped it and ruined all my progress

  2. wright802 commented on Oct 22, 2026

    @wright802

    I've been having this for a couple of days now as well. v0.2.1 and now v0.2.2, Linux. After the processing stops, in the prompt box, I've had fair success in typing continue -- about half the time it will pick up where it left off as if nothing happened. But right now it has this issue and it can't continue (I get the same message after another 4 minutes have passed) so I'll start a new session.

    EDIT: For anyone that might be following this... try switching to lower thinking. I was on High, changed to Medium, typed Continue, and it powered through and kept working my task. I switched back to thinking High after about 5 minutes, I have no idea if that mid-stream change back to High was accepted, token speed never changed much.

    I haven't researched why, this analysis by tomamamma seems rigorous, but if you need me to try something, LMK. Exact message pasted below.

    The assistant stopped answering for 4 minutes, so I ended this turn. Anything it already finished is kept. Send your message again to go on. Try sending the message again.

  3. Penguinehis commented on Oct 22, 2026

    @Penguinehis

    here i needed to roll back to the beta, the V0.2.2 engine stops even on my custom agent making it impossible to continue long sessions

  4. xvcezrqpbxtpzblrt commented on Oct 22, 2026

    @xvcezrqpbxtpzblrt
    Contributor

    Same 4-minute watchdog on 0.2.2, and a restart makes it reproducible every time.

    After restarting the desktop app, the existing 315,993-token chat cannot continue. The first message after the restart re-reads the whole prompt with 0 cache reuse; the turn is cancelled at 259 s while the engine is still reading:

    [strata] reading the prompt: 6,144 of 315,993 tokens, 6 s so far
    ...
    [strata] reading the prompt: 245,760 of 315,993 tokens, 251 s so far
    [strata] done: 0 tokens in 259 s (no speed) (cancel, cancel=True)
    

    Engine log for the same request:

    strata serve: prompt 315993 tokens = 0 reused + 251904 of 315993 read in 258671 ms (973.8 tok/s), 0 generated in 0 ms (0.0 tok/s), drafts accepted 0 of 0, 6 checkpoints (cancelled)
    

    The restart was at 00:29:54, the engine started at 00:31:24, and the request was cancelled at 259 s. The app log is %LOCALAPPDATA%\Strata\runtime\app-1.server.log; the engine log is %LOCALAPPDATA%\Strata\install\strata-swift-iq3_xxs.log.

    Before the restart the same chat was fast because the cache was warm: requests of 308K-315K tokens reused 308K-315K and read only 27-653 tokens in 0.5-3 s. After the restart, the cold reread needs about 324 s at 973.8 tok/s, so the 4-minute watchdog always fires first. Retrying starts the same cold read again, so the chat is effectively stuck after a restart.

    The installed 0.2.2 still has stallMs = 4 * 60_000 and turn.lastActivity updated only from Harness items (host/server/agent/agent-bridge.mjs:118, :427-437). This is the same bug as the report, with restart/cold-cache as the trigger. A fix that counts the engine's /metrics progress (or the prompt-progress keep-alives) would cover it.

    For anyone hitting this: keep the app open (warm cache), or start a new chat / branch so the prompt is below about 230K tokens, which fits in 4 minutes at this prefill speed. There is no setting for the watchdog.

    Environment: Strata 0.2.2, engine 0.1.47, Windows 11 10.0.26300, RTX 3080 Ti Laptop 16 GB, 64 GB RAM, Swift 1.5 IQ3_XXS, max-context 524288 (the chat is 315,993 tokens).

  5. Niko1221 commented on Oct 24, 2026

    @Niko1221
    Owner

    This is fixed in Strata 0.2.3. Please update to the latest release (in the Terminal: strata update; in the app: download the newest installer from the releases page). If it still happens on the latest version, reply here and we will reopen it. Thank you for the report.

  6. wright802 commented on Oct 27, 2026

    @wright802

    working great! Thank you for all you are doing -- Strata is remarkable!

  7. VikenErdogin commented on Oct 30, 2026

    @VikenErdogin

    Unfortunately this is still an issue. I just updated to 0.2.8 today (both Stra App on Windows and Strata server which runs on my separate server within my local network) and I still get "The assistant stopped answering for 4 minutes, [...]" errors. At the moment I cannot provide additional details due to personal time constraints but I will try to look into this further, starting with launching the same test run on Hermes and Unsloth in order to determine if it is server, Strata App or both and how my task and workload might affect or trigger it.

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

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions