Skill: ankechenlab-node/synapse-brain (version 2.0.1)

--- name: synapse-brain description: > Synapse Brain — OpenClaw 持久调度 Agent。 基于 Managed Agents 架构，提供跨 Session 的任务管理、子代理调度、 状态持久化和知识互操作能力。是 synapse-code 和 synapse-wiki 的调度核心。 当用户提到任务调度、跨会话管理、多 Agent 协作、长期项目跟踪时使用此技能。 version: 2.0.1 date: 2026-04-11 user-invocable: true metadata: openclaw: emoji: "🧬" requires: bins: ["python3"] python_min: "3.10" install: [] homepage: "https://github.com/ankechenlab-node/synapse-brain" tags: [multi-agent, orchestrator, session-management, task-routing, brain-agent] --- # Synapse Brain Skill **Synapse Brain = 持久调度 + 跨会话状态 + 子代理编排** 核心理念：AI Agent 应该是持久的"大脑"，而非每次从零开始的无状态会话。 | | 传统 Agent | Synapse Brain | |--|------------|---------------| | **会话** | 每次从零开始 | 状态持久化，醒来即恢复 | | **调度** | 手动调用子代理 | 自动路由、分配、追踪 | | **记忆** | 随会话消失 | state.json 持久化 | | **协作** | 独立工作 | 调度 code/wiki 等 Skills | --- ## 🚦 快速决策：我该用什么？ ``` 你想做什么？ │ ├─ 启动/恢复项目任务 → init（初始化 session） │ └─ 例："继续做用户系统"、"开始新项目 X" │ ├─ 分配子任务给子代理 → dispatch（调度执行） │ └─ 例："并行开发登录和注册"、"让 dev 实现导出" │ ├─ 查询任务状态 → status（状态查询） │ └─ 例："现在进度如何？"、"昨天那个任务完成了没" │ └─ 保存/恢复会话 → save/restore（状态管理） └─ 例："保存当前进度"、"恢复上次会话" ``` --- ## 📋 命令速查卡片 | 命令 | 用途 | 示例 | |------|------|------| | `/synapse-brain init` | 初始化/恢复项目 session | `/synapse-brain init my-project "用户系统"` | | `/synapse-brain dispatch` | 分发任务到子代理 | `/synapse-brain dispatch "并行开发 A 和 B"` | | `/synapse-brain status` | 查询任务/子代理状态 | `/synapse-brain status my-project` | | `/synapse-brain save` | 保存当前会话状态 | `/synapse-brain save` | | `/synapse-brain restore` | 恢复上次会话 | `/synapse-brain restore` | --- ## Brain Agent 架构 ``` 用户请求 ↓ ┌─────────────────────────────┐ │ Synapse Brain (持久调度) │ │ │ │ 1. Session Restore │ ← state.json 恢复上下文 │ 2. Intent Classification │ ← 识别意图类型 │ 3. Task Routing │ ← 路由到合适 Skill/Agent │ 4. Subagent Dispatch │ ← 调用 OpenClaw 子代理 │ 5. Result Aggregation │ ← 汇总结果 │ 6. State Persist │ ← 保存状态到 state.json └─────────────┬───────────────┘ ↓ ┌─────────┼─────────┐ ↓ ↓ ↓ synapse- synapse- 专业子代理 code wiki (dev/qa/arch) ``` --- ## Session 持久化 Brain 通过 `state.json` 维护跨会话状态： ```json { "session_id": "sess_20260410_001", "project": "my-project", "created_at": "2026-04-10T12:00:00Z", "updated_at": "2026-04-10T14:30:00Z", "tasks": [ { "id": "task-001", "title": "用户登录功能", "status": "completed", "skill": "synapse-code", "mode": "lite", "agents_used": ["req-analyst", "developer", "qa-engineer"] }, { "id": "task-002", "title": "用户注册功能", "status": "in_progress", "skill": "synapse-code", "mode": "lite", "stage": "DEV" } ], "subagents": { "active": 1, "completed": 3, "failed": 0 }, "knowledge": { "wiki_initialized": true, "wiki_root": "~/my-project/wiki", "last_ingest": "2026-04-10T13:00:00Z" } } ``` --- ## 子代理调度策略 Brain 根据任务特征自动选择调度策略： | 策略 | 触发条件 | 行为 | |------|---------|------| | **sequential** | 任务有依赖关系 | 依次执行子代理 | | **parallel** | 任务独立可并行 | 同时派发最多 8 个子代理 | | **pipeline** | 标准开发流程 | REQ→ARCH→DEV→INT→QA→DEPLOY | | **standalone** | 简单任务 | Brain 直接完成，不调子代理 | --- ## 与 synapse-code 互操作 ```bash # Brain 调度 code 执行开发任务 /synapse-brain dispatch "实现用户登录" --skill synapse-code --mode lite # 等价于 /synapse-code run my-project "实现用户登录" --mode lite ``` Brain 会： 1. 自动记录任务到 session state 2. 调用 synapse-code 执行 3. 接收结果并更新任务状态 4. 触发 wiki 知识沉淀 --- ## 与 synapse-wiki 互操作 ```bash # Brain 查询已有知识 /synapse-brain query "RAG 是什么？" --skill synapse-wiki # Brain 保存新资料 /synapse-brain ingest ~/raw/article.md --skill synapse-wiki ``` Brain 会： 1. 检查 wiki 是否初始化 2. 调用 synapse-wiki 对应命令 3. 更新知识索引到 session state --- ## Scripts | 脚本 | 用途 | |------|------| | `scripts/state_manager.py` | Session 状态持久化（CRUD） | | `scripts/task_router.py` | 意图识别 + 任务路由 | | `scripts/session_init.py` | 新 session 初始化 | | `scripts/subagent_dispatch.py` | 子代理并行调度 | --- ## 安装 ```bash # 方式 1: 从源码安装（推荐） git clone https://github.com/ankechenlab-node/synapse-brain.git cd synapse-brain ./install.sh # 方式 2: OpenClaw (从 ClawHub) npx clawhub install synapse-brain # 方式 3: 手动复制 cp -r synapse-brain ~/.openclaw/skills/ ``` ## 配置 Brain 需要与 synapse-code 和 synapse-wiki 配合使用： ```json { "brain": { "state_dir": "~/.openclaw/brain-state", "auto_save": true, "auto_save_interval": 300, "skills": { "code": "synapse-code", "wiki": "synapse-wiki" } } } ``` --- ## 使用场景 - 🧠 **长期项目管理** — 跨多个 Session 追踪进度 - 🔄 **复杂任务编排** — 自动调度多个子代理协作 - 📊 **状态可视化** — 随时查询项目进展 - 🤝 **Skill 互操作** — code 和 wiki 的统一入口 - 💾 **会话持久化** — 每次醒来即恢复上下文

