Files
sanguo_moziplus_v2/docs/design/architecture-v2.6.md
T
2026-05-15 14:01:32 +08:00

69 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设计决策:Tick核心+Inbox JSONL加速、Handoff Comment无缝接手、L1/L2/L3对应Opal-Bridge Fidelity、黑板AI Native内容规范+三层约束体系(Schema校验+Skill引导+L1截取)、依赖驱动并行/串行、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 写操作自动写 inbox JSONL Phase 1 实现 CLI 层自动完成
T2-4 Tick Loop + Inbox JSONL Watcher 实现 Phase 1 实现 Daemon 核心改造
T2-5 L2/L3 分层读取 API Phase 2 实现 blackboard.py read --level L2/L3
T2-6 仓库级上下文(Agent Context Pack Phase 3 参考 agent-chorus Context Pack:结构化仓库情报让 Agent 不需要自己探索。量化项目可抽象为项目结构/代码规范/常见陷阱
T2-7 Handoff Comment 的 Skill 引导 Phase 2 Agent Skill 中明确“结束前必须写 Handoff Comment”的行为规范
T2-8 Handoff Comment 的 Skill 解析规则 课题 4 下一个 Agent 的 Skill 中如何解析 Handoff 格式
T2-9 inbox 并发写入的竞态处理 Phase 1 验证 多 Agent 同时写 inbox 文件时的安全性(agent-chorus 同样用追回写入,无锁)
T2-10 inbox 文件的 rotate/truncate 策略 Phase 2 长期运行后文件膨胀的防范
T2-11 Tick 频率 30s vs 60s 的性能验证 Phase 1 验证 黑板查询量的实际测算
T2-12 CLI Schema 校验的 schemas/ 目录定义 Phase 1 实现 handoff/output/decide/observe 四个 schema 文件

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 表定期归档旧数据)
  • 大段分析建议写文件,黑板只存摘要+路径

三层约束体系(AI Native 结构化约束)

