KV 与会话
workflow runtime 为每次执行注入context.kv。KV 分三层:
context.persistentValue。
它的三层 scope 与 context.kv 相同:
KV 作用域
context.kv:global scope。context.kv.workflow:按 workflowName 隔离。context.kv.conversation:按 workflowName + conversationId 隔离。
context.persistentValue 也提供 global / workflow / conversation 三层 scope。
conversation scope 仍然要求 executor 开启 conversation 并拥有非空
conversation id。
KV value 必须可 JSON 序列化,不能是 undefined。本地 CLI/App 的普通
context.kv 会落到本地 KV store;server 运行会落到 server storage。
context.persistentValue 是数据库版 KV。server 运行直接使用 server
PostgreSQL store;CLI/App 本地运行会优先使用已配置的
WORKFLOW_SERVER_URL / WORKFLOW_SERVER_ADMIN_KEY,否则回退到 workspace CLI
保存的 workflow-auth.json,或使用 App 绑定的 server,把
PersistentValue 请求代理到 server 背后的数据库。只有没有连接服务器或
没有注入 persistent store 时,调用
context.persistentValue.*.getValue/setValue/updateValue 才会抛出明确错误。
无论在哪个平台执行,PersistentValue 都不会写入 CLI/App 本地 KV。
server PostgreSQL KV 会记录最后写入来源:last_workflow_name、last_run_id 和 last_conversation_id。这些字段用于 server/web 只读 KV 查看器把 key 关联回执行日志和 conversation。管理端 KV 工作台可按日志关联状态筛选,并展示关联覆盖率、payload 体积、scope 分布、value type 分布和热点 key。旧数据在字段上线前写入时不会自动回填来源,但仍能按 scope/key/value 展示。
server 会额外创建两个只读 view 方便接入开源工具:
workflow_project_kv_view:项目相关的workflow/conversationKV。workflow_global_kv_view:全局globalKV。
workflow_kv_entries。
项目知识库
issue #86 的第二阶段把context.persistentValue 应用到项目内知识库。知识库文档固定包含 markdown 字段,并复用 workflow scope:
| scope | key | 内容 |
|---|---|---|
| workflow | knowledge.documents | 文档索引、标题、摘要和更新时间。 |
| workflow | knowledge.document.<id> | 单篇 markdown 文档正文。 |
workflow.listKnowledgeDocuments、workflow.getKnowledgeDocument、workflow.createKnowledgeDocument、workflow.editKnowledgeDocument、workflow.deleteKnowledgeDocument、workflow.searchKnowledgeDocuments 和 workflow.readKnowledgeDocumentLines。这些方法只写 context.persistentValue.workflow;CLI/App 本地运行在已连接 server 时也可使用,未连接服务器持久存储时会按 PersistentValue 规则报错,不会把知识库落到本地 KV。删除文档会从索引移除并写入 tombstone 标记;当前 KV/PersistentValue 还没有 delete primitive。全文搜索支持 pageSize / cursor 分页和 beforeLines / afterLines 上下文行;指定文档的 n~m 行读取使用 readKnowledgeDocumentLines。
Conversation 文档镜像
issue #86 的第三阶段增加了workspace/workflow/conversation-knowledge 示例,用来把 conversation 应用作为知识库功能测试。这个 workflow 开启 executor.conversation.enabled,每次运行都会把用户消息和一个确定性的 assistant 确认写入 context.conversation,再把当前 conversation 的完整 transcript 转成 markdown 文档:
conversationId 派生,因此同一个 conversation 后续运行会更新同一篇知识库文档。示例提供 --dry-run,用于在没有 server PersistentValue 连接的本地测试中只预览将要写入的 markdown;真实写入可以通过已配置 server 的本地 CLI/App 运行,也可以通过 server API 运行,例如请求体传入 conversation_id 和业务 args。
Token 用量统计
Token 用量统计默认关闭。executor 显式配置后,runtime 会把本次运行的 token 用量写入 KV,并把当前 run 聚合写入运行 report 和 runtime event:enabled 控制统计与 KV 写入;display 控制 App/server 前端是否展示 token badge。display 省略时默认跟随 enabled。
通过 workflow.getLLMProvider() 解析出的 provider 会自动读取 provider 返回的 usage;内置 LLM 节点和自定义节点直接调用 getLLMProvider() 都走同一条统计链路:
provider.chat()在非流式输出中读取output.usage。provider.stream()在 stream finish chunk 中读取usage,同一次 stream 只上报一次。
tokenUsage.enabled 时,context.tokenUsage.report() 是安全 no-op。
cachedInputTokens 表示缓存命中的输入 token;cacheCreationTokens 表示本次调用写入/创建缓存的 token。App 和 server embed 的 badge 仍显示总量,例如 200 tokens;hover 或键盘 focus 时展示 Input、Output、Cache 三项明细,其中 Cache 为 cachedInputTokens + cacheCreationTokens。
统计数据复用 KV 存储,不新增表:
| scope | key | 内容 |
|---|---|---|
| global | tokenUsage.total | 当前系统总 token 用量。 |
| workflow | tokenUsage.total | 当前 workflow/project 的总 token 用量;每个 workflow 只有一条总量记录。 |
| conversation | tokenUsage.total | 当前 conversation 的聚合 token 用量。 |
| conversation | tokenUsage.entry.<runId>.<sequence> | 每次上报的明细;一个 conversation 可以有多条。 |
updateValue(key, updater),用于读改写聚合值。PostgreSQL KV 使用事务行锁减少并发丢增量;本地 KV store 使用读改写 fallback。
开启会话
会话能力默认关闭。必须在 executor 上显式开启:setTitle(title) 会把标题规范化后写入会话状态,并在 App/server embed 中触发标题更新事件。标题为空字符串时会清除标题;如果会话模式未开启,setTitle 是安全 no-op,不会因为标题只是 UI hint 而中断 workflow 执行。其它会话读写方法在未开启时仍会抛出明确错误。
如果 workflow 还声明了 conversation.defaultInput,App、server embed 和公开
embed 会优先渲染共享默认输入框,而不是把主消息继续当成普通 params
文本框。这个默认输入框固定序列化为三组 CLI 参数:
- 文本:
--message - 图片附件:
--images - 普通文件附件:
--files
createInput({ args }) 中显式读取这些参数;推荐直接使用
workflow.parseConversationDefaultInputArgs(args)。默认输入框协议不会把主输入
自动塞进 WorkflowContext,它只约定宿主 UI 如何把消息和附件转换为稳定 args。
如果 workflow 同时声明了 conversation.inputQueue: { enabled: true },App 和
server embed 会在当前会话运行时继续接受新的输入,并把它们放入宿主持久化队列。
恢复后的队列默认处于等待状态:用户需要先手动发送一次新输入,之后等待中的队列项
才会自动按顺序继续执行。
App 和 server embed 还提供“锁定标题”开关。该锁定状态只存在于 Web/App UI 本地会话层,不属于 core API,workflow 代码不能设置锁定状态;锁定后 UI 会忽略 core setTitle 或最终 report 中的标题更新,手动解锁后才允许再次改名。未调用 setTitle 时,UI 会继续使用 conversation id 派生出的默认标题。
conversation_id
外部 API、server run、embed run 和 App 本地会话都可以传入或生成 conversation id。- App 会话面板为每个本地 chat session 生成并复用一个 conversation id。
- 外部 API 使用请求体
conversation_id。 - Embed 会把请求的 conversation id 签名为
embed_<signature>_<nonce>,防止跨 token/session 复用。 - 如果 webhook 或第三方 payload 需要先解析后才能确定稳定会话,
createInput可以返回非空的conversationId或conversation_id。没有显式传入conversation_id时,runtime 会使用这个值作为执行级 conversation id;显式传入的 id 仍然优先。 - Server 日志回放会优先使用最终输出里的
displayInput/display_input作为 conversation 用户气泡展示内容,便于把 webhook payload 等原始输入转换成可读摘要;原始参数和节点详情仍保留用于排查。(issue #47)
会话状态结构
runtime 内部保存:user、assistant、system、tool。
父子 workflow 会话复用
一个 workflow 可以通过workflow.runWorkflow(childWorkflow, input, context, options) 复用另一个 workflow。子 workflow 默认复用父级 kv、persistentValue、conversation、tokenUsage、files、providers、abortSignal 和 nodeHooks,不会开启独立 run report。顶层 executor 入口不会额外插入 run-workflow 包裹节点,只有 workflow 内部显式调用另一个 workflow 时才会出现在父 trace 中。
默认 runWorkflow(child, input, context) 和 runWorkflow(child, input, context, { name }) 都使用 conversationMode: "shared" 和 outputVisibility: "visible"。给 trace 设置显示名不会静默改变会话写入或输出可见性。推荐桥接式父 workflow 调用子 workflow 时显式使用 conversationMode: "shared-readonly"。这种模式允许子 workflow 读取父会话快照,但 appendMessage、setValue 和 setTitle 不会写回父会话。父 workflow 应统一追加本轮 user/assistant transcript、设置标题,并决定最终可见 output,避免父子 workflow 对同一个 conversation 重复写消息。
conversationMode 只影响 context.conversation,不自动隔离 KV。默认 kvMode: "shared" 会让子 workflow 继续读写父级 workflow/conversation KV;如果被复用 workflow 会写入自己的 history、facts、cache 或其它 workflow-scoped 状态,推荐传入 kvMode: "isolated"。隔离模式会给 context.kv.workflow 和 context.kv.conversation 的 key 增加以子 workflow 名称为基础的前缀,避免污染父 workflow 状态;context.kv global scope 仍然共享。context.persistentValue 是 server 持久通道,子 workflow 默认共享父级 persistentValue,不受 kvMode 隔离。
需要父级独占输出时,显式传入 outputVisibility: "hidden"。此时子 workflow 的 output 和 output stream chunks 不会进入父级可见输出,但子 workflow 的非输出节点事件仍进入父 trace。父 workflow 可以读取子 workflow 的返回值,再用自己的 stream output node 输出最终结果,避免父子 workflow 重复展示同一段内容。
如果父 workflow 的目标是复用并展示子 workflow 本身的输出,推荐在父级 output node 的 stream 中调用 runWorkflow,并传入 onOutputChunk / onOutput。子 workflow 支持流式 output 时,父级把 onOutputChunk 收到的 chunk 原样 yield;子 workflow 只有非流式 output 或直接返回 payload 时,父级把 onOutput 收到的结果转成当前 output item。这样父级仍能统一管理 conversation,但 UI 展示顺序由被调用 workflow 的真实输出顺序决定。
如果子 workflow 会展示工具调用过程,工具调用也应由子 workflow 自己作为流式 output item 产出,父级只负责转发。父级不要把工具事件重新组装到最终回答前后,否则容易出现空白等待或展示顺序反转。
runWorkflow 默认最多允许 16 层嵌套调用,防止 A 调 B、B 又调 A 这类环路无限递归。输出桥接 handler 抛错时,runtime 会让本次 runWorkflow 尽早失败,并在错误 metadata 中标记 outputBridgeFailed: true。
外部 Agent SDK 会话
外部 agent SDK 不会自动理解 workflow-code 的 conversation/KV。示例workspace/workflow/openai-agents 和 workspace/workflow/claude-agent
展示了推荐映射方式:
- 在 executor 上开启
conversation: { enabled: true }。 - 每轮运行前读取
context.conversation.getValue(),把最近消息、当前 conversation id 和 run count 作为 SDKrun(..., { context })的纯 JSON 上下文。 - SDK 工具如果需要持久化状态,写入
context.kv.conversation,例如remember_fact把{ key, value }写成 conversation-scoped facts。 - 自定义 function tool 可以直接编排 workflow 代码,例如示例中的
test_tool会发起小型 HTTP GET/POST 请求并把响应摘要返回给 Agent;普通测试用本地 HTTP server 覆盖该网络请求路径。 - 如果外部 SDK 需要访问本地上下文,示例可通过
--local-shell启用 Agents SDK 的shellTool本地模式:environment: { type: "local" }。 实际shell.run(...)由 workflow 实现,并限制在--workdir内执行短命令。 - SDK 返回后再调用
context.conversation.appendMessage(...)记录用户和 assistant 消息,并把轻量 history 摘要写入 conversation KV。 - 如果 SDK 自己有 session 概念,可以像
claude-agent一样把 session id 写进context.conversationstate;同一个 workflow conversation 后续运行再把该 id 作为 SDK resume 参数传回去。 - 外部 SDK 的 token usage 需要 workflow 手动调用
context.tokenUsage.report(...),这样 App/server badge 和 KV 统计才能 与内置 provider 保持一致。
追踪
本文档首版由 issue #32 记录。server/web KV 查看器与来源追踪由 issue #56 记录。PersistentValue 服务器持久值由 issue #86 记录。KV 与会话行为对齐core/kv/index.ts、core/conversation/index.ts、server/src/routes/embed.ts 和 App 会话运行逻辑。