# Agent 业务模块接入规范

> **版本**: v1.0
> **最后更新**: 2026-05-19
> **下次复查触发条件**: 业务模块对 agent 暴露的工具数 > 50 / 新增 surface（如 Teams 真实化） / inputSchema 升级触发 / AgentContext 字段升级触发 / 季度复查
> **适用范围**: 任何业务模块（`backend/src/modules/*` / `backend/src/engines/*`）向 FFAI Agent 暴露能力时，必须遵守本文档全部规则。
> **关联**:
> - [`docs/modules/agent/02-architecture.md §1.3 工具系统`](../modules/agent/02-architecture.md) — Tool Registry 设计事实源
> - [`docs/standards/16-data-layering-and-metadata-policy.md`](16-data-layering-and-metadata-policy.md) — 跨模块写主数据走事件流（本文档接入标准 §3 不可暴露方法清单与之一致）
> - [`docs/standards/05-development-workflow.md`](05-development-workflow.md) — 测试金字塔三层（L1 集成测试在本文档接入标准 §8 强制）
> - Issue #410（本规范的方向 review）/ Issue #410 issuecomment-4528（11 项决策）

> ⚠️ **v1.0 代码层生效条件**：本规范引用的 `@AgentTool` 装饰器 / `AgentToolsModule.forFeature` / `@modules/agent/registry/*` import 路径 / CI ESLint 卡口 / `docs/agent-surface-area.md` 自动生成 由 **PR-A**（#410 PR-A 装饰器 + forFeature + 包装层 + 30 工具迁完 + CI lint）落地。**本规范合入到 PR-A 合入之间的窗口期**，新业务模块仍按现有 `backend/src/modules/agent/tools/` 旧路径开发；PR-A 合后再批量迁。开发者按 v1.0 规则写代码必须确认 PR-A 已合，否则会撞 module-not-found 错。

---

## 0. 为什么需要这份文档

### 0.1 反模式：agent 反向 import 业务模块

FFAI Agent Phase 1 已完成底层协议契约（`packages/agent-protocol` + ToolDescriptor + 工具四轨执行器）。但**"业务模块如何向 agent 暴露能力"的接入规范缺失**，导致**反向耦合**默认成立：

```typescript
// ❌ 反模式：agent 模块反向 import 业务模块
// backend/src/modules/agent/tools/approval-submit.tool.ts
import { ApprovalService } from '@engines/approval/approval.service';

@Injectable()
export class ApprovalSubmitTool implements AgentTool {
  constructor(private readonly approvalService: ApprovalService) {}
  readonly descriptor: ToolDescriptor = { ... };
  async invoke(inv: ToolInvocation): Promise<ToolResult> { ... }
}
```

**这个反模式的具体后果**：

1. **耦合方向反了** — agent 模块 import 每个业务模块的 Service；业务模块自己不知道有哪些能力被 agent 暴露
2. **加工具必改 agent 模块** — 业务模块接 agent 都要往 `agent/tools/` 塞文件，agent 越长越胖
3. **缺自描述事实源** — 业务模块对 agent 暴露哪些能力，没有声明位置；review 业务 PR 看不到对 agent 影响
4. **审计、限流、destructive 二次确认散落** — 每个 wrapper class 自己写一遍
5. **权限模型割裂** — `availability.permissions` 手写 RBAC 字符串，跟业务模块自己的权限点无对齐机制

### 0.2 目标形态：业务模块自持声明 + agent 包装层统一处理

```typescript
// ✅ 目标形态
// engines/approval/approval.service.ts
@Injectable()
export class ApprovalService {

  @AgentTool({
    name: 'approval_submit',
    description: '启动审批流程',
    inputSchema: { ... },
    availability: { surface: [...], permissions: [APPROVAL_PERMISSIONS.CREATE] },
    writeAction: true,
    destructive: false,
  })
  async startApprovalForAgent(dto: StartApprovalDto, ctx: AgentContext) {
    return this.startApproval(dto, ctx.userId);
  }
}

// engines/approval/approval.module.ts
@Module({
  imports: [
    AgentToolsModule.forFeature([ApprovalService]),
  ],
})
export class ApprovalModule {}
```

