docs(skills): add nerve-dev coding agent skill
Comprehensive development guide for AI coding agents covering: - Architecture and core concepts (sense → signal → workflow) - CLI commands reference - Sense and workflow development patterns - nerve.yaml configuration (inline interval/on) - Coding conventions and pitfalls Fixes #187
This commit was merged in pull request #201.
This commit is contained in:
@@ -0,0 +1,413 @@
|
||||
---
|
||||
name: nerve-dev
|
||||
description: >
|
||||
Nerve development guide for AI coding agents. Covers architecture (sense → signal → workflow),
|
||||
CLI commands, sense and workflow development patterns, nerve.yaml configuration, coding conventions,
|
||||
and common pitfalls. Use when developing senses, workflows, or contributing to the Nerve codebase.
|
||||
license: MIT
|
||||
compatibility: Requires Node.js 22+, pnpm, TypeScript. Nerve monorepo with packages/core, packages/cli, packages/daemon.
|
||||
metadata:
|
||||
author: uncaged
|
||||
version: "1.0"
|
||||
---
|
||||
|
||||
# Nerve 开发指南
|
||||
|
||||
Nerve 是一个轻量级本地感知引擎,为自主 agent 持续观察外部状态、响应变化、编排多步骤工作流。
|
||||
|
||||
## 核心数据流
|
||||
|
||||
```
|
||||
External World → Sense → Signal → Workflow → Log
|
||||
↑ ↑
|
||||
"观察什么" "做什么"
|
||||
```
|
||||
|
||||
| 概念 | 说明 |
|
||||
|------|------|
|
||||
| **Sense** | `compute()` 函数,采样或派生数据。返回 `T \| null` — 非 null 发出 Signal,null 静默。每个 Sense 有自己的 SQLite 数据库 |
|
||||
| **Signal** | Sense 返回非 null 时发出的通知。纯事实,无意图。通过内存 Signal Bus 分发,不持久化 |
|
||||
| **Workflow** | 有状态的多步骤执行。包含 **Roles**(有副作用的执行者)和 **Moderator**(纯路由器)。每个实例是一个 **Thread** |
|
||||
| **Log** | 不可变审计日志。记录执行、状态变更、错误 |
|
||||
| **Kernel** | 编排核心。持有 Signal Bus、调度器、进程管理、Workflow Manager |
|
||||
| **Daemon** | `nerve-daemon` — 后台运行的引擎进程 |
|
||||
|
||||
## 用户工作区 `~/.uncaged-nerve/`
|
||||
|
||||
```
|
||||
~/.uncaged-nerve/
|
||||
nerve.yaml # 主配置
|
||||
package.json # 依赖(@uncaged/nerve-core, drizzle-orm 等)
|
||||
senses/ # sense 模块
|
||||
cpu-usage/
|
||||
index.js # compute 函数
|
||||
schema.ts # Drizzle ORM schema
|
||||
migrations/ # SQL 迁移
|
||||
workflows/ # workflow 模块
|
||||
alert/
|
||||
index.ts # WorkflowDefinition
|
||||
data/ # SQLite 数据库、signal 存储
|
||||
logs/ # 日志
|
||||
```
|
||||
|
||||
## nerve.yaml 配置
|
||||
|
||||
```yaml
|
||||
senses:
|
||||
cpu-usage:
|
||||
group: system # worker 分组(同组 sense 共享一个 worker 进程)
|
||||
throttle: 5s # 最小计算间隔
|
||||
timeout: 10s # compute 超时
|
||||
grace_period: null # 首次错误后的宽限期
|
||||
retention: 10000 # _signals 表最大行数
|
||||
interval: 10s # 定时轮询周期
|
||||
on: [disk-usage] # 响应其他 sense 的 signal 触发计算
|
||||
|
||||
disk-usage:
|
||||
group: system
|
||||
throttle: 30s
|
||||
|
||||
workflows:
|
||||
alert:
|
||||
concurrency: 1 # 最大并发 thread 数
|
||||
overflow: queue # 超出时排队(或 drop)
|
||||
|
||||
max_rounds: 10 # workflow 默认最大轮数
|
||||
|
||||
api: # HTTP API 配置
|
||||
bind: "127.0.0.1:9800"
|
||||
token: null # loopback 不强制 token
|
||||
```
|
||||
|
||||
**调度方式**:
|
||||
- `interval: 10s` — 每 10 秒触发一次 compute
|
||||
- `on: [other-sense]` — 当 other-sense 发出 signal 时触发
|
||||
- 两者可组合
|
||||
|
||||
## Sense 开发
|
||||
|
||||
### Sense Anatomy
|
||||
|
||||
一个 sense 由以下文件组成(`nerve create sense <name>` 自动生成):
|
||||
|
||||
```
|
||||
senses/<name>/
|
||||
index.js # compute() 函数 — 必须
|
||||
schema.ts # Drizzle ORM 表定义 — 推荐
|
||||
migrations/ # SQL 迁移文件
|
||||
0001_init.sql # 建表 SQL — Drizzle Kit 生成
|
||||
```
|
||||
|
||||
命名规范:sense ID 用 kebab-case(如 `cpu-usage`),对应 SQL 表名自动转 snake_case(`cpu_usage`)。
|
||||
|
||||
### compute 函数
|
||||
|
||||
compute 返回 `null`(静默)或 `{ signal, workflow }` 结构:
|
||||
|
||||
```typescript
|
||||
// senses/cpu-usage/index.js
|
||||
export async function compute(db, peers, options) {
|
||||
// db: Drizzle ORM SQLite 实例(读写,当前 sense 专用)
|
||||
// peers: Record<string, DrizzleDB>(只读,同组其他 sense 的 DB)
|
||||
// options: { signal: AbortSignal, blobStore: BlobStore }
|
||||
|
||||
const usage = getCpuUsage();
|
||||
|
||||
// 返回 null → 静默(不发 Signal)
|
||||
if (usage <= 80) return null;
|
||||
|
||||
// 返回 { signal, workflow } → 发 Signal,可选触发 Workflow
|
||||
return {
|
||||
signal: { cpu: usage, timestamp: Date.now() },
|
||||
workflow: null, // 不触发 workflow
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
**Signal + Workflow 联动**:Signal 和 Workflow 是蕴含关系 — 有 signal 才可能触发 workflow,两者不互斥:
|
||||
|
||||
```typescript
|
||||
export async function compute(db) {
|
||||
const anomaly = detectAnomaly();
|
||||
if (!anomaly) return null;
|
||||
|
||||
return {
|
||||
signal: { level: "critical", cpu: anomaly.cpu },
|
||||
workflow: {
|
||||
name: "alert",
|
||||
maxRounds: 5,
|
||||
prompt: "CPU 持续高负载,需要分析",
|
||||
dryRun: false,
|
||||
},
|
||||
};
|
||||
// → 先发 Signal,再启动 alert workflow
|
||||
}
|
||||
```
|
||||
|
||||
**`WorkflowTrigger` 类型**(定义在 `@uncaged/nerve-core`):
|
||||
|
||||
```typescript
|
||||
type WorkflowTrigger = {
|
||||
name: string; // workflow 名称
|
||||
maxRounds: number; // 最大轮数(>= 1)
|
||||
prompt: string; // 传递给 workflow 的 prompt
|
||||
dryRun: boolean; // 是否 dry-run
|
||||
};
|
||||
```
|
||||
|
||||
**compute 返回值路由规则**(由 `routeSenseComputeOutput()` 决定):
|
||||
|
||||
| 返回值 | 行为 |
|
||||
|--------|------|
|
||||
| `null` | 静默,不发 Signal |
|
||||
| `{ signal: T, workflow: null }` | 发出 **Signal**,不触发 Workflow |
|
||||
| `{ signal: T, workflow: WorkflowTrigger }` | 先发 **Signal**,再启动 **Workflow** |
|
||||
| `{ signal: T, workflow: 非法对象 }` | 降级为 signal-only(workflow 被忽略) |
|
||||
| 裸值(无 `signal` 键) | 兼容模式:整个值作为 signal payload,不触发 workflow |
|
||||
|
||||
### Drizzle Schema 与迁移
|
||||
|
||||
Schema 使用 Drizzle ORM 定义,迁移 SQL 由 `drizzle-kit generate` 生成:
|
||||
|
||||
```typescript
|
||||
// senses/cpu-usage/schema.ts
|
||||
import { sqliteTable, integer, real } from "drizzle-orm/sqlite-core";
|
||||
|
||||
export const cpuUsage = sqliteTable("cpu_usage", {
|
||||
id: integer("id").primaryKey({ autoIncrement: true }),
|
||||
usage: real("usage").notNull(),
|
||||
ts: integer("ts").notNull(),
|
||||
});
|
||||
```
|
||||
|
||||
生成迁移:
|
||||
|
||||
```bash
|
||||
npx drizzle-kit generate # 生成 migrations/0001_*.sql
|
||||
```
|
||||
|
||||
迁移文件放在 `senses/<name>/migrations/`,daemon 启动时自动执行。
|
||||
|
||||
### 进程隔离
|
||||
|
||||
- 同一 `group` 的 sense 共享一个 long-lived worker 进程
|
||||
- Worker 通过 Node.js IPC(fork + `process.send`)与 kernel 通信
|
||||
- 每个 sense 有独立的 SQLite 数据库
|
||||
|
||||
### `nerve sense` 命令
|
||||
|
||||
```bash
|
||||
nerve sense list # 列出所有 sense 及状态(group、throttle、trigger schedule、last signal)
|
||||
nerve sense trigger <name> # 手动触发一次 compute
|
||||
nerve sense query <name> # 查询 sense 的 SQLite 数据(默认查 _signals 表)
|
||||
nerve sense query <name> "SELECT * FROM cpu_usage ORDER BY ts DESC LIMIT 10"
|
||||
nerve sense schema <name> # 查看 sense 数据库的 CREATE TABLE 语句
|
||||
nerve sense schema <name> --json # JSON 格式输出
|
||||
```
|
||||
|
||||
## Workflow 开发
|
||||
|
||||
### Workflow Anatomy
|
||||
|
||||
一个 workflow 由以下文件组成(`nerve create workflow <name>` 自动生成):
|
||||
|
||||
```
|
||||
workflows/<name>/
|
||||
index.ts # WorkflowDefinition — import roles,定义 moderator
|
||||
roles/
|
||||
analyst/
|
||||
index.ts # role execute 函数
|
||||
prompt.md # prompt 模板(可选)
|
||||
reporter/
|
||||
index.ts
|
||||
```
|
||||
|
||||
每个 role 是一个独立目录,包含 `index.ts`(execute 函数)和可选的 prompt 模板、配置等资源文件。
|
||||
|
||||
### WorkflowDefinition
|
||||
|
||||
```typescript
|
||||
// workflows/alert/index.ts
|
||||
import type { WorkflowDefinition } from "@uncaged/nerve-core";
|
||||
import { analyst } from "./roles/analyst/index.js";
|
||||
import { reporter } from "./roles/reporter/index.js";
|
||||
|
||||
export const definition: WorkflowDefinition = {
|
||||
name: "alert",
|
||||
roles: { analyst, reporter },
|
||||
|
||||
// Moderator: 纯路由函数,决定下一个 role
|
||||
moderator: (context) => {
|
||||
if (context.rounds === 0) return "analyst";
|
||||
if (context.rounds === 1) return "reporter";
|
||||
return "END";
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
```typescript
|
||||
// workflows/alert/roles/analyst/index.ts
|
||||
export const analyst = {
|
||||
async execute(start, messages) {
|
||||
// start: { prompt, maxRounds, dryRun }
|
||||
// messages: 历史消息数组
|
||||
const analysis = await callLLM(start.prompt);
|
||||
return { content: analysis, meta: {} };
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
Role 可以附带 prompt 模板:
|
||||
|
||||
```typescript
|
||||
// workflows/alert/roles/reporter/index.ts
|
||||
import { readFileSync } from "node:fs";
|
||||
import { join, dirname } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const PROMPT = readFileSync(join(__dirname, "prompt.md"), "utf8");
|
||||
|
||||
export const reporter = {
|
||||
async execute(start, messages) {
|
||||
const lastMessage = messages[messages.length - 1];
|
||||
await sendNotification(PROMPT.replace("{{content}}", lastMessage.content));
|
||||
return { content: "已通知", meta: {} };
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### 公共工具(`@uncaged/nerve-workflow-utils`)
|
||||
|
||||
Role 中可使用以下公共工具:
|
||||
|
||||
| 工具 | 用途 |
|
||||
|------|------|
|
||||
| `spawnSafe(cmd, args, opts)` | 安全启动子进程,`shell: false`,支持 timeout 和 dry-run,返回 `Result<SpawnResult>` |
|
||||
| `nerveCommandEnv()` | 返回配置好 pnpm/nerve PATH 的环境变量,用于在 role 中调用 CLI |
|
||||
| `cursorAgent(prompt, opts)` | 调用 cursor-agent CLI(mode: `"plan"` / `"ask"` / `"default"`),返回 `Result<string>` |
|
||||
| `llmExtract(opts)` | 调用 OpenAI 兼容 API + Zod schema 提取结构化数据,返回 `Result<T>` |
|
||||
| `readNerveYaml(dir)` | 安全读取 nerve.yaml(防路径穿越),返回 `Result<string>` |
|
||||
| `isDryRun(startStep)` | 从 workflow StartStep 提取 dry-run 标志 |
|
||||
|
||||
### Workflow 进程模型
|
||||
|
||||
- 每个 workflow **类型**有一个 on-demand worker 进程(可复用)
|
||||
- 每个运行实例是一个 **Thread**,有唯一 `runId`
|
||||
- 支持崩溃恢复和 resume
|
||||
|
||||
### `nerve workflow` & `nerve thread` 命令
|
||||
|
||||
```bash
|
||||
# Workflow 管理
|
||||
nerve workflow list # 列出 workflow 定义(从 nerve.yaml 读取)
|
||||
nerve workflow status # 查看运行中的 workflow(从 daemon 实时获取)
|
||||
nerve workflow trigger <name> # 触发 workflow
|
||||
nerve workflow trigger <name> --max-rounds 5 --prompt '分析 CPU 异常' --dry-run
|
||||
|
||||
# Thread(运行实例)管理
|
||||
nerve thread list # 列出运行记录(默认只显示 running/queued)
|
||||
nerve thread list --all # 包括已完成的
|
||||
nerve thread show <runId> # 查看 role 轮次对话内容
|
||||
nerve thread inspect <runId> # 查看底层事件详情
|
||||
nerve thread kill <runId> # 终止运行中的 thread
|
||||
```
|
||||
|
||||
## 其他 CLI 命令
|
||||
|
||||
### 初始化与开发
|
||||
|
||||
```bash
|
||||
nerve init # 初始化工作区
|
||||
nerve init --from <git-url> # 从 git 克隆
|
||||
nerve create sense <name> # 脚手架 sense 模板
|
||||
nerve create workflow <name> # 脚手架 workflow 模板
|
||||
nerve dev # 前台开发模式(热重载)
|
||||
nerve validate # 校验 nerve.yaml
|
||||
```
|
||||
|
||||
### Daemon 管理
|
||||
|
||||
```bash
|
||||
nerve daemon start # 后台启动
|
||||
nerve daemon stop # 停止
|
||||
nerve daemon status # 状态(pid、uptime、senses、workers)
|
||||
nerve daemon restart # 重启
|
||||
nerve status # 同 daemon status
|
||||
nerve stop # 同 daemon stop
|
||||
nerve logs # 查看日志
|
||||
nerve logs -f # 实时跟踪日志
|
||||
```
|
||||
|
||||
### Remote(多节点管理)
|
||||
|
||||
```bash
|
||||
nerve remote add <name> <host> --token <secret>
|
||||
nerve remote list
|
||||
nerve remote default <name>
|
||||
nerve --host remote-host:9800 sense list
|
||||
```
|
||||
|
||||
### 存储维护
|
||||
|
||||
```bash
|
||||
nerve store archive # 归档 30 天前的日志
|
||||
nerve store archive --vacuum # 归档并压缩 SQLite
|
||||
```
|
||||
|
||||
## 编码规范
|
||||
|
||||
### 语言与范式
|
||||
|
||||
- **Functional-first**:用 `type` + `function`,不用 `class` + `interface`
|
||||
- **No `this`**、**No inheritance**
|
||||
- **No optional properties**:用 `T | null` 替代 `?:`
|
||||
- **`Result<T, E>` 类型** 处理预期错误,`throw` 仅用于不可恢复的 bug
|
||||
- **Always `async/await`**,不用 `.then()` 链
|
||||
- **No dynamic import**(除 sense-runtime.ts 和 workflow-worker.ts)
|
||||
|
||||
### 命名
|
||||
|
||||
| 类型 | 风格 | 示例 |
|
||||
|------|------|------|
|
||||
| 文件 | kebab-case | `signal-bus.ts` |
|
||||
| 类型 | PascalCase | `SignalBus` |
|
||||
| 函数/变量 | camelCase | `createSignalBus` |
|
||||
| 常量 | UPPER_SNAKE | `MAX_RETRY_COUNT` |
|
||||
|
||||
### 工具链
|
||||
|
||||
```bash
|
||||
pnpm run check # biome check(lint + format)
|
||||
pnpm run format # biome format --write
|
||||
pnpm run build # 全量构建
|
||||
pnpm test # 运行测试
|
||||
```
|
||||
|
||||
### Commit 规范
|
||||
|
||||
```
|
||||
<type>(<scope>): <description>
|
||||
|
||||
type: feat | fix | refactor | docs | chore | test
|
||||
scope: core | cli | daemon | ...
|
||||
```
|
||||
|
||||
## 开发流程
|
||||
|
||||
1. **修改代码** → 在对应 package 目录下开发
|
||||
2. **`pnpm run build`** → 确保编译通过
|
||||
3. **`pnpm test`** → 确保测试通过
|
||||
4. **`pnpm run check`** → 确保 biome lint/format 通过
|
||||
5. **`nerve dev`** → 在工作区前台运行验证
|
||||
6. **`nerve sense list`** / **`nerve sense query <name>`** → 检查 sense 输出
|
||||
7. **`nerve logs -f`** → 实时查看日志排查问题
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- Sense `compute()` 返回 `undefined` 和返回 `null` 不同 — `undefined` 会被当作非 null 值尝试持久化
|
||||
- 同 group 的 sense 共享 worker 进程,一个 sense 的阻塞会影响同组其他 sense
|
||||
- Workflow 的 `moderator` 必须是纯函数(无副作用),所有副作用放在 `roles` 里
|
||||
- Log 不能触发后续动作 — 这是防止反馈循环的设计
|
||||
- `nerve.yaml` 中所有 duration 字段支持 `5s`、`10m` 等人类可读格式
|
||||
- Workflow directive 格式是 `{ workflow: { name, maxRounds, prompt, dryRun } }`,signal 和 workflow 是蕴含关系(先 signal 后 workflow)
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"name": "@uncaged/nerve-skills",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"description": "Coding agent skills for Nerve development"
|
||||
}
|
||||
Reference in New Issue
Block a user