☰
OpenProse SQLite 状态管理详解:用单个 state.db 文件支撑多 Agent 编排的持久化、查询与断点恢复
2026/10/17 19:40:07 网站建设 项目流程
  • 人工智能
  • 大模型
  • LLM 网关
  • AI Agent
  • 即时通讯

【免费下载链接】openclaw-zero-token

OpenClaw: Use All Major AI Models NO API Token! Claude/ChatGPT/Gemini/DeepSeek/Doubao/Grok/Qwen/Manus/Kimi

项目地址:https://gitcode.com/gh_mirrors/op/openclaw-zero-token
点击查看免费下载

本文基于 OpenClaw 仓库中的open-prose/skills/prose/state/sqlite.md展开,讲解 OpenProse VM 的实验性 SQLite 状态管理模式:如何以单文件state.db持久化多 Agent 工作流的执行位置、绑定值与 Agent 记忆,如何利用sqlite3CLI 完成原子事务写入、结构化查询和断点恢复,以及它与默认的 filesystem、in-context 状态的取舍边界。

一、什么是 OpenProse 的 SQLite 状态模式

OpenProse 是 OpenClaw 提供的"面向 AI 会话的编程语言":.prose程序由一个由 LLM 本身"具身化"(embody)的虚拟机组装执行,每条session语句通过 Task 工具派生真实的子 Agent 会话。详见 prose.md。VM 在执行过程中必须持续跟踪程序计数器(当前语句位置)、变量绑定(bindings)、并行分支状态、循环迭代次数和持久 Agent 记忆——这些统称为"执行状态"(execution state)。

OpenProse 支持四种状态后端,SQLite 是其中标记为experimental(实验性)的一种:

模式适用场景状态位置
filesystem(默认)复杂程序、需要恢复与调试.prose/runs/{id}/下的文件
in-context简单程序(<30 条语句),无需持久化对话历史(narration 协议)
sqlite(实验性)可查询状态、原子事务、灵活 schema.prose/runs/{id}/state.db
postgres(实验性)真并发写、外部集成、团队协作PostgreSQL 数据库

上表与 SKILL.md 中 "State Modes" 一节的定义一致。默认加载 prose.md 时会同时加载 filesystem.md;只有当用户显式传入--state=sqlite或说出 "use sqlite state" 时,才加载本文档对应的 sqlite.md。

核心设计原则(原文 Key principle):数据库是一个"灵活的共享工作区"(flexible workspace)——VM 与子 Agent 把它当作协调机制来共享使用,而不是把它当作一份刚性契约。

二、前置条件:sqlite3 CLI 与回退策略

SQLite 模式要求sqlite3命令行工具在 PATH 中可用:

平台安装方式
macOS预装(Pre-installed)
Linuxapt install sqlite3/dnf install sqlite3等包管理器
Windowswinget install SQLite.SQLite或从 sqlite.org 下载

回退行为:如果sqlite3不可用,VM 会向用户发出警告,并回退到 filesystem 状态模式,而不是直接报错终止。这与 postgres.md 中psql缺失时"提议回退到 SQLite"的降级链相呼应:postgres → sqlite → filesystem构成了完整的可用性保障。

启用方式

从 SKILL.md 的命令路由表看,启用 SQLite 模式的触发方式是运行时声明:

prose run <file.prose> --state=sqlite # 或直接用自然语言:"use sqlite state"

此外,标准库(lib)中的记忆类程序推荐另一种写法--backend sqlite+以获得跨 run 的持久化(如 lib/README.md 中的示例):

prose run lib/user-memory.prose --backend sqlite+ prose run lib/project-memory.prose --backend sqlite+

三、数据库位置与 Agent 作用域

3.1 每次 run 一个单文件数据库

数据库位于标准的 run 目录内,一次运行 = 一个.db文件:

.prose/runs/{YYYYMMDD}-{HHMMSS}-{random}/ ├── state.db # SQLite 数据库(本模式的核心文件) ├── program.prose # 运行中程序的副本 └── attachments/ # 存不进数据库的大体积输出(可选)

