Repository navigation
feat(runtime): resume interrupted runs safely instead of parking them #4424
Description
Recovery classification is precise, but everything it learns is discarded at the UI.
resolveRuntimeRecovery distinguishes completed / parked / definitely_not_dispatched / indeterminate / corruption, and buildResumePlanFromRuntimeEvents turns any unresolved operation — including nested calls the model never sees — into disposition: 'blocked'. runtime-kernel.ts:3107 then refuses to replay. That part is right: an unproven tool boundary must not be replayed silently.
What happens next is not. runtime-resume-copy.ts carries 30 distinct park reasons, each with its own message, and use-shell-resume.ts:67 collapses all of them into one error toast — "暂时无法继续这一轮" — with the action slot left undefined. The user is told the run cannot continue and is given nothing to do about it.
Failing loud is correct. Stopping there is not. An agent's job is to finish the task, not to hand the user an accurate error and let them figure it out.
What this should become:
- Resume automatically whenever it is provably safe.
definitely_not_dispatchedalready proves a tool never ran — those should replay without asking anyone. - For
indeterminate, verify instead of giving up. We know which tool, which arguments, and which operation is unsettled. Checking the external state is often cheap and decidable. - Ask the user only when verification is genuinely impossible, and then ask about the specific operation — not with a generic toast, but with the choices that matter: I checked, continue / discard this turn / let me look first.
The classification data needed for all three already exists. Only the path from a decision to a user-visible resolution is missing.
Related: the rejection reasons are also flattened before they reach any of this — see #4423.
@M4n5ter @likun666661 — I'd like your read on this before anyone starts.
My position: failing loud is right, and I don't want to give that up. But stopping at "here is an accurate error, you figure it out" is not a defensible product state. An agent exists to finish the task. The recovery classification already tells us which operations are provably safe and which are genuinely unknown; we should be replaying the first group automatically and actively verifying the second, and only asking the user when verification is truly impossible.
Where I'm unsure is the middle step — what verifying external state should mean in general, given that the recovery ADR explicitly declines to derive absolute external-world state. Happy to be pushed back on any of it.
简体中文
恢复判定做得很精细,但它得出的结论在 UI 层被丢掉了。
resolveRuntimeRecovery 能区分 completed / parked / definitely_not_dispatched / indeterminate / corruption,任何未结算的操作(包括模型看不见的嵌套调用)都会让计划变成 blocked,runtime-kernel.ts:3107 随即拒绝回放。这部分是对的:不能证明终态的工具边界不该被静默重放。
问题在下一步。runtime-resume-copy.ts 里有 30 种不同的 park 原因,各有各的文案,而 use-shell-resume.ts:67 把它们全部收敛成一个 error toast「暂时无法继续这一轮」,action 位是 undefined。用户被告知不能继续,然后没有任何可做的事。
Fail loud 是对的,停在这里不对。Agent 的价值是完成任务,不是把一个准确的错误丢给用户,让用户自己想办法。
应该变成:
- 能证明安全就自动续跑。
definitely_not_dispatched已经证明工具没执行过,这类根本不需要问任何人。 indeterminate应该去核实,而不是放弃。 我们知道是哪个工具、什么参数、哪个操作没结算,去检查外部状态往往成本很低且能得出结论。- 只有确实无法核实时才问用户,而且要针对那个具体操作问,给出真正有意义的选项:我确认过了继续 / 放弃这一轮 / 我先看看。
这三步需要的判定数据都已经有了,缺的只是从判定到用户侧结论的那条路径。
@M4n5ter @likun666661 —— 想先听听你们的看法再动手。
我的立场:fail loud 是对的,这点不想放弃。但停在「给你一个准确的错误,你自己想办法」不是一个站得住的产品状态。Agent 的存在就是为了完成任务。 恢复判定已经能告诉我们哪些操作可证明安全、哪些是真的未知;前者应该自动重放,后者应该主动核实,只有确实无法核实时才问用户。
我不确定的是中间那一步 —— 「核实外部状态」在通用意义上到底该是什么,毕竟 recovery ADR 明确声明不承诺推导外部世界的绝对状态。欢迎在任何一点上反驳。
Activity
namanjain24-sudo commentedon Sep 1, 2026
namanjain24-sudo commentedon Sep 1, 2026
M4n5ter commentedon Sep 1, 2026
English
@zhiiw @likun666661 — since #1223, #1521, and #1573 established the recovery and continuation contracts, could you sanity-check this interpretation?
The UX problem is real, but the proposed solution appears to skip over the missing production reconciliation layer.
definitely_not_dispatchedmay be eligible for automatic continuation, but not direct replay of the old tool call. It should still pass the existing workspace, tool-catalog, lineage, claim, and provider-replay gates, then create a continuation with fresh identity. This overlaps with fix(runtime): resume rejects provably-undispatched tools as dangling state #4423.indeterminatecannot be handled by a generic external-state verification hook. Current state does not prove causality. The existing design requires tool-specific durable evidence prepared before dispatch, a trusted reconciler, and an atomic recovery bundle. Without those facts, parking remains correct.A user click cannot resolve an unknown historical outcome. “Continue” must not reopen the old run or silently retry it. Useful actions would be inspecting the unsettled operation, starting a new read-only investigation turn, or explicitly authorizing a new action with fresh Run/Turn identity.
There is also more than one information-loss boundary: diagnostics are collapsed into
dangling_tool_state, then Runtime Host collapses many reasons intosafety_check_failed. The failed-turn banner still has a resume button, but after parking it only repeats the same query.I suggest narrowing this issue to:
definitely_not_dispatched;Runtime Resume Architecture · RecoveryResolver ADR
中文
@zhiiw @likun666661 —— #1223、#1521 和 #1573 建立了 recovery 与 continuation 契约,麻烦确认一下下面的理解是否准确。
这里指出的 UX 问题真实存在,但当前方案似乎跳过了尚未实现的 production reconciliation 层。
definitely_not_dispatched可能适合自动 continuation,但不能直接重放旧 tool call。 它仍应通过现有 workspace、tool catalog、lineage、claim 和 provider replay 检查,然后创建具有全新 identity 的 continuation。这部分也与 fix(runtime): resume rejects provably-undispatched tools as dangling state #4423 重叠。indeterminate不能通过通用的外部状态 verification hook 解决。 当前状态不能证明因果关系。现有设计要求派发前准备 tool-specific durable evidence,由可信 reconciler 核实,并原子提交 recovery bundle。没有这些事实时,parking 仍然正确。用户点击不能解决未知的历史结果。 “Continue”不能重新打开旧 run 或静默重试。合理操作应是查看未决 operation、启动新的只读调查 turn,或者明确授权一个具有全新 Run/Turn identity 的新操作。
信息丢失也不只发生在 toast 层:diagnostic 先被压成
dangling_tool_state,Runtime Host 又将大量原因压成safety_check_failed。失败 turn 上仍有 resume 按钮,但 park 后只会重复相同查询。建议将本 issue 收窄为:
definitely_not_dispatched的 safe-boundary continuation;Runtime Resume Architecture · RecoveryResolver ADR
CuSO41108 commentedon Sep 1, 2026
Hi @M4n5ter,
I traced the current resume path from RecoveryResolver through the Runtime planner, Runtime Host protocol, Desktop IPC, and the interrupted-turn UI.
I understand that definitely_not_dispatched must not directly replay the old tool call, while indeterminate should remain parked unless a tool-specific durable recovery contract exists. I also noticed that #4423 already covers the tool_not_dispatched rejection classification, so I don’t want to duplicate that work.
My previous contribution, #3602, focused on Runtime Host lifecycle fencing and race-oriented regression tests. I would be happy to take a bounded slice of #4424, such as:
Which slice would be most useful, and should it be tracked as a separate child issue? I’ll wait for the acceptance criteria and ownership to be confirmed before starting implementation.
M4n5ter commentedon Sep 7, 2026
English
Reassessed alongside #4958: this issue still has a concrete user-facing gap and should remain open, scoped to actionable recovery for parked runs, not a generic replay engine.
At candidate commit
9a9f76f0f,use-shell-resume.tsstill handlesparkby retaining a description and showing an error toast with a session diagnostic target;app-shell.tsxcontinues to expose the same resume action. There is no operation-specific resolution flow in that handler. A diagnostics destination is useful, but it does not by itself provide a fresh read-only investigation or explicit new-action path for an unsettled operation.Keep the remaining work bounded: preserve safe structured details of the unsettled operation, explain why the old run remains parked, and offer an appropriate new investigation/action without treating the unknown historical result as resolved. #4423 owns the undispatched classification distinction; #4319 owns tool-specific evidence-backed Write/Edit reconciliation. #4958's cooperative handoff does not replace these crash-recovery UX requirements.
AI assistance: Codex assisted with the current-code and scope assessment.
简体中文
结合 #4958 重新评估后,本 issue 仍有具体的用户体验缺口,应保持开启,聚焦 parked 执行的可操作恢复体验,而不是通用重放引擎。
在候选提交
9a9f76f0f中,use-shell-resume.ts对park的处理仍是保留描述并显示带会话诊断入口的错误 toast;app-shell.tsx继续展示同一个 resume 动作。该处理函数没有针对具体操作的解决流程。诊断入口有价值,但它本身并未为未决操作提供新的只读调查或显式新操作路径。剩余工作应保持有界:保留可安全展示的未决操作结构化信息,解释旧 run 为什么继续 parked,并提供适当的新调查/操作入口,而不将未知的历史结果视为已解决。#4423 负责未派发状态的分类区分;#4319 负责基于工具专属证据的 Write/Edit reconciliation。#4958 的协作式交接不能替代这些崩溃恢复体验要求。
AI 辅助:Codex 协助了当前代码与范围评估。
namanjain24-sudo commentedon Sep 11, 2026
@M4n5ter thanks for the detailed answer, and apologies for the long silence on my side.
You answered my tier-2 question directly:
indeterminatecannot be resolved by a generic external-state verification hook, because current state does not prove causality. That hook was exactly what I was proposing, so I'm withdrawing it — without tool-specific durable evidence prepared before dispatch, a trusted reconciler and an atomic recovery bundle, parking stays the correct behaviour.Your 2026-09-07 reassessment also reads right to me: keep this issue scoped to actionable recovery for parked runs, leave the undispatched classification to #4423 and evidence-backed Write/Edit reconciliation to #4319, and don't let #4958's cooperative handoff absorb the crash-recovery UX.
If that narrowed scope is still unclaimed, I'd like to take it — preserve the safe structured details of the unsettled operation, explain in the UI why the old run stays parked, and replace the current resume action (which today just repeats the same query) with an explicit read-only investigation or new-action path, without treating the unknown historical result as resolved. That's
use-shell-resume.tsandapp-shell.tsx, plus whatever detail is currently collapsed intodangling_tool_state.If someone is already on it, or you'd rather this waited until #4423 lands, say so and I'll stay off it.
github-actions commentedon Oct 12, 2026
This issue has had no human activity for 30 days and has been marked stale. It will be closed in 30 days unless someone comments.
If the issue is still current, please confirm it against the latest
mainand add any information that would help move it forward. Assigned issues and issues labelledpinnedare exempt from this policy.