AGENTS.md@src/host/service · git:20260922.109483f · 2026-09-22 · sha256 9a17b19df76f4cef

AGENTS.md@src/host/service git:20260922.109483fA

Immutable. This exact content never changes and is served at /api/v1/blob/9a17b19df76f4cef.

# Service Module Rules

## 适用范围

适用于 `src/host/service/`。本文件定义模式二 API Service 模块特有的稳定治理边界与修改规则;通用规范遵循根 `AGENTS.md`,测试规范遵循 `tests/AGENTS.md`。

本文件描述治理原则,不是源码结构说明。文件移动、重命名或内部重构后,只要职责、事实源、依赖方向与对外契约未变,本文件不随之修改。

## 核心职责

让一个工作流以**常驻 HTTP 服务**的形态对外提供能力,并管理它的完整生命周期:

- 服务子进程的生命周期:启动、停止、崩溃判定、随主进程重启的自动恢复;
- 端口分配与子进程的组合挂载产物(服务进程如何被装配起来);
- 服务进程内的对外协议:请求解析、鉴权、并发上限、流式响应、错误映射;
- 多用户隔离:外部用户标识到会话的稳定映射与持久化。

判断归属:该修改是否改变**服务进程的生命周期、进程间协作契约、对外协议或用户隔离语义**?若主要属于工作流语义、编排推进、图判定、界面展示,应放到对应模块。

## 非职责

- 不实现编排运行时与 Agent 执行——服务进程只是复用宿主能力;
- 不定义工作流文档的形状与合法性(归共享契约与图模块);
- 不拥有持久化原语,也不拥有服务文档以外的资源文件语义(归存储与各模块自身);
- 不实现 GUI 端点(属 remote),不渲染界面(属 client);
- 不直接读写其他模块的内部状态。

## 进程边界

本模块横跨**两个进程侧**,两侧的规则不同:

- 主进程侧:子进程生命周期、端口分配、挂载产物渲染;
- 服务进程侧:对外协议、会话隔离、编排调用。

两侧只允许通过**磁盘事实**(服务文档、挂载产物文件)与**进程启动参数/标准流**协作:

- 禁止依赖内存共享、模块级单例或跨进程函数调用;
- 服务进程侧不得反向操作主进程侧的状态(例如"自己把自己拉起"会造成子进程无限复制);
- 两个进程侧各自可独立测试:主进程侧的进程创建必须经可注入的子进程工厂,服务进程侧的编排依赖必须经抽象缝注入。

## 生命周期契约

- 服务状态的事实源是**持久化的服务文档**,内存登记只是存活子进程的快速索引;两者不一致时以文档为准并自愈;
- 启动幂等:同一服务在存活或启动中时再次启动必须被拒绝(明确错误码),不得产生第二个子进程、不得重复分配端口、不得双写挂载产物;
- 停止必须收敛:先请求优雅退出,超过宽限期后强制终止**整棵进程树**(只杀壳进程会留下孤儿并占用端口);
- 非主动退出(崩溃)必须被判定并记录,且不得覆盖"已主动停止"的终态;
- 随主进程重启的自动恢复只针对"文档状态为运行中"的服务,且必须容忍端口被占用(重新分配),单个恢复失败不得阻断其余;
- 主进程卸载时必须尽力释放全部子进程与其定时器;重复调用清理必须安全。

## 并发与幂等

- 启动的互斥必须在**第一个异步点之前**同步登记,否则并发的两次启动会同时通过检查并各自创建子进程(孤儿进程 + 端口泄漏);
- 停止可被重复调用:重复调用不得泄漏或重复排程强杀定时器(宽限期以最后一次请求为准);
- 关闭/退出事件与主动停止存在竞态:清理必须幂等(内存条目、定时器、文档状态各自只处理一次);
- 会话映射的并发首解析必须去重(同一用户标识只产生一个会话),且"读改写"必须落在同一临界区内,不得跨锁执行。

## 资源释放

- 子进程的 stdout/stderr 必须持续消费,否则管道写满会阻塞子进程;转发失败(控制台不可用)必须降级而非放大;
- 强杀定时器必须在进程退出、条目注销、模块卸载三条路径上都被清理;
- 端口探测必须及时释放监听句柄,不得让探测本身占用端口;
- 任何后台等待都必须有上界:请求等待超时后必须**停止后台编排运行**并给出明确错误;客户端断开必须传播为取消而不是继续占用并发槽。

## 外部依赖显式

- 外部可执行文件(子进程命令)、网络服务(webServer)、官方能力(会话/Agent 创建)一律经运行时守卫 + 可注入缝使用,缺失时给出明确错误或降级告警,不得静默失效;
- 禁止在没有明确职责的位置读取环境变量或全局进程状态;进程环境继承、工作目录等启动参数必须集中在一处构造,便于审计;
- 对外协议层(HTTP)只做"请求/响应 ⇄ 核心调用"的转换与错误映射,**不得包含业务判断**;核心逻辑必须可在无 HTTP 的情况下测试。

