<!-- nerve-cli-version: __NERVE_CLI_VERSION__ -->

## Cursor Agent 使用提示

在 Cursor 中与 Agent 对话时，可以用以下方式指代代码与配置：

- **`@Files` / `@file`**：引用单个文件，例如 `@nerve.yaml`、`@senses/cpu-usage/src/index.ts`，减少幻觉并让修改对准正确路径。
- **`@Folder` / `@Codebase`**：需要跨目录理解工作区结构时使用；改动前仍应优先打开相关 sense/workflow 源文件确认。
- **`@Terminal`**：把 CLI 输出纳入上下文，便于对照 `nerve daemon logs`、`nerve sense query` 等结果。
- **`@Docs`**：若项目或依赖有文档索引，可用来对齐 API 与约定。
- 工作区根目录下的 **`nerve.yaml`**、`senses/`、`workflows/` 是 nerve 的核心入口；讨论调度与配置时优先 `@` 这些路径。
- 本规则由 `nerve agent inject cursor` 安装；更新 CLI 后在同一目录再次执行可覆盖为新版。

---

# Nerve — AI Agent 观测引擎

Nerve 是一个轻量级观测引擎守护进程。它持续观测外部状态，通过声明式规则响应变化，编排多步骤工作流。

## 核心架构

```
External World → Sense → Signal → Workflow → Log
```

| 概念 | 说明 |
|------|------|
| **Sense** | 观测函数，`compute()` 采样或推导数据。返回非 null 则发出 Signal，可选触发 Workflow。每个 Sense 有独立 SQLite 数据库。 |
| **Signal** | Sense 返回非 null 时发出的通知。纯事实，无意图。通过内存 Signal Bus 分发，不持久化。 |
| **Workflow** | 有状态的多步骤执行。包含 Role（有副作用的执行者）和 Moderator（纯路由器）。每个实例是一个 Thread，有唯一 runId。 |
| **Log** | 不可变审计日志。记录执行、状态转换、错误。不能触发 Sense（防止反馈循环）。 |
| **Engine** | 内核，持有 Signal Bus、Process Manager、Workflow Manager。不直接加载用户代码。 |
| **Daemon** | 引擎运行时，作为后台进程运行。 |

**关键规则：**
- 因果链单向：External → Sense → Signal → Workflow + Log
- 进程隔离：每个 Sense group 一个 worker（长期），每个 Workflow 类型一个 worker（按需）
- 两个扩展点：Sense（观测什么 + 何时）、Workflow（做什么）

## 工作区结构

由 `nerve init` 生成的工作区根目录（默认 `~/.uncaged-nerve/`）包含 **`AGENT.md`**。实现 sense/workflow 前先阅读该文件：它与本文 skill 对齐，约定目录布局、`createRole` 用法以及**始终在仓库根目录**执行的构建命令。

```
~/.uncaged-nerve/
├── AGENT.md               # 人类 / Agent 可读的工作区约定（init 生成）
├── nerve.yaml             # 核心配置
├── package.json           # 单一根包（sense/workflow 下不再有独立 package）
├── scripts/build.mjs      # 根目录 esbuild；通过 npm/pnpm 的 build 脚本调用
├── senses/
│   └── <name>/
│       ├── src/index.ts   # exports compute() + table
│       ├── src/schema.ts  # Drizzle 表定义
│       └── migrations/    # SQL 迁移
├── workflows/
│   └── <name>/
│       ├── index.ts       # default export：WorkflowDefinition
│       ├── moderator.ts   # 可选：抽出 moderator，由 index 导入
│       ├── build.ts       # 可选：共享常量 / 纯函数（避免 index 臃肿；非 esbuild 入口）
│       └── roles/
│           └── <role>.ts  # 每角色单文件（推荐平铺，而非 roles/<role>/index.ts）
└── data/                  # 运行时数据（SQLite、blobs）
```

### 命名约定

- **Workflow**：动词开头的 kebab-case（例如 `review-pull-request`、`deploy-staging`）。避免单独名词式命名（如 `notifications`）。
- **Sense**：描述性名词 kebab-case（例如 `cpu-usage`）。

---

## CLI 完整参考

全局选项：`--host <host:port>`（连接远程 daemon）、`--api-token <secret>`（Bearer 认证）

### 初始化与脚手架