path: DEVELOPMENT_JOURNAL.md
content: # Synapse Brain 开发日记

## 项目起源

Synapse Brain 始于 2026-04-10，是 Synapse Skills v2.0.0 架构升级的核心组件。

基于 OpenClaw Managed Agents 架构，解决了一个根本问题：AI Agent 每次会话从零开始，无法追踪长期项目进度。Brain 通过 `state.json` 持久化会话状态，自动路由用户意图到正确的 Skill，并协调多个子代理并行工作。

---

## 开发阶段

### Phase 1: 架构设计
- 设计 Brain/Hands 架构
- 定义 state.json 持久化格式
- 确定 4 种运行模式（standalone/lite/full/parallel）

### Phase 2: 核心脚本开发
- state_manager.py — Session 状态持久化（CRUD + archive + list）
- task_router.py — 意图识别 + 任务路由（6 种意图分类）
- session_init.py — Session 初始化 / 恢复
- subagent_dispatch.py — 子代理并行调度

### Phase 3: Agent 定义
- SOUL.md — Brain Agent 角色定义、决策逻辑、输出格式
- SKILL.md — 技能接口文档、命令速查、架构说明

### Phase 4: 发布准备
- 审计并修复 YAML frontmatter 格式
- 添加 LICENSE (MIT)、README.md、.gitignore
- 修正安装指引（避免 bootstrap 问题）
- 清理 __pycache__ 等构建产物
- 模拟测试验证：意图分类 8/8, CRUD 全部通过

---

## 关键决策

### 1. Brain 定位
Brain 不是执行者，是指挥者。不直接写代码或管理知识，而是调度 synapse-code 和 synapse-wiki 完成工作。

### 2. 状态存储
选择 JSON 文件而非数据库，理由：
- 轻量，无需额外依赖
- 人类可读，便于调试
- 项目级隔离，每个 project 独立 state.json

### 3. 意图路由
基于关键词匹配 + 优先级 + anti-keywords 机制，而非 ML 模型。理由：
- 零依赖，立即可用
- 可解释，易调试
- v2.1.0 可升级为 ML 增强

### 4. 个人路径清除
所有硬编码的 `/Users/leo/` 路径统一替换为 `~/` 或相对路径，确保公开发布安全。

---

## 测试报告

| 模块 | 测试项 | 结果 |
|------|--------|------|
| task_router | 8 种意图分类 | 8/8 通过 |
| state_manager | CRUD + list + archive | 全部通过 |
| session_init | new / resume / auto | 全部通过 |
| subagent_dispatch | dispatch / complete / report | 全部通过 |
| Python 语法 | 4 个脚本 | 全部通过 |
| Shell 语法 | install.sh | 通过 |

---

## 经验教训

### 有效做法
- 审计流程自动化（三个 skills 并行检查）
- 模拟测试先于推送
- 统一 GitHub org 命名

### 踩过的坑
1. **YAML frontmatter 格式** — JSON 流语法在某些解析器下不支持，改为纯 YAML
2. **SOUL.md log.md 引用** — 文档引用了不存在的文件，改为 state.json 内部 log 字段
3. **远程 URL 混乱** — 三个 repo 的 remote origin 曾互相指向错误地址，需逐个确认

---

## 版本历史

| 版本 | 日期 | 说明 |
|------|------|------|
| v2.0.0 | 2026-04-10 | 初始发布 |

---

## 未来规划

### v2.0.1
- task_router ML 增强 — 基于历史路由改进分类准确率
- 子代理自动重试 — 失败任务智能重试
- 进度通知 — Telegram/飞书推送任务状态

### v2.0.2
- synapse-design — 设计创作 Skill
- synapse-analytics — 数据分析 Skill
- Brain Skill Marketplace
- 跨项目知识图谱


path: README.md
content: # Synapse Brain

**OpenClaw 持久调度 Agent — 跨会话任务管理 + 多 Agent 编排**

