37 KiB
AI原生DevOps Platform 架构设计 v2.6
版本: v2.6(Shared Workspace + Blackboard 架构) 基于: architecture-v2.md + v2.0 AI Native 调研 + 技术验证 作者: 庞统(副军师) 日期: 2026-05-15
变更历史
| 版本 | 日期 | 变更内容 |
|---|---|---|
| v2.0 | 2026-05-04 | 初始版本:SQLite 4表 + 状态机 + DAG 引擎 |
| v2.6 | 2026-05-15 | 架构重构:Shared Workspace(Blackboard)取代 DAG 引擎为编排核心 |
| v2.6.1 | 2026-05-15 | 司马懿评审反馈 + Mail 退役决策 + 质量门控 + 决策记录 + 工程修正 |
1. v2.6 核心变革:从 DAG 状态机到 Shared Workspace
1.1 为什么变?
v2.0 的核心是 DAG 引擎 + 状态机 + 邮件通信,本质是给 AI 团队做了一套 ERP:
- 编排是确定性状态机(固定流程)
- 交互是点按钮(Dashboard)
- Agent 间靠邮件异步通信(信息分散在 mail 目录)
- 人的参与密度不变(全程驾驶)
v2.6 的核心是 Shared Workspace(Blackboard)+ Agent 自主决策 + Daemon 投递:
- 编排是 AI agent 在黑板上自主领活(动态协作)
- 交互是自然语言对话
- Agent 间通过黑板共享一切(信息集中在任务空间)
- 人只做方向决策和验收
1.2 核心原则
黑板是唯一真相源,所有 agent 读它、想、行动,写回结果。Daemon 是投递员,不是决策者。
- Agent 决策,Daemon 执行 — 庞统做 plan、张飞领任务、关羽发现风险,都写在黑板上。Daemon 读黑板,执行 spawn/通知。
- 产出在黑板,不在邮件 — 所有任务产出、讨论、观察都在任务的黑板空间里,Sanguo Mail 不介入任务协作。
- Daemon 不阻塞 Agent — Daemon 是常驻管家,定期 tick 检查黑板,spawn agent 执行,不占用任何 agent 的主 session。
- Session 用完即清 — Agent 通过
openclaw agent --agent <id> --session-id <uuid>spawn 隔离 session,执行完 daemon 存档 jsonl 并清理 sessions.json。
2. 架构总览
┌─────────────────────────────────────────────────────────────┐
│ 用户 / 触发器 │
│ (Web / CLI / Cron) │
└──────────────────────────┬──────────────────────────────────┘
│ 写入黑板或触发 daemon
┌──────────────────────────▼──────────────────────────────────┐
│ Shared Workspace(黑板) │
│ │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ SQLite (blackboard.db) │ │
│ │ tasks / comments / outputs / agents / events │ │
│ │ 原子读写(propose→validate→commit 或 SQLite 事务) │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ 任务列表 │ │ 评论线程 │ │ 产出空间 │ │ 讨论区域 │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
└──────────────────────────┬──────────────────────────────────┘
│ daemon tick 读写
┌──────────────────────────▼──────────────────────────────────┐
│ Daemon(管家) │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────┐ │
│ │ Tick 循环 │ │ Session 管理 │ │ 健康检查 │ │
│ │ (60s 轮询) │ │ spawn/archive │ │ zombie/reclaim │ │
│ │ 读黑板→决策 │ │ /cleanup │ │ /stale 任务 │ │
│ └──────┬───────┘ └──────┬───────┘ └──────────────────┘ │
│ │ │ │
│ Daemon 只做三件事: │ │
│ 1. 读黑板,发现需要介入的 │ │
│ 2. Spawn 对应 agent │ │
│ 3. 清理完成的 session │ │
└──────────────────────────┬──────────────────────────────────┘
│ openclaw agent --agent <id> --session-id <uuid>
│ 执行完 → 存档 jsonl → 清理 sessions.json
┌──────────────────────────▼──────────────────────────────────┐
│ Agent 层(将军们) │
│ │
│ Agent 不常驻。被 spawn 时: │
│ 1. 读黑板 → 了解全局状态 │
│ 2. 想和做 → 根据职责自主决策 │
│ 3. 写回黑板 → 产出、评论、领任务 │
│ 4. 退出 → session 被 daemon 清理 │
│ │
│ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ │
│ │庞统 │ │司马懿│ │姜维 │ │关羽 │ │张飞 │ │赵云 │ │
│ │策划 │ │质量 │ │平台 │ │风控 │ │编码 │ │数据 │ │
│ └──────┘ └──────┘ └──────┘ └──────┘ └──────┘ └──────┘ │
│ │
│ 每个 Agent: SOUL.md + IDENTITY.md + Skills + Workspace │
│ Agent 主 session 不参与任务执行(不被污染) │
└─────────────────────────────────────────────────────────────┘
关键区别:v2.0 vs v2.6
| 维度 | v2.0 | v2.6 |
|---|---|---|
| 编排核心 | DAG 引擎 + 状态机 | Blackboard(Shared Workspace) |
| 决策者 | Daemon(状态机驱动) | Agent(在黑板上自主决策) |
| Daemon 角色 | 调度器(决定谁干什么) | 投递员(执行黑板上的决策) |
| Agent 通信 | Sanguo Mail(异步邮件) | 黑板 Comment 线程(共享空间) |
| 信息位置 | 分散(mail + task目录 + session) | 集中(黑板 SQLite) |
| Agent 生命周期 | 固定节点执行 | Spawn 隔离 session,用完即清 |
| 通知机制 | Mail 轮询 | Daemon tick + spawn |
| 协作模式 | 指令式(庞统分配→将军执行) | 自主式(看黑板→领活→写回) |
3. Shared Workspace(黑板)设计
3.1 参考系统对比
| 系统 | 存储 | 原子性 | 讨论 | 状态机 | 发现 |
|---|---|---|---|---|---|
| Claude Code Agent Teams | JSON 文件 | 无(last-write-wins) | inbox 点对点 | pending/in_progress/completed | Agent 轮询 |
| Hermes Kanban v0.13 | SQLite | SQLite 事务 | Comment 线程 | 7 状态完整机 | Dispatcher 60s tick |
| Network-AI | Markdown 文件 | flock 三阶段提交 | signal key | 无 | Agent 主动读 |
| agent-blackboard | SQLite + Ontology | SQLite 事务 | 本体条目 | 无 | Coordinator 分发 |
| 我们的方案 | SQLite | SQLite 事务 | Comment 线程 | 简化状态机 | Daemon tick |
3.2 SQLite Schema
-- ===== 任务表 =====
CREATE TABLE IF NOT EXISTS tasks (
id TEXT PRIMARY KEY, -- task-001
title TEXT NOT NULL,
description TEXT,
status TEXT NOT NULL DEFAULT 'pending',
CHECK (status IN ('pending','claimed','working','review','done','failed','blocked','cancelled')),
-- 分配(谁领了或被指派)
assignee TEXT, -- agent id: zhangfei-dev
assigned_by TEXT, -- 谁分配的:pangtong-fujunshi / user
-- 依赖
depends_on TEXT, -- JSON array of task IDs
parent_task TEXT, -- 父任务(子任务分解时)
-- 优先级和类型
priority INTEGER NOT NULL DEFAULT 5, -- 1(最高)-10(最低)
task_type TEXT, -- coding/review/data/deploy/research/discuss
-- 时间
created_at TEXT NOT NULL DEFAULT (datetime('now')),
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
claimed_at TEXT,
started_at TEXT,
completed_at TEXT,
deadline TEXT,
-- 重试
retry_count INTEGER NOT NULL DEFAULT 0,
max_retries INTEGER NOT NULL DEFAULT 2
);
CREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status);
CREATE INDEX IF NOT EXISTS idx_tasks_assignee ON tasks(assignee);
CREATE INDEX IF NOT EXISTS idx_tasks_parent ON tasks(parent_task);
-- ===== 评论线程表 =====
-- 参考 Hermes kanban_comment:追加写入,所有参与者可见
CREATE TABLE IF NOT EXISTS comments (
id INTEGER PRIMARY KEY AUTOINCREMENT,
task_id TEXT NOT NULL,
author TEXT NOT NULL, -- agent id 或 'user'
body TEXT NOT NULL,
mentions TEXT, -- JSON array: ["zhangfei-dev", "guanyu-dev"]
created_at TEXT NOT NULL DEFAULT (datetime('now')),
FOREIGN KEY (task_id) REFERENCES tasks(id)
);
CREATE INDEX IF NOT EXISTS idx_comments_task ON comments(task_id);
CREATE INDEX IF NOT EXISTS idx_comments_author ON comments(author);
-- 注意:mentions 是 JSON 数组,无法直接建索引。daemon tick 查询用 json_each(mentions)。
-- 数据量小时够用,后续可拆 comment_mentions 关联表优化。
-- ===== 产出表 =====
CREATE TABLE IF NOT EXISTS outputs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
task_id TEXT NOT NULL,
agent TEXT NOT NULL, -- 谁写的
output_type TEXT NOT NULL, -- code/document/data/config/other
title TEXT NOT NULL,
content_path TEXT, -- 文件路径(产出物在 task 目录下)
summary TEXT, -- 一句话摘要
metadata TEXT, -- JSON: {files_changed, lines_added, ...}
created_at TEXT NOT NULL DEFAULT (datetime('now')),
FOREIGN KEY (task_id) REFERENCES tasks(id)
);
CREATE INDEX IF NOT EXISTS idx_outputs_task ON outputs(task_id);
-- ===== 决策记录表 =====
-- Agent 执行过程中的关键决策必须记录。哪怕是自己做的决策也要填一条。
CREATE TABLE IF NOT EXISTS decisions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
task_id TEXT NOT NULL,
decider TEXT NOT NULL, -- 谁做的决策
decision TEXT NOT NULL, -- 决策内容:"选 A 方案"
rationale TEXT NOT NULL, -- 为什么:"B 方案内存开销更大"
alternatives TEXT, -- JSON array: 被排除的选项
created_at TEXT NOT NULL DEFAULT (datetime('now')),
FOREIGN KEY (task_id) REFERENCES tasks(id)
);
CREATE INDEX IF NOT EXISTS idx_decisions_task ON decisions(task_id);
-- ===== 观察表 =====
-- Agent 执行过程中发现的问题、风险、建议
CREATE TABLE IF NOT EXISTS observations (
id INTEGER PRIMARY KEY AUTOINCREMENT,
task_id TEXT NOT NULL,
observer TEXT NOT NULL, -- 谁观察到的
severity TEXT NOT NULL DEFAULT 'info',
CHECK (severity IN ('blocking','warning','info','audit')),
body TEXT NOT NULL,
resolved_by TEXT, -- 谁处理的
resolved_at TEXT, -- 何时处理的
created_at TEXT NOT NULL DEFAULT (datetime('now')),
FOREIGN KEY (task_id) REFERENCES tasks(id)
);
-- ===== 事件日志(审计追踪)=====
CREATE TABLE IF NOT EXISTS events (
id INTEGER PRIMARY KEY AUTOINCREMENT,
task_id TEXT,
agent TEXT,
event_type TEXT NOT NULL,
detail TEXT, -- JSON
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE INDEX IF NOT EXISTS idx_events_task ON events(task_id);
CREATE INDEX IF NOT EXISTS idx_events_time ON events(created_at);
-- 合法 event_type 清单:
-- 任务:task_created, task_claimed, task_started, task_completed, task_failed,
-- task_blocked, task_unblocked, task_reviewed, task_cancelled, task_retried
-- 协作:comment_added, output_written, observation_added, decision_recorded
-- Agent:agent_spawned, agent_completed, agent_zombie_detected
-- Session:session_spawned, session_archived, session_cleanup
-- 系统:daemon_tick, daemon_manual_tick
-- ===== Agent 注册表 =====
CREATE TABLE IF NOT EXISTS agents (
agent_id TEXT PRIMARY KEY,
role TEXT,
current_status TEXT DEFAULT 'idle', -- idle/working/offline
current_task TEXT,
last_active TEXT,
capabilities TEXT -- JSON array: ["coding", "review", "deploy"]
);
-- agents 表更新规则:
-- Agent claim 任务时:自己更新 current_status='working', current_task=task_id
-- Agent 完成退出时:daemon 更新 current_status='idle', current_task=NULL
-- Daemon tick 检测到 zombie:daemon 更新 current_status='offline'
连接配置:
def get_connection():
conn = sqlite3.connect(str(DB_PATH))
conn.row_factory = sqlite3.Row
conn.execute("PRAGMA journal_mode=WAL")
conn.execute("PRAGMA foreign_keys=ON")
conn.execute("PRAGMA busy_timeout=5000")
return conn
3.3 简化状态机
pending → claimed → working → review → done
↑ │ ├→ blocked ──┘ ├→ failed
│ │ └→ failed └→ cancelled
└─────────┘
(review→pending: 审核不通过,打回重做)
(blocked→pending: 阻塞解除)
(failed→pending: 重试)
与 v2.0 的区别: v2.0 有 9 个状态(spawning, ready, reporting 等),v2.6 简化为 8 个。原因是 spawn 逻辑从状态机移到了 daemon——daemon tick 发现黑板需要某人介入就 spawn,不需要 spawning/ready 这些中间状态。
| 状态 | 含义 | 谁触发 |
|---|---|---|
| pending | 待领取 | 任何 Agent 或用户创建 |
| claimed | 已认领 | Agent 自己或被指派 |
| working | 执行中 | Agent |
| review | 待审核 | Agent 完成产出 |
| blocked | 需要帮助 | Agent |
| done | 完成 | 审核通过且所有问题达成一致 |
| failed | 失败 | Agent 或 daemon |
| cancelled | 取消 | 用户 |
完整合法流转矩阵:
VALID_TRANSITIONS = {
"pending": {"claimed", "cancelled"},
"claimed": {"working", "pending", "cancelled"}, # pending: 放弃认领
"working": {"review", "blocked", "failed", "cancelled"},
"review": {"done", "pending", "failed", "cancelled"}, # pending: 审核不通过打回
"blocked": {"pending", "cancelled"}, # pending: 阻塞解除
"done": set(), # 终态
"failed": {"pending"}, # pending: 重试
"cancelled": set(), # 终态
}
3.4 原子操作
任务认领(claim) — 原子 CAS,防止两个人同时领:
def claim_task(task_id: str, agent_id: str) -> bool:
conn = get_connection()
try:
cursor = conn.execute(
"UPDATE tasks SET status='claimed', assignee=?, claimed_at=datetime('now') "
"WHERE id=? AND status='pending' AND (assignee IS NULL OR assignee=?)",
(agent_id, task_id, agent_id)
)
conn.commit()
return cursor.rowcount > 0 # 0 表示被别人抢了或不是指定分配给自己的人
finally:
conn.close()
产出写入 — SQLite 事务保证原子:
def write_output(task_id: str, agent_id: str, output: dict):
conn = get_connection()
try:
conn.execute("BEGIN IMMEDIATE") # 立即获取写锁
conn.execute(
"INSERT INTO outputs (task_id, agent, output_type, title, content_path, summary, metadata) "
"VALUES (?, ?, ?, ?, ?, ?, ?)",
(task_id, agent_id, output['type'], output['title'],
output['path'], output['summary'], json.dumps(output.get('metadata', {})))
)
conn.execute(
"INSERT INTO events (task_id, agent, event_type, detail) VALUES (?, ?, 'output_written', ?)",
(task_id, agent_id, json.dumps({'output_id': output['title']}))
)
conn.commit()
finally:
conn.close()
3.5 评论线程(讨论机制)
参考 Hermes 的 kanban_comment 模式:
def add_comment(task_id: str, author: str, body: str, mentions: list = None):
conn = get_connection()
try:
conn.execute(
"INSERT INTO comments (task_id, author, body, mentions) VALUES (?, ?, ?, ?)",
(task_id, author, body, json.dumps(mentions or []))
)
conn.execute(
"INSERT INTO events (task_id, agent, event_type, detail) VALUES (?, ?, 'commented', ?)",
(task_id, author, json.dumps({'body_preview': body[:100], 'mentions': mentions}))
)
conn.commit()
finally:
conn.close()
讨论示例:
[16:30 庞统] 张飞,你的实现方案我看了,回测数据量大时内存会爆。
关羽,从风控角度也看看? @关羽 @张飞
[16:35 关羽] 同意。建议加分批加载机制,单批不超过 50 万条。
[16:40 张飞] 收到,改成分批加载。预计 30 分钟。
[16:55 庞统] @张飞 注意止损逻辑也需要同步改,分批后止损触发时机变了。
[17:10 张飞] 完成。产出在 output-zhangfei-v2.md。
核心原则:评论都在黑板上,不在任何 agent 的 session 里。Agent 的 session 是临时的。
3.6 竞态解决
任务认领的竞态通过 SQLite 原子 CAS 解决(先到先得)。
职责冲突的解决(张飞和关羽都认为自己该做某个任务):
- 默认:先到先得 — SQLite CAS,谁先 claim 谁做
- 升级:庞统仲裁 — 如果争议,评论中 @庞统 请求仲裁
- 最终:用户拍板 — @user 请求用户决定
不需要复杂的分布式共识——职责分工已经自然避免了大部分冲突。
4. Daemon(管家)设计
4.1 Daemon 的角色定位
Daemon 是投递员,不是决策者。所有决策发生在黑板上,daemon 只执行。
Daemon 做三件事:
- 读黑板 — 定期 tick,检查黑板状态
- Spawn Agent — 根据黑板上的指示,spawn 对应的 agent
- 清理 Session — agent 执行完后,存档 jsonl + 清理 sessions.json
Daemon 不做:
- ❌ 不决定谁做什么(agent 自己决定或庞统在黑板上分配)
- ❌ 不维护状态机(黑板就是状态)
- ❌ 不做业务逻辑(不解析产出、不做评审)
4.2 Daemon Tick 循环
参考 Hermes Dispatcher,但更轻量:
Tick 频率:60 秒(默认),可通过 CLI 手动触发立即执行。
# 手动触发一次 tick(用于需要立即响应的场景)
python3 ~/.sanguo_projects/sanguo_moziplus/cli/daemon.py tick
async def daemon_tick():
"""每 60 秒执行一次"""
# 1. 健康检查
reclaim_stale_tasks() # 超时的 working 任务回收
detect_zombie_sessions() # 进程死了但 session 还在的
# 2. 读黑板
board = read_blackboard() # SQLite 查询
# 3. 处理评论中的 @mention
# Agent A 在评论中 @AgentB → daemon spawn AgentB 来看评论
for comment in board.get_unprocessed_mentions():
target_agent = comment.mentioned_agent
if not is_agent_active(target_agent):
async_spawn_agent(target_agent,
message=f"黑板上有给你的新评论(task-{comment.task_id}),请查看。")
mark_comment_processed(comment.id)
# 4. 处理待领取的任务(庞统在黑板上分配了但 agent 还没领)
for task in board.get_assigned_unclaimed():
if not is_agent_active(task.assignee):
async_spawn_agent(task.assignee,
message=f"黑板上有分配给你的任务({task.title}),请查看并认领。")
# 5. 处理 blocked 任务(agent 请求帮助)
for task in board.get_blocked_tasks():
mentions = get_latest_comment_mentions(task.id)
if mentions:
# 评论中 @ 了某人 → spawn 那个人
for agent_id in mentions:
if not is_agent_active(agent_id):
async_spawn_agent(agent_id,
message=f"任务 {task.id} 被 block,需要你的协助。")
else:
# 没有 @ → spawn 庞统来决定找谁帮忙
async_spawn_agent('pangtong-fujunshi',
message=f"任务 {task.id} 被 block,没有指定协助者,请决定如何处理。")
# 6. 清理完成的 session
for session in get_completed_sessions():
archive_session(session) # mv jsonl → task 目录
cleanup_sessions_json(session) # 编辑 sessions.json 删除记录
4.3 Session 生命周期
1. Daemon spawn
openclaw agent --agent zhangfei-dev --session-id <uuid> \
--message "请检查黑板 task-001..."
↓
2. Agent 执行
- 读黑板(SQLite 查询)
- 做任务(编码/审核/数据分析)
- 写回黑板(产出、评论、状态更新)
↓
3. Agent 退出(自然结束)
↓
4. Daemon 清理
- mv <session_id>.jsonl → task-001/archive/
- mv <session_id>.trajectory.jsonl → task-001/archive/
- 编辑 sessions.json 删除该 session 记录
技术验证结论:
openclaw agent --agent <id> --session-id <uuid>可创建完全隔离的 session ✅- 直接编辑
sessions.json可安全删除 session 记录 ✅(已验证) - Gateway WS
sessions.delete需要operator.adminscope(token 模式不授予,不可用)❌ - 回退方案:直接编辑
sessions.json是安全可靠的 ✅
4.4 Agent Spawn 后的消息内容
Agent 被 spawn 时,daemon 传递的消息应包含足够的上下文让 agent 知道该做什么:
def build_spawn_message(task_id: str, trigger_reason: str, comments_since: str = None):
task = get_task(task_id)
msg = f"黑板任务通知:\n"
msg += f"- 任务:{task.title}({task.id})\n"
msg += f"- 状态:{task.status}\n"
msg += f"- 触发原因:{trigger_reason}\n"
if comments_since:
recent = get_comments_since(task_id, comments_since)
if recent:
msg += f"\n最近评论:\n"
for c in recent:
msg += f" [{c.created_at} {c.author}] {c.body[:200]}\n"
msg += f"\n请读取黑板获取完整信息。"
return msg
5. Agent 与黑板的交互
5.1 Agent 被_spawn_后的工作流程
Agent 被 spawn
↓
1. 读黑板 → 了解任务全局状态
- 读 tasks 表:当前任务的状态、描述、依赖
- 读 comments 表:讨论历史
- 读 outputs 表:已有产出
- 读 observations 表:已知风险
↓
2. 想 → 根据自己的职责自主决策
- 我是编码先锋,这个 pending 任务适合我 → claim
- 我是风控守将,这个 comment @ 我 → 回复
- 我是副军师,这个任务需要分解 → 创建子任务
↓
3. 做 → 执行任务
- 编码、审核、数据分析等
- 过程中发现风险 → 写 observation
- 需要其他人协助 → 写 comment @mention
↓
4. 写回黑板 → 产出、评论、状态更新、决策记录
- 写 outputs 表:产出文件路径 + 摘要
- 写 comments 表:完成说明
- 写 decisions 表:关键决策(哪怕自己的决策也要填一条)
- 更新 tasks 表:status → done/review
↓
5. 退出 → daemon 自动清理 session
5.2 Agent 工具集
Agent 通过 exec 工具调用 CLI 命令操作黑板:
# 读黑板(全部)
python3 ~/.sanguo_projects/sanguo_moziplus/cli/blackboard.py read --task task-001
# 读黑板(过滤:只读和自己相关的)
python3 ~/.sanguo_projects/sanguo_moziplus/cli/blackboard.py read --task task-001 --agent zhangfei-dev
# 读黑板(过滤:只读最近 20 条)
python3 ~/.sanguo_projects/sanguo_moziplus/cli/blackboard.py read --task task-001 --last 20
# 读黑板(过滤:只读特定类型)
python3 ~/.sanguo_projects/sanguo_moziplus/cli/blackboard.py read --task task-001 --type comments
# 认领任务
python3 ~/.sanguo_projects/sanguo_moziplus/cli/blackboard.py claim --task task-001 --agent zhangfei-dev
# 写产出
python3 ~/.sanguo_projects/sanguo_moziplus/cli/blackboard.py output --task task-001 --agent zhangfei-dev \
--type code --title "分批加载实现" --path task-001/output-zhangfei.md \
--summary "实现分批加载,单批50万条"
# 写评论
python3 ~/.sanguo_projects/sanguo_moziplus/cli/blackboard.py comment --task task-001 --author zhangfei-dev \
--body "完成分批加载实现" --mentions "[]"
# 写观察
python3 ~/.sanguo_projects/sanguo_moziplus/cli/blackboard.py observe --task task-001 --observer guanyu-dev \
--severity warning --body "止损逻辑需适配分批模式"
# 记录决策
python3 ~/.sanguo_projects/sanguo_moziplus/cli/blackboard.py decide --task task-001 --decider zhangfei-dev \
--decision "使用分批加载而非流式" --rationale "流式需要改底层框架,分批只需改回测模块"
# 创建任务(任何 Agent 都可以创建)
python3 ~/.sanguo_projects/sanguo_moziplus/cli/blackboard.py create --title "分钟线数据下载" \
--creator zhaoyun-data --task-type data
6. 关键场景流程
6.1 庞统规划 + Agent 领任务
用户 → 庞统(主session):"设计一个动量因子策略"
↓
庞统在黑板上写:
- 创建 task-001(数据准备,pending)
- 创建 task-002(因子计算,pending,depends_on: [task-001])
- 创建 task-003(回测验证,pending,depends_on: [task-002])
- 评论:"建议赵云领 001,张飞领 002 和 003"
↓
Daemon tick 发现 task-001 pending + 庞统评论建议赵云
↓
Daemon spawn 赵云 → 赵云读黑板 → claim task-001 → 执行 → 写产出 → 退出
↓
Daemon tick 发现 task-001 done → task-002 depends_on 满足
↓
Daemon spawn 张飞 → 张飞读黑板 → claim task-002 → 执行 → 写产出 → 退出
↓
(同理 task-003)
6.2 Agent 间协作讨论
张飞执行 task-002 时发现需要分钟线数据
↓
张飞写评论:"@赵云 task-002 需要分钟线数据,能帮忙下载吗?"
张飞更新任务状态 → blocked
↓
Daemon tick 发现 task-002 blocked + 评论 @ 赵云
↓
Daemon spawn 赵云 → 赵云读黑板 → 看到评论 → 下载数据
赵云写评论:"分钟线数据已下载到 /path/to/data" + 写产出
赵云写评论:"@张飞 数据就绪,可以继续"
↓
Daemon tick 发现评论 @ 张飞
↓
Daemon spawn 张飞 → 张飞读黑板 → 看到数据就绪 → 继续 task-002
6.3 Agent 发现风险
张飞在 task-002 中发现止损逻辑有 bug
↓
张飞写 observation(severity: warning):
"止损逻辑在分批模式下可能漏触发"
张飞写评论:"@关羽 止损逻辑需要你从风控角度确认"
↓
Daemon tick 发现 observation + 评论 @ 关羽
↓
Daemon spawn 关羽 → 关羽读黑板 → 审查 → 写评论 + observation
6.4 用户直接参与
用户读黑板 → 发现 task-002 进度慢
↓
用户在黑板上写评论:"task-002 优先级提高,需要今天完成"
↓
Daemon tick 发现用户评论 → 如果张飞未 active → spawn 张飞通知
7. Session 隔离与清理
7.1 技术实现
class SessionManager:
def async_spawn_agent(self, agent_id: str, message: str) -> str:
"""异步 spawn 隔离 session,不等待完成。返回 session_id。"""
session_id = str(uuid.uuid4())
cmd = [
"openclaw", "agent",
"--agent", agent_id,
"--session-id", session_id,
"--message", message,
"--json"
]
# Popen 异步启动,不阻塞 daemon tick
subprocess.Popen(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
log_event(agent=agent_id, event_type='agent_spawned', detail={'session_id': session_id})
return session_id
def cleanup_session(self, agent_id: str, session_id: str, archive_dir: str):
"""存档 jsonl + 文件锁保护下清理 sessions.json"""
sessions_dir = f"/Users/chufeng/.openclaw/agents/{agent_id}/sessions"
store_path = f"{sessions_dir}/sessions.json"
lock_path = f"{sessions_dir}/.cleanup.lock"
# 1. 存档 jsonl 文件
os.makedirs(archive_dir, exist_ok=True)
for ext in ['.jsonl', '.trajectory.jsonl', '.trajectory-path.json']:
src = f"{sessions_dir}/{session_id}{ext}"
if os.path.exists(src):
shutil.move(src, f"{archive_dir}/{session_id}{ext}")
# 2. 文件锁保护下编辑 sessions.json(防止和 Gateway 并发写入冲突)
with open(lock_path, 'w') as lock_file:
fcntl.flock(lock_file, fcntl.LOCK_EX)
try:
with open(store_path) as f:
store = json.load(f)
keys_to_remove = [k for k in store if session_id in k]
for k in keys_to_remove:
del store[k]
with open(store_path, 'w') as f:
json.dump(store, f, indent=2)
finally:
fcntl.flock(lock_file, fcntl.LOCK_UN)
os.unlink(lock_path)
7.2 验证结论
| 验证项 | 结果 |
|---|---|
openclaw agent --session-id <uuid> 创建隔离 session |
✅ 通过 |
| 连续 spawn 多个 session 互不干扰 | ✅ 通过 |
| 并行 spawn 成功 | ✅ 通过 |
| 直接编辑 sessions.json 删除记录安全 | ✅ 通过 |
| jsonl 存档后从原目录删除 | ✅ 通过 |
| Gateway WS sessions.delete(需 admin scope) | ❌ 不可用 |
openclaw sessions cleanup --fix-missing --enforce |
❌ 对 agent main session 报错 |
| Agent 主 session 对 CLI spawn 的 sub 完全无感 | ✅ 确认(设计如此) |
8. Sanguo Mail:退役
v2.6 中 Mail 完全退役。黑板的两个操作替代了 Mail 的所有功能:
| Mail 功能 | 黑板替代 |
|---|---|
| 庞统分配任务 | 庞统在黑板创建 task + 评论 @指定 agent |
| Agent 间通信 | 评论 @mention |
| 结果回传 | 产出写入 outputs 表 + 评论通知 |
| 讨论 | 评论线程 |
黑板比 Mail 更可靠:信息集中在 SQLite(不分散在 mail 目录)、有状态追踪、评论线程保持上下文完整、SQLite 读写比 Mail poller 更可靠。
如果需要系统级通知(daemon 异常、Gateway 状态),在黑板上创建 system 类型任务处理。
9. 质量门控(任务完成标准)
9.1 任务完成 = 司马懿评审通过 + 所有问题达成一致 + 修改完成
一个任务要标记为 done,必须满足:
- 产出已提交 — Agent 写入 outputs 表
- 决策已记录 — 关键决策写入 decisions 表(哪怕是自己的决策也要填一条)
- 司马懿审核通过 — spawn 司马懿审核产出,评论中明确写“通过”
- 所有问题达成一致 — 审核中提出的问题全部解决,讨论达成共识
- 修改已完成 — 审核问题对应的修改已写入产出
9.2 达成一致的原则
- 以用户意图为导向 — 任何人(包括司马懿、庞统)说的都不一定对,最终以用户的原始意图为准
- 讨论达成共识 — 不同意见通过黑板评论讨论解决,不是谁职位高谁说了算
- 用户有最终裁量权 — 讨论无法达成一致时,@user 请用户裁定
9.3 流程
Agent 完成产出 → status: review
↓
Daemon tick → spawn 司马懿审核
↓
司马懿读黑板(产出 + 决策 + 观察)
↓
司马懿写评论:
- “通过” → status: done
- “不通过,原因:XXX” → status: pending(打回重做)+ 评论说明问题
↓
如果有争议:
- 评论中讨论
- 讨论不清 → @user 请求裁定
9.4 决策记录
Agent 执行过程中的每个关键决策都必须记录在黑板的 decisions 表中:
| 字段 | 含义 |
|---|---|
| decider | 谁做的决策 |
| decision | 决策内容(选了什么) |
| rationale | 为什么这样选 |
| alternatives | 被排除的选项 |
哪怕是自己做的决策也要填一条。 目的:
- 后续复盘时能追溯“当时为什么这样选”
- 审核时司马懿能理解决策背后的思考
- 经验沉淀的原始素材
10. 产出物目录约定
~/.sanguo_projects/sanguo_moziplus/artifacts/
└── {task-id}/
├── outputs/ # Agent 产出物(代码、文档、数据)
├── archive/ # session jsonl 存档
└── data/ # 数据文件
Agent 写产出时,content_path 指向此目录。Daemon 存档 session jsonl 时也写入 archive/ 子目录。
11. 保留 v2.0 的设计
以下 v2.0 的设计在 v2.6 中保留:
- SQLite WAL 模式 — 黑板数据库同样使用 WAL
- 结构化产出规范 — output.md frontmatter + 结论 JSON(写在黑板 outputs 表中)
- 观察机制 — v2.0 Report Watcher 的思路升级为 observations 表
- 证据原则 — 结论必须有证据(代码行号、日志、文件内容)
- 审核流程 — 可通过黑板评论 + 状态机实现
12. Phase 规划(v2.6)
Phase 1: 黑板基础设施
- SQLite blackboard.db(5 表 + WAL)
- blackboard.py CLI(读写操作)
- Daemon tick 循环(读黑板 + spawn + 清理)
- Session 管理(spawn + 存档 + 清理)
Phase 2: Agent 交互
- Agent 黑板操作 Skill
- 评论 + @mention 通知链路
- 任务依赖自动推进
- 健康检查(stale reclaim + zombie 检测)
Phase 3: 智能化
- 庞统 AI 规划(读需求 → 创建任务 + 分配建议)
- Agent 自主领活(读黑板 → 匹配职责 → claim)
- 产出验证门禁
- 经验沉淀(observation → knowledge base)
13. 技术选型
| 需求 | 参考系统 | 我们的方案 | 理由 |
|---|---|---|---|
| 共享状态 | Hermes SQLite + Network-AI flock | SQLite WAL + 事务 CAS | 原子性 + 无外部依赖 |
| 讨论 | Hermes kanban_comment | comments 表 + @mention | 简单追加写入,所有人可见 |
| 调度 | Hermes Dispatcher 60s tick | Daemon 60s tick | 同设计,更轻量 |
| 通知 | Claude Code idle notification | Daemon spawn + message | OpenClaw 原生能力 |
| 通信 | Hermes kanban_comment + Claude Code inbox | 黑板 comments + @mention | 替代 Sanguo Mail |
| 竞态 | Network-AI propose→validate→commit | SQLite CAS(first-commit-wins) | SQLite 事务足够 |
| Session | Hermes process-per-worker | openclaw agent --session-id | OpenClaw 原生隔离 |
| 清理 | 无参考 | 编辑 sessions.json | 已验证可行 |
14. 风险和缓解
| 风险 | 概率 | 缓解 |
|---|---|---|
| Agent 上下文不足(隔离 session 没有历史) | 中 | spawn 时传递黑板关键信息 + agent 可主动读黑板 |
| Daemon 单点故障 | 低 | PM2 自动重启 + tick 无状态 |
| SQLite 并发写入 | 中 | WAL + busy_timeout + BEGIN IMMEDIATE |
| 黑板膨胀(大量评论/产出) | 低 | 定期 archive + agent 只读最近 N 条 |
| Agent 不知道该做什么 | 中 | Skill 指导 + 庞统 plan 评论 + daemon 消息含上下文 |
| Sanguo Mail 退役后的系统通知 | 低 | 黑板 system 类型任务替代 |