HarnessRouter 会话与流式深度解析:SSE 实时事件如何覆盖多轮任务、文件与取消
【免费下载链接】harnessrouterHarnessRouter Community Edition: the self-hosted, Apache-2.0 edition of the unified interface for agent harnesses. Run Codex, Claude Code, Hermes, PI, DSH, and more through one API, with sessions, streaming, files, cancellation, and failure handling. Implements the Unified Harness Protocol (UHP), an open standard. Your keys, your infrastructure.项目地址: https://gitcode.com/gh_mirrors/ha/harnessrouter
HarnessRouter 是一个自托管的统一智能体接口,它把 Codex、Claude Code、Hermes、Pi 等 Agent 运行时(harness)封装成一个 API。这篇文章带你深入它的两大核心机制——会话(Session)续接与SSE 流式事件:如何在一个接口里实现多轮对话、实时进度、文件产出和任务取消,而不需要为每个 harness 单独写一遍胶水代码。
为什么"会话 + 流式"是 Agent 产品的生命线
模型 API 给你的是"一次补全":消息进,token 出,工具得你自己跑。而 HarnessRouter 实现的是 Unified Harness Protocol(UHP)——它给你的是一个"任务":活进去,一个会自己规划、调工具、改文件的运行体在里面干活,最后把结果和文件交回来。
任务往往要跑几分钟。没有流式反馈,产品只能给用户一个转圈的加载图标;没有会话续接,第二次提问就得从零开始。UHP 规范把这两件事都写成了强制条款,规范文本见 Sessions 章节 和 Streaming 章节。
会话机制:多轮任务如何"接着上次聊"
用一个 ID 续接整个对话
HarnessRouter 的续接设计非常克制:不需要客户端自己保存聊天记录,只要把上一轮任务的previous_response_id带进下一次请求:
{ "input": "Now add tests for the function you just wrote.", "previous_response_id": "resp_a1b2c3" }规范要求服务端必须做到四件事:在同一个会话里运行新任务、保留同一个工作目录及其文件、把前几轮的对话上下文交给 harness、并且回报同一个metadata.session_id。
这个设计的巧妙之处在于"按 response id 链,而不是按 session id 链"——response id 是客户端手上现成的东西,它来自你刚跑完的那个任务;而且它精确指向对话中的某个点,为将来"从更早的某轮分叉"留了余地,请求体形状却不用改。
另一个实用细节:同一会话内允许换模型。规范明确说"跟进任务切到更便宜的模型"是常见且合法的用法,服务端必须尊重每轮的model字段。
查看会话与完整的历史
| 操作 | 端点 |
|---|---|
| 列出会话(游标分页) | GET /v1/sessions?limit=20&cursor= |
| 查看单个会话 | GET /v1/sessions/{session_id} |
| 查看每轮任务历史 | GET /v1/sessions/{session_id}/turns |
/turns返回有序的轮次历史,客户端可以据此重建自己没存的完整对话。每轮至少带id(可用GET /v1/responses/{id}取完整响应)和status,有则更好地带user、assistant、tools、files字段。分页采用游标制,末页必须返回next_cursor: null,不允许客户端靠"收到的条数不够"来猜结尾。
流式机制:SSE 事件如何实时覆盖任务全程
打开一条流
在POST /v1/responses上设置"stream": true,服务端必须以text/event-stream应答,事件采用标准 Server-Sent Events 帧,每条data是一个 JSON 对象:
data: {"type":"response.created","sequence_number":0,"response":{…}} data: {"type":"response.output_text.delta","sequence_number":7,…,"delta":"Sum"} data: {"type":"response.completed","sequence_number":42,"response":{…}}每个事件必带type和sequence_number,序号从 0 开始、每次严格 +1——客户端因此能检测出丢事件,而不是把缺口当成空白悄悄渲染出来。
事件词汇表:只描述"发生了什么"
UHP 的事件词汇刻意只描述事实、不指挥渲染,主要分几类:
| 类别 | 事件示例 | 含义 |
|---|---|---|
| 任务生命周期 | response.created/response.in_progress | 任务被接受、开始工作 |
| 终端事件 | response.completed/response.incomplete/response.failed | 流必须恰好以其中一个结束 |
| 文本增量 | response.output_text.delta/.done | 文字的下一片碎片 / 完整文本 |
| 工具调用 | response.function_call_arguments.delta | 工具参数的 JSON 碎片,实时可看 |
| 推理摘要 | response.reasoning_summary_text.delta | 可选,很多 harness/模型组合不产生 |
| 非致命错误 | error | 带code/message,之后必须跟一个终端事件 |
两条容易被忽略但很要命的规则:
- 禁止攒到结尾再一次性发送。"结尾一次性送达的流不是流",客户端根本无法区分它和挂死;
- 代理层必须关闭响应缓冲。规范称这是 UHP 服务端"最常见的部署错误"——症状看起来和 harness 慢完全一样。
终端事件是"防丢网",断线不丢活
response.completed/incomplete/failed三个终端事件之一会携带完整的最终response对象——即使你中途漏掉所有中间事件,光靠这一条也能渲染出完整结果。这是刻意的冗余设计:"流中途断线应该只损失延迟,不损失正确性。"
断线后服务端的工作不会中止。客户端可以GET /v1/responses/{response_id}重读结果,或者订阅GET /v1/harnesses/{harness_id}/events拿到该 harness 所有会话的实时事件流。规范的建议很清醒:把流当作优化,把存下来的 response 当作事实来源。
文件:输入两条路,产出带引注
"只会回文本的 agent 只是聊天机器人"。UHP 的 Files 章节 定义了文件的进出:
- 进:小文件可直接用 data URL 内联在
input里;大文件先POST /v1/files上传一次、拿file_id引用,避免每次重试都膨胀 40MB 的 base64。超限必须回413+file_too_large拒绝,而不是静默截断——"被截断的输入会产出自信但错误的答案"。 - 出:agent 产出的文件成为会话容器的 artifact,以**引注(annotation)**形式挂在助手消息上,带
filename和download_url。只渲染文本的客户端依然显示正确答案,能读引注的客户端则可以顺带提供文件下载——artifact 永远是消息的增强,而不是替代品。
上图那个"找重复文件"的任务就是典型场景:助手文字说明 + 一份.py产物 + 若干测试夹具文件,全部从流式事件和最终response里拿到。
取消:两种范围,语义明确
HarnessRouter 把取消拆成两个端点,语义刻意区分:
POST /v1/responses/{response_id}/cancel # 停掉这一个任务 POST /v1/sessions/{session_id}/cancel # 停掉这个会话里正在跑的一切规范给取消定了一套"硬语义":
- 取消是请求,不是即时保证,但服务端必须尽快停下并到达终态;
- 被取消的任务必须结束为
status: "cancelled",绝不能记成 failed(流里它以response.failed事件送达,但 status 字段才是权威); - 取消前已产出的输出必须保留;
- 对已终态任务重复取消必须成功且不改变任何东西——断线后重试取消不该报错;
- 取消不删除会话,对话依然可以继续;
- 建议 1 秒内给出响应。"客户端通常是界面,一个三十秒没反应的 Stop 按钮看起来就是坏的。"
唯一的例外是删除会话(DELETE /v1/sessions/{session_id}):规范明确要求"先取消在途任务,再让会话不可读",因为替代方案是"一个正在运行的任务往已经没了主人的存储里写数据"。
快速上手:两条命令跑起来
社区版(Apache-2.0)自托管只需 Docker、约 4GB 磁盘和你的模型 API key:
git clone https://gitcode.com/gh_mirrors/ha/harnessrouter cd harnessrouter docker run -d --name harnessrouter \ -p 127.0.0.1:3000:3000 \ -v harnessrouter:/data \ harnessrouter/harnessrouter首次启动会自动安装启用的 harness CLI,日志出现ready on :3000即可连接 provider、发起带"stream": true的任务。完整部署、备份与升级细节见 自托管指南。
curl -s -N https://127.0.0.1:3000/v1/responses \ -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \ -d '{"input": "Summarise README.md in three bullets.", "stream": true}'相关资源
- 协议总览与章节索引:protocol/versions/2026-09-28/index.md
- 任务与响应对象:protocol/versions/2026-09-28/tasks.md
- 流式帧的合规测试:protocol/conformance/tests/test_stream_framing.py
- 会话分享的合规检查:protocol/conformance/tests/test_session_sharing_checks.py
- 网关侧任务总线测试:gateway/tests/test_bus_tasks.py
- 自定义 harness 的接入方式:docs/images 中的演示素材对应 protocol/conformance/README.md 的合规流程
一句话总结:HarnessRouter 用previous_response_id让会话续接只有一个字段,用sequence_number让流式进度可验证,用"终端事件携带完整响应"让断线不丢活,再配上带引注的文件产物和语义明确的取消——这四件事合在一起,就是"一次集成、任意 harness"背后的工程答案。
【免费下载链接】harnessrouterHarnessRouter Community Edition: the self-hosted, Apache-2.0 edition of the unified interface for agent harnesses. Run Codex, Claude Code, Hermes, PI, DSH, and more through one API, with sessions, streaming, files, cancellation, and failure handling. Implements the Unified Harness Protocol (UHP), an open standard. Your keys, your infrastructure.项目地址: https://gitcode.com/gh_mirrors/ha/harnessrouter
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考