Skill 软引导的问题是"可看可不看",等于没有约束。数据库硬限制是传统 CRUD 思维。中间地带是 CLI 层 Schema 校验——参考 agent-chorus 的 JSON Schema 模式(每个操作都有 schemas/*.schema.json),在写入接口层校验结构。

机制 约束力 适用对象
Schema 校验 blackboard.py CLI 写入时校验 JSON Schema 强(不符合返回明确错误) 结构化操作(handoff / output / decide / observe
Skill 引导 Agent Skill 中的行为规范文本 弱(可看可不看) 非结构化操作(普通评论、讨论)
L1 截取 Daemon 构建 L1 时自动截取 代码层面,Agent 无感 传递优化

为什么这是 AI Native

  1. Schema 是可执行文档——Agent 不需要读 Skill 就能知道格式要求(CLI 错误信息会告诉它缺了什么)
  2. 错误信息是建设性的——"Handoff must include 'completed' field" 让 Agent 知道该补什么
  3. 约束的是结构,不是内容——不限制写多长,只限制必须包含哪些字段
  4. Agent 可以自主决定深度——可选字段可以不写
  5. 和 OpenAI Agent SDK 的 handoff input_type 同理——Pydantic schema 校验交接数据

Schema 定义schemas/ 目录):

// schemas/handoff.schema.json
{
  "type": "object",
  "required": ["completed", "artifacts"],
  "properties": {
    "completed": { "type": "string", "description": "完成了什么" },
    "artifacts": {
      "type": "array",
      "items": { "type": "string" },
      "description": "产出物路径列表"
    },
    "remaining": { "type": "string", "description": "未完成事项(可选)" },
    "next_steps": { "type": "string", "description": "对接手者的建议(可选)" }
  }
}

// schemas/observe.schema.json
{
  "type": "object",
  "required": ["severity", "body"],
  "properties": {
    "severity": {
      "type": "string",
      "enum": ["info", "warning", "critical"],
      "description": "info=只记录不触发; warning=下次tick触发庞统; critical=立即spawn庞统"
    },
    "body": { "type": "string", "description": "风险描述" }
  }
}

severity 枚举与处理方式对齐(与 §4.7 Guardrail 体系一致):

severity Daemon 处理 对应 Guardrail 行为
info 只记录,不触发 不介入
warning 下次 tick 统一处理 spawn 庞统(L3)判断
critical inbox 通知 → 立即 spawn 庞统 立即介入
操作 Schema 文件 必填字段 Schema 校验 CLI 附加校验
--handoff handoff.schema.json completed + artifacts 结构完整,类型正确 artifacts 中路径是否存在
--output output.schema.json summary + content_path summary 非空字符串 content_path 文件是否存在
--decide decide.schema.json decision + rationale 两个字段非空字符串
--observe observe.schema.json severity + body severity 枚举值(info/warning/critical)
--comment(普通) body 无 Schema 校验

Agent 使用方式

# 结构化操作:CLI 校验 Schema
blackboard.py comment --task task-001 --author zhangfei-dev \
  --handoff '{"completed": "分批加载实现", "artifacts": ["task-001/output.md"], "remaining": "止损逻辑"}'
# 校验失败 → 返回具体错误:"Handoff must include 'completed' field"
# 校验通过 → 写入 comments 表,body 自动格式化为结构化文本

# 普通评论:无 Schema 校验
blackboard.py comment --task task-001 --author zhangfei-dev \
  --body "@赵云 task-002 需要分钟线数据"

落地到 schema(存储层):

  • outputs 表:content_path + summary,不存文件内容
  • comments 表:body 存完整内容(不截断),handoff 类型自动格式化
  • 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 B:LLM 摘要 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. 写 Handoff Comment → 退出
   - Agent 结束前必须写一条结构化的交接评论(借鉴 agent-chorus checkpoint)
   - 内容:完成什么、产出在哪、还剩什么、建议下一步
   - 这条 comment 会出现在下一个 Agent 的 L1 消息中(最近 3 条评论),实现无缝接手
   - 示例:
     ```
     blackboard.py comment --task task-001 --author zhangfei-dev \
       --body "## Handoff\n完成:分批加载实现\n产出:task-001/output-zhangfei-v2.md\n未完成:止损逻辑分批适配\n建议下一步:关羽 review 止损逻辑"
     ```
    ↓
6. Daemon 自动清理 session
   - 通知 Daemon(inbox JSONL)
   - Daemon 检测到完成 → 继续下一步(解锁下游 / spawn review / 清理 session)

设计推导(Handoff Comment):

  • agent-chorus 的核心机制是 Standup + Conclude:Agent 开始时读 inbox,结束时广播状态
  • 映射到黑板:Standup = Agent spawn 后读黑板(L1),Conclude = Agent 结束时写 handoff comment
  • agent-chorus 的 checkpoint 广播给所有其他 Agent → 我们的 handoff comment 通过 L1 自然传递给下一个 Agent
  • 关键价值:黑板上的状态足够让 Agent B 无缝接手 Agent A 的工作--这正是 agent-chorus 解决的核心问题

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

# 写 Handoff Comment(结构化,CLI 校验 Schema
python3 ~/.sanguo_projects/sanguo_moziplus/cli/blackboard.py comment --task task-001 --author zhangfei-dev \
  --handoff '{"completed": "分批加载实现", "artifacts": ["task-001/output.md"], "remaining": "止损逻辑分批适配"}'
# --handoff 使用 schemas/handoff.schema.json 校验
# 缺必填字段 → CLI 返回具体错误,如 "Handoff must include 'completed' field"
# 校验通过 → 自动格式化为结构化评论写入 comments 表

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"
    ↓
庞统写 inbox 通知: task_created
    ↓
Daemon Tick 发现 task-001 pending + 庞统评论建议赵云
  → spawn 赵云(L1 消息含任务核心 + 庞统建议)
    ↓
赵云读黑板 → claim task-001 → 执行 → 写产出
  → 写 Handoff Comment: "完成:分钟线数据下载 | 产出:task-001/data/ | 无未完成事项"
  → 更新 status→done → 通知 Daemon(inbox JSONL)
    ↓
Daemon ~1s 内收到 inbox 通知 → mini-tick:
  → 查询 depends_on 包含 task-001 的 pending 任务 → task-002
  → task-002 的依赖全部满足 → spawn 张飞(L1 消息含赵云的 handoff 摘要)
    ↓
(同理 task-002 done → 即时触发 task-003)

对比 polling 版:task-001 done 到 task-002 spawn 的延迟从 ≤60s 降到 ≤1s。张飞的 L1 消息中包含赵云的 Handoff Comment,无需额外查询即可无缝接手。

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

张飞执行 task-002 时发现需要分钟线数据
    ↓
张飞写评论:"@赵云 task-002 需要分钟线数据,能帮忙下载吗?"
张飞更新任务状态 → blocked
  → 通知 Daemon(inbox JSONL)
    ↓
Daemon ~1s 内收到 inbox 通知 → mini-tick:
  → 解析 @mention → 赵云
  → spawn 赵云(L1 消息含评论摘要)
    ↓
赵云读黑板 → 看到评论 → 下载数据 → 写产出
赵云写评论:"@张飞 数据就绪,可以继续" + 写产出
  → 通知 Daemon(inbox JSONL)
    ↓
Daemon 收到通知 → @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(读写操作 + inbox JSONL 通知)
  3. Daemon 核心循环(Tick 30s + Inbox 检查 + 启动全量扫描)
  4. Session 管理(spawn + 存档 + 清理)
  5. L1 spawn message 模板

Phase 2: 事件驱动 + Agent 交互

  1. Agent 黑板操作 Skill(含 Handoff Comment 规范)
  2. Inbox JSONL 即时通知(task_completed → 解锁下游、@mention → spawn
  3. 任务依赖自动推进(complete→auto-unlock
  4. 评论 + @mention 通知链路(Inbox 加速版)
  5. 健康检查(stale reclaim + zombie 检测)
  6. L2/L3 分层读取 APIblackboard.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 + agent-chorus JSONL inbox Tick 核心 + Inbox JSONL 加速 + 启动全量扫描 Tick 兜底可靠,inbox 加速即时响应,零新依赖
调度 Hermes Dispatcher 60s tick Tick 30s + Inbox JSONL 加速 + 启动全量扫描 Tick 可靠 + inbox 即时
上下文传递 GSD Wave Execution + Claude Code file ref + Opal-Bridge Fidelity L1 必传 + L2/L3 按需读取 + Handoff Comment 信号噪声比优化 + 无缝接手
通知 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 类型任务替代