**业务模块对 agent 暴露什么，在业务模块自己的代码里就能看到**。审计 / 限流 / 错误分档 / destructive 二次确认 / metric 全部由 ToolRegistry.invoke **包装层**统一处理，业务方法保持薄实现。

---

## 1. 总原则（架构层不变量）

### 1.1 装饰器 + forFeature 显式收集

业务模块通过两处声明对 agent 暴露能力：

| 位置 | 内容 | 谁看 |
|---|---|---|
| **service 方法上** | `@AgentTool({ name, description, inputSchema, availability, ... })` 装饰器 | 业务工程师改方法时立刻知道"我是 agent 工具" |
| **module 上** | `imports: [AgentToolsModule.forFeature([Service1, Service2])]` 显式登记要扫的 service | 看模块就知道"本模块对 agent 暴露几条能力"+ 显式扫描边界 |

**为什么不用 NestJS DiscoveryService 全局扫**：

- 启动失败诊断差（反射报错栈帧难看）
- IDE Find Usages 跳不动（装饰器是数据非引用）
- 启动性能（扫数百 provider）
- 错配整应用挂（违反 fail-fast）
- ToolDescriptor 字段演化时 TypeScript 类型检查弱

详细取舍见 #410 plan-review Q1.1（[`.learnings/2026-05-19-410-plan-review.md`](../../.learnings/2026-05-19-410-plan-review.md)）。

### 1.2 ToolRegistry.invoke 包装层

所有 `@AgentTool` 调用经过统一包装层（不允许业务方法自己写 audit / quota）：

```
ToolRegistry.invoke(name, invocation)
  ├─ 1. audit.before (同步 DB)
  ├─ 2. quota.check (同步)
  ├─ 3. destructive + mode-aware confirm (planMode/permissionMode)
  ├─ 4. 反射调 service.method(input, ctx)
  ├─ 5. 异常分档 (3 档) + sanitize
  ├─ 6. audit.after (异步队列 + 3 次指数退避 + DLQ)
  └─ 7. metric upload
```

**业务方法保持薄实现**：接 input + ctx → 干业务 → 返回结果。审计 / 限流 / 错误处理 / metric 全部不入侵业务逻辑。

### 1.3 异常分档

包装层按异常类型分三档处理：

| 档 | 触发 | 处理 | LLM 看到 |
|---|---|---|---|
| 1 业务校验 | `HttpException` status < 500（BadRequest / NotFound / UnprocessableEntity 等） | `ToolResult.ok=false` + `errorMessage = sanitize(err.message)` | 看到原因可自纠重试 |
| 2 安全 | `ForbiddenException` / `UnauthorizedException` | `ToolResult.ok=false` + `errorMessage='权限不足'` + `audit.security(详细原因)` | 知道被拒；详细仅 audit |
| 3 系统 | 其他（DB 挂 / 业务方法抛 Error） | `audit.systemError` + `metric.systemError.inc()` + `throw err` | 不到 LLM；session 进 error 态 |

**sanitize 黑名单**（必须脱敏才进 errorMessage）：

- SQL 关键字（`SELECT * FROM` / `INSERT INTO` / `UPDATE ... SET` / `DELETE FROM` / `JOIN ... ON` ...）
- 表名（`@@map` 或 prisma model 名匹配）
- 文件绝对路径（`/var/...` / `/Users/...` / `/home/...` / `C:\\...`）
- 堆栈跟踪（`at Function.xxx (...)` / `at async ...`）
- IP 地址（IPv4 + IPv6 正则）
- email（`\S+@\S+\.\S+`）
- token / API key（`Bearer eyJ...` / `sk-...` / `gha_...`）

