DSH / Atlas
2026-08-21implementedfeaturearchived 2026-09-04

headless streams provider reasoning to stderr

headless 将提供方推理流式写入 stderr

The one-shot headless runner waits for complete Agent quiescence before printing the final Assistant text. Reasoning-capable providers expose reasoning through live `agent/assistant-stream` chunk frames and the final durable settlement, but a long reasoned response leaves the terminal silent if the runner observes only settled history. The final answer must remain the only stdout payload so command substitution and o

English

Problem

The one-shot headless runner waits for complete Agent quiescence before printing the final Assistant text. Reasoning-capable providers expose reasoning through live agent/assistant-stream chunk frames and the final durable settlement, but a long reasoned response leaves the terminal silent if the runner observes only settled history. The final answer must remain the only stdout payload so command substitution and other consumers keep a stable result channel.

The earlier direct core entry-point decision required empty stderr on every successful run. That clause prevents live reasoning progress and is superseded by this note; its transport, durability, and completion decisions remain unchanged.

Decision

headless-runner observes agent/assistant-stream for the exact Agent it creates after startup quiescence and before submitting the task. Once the owned interval opens with turn/start, each non-empty reasoning-delta chunk frame is written immediately to stderr. A contiguous reasoning phase starts with dsh: reasoning: on its own line; deltas retain provider order without token-boundary decoration. Reasoning block boundaries and usage metadata keep that phase open; a later non-reasoning block or output delta, stream end, new turn, or listener disposal terminates it with one newline when the provider supplied none.

This output is a transient projection of the process-local Assistant stream. The runner still derives final text and exit status from the flushed durable Session settlements rather than from progress-presentation state. SDK projections do not expose the live frames.

Reasoning progress is not TTY-gated and has no separate flag. A redirected stderr stream and a supervisor receive the same provider-reported content as an attached terminal. A successful run without reasoning still writes nothing to stderr; terminal model and driver errors keep their existing dsh: diagnostics after any open reasoning phase is terminated.

Verification

The package test holds the Agent active after a reasoning frame and observes stderr before idle, then pins newline ownership for provider-terminated and unterminated phases plus terminal errors. The owner-local product expectation drives the shipped headless profile through a reasoning-plus-tool round and pins both stderr and the persisted Session. Recorded-session replay reconstructs expected stderr by expanding embedded Assistant streams, closes sections on text and tool-call output, and uses the raw run log before fixture path tokenization in record modes. Built-bin acceptance sends reasoning_content through the native DeepSeek SSE adapter and requires reasoning on stderr while stdout remains the final answer.

Alternatives considered

Dump reasoning after quiescence. Folding reasoning from the persisted log would preserve content but leave the terminal silent during the long-running interval that motivates the feature.

Wrap the LLM stream. Tapping ctx.llm.stream() would place a presentation concern in the request path and duplicate the scoped frames that the agent loop already publishes.

Print a spinner or periodic heartbeat. A timer reports process liveness rather than provider progress, adds an interval policy, and still hides reasoning that the provider already supplies. Time before the first reasoning delta remains silent and can be addressed separately if providers buffer their first token.

Enable output only on a TTY or explicit flag. Headless runs under CI and supervisors need the same progress signal, while implicit TTY-dependent behavior makes redirected runs differ from interactive runs. Callers that do not want reasoning logs redirect stderr.

Consequences

Reasoning-capable successful runs now write provider-reported content to stderr, so log collectors may retain substantially more and potentially sensitive model output. Stdout remains one final assistant result, text-only success keeps stderr empty, errors remain line-separated, and no new configuration or durable format is introduced. Silence before the provider emits its first non-empty reasoning delta remains an explicit limitation.

中文

问题

一次性 headless runner 会等待 Agent(智能体)完全停稳,再打印最终 Assistant 文本。具备推理能力的 provider 会通过实时 agent/assistant-stream chunk frame 与最终持久 settlement 暴露 reasoning,但如果 runner 只观察已结算历史,耗时较长的 reasoning response 会让终端始终静默。最终答案必须继续作为 stdout 中唯一 payload,使命令替换和其他消费方保持稳定结果通道。

此前的直接使用核心服务入口决策要求每次成功运行都保持 stderr 为空。该条款会阻止实时推理进度,因此由本 Agent Note 取代;其中关于传输、持久性与完成状态的其他决策保持不变。

决策

headless-runner 在启动工作完全停稳后、提交任务前,为其创建的精确 Agent 观察 agent/assistant-stream。自身持有的区间以 turn/start 打开后,每个非空 reasoning-delta chunk frame 都会立即写入 stderr。一段连续 reasoning 以独占一行的 dsh: reasoning: 开始;各 delta 保持 provider 顺序,不添加 token 边界装饰。Reasoning block boundary 与 usage metadata 会保持该段打开;之后出现非 reasoning block 或 output delta、stream end、新 turn 或 listener dispose 时,如果 provider 没有输出末尾换行,runner 会用一个换行终止该段。

该输出是进程本地 Assistant stream 的瞬态投影。runner 仍从 flush 后的持久 Session settlement 而非进度呈现状态推导最终文本与退出状态。SDK 投影不公开 live frame。

推理进度不按 TTY 启用,也没有单独 flag。重定向的 stderr 流与监督进程会收到和已连接终端相同的提供方报告内容。没有推理内容的成功运行仍不会写入 stderr;终止态模型错误与驱动器错误继续在任何已打开推理段终止后输出既有的 dsh: 诊断。

验证

包测试在 reasoning frame 后保持 Agent 活跃,并在 idle 前观察 stderr;测试同时固定由 provider 终止和未终止的 reasoning 段换行归属,以及 terminal error。产品自有期望通过包含 reasoning 与工具调用的 turn 驱动随附 headless profile,并固定 stderr 与持久 Session。录制 Session replay 通过展开嵌入式 Assistant stream 重建预期 stderr,在 text 与 tool-call output 处关闭 reasoning 段,并在 record mode 下于 fixture path tokenization 前使用原始 run log。构建后二进制 acceptance 通过原生 DeepSeek SSE adapter 发送 reasoning_content,要求 reasoning 出现在 stderr,同时 stdout 仍只包含最终答案。

考虑过的替代方案

完全停稳后再输出推理。 从持久化日志折叠推理能够保留内容,但在导致本功能产生的长时间运行区间内,终端仍会保持静默。

包装 LLM stream。 截取 ctx.llm.stream() 会把呈现职责放入请求路径,并重复处理 agent loop 已发布的 scoped frame。

打印 spinner 或周期性心跳。 定时器报告的是进程存活状态,而不是提供方进度;它还会新增间隔策略,并继续隐藏提供方已经给出的推理。首个推理分片前的时间仍保持静默;如果提供方会缓冲首个 token,可以另行处理。

仅在 TTY 或显式 flag 下启用输出。 CI 与监督进程中的 headless 运行需要相同的进度信号,而隐式依赖 TTY 会让重定向运行与交互式运行产生差异。不需要推理日志的调用方可以重定向 stderr。

后果

具备推理能力的成功运行会把提供方报告的内容写入 stderr,因此日志收集器可能保留明显更多且可能敏感的模型输出。stdout 仍只包含一个最终 assistant 结果,没有推理内容的成功运行保持 stderr 为空,错误继续与推理内容分行,并且本决策不引入新配置或持久化格式。提供方发出首个非空推理分片前保持静默,这是明确的限制。