Files
sanguo_moziplus_v2/docs/design/architecture-v2.6.md
T
2026-05-15 13:37:58 +08:00

62 KiB
Raw Blame History

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 退役决策 + 质量门控 + 决策记录 + 工程修正
v2.6.2 2026-05-15 课题1设计决策:三层执行模型、续杯机制、AI驱动retry、Guardrail体系、must_haves三件套、分级审查矩阵
v2.6.2.1 2026-05-15 司马懿评审反馈:L2/L3区分标准、timeout修正、outputs关联attempt、Scope Guard异步、risk_level自动
v2.6.3 2026-05-15 课题2设计决策:双层事件架构(EventBus+Signal File+Tick)、L1/L2/L3上下文传递、黑板是索引不是仓库、依赖驱动并行/串行、Phase规划更新

课题 1-2 遗留 TODO(需后续课题解决)

# 待解决事项 归属课题 说明
T1-1 spawn sub 是否阻塞?需要调查 课题 2 课题 2 解决:不阻塞,signal file 异步
T1-2 事件驱动取代 polling tick 课题 2 课题 2 解决:双层事件架构
T1-3 依赖推进(done→自动解锁下游) 课题 2 课题 2 解决:task_completed 事件即时解锁
T2-1 files_modified 冲突检测 课题 2 D2-4 决策:不做,Agent 评论自然协调
T2-2 Auto-compact 课题 2 D2-6 决策:不做,隔离 session 天然无 context rot
T1-4 Agent 间自主协商机制 课题 3 事件驱动 + 挑战体系共同支撑
T1-5 Scope Guard 的 Skill 定义 课题 4 scope_declaration 格式、检查 prompt 模板
T1-6 truths 验证的具体实现 课题 4 AI 级别验证,怎么让 AI 判断 truths 达成
T1-7 outputs attempt_number 过滤规则 课题 4 重试时 Agent 看到之前 attempt output 的规则
T1-8 状态机细化(review 轮次、sub_status) 课题 3 挑战体系引入 review 内部状态
T2-3 blackboard.py 写操作自动写 signal file Phase 1 实现 CLI 层自动完成
T2-4 EventBus + Signal File Watcher 实现 Phase 1 实现 Daemon 核心改造
T2-5 L2/L3 分层读取 API Phase 2 实现 blackboard.py read --level L2/L3

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 是投递员,不是决策者。

  1. Agent 决策,Daemon 执行 - 庞统做 plan、张飞领任务、关羽发现风险,都写在黑板上。Daemon 读黑板,执行 spawn/通知。
  2. 产出在黑板,不在邮件 - 所有任务产出、讨论、观察都在任务的黑板空间里,Sanguo Mail 不介入任务协作。
  3. Daemon 不阻塞 Agent - Daemon 是常驻管家,定期 tick 检查黑板,spawn agent 执行,不占用任何 agent 的主 session。
  4. 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,

    -- must_haves 与风险等级(课题1设计决策)
    must_haves TEXT,                          -- JSON: {truths: [], artifacts: [], constraints: []}
    risk_level TEXT DEFAULT 'standard',       -- high/standard/low/research
    estimated_duration_minutes INTEGER        -- 预估工时(续杯硬上限 = 3x 此值)
);

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, ...}
    attempt_number INTEGER DEFAULT 1,       -- 关联 task_attempts.attempt_number
    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"]
);

-- ===== 任务尝试记录(参考 Hermes task_runs)=====
CREATE TABLE IF NOT EXISTS task_attempts (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    task_id TEXT NOT NULL,
    attempt_number INTEGER NOT NULL,
    agent TEXT NOT NULL,
    outcome TEXT NOT NULL,  -- completed/blocked/crashed/timed_out/spawn_failed/reclaimed
    exit_code INTEGER,
    log_path TEXT,
    summary TEXT,
    metadata TEXT,  -- JSON: {duration_seconds, token_count, ...}
    started_at TEXT NOT NULL DEFAULT (datetime('now')),
    completed_at TEXT,

    FOREIGN KEY (task_id) REFERENCES tasks(id)
);

CREATE INDEX IF NOT EXISTS idx_attempts_task ON task_attempts(task_id);

-- 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 解决(先到先得)。

职责冲突的解决(张飞和关羽都认为自己该做某个任务):

  1. 默认:先到先得 - SQLite CAS,谁先 claim 谁做
  2. 升级:庞统仲裁 - 如果争议,评论中 @庞统 请求仲裁
  3. 最终:用户拍板 - @user 请求用户决定