```bash
nerve init                          # 初始化工作区
nerve init --from <git-url>         # 从 git 仓库克隆工作区
nerve init workspace                # 只初始化工作区结构

nerve create sense <name>           # 创建 sense 脚手架
nerve create sense <name> --force   # 覆盖已有
nerve create workflow <name>        # 创建 workflow 脚手架
nerve create workflow <name> --force

nerve validate                      # 验证 nerve.yaml 配置
```

### Daemon 管理

```bash
nerve daemon start                  # 启动后台 daemon
nerve daemon start --port 3000      # 指定 HTTP API 端口
nerve daemon stop                   # 停止 daemon
nerve daemon restart                # 重启
nerve daemon status                 # 查看状态
nerve daemon logs                   # 查看日志
nerve daemon logs --follow          # 实时日志
nerve daemon logs --n 50            # 最近 50 行

nerve dev                           # 前台开发模式（不 fork daemon）
nerve dev --port 3000               # 指定端口
```

### Sense 操作

```bash
nerve sense list                    # 列出所有注册的 sense
nerve sense trigger <name>          # 手动触发 sense 计算
nerve sense schema <name>           # 查看 sense 数据库表结构
nerve sense schema <name> --json    # JSON 格式
nerve sense query <name> <sql>      # 对 sense 数据库执行只读 SQL
nerve sense query <name> "SELECT * FROM samples ORDER BY ts DESC LIMIT 10" --json
```

### Workflow 操作

```bash
nerve workflow list                 # 列出 nerve.yaml 中定义的 workflow
nerve workflow status               # 查看运行中的 workflow 状态
nerve workflow trigger <name>       # 触发 workflow
nerve workflow trigger <name> --prompt "检查生产环境"
nerve workflow trigger <name> --maxRounds 50
nerve workflow trigger <name> --dryRun   # 干跑模式
```

### Thread（Workflow 执行记录）

```bash
nerve thread list                   # 列出最近的 workflow 执行
nerve thread list --all             # 包含已完成/失败的
nerve thread list --workflow <name> # 按 workflow 过滤
nerve thread list --limit 50        # 最多 50 条

nerve thread show <runId>           # 查看 role 对话轮次
nerve thread show <runId> --budget 16000  # 增大输出预算（默认 8000 字符）

nerve thread inspect <runId>        # 查看详情和事件

nerve thread kill <runId>           # 终止运行中/排队中的 thread
```

### Store（日志归档）

```bash
nerve store archive                 # 导出旧日志到 JSONL 归档
nerve store archive --vacuum        # 归档后 VACUUM 数据库
```

### Knowledge（知识库）

```bash
nerve knowledge sync                # 从 knowledge.yaml 重建索引
nerve knowledge query "搜索内容"     # 搜索知识库
nerve knowledge query "内容" --limit 5
nerve knowledge query "内容" -g     # 搜索所有注册仓库
```

### Remote（远程 daemon）

```bash
nerve remote add <name> <host:port> --token <secret>
nerve remote list
nerve remote show <name>
nerve remote set-url <name> <host>
nerve remote set-token <name> <token>
nerve remote remove <name>
nerve remote default <name>         # 设为默认远程
```

### Agent（向 Hermes 注入本 skill）

```bash
nerve agent status                  # CLI 版本与各 Hermes 注入目录中的 skill 版本
nerve agent inject hermes           # 安装到 ~/.hermes/skills/nerve
nerve agent inject hermes --profile <name>   # 写入 ~/.hermes/profiles/<name>/skills/nerve
nerve agent update                  # 将所有已注入目录更新到当前 CLI 对应版本
nerve agent remove hermes           # 移除默认 profile 的注入
nerve agent remove hermes --profile <name>

nerve agent inject cursor              # 在 cwd 生成 .cursorrules
nerve agent inject cursor --path /foo  # 在指定目录生成
nerve agent remove cursor [--path /foo]
```

---

## nerve.yaml 配置参考

```yaml
# 引擎全局配置
max_rounds: 100              # moderator 最大轮次（默认 100）

# Sense 配置
senses:
  cpu-usage:
    group: system            # 必填，同 group 的 sense 共享 worker
    interval: 10s            # 轮询间隔（duration: 5s, 10m, 1h）
    throttle: 5s             # 最小计算间隔
    timeout: 10s             # compute 超时
    grace_period: null       # 优雅关闭等待
    retention: 10000         # _signals 表最大行数（默认 10000）

  system-health:
    group: derived
    on: [cpu-usage, disk-usage]  # 响应式：被列出的 sense 发出 signal 时触发
    throttle: null
    timeout: null

# Workflow 配置
workflows:
  my-workflow:
    concurrency: 1           # 必填，并发数
    overflow: drop           # 必填，超并发时处理：drop | queue
    max_queue: 100           # overflow=queue 时的队列上限（默认 100）

# HTTP API
api:
  port: 3000                 # null = 不启用 HTTP
  host: "127.0.0.1"         # 监听地址
  token: null                # 非 loopback 时必填

# LLM Extract（可选）
extract:
  provider: anthropic
  model: claude-sonnet-4-20250514
```