---

## 2. 接入 4 步速查

新业务模块要让 agent 调用，4 步完成：

```typescript
// Step 1: 业务 service 引入装饰器和上下文类型（唯一允许 import 的 agent 入口）
import { AgentTool, AgentContext } from '@modules/agent/registry';

@Injectable()
export class MyService {

  // Step 2: 在希望暴露的方法上贴 @AgentTool，第二参数收 AgentContext
  @AgentTool({
    name: 'my_action',                                 // snake_case <domain>_<action> 两段制
    description: 'AI 看到的人类语言描述',                  // 必填，i18n 由 agent 侧处理
    inputSchema: {                                     // 扁平 3 类型（string/number/boolean）
      foo: { type: 'string', required: true, description: '...' },
      bar: { type: 'number', required: false },
    },
    availability: {
      surface: ['web', 'desktop'],                    // 可见 surface
      permissions: [MY_PERMISSIONS.READ],             // 引用模块自己的 RBAC 常量，禁止字面量
    },
    writeAction: false,                                // 是否写动作
    destructive: false,                                // 是否破坏性（删除/不可逆）
  })
  async myActionForAgent(input: MyInput, ctx: AgentContext): Promise<MyOutput> {
    // 业务方法保持薄：拿 ctx.userId / ctx.organizationId 直接调内部已有方法
    return this.myAction(input, ctx.userId);
  }
}
```

```typescript
// Step 3: 在 module 显式登记要被扫描的 service
import { AgentToolsModule } from '@modules/agent/registry';

@Module({
  imports: [
    AgentToolsModule.forFeature([MyService]),         // 列出本模块对 agent 暴露的 service
  ],
  providers: [MyService],
  exports: [MyService],
})
export class MyModule {}
```

```typescript
// Step 4: 写 L1 集成测试（HTTP → 真 DB）覆盖：正常路径 + 权限拒绝 + 输入校验失败
// 详见 §3.8 测试约定
```

启动后自动：
- 工具出现在 `ToolRegistry.list()`
- 工具出现在 `docs/agent-surface-area.md`（CI 卡漂移）
- 任何 `invoke` 自动走包装层（audit / quota / destructive / 异常分档 / sanitize / metric）

---

## 3. 12 条接入标准（细则）

### 3.1 命名约定

- **snake_case `<domain>_<action>` 两段制**（CI 校验）
- 例：`approval_submit` / `project_query` / `form_draft` / `meeting_book`
- 禁用：dot.case（`approval.submit`）、CamelCase（`ApprovalSubmit`）、kebab-case（`approval-submit`）、单段（`submit`）、三段以上（`approval_form_submit`）
- `<domain>` 通常是模块名简写（`approval` / `devtracker` / `form` / `meeting`）
- 历史一致：现网 30 工具已全部 snake_case 两段制

### 3.2 方法签名标准

```typescript
async <name>ForAgent(input: <DTO>, ctx: AgentContext): Promise<<ResultDTO>>
```

- **第一参数** = 业务 DTO（带 class-validator 装饰器）
- **第二参数** = `AgentContext` 类型（**禁止省略**，即使方法不用 ctx）
- **返回值** = 普通业务结果对象（包装层负责转 `ToolResult`，业务方法不感知）
- 方法名建议加 `ForAgent` 后缀，跟业务模块自己用的同名方法区分

### 3.3 AgentContext 标准字段

```typescript
interface AgentContext {
  readonly userId: string;          // 当前用户 ID（JWT 解析）
  readonly organizationId: string;  // 当前组织 ID
  readonly surface: 'web' | 'desktop' | 'mobile' | 'teams' | 'cli';
  readonly sessionId: string;       // agent session ID
  readonly turnId: string;          // agent turn ID
  readonly trace: { traceId: string };
}
```

