Repository navigation
[0.2.0-rc.2] 目标在"创建它的那一轮"结束时被驱动器自行 pause,roundsStarted 恒为 0,自动续行从未真正跑过一轮 #9622
Replies: 3 comments
附:两份补丁(可直接应用 / 移植)补丁针对发布产物 补丁 1(行为):回合结束不再因「抢占」而自行 pause目标文件: --- a/dsh-goal-round-driver/lib/index.js
+++ b/dsh-goal-round-driver/lib/index.js
@@ -216,7 +216,7 @@ function apply(ctx) {
state.competingQueued = false;
const attempt = state.attempt;
const goal = currentGoal(state);
- if (attempt !== void 0 && (attempt.phase === "queued" || attempt.phase === "claimed" || attempt.cancelled) && goal !== void 0 && goal.phase === "active" && goal.activation === "armed" && attempt.goalId === goal.id && attempt.revision === goal.revision) {
+ if (attempt !== void 0 && attempt.cancelled === true && attempt.preempted !== true && goal !== void 0 && goal.phase === "active" && goal.activation === "armed" && attempt.goalId === goal.id && attempt.revision === goal.revision) {
state.attempt = void 0;
try {
ctx.goals.pause(agent, goalRef(goal));
@@ -240,7 +240,7 @@ function apply(ctx) {
const attempt = state.attempt;
if (attempt !== void 0 && sameQueued(message.content, message.source, attempt)) return;
state.competingQueued = true;
- if (attempt?.phase === "queued") attempt.stale = true;
+ if (attempt?.phase === "queued") { attempt.stale = true; attempt.preempted = true; }
});
ctx.on("agent/inbox/claimed", ({ agent, message }) => {
const attempt = stateFor(agent).attempt;语义:原条件把「驱动器自身的陈旧预留」(
补丁 2(文案):两条「死路」错误信息指向可达出路目标文件: --- a/dsh-goal/lib/index.js
+++ b/dsh-goal/lib/index.js
@@ -636,7 +636,7 @@ let GoalService = (() => {
- if (current !== void 0 && current.phase !== "complete") throw new GoalError(`goal "${current.id}" already exists with phase "${current.phase}"`, "GOAL_ALREADY_EXISTS");
+ if (current !== void 0 && current.phase !== "complete") throw new GoalError(`goal "${current.id}" already exists with phase "${current.phase}"; a paused goal can be completed via update_goal(action="complete"), after which create_goal succeeds`, "GOAL_ALREADY_EXISTS");
--- a/dsh-tool-goal/lib/index.js
+++ b/dsh-tool-goal/lib/index.js
@@ -349,7 +349,7 @@ function apply(ctx, config) {
- if (args.action === "resume" && current?.id === ref.id && current.revision === ref.revision && current.phase === "paused") throw new HarnessError("the model cannot resume a paused goal; the user must resume it", "GOAL_TOOL_RESUME_PAUSED");
+ if (args.action === "resume" && current?.id === ref.id && current.revision === ref.revision && current.phase === "paused") throw new HarnessError("the model cannot resume a paused goal; the user must resume it (recoverable path: complete the paused goal, then create a new one)", "GOAL_TOOL_RESUME_PAUSED");动机:paused 之后,模型侧 验证与局限
有意未包含(建议分开处理)
|
|
Your elimination is right, and here is the code path it lands on, with the one condition you could not observe from the logs.
ctx.on('agent/status', ({ agent, status }) => {
const state = stateFor(agent)
if (status === 'idle') {
state.competingQueued = false
const attempt = state.attempt
const goal = currentGoal(state)
const pause = attempt !== undefined
&& (attempt.phase === 'queued' || attempt.phase === 'claimed' || attempt.cancelled)
&& goal !== undefined && goal.phase === 'active' && goal.activation === 'armed'
&& attempt.goalId === goal.id && attempt.revision === goal.revision
if (pause || attempt?.phase === 'queued') {
state.attempt = undefined
if (attempt.phase === 'queued') agent.inbox.remove(attempt.messageId)
if (pause) ctx.goals.pause(agent, goalRef(goal))
}
requestDrive(state)
}
})Two consequences for your reproduction:
That narrows the question: what should have started a turn when the reserved round message was queued at quiescence, and did not? The decisive measurement is the round message's own lifecycle rather than goal state — look for the inbox event carrying a message with One addition to your recovery findings: |
补充实测:唤醒其实成功了,但回合在 8 毫秒后被
|
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
结论摘要
0.2.0-rc.2的 ACP 交互式宿主中,goal 在"创建它的那一轮"结束时被 pause,roundsStarted保持 0:整场会话 4 个 goal,没有任何一轮进入过模型历史。凡在回合结束时仍为active的 2 个 goal,全部在创建后 5.3 秒 / 8.9 秒内被自发 pause(两次独立复现,见下方时间线)。dsh-goal-round-driver:全仓仅 4 处可调用ctx.goals.pause,其中 3 处(Web UI 点击 //goal pause命令 / 模型工具)在本次观测中均未发生,且dsh-acp、dsh-acp-app对 goal 零引用(宿主无此能力)。resume硬拒(GOAL_TOOL_RESUME_PAUSED)、create亦拒(GOAL_ALREADY_EXISTS),而两条错误文案都没有指出唯一可行的出路(对 paused goal 执行complete后可立即新建,已实测)。EN / English (short)
In an interactive ACP host on
0.2.0-rc.2, a goal is paused at the end of the very turn that created it, withroundsStartedstuck at0— across three goals in one session, not a single round ever entered the model history, so the unattended-continuation feature never actually ran.Attribution by elimination: only four call sites can reach
ctx.goals.pause(dsh-client-ui-goal:590,dsh-command-goal:148,dsh-tool-goal:353,dsh-goal-round-driver:222). The first three did not happen in this observation, anddsh-acp/dsh-acp-appcontain zero goal references, so the host cannot have done it. What remains is the driver's ownagent/status → idlehandler (:219→:222), which pauses the goal when a lingering reservation from the still-open turn is still present.Recovery is also blocked twice over:
resumeis hard-rejected (GOAL_TOOL_RESUME_PAUSED),createis rejected (GOAL_ALREADY_EXISTS), and neither message mentions the one path that works —completethe paused goal, then create a new one (verified live).时间线(会话日志原始时间戳,毫秒级)
goal/changeoperation=create(goal-b5f6a3e0-…,maxGoalRounds=3)——发生于模型回合仍在进行时goal/changeoperation=pause(revision1→2,roundsStarted仍为 0)← 回合结束、进入 idle 的瞬间user/message(人类消息)人类消息晚于 pause 11.2 秒——初版"抢占导致暂停"的说法由此被证伪。
补充观测:在回合保持开启的状态下创建 goal 并每几秒轮询一次,10 秒内始终为
active/armed,未见自发暂停。即暂停需要"回合结束(idle)"这一条件。第二次复现(独立,可对照):
goal/changecreate(另一 goal,maxGoalRounds=3)goal/changepause(+8.9 秒,回合结束时)两次复现的间隔分别为 5.3 秒与 8.9 秒,均紧贴回合结束;两次的人类消息都在暂停之后才到达。第二次复现中,人类刻意保持沉默 4 分 28 秒,期间没有任何自动续行发生,
roundsStarted仍为 0——这是一个干净的空对照组。状态快照(实测原始返回)
创建时:
{ "goal": { "id": "goal-b5f6a3e0-…", "revision": 1, "phase": "active", "roundsStarted": 0, "maxGoalRounds": 3 }, "activation": "armed" }回合结束后:
{ "goal": { "id": "goal-b5f6a3e0-…", "revision": 2, "phase": "paused", "roundsStarted": 0, "maxGoalRounds": 3 }, "activation": "disarmed" }两次自救尝试:
代码级归因
四个
ctx.goals.pause调用点:dsh-client-ui-goal/lib/client.js:590dsh-command-goal/lib/index.js:148/goal pausedsh-tool-goal/lib/index.js:353update_goal(action="pause")dsh-goal-round-driver/lib/index.js:222agent/status → idle时宿主能力排除:
dsh-acp、dsh-acp-app对goal零引用;goals.pause虽以@Remote("pause")暴露(dsh-goal/lib/index.js:498/676),但没有任何宿主侧代码调用它。驱动器侧的相关代码:
goal-round-driver/lib/index.js:213-230agent/status → idle处理器:219attempt.phase === "queued" || "claimed" || attempt.cancelled,且 goalactive+armed→ 注意queued本身即可触发:222ctx.goals.pause(agent, goalRef(goal)):237-244agent/inbox/inserted:竞争消息到达 →competingQueued=true;queued的 attempt 标记stale=true:249-252agent/inbox/discarded→attempt.cancelled = true:259admitted才计入 Round → 解释roundsStarted为何恒为 0推断链(agent 级事件不落盘,此段为推断,非直接观测):goal 在回合仍开启时创建 → 驱动器在某个时刻预排了一轮(
attempt.phase = "queued")→ 该轮因回合未结束而被agent/pre-step校验判为陈旧并拒绝(README:「因陈旧而被拒绝的预留不会消耗 Round 编号」)→ 但 attempt 未被清除 → 回合结束进入 idle →:219命中该残留 →:222暂停并解除武装。即:驱动器把它自己刚刚尝试推进的目标掐死了,且不消耗任何 Round——外部只能看到"goal 从未运行却被暂停"。
与已有讨论的关系(已逐条比对)
goal/complete→ 会话永久无响应、无自愈create丢写;本条为驱动器残留预留建议
:219把queued(含仅被标记stale)与cancelled同等对待。建议:陈旧/被拒的自身预留应清理state.attempt并重新排队,只有外部显式取消才进入 pause。goal/change只记录operation,无source/cause,因此无法直接回答"谁暂停的",只能靠排除法(本报告即如此完成)。建议记录触发者与原因码。GOAL_ALREADY_EXISTS与GOAL_TOOL_RESUME_PAUSED都应说明可达的下一步:对 paused goal 执行complete后可立即新建(已实测有效)。roundsStarted: 0无法区分"未触发 / 已预留被拒 / 被取消";建议在 goal 视图或日志中暴露最近一次 attempt 的 phase 与拒绝原因。影响
在交互式宿主中,"创建 goal 后正常结束回合"是最常见的使用方式。若此时目标必然被暂停且不消耗任何 Round,则该特性完全无法产生自动续行——用户与模型都只会看到"目标莫名 paused、模型又被两条错误挡住",外在表现即"agent 停手、必须由人推动"。
验证方式与署名
sessions/**/session.v4.jsonl)的原始goal/change与user/message事件时间戳;状态快照与错误文案为工具实测原始返回。All reactions