一条 Prompt 在 Harness 中经过了谁?

94 阅读27分钟

今天要学习 DeepSeek Harness 如何把网页中的一条指令,变成模型请求、真实工具操作,再把执行过程送回界面。我们沿用“运行测试,失败就修复文件”的例子,按相同格式拆清每个对象的作用、输入、内部处理、输出和缺失后果。


whiteboard_exported_image.png

小炎: 我先试着说一遍:Client 生成 Session 和 system prompt,发送给 Remote 服务器。Remote 按 sessionId 找到对应 Service,Controller 检查之后找到 Agent。Agent 循环请求模型、调用工具,同时保存 Session,最后把答案传回 Client。这个方向对吗?

大老师: 你抓住了“用户输入 → 执行 → 显示结果”的方向,但中间有四个分界需要修正。

Client 构建的是提交命令;普通网页提交并不在 Client 侧装配 system prompt。Remote 位于 Client,是远程方法的调用入口;真正接收网络请求的是 Host,也就是运行 Agent、管理会话和工具的执行端。Gateway 按接口入口找到 Service,业务层再按 sessionId 找到 Agent。Session 则贯穿整个执行过程,持续记录事实。

还有一个容易忽略的地方:提交接口先返回“已接纳”,最终答案通过持续订阅返回。它们是两条返回路径。

我们先固定一个场景:网页已经打开会话 s_123,你发送:

请运行测试,如果失败就修复 src/math.ts,然后告诉我结果。

为了让输入输出有具体形状,下面使用这些示例值:

sessionId = s_123                 本次操作的会话
requestId = req_42                本次用户提交
rpcId = rpc_7                     本次网络调用
mode = queue                     排入后续 turn
cwd = /workspace/demo            工作目录
provider/model = deepseek/deepseek-chat
当前可见工具 = bash、read、edit

这些值与后面的测试输出用于教学,并非一次实际运行的录制。模型也不一定会采用我们列出的行动顺序。


小炎: 我需要先知道对象是什么。尤其是 Service,它为什么夹在 Gateway 和 Agent 之间?

大老师: Service 是注册到 Cordis 中、供其他组件调用的能力对象。Cordis 是 Harness 使用的组件装配框架;它让组件可以按名称取得能力、接收事件,并管理启动与释放。

比如 Session Controller 是处理会话相关外部命令的 Service;LLM Service 提供模型调用能力。Service 是一种角色,不是某条请求,也不是某个 Session。

再补齐两个相关名词。Cordis 的 Context 是组件取得服务、注册贡献和监听事件的运行入口。Harness 的 Scope 是能力可见范围,决定一个 Agent 能使用哪些工具、指令贡献以及相关监听。它们支持整条链,不能画成用户消息依次穿过的三个关卡。

对象作用输入(本例)内部处理输出(本例)没有它会怎样
Cordis Context连接组件与所需能力服务注册、监听,以及调用者所在的运行环境按当前上下文取得服务,管理贡献和监听的生命周期调用者可以取得 llm、agents 等能力需要由其他机制承担依赖连接与生命周期管理
Cordis Service提供可复用的能力入口所属 Context、名称、配置和方法参数提供具体能力;不同 Service 的实现不同例如 sessionController.prompt(request)对应能力必须由调用者自行实现或通过其他入口提供
Harness Scope限定 Agent 的能力视野Agent 所在范围及其可见注册决定装配时可见的指令、工具和贡献本例可见 bash、read、edit缺少范围机制时,难以让不同 Agent 使用不同能力组合

所以并不存在“根据 Session 找一个通用 Service,再让 Service 找 Agent”的统一流程。这里调用的具体 Service 是 Session Controller,它提供处理会话命令的方法。


小炎: 那能不能像网络协议一样分层?我不想把所有名字挤进一条箭头。

大老师: 可以按职责分层,但这是一张阅读地图,不是每层都会装配一次模型 prompt 的协议栈。

