Never block the user on work in flight — aligning the tool, agent, and session layers #3567
Replies: 1 comment
|
Thanks for pulling these three threads into one place — the table alone clarified something I hadn't fully seen: the three-layer framing makes it obvious this is one principle, not three features. I think the questions are the right ones, especially the push to settle Q2 before anyone builds. One untangling that helped me, and then my current answer to Q2 (consistent with where I landed in #3342, now generalized): Two different "blockings" are mixed together here. The through-line principle is that the user is never blocked — and that guarantee is delivered by mid-turn steering plus the surfaces from Q5, not by the continuation model. A suspended turn is still fully inspectable and steerable. What yield/poll vs spawn/notify actually disagree about is a narrower thing: who absorbs the waiting between wall-clock time and the model's discrete steps — the KV-cache cost concern from #3342 lives in this second question, not the first. On Q2, my answer is neither of the two — and I think this thread has already written down the real answer. The one place the principle holds today, For the default continuation, the #3342 comparison of ten agent codebases convinced me that polling is a dominated strategy (the model is the only actor that pays to check the time), and that wake-up-by-default charges an admission/dedup apparatus as its entry fee — every project that ships it was forced to build one. The third form is park: the call simply doesn't return; the runtime suspends the loop and fills the same call when a boundary is reached. "The tool call hasn't returned yet" is already a native concept of the agent loop, so this builds nothing new — and it lands naturally on the Runtime Host as the sole continuation authority. This also gives a concrete answer to Q3: while background work runs, the parent turn is parked — open, suspended at a tool call, inspectable and steerable, ended by the model's own decision rather than by the background work. On Q4, I'd frame the Agent Graph as principled coexistence rather than the odd one out: it deliberately ends the parent turn to buy durability and crash recovery, which is exactly right for dependency-driven batch work; park serves "start this and stay with me." The difference is turn-lifecycle semantics, not duplicated mechanism. And on Q6, addressed signals feel orthogonal — they model interactions that aren't completion-shaped, and could layer onto handles later without changing any of the above. The honest cost to weigh: a parked parent turn is not log-backed the way a graph yield is, so crash recovery would need the turn to be reconstructible from the event log. Durability is what park gives up; I think that's the right trade for the interactive case, but it's the assumption I'd most like to see stress-tested. (Drafted with AI assistance; codebase claims verified against current 中文版本感谢把三条线汇总到一处——光是那张对应表就让我看清了一件之前没完全意识到的事:三层框架说明了这是一条原则,而不是三个独立功能。这里提的问题也都问得准,尤其是"动工之前先定 Q2"这个推动。 先分享一个帮我理清思路的拆分,然后是我对 Q2 的当前答案(与我在 #3342 的结论一致,这里推广到全部三层): 这里混着两种不同的"阻塞"。 主线原则说的是用户不被阻塞——而这条保证由 mid-turn steering 和 Q5 的界面呈现来兑现,与 continuation 模型无关:一个悬置中的 turn 照样可以完全观察和介入。yield/poll 与 spawn/notify 真正的分歧是一个更窄的问题:谁来吸收"现实时间"和"模型离散步进"之间的等待——#3342 的 KV cache 成本问题属于后者,不属于前者。 对 Q2,我的答案是两个都不选——而且我认为本串已经把真正的答案写下来了。 今天唯一符合原则的地方, 默认 continuation 方面,#3342 里对十个 agent 代码库的对比让我确信:轮询是被支配策略(模型是系统里唯一"看表要付钱"的角色),默认唤醒则要交一整套准入/去重机制作为入场费——每个落地唤醒的项目都被迫建了一套。第三形态是 park:调用干脆不返回;runtime 挂起循环,在边界到达时填充同一个调用。"tool call 还没返回"本来就是 agent loop 的原生概念,所以这不造任何新东西——并且它天然落在 Runtime Host 这个唯一的 continuation 权威上。 这也具体回答了 Q3:后台工作运行期间,父 turn 是 parked——开启、悬置在一个 tool call 上、可观察可介入、由模型自己决定何时结束,而不是被后台工作结束。对 Q4,我会把 Agent Graph 定位为有原则的共存而不是体系例外:它故意结束父轮,换取持久化和崩溃恢复,对依赖驱动的批量工作这完全正确;park 服务的是"起个任务、继续陪着我"。差异在 turn 生命周期语义,不是机制重复。Q6 的 addressed signal 我觉得是正交的——它建模的是"不以完成为形态"的交互,日后可以叠加在 handle 之上,不改变上面任何结论。 需要权衡的诚实代价:park 的父轮不像 graph yield 那样以日志为底,崩溃恢复需要 turn 能从 event log 重建。durability 是 park 真正放弃的东西;对交互场景我认为这是正确的取舍,但这是我最希望被挑战的假设。 (本评论由 AI 辅助起草;代码层面的论断均已对照当前 |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
English
Opening this at @Astro-Han's suggestion on #3540, to give the shared direction one place to live rather than three.
The through-line
Three threads are currently open that look like separate features but are the same principle at three layers. Astro-Han put it well in #3540: the user wants to inspect and steer the agent at any given time without being blocked by running subagents or any specific job.
Bashwithbackground=falseholds the whole agent until the command returns — and past roughly five minutes the provider's KV cache expires, so the block costs money as well as timeagent_spawnawaits the child to a terminal AgentRun, so delegating freezes the session for as long as the delegated work takesEach is defensible on its own. Read together they say something stronger: whenever Maka starts work, the user should keep the ability to watch it, redirect it, and stop it. Today that holds for exactly one case —
Bashwithrun_in_background: true, which already has retrieval viaRead({ref}), cancellation viaStopBackgroundTask, and input viaWriteStdin. Everywhere else, starting work means surrendering the session until it finishes.Worth noting the project already committed to this principle inside a single turn: mid-turn steering exists so a user can intervene while the model is working (#3529, #3530). What is missing is the same guarantee once work crosses a boundary — into a tool, into a child agent, into another session.
The part that is genuinely undecided
The two threads that propose a mechanism do not propose the same one, and I do not think the project can adopt both without the surfaces drifting apart.
#3342 argues for yield + poll, following Codex:
exec_commandreturns a session id after a short yield window, and the agent polls withwrite_stdin. The stated advantages are that no single tool call can hang the agent, and that polling is cheap when the KV cache is warm.#3540 argues for spawn + notify, following the shape Claude Code and
dsh-plugin-product-subagentsconverge on: start work in the background, keep the parent turn alive, deliver a follow-up to a named child, and let the host wake the parent when something needs attention.They are not interchangeable. Yield/poll keeps the model in control of cadence and needs no new wake machinery, but every observation costs a tool call and the model has to remember to look. Spawn/notify frees the model from polling, but requires a live-children registry, a wake path, and a decision about what a parent turn is while background work continues.
Maka also already has a third answer for the agent layer — the Agent Graph — which is durable and log-backed but ends the parent turn at
yield_agent_graphand resumes it as a new turn at a reconciliation checkpoint. That is a good fit for dependency-driven batch work and a poor one for "start this and stay with me", so it does not settle the question either.Questions worth settling here
Bashto be Codex-shaped while agents are graph-shaped?agent_pollanalogous towrite_stdin, and does that make the Agent Graph the odd one out?What I would find most useful
A decision on question 2 before anyone builds. #3342 and #3540 can each be implemented in isolation and would then be hard to reconcile — the cost of picking late is much higher than the cost of picking now.
Happy to keep #3540 narrow and defer to whatever this thread lands on. cc @likun666661, who Astro-Han suggested would have context on the swarm and graph design.
Investigated with AI assistance (Claude Code); Maka behaviour cited in #3540 was verified against source.
中文
应 @Astro-Han 在 #3540 中的建议开设此讨论,让这个共同方向有一个统一的落脚点,而不是分散在三处。
共同的主线
目前有三条线看起来是彼此独立的需求,实际上是同一条原则在三个层次上的体现。Astro-Han 在 #3540 里的表述很准确:用户希望随时能够观察和引导 agent,而不被运行中的子 agent 或任何具体任务阻塞。
Bash在background=false时会占住整个 agent 直到命令返回 —— 而一旦超过约五分钟,provider 的 KV cache 就会过期,于是这次阻塞不只耗时间,还要花钱agent_spawn会等待子 agent 抵达终态 AgentRun,因此委派工作会把会话冻结整个委派时长每一条单独看都站得住。合起来看,它们说的是一件更强的事:只要 Maka 开始工作,用户就应当保有观察它、重定向它、停止它的能力。 今天满足这一点的只有一个地方 ——
Bash的run_in_background: true,它已经具备取回(Read({ref}))、取消(StopBackgroundTask)和输入(WriteStdin)。其余任何地方,启动工作都意味着交出会话直到它结束。值得一提的是,项目在单轮内部已经承认了这条原则:mid-turn steering 之所以存在,就是为了让用户能在模型工作时介入(#3529、#3530)。缺的是同一条保证在工作跨越边界之后是否继续成立 —— 进入工具、进入子 agent、进入另一个会话。
真正尚未决定的部分
两条提出了具体机制的线程,提的并不是同一种机制;而我认为项目无法同时采纳两者又不让各个界面产生分裂。
#3342 主张 yield + poll,沿用 Codex:
exec_command在一小段 yield 窗口后返回 session id,之后由 agent 通过write_stdin轮询。其列出的优势是:任何单次 tool call 都不会挂死 agent;且在 KV cache 命中的前提下轮询开销很低。#3540 主张 spawn + notify,沿用 Claude Code 与
dsh-plugin-product-subagents收敛出的形态:在后台启动工作、保持父轮存活、向具名子 agent 追加指令,并由 host 在需要关注时唤醒父轮。两者不可互换。yield/poll 让模型自己掌握节奏、无需新的唤醒机制,但每一次观察都要花掉一次 tool call,而且需要模型记得去看。spawn/notify 免去了轮询,但需要一份 live-children 注册表、一条唤醒路径,以及对「后台工作进行时父轮究竟算什么」的明确定义。
Maka 在 agent 层其实还有第三种既有答案 —— Agent Graph。它是持久化且以日志为底的,但会在
yield_agent_graph处终结父轮,并在协调检查点以新的一轮恢复。这对依赖驱动的批量工作是合适的,对「先起个任务、你继续陪着我」则不合适,所以它同样没有为这个问题收尾。值得在此明确的问题
Bash走 Codex 形态、agent 走 graph 形态也没问题?write_stdin的agent_poll?这是否会让 Agent Graph 变成体系里的例外?我认为最有价值的推进
在任何人动工之前先就问题 2 达成结论。 #3342 与 #3540 各自都能被独立实现,而一旦都落地就很难再对齐 —— 晚做选择的代价,远高于现在就做选择。
我很乐意让 #3540 保持窄范围,并以本讨论的结论为准。cc @likun666661,Astro-Han 提到他对 swarm 与 graph 的设计更有上下文。
本调查借助 AI(Claude Code)完成;#3540 中引用的 Maka 行为均已对照源码验证。
All reactions