## 单一事实源

- 服务状态字段只有一处写者;运行字段的更新必须"读最新值后合并写",不得整文档覆盖(并发写入会互相丢更新);
- 每个外部用户标识到会话的映射只有一处实现,且持久化文件按服务隔离;
- 服务进程的挂载产物只有一处渲染实现,产物是"配置事实"而非"可手改文件";
- 一个概念只允许一处字面量:错误码、状态取值、端点路径的取值来源必须唯一。

## 公共边界

- 模块外只允许经模块的**公共入口**导入,禁止直接引用模块内部文件;
- 只把真实、稳定、可维护的契约放进公共入口(生命周期管理器与其依赖缝、端口分配、挂载产物渲染、会话映射、协议核心与 HTTP 注册);
- 内部实现(进程树终止、标识消毒、密钥哈希、请求体读取、响应写出、会话事件读取助手)不进入公共入口;禁止「全量 barrel」;
- 新增公共符号前先判断它是否表达对外稳定契约;放宽入口比收紧入口的代价更高。

## 依赖边界

- 只允许依赖共享契约层、存储门面、编排运行时的**接口**与图判定;
- 不得依赖 agent / tools / remote / client 的实现;
- 服务进程入口插件属包级二级入口(对外发布路径固定),其进程内实现属本模块;入口只做装配与协调,不承载业务语义;
- 依赖方向单向:调用方依赖本模块的生命周期与协议能力,本模块不反向依赖任何调用方。

## 契约稳定性

- 错误码、服务状态取值、对外协议(端点路径、请求/响应字段、鉴权方式)属公共契约:修改必须同步调用方、测试与文档;
- 服务的持久化字段与映射文件形态属兼容契约:新增字段默认可选;退役字段只保留读取兼容并说明剥除时机;
- 挂载产物是子进程的装配契约:其字段增删必须同时检查渲染方与消费方的解析;产物变更属行为变化,必须记录旧行为 → 新行为 → 原因 → 影响范围 → 测试变化。

## 模块内部组织

- 文件结构不是架构契约,禁止为"看起来模块化"机械拆分;拆分依据只能是职责、变化原因、进程边界与测试边界;
- 文件命名必须表达**稳定职责与所属维度**;同名概念(尤其是跨进程侧的"会话"相关概念)必须能一眼区分职责,名称失真的文件应立即重命名并同步全部引用;
- 不得创建 `utils.ts`、`helpers.ts`、`common.ts`、`shared.ts`、`misc.ts` 等无明确职责的聚合文件;
- 内部文件之间使用相对路径导入,公共入口不含实现。

## 修改前检查

1. 改动属于主进程侧还是服务进程侧?两侧的契约是否都要同步?
2. 是否改变服务生命周期(启动/停止/崩溃/恢复)或错误码?受影响的调用方与测试是哪些?
3. 是否引入新的并发窗口(跨 await 的检查-再动作)?互斥登记是否在第一个异步点之前?
4. 是否新增未释放的资源(定时器、监听器、子进程、管道)?
5. 是否把业务判断放进协议层,或把进程装配细节放进核心逻辑?
6. 是否产生第二份事实源(重复的状态写入、重复的校验、重复的映射)?
7. 是否越界进入其他模块职责(工作流语义、GUI 端点、界面)?

## 验证

除根 `AGENTS.md` 的标准命令外,本模块改动必须额外确认:

- 并发启动、重复停止、崩溃/退出竞态、自动恢复失败等路径有测试锁定(用可注入的子进程与端口工厂,不真实 fork);
- 生命周期的状态迁移(含"文档状态与内存登记不一致")有测试锁定;
- 协议层的参数错误、鉴权失败、并发超限、超时、客户端断开均有测试锁定,且断言的是可观察的响应与调用副作用;
- 目录与文件路径的**不真实执行**保证:测试不得真实运行子进程命令、不得真实强杀进程;
- 公共入口导出面未意外扩大、无旧路径残留、无循环依赖、无重复实现。

## 核心原则

1. 两个进程侧只经磁盘事实与启动参数协作,不共享内存。
2. 服务状态以持久化文档为事实源,内存登记只是索引。
3. 启动互斥在第一个异步点之前,停止必须收敛整棵进程树。
4. 一切后台等待有上界,取消(超时/断开)必须传播。
5. 协议层只做转换与错误映射,核心逻辑可脱离 HTTP 测试。
6. 模块外只经公共入口协作,内部实现不外溢。
7. 生命周期与协议契约变化必须显式记录并锁定测试。