层解决的问题主要对象
L1 用户提交如何把人的意图变成客户端动作UI、Client Session、Prompt command
L2 远程调用如何把方法调用送到执行端Remote、Descriptor、Connection、RPC envelope
L3 Host 接入请求是否符合协议,应该交给哪个能力Host Connection handler、Gateway、GatewayInvocation
L4 业务接纳目标会话能否接收这条输入Session Controller、命令处理器、Agent 解析器与 Registry
L5 Agent 调度何时处理输入,何时继续模型—工具循环Inbox、Agent、Turn、Step
L6 请求形成本次模型究竟看到什么PromptAssembly、各类 Projection、请求准备与构建
L7 外部执行怎样调用模型,怎样执行工具LLM Adapter、Model、工具执行管线
L8 返回显示怎样把执行事实和状态呈现给人follow、control、Client model、UI

另外,Session 是 L4–L8 都会使用的事实层。它既为下一次模型请求提供历史,也为界面提供可回放的记录。


小炎: 从点击发送开始,Client 到底构建了什么?sessionId 也是这时生成的吗?

大老师: 本例操作的是已有会话,所以复用 s_123。会话标识在创建会话时确定;当前创建接口允许调用方提供,也允许 Host 生成。每次发送消息并不会再创建一个 Session。

网页先组织一条 Prompt command。这里的 command 是“请把这份输入交给指定会话”的命令数据,不是发给模型的完整 prompt。

{
  "sessionId": "s_123",
  "requestId": "req_42",
  "mode": "queue",
  "content": [
    {
      "type": "text",
      "text": "请运行测试,如果失败就修复 src/math.ts,然后告诉我结果。"
    }
  ]
}
对象作用输入(本例)内部处理输出(本例)没有它会怎样
UI接收人的操作输入文本、当前会话、发送按钮点击处理输入状态并发起 Client 调用session.prompt(...)人需要通过终端或程序等其他入口提交
Client Session管理界面端的会话镜像与提交状态s_123 和输入内容登记待确认提交,调用 Remote,随后用 Host 更新维护镜像本地 pending 状态与远程提交调用UI 必须自行协调提交、确认和事件更新
Prompt command明确提交的目标和语义会话、提交标识、发送模式、文本它是数据;由 Client 组织,由 Host 读取上面的 JSON 数据执行端无法明确这条输入属于哪个会话、按何种模式接收

Client Session 是界面镜像,Host 的 Core Session 是执行事实。 名字都有 Session,职责却不同;本地显示“已发送”也不等于 Host 已完成任务。


小炎: Remote 不是服务器,那为什么我能调用 remote.session.prompt()?

大老师: 因为 Remote 把远程接口包装成客户端可以调用的方法。RPC,也就是远程过程调用,解决的是“调用另一端的方法”这个问题。Remote 提供方法门面,Connection 提供传输。

Descriptor 是接口描述数据,说明方法对应哪个入口、参数怎样编码、结果怎样解码等。Endpoint 是接口入口名称;本例是 session/prompt。

对象作用输入(本例)内部处理输出(本例)没有它会怎样
Client Remote把客户端方法调用转换成远程调用remote.session.prompt(command)根据 Descriptor 准备参数和 endpoint向 Connection 调用 session/prompt,参数放在 args 中Client 需要手工维护远程接口约定
Remote Descriptor描述两端共同遵守的接口契约prompt 方法的声明它是描述数据;调用器按它编码参数、定位方法、处理结果endpoint、参数名及结果规则手写参数与服务器方法更容易不一致
Connection / RPC caller承担通信及响应配对endpoint、参数和取消信号生成 rpcId,发送请求,检查响应与 id;连接体系还管理持续流与连接状态请求发往 Host,返回对应调用结果同样的业务调用需要另一种传输实现
ClientRequest envelope给网络请求一个可验证的外壳rpc_7、session/prompt、参数它是数据;传输层序列化,Host 解析带 type、rpcId、method、payload 的 JSON两端缺少统一的请求类型和配对规则

本例的网络请求可表示为:

POST api/session/prompt
Content-Type: application/json
{
  "type": "client-request",
  "rpcId": "rpc_7",
  "method": "session/prompt",
  "payload": {
    "args": {
      "request": {
        "sessionId": "s_123",
        "requestId": "req_42",
        "mode": "queue",
        "content": [
          {
            "type": "text",
            "text": "请运行测试,如果失败就修复 src/math.ts,然后告诉我结果。"
          }
        ]
      }
    }
  }
}

网页传输器使用相对路由;这里展示的是本例的 endpoint 与参数结构。requestId 标识用户提交,rpcId 标识一次传输调用,它们不能混用。