**仅含 6 字段**。业务方法若需 `locale` / `permissionMode` / `planMode` / `permissions` / `departmentId` / `regionId`，**自行从 session 二次查**：

```typescript
// 标准代码片段：从 ctx 拿到 session 再查缺失字段
async myActionForAgent(input: MyInput, ctx: AgentContext) {
  const session = await this.agentSessions.getById(ctx.sessionId, ctx.userId);
  const locale = session.locale ?? 'zh-CN';
  const permissions = session.permissions ?? [];
  // ... 业务逻辑
}
```

**未来升级触发条件**（自动开升级 issue）：
- ≥ 3 个业务方法 grep `session.locale` / `session.permissionMode` / `session.planMode`
- 或新增 surface 时缺字段语义模糊

### 3.4 哪些方法可暴露 / 哪些不能

| ✅ 可暴露 | ❌ 不可暴露 |
|---|---|
| 业务幂等（同输入同输出，重试安全） | 跨租户读 / 写 |
| 自带审计记录（不依赖 agent 包装层） | 直改 schema / 元数据 |
| 不持久敏感数据（密码 / token / 内部 ID） | 删除类操作（除非 `destructive: true` 显式声明） |
| 走业务模块自己的 RBAC | 直接调底层 prisma 跨模块写 L2 主数据（违反 [`16-data-layering`](16-data-layering-and-metadata-policy.md) §3.2 事件流） |
| 业务结果可序列化 JSON | 返回流（Stream）/ 大文件二进制 |

**跨模块原则**：模块 A 的 service 不能 `@AgentTool` 一个直接写模块 B 主数据的方法——必须 emit 事件让 B 自己消费（参考 [`16-data-layering`](16-data-layering-and-metadata-policy.md) §3.2）。

### 3.5 权限映射

`availability.permissions` 必须引用业务模块自己的 RBAC 点**常量**：

```typescript
// ✅ 正确：引用常量
import { APPROVAL_PERMISSIONS } from '@engines/approval/permissions.constants';

@AgentTool({
  availability: { permissions: [APPROVAL_PERMISSIONS.CREATE] },
})

// ❌ 禁止：字面量
@AgentTool({
  availability: { permissions: ['approval:create'] },  // ESLint fail
})
```

**CI 校验**：
- ESLint 规则：`availability.permissions` 必须是 import 的常量，禁字符串字面量
- L1 测试：所有 `@AgentTool` 的 permissions 在 RBAC 点表里真实存在

### 3.6 错误处理标准

**业务方法直接 throw NestJS 异常**，**禁止业务方法自己返回 `ToolResult.ok=false`**（违反 Q1.5 "业务方法保持薄"）：

```typescript
// ✅ 正确
async myActionForAgent(input, ctx) {
  if (!input.foo) throw new BadRequestException('foo 必填');
  if (!hasPermission) throw new ForbiddenException('user xxx 无权操作 tableXxx');
  return this.doBusinessLogic(input);
}

// ❌ 禁止
async myActionForAgent(input, ctx) {
  if (!input.foo) return { ok: false, errorMessage: 'foo 必填' };  // 包装层接管，业务不自管
}
```

错误码 / 错误消息 i18n 由业务模块自己负责，**包装层只做异常类型 → 三档策略映射**（见 §1.3）。

### 3.7 审计标准

**所有 `@AgentTool` 调用强制经过 audit-system**——包装层自动处理，业务方法**不写 audit 代码**：

```typescript
// ❌ 禁止：业务方法自己写 audit
async myActionForAgent(input, ctx) {
  await this.audit.log({ user: ctx.userId, action: 'my_action', input });  // 包装层已做，重复
  return this.doBusinessLogic(input);
}
```

audit 记录字段：
- `agent_session_id` / `agent_turn_id` / `tool_name`
- `input` （input 全量；包装层负责屏蔽 `descriptor.inputSchema` 标 `sensitive=true` 的字段——v1.0 暂不支持，全量记录，**升级触发条件见 §9**）
- `output` （ToolResult.output 全量）
- `duration_ms`
- `verdict`（ok / business_error / security_error / system_error）