不需要复杂的分布式共识--职责分工已经自然避免了大部分冲突。

3.7 黑板是索引不是仓库(AI Native 内容规范)

核心原则:黑板只存元数据 + 摘要 + 文件路径,不存大段文本内容。

设计推导(课题 2):

  • Network-AI 的核心洞察:Agent 只读黑板摘要,详细数据在文件中
  • Claude Code 的 file reference 模式:不内联,只引用
  • agent-chorus 的 Context Pack 实验证明:结构化上下文让 Agent 文件打开量降 70%、token 消耗降 60%、零生产风险答案
  • Opal-Bridge 的 Fidelity 三档:无损/摘要/混合,传递时按需降档
  • 一个典型任务全量黑板信息 ~1100-1750 tokens,极端 ~4000 tokens--远小于 128K context
  • 问题不是空间不够,而是信号噪声比:全量注入让 Agent 在无关信息上浪费注意力

AI Native 内容规范--不做硬限制,做软引导:

传统做法是给每个字段设长度上限(如 comments.body ≤ 2000 字符),这是 CRUD 应用的思维。 AI Native 的做法是:Agent 是智能体,有能力判断"这段分析应该写文件还是直接写评论"。规范是指导性的,不是强制性的。

  • 不做硬限制--不设字段长度上限,不截断,不报错
  • 做软引导--Agent 的 Skill 中写"评论应简洁明了,大段分析写文件后在评论中给路径"
  • 做传递优化--L1 传递时自动截取(最近 3 条评论、每条 100 字符),这是传递层面的优化,不是存储层面的限制
  • 做信息分层--黑板上的 comments 表存完整内容(不截断),但 L1 传递时只取摘要

为什么这样做是 AI Native:

  1. Agent 是智能体,不是 API 客户端--它有能力判断"这段分析应该写文件还是直接写评论"
  2. 如果硬限制导致信息丢失,Agent 会绕过限制(拆成多条评论、用文件存储),反而更混乱
  3. 真正需要控制的是传递时的信息量(L1 预算),不是存储时的信息量

黑板上"必要信息"的定义(指导性):

类别 上黑板 不上黑板
决策 谁、选了什么、为什么 完整备选方案对比表
产出 title + summary + content_path 代码全文、数据文件
状态 当前 status + 最新 observation 完整事件日志(可归档)
讨论 结论 + 关键论据 来回辩论的完整过程
风险 severity + 一句话描述 详细影响分析报告

防爆炸机制:

  • 产出物只存路径(outputs.content_path)
  • 事件日志有 TTL(events 表定期归档旧数据)
  • 大段分析建议写文件,黑板只存摘要+路径

落地到 schema:

  • outputs 表:content_path + summary,不存文件内容
  • comments 表:body 存完整内容(不截断),大段分析 Agent 自主决定是否写文件
  • decisions 表:decision + rationale 是结构化文本
  • observations 表:body 是风险描述

Agent 获取信息的分层策略(L1/L2/L3)详见 §4.4,对应 Opal-Bridge Fidelity 三档。


4. Daemon(管家)设计

4.1 Daemon 的角色定位

Daemon 是投递员,不是决策者。所有决策发生在黑板上,daemon 只执行。

Daemon 做三件事:

  1. 读黑板 - 定期 tick,检查黑板状态
  2. Spawn Agent - 根据黑板上的指示,spawn 对应的 agent
  3. 清理 Session - agent 执行完后,存档 jsonl + 清理 sessions.json

Daemon 不做:

  • 不决定谁做什么(agent 自己决定或庞统在黑板上分配)
  • 不维护状态机(黑板就是状态)
  • 不做业务逻辑(不解析产出、不做评审)

三层执行模型:Daemon 的操作按成本和复杂度分为三层:

层级 方式 成本 适用场景 例子
L1 Daemon 直接操作 SQLite 读写、文件操作 几乎为零 纯机械动作 更新状态、记录事件、机械验证(文件存在、JSON格式、字段非空)
L2 spawn sub openclaw agent --agent <id> --session-id <uuid> 轻量(隔离 session,单任务) 轻量 AI 判断 scope guard、格式校验、快速评估、假死 reminder
L3 run agent spawn 完整 Agent 到黑板上工作 完整(读黑板+思考+写回) 重度 AI 工作 庞统拆解、张飞编码、司马懿 review、庞统纠错

关键区别:

  • L2 的 sub 是一次性、单任务的("帮我检查这个输出是否在 scope 内"),执行完就退出
  • L3 的 agent 是完整的黑板参与者(读全局、自主决策、写回多个表)

