docs: add §5 runtime model (process isolation, persistence, hot reload, error handling)
This commit is contained in:
@@ -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 不知道自己什么时候被调用
|
||||
|
||||
Reference in New Issue
Block a user