---

## Sense 开发指南

### compute 函数签名

Sense 的 `compute` **无参数**。它不接收数据库句柄：daemon 在 worker 内调用 `SenseComputeFn`，由运行时负责把非 null 结果的 `signal` 写入该 sense 的 Drizzle 表并记入 `_signals`。超时由运行时控制（对应 `nerve.yaml` 里的 `timeout`），无需在业务代码里读取 `AbortSignal`。

```typescript
import type { ComputeResult, SenseComputeFn } from "@uncaged/nerve-core";

export const compute: SenseComputeFn<MySignalShape> = async () => {
  // ...
};
// 或等价地：
export async function compute(): Promise<ComputeResult<MySignalShape>> {
  // ...
}
```

（运行时定义见 `@uncaged/nerve-core` 的 `SenseComputeFn` / `SenseModule`，daemon 侧在 `sense-runtime.ts` 的 `executeCompute` 中插入 `result.signal`。）

### 返回值

```typescript
// 返回 null = 静默，不发 signal
// 返回非 null = 发出 signal（并写入业务表），可选触发 workflow
type ComputeResult<T> =
  | null
  | { signal: T; workflow: WorkflowTrigger | null };

type WorkflowTrigger = {
  name: string;          // workflow 名称（对应 nerve.yaml 中的 key）
  maxRounds: number;     // moderator 最大轮次
  prompt: string;        // 初始 prompt
  dryRun: boolean;       // 干跑模式
};
```

若返回值是普通对象且不含 `signal` 字段，内核会按 shorthand 视为 `{ signal: payload, workflow: null }`（见 core 的 `routeSenseComputeOutput`）。

### Sense 模块导出

```typescript
// senses/<name>/src/index.ts
import type { ComputeResult } from "@uncaged/nerve-core";
import { table } from "./schema.js";

type Row = { ts: number; value: number };

export async function compute(): Promise<ComputeResult<Row>> {
  const row: Row = { ts: Date.now(), value: Math.random() }; // 替换为真实观测逻辑
  return { signal: row, workflow: null };
}

export { table };
```

### Schema 定义

```typescript
// senses/<name>/src/schema.ts
import { sqliteTable, integer, real } from "drizzle-orm/sqlite-core";

export const table = sqliteTable("samples", {
  ts: integer("ts").notNull(),
  value: real("value").notNull(),
});
```

### 调度方式

1. **interval 轮询**：`interval: 10s` — 每 10 秒执行一次
2. **响应式触发**：`on: [cpu-usage]` — 当 cpu-usage 发出 signal 时触发
3. 两者可以组合

### 调试

```bash
nerve dev                           # 前台运行，看实时输出
nerve sense trigger <name>          # 手动触发一次
nerve sense query <name> "SELECT * FROM samples ORDER BY ts DESC LIMIT 5"
```

### 完整示例：CPU 监控

```typescript
// senses/cpu-usage/src/schema.ts
import { sqliteTable, integer, real } from "drizzle-orm/sqlite-core";

export const table = sqliteTable("samples", {
  ts: integer("ts").notNull(),
  value: real("value").notNull(),
});

// senses/cpu-usage/src/index.ts
import os from "node:os";
import type { ComputeResult } from "@uncaged/nerve-core";
import { table } from "./schema.js";

type Row = { ts: number; value: number };

export async function compute(): Promise<ComputeResult<Row>> {
  const oneMin = os.loadavg()[0];
  return { signal: { ts: Date.now(), value: oneMin }, workflow: null };
}

export { table };
```

nerve.yaml:
```yaml
senses:
  cpu-usage:
    group: system
    interval: 10s
    throttle: 5s
    timeout: 10s
    retention: 10000
```

---

## Workflow 开发指南

### 核心类型