L2 与 L3 的区分标准:是否读黑板全局。

  • L2:不读黑板全局上下文,只拿当前任务的特定字段做判断。spawn 时传递局部数据(如 scope_declaration 文本 + task.truths),sub 返回结果后退出。
  • L3:读黑板全局(tasks + comments + outputs + decisions + observations),做全局决策。spawn 时只传任务 ID + 触发原因,Agent 自己读黑板。

这个区分决定了 spawn 时的消息内容--L2 传数据,L3 传指针。

4.2 事件驱动架构(课题 2 设计决策)

设计推导过程

三个参考系统的做法:

系统 架构 事件通知方式 启示
open-multi-agent 单进程 TypeScript 纯 EventEmitter--queue.on('task:ready', handler)。TaskQueue 内部维护 listeners Map,complete() 时同步触发 emit。零基础设施 和我们的黑板同构:TaskQueue.complete() = 我们的任务完成,unblockDependents() = 我们的依赖解锁
agent-chorus 多进程(Claude/Codex/Gemini 各自独立) 本地 JSONL 文件队列--chorus send 写入 .agent-chorus/messages/<target>.jsonl,chorus messages --agent <self> --clear 读并清空。纯文件系统,无网络 Standup+Conclude 模式:Agent 开始时读 inbox,结束时广播状态。JSONL inbox 做跨进程通信
Edict 分布式(API Gateway + Orchestrator + Agent Pool) Redis Streams Event Bus--task.created 等主题 + WebSocket 推送 Dashboard 我们是单机单进程,不需要 Redis

推导结论:

  1. open-multi-agent 证明:单进程内 EventEmitter 完全够用,但它是单进程,我们是跨进程
  2. agent-chorus 证明:跨进程通信用 JSONL 文件就行,不需要 HTTP/Redis/MQ
  3. Edict 的 Redis Streams 是分布式场景所需,我们不需要
  4. 真正需要即时响应的场景只有 4 个:task_completed / task_failed / @mention / user_action。其他 ≤30s 延迟完全可接受
  5. 60s Tick 本身不是问题,问题是 Tick 的效率--应该 Tick 是核心,加速器可选

用户反馈与设计迭代:

  • 初始设计:Signal File 跨进程通知 → 用户质疑"Signal File 存在的意义是什么"
  • 第二版:HTTP 端点 → 用户要求"基于优秀实践推导,不是拍脑袋换方案"
  • 最终版:Tick 核心 + Inbox JSONL 加速(agent-chorus 模式)--基于三个参考系统的实际代码推导

D2-1:Tick 核心 + Inbox 加速(最终方案)

┌──────────────────────────────────────────────────────────────────┐
│                    Daemon (Python asyncio)                       │
│                                                                   │
│  核心:Tick Loop(30s 主循环)                                     │
│  ┌─────────────────────────────────────────────────────────────┐ │
│  │ 读黑板全量状态(SQLite 查询)                                  │ │
│  │ 发现需要处理的(mention / blocked / done → pending)           │ │
│  │ 执行对应操作(spawn / 通知 / 清理)                             │ │
│  │ 健康检查(zombie / stale / heartbeat)                         │ │
│  │                                                               │ │
│  │ 设计推导:Hermes 60s tick 证明 polling 可靠稳定。              │ │
│  │ 我们从 60s 降到 30s,因为黑板查询比 Hermes 轻量。              │ │
│  └─────────────────────────────────────────────────────────────┘ │
│          ↑                                                        │
│  加速:Inbox JSONL(agent-chorus 模式)                           │
│  ┌─────────────────────────────────────────────────────────────┐ │
│  │ Agent 写黑板后,可选:追加一行 JSON 到 daemon inbox             │ │
│  │ Daemon 主循环每 1s 检查 inbox 是否有新内容                     │ │
│  │ 有新内容 → 立即执行一次 mini-tick(只处理触发的事件)            │ │
│  │ 处理完清空 inbox                                               │ │
│  │                                                               │ │
│  │ 设计推导:agent-chorus 用 JSONL inbox 做跨 Agent 通信,         │ │
│  │ 我们用 JSONL inbox 做 Agent→Daemon 通知。同理同构。            │ │
│  │ inbox 是加速器,不是核心。Tick 兜底所有场景。                    │ │
│  └─────────────────────────────────────────────────────────────┘ │
│          ↑                                                        │
│  恢复:启动时全量扫描                                              │
│  ┌─────────────────────────────────────────────────────────────┐ │
│  │ Daemon 重启后立即做一次完整 Tick(PM2 自动重启)                │ │
│  │ 消除重启后的 30s 空窗                                          │ │
│  │ 不需要 EventBus 持久化--黑板(SQLite)是唯一真相源              │ │
│  └─────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────┘