Run ID 格式与 filesystem 模式完全一致:{YYYYMMDD}-{HHMMSS}-{random6},例如.prose/runs/20260116-143052-a7b3c9/state.db。单文件可移植性是 SQLite 相对 filesystem 的关键优势——整个 run 状态可以作为一个文件被复制、归档、传输。

3.2 项目级与用户级 Agent 需要跨 run 存活

execution 作用域的 Agent(默认)记忆存放在每次 run 私有的state.db中,随 run 消亡。但persist: project与persist: user声明的持久 Agent 必须跨越 run 存活,因此使用独立数据库:

# 项目级 .prose/ ├── agents.db # 项目作用域 Agent 记忆(跨 run 存活) └── runs/ └── {id}/ └── state.db # 执行作用域状态(随 run 消亡) # 用户级 ~/.prose/ └── agents.db # 用户作用域 Agent 记忆(跨项目存活)

agents与agent_segments表按作用域落位:项目级在.prose/agents.db,用户级在~/.prose/agents.db。VM 在首次使用时初始化这些数据库,并在派生子 Agent 时把正确的数据库路径告知对方。这一设计与 filesystem.md 中"Memory Scoping for Persistent Agents" 表格的作用域划分(execution / project / user / custom)一一对应,只是把目录结构换成了数据库文件。

四、职责分离:VM 与子 Agent 的契约

SQLite 模式沿用 postgres.md "Responsibility Separation" 一节定义的 VM/子 Agent 契约,仅存在 SQLite 特有的三处差异:

  1. VM 创建的是state.db文件,而不是 PostgreSQL 的openproseschema;
  2. 子 Agent 的确认消息指向本地数据库路径,例如.prose/runs/<runId>/state.db;
  3. 清理(cleanup)通常是VACUUM或直接删除文件,而非 drop schema 对象。

契约的核心不变量是:VM 只跟踪"位置"(location),不持有完整值;子 Agent 直接把输出写入数据库,完成后只返回一条确认消息。文档中给出的 SQLite 风格确认消息示例:

Binding written: research Location: .prose/runs/20260116-143052-a7b3c9/state.db (bindings table, name='research', execution_id=NULL)
Binding written: result Location: .prose/runs/20260116-143052-a7b3c9/state.db (bindings table, name='result', execution_id=43) Execution ID: 43

与 filesystem 模式"返回文件路径"相比,这里返回的是"表 + 主键条件"形式的定位信息,语义完全等价——VM 的上下文保持精简,从而支持任意大体积的中间值。

五、核心 Schema:最小可用表的完整定义

VM 初始化时创建以下表。原文强调这是最小可用 schema(minimum viable schema),鼓励按需扩展。完整 DDL 如下:

-- Run metadata CREATE TABLE IF NOT EXISTS run ( id TEXT PRIMARY KEY, program_path TEXT, program_source TEXT, started_at TEXT DEFAULT (datetime('now')), updated_at TEXT DEFAULT (datetime('now')), status TEXT DEFAULT 'running', -- running, completed, failed, interrupted state_mode TEXT DEFAULT 'sqlite' ); -- Execution position and history CREATE TABLE IF NOT EXISTS execution ( id INTEGER PRIMARY KEY AUTOINCREMENT, statement_index INTEGER, statement_text TEXT, status TEXT, -- pending, executing, completed, failed, skipped started_at TEXT, completed_at TEXT, error_message TEXT, parent_id INTEGER REFERENCES execution(id), -- for nested blocks metadata TEXT -- JSON for construct-specific data (loop iteration, parallel branch, etc.) ); -- All named values (input, output, let, const) CREATE TABLE IF NOT EXISTS bindings ( name TEXT, execution_id INTEGER, -- NULL for root scope, non-null for block invocations kind TEXT, -- input, output, let, const value TEXT, source_statement TEXT, created_at TEXT DEFAULT (datetime('now')), updated_at TEXT DEFAULT (datetime('now')), attachment_path TEXT, -- if value is too large, store path to file PRIMARY KEY (name, IFNULL(execution_id, -1)) -- IFNULL handles NULL for root scope ); -- Persistent agent memory CREATE TABLE IF NOT EXISTS agents ( name TEXT PRIMARY KEY, scope TEXT, -- execution, project, user, custom memory TEXT, created_at TEXT DEFAULT (datetime('now')), updated_at TEXT DEFAULT (datetime('now')) ); -- Agent invocation history CREATE TABLE IF NOT EXISTS agent_segments ( id INTEGER PRIMARY KEY AUTOINCREMENT, agent_name TEXT REFERENCES agents(name), segment_number INTEGER, timestamp TEXT DEFAULT (datetime('now')), prompt TEXT, summary TEXT, UNIQUE(agent_name, segment_number) ); -- Import registry CREATE TABLE IF NOT EXISTS imports ( alias TEXT PRIMARY KEY, source_url TEXT, fetched_at TEXT, inputs_schema TEXT, -- JSON outputs_schema TEXT -- JSON );