```typescript
import type {
  WorkflowDefinition,
  RoleResult,
  ThreadContext,
  RoleMeta,
  Moderator,
} from "@uncaged/nerve-core";
import { END } from "@uncaged/nerve-core";

// Role<Meta> — (ctx: ThreadContext) => Promise<RoleResult<Meta>>
// RoleResult<Meta> — { content: string; meta: Meta }
// ThreadContext<M extends RoleMeta> — threadId, start（__start__ 帧）, steps（各 role 轮次）
// Moderator<M> — (ctx) => 下一个 role 名 | END
// WorkflowDefinition<M extends RoleMeta> — name, roles, moderator
```

### createRole 四元组（接入 LLM 时推荐）

工作区根目录需安装 **`@uncaged/nerve-workflow-utils`**（及所选 agent 适配器包）。默认 `nerve init` 的 `package.json` 不含该依赖时，在 `~/.uncaged-nerve` 下执行 `pnpm add @uncaged/nerve-workflow-utils`（或 npm 等价命令）。

使用 **`createRole`**，按固定顺序传入四件事：

1. **adapter** — `AgentFn`，`(ctx, systemPrompt) => Promise<string>`（原始模型输出文本）。
2. **prompt** — `string`，或 `async (ctx: ThreadContext) => string`。
3. **meta** — `z.ZodType<M>`，供 moderator 路由的结构化 meta。
4. **extract** — `{ provider: LlmProvider; dryRun: boolean | null }`，声明从回复中抽取 meta 时用的 LLM（OpenAI 兼容）及是否 dry-run。

```typescript
import { createLlmAdapter, createRole } from "@uncaged/nerve-workflow-utils";
import type { ThreadContext } from "@uncaged/nerve-core";
import { z } from "zod";

const provider = {
  baseUrl: "https://api.example.com/v1",
  apiKey: process.env.EXAMPLE_API_KEY!,
  model: "gpt-4o-mini",
};

const planMeta = z.object({ next: z.enum(["execute", "stop"]) });

export const planner = createRole(
  createLlmAdapter(provider),
  async (ctx: ThreadContext) => `规划任务：${ctx.start.content}`,
  planMeta,
  { provider, dryRun: null },
);
```

`createLlmAdapter` 仅位于 **`@uncaged/nerve-workflow-utils`**：用 `LlmProvider` 生成 `AgentFn`，单轮对话里 **system** 来自 `createRole` 解析后的 prompt 字符串，**user** 为线程起点 `ctx.start.content`。

### 基本 Workflow 示例（平铺 `roles/<role>.ts`）

```typescript
// workflows/example/roles/main.ts
import type { RoleResult, ThreadContext } from "@uncaged/nerve-core";

export async function main(ctx: ThreadContext): Promise<RoleResult<{ round: number }>> {
  const prompt = ctx.start.content;
  return {
    content: `处理完成: ${prompt}`,
    meta: { round: ctx.steps.length },
  };
}
```

```typescript
// workflows/example/index.ts
import type { ThreadContext, WorkflowDefinition } from "@uncaged/nerve-core";
import { END } from "@uncaged/nerve-core";

import { main } from "./roles/main.js";

type Meta = Record<"main", { round: number }>;

const workflow: WorkflowDefinition<Meta> = {
  name: "example",
  roles: { main },
  moderator(ctx: ThreadContext<Meta>) {
    return ctx.steps.length === 0 ? "main" : END;
  },
};

export default workflow;
```

可选：将 `moderator` 挪到 `moderator.ts` 再 `import { route } from "./moderator.js"`，保持 `index.ts` 只负责组装 `WorkflowDefinition`。

### 多 Role Workflow 示例

```typescript
// workflows/plan-execute-review/roles/planner.ts
import type { RoleResult, ThreadContext } from "@uncaged/nerve-core";

export async function planner(ctx: ThreadContext): Promise<RoleResult<{ status: string }>> {
  void ctx;
  return { content: "计划: ...", meta: { status: "planned" } };
}
```

```typescript
// workflows/plan-execute-review/roles/executor.ts
import type { RoleResult, ThreadContext } from "@uncaged/nerve-core";

export async function executor(ctx: ThreadContext): Promise<RoleResult<{ status: string }>> {
  void ctx;
  return { content: "执行: ...", meta: { status: "executed" } };
}
```

```typescript
// workflows/plan-execute-review/roles/reviewer.ts
import type { RoleResult, ThreadContext } from "@uncaged/nerve-core";

export async function reviewer(ctx: ThreadContext): Promise<RoleResult<{ status: string }>> {
  void ctx;
  return { content: "审核通过", meta: { status: "approved" } };
}
```