小炎: 那类似 FastAPI 的服务器部分是谁?为什么检查完网络请求,还要再检查一次?

大老师: 最接近服务器路由职责的是 Host 的接入处理器、Gateway 和 Controller 这一组,但它们分别解决不同问题。

接入层处理认证与网络格式;Gateway 按接口契约把请求转成方法调用;Controller 判断业务上能否接收。JSON 合法不意味着会话存在,方法名正确不意味着内容非空。

对象作用输入(本例)内部处理输出(本例)没有它会怎样
Host Connection handler接收并检查网络请求HTTP 请求、JSON 外壳、调用方信息按接入配置检查认证、路由、内容类型、外壳和 method 一致性可交给 Gateway 的调用数据协议错误和业务逻辑会混在一起
Typert Gateway按接口契约派发调用endpoint=session/prompt 与命名参数查 Descriptor,准备参数,解析对应 Service 与方法调用 Session Controller 的 prompt(request, signal)接入层必须自行实现契约检查和派发
GatewayInvocation携带一次调用的上下文调用方、endpoint、取消信号等保存并提供本次调用的身份与取消信息被调用方法所需的调用上下文方法难以关联调用来源及本次调用的取消状态
Session Controller提供会话 API 入口解码后的 command 与 signal委派给负责命令、历史或状态的内部处理器本例得到命令处理器的接纳结果外部调用需要其他会话业务入口
Session Command Controller实施提交的业务准入s_123、req_42、content、mode检查内容,解析 Agent,处理重复提交和附件准入;构造用户消息并调用 followup 或 steer输入进入 Agent;返回 { accepted: true }合法网络请求仍无法按会话业务规则执行

这里要记住两种查找:

endpoint = session/prompt → 调哪个 Service 方法?
sessionId = s_123         → 这个方法操作哪个 Session / Agent?

Gateway 并不是拿 s_123 去决定调用 Session Controller 还是 LLM Service。接口入口已经说明了本次要调用什么能力。


小炎: “找到 Agent”又涉及 Registry、SessionStore 和持久化,它们有什么区别?

大老师: 它们分别管理执行者、已打开的会话和磁盘上的记录。首先定义 Agent:它是协调输入、模型调用与工具执行的运行对象。Core Session 则记录这个运行过程中的事实,二者不能互相代替。

对象作用输入(本例)内部处理输出(本例)没有它会怎样
API Agent 解析器把会话 id 解析成可操作的 Agents_123查询 live Agent;必要时协调从保存的会话恢复,并协调重复恢复请求当前可操作的 agent(s_123),或明确失败Controller 需要自己处理已有 Agent 与恢复路径
Agent Registry / agents管理进程中的 AgentAgent id、创建或恢复参数保存 live Agent,提供查找、创建、恢复等能力对应 Agent 实例业务层无法统一定位与管理执行者
SessionStore / sessions管理已打开的 Core Session会话 id 与会话创建、打开请求管理内存中的 Session 生命周期,并与持久化能力协作session(s_123)运行中的会话需要其他管理机制
Session persistence保存和读取会话记录Session 日志、元数据、会话标识将记录保存到持久介质,支持后续读取与恢复重启后仍可读取的记录进程结束后不能仅靠内存找回会话
Core Session保留执行事实并提供不同视图用户消息、模型消息、工具结果、运行边界等追加事件,维护当前有效消息视图与请求相关记录历史事件、模型消息投影、界面历史来源下一步模型与界面缺少共同的事实依据

本例首先尝试找到已经运行的 Agent,不能把“根据 sessionId 找到 Agent”理解成“寻找之前某个 HTTP 请求并继续执行”。HTTP 调用会结束,会话可以继续存在。

SessionStore 也不能简单定义为磁盘数据库;管理已打开对象与保存对象是两个职责。


小炎: Agent 已经找到了,为什么接口只返回 accepted,不直接返回修复结果?

大老师: 因为接纳输入和完成任务之间,可能隔着排队、多次模型调用、多次工具执行、等待用户或失败。

Inbox 是 Agent 的待处理输入队列。queue 通过 followup() 把消息安排给后续 Turn;Turn 是一轮任务执行的边界。一轮里可以包含多个 Step;Step 是循环中一次模型调用及其工具处理的执行边界。一次 Step 还可能因为重试包含多个模型请求尝试。

