fix: pre-v1-style lifecycle resilience — mailbox delivery, uniform revival, unexitable runner, waiting timeout, broader retries, crash-safe identity - #923
Conversation
…guardrail errors in the TUI
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
Greptile SummaryRestores resilient agent lifecycle handling across execution, persistence, messaging, retries, and the TUI.
Confidence Score: 4/5The PR should not merge until an error-parked agent can adopt a changed model configuration when the user attempts recovery. The current wake path reuses the startup Files Needing Attention: strix/core/execution.py, strix/core/runner.py Important Files Changed
Reviews (4): Last reviewed commit: "fix: salvage a crashed run's full histor..." | Re-trigger Greptile |
| prompt_cache=settings.llm.prompt_cache, | ||
| ) | ||
| logger.info("model switched on wake: %s -> %s", current_model, new_model) | ||
| return dataclasses.replace(run_config, model=new_model, model_settings=model_settings) |
There was a problem hiding this comment.
When a parked agent switches models, this replacement updates RunConfig.model but leaves the existing ReportUsageHooks instance bound to the startup model, causing subsequent token usage and telemetry to be attributed to the wrong model.
Knowledge Base Used:
Prompt To Fix With AI
This is a comment left during a code review.
Path: strix/core/execution.py
Line: 86
Comment:
**Usage hook retains old model**
When a parked agent switches models, this replacement updates `RunConfig.model` but leaves the existing `ReportUsageHooks` instance bound to the startup model, causing subsequent token usage and telemetry to be attributed to the wrong model.
**Knowledge Base Used:**
- [Core Execution and Sessions](https://app.greptile.com/strix-org-3/-/custom-context/knowledge-base/usestrix/strix/-/docs/core-execution-and-sessions.md)
- [Configuration and Telemetry](https://app.greptile.com/strix-org-3/-/custom-context/knowledge-base/usestrix/strix/-/docs/telemetry-and-config.md)
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.| configure_sdk_model_defaults(settings) | ||
| model_settings = make_model_settings( | ||
| settings.llm.reasoning_effort, | ||
| model_name=new_model, | ||
| force_required_tool_choice=settings.llm.force_required_tool_choice, | ||
| request_timeout=settings.llm.timeout, | ||
| prompt_cache=settings.llm.prompt_cache, | ||
| ) | ||
| logger.info("model switched on wake: %s -> %s", current_model, new_model) | ||
| return dataclasses.replace(run_config, model=new_model, model_settings=model_settings) |
There was a problem hiding this comment.
Tool schema remains model-stale
When the replacement model requires a different tool schema, this wake path updates the model without rebuilding the existing agent or captured child-agent factory, causing resumed or newly spawned agents to send incompatible tool definitions and have tool requests rejected.
Knowledge Base Used:
- CLI and Runner: from
strixinvocation to a running scan - Core Execution and Sessions
- Configuration and Telemetry
Prompt To Fix With AI
This is a comment left during a code review.
Path: strix/core/execution.py
Line: 77-86
Comment:
**Tool schema remains model-stale**
When the replacement model requires a different tool schema, this wake path updates the model without rebuilding the existing agent or captured child-agent factory, causing resumed or newly spawned agents to send incompatible tool definitions and have tool requests rejected.
**Knowledge Base Used:**
- [CLI and Runner: from `strix` invocation to a running scan](https://app.greptile.com/strix-org-3/-/custom-context/knowledge-base/usestrix/strix/-/docs/cli-and-runner.md)
- [Core Execution and Sessions](https://app.greptile.com/strix-org-3/-/custom-context/knowledge-base/usestrix/strix/-/docs/core-execution-and-sessions.md)
- [Configuration and Telemetry](https://app.greptile.com/strix-org-3/-/custom-context/knowledge-base/usestrix/strix/-/docs/telemetry-and-config.md)
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.…evival, waiting timeout, broader retries
… loses no context
Runtime test results — PR #923 (scan lifecycle resilience) ✅Ran full smokes + a live end-to-end TUI scan (openrouter/claude-sonnet-4) against a local deliberately-vulnerable app, plus the crash-recovery path. All target behaviors verified. Smoke: Resilience: crash → visible park → user-revival → identity retainedAn injected first-cycle subagent crash parked the child with a visible red error (not silent) and the runner kept running. A user message revived the crashed agent (crashed → running), and the revived child kept its own seeded identity/task — confirmed in the SQLite session ( E2E happy path (root + subagent spawn → finding)Root "Strix" spawned child "XSS Testing Agent", which found and reported Reflected XSS on Note: the crash trigger is a temporary synthetic env-gated hook (reverted); the park/wake/identity-retention behavior exercised is the real production path. Tested via Devin. |
|
@greptileai please re-review — the last reviewed commit ( |
|
The 3/5 blocking concern (and the P1 comments about
The model-switch-on-wake recovery path was intentionally dropped earlier in this PR's history (guardrail failures are now treated like any other LLM error — no model switching). So "awakens parked agents with their original blocked model" is no longer reachable. The review appears to have re-surfaced line comments anchored to superseded diff hunks rather than the current HEAD ( |
|
The remaining 4/5 blocker — "should not merge until an error-parked agent can adopt a changed model configuration [on wake]" — is describing intended behavior, not a defect. This PR deliberately treats a guardrail/blocked-model failure like any other LLM error: it parks visibly with the error and is revived by a user message. Model-switching-on-wake (the old Given that, I'm not going to reintroduce the model-switch path. The stale P1 line comments (comments 3–5) are anchored to the removed |








Summary
Restores the pre-v1 (v0.8.x
BaseAgent.agent_loop) lifecycle resilience properties on top of the SDK-based runtime, so a scan can degrade but never wedge, dead-end, hide its errors, or lose an agent's context on a crash. Follow-up to #919 (freeze fix); this replaces the earlier guardrail-specific scope of this PR.1. Unhangable message delivery (mailbox).
coordinator.send()used to append to the target's SQLite session undersession_write_lockand could block forever (or drop the message when no session was attached) — a "delivered" message with no revival. Now:Mailboxes are included in the resume snapshot so queued messages survive a restart.
2. Uniform revival; statuses are labels, not lifecycles. Any user message now revives any park. Pre-v1 nuance kept: an error-parked agent (
failed/crashed) is only released by a user message (AgentRuntime.user_wake_required); peer messages stay queued until then, so agents can't wake a broken peer into a retry loop.3. The interactive runner can never die. Both interactive
_run_cyclecall sites go through_run_cycle_parked, which converts any escaped exception intoset_status(failed, error=...)+ parent notice instead of killing the runner task — so a wake always has a live loop to consume it (root cause of "crashed root ignored 11 user messages").4. Waiting-timeout auto-resume. A plainly-
waitingsubagent (no error, no user gate) auto-resumes after 10 minutes with a "Waiting timeout reached" note, like pre-v1'swaiting_timeout=600. The interactive root still waits forever.5. Pre-v1 retry breadth.
_is_transient_model_errornow retries anythinglitellm._should_retry(status_code)approves or statusless provider errors (5 attempts, backoff capped at 90s, ~62s total — was 4/30s). Rate limits (429) are retryable again; DNS/OSError/ConnectionErrorare covered; content-guardrail rejections are deterministic and still not retried (they park with the error shown).6. Errors always visible. The TUI records the stored error into the agent's pane for any status carrying one (was
failed/crashed/waitingonly), and the status footer shows it in red with "Send message to resume".7. Crash-safe agent context (no more amnesiac revival). A revived agent could come back with an empty session — no identity, no task, no pre-crash turns — and drift into acting like the root orchestrator. Root cause is in the SDK's persistence timing: a streamed run's items are committed to the session only as each turn completes (
run_internal/run_loop.py), and the opening input is saved only at end-of-first-turn (_stream_input_persisted); under the sandbox runtime the "save input at run start" fast-path is skipped. So a model error mid-turn (guardrail block, connection storm, timeout) leaves the run's input + in-flight turn uncommitted, and on revival the loop resumes withinput=[]against a session missing them. Two-layer fix that stops depending on the SDK committing on our behalf:[]— exactly how resume already works.seed_initial_input()no-ops on a non-empty session (idempotent), andprepare_input_with_session([], session)then feeds the model the seeded history withappended=[](no double-persist).Applies to root and children, interactive and headless.
Tests: new regressions for mailbox delivery without a session, user-only revival of error parks, wait timeout, mailbox snapshot round-trip, park-instead-of-raise,
seed_initial_inputpersist/idempotency, arun_agent_loopfirst-turn-crash test (identity survives, cycle 1 gets[]), and_salvage_stream_to_session(full multi-turn history preserved; no-op when nothing new); retry-classification tests updated. 604 passed.Link to Devin session: https://app.devin.ai/sessions/0feae08cebc44891b762d11c55b0d9f7
Requested by: @0xallam