> **v1.0 未强制字段**：`agent_confidence` / `routing_tier` / `modified_action` 等 #409 引入字段后续扩展，详见 §9 复查触发条件。

### 3.8 测试约定（L1 集成测试）

每个 `@AgentTool` 至少一个 L1 集成测试，覆盖：

- **正常路径**：合法 input + 合法 ctx → ToolResult.ok=true + 业务效果（DB 变更 / 副作用）符合预期
- **权限拒绝**：用户缺所需 RBAC 点 → ToolResult.ok=false + errorMessage='权限不足' + audit.security 含详细原因
- **输入校验失败**：input 缺必填字段 / 类型错误 → ToolResult.ok=false + errorMessage 含字段名

测试位置：`testing/backend/integration/<module>/<tool-name>.test.ts`

L1 集成测试规范见 [`docs/standards/05-development-workflow.md`](05-development-workflow.md) § 测试金字塔。

### 3.9 数据返回约束

`@AgentTool` 方法返回必须：

- **可序列化 JSON**（无函数、无 Symbol、无循环引用、无 Date 直接返）
- **字段命名 camelCase**
- **不含敏感字段**：密码 / token / 内部 ID（如 prisma 主键的内部 UUID 若不暴露给用户）
- **大对象上限 100KB**（包装层校验，超限 truncate + 提示 LLM "结果过大请收窄查询"）
- **日期字段** ISO 8601 字符串（`2026-05-19T13:35:50+08:00`）不传 Date 对象

### 3.10 安全（destructive 二次确认）

`destructive: true` 的工具表示**不可逆操作**（删除 / 覆盖 / 提交到外部系统）：

```typescript
@AgentTool({
  name: 'my_destroy_action',
  destructive: true,
  writeAction: true,
  // ...
})
```

包装层行为：
- `permissionMode = ASK_EVERY_TIME` → 调用前**强制人工确认**（confirmer pattern）
- `permissionMode = READ_ONLY` → 工具在 `list()` 不可见，invoke 直接 Forbidden
- 批量操作必须 `descriptor.maxBatchSize` 字段限制单次调用上限（v1.0 暂不强制，工具自行限制）
- 速率限制由 `quota.check` 在包装层处理

**审计**：destructive 工具调用强制单独 audit channel（`agent_audit_destructive` 表，**v1.0 暂复用主 audit 表**，加 `destructive=true` 标记字段）。

### 3.11 i18n

- `description` 字段是**英文或中文均可**——agent 侧根据 user locale 给 LLM 加 prompt 包装（不要求业务模块多语言）
- 业务方法返回 `errorMessage` 时**应该走业务模块自己的 i18n key**（如有），不强制 v1.0；硬编码中文 v1.0 可接受
- 业务方法返回的用户可见结果（output 字段）**应该按 session.locale 渲染**——业务方法自行查 `session.locale`（§3.3 标准代码片段）

### 3.12 schema 演化策略

`@AgentTool` descriptor 字段变更兼容策略：

- **加字段**（如 v1.1 加 `destructive`）：可选字段 → 老工具不改也兼容
- **删字段**：禁止；如要弃用某字段，保留至少 6 个月 deprecation 期，在 ToolDescriptor 加 `deprecated: true` + audit warning
- **改字段类型**：禁止；新字段名上加新类型，老字段 deprecate
- **改 name**：等同删除 + 新增；必须走 alias 表（`{ oldName: newName }`）+ 双向解析 + 6 个月观察期 + 历史 session 兼容窗口
- 现网 30 工具命名已稳定，**v1.0 不主动改名**

---

## 4. inputSchema 约束（v1.0 限制）