对象作用输入(本例)内部处理输出(本例)没有它会怎样
ReactLoopInbox安排输入在哪个边界生效带 req_42 来源的用户消息,目标 next-turn记录排队输入;到相应边界由 Agent 领取本轮领取的用户消息运行中纠偏与后续任务容易混在一起
ReactLoopAgent协调模型—工具循环Inbox、Session、可用能力和运行配置调度 Turn/Step,形成请求,处理模型流与工具,决定继续或结束Session 事实与执行状态变化请求与工具之间缺少统一调度者
Turn表示一轮执行的开始和结束本轮输入与运行状态Agent 记录 turn 边界并循环 Step一轮执行的完成、阻塞或中断等结果难以区分“这轮结束”与“某次模型调用结束”
Step表示一次循环推进已领取输入、当前历史和能力Agent 形成请求并处理模型/工具结果;必要时重试助手消息、工具结果、Step 结束状态无法清楚说明何时重新装配、何时只是重试

Turn 和 Step 是执行边界,不能因为它们在对象表中出现,就假定各自都是一个长期 Service 实例。

业务接纳成功后,RPC 的成功值是:

{ "accepted": true }

它表示该提交已进入目标 Agent 的输入路径;重复的已接纳提交也可能得到同样确认。它不承诺“测试通过”或“修复成功”。Agent 可以在确认响应返回途中已经开始执行,因此也不能保证所有执行事件都在网页收到 accepted 之后才到达。


小炎: 我想先把这段画对。accepted 应该接在 Agent 前面吗?

大老师: 应该画成返回 Client 的分支。Agent 处理的是用户消息,不是拿着 accepted 继续调用模型。

sequenceDiagram
    participant UI as UI / Client Session
    participant R as Client Remote
    participant C as Client Connection
    participant H as Host 接入
    participant G as Gateway
    participant S as Session Controller / 命令处理器
    participant A as Agent 解析器 / Registry
    participant L as 目标 Agent
    participant I as Inbox
    UI->>R: command:s_123、req_42、queue、content
    R->>C: session/prompt + 命名参数
    C->>H: HTTP + rpc_7 + 请求外壳
    H->>G: 已通过接入检查的调用
    G->>S: prompt(request, signal)
    S->>A: 按 sessionId 解析 Agent
    A-->>S: agent(s_123)
    S->>L: followup(UserMessage)
    L->>I: 排入 next-turn 并安排唤醒
    S-->>G: accepted=true
    G-->>H: 调用成功值
    H-->>C: rpc_7 对应的响应
    C-->>R: RPC 结果
    R-->>UI: 接纳确认
    Note over L,I: 执行由 Agent 调度,可与确认响应传输并行
    Note over UI,L: 最终消息与状态通过另行订阅返回

这个图只画提交路径。把订阅和模型循环也全部塞进来,会再次让人误以为它们都在等待这一次 HTTP 返回。


小炎: 接下来以 prompt 装配为主线。每一层是不是都会装配 prompt,只是装配内容不同?

大老师: 并不是。Client 组织命令,Remote 组织远程参数,Connection 组织网络外壳。它们转换的是提交的表示形式。真正面向模型的装配在 Agent 的 Step 中发生。

PromptAssembly 是一次装配产生的材料对象。Plugin 是装入运行环境、注册能力或贡献的组件;Agent 向当前 Scope 中可见的贡献收集材料:

sections:行为指令段落 → system prompt
contexts:当前运行状态 → 运行上下文消息
tools:工具名称、说明和参数结构 → 模型可见工具定义
variables:段落中需要代入的值 → 渲染时使用

比如本例可以收集到工作目录 /workspace/demo、编码指令,以及 bash、read、edit 的工具定义。收集结果并不是已经发给供应商的 HTTP 请求。

这里的 Projection,投影,指把一组数据转换成某种用途的表示。例如把装配材料变成需要记录的上下文消息,或把 Session 的当前有效历史变成模型消息。

