Fleet API 参考

August 14, 2026 · View on GitHub

宿主进程通过主入口提供 ctx.fleet,独立 @wha1echai/dsh-cross-session/tool 入口把该服务暴露为模型工具。Consumer 必须注入 fleet,不得直接依赖 ctx.agents

当前 Provider 只访问同一运行中 DSH runtime(即同一个 dsh 进程)的 live Agent。它不提供跨进程、跨终端或跨设备、本地到服务器、remote Web、gateway、daemon 或多 runtime 路由。

配置

- id: dsh-cross-session
  name: '@wha1echai/dsh-cross-session'
  config:
    defaultTailMessages: 8
    maxTailMessages: 32
    maxMessageTextChars: 2000
    targetRefTtlMs: 300000
    selectionTtlMs: 60000
    maxSelectionsPerCaller: 32
    replyReceiptTtlMs: 600000
    maxReplyRecordsPerCaller: 32
    maxReplyMessages: 8
    maxReplyTextChars: 8000
字段默认值约束含义
defaultTailMessages8正安全整数,不大于 maxTailMessagesdirect/confirmed inspect 的默认尾部消息数
maxTailMessages32正安全整数单次 inspect 的尾部消息上限
maxMessageTextChars2000正安全整数每条摘要的文本字符上限
targetRefTtlMs300000正安全整数caller-bound target reference 有效期
selectionTtlMs60000正安全整数single-attempt write selection 有效期
maxSelectionsPerCaller32正安全整数每个 exact caller 保留的最大 live selection 数
replyReceiptTtlMs600000正安全整数开始观察 reply 以及保留未消费 terminal result 的有效期
maxReplyRecordsPerCaller32正安全整数每个 exact caller 的最大未消费/active reply record 数
maxReplyMessages8正安全整数turn result 保留的尾部 text-bearing assistant message 数
maxReplyTextChars8000正安全整数每条 reply assistant text 的字符上限

Expiry 采用 lazy prune,不运行后台 timer。Selection 超限时淘汰最旧记录;reply record 达到上限时,新 selected send 在 target 副作用前返回 fleet-reply-capacity

工具配置与发现

独立 dsh-cross-session-tools row 默认 controlMode: read-only

controlMode模型可见工具
read-onlyfleet_listfleet_inspect
message只读工具 + fleet_sendfleet_steer
full全部工具,包括 fleet_cancel

已运行 Session 在下一次模型请求中通过正常 ToolRuntime composition 看到当前工具。核心 ./tool 入口只注册上述五个工具,不注册或宣传 subagent/workflow 工具。可选 ./reply-job 入口在 ctx.jobs 可用时另注册 fleet_wait;实际启动 job 还要求 owning Agent 的 composition 有官方 Jobs controller Consumer。两个 Consumer 都应挂在 intended Agent 的 host/preset composition,ToolRuntime 会按注册 scope 向该 composition 及其后代暴露工具,不向 sibling preset 暴露。

五个工具都要求 owning Agent,并且只从 exec.agent.session.id 派生 caller identity。模型不能提交 caller id。List/inspect 为 parallel;send/steer/cancel 为 exclusive。

写入工具的副作用

fleet_send 把消息排入目标的下一 turn;如果目标正在运行,它等待当前 turn 结束,不修改该 turn。fleet_steer 把消息放入当前 turn 的下一 step boundary;目标 idle 时它也会唤醒目标并以该 steering input 开始一个 turn。需要等待正在运行的目标完成当前 turn 时使用 fleet_send,需要修改当前工作方向时使用 fleet_steer。两者都可能触发模型请求和工具调用,因此可能消耗模型与工具资源。fleet_cancel 会中断目标 Agent 的活动工作;取消不会回滚已经被模型或工具接受的工作,工具调用外层的 late abort 也不撤销已发生的 Fleet 写入。

模型 confirmed-target protocol

fleet_list
  -> caller-bound target_ref
fleet_inspect(target_ref)
  -> exact-Agent-bound selection_handle when writable
fleet_send / fleet_steer / fleet_cancel(selection_handle)

模型不再为 inspect/write 提交 session_id,也没有 direct-ID fallback。Handle 是 byte-exact opaque value,不做 trim 或其他规范化;损坏、带首尾空白或错误的 handle 只会失效,不会被解析为 Session ID 或替换成其他目标。

