This repository has been archived on 2026-06-01. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
nerve/packages/cli/skills/hermes/SKILL.md
T
xiaoju 1849789c02 docs(cli): sync Hermes SKILL with workspace AGENT and runtime types
Update sense compute docs to SenseComputeFn (no DB/peers). Document AGENT.md, flat roles, moderator/build helpers, createRole + createLlmAdapter, verb-first naming, nerve agent commands, and root npm/pnpm build.

Fixes #298

Made-with: Cursor
2026-04-30 14:15:14 +00:00

20 KiB

name, version, description, metadata
name version description metadata
nerve 0.5.0 Nerve — AI agent 观测引擎。掌握 nerve 的核心概念、CLI 操作、sense/workflow 开发。 加载此 skill 后你可以:查看系统状态、监控 sense、触发 workflow、开发新 sense 和 workflow。
hermes
tags homepage
nerve
sense
workflow
monitoring
agent-kernel
https://git.shazhou.work/uncaged/nerve

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-requestdeploy-staging)。避免单独名词式命名(如 notifications)。
  • Sense:描述性名词 kebab-case(例如 cpu-usage)。

CLI 完整参考

全局选项:--host <host:port>(连接远程 daemon)、--api-token <secret>(Bearer 认证)

初始化与脚手架

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 管理

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 操作

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 操作

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 执行记录)

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(日志归档)

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

Knowledge(知识库)

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

Remote(远程 daemon)

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)

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.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

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

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

(运行时定义见 @uncaged/nerve-coreSenseComputeFn / SenseModule,daemon 侧在 sense-runtime.tsexecuteCompute 中插入 result.signal。)

返回值

// 返回 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 模块导出

// 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 定义

// 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. 两者可以组合

调试

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

完整示例:CPU 监控

// 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:

senses:
  cpu-usage:
    group: system
    interval: 10s
    throttle: 5s
    timeout: 10s
    retention: 10000

Workflow 开发指南

核心类型

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 initpackage.json 不含该依赖时,在 ~/.uncaged-nerve 下执行 pnpm add @uncaged/nerve-workflow-utils(或 npm 等价命令)。

使用 createRole,按固定顺序传入四件事:

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

// 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 },
  };
}
// 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.tsimport { route } from "./moderator.js",保持 index.ts 只负责组装 WorkflowDefinition

多 Role Workflow 示例

// 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" } };
}
// 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" } };
}
// 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" } };
}
// 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。已知适配器 IDechocursorhermescodex

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

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

Workflow 运行状态

queuedstartedcompleted | failed | crashed | killed | interrupted | dropped


日常操作 Pattern

查看系统整体状态

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

检查某个 sense 的历史数据

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

手动触发 workflow

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

排查 sense 报错

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

开发新 sense

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

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 是纯路由逻辑,不能有副作用。