对象或方法作用输入(本例)内部处理输出(本例)没有它会怎样
systemPrompt.assemble() / PromptAssembly汇集本 Step 可见的材料Agent Scope、已注册贡献、当前环境收集并排序指令与上下文,收集工具和变量四类材料的装配结果动态能力难以反映到当前模型视野
RuntimeContextProjection将运行状态变化转成消息工作目录等当前快照与旧快照比较内容;相同去重,变化或清空时产生相应表示必要的 runtime-context 用户消息环境变化可能丢失,或每步重复堆入相同内容
agent/pre-step 扩展点在进入 Step 前调整或拒绝已领取用户消息、候选上下文消息、运行位置可由注册组件改写消息或拒绝本次进入允许进入时的消息与装配材料统一的 Step 准入与改写需要放到其他位置
renderPrompt()把指令材料变成文字已排序 sections 与 variables插值、过滤空内容、连接段落本次 system 文本结构化段落尚不能作为模型指令使用
prepareRequest() / PreparedLlmCall确定模型与适配器能力路由配置、turn/step、取消信号经过请求扩展点,准备 provider/model 与适配器已解析配置、可调用的适配器及能力信息后续无法可靠决定请求该怎样交给哪个模型
SystemPromptProjection决定 system 如何进入事实与请求视图system 文本、旧记录、适配器能力决定追加、替换、清除或不变必要的 system/message 提交system 的变化难以按当前模型协议记录和投影
buildRequest() / Harness Request形成内部模型请求模型配置、工具、Session 有效历史记录必要的请求配置变化,取得消息和工具定义历史,构造请求messages、tools、toolHistory、配置与 signal装配材料还没有变成实际可调用的请求

本例对应的关键顺序是:

领取 Inbox 输入
→ 收集 PromptAssembly
→ 计算运行上下文变化
→ agent/pre-step 决策
→ 开始 Step,渲染 system 文本
→ prepareRequest,确定路由与适配器
→ 按适配器能力记录 system,首次尝试追加本 Step 新消息
→ buildRequest
→ 调用模型

表中的 prepareRequest()、buildRequest() 是 Agent 的方法;PromptAssembly、PreparedLlmCall、Request 是数据或调用准备结果。它们不是一批独立服务器。


小炎: Session 在这里提供什么?是不是把完整日志直接塞进 prompt?

大老师: Session 提供当前有效的模型消息视图,不把所有运行事件原样发送给模型。

Surface 是 Session 当前有效消息视图的结构。deriveMessages() 从它导出模型消息;压缩等机制可以改变有效视图,同时保留原始日志。模型需要用户、助手和工具反馈,不需要把每个内部调度事件当成聊天文本。

另外,toolHistory() 这个名字很容易误导:它记录的是历史工具定义及增删变化,供适配器投影能力使用。执行过哪些工具、返回了什么,则通过相应消息和结果进入 deriveMessages() 的历史。

本例第一次请求的模型视野,可以简化成:

system:Agent 行为指令与当前可见的工具使用指导
user:请运行测试,如果失败就修复 src/math.ts,然后告诉我结果。
user:当前运行上下文,例如 cwd=/workspace/demo
tools:bash、read、edit 的定义
config:provider/model 等模型配置

这是帮助理解的内容示意,不是某个供应商的逐字段 HTTP 格式。实际指令文本与消息投影方式取决于当前贡献和适配器。

没有 Session 提供共同事实层,模型下一步看到的历史与网页展示的历史就需要分别维护,更容易出现“界面显示过,模型却不知道”或“模型用过,恢复后却找不到依据”的问题。


小炎: 请求发出之后,模型和工具各自做什么?模型返回 bash 就等于命令已经执行了吗?

大老师: 模型返回工具调用,表示提出行动。真正运行命令的是 Harness 的工具执行能力。

LLM Adapter 是模型协议适配器,把 Harness 的统一请求转换成供应商协议,并将返回的数据归一化。Tool call 是模型给出的工具名称与参数。Sandbox 是操作系统层面的执行限制;审批和 guard 则是执行前的策略检查。这些角色都不能与模型的判断混为一谈。

