Liking cljdoc? Tell your friends :D

im.ttalk.agent.agui.runtime

Agent Runtime —— 会话注册表 + run 生命周期 + 事件订阅。

设计见 docs/agent-runtime-design.md。一句话:run 的寿命不再由调用方的寿命 决定。agent/chat-stream 把 token 直推调用方的 sink,sink 死了这轮的输出就 没了;这里 run 立刻起跑,事件进会话的环形缓冲再扇出给订阅者——断线重连凭 :since 补齐缺口,另一个请求线程凭 conversation-id 就能 stop / resume。

零 web 依赖(design-principles §2):订阅是回调(on-event + 返回退订 函数),与 on-token 同族。SSE 帧 / 路由 / 鉴权在 examples/copilotkit/。

本 ns 立的不变量(§3.3):

一个 conversation-id 恰好对应一个 agent 状态机,且同时至多一个 in-flight run。

这条今天靠 simple-agent docstring 里的警告维持(「同一个 agent 上不要并发多个 chat-async」),进了注册表就靠结构维持。

(def rt (runtime {:agent-fn (fn [{:keys [conversation-id tools]}]
                              (agent/create-agent {:provider p :memory store
                                                   :conversation-id conversation-id
                                                   :tools (into [#'my-tool] tools)}))}))
(subscribe rt "c1" {:on-event prn})          ;; => 退订函数
(start-run! rt "c1" "北京天气?")             ;; => {:status :started :run-id ...}
(stop! rt "c1")
Agent Runtime —— 会话注册表 + run 生命周期 + 事件订阅。

设计见 docs/agent-runtime-design.md。一句话:**run 的寿命不再由调用方的寿命
决定**。`agent/chat-stream` 把 token 直推调用方的 sink,sink 死了这轮的输出就
没了;这里 run 立刻起跑,事件进会话的环形缓冲再扇出给订阅者——断线重连凭
`:since` 补齐缺口,另一个请求线程凭 conversation-id 就能 stop / resume。

**零 web 依赖**(design-principles §2):订阅是回调(`on-event` + 返回退订
函数),与 `on-token` 同族。SSE 帧 / 路由 / 鉴权在 examples/copilotkit/。

本 ns 立的不变量(§3.3):

> **一个 conversation-id 恰好对应一个 agent 状态机,且同时至多一个 in-flight run。**

这条今天靠 `simple-agent` docstring 里的警告维持(「同一个 agent 上不要并发多个
chat-async」),进了注册表就靠结构维持。

```clojure
(def rt (runtime {:agent-fn (fn [{:keys [conversation-id tools]}]
                              (agent/create-agent {:provider p :memory store
                                                   :conversation-id conversation-id
                                                   :tools (into [#'my-tool] tools)}))}))
(subscribe rt "c1" {:on-event prn})          ;; => 退订函数
(start-run! rt "c1" "北京天气?")             ;; => {:status :started :run-id ...}
(stop! rt "c1")
```
raw docstring

awaitingclj

(awaiting rt conv-id)

会话是否在等 resume(暂停中)。返回 {:run-id :reason :pending-tool :pending-tools} 或 nil。:pending-tools 是同一批里所有被叫停的调用(单条时就是 [pending-tool])。

会话是否在等 resume(暂停中)。返回 `{:run-id :reason :pending-tool :pending-tools}`
或 nil。`:pending-tools` 是同一批里所有被叫停的调用(单条时就是 `[pending-tool]`)。
sourceraw docstring

buffered-eventsclj

(buffered-events rt conv-id)

会话在事件存储里现存的事件(只读)。

缺省存储是有界的环形缓冲(:buffer-size,缺省 512 条)且不落库——它是为 断线续传服务的,不是事件日志;想要完整历史找 ChatMemory(§8.2)。插了持久化的 :event-store 时,这里返回该存储保留的部分(会话不在内存里也能读)。 /threads/:id/events 这类只读端点照实返回它。

会话在事件存储里**现存**的事件(只读)。

缺省存储是**有界**的环形缓冲(`:buffer-size`,缺省 512 条)且**不落库**——它是为
断线续传服务的,不是事件日志;想要完整历史找 ChatMemory(§8.2)。插了持久化的
`:event-store` 时,这里返回该存储保留的部分(会话不在内存里也能读)。
`/threads/:id/events` 这类只读端点照实返回它。
sourceraw docstring

conversationsclj

(conversations rt)
source

forget!clj

(forget! rt conv-id)

把一个会话从注册表里摘掉(不动 ChatMemory——历史归历史)。

在跑的 run 先停掉:不停的话它收尾时还会往一个没人再读的缓冲里写。

把一个会话从注册表里摘掉(**不动 ChatMemory**——历史归历史)。

在跑的 run 先停掉:不停的话它收尾时还会往一个没人再读的缓冲里写。
sourceraw docstring

purge!clj

(purge! rt conv-id)

删除线程时用:forget! 之外再让事件存储真删(es-delete!)。 缺省环形缓冲上与 forget! 等价;持久化存储上,forget! 只是腾内存、数据还在, purge! 才把这条线程的事件删掉。同样不动 ChatMemory(那由调用方决定)。

删除线程时用:`forget!` 之外再让事件存储**真删**(`es-delete!`)。
缺省环形缓冲上与 `forget!` 等价;持久化存储上,`forget!` 只是腾内存、数据还在,
`purge!` 才把这条线程的事件删掉。同样不动 ChatMemory(那由调用方决定)。
sourceraw docstring

resume-run!clj

(resume-run! rt conv-id decision)
(resume-run! rt conv-id decision payload)
(resume-run! rt conv-id decision payload {:keys [tools] :as opts})

凭 conversation-id 恢复暂停的会话——这就是跨请求 HITL:审批按钮是另一个 HTTP 请求,它手上只有 conv-id,没有 agent 对象。

decision / payload 语义与 agent/resume 逐字相同(approved / rejected / reply / retry / proceed,见 docs/hitl-timeline-design.md §2)。

前端工具(AG-UI client-side tool)走的也是这条路::reply + {:message 结果} ——「pending 工具不执行、载荷即其结果」正是 ask-user 语义(§7.2)。

凭 conversation-id 恢复暂停的会话——**这就是跨请求 HITL**:审批按钮是另一个
HTTP 请求,它手上只有 conv-id,没有 agent 对象。

`decision` / `payload` 语义与 `agent/resume` 逐字相同(approved / rejected /
reply / retry / proceed,见 docs/hitl-timeline-design.md §2)。

前端工具(AG-UI client-side tool)走的也是这条路:`:reply` + `{:message 结果}`
——「pending 工具不执行、载荷即其结果」正是 ask-user 语义(§7.2)。
sourceraw docstring

run-detached!clj

(run-detached! {:keys [agent message on-event opts run-id now]})

在一个临时发射器上跑一次 agent 调用——不进注册表:没有会话条目、 没有环形缓冲、没有订阅表、没有 stop / resume / 暂停。

为什么要有这条:CopilotKit 的 /suggest(「猜用户下一句想说什么」)是一次 不留痕的 run。它刻意不走 runner——runner 会按 threadId 写线程表,于是 一次一次性的建议把一堆废线程留在那儿。这里同样:事件直接给 on-event, 跑完就没了。

agent 由调用方给(它决定这次用哪份配置、落不落 memory——策略不该埋在 库里)。返回 CompletableFuture,完成值是 agent 的结果。

  • :message 这一轮的用户输入
  • :on-event (fn [中立事件])
  • :opts 透传给 chat-async(:tool-choice / :max-iterations …)
  • :run-id 可选,缺省现生成
在一个**临时发射器**上跑一次 agent 调用——**不进注册表**:没有会话条目、
没有环形缓冲、没有订阅表、没有 stop / resume / 暂停。

为什么要有这条:CopilotKit 的 `/suggest`(「猜用户下一句想说什么」)是一次
**不留痕**的 run。它刻意不走 runner——runner 会按 threadId 写线程表,于是
一次一次性的建议把一堆废线程留在那儿。这里同样:事件直接给 `on-event`,
跑完就没了。

`agent` 由调用方给(**它决定这次用哪份配置、落不落 memory**——策略不该埋在
库里)。返回 `CompletableFuture`,完成值是 agent 的结果。

- `:message`         这一轮的用户输入
- `:on-event`        (fn [中立事件])
- `:opts`            透传给 `chat-async`(`:tool-choice` / `:max-iterations` …)
- `:run-id`          可选,缺省现生成
sourceraw docstring

run-statusclj

(run-status rt conv-id)
source

runtimeclj

(runtime {:keys [agent-fn] :as opts})

构造运行时。显式值,无全局单例——CopilotKit 的 in-memory store 是进程全局 的,为此得写一段 warning 解释「你把所有会话的 bound 改了」(§8.4);这里想建 几个建几个。

  • :agent-fn 必填 (fn [{:keys [conversation-id tools]}] agent)。 :tools 是本 run 追加的内联工具(AG-UI 前端 action), 没有则为 []。同一 conversation-id 的 agent 会被 runtime 换上会话级 state-atom,故每次现建也不丢暂停态。
  • :on-concurrent :reject(缺省)| :supersede(§4.4)
  • :buffer-size 会话事件环形缓冲条数(缺省 512);只在没给 :event-store 时生效
  • :event-store 事件存储(agui.event-store/EventStore),缺省 (es/ring-store {:buffer-size ..})——即原来的进程内环形缓冲。 可替换挂点:外部库实现该协议即可持久化事件(本库不带持久化 实现,见该 ns 文档与 designs/agui-borrow.md §8)
  • :idle-ttl-ms 空闲会话驱逐(缺省 30min;有 run / 有订阅者 / 暂停中的不驱逐)
  • :supersede-wait-ms supersede 时等旧 run 收尾的上限(缺省 5s)
  • :orphan-cancel-ms 最后一个订阅者走后过这么久仍没人来,就以 :orphan 原因 停掉在跑的 run(缺省 0 = 关,与上游同:没人看的 run 照样跑完——断线 ≠ 停止,刷新正是 :since 要救的场景)。 给「烧 token 的长 run 没人看就止损」的部署方 (designs/agui-borrow.md §5)
  • :now (fn [] ms),可注入(测试)
  • :state-tools? 给模型两把写共享状态的工具(缺省 false)。开了之后 agui.tools/state-tools 的 AGUISendStateSnapshot / AGUISendStateDelta 进每个 run 的工具表,模型于是写得动 共享状态。缺省关:它改变模型看见的工具列表,装配方 该显式点头;/info 的 capabilities.state 跟着报
  • :subagent-events? 子 agent lane 的事件(缺省 false)。开了之后 :agent-fn 会多收到一个 :subagent-observer,把它交给 delegate-tool 的 :observer,委派期间的 token / 工具调用 就带着 subagentRunId 进事件流 (docs/subagent-event-attribution-design.md)。 缺省关不是保守:@ag-ui/client ≤ 0.0.57 在 HTTP transport 里校验事件的 discriminated union,一条 SUBAGENT_STARTED 掐断整条流,客户端侧救不回来。
  • :event-transform (fn [{:keys [run-id conversation-id]}] (fn [event] events)), 可选。事件流插件的挂点(见 agui.event/emitter 的 :transform):每个 run 现造一个有状态的 transform。 agui.a2ui/event-transform(本模块)与 copilotkit.genui/event-transform(examples)各是现成的一个
构造运行时。**显式值,无全局单例**——CopilotKit 的 in-memory store 是进程全局
的,为此得写一段 warning 解释「你把所有会话的 bound 改了」(§8.4);这里想建
几个建几个。

- `:agent-fn`          **必填** `(fn [{:keys [conversation-id tools]}] agent)`。
                       `:tools` 是本 run 追加的内联工具(AG-UI 前端 action),
                       没有则为 `[]`。同一 conversation-id 的 agent 会被
                       runtime 换上会话级 state-atom,故每次现建也不丢暂停态。
- `:on-concurrent`     `:reject`(缺省)| `:supersede`(§4.4)
- `:buffer-size`       会话事件环形缓冲条数(缺省 512);只在没给 `:event-store` 时生效
- `:event-store`       事件存储(`agui.event-store/EventStore`),缺省
                       `(es/ring-store {:buffer-size ..})`——即原来的进程内环形缓冲。
                       **可替换挂点**:外部库实现该协议即可持久化事件(本库不带持久化
                       实现,见该 ns 文档与 designs/agui-borrow.md §8)
- `:idle-ttl-ms`       空闲会话驱逐(缺省 30min;有 run / 有订阅者 / 暂停中的不驱逐)
- `:supersede-wait-ms` supersede 时等旧 run 收尾的上限(缺省 5s)
- `:orphan-cancel-ms`  最后一个订阅者走后过这么久仍没人来,就以 `:orphan` 原因
                       停掉在跑的 run(缺省 **0 = 关**,与上游同:没人看的 run
                       照样跑完——断线 ≠ 停止,刷新正是 `:since` 要救的场景)。
                       给「烧 token 的长 run 没人看就止损」的部署方
                       (designs/agui-borrow.md §5)
- `:now`               `(fn [] ms)`,可注入(测试)
- `:state-tools?`      给模型两把写共享状态的工具(缺省 **false**)。开了之后
                       `agui.tools/state-tools` 的 `AGUISendStateSnapshot` /
                       `AGUISendStateDelta` 进每个 run 的工具表,模型于是写得动
                       共享状态。**缺省关**:它改变模型看见的工具列表,装配方
                       该显式点头;`/info` 的 `capabilities.state` 跟着报
- `:subagent-events?`  子 agent lane 的事件(缺省 **false**)。开了之后
                       `:agent-fn` 会多收到一个 `:subagent-observer`,把它交给
                       `delegate-tool` 的 `:observer`,委派期间的 token / 工具调用
                       就带着 `subagentRunId` 进事件流
                       (docs/subagent-event-attribution-design.md)。
                       **缺省关不是保守**:`@ag-ui/client` ≤ 0.0.57 在 HTTP
                       transport 里校验事件的 discriminated union,一条
                       `SUBAGENT_STARTED` 掐断整条流,客户端侧救不回来。
- `:event-transform`    `(fn [{:keys [run-id conversation-id]}] (fn [event] events))`,
                       可选。**事件流插件的挂点**(见 `agui.event/emitter` 的
                       `:transform`):每个 run 现造一个有状态的 transform。
                       `agui.a2ui/event-transform`(本模块)与
                       `copilotkit.genui/event-transform`(examples)各是现成的一个
sourceraw docstring

shutdown!clj

(shutdown! rt)

停掉所有 in-flight run、等它们收尾(有上限)、关掉所有订阅者、清表。

等不到的不硬等::shutdown-wait-ms 到点就走人——事件流的终态由 finish-run! 在各自线程上照常补,只是我们不再等它。

停掉所有 in-flight run、等它们收尾(有上限)、关掉所有订阅者、清表。

等不到的不硬等:`:shutdown-wait-ms` 到点就走人——事件流的终态由 `finish-run!`
在各自线程上照常补,只是我们不再等它。
sourceraw docstring

start-run!clj

(start-run! rt conv-id message)
(start-run! rt
            conv-id
            message
            {:keys [tools discard-pause? parent-run-id state] :as opts})

起一个新 run。立刻返回,run 在后台跑。

返回 {:status :started :run-id ... :since <起跑前的 seq 水位>},或:

  • {:status :busy :run-id <在跑的那个>}(:reject 策略下已有 in-flight run)
  • {:status :awaiting-resume :pending-tool ... :reason ...}——会话暂停中, 先 resume-run!。这里刻意不沿用 simple-agent 的 cancel-pending! 语义(新 turn 静默丢弃未 resume 的暂停):web 场景下「用户没点审批就又发 了一句」应当显式暴露,静默丢一次审批是隐蔽的语义损失(§4.4)。

:since 是给 HTTP 层用的:run 立刻起跑并发出 :run/started,而你要等本 函数返回才能 subscribe。拿返回的 :since 去订阅,那一段真空就补齐了—— 这正是会话级单调 :seq 的用处(§4.2 契约 1)。

:discard-pause? —— 显式丢弃会话上那个没人答复的暂停,直接起新 run。 给 AG-UI 前端工具用:客户端把工具塞在 RunAgentInput.tools 里,模型调了它, 会话就停在那儿等结果——而客户端可能永远不回(CopilotKit 的 suggestions 就是这样:它只读 TOOL_CALL_ARGS,从不回结果)。那种情况下用户下一句话到达 时,正确的做法是丢掉它继续,而不是把会话永远卡住。缺省仍是拒绝(§4.4): 人工审批被静默丢掉是隐蔽的语义损失,要丢就显式说。

:state —— RunAgentInput.state,作为本 run 共享状态的基线装上(不发事件, 客户端本来就有这份)。后续 delta 对着它算,服务端与客户端才不会各算各的。

:parent-run-id —— 调用方声明「这一轮挂在哪条 run 下面」。我们不解释它 (不建父子索引、不做级联停止),只把它挂到 :run/started 上原样发出去: AG-UI 的 RUN_STARTED.parentRunId 就是这么个用途,客户端拿它把这条 run 接回 父链。协议里它是可选的,没有就没有。

opts 直通 agent/chat-async(:system-prompt / :max-iterations / :tool-choice …); :tools 是本 run 追加的内联工具,会交给 :agent-fn。

起一个新 run。**立刻返回**,run 在后台跑。

返回 `{:status :started :run-id ... :since <起跑前的 seq 水位>}`,或:
- `{:status :busy :run-id <在跑的那个>}`(`:reject` 策略下已有 in-flight run)
- `{:status :awaiting-resume :pending-tool ... :reason ...}`——会话暂停中,
  先 `resume-run!`。**这里刻意不沿用 `simple-agent` 的 `cancel-pending!`
  语义**(新 turn 静默丢弃未 resume 的暂停):web 场景下「用户没点审批就又发
  了一句」应当显式暴露,静默丢一次审批是隐蔽的语义损失(§4.4)。

**`:since` 是给 HTTP 层用的**:run 立刻起跑并发出 `:run/started`,而你要等本
函数返回才能 `subscribe`。拿返回的 `:since` 去订阅,那一段真空就补齐了——
这正是会话级单调 `:seq` 的用处(§4.2 契约 1)。

`:discard-pause?` —— **显式丢弃**会话上那个没人答复的暂停,直接起新 run。
给 AG-UI 前端工具用:客户端把工具塞在 `RunAgentInput.tools` 里,模型调了它,
会话就停在那儿等结果——**而客户端可能永远不回**(CopilotKit 的 suggestions
就是这样:它只读 `TOOL_CALL_ARGS`,从不回结果)。那种情况下用户下一句话到达
时,正确的做法是丢掉它继续,而不是把会话永远卡住。**缺省仍是拒绝**(§4.4):
人工审批被静默丢掉是隐蔽的语义损失,要丢就显式说。

`:state` —— `RunAgentInput.state`,作为本 run 共享状态的**基线**装上(不发事件,
客户端本来就有这份)。后续 delta 对着它算,服务端与客户端才不会各算各的。

`:parent-run-id` —— 调用方声明「这一轮挂在哪条 run 下面」。我们**不解释**它
(不建父子索引、不做级联停止),只把它挂到 `:run/started` 上原样发出去:
AG-UI 的 `RUN_STARTED.parentRunId` 就是这么个用途,客户端拿它把这条 run 接回
父链。协议里它是可选的,没有就没有。

`opts` 直通 `agent/chat-async`(`:system-prompt` / `:max-iterations` / `:tool-choice` …);
`:tools` 是本 run 追加的内联工具,会交给 `:agent-fn`。
sourceraw docstring

stop!clj

(stop! rt conv-id)
(stop! rt conv-id run-id)
(stop! rt conv-id run-id reason)

请求停止。返回 true 只表示「取消已登记」,不表示「已经停了」——JVM 上没有 抢占原语,取消 = 放弃等待 + 协作式中断:provider 流式循环下次检查时退出、 react 循环在轮边界不再续跑,已经在跑的工具会跑完(§4.8,口径同 docs/tool-timeout-design.md)。停稳的信号是 :run/cancelled 事件。

传 run-id 则只停这一个 run,绝不误伤会话上更新的那个。

reason(缺省 :stop)落在 :run/cancelled 的 :reason 上,见 event/stop-reasons。第一个原因算数。

请求停止。**返回 true 只表示「取消已登记」,不表示「已经停了」**——JVM 上没有
抢占原语,取消 = 放弃等待 + 协作式中断:provider 流式循环下次检查时退出、
react 循环在轮边界不再续跑,**已经在跑的工具会跑完**(§4.8,口径同
docs/tool-timeout-design.md)。停稳的信号是 `:run/cancelled` 事件。

传 `run-id` 则只停这一个 run,绝不误伤会话上更新的那个。

`reason`(缺省 `:stop`)落在 `:run/cancelled` 的 `:reason` 上,见
`event/stop-reasons`。第一个原因算数。
sourceraw docstring

subscribeclj

(subscribe rt conv-id {:keys [since on-event on-close]})

订阅一个会话的事件流。返回退订函数。

  • :since 上次收到的 :seq(会话级单调,跨 run 连续)。缺省 nil = 只收新事件。
  • :on-event (fn [event])——必须立刻返回(往你的 sink 里 put 一下)。 抛异常即被摘除。
  • :on-close (fn [reason]),可选(退订 / shutdown! 时触发)。

:since 早于缓冲起点时先发一条 :run/resync(带 ChatMemory 里的完整消息 快照)再接 live tail——折叠后的真相 ChatMemory 里本来就有,不在事件侧再造 一份(§8.1:不建第二个真相店,也因此不需要 CopilotKit 的 compactEvents)。

订阅一个会话的事件流。返回**退订函数**。

- `:since`   上次收到的 `:seq`(会话级单调,跨 run 连续)。缺省 nil = 只收新事件。
- `:on-event` `(fn [event])`——**必须立刻返回**(往你的 sink 里 put 一下)。
  抛异常即被摘除。
- `:on-close` `(fn [reason])`,可选(退订 / `shutdown!` 时触发)。

`:since` 早于缓冲起点时先发一条 `:run/resync`(带 ChatMemory 里的完整消息
快照)再接 live tail——**折叠后的真相 ChatMemory 里本来就有**,不在事件侧再造
一份(§8.1:不建第二个真相店,也因此不需要 CopilotKit 的 `compactEvents`)。
sourceraw docstring

cljdoc builds & hosts documentation for Clojure/Script libraries

Keyboard shortcuts
Ctrl+kJump to recent docs
←Move to previous article
→Move to next article
Ctrl+/Jump to the search field
× close