工具参数Canonical output
fleet_listroots_only?, running_only?{ agents: FleetTargetView[], count }
fleet_inspecttarget_ref, tail_messages?{ agent: FleetInspectView, selection? }
fleet_sendselection_handle, text{ sessionId, messageId, deliveryId, replyReceipt, replyReceiptExpiresAt }
fleet_steerselection_handle, text{ sessionId, messageId, deliveryId }
fleet_cancelselection_handle, keep_inbox?{ sessionId, accepted: true }

FleetTargetView 包含完整 FleetAgentView,并增加:

{
  targetRef: string
  targetRefExpiresAt: number
}

fleet_inspect 返回:

{
  agent: FleetInspectView
  selection?: {
    handle: string
    expiresAt: number
  }
}

Self target 和 runtime delegated target 可以 inspect,但不返回 selection。写成功结果中的 sessionId 来自 Provider 的 exact target record,Consumer 不从 handle 或模型输入推断。

Selection 固定 single-attempt。空文本等输入失败发生在消费前;所有 caller、target 和当前写授权检查通过后,在调用 Agent 副作用前消费。Agent 方法抛错或 ToolRuntime late abort 都不恢复 selection。

Service

可信程序化 Consumer 可继续使用 direct lane:

ctx.fleet.list(filter?)
ctx.fleet.inspect(sessionId, options?)
ctx.fleet.send(sessionId, text, caller?)
ctx.fleet.steer(sessionId, text, caller?)
ctx.fleet.cancel(sessionId, options?)
ctx.fleet.subscribe(listener)

模型工具使用 confirmed-target lane:

ctx.fleet.listTargets(options)
ctx.fleet.inspectTarget(targetRef, options)
ctx.fleet.sendSelected(selectionHandle, text, options)
ctx.fleet.waitForReply(replyReceipt, options)
ctx.fleet.steerSelected(selectionHandle, text, options)
ctx.fleet.cancelSelected(selectionHandle, options)

两类 handle 只由 Provider 保存,并同时绑定:

  • exact caller Agent 与 callerSessionId
  • exact target Agent 与原始 sessionId
  • 当前 Provider instance;
  • expiry。

每次使用都重新检查 ctx.agents.get(id) === exactAgent。Confirmed-target options 还必须携带 ToolRuntime 提供的 exact caller Agent;callerSessionId 只是与该对象的交叉校验,不能单独授予归因。Caller/target disposal、同 ID replacement、expiry、caller mismatch、Provider unload 和重复 selection 使用都会 fail closed。Provider unload 后 retained Service reference 不读取 AgentRegistry。

Direct send / steer 的消息来源固定为:

{ kind: 'plugin', plugin: 'dsh-cross-session' }

Confirmed-target selected send / steer 使用 versioned fleet-relay source:

{
  kind: 'fleet-relay'
  version: 1
  form: 'relay'
  senderSessionId: SessionId
  deliveryId: FleetDeliveryId
}

senderSessionId 只能来自 Provider 保存的 exact caller Agent;deliveryId 由 Provider 生成,用于 receipt 和 inspect correlation。工具输入、标题、target reference、selection handle 和正文都不能覆盖这些字段。正文以同时包含 encoded sender 和 encoded delivery id 的 header,加固定 marker 后从下一个独立 text block 开始的原始 body 发送;body 保持不可信模型输入,不参与授权或 correlation 解析。Direct caller 的字符串 callerSessionId 不会伪造 relay source。

Direct/selected cancel 的原因固定为:

{ kind: 'hook', reason: 'fleet-cancel' }

Reply observation 与 Jobs Consumer

Selected send 返回的 replyReceipt 是 caller-bound、exact-target-bound、Provider-bound、single-observer capability。Provider 在 followup() 前创建 record,并按公开事件建立:

exact message id -> agent/inbox/claimed turn -> same-turn assistant/message -> turn/end

waitForReply() 返回 claimed turn 的结果,不声称 assistant output 只由该 relay 因果产生,也不等待 whole-Agent idle。它只覆盖 send/followup,不覆盖 steer。Turn result 报告 admitted、完整 turnEndReason、bounded assistantMessagesomittedAssistantMessages;另有 claim 前 discarded 与 terminal result 前 target-unavailable