**v1.0 `inputSchema` 仅支持扁平 `string | number | boolean`** —— 不支持嵌套对象、数组、enum、oneOf 等 JSON Schema 高级特性。

```typescript
@AgentTool({
  inputSchema: {
    title: { type: 'string', required: true, description: '...' },
    amount: { type: 'number', required: true },
    isUrgent: { type: 'boolean', required: false },
    // ❌ 不支持: nestedField: { type: 'object', properties: { ... } }
  },
})
```

### 4.1 嵌套输入的过渡方案：string + JSON.parse

嵌套结构输入用 `type: 'string'` + 业务方法 `JSON.parse`：

```typescript
@AgentTool({
  inputSchema: {
    variables: {
      type: 'string',
      required: false,
      description: 'JSON 字符串：含字段 amount / city / days 等流程变量',
    },
  },
})
async myActionForAgent(input: { variables?: string }, ctx: AgentContext) {
  let variables: Record<string, unknown> = {};
  if (input.variables) {
    try {
      variables = JSON.parse(input.variables);
    } catch (err) {
      throw new BadRequestException(`variables JSON 解析失败：${(err as Error).message}`);
    }
  }
  // ... 业务逻辑 + 业务层结构校验（推荐 ajv / class-validator）
}
```

**这是 v1.0 的临时约定，不是反模式**。业务方法 invoke 首行必须 JSON.parse + 业务层结构校验。

### 4.2 升级触发条件

满足任一自动开 issue 升级 inputSchema 到 JSON Schema 子集：

- ≥ 3 个工具用 `type: 'string'` 塞 JSON 字符串
- `agent_tool_invoke.input_parse_error` 监控指标 > 5%
- 新工具明确需要嵌套对象 / 数组输入（如 AI Form Genesis 的 form spec）

升级后必须支持：object / array / enum / required 列表（4 项基础特性）；预选 schema validator：ajv。

---

## 5. 反模式（明确禁止）

| 反模式 | 为什么禁止 | 正确做法 |
|---|---|---|
| ❌ agent 模块 import 业务模块 | 反向耦合 + 无自描述事实源 | 业务模块 `@AgentTool` + `forFeature` |
| ❌ 业务模块 import `@modules/agent/services/*` | 跨过 registry 唯一入口 | 仅 import `@modules/agent/registry`（CI lint 卡） |
| ❌ 业务方法自己写 audit | 反 DRY；30 工具写 30 次 | 包装层统一处理 |
| ❌ 业务方法返回 `ToolResult.ok=false` | 业务方法应保持薄；ToolResult 是包装层语义 | throw NestJS HttpException，包装层转换 |
| ❌ 业务方法 try/catch 所有异常 | 系统错误被吞 → audit 漏 → 监控缺 | 让异常抛出，包装层分档 |
| ❌ `availability.permissions: ['approval:create']` 字面量 | 权限点漂移无 CI 兜底 | 引用模块自己的 RBAC 常量（CI lint 卡） |
| ❌ 装饰器贴但 module 不 forFeature | 工具不可见无报错 | CI lint fail-fast |
| ❌ 跨模块写 L2 主数据 | 违反 [`16-data-layering`](16-data-layering-and-metadata-policy.md) §3.2 事件流 | emit 事件让目标模块自己消费 |
| ❌ 工具命名 dot.case / CamelCase | 现网 30 工具已 snake_case；改名爆炸半径大 | snake_case `<domain>_<action>` 两段制 |
| ❌ 业务方法签名省略 `ctx: AgentContext` | 包装层无法传上下文 | 即使不用 ctx 也必须保留参数 |

---

## 6. 现有 30 工具示例与迁移

### 6.1 业务模块工具（3 个反向依赖，PR-A 迁移目标）