[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
[![Version](https://img.shields.io/badge/version-2.0.0-green.svg)](SKILL.md)

---

## 是什么

Synapse Brain 是基于 OpenClaw Managed Agents 架构的持久调度核心。它解决了一个根本问题：

> AI Agent 每次会话都从零开始，无法追踪长期项目进度。

Brain 通过 `state.json` 持久化会话状态，自动路由用户意图到正确的 Skill（synapse-code 或 synapse-wiki），并协调多个子代理并行工作。

## 架构

```
用户请求 → Synapse Brain
            ├── 意图识别 (task_router.py)
            ├── Session 恢复 (state_manager.py)
            ├── 路由到 Skill (code / wiki)
            ├── 子代理调度 (subagent_dispatch.py)
            └── 状态持久化 (state.json)
```

## 安装

```bash
# 克隆仓库
git clone https://github.com/ankechenlab-node/synapse-brain.git
cd synapse-brain

# 运行安装
./install.sh
```

## 快速开始

```bash
# 初始化项目 session
/synapse-brain init my-project "用户系统开发"

# 查看当前状态
/synapse-brain status my-project

# 通过 Brain 调度开发任务（需要 synapse-code 已安装）
/synapse-brain dispatch "实现登录功能" --skill synapse-code --mode lite

# 保存当前进度
/synapse-brain save
```

## 与 Synapse 生态协作

- **[synapse-code](https://github.com/ankechenlab-node/synapse-code)** — 代码开发工作流引擎，被 Brain 调度执行开发任务
- **[synapse-wiki](https://github.com/ankechenlab-node/synapse-wiki)** — 知识库管理系统，被 Brain 调度管理知识

三者配合形成完整闭环：开发 → 知识沉淀 → 查询复用。

## 脚本

| 脚本 | 用途 |
|------|------|
| `scripts/state_manager.py` | Session 状态持久化（CRUD） |
| `scripts/task_router.py` | 意图识别 + 任务路由 |
| `scripts/session_init.py` | Session 初始化 / 恢复 |
| `scripts/subagent_dispatch.py` | 子代理并行调度 |

## 配置

安装后自动生成 `config.json`，默认配置：

```json
{
  "brain": {
    "state_dir": "~/.openclaw/brain-state",
    "auto_save": true,
    "auto_save_interval": 300,
    "skills": {
      "code": "synapse-code",
      "wiki": "synapse-wiki"
    }
  }
}
```

## 许可证

MIT License — 详见 [LICENSE](LICENSE)


path: SOUL.md
content: # SOUL.md — Synapse Brain (持久调度 Agent)

## 🎯 角色

你是 Synapse Brain — OpenClaw 的持久调度核心。你不是执行者，你是指挥者。

**你不是**：代码编辑器、文案写手、测试工程师。
**你是**：任务理解者、资源调度者、状态维护者、结果交付者。

---

## 🔑 核心职责

1. **Session 管理** — 维护跨会话状态，每次醒来立即恢复上下文
2. **意图识别** — 理解用户想要什么，而不是字面说了什么
3. **任务路由** — 将任务分发到正确的 Skill/Agent
4. **子代理调度** — 管理 1-8 个子代理的并行执行
5. **状态持久化** — 保存进度到 state.json，确保不丢失
6. **结果交付** — 汇总子代理产出，以用户友好的方式呈现

---

## 🧠 决策逻辑

### Session 启动

```
1. 检查 state.json 是否存在
   ├─ 是 → 恢复上次会话，展示当前状态
   └─ 否 → 这是新会话，初始化
2. 检查是否有活跃子代理
   ├─ 是 → 检查状态，报告进度
   └─ 否 → 等待用户指令
```

### 任务路由

```
用户输入
    ↓
是简单问题/查询？
├─ 是 → 直接回答（不需要子代理）
└─ 否
    ↓
涉及代码开发？
├─ 是 → 路由到 synapse-code
│        ├─ 简单 → standalone
│        ├─ 中等 → lite (REQ→DEV→QA)
│        └─ 复杂 → full (6 阶段)
└─ 否
    ↓
涉及知识管理？
├─ 是 → 路由到 synapse-wiki
│        ├─ 保存资料 → ingest
│        ├─ 查询 → query
│        └─ 整理 → lint
└─ 否
    ↓
是多任务/复杂编排？
├─ 是 → 并行调度子代理
└─ 否 → Brain 直接处理
```

---

## 📤 输出格式

### Session 恢复

```
🧠 Session 恢复: {project}
━━━━━━━━━━━━━━━━━━━━━━━━
📊 当前状态:
  - 已完成: 3 个任务
  - 进行中: 1 个任务 (DEV 阶段)
  - 子代理: 2 活跃, 5 已完成
━━━━━━━━━━━━━━━━━━━━━━━━
继续上次的工作？还是有新任务？
```

### 任务完成

```
✅ 任务完成: {task_title}
━━━━━━━━━━━━━━━━━━━━━━━━
📦 交付物:
  - 代码: src/module.py
  - 测试: tests/test_module.py (覆盖 95%)
  - 文档: .knowledge/req-001.md

📊 执行统计:
  - 模式: lite (3 阶段)
  - 子代理: req-analyst, developer, qa-engineer
  - 状态: 已保存到 state.json
━━━━━━━━━━━━━━━━━━━━━━━━
```

---

## 🤝 与 Skills 协作

### 调度 synapse-code

```markdown
你是 Brain，要调用 synapse-code 执行开发任务：
1. 将用户需求转为 synapse-code 能理解的输入
2. 选择合适的模式（standalone/lite/full）
3. 等待 synapse-code 完成
4. 接收结果，更新任务状态
5. 如果成功，触发 synapse-wiki 知识沉淀
```

### 调度 synapse-wiki

```markdown
你是 Brain，要调用 synapse-wiki 管理知识：
1. 检查 wiki 是否初始化
2. 调用对应命令（ingest/query/lint）
3. 将结果记录到 session state
```

---

## 📋 Session 状态管理规则

1. **自动保存** — 每个子代理完成后自动保存
2. **冲突处理** — 如 state.json 被其他进程修改，merge 而非覆盖
3. **清理策略** — 超过 30 天的 completed 任务归档
4. **恢复策略** — in_progress 任务标记为 interrupted，等待用户确认

---

## ⚡ 工作原则

1. **不要自己写代码** — 你的职责是调度，不是执行
2. **保存比完美重要** — 宁可保存 80% 的进度，也不要因追求 100% 而丢失一切
3. **状态对用户可见** — 用户随时可以 `/synapse-brain status` 查询进度
4. **失败要透明** — 子代理失败时，如实报告，不要隐藏
5. **简单任务直接做** — 不需要为了"改个文案"而启动完整 Pipeline

---

## 🔒 边界

- 不直接修改用户文件（通过 code/wiki skills 间接操作）
- 不删除 state.json 中的历史记录（只归档）
- 不在用户不知情的情况下启动子代理
- 不承诺子代理的执行时间（取决于任务复杂度）

---

## 🧬 Session 持久化协议

每个 Session 结束时：

1. 更新 `state.json` 中所有任务状态
2. 记录本次会话活动到 state.json 的 `log` 字段
3. 如果有 in_progress 任务，标记下次恢复点
4. 向用户报告状态已保存

---

*Built: 2026-04-10 | Synapse v2.0 — Brain/Hands Architecture*


path: install.sh
content: #!/bin/bash
# install.sh — Synapse Brain 安装脚本
#
# 用法:
#   ./install.sh
#   ./install.sh --dry-run
#   ./install.sh --uninstall

set -e

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SKILL_NAME="$(basename "$SCRIPT_DIR")"
SKILL_DEST="$HOME/.openclaw/skills/$SKILL_NAME"

BLUE='\033[0;34m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
CYAN='\033[0;36m'
BOLD='\033[1m'
NC='\033[0m'

log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
log_success() { echo -e "${GREEN}[✓]${NC} $1"; }
log_warning() { echo -e "${YELLOW}[⚠]${NC} $1"; }
log_error() { echo -e "${RED}[✗]${NC} $1"; }
log_step() { echo -e "${CYAN}[⟳]${NC} $1"; }

DRY_RUN=false
UNINSTALL=false
FORCE=false

for arg in "$@"; do
    case $arg in
        --dry-run) DRY_RUN=true ;;
        --uninstall) UNINSTALL=true ;;
        --force) FORCE=true ;;
        --help|-h)
            echo "Synapse Brain 安装脚本"
            echo "用法: $0 [--dry-run] [--uninstall] [--force]"
            exit 0 ;;
    esac
done

install_skill() {
    log_step "安装 $SKILL_NAME..."

    if [[ -d "$SKILL_DEST" ]]; then
        if [[ "$FORCE" == true ]]; then
            log_warning "强制覆盖已存在的安装..."
            rm -rf "$SKILL_DEST"
        else
            log_warning "已存在: $SKILL_DEST"
            rm -rf "$SKILL_DEST"
        fi
    fi

    if [[ "$DRY_RUN" == true ]]; then
        log_info "[DRY-RUN] Would copy: $SCRIPT_DIR → $SKILL_DEST"
        return 0
    fi

    # Create parent directory
    mkdir -p "$(dirname "$SKILL_DEST")"

    # Copy skill files
    rsync -av --exclude 'node_modules' --exclude '.git' --exclude '__pycache__' \
        "$SCRIPT_DIR/" "$SKILL_DEST/"
    log_success "文件复制完成"

    # Create brain state directory
    STATE_DIR="$HOME/.openclaw/brain-state"
    if [[ ! -d "$STATE_DIR" ]]; then
        mkdir -p "$STATE_DIR"
        log_success "创建状态目录: $STATE_DIR"
    fi

    # Create default config
    CONFIG_FILE="$SKILL_DEST/config.json"
    if [[ ! -f "$CONFIG_FILE" ]]; then
        cat > "$CONFIG_FILE" << 'EOF'
{
  "brain": {
    "state_dir": "~/.openclaw/brain-state",
    "auto_save": true,
    "auto_save_interval": 300,
    "skills": {
      "code": "synapse-code",
      "wiki": "synapse-wiki"
    }
  }
}
EOF
        log_success "创建默认配置: config.json"
    fi
}

uninstall_skill() {
    if [[ ! -d "$SKILL_DEST" ]]; then
        log_warning "未找到安装: $SKILL_DEST"
        return 0
    fi
    if [[ "$DRY_RUN" == true ]]; then
        log_info "[DRY-RUN] Would remove: $SKILL_DEST"
        return 0
    fi
    rm -rf "$SKILL_DEST"
    log_success "卸载完成"
}

post_install() {
    log_step "后安装检查..."

    if [[ -f "$SKILL_DEST/SKILL.md" ]]; then
        log_success "SKILL.md exists"
    else
        log_error "SKILL.md not found!"
        exit 1
    fi

    if [[ -f "$SKILL_DEST/SOUL.md" ]]; then
        log_success "SOUL.md exists"
    else
        log_error "SOUL.md not found!"
        exit 1
    fi

    # Verify Python scripts
    local syntax_ok=0
    local syntax_err=0
    for script in "$SKILL_DEST/scripts"/*.py; do
        if [[ -f "$script" ]]; then
            if python3 -m py_compile "$script" 2>/dev/null; then
                ((syntax_ok++))
            else
                log_warning "语法错误: $(basename "$script")"
                ((syntax_err++))
            fi
        fi
    done
    log_success "Python 脚本: $syntax_ok 正确, $syntax_err 错误"

    echo ""
    echo -e "${BOLD}Synapse Brain 安装完成!${NC}"
    echo ""
    echo "配置: $SKILL_DEST/config.json"
    echo "状态: $HOME/.openclaw/brain-state/"
    echo ""
    echo "下一步:"
    echo "  1. 确保 synapse-code 和 synapse-wiki 已安装"
    echo "  2. 编辑 config.json 配置关联 skills"
    echo "  3. 使用 /synapse-brain init my-project \"项目名\" 开始"
}

main() {
    if [[ "$UNINSTALL" == true ]]; then
        uninstall_skill
    else
        install_skill
        if [[ "$DRY_RUN" == false ]]; then
            post_install
        fi
    fi
}

main


path: references/architecture.md
content: # Synapse Brain — Architecture Reference

## Brain/Hands Architecture

This document describes the v2.0 architecture where Synapse Brain acts as the persistent orchestrator for synapse-code and synapse-wiki.

### Architecture Overview

```
┌─────────────────────────────────────────┐
│           User (Telegram/CLI)            │
└──────────────────┬──────────────────────┘
                   │
                   ▼
┌─────────────────────────────────────────┐
│         Synapse Brain (持久调度)          │
│                                         │
│  SOUL.md: Agent 角色定义                  │
│  SKILL.md: 技能接口定义                   │
│  Scripts:                                │
│    - state_manager.py (持久化)            │
│    - task_router.py (路由)               │
│    - session_init.py (会话管理)           │
│    - subagent_dispatch.py (子代理调度)    │
└────┬───────────┬──────────┬─────────────┘
     │           │          │
     ▼           ▼          ▼
┌────────┐ ┌────────┐ ┌──────────┐
│ code   │ │ wiki   │ │ 子代理    │
│ Skill  │ │ Skill  │ │ (dev/qa) │
└────────┘ └────────┘ └──────────┘
```

### State Persistence

Session state is stored in `~/.openclaw/brain-state/{project}.json`:

```json
{
  "session_id": "unique_id",
  "project": "name",
  "title": "description",
  "created_at": "ISO timestamp",
  "updated_at": "ISO timestamp",
  "tasks": [
    {
      "id": "task-001",
      "title": "task description",
      "status": "completed|in_progress|failed|interrupted|pending",
      "skill": "synapse-code|synapse-wiki",
      "mode": "standalone|lite|full|parallel",
      "stage": "REQ|ARCH|DEV|INT|QA|DEPLOY",
      "created_at": "timestamp",
      "updated_at": "timestamp"
    }
  ],
  "subagents": {
    "active": 0,
    "completed": 0,
    "failed": 0
  },
  "knowledge": {
    "wiki_initialized": false,
    "wiki_root": null,
    "last_ingest": null
  },
  "log": ["[timestamp] event description"]
}
```

### Intent Routing Pipeline

1. **Input**: User natural language
2. **Classification**: task_router.py matches keywords → intent
3. **Routing**: Intent maps to skill + command + mode
4. **Dispatch**: Brain invokes the target skill
5. **Result**: Output aggregated and state updated

### Task Statuses

| Status | Meaning |
|--------|---------|
| `pending` | Planned, not started |
| `in_progress` | Actively being worked on |
| `interrupted` | Session ended before completion |
| `completed` | Done and verified |
| `failed` | Failed with error |

### Migration from v1.x

v1.x used external `pipeline.py` for orchestration. v2.0 replaces this with:

| v1.x | v2.0 |
|------|------|
| `pipeline.py` | Brain Agent + subagents |
| `config.json` pipeline.workspace | `state.json` per project |
| External ContractBoard | Brain internal state tracking |
| Manual mode selection | Automatic intent routing |

The `legacy` mode is still supported for backward compatibility with existing `pipeline.py` installations.


path: scripts/session_init.py
content: #!/usr/bin/env python3
"""
Synapse Brain — Session Init
初始化新 Session 或恢复已有 Session。

Usage:
    python session_init.py new <project> <title>
    python session_init.py resume <project>
    python session_init.py auto <project>  # auto: resume if exists, else create
"""

import json
import os
import sys
from datetime import datetime, timezone

# Import state_manager functions
sys.path.insert(0, os.path.dirname(__file__))
from state_manager import create, status, _load, _ensure_state_dir


def resume(project: str) -> dict | None:
    """Resume an existing session."""
    state = _load(project)
    if not state:
        print(f"[brain] No session to resume for '{project}'.")
        return None

    # Check for interrupted tasks
    interrupted = [t for t in state["tasks"] if t["status"] == "in_progress"]
    if interrupted:
        print(f"[brain] Found {len(interrupted)} interrupted task(s):")
        for t in interrupted:
            print(f"  ⏸️ {t['id']}: {t['title']}")
        # Mark as waiting for user confirmation
        for t in interrupted:
            t["status"] = "interrupted"
            t["interrupted_at"] = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")

    print(f"\n🧠 Session resumed: {project}")
    status(project)
    return state


def auto_init(project: str, title: str) -> dict:
    """Auto: resume if session exists, else create new."""
    existing = _load(project)
    if existing:
        return resume(project) or create(project, title)
    return create(project, title)


def main():
    if len(sys.argv) < 2:
        print(__doc__)
        sys.exit(1)

    cmd = sys.argv[1]

    if cmd == "new":
        if len(sys.argv) < 4:
            print("Usage: session_init.py new <project> <title>")
            sys.exit(1)
        create(sys.argv[2], sys.argv[3])
    elif cmd == "resume":
        if len(sys.argv) < 3:
            print("Usage: session_init.py resume <project>")
            sys.exit(1)
        resume(sys.argv[2])
    elif cmd == "auto":
        if len(sys.argv) < 3:
            print("Usage: session_init.py auto <project> [title]")
            sys.exit(1)
        title = sys.argv[3] if len(sys.argv) > 3 else "Untitled"
        auto_init(sys.argv[2], title)
    else:
        print(f"Unknown command: {cmd}")
        sys.exit(1)


if __name__ == "__main__":
    main()


path: scripts/state_manager.py
content: #!/usr/bin/env python3
"""
Synapse Brain — Session State Manager
持久化 Session 状态，支持跨会话恢复。

Usage:
    python state_manager.py create <project> <title>
    python state_manager.py update <project> <task_id> <status> [data]
    python state_manager.py status <project>
    python state_manager.py list
    python state_manager.py archive <project>
"""

import json
import os
import sys
from datetime import datetime, timezone
from pathlib import Path


STATE_DIR = os.path.expanduser("~/.openclaw/brain-state")


def _state_path(project: str) -> str:
    return os.path.join(STATE_DIR, f"{project}.json")


def _ensure_state_dir():
    Path(STATE_DIR).mkdir(parents=True, exist_ok=True)


def _now() -> str:
    return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")


def create(project: str, title: str):
    """Create a new session state."""
    _ensure_state_dir()
    path = _state_path(project)
    if os.path.exists(path):
        print(f"[brain] Session already exists for '{project}'. Use 'update' to modify.")
        return _load(project)

    state = {
        "session_id": f"sess_{datetime.now(timezone.utc).strftime('%Y%m%d_%H%M%S')}",
        "project": project,
        "title": title,
        "created_at": _now(),
        "updated_at": _now(),
        "tasks": [],
        "subagents": {
            "active": 0,
            "completed": 0,
            "failed": 0
        },
        "knowledge": {
            "wiki_initialized": False,
            "wiki_root": None,
            "last_ingest": None
        },
        "log": []
    }

    with open(path, "w") as f:
        json.dump(state, f, indent=2)

    print(f"[brain] Session created: {project} — {title}")
    return state


def _load(project: str) -> dict | None:
    path = _state_path(project)
    if not os.path.exists(path):
        print(f"[brain] No session found for '{project}'.")
        return None
    with open(path) as f:
        return json.load(f)


def _save(project: str, state: dict):
    _ensure_state_dir()
    state["updated_at"] = _now()
    with open(_state_path(project), "w") as f:
        json.dump(state, f, indent=2)
    print(f"[brain] State saved: {project}")


def update(project: str, task_id: str, status: str, data: str = None):
    """Update task status in session."""
    state = _load(project)
    if not state:
        return

    # Find task
    task = None
    for t in state["tasks"]:
        if t["id"] == task_id:
            task = t
            break

    if not task:
        # Create new task
        task = {
            "id": task_id,
            "title": task_id,
            "status": status,
            "created_at": _now()
        }
        state["tasks"].append(task)

    task["status"] = status
    task["updated_at"] = _now()

    if data:
        try:
            task_data = json.loads(data)
            task.update(task_data)
        except json.JSONDecodeError:
            task["note"] = data

    # Log
    state["log"].append(f"[{_now()}] Task {task_id} -> {status}")

    _save(project, state)


def status(project: str):
    """Show session status."""
    state = _load(project)
    if not state:
        return

    tasks = state["tasks"]
    completed = sum(1 for t in tasks if t["status"] == "completed")
    in_progress = sum(1 for t in tasks if t["status"] == "in_progress")
    failed = sum(1 for t in tasks if t["status"] == "failed")

    print(f"\n🧠 Session: {state['project']} — {state['title']}")
    print(f"Created: {state['created_at']} | Updated: {state['updated_at']}")
    print(f"\n📊 Tasks: {len(tasks)} total | {completed} done | {in_progress} active | {failed} failed")
    print(f"🤖 Subagents: {state['subagents']['completed']} completed | {state['subagents']['active']} active | {state['subagents']['failed']} failed")

    if tasks:
        print(f"\n📋 Task List:")
        for t in tasks[-10:]:
            icon = {"completed": "✅", "in_progress": "🔵", "failed": "❌", "pending": "⏳"}.get(t["status"], "⚪")
            print(f"  {icon} {t['id']}: {t['title']} ({t['status']})")
    print()


def list_sessions():
    """List all sessions."""
    _ensure_state_dir()
    files = list(Path(STATE_DIR).glob("*.json"))
    if not files:
        print("[brain] No sessions found.")
        return
    print(f"\n🧠 Sessions ({len(files)}):")
    for f in sorted(files):
        with open(f) as fh:
            state = json.load(fh)
        print(f"  - {state['project']}: {state['title']} ({state['updated_at']})")
    print()


def archive(project: str):
    """Archive completed tasks older than 30 days."""
    state = _load(project)
    if not state:
        return

    archived = 0
    remaining = []
    for t in state["tasks"]:
        if t["status"] == "completed" and t.get("updated_at", "") < _now():
            # Simple archive: move to archived list
            archived += 1
        else:
            remaining.append(t)

    state["archived_count"] = state.get("archived_count", 0) + archived
    state["tasks"] = remaining
    _save(project, state)
    print(f"[brain] Archived {archived} completed tasks.")


def main():
    if len(sys.argv) < 2:
        print(__doc__)
        sys.exit(1)

    cmd = sys.argv[1]

    if cmd == "create":
        if len(sys.argv) < 4:
            print("Usage: state_manager.py create <project> <title>")
            sys.exit(1)
        create(sys.argv[2], sys.argv[3])
    elif cmd == "update":
        if len(sys.argv) < 5:
            print("Usage: state_manager.py update <project> <task_id> <status> [data]")
            sys.exit(1)
        data = sys.argv[5] if len(sys.argv) > 5 else None
        update(sys.argv[2], sys.argv[3], sys.argv[4], data)
    elif cmd == "status":
        if len(sys.argv) < 3:
            print("Usage: state_manager.py status <project>")
            sys.exit(1)
        status(sys.argv[2])
    elif cmd == "list":
        list_sessions()
    elif cmd == "archive":
        if len(sys.argv) < 3:
            print("Usage: state_manager.py archive <project>")
            sys.exit(1)
        archive(sys.argv[2])
    else:
        print(f"Unknown command: {cmd}")
        print(__doc__)
        sys.exit(1)


if __name__ == "__main__":
    main()


path: scripts/subagent_dispatch.py
content: #!/usr/bin/env python3
"""
Synapse Brain — Subagent Dispatch (v2.0.1)
子代理并行调度管理器，支持自动重试。

Usage:
    python subagent_dispatch.py dispatch <project> <task_id> --mode parallel --agents 3
    python subagent_dispatch.py report <project>
    python subagent_dispatch.py retry <project> <subagent_id>
"""

import json
import os
import sys
import time
from datetime import datetime, timezone

sys.path.insert(0, os.path.dirname(__file__))
from state_manager import _load, _save

MAX_ATTEMPTS = 3
RETRY_BASE_DELAY = 5  # seconds


def dispatch(project: str, task_id: str, mode: str = "sequential", agents: int = 1):
    """Dispatch subagents for a task."""
    state = _load(project)
    if not state:
        print(f"[brain] No session for '{project}'. Initialize first.")
        return

    # Validate agent count (OpenClaw max: 8)
    max_agents = 8
    agents = min(agents, max_agents)

    # Create subagent records
    subagents = []
    for i in range(agents):
        subagents.append({
            "id": f"sub-{task_id}-{i+1}",
            "task_id": task_id,
            "status": "pending",
            "attempts": 0,
            "max_attempts": MAX_ATTEMPTS,
            "created_at": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
        })

    if "subagents_list" not in state:
        state["subagents_list"] = []
    state["subagents_list"].extend(subagents)

    # Update state
    state["subagents"]["active"] += agents
    state["log"].append(
        f"[{datetime.now(timezone.utc).strftime('%Y-%m-%dT%H:%M:%SZ')}] "
        f"Dispatched {agents} subagents for {task_id} (mode={mode})"
    )

    _save(project, state)

    print(f"[brain] Dispatched {agents} subagent(s) for task '{task_id}'")
    print(f"  Mode: {mode}")
    print(f"  Active: {state['subagents']['active']}")

    return subagents


def complete(project: str, subagent_id: str, success: bool = True, result: str = None):
    """Mark a subagent as completed or trigger retry on failure."""
    state = _load(project)
    if not state:
        return

    # Find the subagent
    subagent = None
    for sa in state.get("subagents_list", []):
        if sa["id"] == subagent_id:
            subagent = sa
            break

    if not subagent:
        print(f"[brain] Subagent '{subagent_id}' not found")
        return

    if success:
        subagent["status"] = "completed"
        subagent["completed_at"] = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
        subagent["attempts"] = max(subagent.get("attempts", 0) + 1, 1)
        state["subagents"]["completed"] += 1
        state["subagents"]["active"] = max(0, state["subagents"]["active"] - 1)
        state["log"].append(
            f"[{datetime.now(timezone.utc).strftime('%Y-%m-%dT%H:%M:%SZ')}] "
            f"Subagent {subagent_id} -> completed"
        )
        _save(project, state)
        print(f"[brain] Subagent {subagent_id} completed")
    else:
        attempts = subagent.get("attempts", 0) + 1  # count this attempt
        max_attempts = subagent.get("max_attempts", MAX_ATTEMPTS)
        subagent["attempts"] = attempts

        if attempts < max_attempts:
            # Retry with exponential backoff
            delay = RETRY_BASE_DELAY * (2 ** (attempts - 1))
            subagent["status"] = "retrying"
            subagent["last_failure"] = result or "Unknown error"
            subagent["last_failure_at"] = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
            subagent["next_retry_at"] = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
            state["log"].append(
                f"[{datetime.now(timezone.utc).strftime('%Y-%m-%dT%H:%M:%SZ')}] "
                f"Subagent {subagent_id} failed (attempt {attempts}/{max_attempts}), "
                f"retrying in {delay}s: {result or 'Unknown'}"
            )
            _save(project, state)
            print(f"[brain] Subagent {subagent_id} failed, retry {attempts}/{max_attempts} in {delay}s")
            return {"retrying": True, "attempt": attempts, "delay": delay}
        else:
            # Max attempts exceeded
            subagent["status"] = "failed"
            subagent["final_error"] = result or "Unknown error"
            subagent["failed_at"] = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
            state["subagents"]["failed"] += 1
            state["subagents"]["active"] = max(0, state["subagents"]["active"] - 1)
            state["log"].append(
                f"[{datetime.now(timezone.utc).strftime('%Y-%m-%dT%H:%M:%SZ')}] "
                f"Subagent {subagent_id} -> failed (max attempts exceeded): {result or 'Unknown'}"
            )
            _save(project, state)
            print(f"[brain] Subagent {subagent_id} failed after {max_attempts} attempts")
            return {"retrying": False, "attempts": max_attempts}


def retry_subagent(project: str, subagent_id: str):
    """Manually retry a failed subagent."""
    state = _load(project)
    if not state:
        return

    subagent = None
    for sa in state.get("subagents_list", []):
        if sa["id"] == subagent_id:
            subagent = sa
            break

    if not subagent:
        print(f"[brain] Subagent '{subagent_id}' not found")
        return

    if subagent["status"] not in ("failed", "completed"):
        print(f"[brain] Subagent '{subagent_id}' is {subagent['status']}, not retryable")
        return

    subagent["status"] = "pending"
    subagent["attempts"] = 0
    subagent.pop("final_error", None)
    subagent.pop("last_failure", None)
    state["subagents"]["active"] += 1
    state["subagents"]["failed"] = max(0, state["subagents"]["failed"] - 1)
    state["log"].append(
        f"[{datetime.now(timezone.utc).strftime('%Y-%m-%dT%H:%M:%SZ')}] "
        f"Subagent {subagent_id} manually retried"
    )
    _save(project, state)
    print(f"[brain] Subagent {subagent_id} reset for retry")


def report(project: str):
    """Generate subagent execution report."""
    state = _load(project)
    if not state:
        return

    sa = state["subagents"]
    subagents_list = state.get("subagents_list", [])
    total = len(subagents_list)

    print(f"\n Subagent Report: {project}")
    print(f"  Active:    {sa['active']}")
    print(f"  Completed: {sa['completed']}")
    print(f"  Failed:    {sa['failed']}")
    print(f"  Total:     {total}")

    # Show retry details for failed subagents
    failed = [s for s in subagents_list if s["status"] == "failed"]
    if failed:
        print(f"\n  Failed subagents (max retries exceeded):")
        for s in failed:
            error = s.get("final_error", s.get("last_failure", "Unknown"))
            print(f"    - {s['id']}: {error}")
    print()


def main():
    if len(sys.argv) < 2:
        print(__doc__)
        sys.exit(1)

    cmd = sys.argv[1]

    if cmd == "dispatch":
        if len(sys.argv) < 4:
            print("Usage: subagent_dispatch.py dispatch <project> <task_id> [--mode MODE] [--agents N]")
            sys.exit(1)
        mode = "sequential"
        agents = 1
        i = 4
        while i < len(sys.argv):
            if sys.argv[i] == "--mode" and i + 1 < len(sys.argv):
                mode = sys.argv[i + 1]
                i += 2
            elif sys.argv[i] == "--agents" and i + 1 < len(sys.argv):
                agents = int(sys.argv[i + 1])
                i += 2
            else:
                i += 1
        dispatch(sys.argv[2], sys.argv[3], mode, agents)
    elif cmd == "complete":
        if len(sys.argv) < 4:
            print("Usage: subagent_dispatch.py complete <project> <subagent_id> [--success BOOL] [--result MSG]")
            sys.exit(1)
        success = True
        result = None
        i = 4
        while i < len(sys.argv):
            if sys.argv[i] == "--success" and i + 1 < len(sys.argv):
                success = sys.argv[i + 1].lower() in ("true", "1", "yes")
                i += 2
            elif sys.argv[i] == "--result" and i + 1 < len(sys.argv):
                result = sys.argv[i + 1]
                i += 2
            else:
                i += 1
        complete(sys.argv[2], sys.argv[3], success, result)
    elif cmd == "retry":
        if len(sys.argv) < 4:
            print("Usage: subagent_dispatch.py retry <project> <subagent_id>")
            sys.exit(1)
        retry_subagent(sys.argv[2], sys.argv[3])
    elif cmd == "report":
        if len(sys.argv) < 3:
            print("Usage: subagent_dispatch.py report <project>")
            sys.exit(1)
        report(sys.argv[2])
    else:
        print(f"Unknown command: {cmd}")
        sys.exit(1)


if __name__ == "__main__":
    main()


path: scripts/task_router.py
content: #!/usr/bin/env python3
"""
Synapse Brain — Task Router (v2.0.1)
意图识别 + 任务路由，混合关键词 + ML 分类。

ML 方法: TF-IDF + 余弦相似度（轻量，无需大模型）
可选升级: 设置 SYNPASE_ROUTER_MODEL=sentence-transformers 启用 embedding 模型

Usage:
    python task_router.py "实现用户登录功能"
    python task_router.py "保存这篇关于 RAG 的文章" --wiki
    python task_router.py "同时开发 A、B、C 三个模块"
"""

import json
import os
import sys

# =============================================================================
# Keyword Rules (unchanged from v2.0.0 — fast path)
# =============================================================================

INTENT_RULES = [
    {
        "intent": "development",
        "keywords": ["代码", "开发", "实现", "功能", "bug", "修复", "接口", "API", "编程",
                     "写代码", "build", "implement", "feature", "fix", "重构", "优化"],
        "skill": "synapse-code",
        "default_mode": "lite",
        "priority": 1
    },
    {
        "intent": "knowledge_ingest",
        "keywords": ["保存", "存储", "资料", "文章", "文档", "知识", "ingest", "保存起来",
                     "记下来", "记录", "收藏", "save", "article"],
        "skill": "synapse-wiki",
        "command": "ingest",
        "priority": 1
    },
    {
        "intent": "status",
        "keywords": ["进度", "状态", "status", "完成没", "怎么样了", "进展", "check"],
        "skill": "synapse-brain",
        "command": "status",
        "priority": 2,
        "anti_keywords": ["是什么", "什么是", "什么意思", "解释", "what", "how"]
    },
    {
        "intent": "knowledge_query",
        "keywords": ["是什么", "怎么", "如何", "查询", "知识", "query", "了解", "什么是",
                     "什么意思", "what", "how", "解释"],
        "skill": "synapse-wiki",
        "command": "query",
        "priority": 1
    },
    {
        "intent": "knowledge_lint",
        "keywords": ["健康", "检查", "死链", "孤立", "lint", "整理"],
        "skill": "synapse-wiki",
        "command": "lint",
        "priority": 1
    },
    {
        "intent": "session",
        "keywords": ["恢复", "继续", "session", "restore", "上次", "保存会话"],
        "skill": "synapse-brain",
        "command": "session",
        "priority": 1
    }
]

SCENARIO_KEYWORDS = {
    "writing": ["文章", "文案", "公众号", "邮件", "稿", "写篇", "新闻稿"],
    "design": ["设计", "logo", "UI", "图", "视觉", "海报", "排版"],
    "analytics": ["数据", "分析", "报表", "图表", "可视化", "销售分析"],
    "translation": ["翻译", "译成", "本地化", "英文版", "中文版"],
    "research": ["调研", "研究", "学习", "了解", "进展", "文献"],
    "development": ["代码", "开发", "实现", "功能", "bug", "接口", "API", "编程"]
}

PARALLEL_KEYWORDS = ["同时", "并行", "一起", "多个", "parallel", "并发", "分别"]

# =============================================================================
# ML Intent Classifier (TF-IDF + Cosine Similarity)
# =============================================================================

# Training examples: (text, intent)
_TRAINING_EXAMPLES = [
    # development
    ("帮我写一个用户注册功能", "development"),
    ("实现 REST API 接口", "development"),
    ("修复这个 bug，登录页面报错", "development"),
    ("重构数据库连接池代码", "development"),
    ("优化查询性能", "development"),
    ("build a todo list app", "development"),
    ("implement user authentication", "development"),
    ("开发一个电商后台管理系统", "development"),
    ("写一个 Python 爬虫", "development"),
    ("实现微信支付接口", "development"),
    # knowledge_ingest
    ("保存这篇技术文章", "knowledge_ingest"),
    ("把这份文档存到知识库", "knowledge_ingest"),
    ("ingest this research paper", "knowledge_ingest"),
    ("记录这次会议的内容", "knowledge_ingest"),
    ("收藏这个设计方案", "knowledge_ingest"),
    # status
    ("项目进度怎么样", "status"),
    ("check the current status", "status"),
    ("开发完成没", "status"),
    ("现在进展如何", "status"),
    # knowledge_query
    ("什么是 RAG", "knowledge_query"),
    ("怎么部署 Docker 容器", "knowledge_query"),
    ("RAG 是什么意思", "knowledge_query"),
    ("解释一下微服务架构", "knowledge_query"),
    ("how does OAuth work", "knowledge_query"),
    ("knowledge about event-driven architecture", "knowledge_query"),
    # knowledge_lint
    ("检查知识库健康", "knowledge_lint"),
    ("整理一下 wiki，有死链吗", "knowledge_lint"),
    ("lint the knowledge base", "knowledge_lint"),
    # session
    ("恢复上次的 session", "session"),
    ("继续之前的工作", "session"),
    ("restore my previous session", "session"),
]

# Cache for TF-IDF vectors
_classifier_cache = {}


def _get_classifier():
    """Lazy-load TF-IDF classifier. Returns (vectorizer, tfidf_matrix, labels)."""
    cache_key = "tfidf"
    if cache_key in _classifier_cache:
        return _classifier_cache[cache_key]

    try:
        from sklearn.feature_extraction.text import TfidfVectorizer
        from sklearn.metrics.pairwise import cosine_similarity
    except ImportError:
        return None

    texts = [ex[0] for ex in _TRAINING_EXAMPLES]
    labels = [ex[1] for ex in _TRAINING_EXAMPLES]

    vectorizer = TfidfVectorizer(analyzer="char", ngram_range=(2, 4))
    tfidf_matrix = vectorizer.fit_transform(texts)

    result = (vectorizer, tfidf_matrix, labels, cosine_similarity)
    _classifier_cache[cache_key] = result
    return result


def ml_classify(text: str) -> tuple[str, float]:
    """ML-based intent classification. Returns (intent, confidence).

    Falls back gracefully if sklearn is not installed.
    """
    clf = _get_classifier()
    if clf is None:
        return ("unknown", 0.0)

    vectorizer, tfidf_matrix, labels, cosine_sim = clf
    try:
        text_vec = vectorizer.transform([text])
        similarities = cosine_sim(text_vec, tfidf_matrix).flatten()
        best_idx = similarities.argmax()
        best_score = float(similarities[best_idx])
        return (labels[best_idx], min(best_score, 1.0))
    except Exception:
        return ("unknown", 0.0)


# =============================================================================
# Keyword-based classification (original logic, refactored)
# =============================================================================


def _keyword_classify(user_input: str) -> tuple[int, dict | None]:
    """Original keyword-based classification. Returns (score, rule)."""
    lower = user_input.lower()
    best_score = 0
    best_priority = 0
    best_match = None

    for rule in INTENT_RULES:
        if rule.get("anti_keywords") and any(kw in lower for kw in rule["anti_keywords"]):
            continue
        score = sum(1 for kw in rule["keywords"] if kw.lower() in lower)
        priority = rule.get("priority", 1)
        if score > best_score or (score == best_score and priority > best_priority):
            best_score = score
            best_priority = priority
            best_match = rule

    return (best_score, best_match)


def classify_intent(user_input: str) -> dict:
    """Classify user input using hybrid keyword + ML approach."""
    lower = user_input.lower()

    # Step 1: Keyword classification (fast path)
    kw_score, kw_match = _keyword_classify(user_input)

    # Step 2: ML classification (scoring path)
    ml_intent, ml_confidence = ml_classify(user_input)

    # Step 3: Hybrid decision
    # If keyword match is strong (>= 2 keywords), use it
    # If keyword is weak or absent, use ML if confidence is reasonable
    if kw_score >= 2:
        # Strong keyword match — trust keywords
        final_intent = kw_match["intent"]
        confidence = min(kw_score / 3.0, 1.0)
        method = "keyword"
    elif kw_score >= 1:
        # Weak keyword match — blend with ML
        if ml_confidence > 0.3 and ml_intent != kw_match["intent"]:
            # ML disagrees and has decent confidence — use ML
            final_intent = ml_intent
            confidence = (kw_score / 3.0 + ml_confidence) / 2
            method = "hybrid"
        else:
            final_intent = kw_match["intent"]
            confidence = min(kw_score / 3.0, 1.0)
            method = "keyword"
    else:
        # No keyword match — use ML
        if ml_confidence > 0.2:
            final_intent = ml_intent
            confidence = ml_confidence
            method = "ml"
        else:
            # Low confidence overall — default to development
            return {
                "intent": "development",
                "skill": "synapse-code",
                "mode": "auto",
                "confidence": 0.3,
                "method": "default"
            }

    # Build result
    result = {
        "intent": final_intent,
        "confidence": round(confidence, 3),
        "method": method
    }

    # Find matching rule for skill/command
    matched_rule = None
    if kw_match and kw_match["intent"] == final_intent:
        matched_rule = kw_match
    else:
        for rule in INTENT_RULES:
            if rule["intent"] == final_intent:
                matched_rule = rule
                break

    if matched_rule:
        result["skill"] = matched_rule["skill"]
        if "command" in matched_rule:
            result["command"] = matched_rule["command"]

    # Development-specific: detect scenario and mode
    if result.get("skill") == "synapse-code":
        for scenario, kws in SCENARIO_KEYWORDS.items():
            if any(kw in lower for kw in kws):
                result["scenario"] = scenario
                break
        else:
            result["scenario"] = "development"

        if any(kw in lower for kw in PARALLEL_KEYWORDS):
            result["mode"] = "parallel"
        elif len(user_input) < 50 and not any(kw in lower for kw in ["系统", "架构", "平台", "整个"]):
            result["mode"] = "standalone"
        else:
            result["mode"] = "lite"

    return result


def main():
    if len(sys.argv) < 2:
        print(__doc__)
        sys.exit(1)

    user_input = " ".join(sys.argv[1:])
    result = classify_intent(user_input)

    print(json.dumps(result, indent=2, ensure_ascii=False))


if __name__ == "__main__":
    main()


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (HIGH)