Abort 只停止 observation,不 cancel 或 steer target。结果可在 wait 注册前完成并短期保留一次;第二次观察、caller mismatch、expiry 或 stale receipt 返回 fleet-reply-invalid

可选 @wha1echai/dsh-cross-session/reply-jobctx.jobs 挂载时注册:

fleet_wait(reply_receipt) -> { jobId }

它只生产 owner-scoped fleet-reply job,并通过独立入口配置 maxOutputBytes(默认 300000)限制官方 job output/notice 的完整 UTF-8 大小。官方 dsh-tool-jobs 继续提供 job_output / job_list / job_kill、controller 和 completion notice;Fleet 不复制这些能力。./reply-job 应与这些 controls 挂在同一个 composition scope。Job kill 只 abort observation,不取消 target。

Agent 视图与 runtime ownership

interface FleetAgentView {
  sessionId: string
  status: AgentStatus
  kind: 'root' | 'delegated'
  control: 'direct' | 'subagent' | 'observe-only'
  title?: string
  parentSessionId?: string
  cwd?: string
  blank: boolean
  queueCount: number
  updatedAt?: number
}

kind 的唯一权威来源是 exact live Agent 是否属于 ctx.agents.roots()。Durable originparentSession 不参与 runtime classification 或授权;parentSession 只投影为 lineage metadata。

Runtime delegated Agent 保持只读:有 ctx.subagents 时 direct write 返回 fleet-delegated-write-deferred,否则返回 fleet-observe-only。Confirmed inspect 不为 delegated target 签发 selection。L2b 才会设计精确 parent authority 的 child write API。

title 是可选的展示字段,只在 sessionTitle 服务可用且 exact live Session 的日志中已有 session/title 时出现。Fleet 只调用 get(agent.session),不调用 refreshregister 或任何标题 Provider;服务缺失、卸载或没有已记录标题时省略该字段。标题不参与 Session identity、list 顺序、过滤、target reference、selection、路由或授权。

FleetInspectView 还包含 omittedMessagestailMessages。Relay 消息的摘要可带窄 relay 投影 { version, form, senderSessionId, deliveryId };不会暴露 target reference 或 selection handle。omittedMessages 只统计过滤出 user/assistant 后因 tail 上限省略的消息数,不统计 tool、reasoning 或其他角色。每条 FleetMessageSummary.textTruncated 只表示当前摘要的原始文本超过 maxMessageTextChars,与 tail omission 独立。

错误码

FleetError 继承 DSH HarnessError,真实 ToolRuntime 保留:

{
  "name": "FleetError",
  "code": "fleet-selection-invalid"
}

每个 FleetError 同时提供:

{
  "actionTaken": false,
  "targetSubstitutionAllowed": false,
  "nextAction": "relist-or-ask-user"
}
code条件
fleet-unavailableProvider 已卸载
fleet-not-founddirect API 找不到 live sessionId
fleet-self-targetdirect caller 控制自己
fleet-delegated-write-deferreddelegated write 需要未来 L2b parent authority
fleet-observe-onlydelegated target 没有可用 subagent seam
fleet-empty-textsend/steer 文本为空
fleet-caller-unavailableconfirmed-target caller 已不是 exact live Agent
fleet-target-reference-invalidtarget reference unknown、expired、mismatched 或 stale
fleet-selection-invalidselection unknown、expired、mismatched、stale 或已消费
fleet-reply-invalidreply receipt unknown、expired、foreign、stale、active 或已消费
fleet-reply-capacitycaller 的 unconsumed/active reply record 已达配置上限

Selected write receipt 的 messageId 是消息 correlation,deliveryId 是 Provider 生成的 relay observability identity。Send 的 replyReceipt 允许之后观察 exact claimed turn;delivery receipt 本身仍只代表 Agent inbox 方法同步接受,不表示目标已 claim、完成 turn 或产生 reply。Selected steer 不返回 reply receipt,也不拥有独立 reply 语义。target_ref / selection_handle 不出现在 relay source、body、receipt 或 inspect projection 中,也不进入 transient Provider relay state;正常 DSH tool/call audit 仍保留工具 arguments。

无效 reference/selection 的错误明确说明:

No action was taken. Do not substitute another Fleet session. Relist or ask the user.