为什么不选的替代方案:

  • EventBus + Signal File(初始设计):Signal File 需要额外的扫描/读/删循环,增加了耦合链
  • HTTP 端点(第二版):引入网络依赖,Daemon 需要跑 HTTP 服务,不够简单
  • Redis pub/sub(Edict 方案):引入新依赖,v2.6 已去掉 Redis;我们不需要分布式
  • SQLite update-hook:需要 C API 绑定
  • fswatch/watchdog:跨平台兼容性差

Inbox JSONL 具体实现(参考 agent-chorus chorus send 模式):

# blackboard.py 写完 SQLite 后,可选追加一行 JSON
INBOX_PATH = Path("~/.sanguo_projects/sanguo_moziplus_v2/inbox/daemon.jsonl")

# 写入格式(参考 agent-chorus message schema: from/to/timestamp/content/cwd)
async def notify_daemon(event_type: str, task_id: str, agent: str):
    line = json.dumps({
        "from": agent,
        "to": "daemon",
        "timestamp": datetime.now(timezone.utc).isoformat(),
        "event": event_type,     # comment_added / task_completed / task_failed
        "task_id": task_id,
    })
    async with aiofiles.open(INBOX_PATH, mode='a') as f:
        await f.write(line + '\n')

# Daemon 主循环中检查 inbox
async def check_inbox():
    if not INBOX_PATH.exists():
        return
    lines = INBOX_PATH.read_text().strip().split('\n')
    INBOX_PATH.unlink()  # 原子读取+删除(参考 agent-chorus --clear 模式)
    for line in lines:
        msg = json.loads(line)
        await handle_event(msg['event'], msg['task_id'], msg['from'])

Daemon 主循环:

async def daemon_main_loop():
    # 启动时全量扫描
    await full_tick()

    while True:
        # 1. 检查 inbox(每 1s)
        await check_inbox()  # 有内容则立即执行 mini-tick

        # 2. 定期 Tick(每 30s)
        if time.time() - last_tick > 30:
            await full_tick()
            last_tick = time.time()

        await asyncio.sleep(1)

D2-2:依赖声明的并行/串行自动决策

设计推导:open-multi-agent 的 TaskQueue.complete() → unblockDependents() 是核心模式--complete→auto-unlock,纯依赖声明驱动。其 scheduler.ts 还提供了 4 种调度策略(round-robin / least-busy / capability-match / dependency-first)。

串行触发链(Tick + Inbox 加速版):

Agent A 完成 task-001
  → 写黑板 outputs + 更新 status → done + 写 handoff comment
  → 通知 Daemon(inbox JSONL)
  → Daemon 下次循环(~1s 内)收到通知 → mini-tick:
      查询所有 depends_on 包含 task-001 的 pending 任务
      → task-002 depends_on: [task-001],检查 task-001 done ✅
      → spawn Agent B 执行 task-002
  (如果 inbox 通知丢失 → 30s Tick 兜底补上)

并行:depends_on 为空且 assignee 不同的任务,自然并行(Daemon 分别 spawn)。不需要额外逻辑。

不做 files_modified 冲突检测(D2-4):Agent 通过黑板评论自然协调("我在改 main.py,你别碰"),不需要系统强制。Scope Guard(课题 1)作为兜底。实际覆盖:depends_on 覆盖 80%+ 的显式依赖场景,边角场景通过黑板评论 + 庞统仲裁补充。

与课题 1 的兼容性

课题 1 设计 事件驱动后变化 改善
续杯机制 task_completed 通知加速依赖解锁 @mention 从 ≤60s 降到 ≤1s
retry 由 AI 决策 task_failed 通知加速 retry 链 庞统更快介入
Guardrail 吹哨人 observation 写入后通知 Daemon Daemon 即时感知问题
三层执行模型 不变,Tick/inbox 处理仍按 L1/L2/L3 分层 一致

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.admin scope(token 模式不授予,不可用)
  • 回退方案:直接编辑 sessions.json 是安全可靠的

4.4 Agent Spawn 的上下文分层传递(课题 2 设计决策)

设计推导GSD Wave Execution 证明隔离 session + 新鲜 context > 单一 session + 压缩。Claude Code 的 file reference 模式证明"引用而非内联"是最优策略。Opal-Bridge 的 Fidelity 三档提供了理论框架。agent-chorus 的 Context Pack 实验证明结构化上下文让 Agent 效率提升 60-70%。问题不是 context 不够大,而是信号噪声比。