对象作用输入(本例)内部处理输出(本例)没有它会怎样
LLM Adapter隔离模型供应商协议差异Harness Request,选择 DeepSeek转换消息与工具,发送请求,将返回归一化异步 StreamChunk 流Agent 必须直接理解每家供应商协议
Model在当前视野内决定下一步指令、历史、工具定义生成文本或结构化行动请求可能给出 bash 调用,命令为 pnpm test需要其他机制选择行动并生成回答
Tool guard / Approval / Sandbox判断并限制可执行行动tool call、参数、策略与执行环境检查准入;策略需要时请求审批;按环境限制执行允许、拒绝或等待,以及受限执行条件行动缺少相应策略检查或系统约束
Tool Executor执行已准入的工具bash 调用、取消信号调用真实工具实现,收集结果例如退出码 1 和测试失败输出模型只提出动作,文件与进程不会因此改变

假设工具实际返回:

command: pnpm test
exitCode: 1
output: src/math.ts:8 expected 3 received 4

Agent 把助手的工具调用与工具结果记录到 Session。注意,工具成功运行、测试通过、用户目标完成,是三个不同判断:命令可以成功启动,却报告测试失败。


小炎: 那 Step 2 会收到什么?是不是把工具结果放进 Inbox,再拼一次完整 prompt?

大老师: 工具结果进入 Session 后,下一次请求就能通过模型消息投影看到它。继续循环并不要求 Inbox 一定出现新消息。

Inbox 管理新输入何时生效;Session 保留已经发生的对话与执行事实。某些工具还会另外提供补充上下文,运行中的用户也可能发来 steer,这些才可能成为下一 Step 要领取的输入。

比较项Step 1Step 2
新领取的输入本例的用户指令可以为空;也可能有纠偏或补充上下文
历史事实原有会话历史及本次已接纳的模型输入增加 Step 1 的助手工具调用和测试结果
装配材料收集当前指令、环境、工具、变量再次收集,以反映可能变化的能力和环境
模型配置准备本次 provider/model 与能力重新准备;可受请求路由影响
本次可能输出请求执行 pnpm test结合失败信息,可能先读取 src/math.ts 再判断修改

这里的“重新装配”不意味着把相同的 system 和环境消息无限追加。未变化的内容会通过相应记录与投影机制复用或去重;新请求仍会形成完整的当前视野。对本例而言,最关键的新内容是工具反馈。

还要区分 新 Step 和 同一 Step 的重试。当前实现对一个已进入的 Step 使用同一份装配材料;每次模型请求尝试会重新准备和构建请求,本 Step 新领取的消息只在首次尝试追加,避免因为重试复制用户输入。

flowchart TB
    I[Inbox:在执行边界领取输入,可为空] --> P[本 Step:收集与渲染材料]
    P --> B[准备模型配置,记录必要变化,构建请求]
    S[(Core Session:执行事实与有效消息视图)] -->|deriveMessages 提供历史| B
    B --> M[LLM Adapter 与 Model]
    M --> D{输出是否包含工具调用?}
    D -->|有| T[准入检查与工具执行]
    T -->|工具结果| S
    M -->|助手消息| S
    P -->|必要的新指令与上下文| S
    B -->|必要的请求配置记录| S
    T --> N{是否需要继续?}
    N -->|继续,允许没有新输入| I
    N -->|结束| E[收束本轮]
    D -->|没有工具调用,普通完成| E
    E -->|运行边界| S

这是普通执行主线的简图,省略了失败、阻塞、取消与重试分支。Session 在旁边持续参与,工具结果也没有被画成必经 Inbox 的输入。


小炎: 最后模型回答出来,为什么还需要 follow、control 和 Client model?

大老师: 因为界面需要持续观察执行过程,而一次提交 RPC 已经结束。还需要同时处理历史、正在生成的输出,以及会话状态。

Stream,流,是持续送来多条更新的返回方式。Frame,帧,是一条流中的更新单位。Cursor,游标,说明历史读取到哪里,帮助断线后补齐。Client model 再把这些更新折叠成 UI 可以使用的状态。

对象作用输入(本例)内部处理输出(本例)没有它会怎样
Session 事件与观察机制将执行事实提供给观察者用户、助手、工具及运行边界的变化Session 记录事实,相关观察器提供历史与变化;持久化能力另行负责保存可回放的记录与更新通知界面和恢复逻辑缺少统一变化来源
session.follow跟随一个会话的历史与实时助手输出会话 id、历史位置、订阅取消信号提供会话记录,并承载助手实时流帧测试调用与结果记录,以及正在生成的回答片段需要另一种会话跟随与历史补齐机制
session.control跟随 Host 范围的会话状态投影控制订阅与当前会话投影提供状态基线和后续投影变化当前哪些会话存在、是否运行等状态界面需要其他机制维护 Host 范围的会话状态
Client model / Projection把更新变成界面状态历史事件、实时帧、状态变化合并更新,维护历史位置与本地视图可渲染的消息、工具卡片与运行状态UI 必须逐个处理原始协议更新
Conversation / UI展示执行过程和结果Client 的会话视图渲染文本、工具过程和状态人看到测试失败、检查文件、后续回答执行仍可发生,但人没有这一展示入口