5.1 Schema 设计要点逐表解读

  • run:一次运行的元数据。status取值running / completed / failed / interrupted;state_mode固定记录为'sqlite',用于多模式混用时的溯源。
  • execution:执行位置与历史的"程序计数器表"。parent_id自引用外键构成块调用的调用树(call tree);metadata列以 JSON 文本承载构造特定的数据——循环迭代号、并行分支标识、重试次数都存这里,配合 SQLite 的json_extract/json_set函数使用。
  • bindings:所有命名值(input / output / let / const)。主键(name, IFNULL(execution_id, -1))是一个值得注意的技巧:SQLite 不允许主键含 NULL,因此用IFNULL把根作用域的 NULL 映射为-1,从而让"同名绑定在不同执行帧中互不冲突"。
  • agents+agent_segments:持久 Agent 的"当前记忆快照 + 历史分段"两表结构,对应 filesystem 模式中memory.md+{name}-NNN.md的文件对。
  • imports:导入注册表,记录use语句拉取的程序别名、来源 URL 与输入/输出 schema(JSON 文本)。

5.2 Schema 约定

原文列出的七条书写约定:

约定规则
时间戳ISO 8601 格式,默认datetime('now')
JSON 字段结构化数据以 JSON 文本存于metadata、*_schema列
大值绑定值超过约 100KB 时写入attachments/{name}.md,库中只存路径
扩展表一律加x_前缀(如x_metrics、x_audit_log)
匿名绑定未显式捕获的session "..."自动生成anon_001、anon_002等名称
导入绑定以导入别名为前缀限定作用域:research.findings、research.sources
作用域绑定用execution_id列——根作用域为 NULL,块内调用为非空

5.3 作用域解析查询(Scope Resolution)

对于递归块(recursive block),绑定值被限定在各自的执行帧内。变量解析需要沿调用栈向上回溯。原文给出的完整递归 CTE 版本:

-- Find binding 'result' starting from execution_id 43 WITH RECURSIVE scope_chain AS ( -- Start with current execution SELECT id, parent_id FROM execution WHERE id = 43 UNION ALL -- Walk up to parent SELECT e.id, e.parent_id FROM execution e JOIN scope_chain s ON e.id = s.parent_id ) SELECT b.* FROM bindings b LEFT JOIN scope_chain s ON b.execution_id = s.id WHERE b.name = 'result' AND (b.execution_id IN (SELECT id FROM scope_chain) OR b.execution_id IS NULL) ORDER BY CASE WHEN b.execution_id IS NULL THEN 1 ELSE 0 END, -- Prefer scoped over root s.id DESC NULLS LAST -- Prefer deeper (more local) scope LIMIT 1;

如果已知作用域链,可以用更简单的直查版本:

-- Direct lookup: check current scope, then parent, then root SELECT * FROM bindings WHERE name = 'result' AND (execution_id = 43 OR execution_id = 42 OR execution_id IS NULL) ORDER BY execution_id DESC NULLS LAST LIMIT 1;