L1/L2/L3 对应 Opal-Bridge Fidelity 三档

Opal-Bridge Fidelity 我们的映射 场景
Mode A:无损(完整上下文) L1 + L2 + L3 复杂任务,Agent 需要完整了解讨论历史和产出
Mode BLLM 摘要 L1 + L2 标准任务,核心信息 + 扩展摘要
Mode C:混合保留最近N条 L1 简单任务,只传核心,Agent 按需取更多

Agent 自己决定用哪个 Fidelity 档位——收到 L1 后判断信息是否足够,不够就 L2/L3。

D2-5:三层上下文传递(L1 必传 / L2 按需 / L3 按需)

层级 内容 Token 估算 谁决定
L1(spawn message) 任务核心 + 角色 + 触发原因 + 依赖状态 + 最近评论 + must_haves ~300-500 Daemon 自动
L2(CLI 按需) 完整评论线程 + 产出摘要 + 决策记录 + 观察记录 ~500-1500 Agent 自主决定
L3(文件按需) 产出物文件完整内容 + 完整事件日志 + 子任务详情 ~2000-10000 Agent 自主决定

L1 Spawn Message 模板:

def build_spawn_message_L1(task_id: str, agent_id: str, trigger: str) -> str:
    task = get_task(task_id)

    # 依赖状态摘要(1行/依赖任务)
    deps_status = []
    for dep_id in json.loads(task['depends_on'] or '[]'):
        dep = get_task(dep_id)
        deps_status.append(f"  {dep_id}: {dep['status']} - {dep['title']}")

    # 最近 3 条评论摘要(截断 100 字符)
    recent_comments = get_comments(task_id, limit=3)
    comments_str = ""
    for c in recent_comments:
        comments_str += f"  [{c['created_at'][:16]} {c['author']}] {c['body'][:100]}\n"

    # must_haves 摘要
    must_haves = json.loads(task.get('must_haves') or '{}')
    truths_str = ', '.join(must_haves.get('truths', []))

    return f"""黑板任务通知(L1):
任务:{task['title']}({task['id']})
状态:{task['status']} | 类型:{task['task_type']} | 风险:{task['risk_level']}
触发原因:{trigger}
描述:{task['description'] or '(无)'}
验收标准(truths):{truths_str or '(未定义)'}

依赖状态:
{chr(10).join(deps_status) if deps_status else '  (无依赖)'}

最近评论:
{comments_str if comments_str else '  (无评论)'}

请使用以下命令获取更多信息:
  L2(扩展):blackboard.py read --task {task_id} --level L2
  L3(全量产出):blackboard.py read --task {task_id} --type outputs
"""

D2-6:不需要 Auto-compact:v2.6 的 Agent 每次 spawn 都是隔离的新鲜 session,天然没有 context rot。唯一可能有累积的是庞统主 session(长期在线协调),属 Phase 3 优化。

D2-7:Context 预算分配(128K 模型):

组件 预算 说明
System Prompt + SOUL.md + IDENTITY.md ~3K-5K tokens 固定开销
Skills + AGENTS.md ~2K-4K tokens 固定开销
L1 spawn message ~300-500 tokens 必传
L2 黑板扩展(按需) ~500-1500 tokens Agent 自主决定
L3 产出物文件(按需) ~2K-10K tokens Agent 自主决定
工作空间(Agent 思考+输出) ~30K-50K tokens 预留
总计 ~40K-70K tokens 远小于 128K,安全

4.5 续杯与心跳

参考 v1.0 实践 + Hermes v0.13 Claim TTL。

正常流(大多数情况):

  1. Agent spawn → 开始工作
  2. Agent 每个关键进展写黑板 observation(既是进度汇报,也是心跳信号)
  3. Daemon tick 看到 working 状态 + 有新 observation → 不干预(健康状态)
  4. Agent 完成产出 → 写 output + 状态流转 → Daemon 检测到继续下一步

异常流:

情况 Daemon 检测到 行为 层级
Agent 有进展 黑板有新 observations 不干预(无限续) L1
Agent 没进展但 session 活跃 无新 observations 但 session 还在 不干预(可能正在思考) L1
↑ 判断信号:observations 最后写入时间 < estimated_duration_minutes,纯 L1 查询,不依赖 AI 判断
timeout(agent run 返回超时)+ 产出达标 agent run 返回超时 + outputs 表有内容 幻觉门控验证产出 → 通过则继续流转 L1→L2
timeout(agent run 返回超时)+ 产出不达标 agent run 返回超时 + outputs 为空 L2 spawn sub 发 reminder 让 Agent 继续(假死处理) L2
timeout + 产出不达标 + reminder 后仍无进展 二次 timeout 回收到 pending,记录 failure_detail L1
非timeout 错误(进程退出) 进程已死 进入 AI 纠错流程 L3
硬上限超时 working 状态超过 3x 预估工时 强制回收,记录事件 L1

设计推导:

  • v1.0 实践证明:看结果不看过程(即使 CLI 报错/超时,产出文件存在且有效就算成功)
  • 续命和重试是两个独立预算:续命(Agent有进度→无限续),重试(Agent真挂→有限次)
  • Hermes 的 Claim TTL(默认15分钟)提供了超时回收的参考值

timeout 的检测:timeout 信号来自 openclaw agent --agent <id> 的返回值(阻塞调用)。Agent 在执行过程中通过写黑板 observations 维持活跃信号--Daemon tick 检查 observations 的最后写入时间,如果有新 observation 说明 Agent 还在工作。但最终判断 Agent 是否超时,以 agent run 的返回值为准。

reminder 后的硬时间上限:reminder 后如果超过 estimated_duration_minutes 仍未完成(从 reminder 时间算起),才回收任务。

4.6 AI 驱动的 Retry(纠错协商)

参考 v1.0 _handle_blocked_node() + Hermes task_runs + Claude Code Teams "before retrying, answer what failed"。

核心原则:Retry 原因由 AI 判断,Daemon 只执行。

流程:

  1. Agent 失败(产出 status=failed 或 Daemon 检测到异常终止)
  2. Daemon 不判断原因,只在黑板上记录这次 attempt(task_runs 模式,每次 attempt 独立记录)
  3. Daemon spawn 庞统(L3)看黑板上的失败记录 + 之前所有 attempts
  4. 庞统在黑板上写决策(四种选择之一):
    • "同一 Agent 重试" + 失败原因分析 + 改进建议
    • "换 Agent 重试" + 为什么换 + 新 Agent 优势
    • "任务需要用户介入" + 卡在哪 + 建议
    • "任务无法完成,建议取消" + 为什么
  5. Daemon 读庞统决策,执行对应操作
  6. 如果重试后仍失败 → 新 attempt 记录 → 再次 spawn 庞统
  7. Circuit breaker:同一 task 总 attempt 数达到 N 次(默认3次,不区分是哪个 Agent)→ 自动 block + 通知用户。理由:3 次尝试都不成功说明问题在任务本身而非 Agent 能力。

失败记录:谁记录什么?

记录者 记录内容 黑板位置
Daemon 机械类失败(进程退出码、超时) events 表,event_type=task_failed
司马懿 内容类失败(评审不通过) reviews 表(verdict=needs_revision + issues)
庞统 方向类失败(需求偏离) decisions 表(重规划原因)
Agent 自己 能力不足/专业外,主动报告失败 comments 表(说明原因)+ tasks status→failed
Agent(重试时) 新 attempt 的产出 outputs 表(带 attempt_number)

Agent 重试时能看到什么:黑板上的 events(失败记录)+ reviews(评审意见)+ comments(讨论)。全部在黑板上,spawn 时自然读到。

设计推导:

  • v1.0 实践:庞统分析原因 → 司马懿 challenge → 三轮协商 → 执行,方向正确但由引擎硬编码调用
  • v2.6 改进:Agent 在黑板上自主协商(需要事件驱动支持,见课题2),Daemon 只 spawn 不调度
  • Hermes task_runs:每次 attempt 独立记录(attempt_number, outcome, log_path, exit_code),可追溯可审计

4.7 Guardrail 体系(吹哨人机制)

参考 OpenAI Agent SDK OutputGuardrail + GSD must_haves + v1.0 M4 Guard 机制。

核心原则:Guardrail 是吹哨人不是终结者。检测到问题写黑板(observation),触发后续 AI 判断链。决策权在黑板上,执行权在 Daemon。

三个 Guardrail:

Guardrail 触发时机 检测方式 发现问题后
Scope Guard Agent claim 任务后在工作过程中写 decisions(scope 相关)时 L2 sub 异步对比 scope_declaration vs task.truths 写 observation(severity=warning),Daemon 下次 tick 触发庞统判断
Output Guard Agent 写 output 时 L1 机械检查(文件存在、格式正确、字段非空)+ L2 语义检查 机械失败直接打回,语义问题写 observation
Format Guard Agent 写任何结构化数据时 L1 JSON Schema 校验 格式错误直接打回重做

