docs: add §5 runtime model (process isolation, persistence, hot reload, error handling)

This commit is contained in:
2026-04-22 03:39:29 +00:00
parent aa5b0c6349
commit 96c740d175
+213 -6
View File
@@ -207,7 +207,214 @@ Signal 循环 ──→ ThreadStart ──→ Thread 循环
- Signal 循环:无状态、幂等、可合并、可丢弃
- Thread 循环:有状态、严格顺序、每个 Command Event 必须响应
## 5. 函数式性质
## 5. 运行时模型
### 5.1 进程架构
Engine 主进程与用户代码完全分离。主进程**永远不加载用户代码**,所有用户代码在独立子进程中运行。
```
systemd / pm2
└─ nerve-engine (永驻主进程,纯引擎代码)
├─ Scheduler ← nerve.yaml
├─ Signal Bus ← worker 产出的 signal
├─ File Watcher ← 监听 ~/.uncaged-nerve/ 变化
└─ Process Manager
├─ nerve-worker group=system (永驻,用户 sense 代码)
├─ nerve-worker group=tasks (永驻,用户 sense 代码)
├─ nerve-worker workflow=cleanup (按需启动,用户 workflow 代码)
└─ nerve-worker workflow=code-review (按需启动,用户 workflow 代码)
```
#### 隔离理由
worker_thread 的隔离是假的——用户代码的 `process.exit()`、native module segfault、OOM 都会杀死主进程。只有进程边界才是真正的隔离墙。
#### Sense Worker
Sense 按 group 分组,同 group 共享一个 worker 进程。用户决定隔离粒度。
```yaml
senses:
cpu-usage:
group: system
disk-usage:
group: system
memory-usage:
group: system
active-tasks:
group: tasks
```
- 同 group 的 sense 一个出问题会影响同组,但不影响其他组和引擎
- Worker 长驻,跟 engine 一起活
- 崩溃后 engine 自动 respawn
#### Workflow Worker
同一个 workflow 的所有 thread 共享一个 worker 进程。`concurrency` 控制进程内的并发 async task 数。
```yaml
workflows:
cleanup:
concurrency: 1
overflow: drop
code-review:
concurrency: 3
overflow: queue
```
- 进程数 = workflow 种类数,不会膨胀
- 有活跃 thread 时启动,所有 thread 完成后退出(或保持待命一段时间)
- 崩溃后 engine respawn worker,从持久化状态恢复 thread
#### 进程对比
| | Sense Worker | Workflow Worker |
|---|---|---|
| 粒度 | 按 group | 按 workflow 种类 |
| 内部并发 | 多 sense 的 compute | 多 thread(async) |
| 生命周期 | 永驻 | 有活跃 thread 时存活 |
| 崩溃恢复 | respawn,继续调度 | respawn,从持久化状态恢复 |
| 状态 | 无 | 持久化状态机 |
### 5.2 主进程 ↔ Worker 通信
子进程通过 stdio/IPC 与主进程通信,协议极简:
```typescript
// 主进程 → worker
{ type: 'compute', sense: 'cpu-usage' } // 触发一次 compute
{ type: 'shutdown' } // 优雅退出
// worker → 主进程
{ type: 'signal', sense: 'cpu-usage', payload: ... } // compute 完成
{ type: 'error', sense: 'cpu-usage', error: ... } // compute 失败
{ type: 'ready' } // worker 启动完成
```
主进程不关心 payload 的内容,只负责转发 signal 到 bus。
### 5.3 Sense 运行时配置
```yaml
senses:
cpu-usage:
group: system
throttle: 5s # 无论怎么触发,最快 5 秒一次
timeout: 3s # compute 超时则 abort,记录错误 signal
active-tasks:
group: tasks
throttle: 10s
timeout: 30s
```
- **throttle**:最小触发间隔,防止高频 signal 导致的无意义重算
- **timeout**:compute 超时上限,超时不杀 daemon,只记录错误 signal
### 5.4 Thread 状态持久化与恢复
Thread 是状态机。每一步转换之前持久化状态,确保崩溃后可恢复。
```typescript
interface ThreadState {
threadId: string
workflowId: string
currentStep: string // 状态机当前节点
context: Record<string, any> // 累积的上下文
history: CommandEvent[] // 已完成的步骤记录
status: 'running' | 'crashed' | 'completed' | 'failed'
updatedAt: number
}
```
执行流程:
```
moderator 决定下一步 → 持久化状态 → execute role → 写结果 → 下一步
如果这里挂了
恢复时从这一步重试
```
恢复流程:
```
1. engine 检测到 workflow worker 挂了
2. respawn worker
3. worker 启动时扫描 db,找到 status=running 的 thread
4. 从 currentStep 恢复执行
```
恢复要求 Role 的 execute 尽量幂等。非幂等的 Role 可以标记:
```typescript
roles: [
{ id: 'analyzer', execute: ..., idempotent: true }, // 可安全重试
{ id: 'deployer', execute: ..., idempotent: false }, // 崩溃后标记 crashed,等介入
]
```
### 5.5 热更新
主进程 watch `~/.uncaged-nerve/` 文件变化,按类型处理:
| 变化 | 处理 |
|------|------|
| sense ts 文件修改 | 等当前 compute 完成 → kill 对应 group worker → respawn |
| workflow ts 文件修改 | 等所有活跃 thread 完成 → kill worker → respawn |
| nerve.yaml 修改 | 主进程重新解析,diff 变更(见下) |
nerve.yaml diff 处理:
| 变更 | 处理 |
|------|------|
| 新增 sense | spawn worker(或加入已有 group) |
| 删除 sense | 从 worker 中移除 |
| 修改 reflex | 更新 scheduler,不动 worker |
| 修改 throttle/timeout | 更新 scheduler |
| 修改 group | kill 旧 worker,按新分组 respawn |
### 5.6 错误处理
单个 sense 或 workflow 的失败不影响其他组件。
| 情况 | 处理 |
|------|------|
| compute 抛异常 | 记录错误 signal,下次触发重试 |
| compute 超时 | abort,记录超时 signal |
| 存储写入失败 | 记录错误,不发 signal(未成功产出) |
| nerve.yaml 语法错误 | daemon 拒绝加载,保持当前配置 |
| sense ts 语法错误 | 该 group worker 加载失败,其他 group 正常 |
| workflow worker 崩溃 | 幂等 thread 自动恢复,非幂等标记 crashed |
### 5.7 自监测
Daemon 用自己的 Sense 机制监测自身健康:
```typescript
// senses/nerve-health.ts
export async function compute(): Promise<NerveHealth> {
return {
uptime: process.uptime(),
activeSenses: scheduler.activeSenseCount(),
pendingComputes: scheduler.pendingCount(),
lastErrors: errorLog.recent(10),
memoryUsage: process.memoryUsage(),
}
}
```
但 daemon 自身挂了这个 sense 也不会跑,所以外层 systemd/pm2 是最后防线:
```
systemd (最后防线,只管进程存活)
└─ nerve-engine (自监测 + 丰富健康数据)
└─ nerve-health sense → 可产出 signal 通知外部
```
## 6. 函数式性质
用 Haskell 描述核心类型和函数:
@@ -243,7 +450,7 @@ execute :: Role -> Prompt -> IO CommandEvent -- 有副作用 ❌
| `execute` (Role) | IO — 调 API、改文件 |
| Reflex 条件判断 | 纯数据,引擎硬编码 |
## 6. 引擎架构
## 7. 引擎架构
```
┌──────────────────────────────────────────────┐
@@ -272,7 +479,7 @@ Daemon 的职责极简:
3. 将结果写入对应 sense 的存储
4. 如果结果是 `ThreadStart`,发起 Workflow Thread(post-MVP)
## 7. 依赖关系与生命周期
## 8. 依赖关系与生命周期
### 依赖图
@@ -295,7 +502,7 @@ Reflex ──→ Sense ──→ Sense (复合依赖)
清理是可选的离线 GC 过程。
## 8. 用户本地配置
## 9. 用户本地配置
用户的 nerve 实例位于 `~/.uncaged-nerve/`,作为一个 local git repo 管理:
@@ -332,7 +539,7 @@ Reflex ──→ Sense ──→ Sense (复合依赖)
- **data/blobs/** — CAS(Content-Addressable Storage),sha256 前两位分片目录,sense 存大对象时写 blob 拿 hash,db 里只存 hash 引用
- **data/ 和 node_modules/ gitignored** — 只有逻辑和配置进版本控制
## 9. MVP 范围
## 10. MVP 范围
| 范围 | 状态 |
|------|------|
@@ -341,7 +548,7 @@ Reflex ──→ Sense ──→ Sense (复合依赖)
| Nerve Daemon(读 yaml,按条件调 compute) | ✅ MVP |
| Workflow(Role + Moderator + Thread) | ❌ Post-MVP |
## 10. 设计原则
## 11. 设计原则
1. **Sense 是唯一的一等公民** — 原始采样和派生计算统一为 Sense
2. **计算与触发解耦** — compute 不知道自己什么时候被调用