| 当前位置 | 迁移到 | 当前 name | 装饰器后 name |
|---|---|---|---|
| `backend/src/modules/agent/tools/approval-submit.tool.ts` | `engines/approval/approval.service.ts` 方法 | `approval_submit` | `approval_submit`（不变） |
| `backend/src/modules/agent/tools/project-query.tool.ts` | `modules/devtracker/services/items.service.ts` 方法 | `project_query` | `project_query`（不变） |
| `backend/src/modules/agent/tools/knowledge-query.tool.ts` | `modules/knowledge-base/services/knowledge-qa.service.ts` 方法 | `knowledge_query` | `knowledge_query`（不变） |

### 6.2 Agent 内部工具（27 个，PR-A 全迁完）

`web_search` / `web_fetch` / `todo_write` / `task_create/update/list/stop` / `cron_create/list/update/delete` / `enter_plan_mode` / `exit_plan_mode` / `set_permission_mode` / `delegate_task` / `send_message` / `file_save` / `scratchpad_read/write` / `ask_user` / `file_read/write/list` / `clipboard_read/write` / `notify_push` / `shell_open_external` / `shell_exec`

迁移到 agent 自己的 service（按职责分组），**不再以 wrapper class 形式存在**。详细分组见 PR-A 实施工单。

### 6.3 物理位置

业务模块对 agent 暴露的所有装饰器、类型、forFeature 静态方法在：

```
backend/src/modules/agent/registry/
  agent-tool.decorator.ts        # @AgentTool() 装饰器
  agent-tools.module.ts          # AgentToolsModule.forFeature
  tool-registry.service.ts       # ToolRegistry + 包装层
  types.ts                       # AgentContext / AgentToolResult / ToolDescriptor
  index.ts                       # 业务模块唯一 import 入口
```

> **类型 shape 定义**：见 [`backend/src/modules/agent/tools/tool.types.ts`](../../backend/src/modules/agent/tools/tool.types.ts) 现网定义。**命名映射**（现网 → PR-A 后目标命名，字段语义不变）：
>
> | 现网（PR-A 前）| PR-A 后 | 内容 |
> |---|---|---|
> | `ToolInvocation` | `AgentContext`（加 `input` 拆出）| 调用上下文（userId / organizationId / sessionId 等）|
> | `ToolResult` | `AgentToolResult` | 返回结果（ok / output / errorMessage）|
> | `ToolDescriptor` | `ToolDescriptor`（不变）| 工具描述（name / inputSchema / availability ...）|
> | `AgentTool` interface | `@AgentTool()` 装饰器 | 工具接入入口形态翻转 |
>
> PR-A 重命名 + 物理位置从 `backend/src/modules/agent/tools/` → `backend/src/modules/agent/registry/types.ts`。


**业务模块统一 import**：

```typescript
import {
  AgentTool,
  AgentContext,
  AgentToolsModule,
  AgentToolResult,
} from '@modules/agent/registry';
```

**CI lint**：业务模块只能 import `@modules/agent/registry`，禁 `@modules/agent/{services,tools,providers,...}`。

---

## 7. 与其他 standards 的关系

| 文档 | 关系 |
|---|---|
| [`02-backend-architecture.md`](02-backend-architecture.md) | 后端三层（Core / Engines / Modules）；本规范让 Engines / Modules 通过 `@modules/agent/registry` 单向依赖 agent，不污染 Core |
| [`05-development-workflow.md`](05-development-workflow.md) | 测试金字塔；本规范 §3.8 强制 L1 集成测试 |
| [`06-documentation-system.md`](06-documentation-system.md) | 模块文档体系；新模块要加 `@AgentTool` 时**必须**先在模块 `07-api.md` 文档对应方法旁标注"对 agent 暴露" + 在 PRD 写明能力 |
| [`13-pr-description-spec.md`](13-pr-description-spec.md) | PR 描述模板；增加 `@AgentTool` 的 PR 必须在 PR 描述列出新增 / 变更 / 删除的工具清单（`docs/agent-surface-area.md` 自动生成可作为附件） |
| [`16-data-layering-and-metadata-policy.md`](16-data-layering-and-metadata-policy.md) §3.2 | 跨模块写主数据走事件流；本规范 §3.4 与之一致 |
| [`docs/modules/agent/02-architecture.md §1.3`](../modules/agent/02-architecture.md) | Tool Registry 事实源；本规范是 §1.3 的**接入开发者视角**补充 |