后续动作链(问题升级):

Guardrail 检测到问题 → 写黑板 observation
  ↓
Daemon tick 读到 observation
  ↓
根据 severity 分级处理:
  - blocking → L3 立即 spawn 庞统
  - warning → L3 spawn 庞统(下次 tick 统一处理)
  - info → 只记录,不触发
  ↓
庞统在黑板上写决策:
  - "确认偏离,打回" → Daemon 改状态回 pending
  - "方向扩展合理,批准继续" → 继续
  - "需要用户判断" → 通知用户

设计推导:

  • OpenAI Agent SDK:Guardrail 本身是轻量 AI Agent(并行运行,专门做检查),不是 if-else 规则
  • GSD must_haves truths:面向可观测行为,不是实现步骤
  • v1.0 M4 Guard:entry/exit guard + skill 化检查逻辑,方向正确但绑定在 DAG 节点上

Scope Guard(异步检查,不阻塞 Agent 执行):

  • 触发时机:Agent claim 任务后在工作过程中写 decisions(scope 相关)时
  • 检查方式:L2 sub 异步对比 scope_declaration vs task.truths
  • 不阻塞:Agent 写完 scope_declaration 后继续工作,不等 Guard 结果
  • 发现问题:写 observation(severity=warning),Daemon 下次 tick 触发庞统判断
  • 兜底:即使 Scope Guard 漏报,庞统在 review 阶段仍会检查方向正确性

5. Agent 与黑板的交互

5.1 Agent 被_spawn_后的工作流程

Agent 被 spawn
    ↓
1. 读黑板 → 了解任务全局状态
   - 读 tasks 表:当前任务的状态、描述、依赖
   - 读 comments 表:讨论历史
   - 读 outputs 表:已有产出
   - 读 observations 表:已知风险
    ↓
2. 想 → 根据自己的职责自主决策
   - 我是编码先锋,这个 pending 任务适合我 → claim
   - 我是风控守将,这个 comment @ 我 → 回复
   - 我是副军师,这个任务需要分解 → 创建子任务
   - Agent claim 任务后、开始工作前,写 scope_declaration 到 decisions 表:
     "我计划做什么,产出什么"
     Scope Guard(L2 sub)会对比 scope_declaration vs task.truths
    ↓
3. 做 → 执行任务
   - 编码、审核、数据分析等
   - 过程中发现风险 → 写 observation
   - 需要其他人协助 → 写 comment @mention
    ↓
4. 写回黑板 → 产出、评论、状态更新、决策记录
   - 写 outputs 表:产出文件路径 + 摘要
   - 写 comments 表:完成说明
   - 写 decisions 表:关键决策(哪怕自己的决策也要填一条)
   - 更新 tasks 表:status → done/review
   - must_haves 三件套(任务创建时由庞统定义):
     - truths:用户视角的可观测行为("用户能看到回测结果"),不是实现步骤("编写回测脚本")
     - artifacts:必须存在的产出文件
     - constraints:继承的约束(如"不超过500行"、"必须用vnpy")
    ↓
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"
    ↓
庞统写 signal file: task_created
    ↓
Daemon EventBus 收到 task_created(低优先级)
  → Tick 批量处理: spawn 赵云通知 task-001
    ↓
赵云读黑板 → claim task-001 → 执行 → 写产出 → 更新 status→done
  → 写 signal file: task_completed
    ↓
Daemon EventBus 即时收到 task_completed
  → 查询 depends_on 包含 task-001 的 pending 任务 → task-002
  → task-002 的依赖全部满足 → 触发 task_ready
  → spawn 张飞执行 task-002
    ↓
(同理 task-002 done → 即时触发 task-003)

对比 polling 版:task-001 done 到 task-002 spawn 的延迟从 ≤60s 降到 ~0ms。

6.2 Agent 间协作讨论(事件驱动版)

张飞执行 task-002 时发现需要分钟线数据
    ↓
张飞写评论:"@赵云 task-002 需要分钟线数据,能帮忙下载吗?"
张飞更新任务状态 → blocked
  → 写 signal file: comment_added
    ↓
Daemon EventBus 即时收到 comment_added
  → 解析 @mention → 赵云
  → spawn 赵云(L1 消息含评论摘要)
    ↓
赵云读黑板 → 看到评论 → 下载数据 → 写产出
赵云写评论:"@张飞 数据就绪,可以继续"
  → 写 signal file: comment_added
    ↓