这一逻辑与 filesystem.md 中"先查{name}__{current_execution_id}.md,再逐级向上,最后落到根作用域{name}.md,首个命中者胜出"的文件版作用域解析完全同构——只是把文件查找换成了 SQL 排序。

六、数据库交互:VM 侧与子 Agent 侧

VM 与子 Agent 都通过sqlite3CLI 交互,写权按表划分(VM 独占execution,子 Agent 写bindings/agents/agent_segments)。

6.1 VM 侧的典型操作

# Initialize database sqlite3 .prose/runs/20260116-143052-a7b3c9/state.db "CREATE TABLE IF NOT EXISTS..." # Update execution position sqlite3 .prose/runs/20260116-143052-a7b3c9/state.db " INSERT INTO execution (statement_index, statement_text, status, started_at) VALUES (3, 'session \"Research AI safety\"', 'executing', datetime('now')) " # Read a binding sqlite3 -json .prose/runs/20260116-143052-a7b3c9/state.db " SELECT value FROM bindings WHERE name = 'research' " # Check parallel branch status sqlite3 .prose/runs/20260116-143052-a7b3c9/state.db " SELECT statement_text, status FROM execution WHERE json_extract(metadata, '$.parallel_id') = 'p1' "

注意sqlite3 -json输出选项——VM 读取绑定值时以 JSON 格式取回,便于直接嵌入后续派生提示(prompt)。

6.2 子 Agent 侧的输出写入

VM 在派生 session 时把数据库路径和写入指令一并下发。根作用域(块调用之外)的写入指令:

Your output database is: .prose/runs/20260116-143052-a7b3c9/state.db When complete, write your output: sqlite3 .prose/runs/20260116-143052-a7b3c9/state.db " INSERT OR REPLACE INTO bindings (name, execution_id, kind, value, source_statement, updated_at) VALUES ( 'research', NULL, -- root scope 'let', 'AI safety research covers alignment, robustness...', 'let research = session: researcher', datetime('now') ) "

块调用内部(必须携带execution_id):

Execution scope: execution_id: 43 block: process depth: 3 Your output database is: .prose/runs/20260116-143052-a7b3c9/state.db When complete, write your output: sqlite3 .prose/runs/20260116-143052-a7b3c9/state.db " INSERT OR REPLACE INTO bindings (name, execution_id, kind, value, source_statement, updated_at) VALUES ( 'result', 43, -- scoped to this execution 'let', 'Processed chunk into 3 sub-parts...', 'let result = session \"Process chunk\"', datetime('now') ) "

execution 作用域的持久 Agent(如resume: captain):

Your memory is in the database: .prose/runs/20260116-143052-a7b3c9/state.db Read your current state: sqlite3 -json .prose/runs/20260116-143052-a7b3c9/state.db "SELECT memory FROM agents WHERE name = 'captain'" Update when done: sqlite3 .prose/runs/20260116-143052-a7b3c9/state.db "UPDATE agents SET memory = '...', updated_at = datetime('now') WHERE name = 'captain'" Record this segment: sqlite3 .prose/runs/20260116-143052-a7b3c9/state.db "INSERT INTO agent_segments (agent_name, segment_number, prompt, summary) VALUES ('captain', 3, '...', '...')"

项目级 Agent 改写.prose/agents.db,用户级 Agent 改写~/.prose/agents.db——表结构相同,只是数据库文件不同。

七、主线程的上下文保留:数据库不能替代叙述

原文用 "This is critical" 强调:数据库负责持久化与协调,但 VM 仍必须在主对话中叙述(narrate)关键事件。SQLite 模式下的叙述示例:

[Position] Statement 3: let research = session: researcher Spawning session, will write to state.db [Task tool call] [Success] Session complete, binding written to DB [Binding] research = <stored in state.db>

叙述标记([Position]、[Success]、[Binding]等)与 in-context.md 中的 narration 协议共用一套词汇,只是[Binding]的内容从"值"变成了"库内位置"。两者的分工:

目的机制
工作记忆(Working memory)对话叙述——VM 不重新查询即可"记住"的内容
持久状态(Durable state)SQLite 数据库——跨越上下文限制,支持恢复
子 Agent 协调SQLite 数据库——共享访问点
调试 / 检查SQLite 数据库——可查询的历史

一句话概括:叙述是 VM 对执行的"心理模型",数据库才是恢复与检查的"事实来源"(source of truth)。

八、并行执行的分支追踪

对parallel块,VM 用execution.metadata的 JSON 字段追踪分支,且只有 VM 写execution表(子 Agent 只写bindings)。完整流程 SQL:

-- VM marks parallel start INSERT INTO execution (statement_index, statement_text, status, metadata) VALUES (5, 'parallel:', 'executing', '{"parallel_id": "p1", "strategy": "all", "branches": ["a", "b", "c"]}'); -- VM creates execution record for each branch INSERT INTO execution (statement_index, statement_text, status, parent_id, metadata) VALUES (6, 'a = session "Task A"', 'executing', 5, '{"parallel_id": "p1", "branch": "a"}'); -- Subagent writes its output to bindings table (see "From Subagents" section) -- Task tool signals completion to VM via substrate -- VM marks branch complete after Task returns UPDATE execution SET status = 'completed', completed_at = datetime('now') WHERE json_extract(metadata, '$.parallel_id') = 'p1' AND json_extract(metadata, '$.branch') = 'a'; -- VM checks if all branches complete SELECT COUNT(*) as pending FROM execution WHERE json_extract(metadata, '$.parallel_id') = 'p1' AND status != 'completed';

要点:分支记录通过parent_id挂到parallel:语句的记录之下;完成判定不是靠 Task 工具的返回值直接写库,而是 Task 返回后由 VM 用json_extract按parallel_id + branch定位并更新;pending计数归零即 join 完成。

九、循环、错误处理与大输出

9.1 循环追踪

循环的迭代状态同样存于metadataJSON,用json_set原地更新:

-- Loop metadata tracks iteration state INSERT INTO execution (statement_index, statement_text, status, metadata) VALUES (10, 'loop until **analysis complete** (max: 5):', 'executing', '{"loop_id": "l1", "max_iterations": 5, "current_iteration": 0, "condition": "**analysis complete**"}'); -- Update iteration UPDATE execution SET metadata = json_set(metadata, '$.current_iteration', 2), updated_at = datetime('now') WHERE json_extract(metadata, '$.loop_id') = 'l1';

9.2 错误处理

-- Record failure UPDATE execution SET status = 'failed', error_message = 'Connection timeout after 30s', completed_at = datetime('now') WHERE id = 15; -- Track retry attempts in metadata UPDATE execution SET metadata = json_set(metadata, '$.retry_attempt', 2, '$.max_retries', 3) WHERE id = 15;

失败状态落在status/error_message,重试计数落在metadata——与 filesystem.md 中# <-- RETRYING (attempt 2/3)这类注释标注是同一语义的两种载体。

9.3 大输出(>100KB)走附件

当绑定值过大不便存库时,遵循三步:内容写入attachments/{binding_name}.md→ 路径存入attachment_path列 →value保留摘要或留空:

INSERT INTO bindings (name, kind, value, attachment_path, source_statement) VALUES ( 'full_report', 'let', 'Full analysis report (847KB) - see attachment', 'attachments/full_report.md', 'let full_report = session "Generate comprehensive report"' );

十、断点恢复:用查询代替重读

恢复被中断的 run 时,VM 依次执行以下查询(定位当前位置 → 取回全部已完成的绑定 → 取回 Agent 记忆 → 检查并行块状态):

-- Find current position SELECT statement_index, statement_text, status FROM execution WHERE status = 'executing' ORDER BY id DESC LIMIT 1; -- Get all completed bindings SELECT name, kind, value, attachment_path FROM bindings; -- Get agent memory states SELECT name, memory FROM agents; -- Check parallel block status SELECT json_extract(metadata, '$.branch') as branch, status FROM execution WHERE json_extract(metadata, '$.parallel_id') IS NOT NULL AND parent_id = (SELECT id FROM execution WHERE status = 'executing' AND statement_text LIKE 'parallel:%');