所以不能把所有实时助手输出都划给 control。当前 follow 也承载助手的实时流帧;control 关注 Host 范围的会话状态投影。取消 Agent 是另一个命令,不是“关闭流就取消任务”。

flowchart LR
    C[Client 提交] -->|prompt RPC| H[Host 业务接纳]
    H -->|接纳确认 accepted| C
    H --> A[Agent 调度与执行]
    A --> S[(Session 执行事实)]
    S --> F[follow:历史记录]
    A -->|助手实时帧| F
    A -->|执行状态影响会话投影| P[Host 会话状态投影]
    P --> K[control:状态基线与更新]
    F --> V[Client model / UI]
    K --> V

这里画的是数据来源和返回职责,不代表每次提交都要新开两个流。界面可以已有订阅,且响应与流更新的网络到达顺序不能当成业务顺序。


小炎: 那我把原来的 11 步改成下面这一版,应该怎样说才完整?

大老师: 可以这样向别人讲:

  1. 网页把用户文本整理成指定会话的提交命令,并显示待确认状态。
  2. Client Remote 根据接口描述,把方法调用转换成 endpoint 与参数。
  3. Connection 添加网络调用标识和请求外壳,发往 Host。
  4. Host 接入层检查认证和协议格式,Gateway 按 endpoint 找到 Service 方法。
  5. Session Controller 的命令处理逻辑检查内容与业务条件,并按 sessionId 解析目标 Agent。
  6. 用户消息按 queue 或 steer 语义进入 Agent 的输入路径;接纳结果返回 Client。
  7. Agent 在相应执行边界领取新输入,开启或继续 Turn/Step。
  8. 每个新 Step 收集当前能力与上下文,结合 Session 有效历史形成模型请求。
  9. LLM Adapter 调用模型;模型提出工具调用时,Harness 检查并执行。
  10. 用户输入、模型输出、工具反馈和运行事实持续进入 Session,Agent 据此决定继续或结束。
  11. 已建立的订阅把历史、实时输出和状态送到 Client,Client 更新界面。

其中第 6 步的“已接纳”和第 11 步的“最终答案”是不同返回。对于终端、SDK 或其他程序入口,前面的 Client/Remote/Gateway 路径可能不同;这里讲的是普通网页调用,不能要求所有入口都照搬这一组对象。


小炎: 所以 Harness 不只是“把 prompt 发给模型”,还要把哪些责任分开?

大老师: 至少要分开提交表示、传输契约、业务接纳、执行调度、事实记录和观察显示。每一层的输出只完成自己的职责:网络成功不等于业务接纳,业务接纳不等于模型完成,模型完成也不等于测试通过。

总结:从调用顺序看责任边界

whiteboard_exported_image.png

一条用户指令先成为 Client 命令,再成为远程方法参数和网络请求;Host 将其派发给业务 Service,业务层把输入交给目标 Agent。Agent 按 Turn/Step 调度,结合当前能力与 Session 历史构造模型请求。模型的行动请求由工具执行能力落实,反馈记录到 Session,并影响后续 Step。界面通过持续订阅观察事实与状态。

整条链中,endpoint 选择能力入口,sessionId 选择执行会话,requestId 标识用户提交,rpcId 配对网络调用。Session 是共同事实层,Client 是它的界面镜像;accepted 是接纳确认,最终消息另行返回。

可以迁移到其他系统的一条设计认识是:接收任务、执行任务、记录事实和展示进度需要清楚的责任边界。判断一次调用“成功”之前,先说明它确认了哪一层的成功。


源码复查入口(当前工作区版本):

学习进度:本文整理和校正 N03 的调用边界,N03 仍为“讲解中”;不新增 C14“模型请求装配”的学习进度。后续候选仍为 N04“执行世界的能力图”。