```typescript
// workflows/plan-execute-review/index.ts
import type { WorkflowDefinition, ThreadContext } from "@uncaged/nerve-core";
import { END } from "@uncaged/nerve-core";

import { executor } from "./roles/executor.js";
import { planner } from "./roles/planner.js";
import { reviewer } from "./roles/reviewer.js";

type Roles = Record<"planner" | "executor" | "reviewer", { status: string }>;

const workflow: WorkflowDefinition<Roles> = {
  name: "plan-execute-review",
  roles: { planner, executor, reviewer },
  moderator(ctx: ThreadContext<Roles>) {
    if (ctx.steps.length === 0) return "planner";
    const last = ctx.steps[ctx.steps.length - 1];
    if (last.role === "planner") return "executor";
    if (last.role === "executor") return "reviewer";
    return END;
  },
};

export default workflow;
```

### Agent 适配器

Workflow role 可以集成 AI agent。已知适配器 **ID**：`echo`、`cursor`、`hermes`、`codex`。

```typescript
type AgentFn = (ctx: ThreadContext, systemPrompt: string) => Promise<string>;
```

没有现成 agent 包时，用 **`createLlmAdapter`（`@uncaged/nerve-workflow-utils`）** 从 OpenAI 兼容的 `LlmProvider` 构造 `AgentFn`，再交给 **`createRole`** 的四元组。

### Workflow 运行状态

`queued` → `started` → `completed` | `failed` | `crashed` | `killed` | `interrupted` | `dropped`

---

## 日常操作 Pattern

### 查看系统整体状态

```bash
nerve daemon status                 # daemon 是否在运行
nerve sense list                    # 所有 sense 及其调度配置
nerve workflow status               # 运行中的 workflow
nerve thread list                   # 最近的 workflow 执行记录
```

### 检查某个 sense 的历史数据

```bash
nerve sense query cpu-usage "SELECT * FROM samples ORDER BY ts DESC LIMIT 10" --json
nerve sense schema cpu-usage        # 查看表结构
```

### 手动触发 workflow

```bash
nerve workflow trigger my-workflow --prompt "手动检查"
nerve thread list --workflow my-workflow  # 查看执行状态
nerve thread show <runId>           # 查看对话详情
```

### 排查 sense 报错

```bash
nerve daemon logs --follow          # 查看实时日志
nerve sense trigger <name>          # 手动触发看报错
nerve dev                           # 前台模式，更详细的输出
```

### 开发新 sense

```bash
nerve create sense my-sensor        # 脚手架
# 编辑 senses/my-sensor/src/index.ts 和 schema.ts
nerve validate                      # 验证配置
nerve dev                           # 前台测试
nerve sense trigger my-sensor       # 单次触发验证
nerve sense query my-sensor "SELECT * FROM ..."  # 检查数据
```

### 开发新 workflow

```bash
nerve create workflow my-flow       # 脚手架（当前 CLI 可能仍生成 roles/<name>/ 子目录）
# 推荐对齐 AGENT.md：workflows/my-flow/index.ts + roles/<role>.ts（平铺），moderator 可拆到 moderator.ts
nerve validate                      # 验证配置
cd ~/.uncaged-nerve && npm run build   # 工作区根目录构建（等价：pnpm run build）；勿在单个 workflow 子目录单独跑 build
nerve workflow trigger my-flow --prompt "测试" --dryRun  # 干跑
nerve thread show <runId>           # 查看执行轨迹
```

---

## Pitfalls

- **Sense 返回值**：返回 `null` 表示静默（不发 signal）；返回 `{ signal, workflow }` 才发 signal。不要返回 undefined。
- **Sense 持久化**：daemon 在 `compute()` 返回非 null 时自动执行 `db.insert(table).values(signal)` 并写入 `_signals`；业务代码不要自行 insert。
- **no optional properties**：nerve 代码规范禁止 `?:`，用 `T | null` 代替。
- **函数式风格**：用 `function` + `type`，不用 `class` + `interface`。
- **workflow 用 default export**：工作区里通常只有 `workflows/<name>/index.ts` 使用 default export（daemon 加载约定）。
- **_signals 表**：每个 sense 自动有 `_signals` 表记录 signal 历史，受 `retention` 配置限制。
- **concurrency + overflow**：workflow 必须配置并发策略，否则验证失败。
- **moderator 是同步函数**：不要加 async，moderator 是纯路由逻辑，不能有副作用。