对比三种模式的恢复路径:filesystem 模式靠"读state.md+ 加载bindings/目录",in-context 模式靠"重读对话",SQLite 模式则靠"查询数据库"——这也是原文对比表中 "Resumption" 一行的内容。

十一、Schema 弹性:数据库就是你的工作区

与 filesystem 模式"文件结构即状态"的严格约定不同,SQLite 模式刻意不那么具规定性(less prescriptive):核心 schema 只是起点,鼓励:

  • 给现有表加列(如ALTER TABLE bindings ADD COLUMN token_count INTEGER);
  • 建x_前缀的扩展表;
  • 存自定义指标(耗时、token 数、模型信息);
  • 针对自己的查询模式建索引;
  • 用 JSON 函数处理半结构化数据。

文档给出的扩展示例:

-- Custom metrics table CREATE TABLE x_metrics ( execution_id INTEGER REFERENCES execution(id), metric_name TEXT, metric_value REAL, recorded_at TEXT DEFAULT (datetime('now')) ); -- Add custom column ALTER TABLE bindings ADD COLUMN token_count INTEGER; -- Create index for common query CREATE INDEX idx_execution_status ON execution(status);

十二、与其他状态模式的对比

原文对比表的完整内容(本文在 postgres.md 中核实到其同构表格,四模式版本多一列 PostgreSQL):

维度filesystemin-contextsqlite
状态位置.prose/runs/{id}/文件对话历史.prose/runs/{id}/state.db
可查询性通过读文件否是(SQL)
原子更新否不适用是(事务)
Schema 灵活性刚性的文件结构不适用灵活(可加表/列)
恢复方式读state.md重读对话查询数据库
复杂度上限高低(<30 条语句)高
依赖无无sqlite3 CLI
状态StableStableExperimental

选择建议(综合 SKILL.md 与 in-context.md 的量化阈值):语句数 <30、并行分支 <5、无深度嵌套的小程序用 in-context;需要恢复/调试/大输出的复杂程序用默认 filesystem 或想要 SQL 查询能力时用 sqlite;当出现 >5 个并发写分支、外部看板查询或团队共享需求时,再考虑 postgres.md 描述的 PostgreSQL 模式。

十三、总结

SQLite 状态管理的设计契约可以浓缩为七点:

  1. 每次 run 使用单个数据库文件(state.db),跨 run 的 Agent 记忆另存agents.db/~/.prose/agents.db;
  2. VM 与子 Agent 之间有清晰的职责分离:VM 独占execution表并只跟踪位置,子 Agent 直写自己的bindings;
  3. 支持结构化查询检查状态(含递归 CTE 的作用域解析);
  4. 借助事务提供原子更新;
  5. 允许灵活的 schema 演进(x_扩展表、加列、加索引);
  6. 依赖sqlite3 CLI,缺失时回退 filesystem 模式并告警;
  7. 状态为experimental——预期会有变化。

核心契约一句话:VM 负责管理执行流并派生子 Agent,子 Agent 把自己的输出直接写进数据库;"发生的事都被记录,被记录的事都可以被查询"(what happens is recorded, and what is recorded can be queried)。

如需在 OpenClaw 中启用 OpenProse 能力,可参考 open-prose/README.md:在插件配置中启用open-prose并重启 Gateway 即可使用/prose命令与prose run ... --state=sqlite流程。

  • 人工智能
  • 大模型
  • LLM 网关
  • AI Agent
  • 即时通讯

【免费下载链接】openclaw-zero-token

OpenClaw: Use All Major AI Models NO API Token! Claude/ChatGPT/Gemini/DeepSeek/Doubao/Grok/Qwen/Manus/Kimi

项目地址:https://gitcode.com/gh_mirrors/op/openclaw-zero-token
点击查看免费下载

相关推荐

上一篇:为什么选择OpenSora-STDiT-v2-stage3?NPU平台视频生成的7大核心优势
下一篇:终极指南:8种ggml模型量化方法的详细对比

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询