Daemon EventBus 即时收到 → @mention → spawn 张飞
    ↓
张飞读黑板 → 看到数据就绪 → 继续 task-002

对比 polling 版:@mention 响应从 ≤60s 降到 ≤1s。

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,必须满足:

  1. 产出已提交 - Agent 写入 outputs 表
  2. 决策已记录 - 关键决策写入 decisions 表(哪怕是自己的决策也要填一条)
  3. 司马懿审核通过 - spawn 司马懿审核产出,评论中明确写"通过"
  4. 所有问题达成一致 - 审核中提出的问题全部解决,讨论达成共识
  5. 修改已完成 - 审核问题对应的修改已写入产出

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 被排除的选项

哪怕是自己做的决策也要填一条。 目的:

  • 后续复盘时能追溯"当时为什么这样选"
  • 审核时司马懿能理解决策背后的思考
  • 经验沉淀的原始素材

9.5 分级审查矩阵

不是所有任务都值得同等深度的审查。按任务风险等级决定审查深度。

风险等级 任务类型 审查深度 参与者
高风险 量化策略、生产部署、数据删除 三阶段审查(方案审查→Output Guardrail→产出审查)+ 可选多视角对抗 庞统+司马懿+对应执行者
标准 编码、数据处理、配置修改 二阶段(Output Guardrail + 产出审查) 司马懿+执行者
低风险 调研报告、文档更新、日志查看 一阶段(Output Guardrail 机械检查) Daemon 自动
调研 技术调研、方案探索 一阶段(庞统确认方向) 庞统

风险等级:庞统创建任务时标注。默认值为 standard。庞统的 Skill 中内置规则:创建 task_type 为 strategydeploy 的任务时自动设为 high,research 类型自动设为 research。无需庞统手动判断。

设计推导:

  • v1.0 实践:每个节点都要司马懿审查,简单任务过重
  • superpowers:三阶段审查(implementer → spec reviewer → code quality reviewer),不同阶段不同深度
  • Hermes:per-task retry budget,任务级别差异化

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 中保留:

  1. SQLite WAL 模式 - 黑板数据库同样使用 WAL
  2. 结构化产出规范 - output.md frontmatter + 结论 JSON(写在黑板 outputs 表中)
  3. 观察机制 - v2.0 Report Watcher 的思路升级为 observations 表
  4. 证据原则 - 结论必须有证据(代码行号、日志、文件内容)
  5. 审核流程 - 可通过黑板评论 + 状态机实现

12. Phase 规划(v2.6)

Phase 1: 黑板基础设施

  1. SQLite blackboard.db(5 表 + WAL)
  2. blackboard.py CLI(读写操作 + signal file 写入)
  3. Daemon 核心循环(EventBus + Signal File Watcher + Tick 兜底)
  4. Session 管理(spawn + 存档 + 清理)
  5. L1 spawn message 模板

Phase 2: 事件驱动 + Agent 交互

  1. Agent 黑板操作 Skill
  2. EventBus 即时处理(task_completed → 解锁下游、@mention → spawn)
  3. 任务依赖自动推进(complete→auto-unlock)
  4. 评论 + @mention 通知链路(即时版)
  5. 健康检查(stale reclaim + zombie 检测)
  6. L2/L3 分层读取 API(blackboard.py read --level)

Phase 3: 智能化

  1. 庞统 AI 规划(读需求 → 创建任务 + 分配建议 + must_haves)
  2. Agent 自主领活(读黑板 → 匹配职责 → claim + scope_declaration)
  3. 产出验证门禁(Output Guard + Scope Guard)
  4. AI 驱动 Retry + Circuit breaker
  5. 经验沉淀(observation → knowledge base)

13. 技术选型

需求 参考系统 我们的方案 理由
共享状态 Hermes SQLite + Network-AI flock SQLite WAL + 事务 CAS 原子性 + 无外部依赖
讨论 Hermes kanban_comment comments 表 + @mention 简单追加写入,所有人可见
事件驱动 open-multi-agent EventEmitter asyncio.Queue EventBus + Signal File 零基础设施,进程内 ~0ms,跨进程 ~500ms
调度 Hermes Dispatcher 60s tick EventBus 即时 + Signal File + 30s Tick 兜底 即时响应 + 健康检查
上下文传递 GSD Wave Execution + Claude Code file ref L1 必传 + L2/L3 按需读取 信号噪声比优化
通知 Claude Code idle notification Daemon spawn + L1 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 类型任务替代