---

## 8. CI 强制清单

| 检查项 | 触发 | 失败动作 |
|---|---|---|
| 命名规范 `<domain>_<action>` 两段制 | 启动期扫描 + ESLint | fail-fast / lint error |
| `availability.permissions` 必须引用常量 | ESLint | lint error |
| 业务模块只能 import `@modules/agent/registry` | ESLint | lint error |
| service 含 `@AgentTool` 但 module 未 `forFeature` | 启动期扫描 + L1 测试 | fail-fast |
| 工具重名 | 启动期注册 | throw `Tool already registered` |
| `docs/agent-surface-area.md` 漂移 | CI git diff | PR push fail |
| `AgentContext` 6 字段完整 | L1 测试 | test fail |
| 异常分档行为 | L1 测试（业务/安全/系统三档） | test fail |
| sanitize 黑名单 fuzz | L1 测试 | test fail |
| 30 工具回归 | L1 测试 | test fail |

---

## 9. 复查触发条件

满足任一触发本文档复查 + 升级。

### 9.1 规模 / 覆盖面触发

- 业务模块对 agent 暴露的工具数 > 50
- 新增 surface（如 Teams / Mobile 真实化）
- inputSchema 升级触发（v1.0 §4.2 任一条命中）
- AgentContext 字段升级触发（v1.0 §3.3 任一条命中）

### 9.2 v1.0 暂缓项强制升级（hard 触发）

- **`sensitive` 字段屏蔽**（§3.7）：**任一 `@AgentTool` 的 `inputSchema` 含 `password` / `token` / PII 字段名**（input 字段名敏感检测；注：§3.9 是 output 数据返回约束，作用域不同——本触发针对 input 字段名，由包装层 sensitive 检测 + CI lint 实现） → 强制升级包装层 sensitive 屏蔽机制 + CI lint 卡 "input DTO 不得含 password/token 字段名"
- **`maxBatchSize` 强制**（§3.10）：**出现 ≥ 2 个 destructive 批量工具** → 强制 `descriptor.maxBatchSize` 字段必填 + CI 卡空值
- **`agent_audit_destructive` 独立表**（§3.10）：**destructive 工具单日累计调用量 > 100** → 拆 `agent_audit_destructive` 独立表 + 迁移现有 `destructive=true` 标记记录

### 9.3 业务驱动扩展（soft 触发）

- L3 Agent 主动提单（#409 阶段 3）启动 — 引入 `agent_confidence` / `routing_tier` / `modified_action` / `modification_diff` / `time_to_decision` 等字段到 AuditLog
- EU AI Act Article 14 / NIST AI RMF 合规要求更新
- GDPR Article 22 解释 / 实施细则变更

### 9.4 周期

- 季度复查（每季度末由 doc-review 安排）

---

## 10. 参考

- Issue #410 工程审查 11 项决策：[`.learnings/2026-05-19-410-plan-review.md`](../../.learnings/2026-05-19-410-plan-review.md)
- Review 评论：http://43.130.59.228/FFAIWorkspace/workspace/issues/410#issuecomment-4528
- 现有 ToolDescriptor 事实源：[`backend/src/modules/agent/tools/tool.types.ts`](../../backend/src/modules/agent/tools/tool.types.ts)
- 现有 ToolRegistry：[`backend/src/modules/agent/tools/tool-registry.service.ts`](../../backend/src/modules/agent/tools/tool-registry.service.ts)
- Agent 架构 §1.3 工具系统：[`docs/modules/agent/02-architecture.md`](../modules/agent/02-architecture.md)
