# Agent Sidecar 第二阶段 Agent Gateway 技术设计

创建日期：2026-09-02

状态：Draft

关联 PRD：`docs/design/2026-09-02-agent-codex-workspace-sidecar-prd.md`

前序设计：`docs/design/2026-09-02-agent-sidecar-phase-1-technical-design.md`

实现阶段：第二阶段——Agent Gateway、对外 Project 管理合约与 Core Server Audit

## 1. 目标

第二阶段把第一阶段已经提供的 Agent Sidecar `ProjectService` 接入 Agent Gateway。Agent
Gateway 从静态 Agent Provider 配置中识别 Managed Agent，为每个实例维护独立的 Sidecar
gRPC Client，并向上游提供统一的 gRPC、HTTP 和 OpenAPI Project 管理接口。

本阶段完成后，应能够通过 Agent Gateway：

1. 列出所有静态配置的 Agent，并返回 Provider、Project 管理能力和默认 Agent 标识；
2. 按 `agent_id` 将 Project、Global、文件和目录请求路由到正确 Sidecar；
3. 在对外 contract 中直接复用 Sidecar 的 Project entity，同时保持适合 HTTP Client 的扁平请求；
4. 只接受由 Kong 注入的可信 operator，不允许请求正文指定操作者；
5. 在文件系统 mutation 成功后，通过 Core Server `AuditService` 保存精简且不包含正文的审计记录；
6. 保持 Sidecar 不可用、revision 冲突和文件系统错误的稳定错误语义；
7. 使用两个独立的测试 Sidecar 验证多实例路由和数据隔离。

本阶段不实现 Management Portal，也不修改 app-delivery 或部署环境。Portal Client、Kong
Ingress、Core IAM policy 和真实 dev 多实例部署留给后续阶段。

## 2. 范围

### 2.1 本阶段范围

- 扩展 Agent Gateway Provider 配置，增加可选通用 `sidecar` 配置；
- 新增 Agent Sidecar gRPC Client registry 和生命周期管理；
- 将 Project 管理合并到 Agent Gateway `AgentService`，并提供统一 API interface 和 Handler；
- 新增通用 Agent entity、Project 管理 RPC、HTTP binding 和 OpenAPI；
- 直接复用 `agent-sidecar` 的 `ProjectRef`、`Project`、`EntryType`、`Entry` 和 `FileContent`；
- 建立 Agent Gateway 与 Agent Sidecar contract 的本地 Buf multi-module workspace；
- 从可信 gRPC metadata 读取 operator，并向 Sidecar 传递诊断 metadata；
- 新增 Core Server Audit gRPC Client；
- 新增 Agent Project Audit 类型及 Core Server converter；
- 只为成功的 Project 和 Entry mutation 写 Core Server AuditLog；
- 新增 Project 管理、Sidecar unavailable、revision conflict 和 Audit failure metrics；
- 补齐 contract、配置、静态 Agent 列表、路由、审计和错误语义测试。

### 2.2 不在本阶段

- Management Portal 菜单、页面、目录树、编辑器和交互；
- TypeScript Agent Gateway Client；
- Kong Ingress、Core IAM permission 或 app-delivery 配置；
- Agent Codex、Agent Sidecar 或 Agent Gateway 的 dev/staging/prod 部署；
- 第二个真实 dev Agent Codex 实例；
- 修改 Agent Sidecar contract 或文件系统实现；
- 动态实例注册、Kubernetes discovery、健康状态探测或实例数据库；
- Sidecar token、TLS、mTLS 或 ServiceAccount JWT；
- Project/Entry mutation 与 Agent Task 的协调锁；
- Gateway 本地 audit 表、outbox、Audit 重试或 Audit 幂等 key；
- 文件正文、目录内容或 revision 的 Gateway 缓存；
- Git clone、pull、push、commit、branch、remote 或凭证管理。

## 3. 当前基线与术语

### 3.1 第一阶段基线

第一阶段已经提供：

- `packages/contracts/agent-sidecar` 内部 Proto module；
- `aisupport.agent_sidecar.api.project.v1.ProjectService`；
- 生成的 Go message 和 gRPC Client/Server；
- `services/agent-sidecar` 的 Project、Global、Entry 和 File 实现；
- 标准 gRPC Health 服务；
- Project/Global allowlist、denylist、符号链接拒绝、revision 和原子写入语义。

第二阶段把 Sidecar contract 视为内部服务事实来源，不在 Gateway 中重新定义 Project
entity，也不改变 Sidecar 的路径和安全策略。

### 3.2 Managed Agent

`agentRuntime.agents` 中配置了 `sidecar` 的 Agent Provider 是 Managed Agent：

- map key 是稳定且区分大小写的 `agent_id`；
- `sidecar` 配置与 `codexCLI`、`codexAppServer` 同级，不绑定具体 Provider 类型；
- 未配置 `sidecar` 的 Provider 仍出现在 `ListAgents`，但 Project 管理能力为 false；
- 配置只在 Gateway 启动时加载，不写数据库、不动态刷新；
- Sidecar 不可达不会阻止 Gateway 启动，也不会影响该 Provider 的 Agent Task 能力。

### 3.3 Project API 中的 Global

对外产品统一使用 Project 管理语义。Entry 和 File API 放在 `/projects` 路径下，但仍通过
Sidecar `ProjectRef` 支持两种逻辑根：

- `PROJECT_SCOPE_PROJECT`：管理指定 Project；
- `PROJECT_SCOPE_GLOBAL`：管理 Sidecar 暴露的受限 Global 视图。

路径中的 `/projects` 表示这组接口属于 Agent Project 管理能力，不表示 Global 被持久化为
Project，也不改变 Global 只允许 `skills/**` 和 `AGENTS.md` 的安全边界。

## 4. 整体架构

请求链路为：

```text
Management Portal（后续阶段）
        |
        | Authorization
        v
Kong + Core Auth/IAM
        |
        | authenticated request + X-Operator
        v
Agent Gateway AgentService
        |
        +-- agent_id -> Sidecar Client Registry -> Agent Sidecar ProjectService
        |
        +-- successful mutation -> Core Server AuditService
```

职责边界：

### 4.1 Kong 与 Core IAM

- 完成用户认证和管理 API 的 admin 权限判断；
- 覆盖外部请求携带的身份 header；
- 向 Agent Gateway 注入可信 `X-Operator`；
- 未授权请求不得到达 Agent Gateway。

### 4.2 Agent Gateway

- 要求每个 Project 管理请求都具有非空可信 operator；
- 根据 `agent_id` 选择 Sidecar Client；
- 为 Sidecar 调用设置 deadline 并转发请求；
- 将 Sidecar entity 原样返回上游；
- 统一处理未知实例、连接失败和 Audit 失败；
- 在 Sidecar mutation 成功后写 Core Server Audit；
- 不读取本地文件系统，不记录或保存文件正文；
- 不调用 Task Service 检查 Project 是否正在使用。

### 4.3 Agent Sidecar

- 继续负责 Project/Global 解析、路径安全和所有文件系统操作；
- 继续负责分页 cursor、revision、文件类型、Git 初始化和原子 mutation；
- 不感知 `agent_id`、Kong、Core IAM 或 Core Audit；
- 不承担权威管理员审计。

### 4.4 Core Server Audit

- 持久化成功 mutation 的 AuditLog；
- 使用新的 Agent Project Audit 类型支持过滤和展示；
- 不接收文件正文、revision、request ID、duration 或 Sidecar endpoint；
- Audit 创建失败不触发 Sidecar rollback。

## 5. Contract 布局与 Buf Workspace

### 5.1 文件布局

Agent Gateway 新增：

```text
packages/contracts/agent-gateway/
  api/agent-gateway/agent/v1/agent_service.proto
  entity/agent-gateway/agent/v1/agent.proto

packages/sdk-go/contracts/
  api/agent-gateway/agent/v1/...
```

Gateway API import 第一阶段 entity：

```proto
import "entity/agent-sidecar/project/v1/project.proto";
```

Core Server 更新：

```text
packages/contracts/core-server/entity/core-server/audit/v1/audit.proto
services/core-server/internal/services/audit/repo/cvt/...
```

### 5.2 Multi-module workspace

当前 `agent-gateway` 和 `agent-sidecar` 各自拥有独立 `buf.yaml`。为允许 Gateway Proto
直接 import Sidecar entity，在 `packages/contracts` 增加顶层 Buf v2 配置：

```yaml
version: v2
modules:
  - path: agent-gateway
  - path: agent-sidecar
deps:
  - buf.build/googleapis/googleapis
lint:
  use:
    - STANDARD
  except:
    - PACKAGE_DIRECTORY_MATCH
breaking:
  use:
    - FILE
```

调整规则：

- `agent-gateway/buf.yaml`、`agent-sidecar/buf.yaml` 的 module 配置合并到顶层；
- Google APIs dependency 和 lock 移到顶层；
- 两个 module 保留各自 `buf.gen.yaml`；
- 从任一 module 目录执行 `buf lint` 时使用顶层 workspace；
- 从 `agent-gateway` 执行 `buf generate` 只生成 Gateway 文件，不重新生成 Sidecar；
- 从 `agent-sidecar` 执行 `buf generate` 只生成 Sidecar 文件；
- 两个 CI 都把顶层 Buf 配置和 lock 加入 path trigger；
- Core Server 保持自己的 Proto module 和生成流程，不加入此 workspace。

### 5.3 生成产物

第二阶段生成并提交：

- Agent Gateway Go messages；
- `AgentService` gRPC Client/Server；
- grpc-gateway handler；
- `agent-gateway.swagger.yaml` 中的 Project 管理 paths 和复用的 Sidecar entity schemas。

本阶段不增加 Orval 配置或 TypeScript export。Phase 3 从本阶段生成的 Swagger 创建 Portal
Client。

## 6. Agent Gateway Entity

`Agent` 只包含配置派生的静态信息，不包含凭证、endpoint 或运行时健康状态：

```proto
message Agent {
  string agent_id = 1;
  Provider provider = 2;
  bool project_management_enabled = 3;
  bool is_default = 4;
}
```

## 7. `AgentService`

### 7.1 Service

```proto
service AgentService {
  rpc ListAgents(ListAgentsRequest) returns (ListAgentsResponse);
  rpc ListProjects(ListProjectsRequest) returns (ListProjectsResponse);
  rpc CreateProject(CreateProjectRequest) returns (CreateProjectResponse);
  rpc DeleteProject(DeleteProjectRequest) returns (DeleteProjectResponse);
  rpc ListProjectEntries(ListProjectEntriesRequest) returns (ListProjectEntriesResponse);
  rpc GetProjectFile(GetProjectFileRequest) returns (GetProjectFileResponse);
  rpc CreateProjectEntry(CreateProjectEntryRequest) returns (CreateProjectEntryResponse);
  rpc UpdateProjectFile(UpdateProjectFileRequest) returns (UpdateProjectFileResponse);
  rpc MoveProjectEntry(MoveProjectEntryRequest) returns (MoveProjectEntryResponse);
  rpc DeleteProjectEntry(DeleteProjectEntryRequest) returns (DeleteProjectEntryResponse);
}
```

除 `ListAgents` 外，所有请求第一个字段都是 `string agent_id = 1`。Gateway 请求保持
扁平，不嵌套完整 Sidecar request/response，避免 REST query 或 JSON 出现额外
`request`/`response` 层。

### 7.2 请求与响应

```text
ListAgentsRequest  {}
ListAgentsResponse { Agent agents[] }

ListProjectsRequest  { agent_id, page_size, cursor }
ListProjectsResponse { Sidecar Project projects[], next_cursor }

CreateProjectRequest  { agent_id, name }
CreateProjectResponse { Sidecar Project project }

DeleteProjectRequest  { agent_id, name, confirm_name }
DeleteProjectResponse {}

ListProjectEntriesRequest {
  agent_id,
  Sidecar ProjectRef project,
  path,
  page_size,
  cursor
}
ListProjectEntriesResponse {
  Sidecar Entry entries[],
  next_cursor,
  directory_revision
}

GetProjectFileRequest  { agent_id, Sidecar ProjectRef project, path }
GetProjectFileResponse { Sidecar FileContent file }

CreateProjectEntryRequest {
  agent_id,
  Sidecar ProjectRef project,
  parent_path,
  name,
  Sidecar EntryType type
}
CreateProjectEntryResponse { Sidecar Entry entry }

UpdateProjectFileRequest {
  agent_id,
  Sidecar ProjectRef project,
  path,
  content,
  expected_revision
}
UpdateProjectFileResponse { Sidecar Entry entry }

MoveProjectEntryRequest {
  agent_id,
  Sidecar ProjectRef project,
  source_path,
  destination_parent_path,
  destination_name,
  expected_revision
}
MoveProjectEntryResponse { Sidecar Entry entry }

DeleteProjectEntryRequest {
  agent_id,
  Sidecar ProjectRef project,
  path,
  recursive,
  expected_revision
}
DeleteProjectEntryResponse {}
```

Gateway 不重复校验 Project name、ProjectRef、path、entry type、content、revision 或 cursor；
这些规则由 Sidecar 负责并通过既有 gRPC code 返回。Gateway 只校验 `agent_id`、operator 和
client routing 所需字段。

### 7.3 HTTP binding

| RPC | HTTP |
| --- | --- |
| `ListAgents` | `GET /api/v1/agent-gateway/agents` |
| `ListProjects` | `GET /api/v1/agent-gateway/agents/{agent_id}/projects` |
| `CreateProject` | `POST /api/v1/agent-gateway/agents/{agent_id}/projects` |
| `DeleteProject` | `DELETE /api/v1/agent-gateway/agents/{agent_id}/projects/{name}` |
| `ListProjectEntries` | `GET /api/v1/agent-gateway/agents/{agent_id}/projects/entries` |
| `GetProjectFile` | `GET /api/v1/agent-gateway/agents/{agent_id}/projects/files` |
| `CreateProjectEntry` | `POST /api/v1/agent-gateway/agents/{agent_id}/projects/entries` |
| `UpdateProjectFile` | `PUT /api/v1/agent-gateway/agents/{agent_id}/projects/files` |
| `MoveProjectEntry` | `POST /api/v1/agent-gateway/agents/{agent_id}/projects/entries:move` |
| `DeleteProjectEntry` | `DELETE /api/v1/agent-gateway/agents/{agent_id}/projects/entries` |

Encoding 规则：

- GET 和 DELETE 的非 path 字段由 grpc-gateway 生成 query encoding；
- `ProjectRef` query 使用生成器定义的 nested field encoding；
- POST 和 PUT 使用 `body: "*"`；
- path 中的 `agent_id` 是权威值，调用方不需要在 JSON body 重复提交；
- contract 中不定义 operator、admin、request ID 或 endpoint 字段；
- Swagger 中 Entry 和 File 的所有 paths 都必须包含 `/projects` 前缀。

## 8. Agent Gateway 配置

### 8.1 Agent Sidecar

在通用 `AgentProviderConfig` 中增加：

```yaml
agentRuntime:
  agents:
    codex-a:
      provider: codex_app_server
      codexAppServer:
        endpoint: ws://agent-codex-a:4500
        token: REPLACE_FROM_SECRET
        timeout: 10m
        sandboxMode: workspace-write
        approvalPolicy: never
      sidecar:
        endpoint: agent-codex-a:9091
        timeout: 10s
```

对应配置结构：

```text
SidecarConfig {
  Endpoint string
  Timeout  time.Duration // default 10s
}

AgentProviderConfig {
  Provider
  CodexCLI
  CodexAppServer
  Sidecar *SidecarConfig
}
```

Normalize/Validate：

- `sidecar` 缺失时不填默认对象；
- `sidecar` 存在时填充默认 timeout；
- endpoint 必须非空；
- timeout 必须大于 0；
- endpoint 使用 gRPC target 格式，例如 `agent-codex:9091` 或 `dns:///agent-codex:9091`；
- 本阶段只使用 plaintext transport，不配置 token 或 TLS；
- 不主动连接 endpoint 作为配置校验的一部分。

### 8.2 Core Server Audit Client

新增：

```yaml
clients:
  core:
    address: core-server:9090
```

`clients.core.address` 是 Core Server 内部 plaintext gRPC 地址。实现阶段在 Agent Gateway
真正启用 Project 管理前必须提供该配置；本设计不修改任何环境的部署配置。

## 9. Sidecar Client Registry

### 9.1 代码边界

新增 `internal/integrations/agentsidecar`：

```text
ClientRegistry
  agents map[agent_id]Client

Client
  conn
  ProjectServiceClient
  timeout
```

职责：

- 遍历配置并只为含 `sidecar` 的 Agent 建立 Client；
- 使用共享 gRPC dial options：plaintext、round-robin 和 tracing；
- `grpc.NewClient` 只建立 ClientConn，不等待远端 ready；
- `Get(agentID)` 对未知或未托管 Agent 返回领域 `NotFound`，不得 panic；
- 为每次调用创建不超过实例 timeout 的 context；
- 从请求 context 传播 trace，并添加 `x-request-id`、`x-operator` outgoing metadata；
- 统一关闭全部 ClientConn。

Registry 作为 Gateway Service 生命周期的一部分注册到 Service Manager。Gateway shutdown
先停止 HTTP/gRPC server，再停止 Service Manager 并关闭 Sidecar/Core ClientConn。

### 9.2 Deadline

业务调用 deadline 使用以下规则：

1. 如果调用方没有更早的 deadline，使用 `sidecar.timeout`；
2. 如果调用方 deadline 更早，保留调用方 deadline；
3. 调用方主动取消返回 `Canceled`；
4. Sidecar timeout 或连接不可用映射为 `Unavailable`；
5. 不为任何读写请求配置自动 retry。

不在业务操作前执行 Health RPC，避免一次操作产生两次远端调用和检查后的竞态。

## 10. 静态 Agent 列表

`ListAgents` 按 `agent_id` 返回统一 Agent runtime 中的静态配置派生信息，包括 Provider、
是否配置 Sidecar 和 `is_default`。该接口不执行 Sidecar Health RPC，也不要求服务层
`X-Operator`。Sidecar 可用性只在实际 Project 请求中按调用结果体现。

## 11. 身份与 operator

### 11.1 授权边界

本阶段采用“只由 Kong 授权”：

- Kong 使用现有 Core Auth/IAM 对 URI 和 HTTP method 授权；
- admin policy 的定义和 Ingress 绑定不在本阶段；
- Gateway 不调用 Core Auth、不解析 `X-Principal-Profile`、不检查 role 名称；
- Gateway 的 HTTP Service 只能在完成 Kong/IAM 配置后向 Portal 暴露；
- 当前内部 gRPC 和 ClusterIP 仍属于集群可信网络边界。

### 11.2 operator 提取

- grpc-gateway 从 Kong 设置的 `Grpc-Metadata-X-Operator` 获得 gRPC metadata
  `x-operator`；
- Handler 从 incoming metadata 读取第一个非空 `x-operator`；
- 除 `ListAgents` 外的九个 Project 管理 RPC 都要求 operator，包括 List 和 Get；
- 缺失 operator 返回 `Unauthenticated`；
- operator 不存在于 Proto request，外部请求无法通过 JSON 覆盖；
- mutation 审计直接使用该 operator；
- Sidecar 收到相同 `x-operator`，只用于诊断，不参与文件权限判断。

### 11.3 request ID

Gateway 使用当前 OpenTelemetry trace ID 作为 Sidecar metadata `x-request-id`。它只用于跨
Gateway/Sidecar 诊断，不写入 Core Audit details。没有有效 trace ID 时允许省略该 metadata，
不额外引入业务 request ID contract。

## 12. Project 请求转发

Project 管理并入 `internal/services/agent`，单个 concrete Service 分别实现 Runtime、Registry
和 Management 窄接口，并使用 `go.uber.org/mock` 生成 mock。

Handler 只负责：

- 提取可信 operator；
- 将 Proto request 转为业务参数；
- 调用 Project Service；
- 返回生成的 Proto response。

Project Service 负责：

- 从 registry 解析 `agent_id`；
- 创建受 timeout 限制且包含诊断 metadata 的 Sidecar context；
- 构造 Sidecar request；
- 调用对应 `ProjectServiceClient`；
- 对 mutation 执行 Core Audit；
- 记录业务 metrics；
- 映射 Gateway 自身产生的错误。

Sidecar 的 Project、Entry 和 File response entity 直接放入 Gateway response，不进行字段
复制。Gateway request 到 Sidecar request 的组装保持一一对应，不修改 content、revision、
cursor 或 path。

## 13. 错误模型

### 13.1 Sidecar code

以下 Sidecar gRPC code 原样返回：

| code | 场景 |
| --- | --- |
| `InvalidArgument` | 名称、ProjectRef、path、cursor、正文或确认参数非法 |
| `NotFound` | Project、目录或文件不存在 |
| `AlreadyExists` | Create 或 Move 目标已存在 |
| `PermissionDenied` | denylist、Global allowlist、符号链接或受保护根 |
| `Aborted` | revision 冲突或 cursor 失效 |
| `ResourceExhausted` | 文件、路径、深度或分页限制超出 |
| `FailedPrecondition` | 目标类型错误、非空目录未指定 recursive 等 |
| `Internal` | Sidecar 未分类文件系统错误 |

### 13.2 Gateway code

| code | 场景 |
| --- | --- |
| `Unauthenticated` | 缺少可信 `x-operator` |
| `NotFound` | `agent_id` 未配置 Sidecar |
| `Unavailable` | Sidecar 连接失败、不可达或超过 Gateway sidecar timeout |
| `Canceled` | 调用方主动取消请求 |
| `Internal` | Sidecar mutation 已成功，但 Core Audit 创建失败 |

Audit 失败使用固定错误消息，表达：

```text
operation may have completed, but recording the audit log failed; refresh before retrying
```

错误消息不得包含 Core/Sidecar endpoint、文件正文、物理路径或原始网络错误。Gateway 不在
Audit 失败后再次调用 Sidecar，也不尝试反向 mutation。

## 14. Core Server Audit 设计

### 14.1 Audit 类型

Core contract 增加：

```proto
AUDIT_LOG_TYPE_AGENT_PROJECT = 18;
```

同时更新：

- entity 到 model 的 `agent_project` 映射；
- model 到 entity 的反向映射；
- `ListAuditLogTypes` 使用的支持类型列表；
- converter、validator 和 handler 测试。

不新增 Core Server API、数据库列或 migration。现有 `audit_logs.type` 是字符串，可直接保存
新类型。

### 14.2 Audit Client

Agent Gateway 新增 Core Client wrapper：

- 使用 `clients.core.address`；
- Agent Service 直接使用 wrapper 暴露的 `AuditServiceClient`；
- `CreateAuditLog` 继承调用方 context，不增加内部 timeout；
- 使用 plaintext gRPC 和 tracing；
- ClientConn 由 wrapper 持有并在 Gateway shutdown 时关闭；
- 测试复用生成的 `AuditServiceClient` mock；
- 不调用 Core User、Auth 或 IAM RPC。

### 14.3 写入时机

mutation 流程固定为：

1. 校验 operator 和 `agent_id`；
2. 调用 Sidecar mutation；
3. Sidecar 失败时直接返回，且不调用 Audit；
4. Sidecar 成功后构造 AuditLog；
5. 同步调用 Core `CreateAuditLog`；
6. Audit 成功后返回 Sidecar response；
7. Audit 失败时返回 `Internal`，不返回成功 response。

只记录成功的文件系统操作。不存在“失败 AuditLog”，也不把错误 result 写入 details。

由于文件系统和 Core Database 之间没有分布式事务，Audit 失败时 Sidecar mutation 已经提交。
这是本阶段接受的显式边界。调用方收到该错误后必须先重新读取资源状态，不能盲目重试。

### 14.4 AuditLog 固定字段

```text
type       = AUDIT_LOG_TYPE_AGENT_PROJECT
object_id  = agent_id
operator   = trusted X-Operator
operation  = fixed operation name
operate_at = Sidecar mutation completed time
details    = operation-specific JSON
```

operation 只允许：

```text
create_project
delete_project
create_entry
update_file
move_entry
delete_entry
```

`details` 使用 snake_case key，并通过共享 `object.MarshalWithoutError` 序列化。

### 14.5 Operation details

Create/Delete Project：

```json
{
  "project_name": "demo"
}
```

Create Entry：

```json
{
  "scope": "project",
  "project_name": "demo",
  "parent_path": "docs",
  "name": "README.md",
  "entry_type": "file"
}
```

Update File：

```json
{
  "scope": "project",
  "project_name": "demo",
  "path": "docs/README.md"
}
```

Move Entry：

```json
{
  "scope": "project",
  "project_name": "demo",
  "source_path": "README.md",
  "destination_parent_path": "docs",
  "destination_name": "README.md"
}
```

Delete Entry：

```json
{
  "scope": "project",
  "project_name": "demo",
  "path": "docs/README.md",
  "recursive": false
}
```

Global 操作使用 `scope: "global"` 且 `project_name` 为空字符串。Audit details 不记录：

- result、gRPC code 或错误文本；
- duration、request ID 或 trace ID；
- content、文件摘要或正文片段；
- expected revision、返回 revision 或 cursor；
- Sidecar/Core endpoint；
- Projects Root、CODEX_HOME 或其他物理路径；
- Secret、token 或 request/response Proto payload。

## 15. 日志与 Metrics

### 15.1 日志

- 保持 Agent Gateway RPC middleware `LogPayload=false`；
- 成功 mutation 的权威审计只写 Core Server Audit，不额外写重复的本地 audit log；
- Audit RPC 失败写 operational error log，只包含 `agent_id`、operation 和安全错误分类；
- Sidecar unavailable 日志不输出文件正文或请求 Proto；
- 普通 access log 可以继续包含框架已有的 latency 和 trace ID，它不属于 AuditLog。

### 15.2 Metrics

Project Management 当前仅使用 Agent Gateway 已有的 RPC metrics 统计请求结果和耗时，不新增
project 专属 metrics。revision conflict、Sidecar unavailable 和 Audit failure 等业务指标后续按实际
告警与排障需求补充。

现有 RPC Gateway server 继续通过 `/metrics` 暴露通用 metrics；metrics 不包含文件正文、revision、
cursor、endpoint、Project name 或 operator。

## 16. 依赖装配与生命周期

Agent Gateway `internal/deps` 增加：

```text
Agent Sidecar ClientRegistry
Core Audit Client
project/api.Service
Project Service group:"service"
Project Handler
Metrics Gateway Handler
```

依赖方向：

```text
Project Handler
    -> project/api.Service
        -> Agent Sidecar ClientRegistry
        -> Core Audit Client

Agent Sidecar ClientRegistry
    -> generated Sidecar ProjectServiceClient
```

生命周期：

1. 配置 Normalize/Validate；
2. 创建数据库、现有 Agent Runtime 和现有 Services；
3. 创建 Sidecar Client registry 和 Core Audit Client；
4. 装配 Project Service、Handler 和 metrics handler；
5. `--check` 只验证配置和 Client 可构造，不要求 Sidecar/Core 在线；
6. 启动 Service Manager、gRPC server 和 grpc-gateway server；
7. shutdown 时先停止外部 server；
8. 停止 Service Manager 并关闭 Sidecar/Core ClientConn；
9. 最后关闭现有数据库连接。

## 17. 测试策略

### 17.1 Contract 与生成

- 顶层 Buf workspace 能同时解析 Gateway 和 Sidecar module；
- Gateway Proto 能 import Sidecar entity；
- 分别执行两个 module 的 `buf lint` 和 `buf generate`；
- Gateway generate 不产生 Sidecar 文件 drift，Sidecar generate 不产生 Gateway 文件 drift；
- Swagger 包含十个 RPC；
- Entry/File/Move/Delete paths 全部包含 `/projects`；
- POST/PUT body 和 GET/DELETE query encoding 与 Proto 一致；
- contract 生成后再次执行无 diff。

### 17.2 配置与 Client lifecycle

- 无 `sidecar` 的 Provider 不创建 Client；
- Sidecar endpoint 为空或 timeout 非法时配置校验失败；
- Sidecar 缺失时不意外构造默认配置；
- Core address 缺失时配置校验失败；
- Gateway 启动时 Sidecar 不在线仍能完成依赖装配；
- shutdown 关闭每个 Sidecar connection 和 Core connection；
- `Get` 未知或未托管 agent 返回 NotFound，不 panic。

### 17.3 多实例路由

启动两个独立 in-process fake Sidecar gRPC server：

- 两端返回同名 Project 但不同 metadata；
- `agent_id=codex-a` 只调用 A；
- `agent_id=codex-b` 只调用 B；
- 并发请求不会串 Client；
- 每个 RPC 的 agent、ProjectRef、path、content 和 revision 均原样转发；
- 不调用 Agent Runtime Provider 或 Task Service；
- Client 不缓存 Project、Entry 或 File response。

### 17.4 静态 Agent 列表

- 返回全部静态配置的 Agent，并按 `agent_id` 排序；
- Provider、Project 管理能力和 `is_default` 与启动配置一致；
- 未配置 Sidecar 的 Agent 仍返回，Project 管理能力为 false；
- 不调用 Sidecar，不要求 `x-operator`，不暴露 endpoint、token 或本地路径。

### 17.5 身份

- 每个 RPC 缺少 `x-operator` 都返回 Unauthenticated；
- metadata 中的 operator 进入 Core Audit 和 Sidecar outgoing metadata；
- request body 无 operator 字段；
- 伪造的普通 JSON/header 不能覆盖 grpc-gateway 接收的可信 metadata；
- Gateway 不调用 Core Auth/IAM。

### 17.6 Sidecar 转发与错误

- 九个 Sidecar RPC 的 success response；
- 所有业务 gRPC code 原样返回；
- Sidecar timeout 映射 Unavailable；
- caller cancel 保持 Canceled；
- 未知 agent 返回 NotFound；
- mutation 不自动重试；
- operation 前不额外调用 Health；
- RPC/access log 不含 UpdateProjectFile content。

### 17.7 Core Audit

- Core 新类型完成 entity/model 双向转换；
- `ListAuditLogTypes` 包含 Agent Project；
- Sidecar mutation 失败时 Audit Client 调用次数为 0；
- Sidecar mutation 成功时 Audit Client 调用次数为 1；
- ListAgents、ListProjects、ListProjectEntries 和 GetProjectFile 不写 Audit；
- 六种 operation 的 type、object ID、operator、operate_at 和 details 精确匹配；
- Project 与 Global details 正确；
- UpdateProjectFile Audit 不包含 content 或 revision；
- Audit details 不包含 duration、request ID、trace ID、cursor 或 endpoint；
- Audit 失败返回 Internal；
- Audit 失败后不再次调用 Sidecar，也不执行补偿 mutation；
- Audit 成功后返回原 Sidecar response。

### 17.8 Metrics

- success、sidecar_error 和 audit_error 计数；
- revision conflict 计数；
- unavailable 按静态 agent ID 和 operation 计数；
- Audit failure 计数；
- metrics label 不包含路径、Project name、operator、revision 或正文；
- `/metrics` 可以读取注册后的指标。

## 18. 验证命令

实现阶段至少执行：

```text
cd packages/contracts/agent-sidecar && buf lint && buf generate
cd packages/contracts/agent-gateway && buf lint && buf generate
cd services/core-server && make generate-proto
cd services/core-server && GOCACHE=/tmp/core-server-go-cache go test ./internal/services/audit/...
cd services/agent-gateway && make generate
cd services/agent-gateway && GOCACHE=/tmp/agent-gateway-go-cache go test ./...
cd services/agent-gateway && GOCACHE=/tmp/agent-gateway-go-cache go vet ./...
git diff --check
```

生成命令完成后再次执行并要求生成文件无额外 diff。额外检查 Swagger paths、Audit details
fixtures 以及日志测试，确认没有文件正文或被禁止的审计字段。

## 19. 实施顺序

1. 建立 Gateway/Sidecar Buf multi-module workspace；
2. 新增通用 Agent entity 和 `AgentService`；
3. 生成 Gateway Go、grpc-gateway 和 Swagger；
4. 为 Core Audit 增加 Agent Project 类型并生成 Core contract；
5. 扩展 Gateway Sidecar/Core Client 配置；
6. 实现 Sidecar Client registry 和 Core Audit Client；
7. 将 Project 管理并入 Agent Service API、Service 和 Handler；
8. 接入 operator、deadline、metadata 和错误映射；
9. 接入成功 mutation 的同步 Core Audit；
10. 接入 metrics 和 `/metrics`；
11. 补齐 contract、Core Audit、Gateway unit/integration tests；
12. 重复执行生成、测试、vet 和 diff gate；
13. 后续阶段另行完成 Kong/IAM、app-delivery、Portal 和真实 dev 验收。

## 20. 第二阶段验收标准

- Gateway contract 可直接复用 Sidecar entity，两个 Proto module 可独立重复生成；
- OpenAPI 提供完整十个管理 RPC，所有 Entry/File API 都位于 `/projects` 下；
- 所有注册 Agent 都出现在 Agent 列表，只有配置了 Sidecar 的 Agent 启用 Project 管理；
- 单个 Sidecar 不可用不会影响其他实例的列表状态；
- 所有业务调用按 `agent_id` 路由且不会串实例；
- Gateway 不缓存或记录文件正文，不查询 Task 状态；
- 所有管理 RPC 都要求可信 operator；
- 只有成功的六类 mutation 创建 Core AuditLog；
- AuditLog 只包含 agent、operator、operation、时间和必要资源定位信息；
- AuditLog 不包含 result、duration、request ID、trace ID、revision、cursor 或正文；
- Audit 失败返回明确 Internal，且不会重试 Sidecar 或回滚已经完成的 mutation；
- Sidecar 原有业务 code、revision 和 cursor 语义保持不变；
- 两个 fake Sidecar 的多实例隔离测试通过；
- Core Audit 和 Agent Gateway 的生成、测试、vet 与 diff gate 全部通过；
- 本阶段没有 Portal、TypeScript Client、Ingress、IAM policy 或部署变更。

## 21. 已知限制与后续边界

| 限制 | 第二阶段处理 | 后续方向 |
| --- | --- | --- |
| Sidecar mutation 与 Audit 不在同一事务 | mutation 成功后同步审计；审计失败返回可能已完成的 Internal | 如需强保证，引入带幂等 key 的 outbox |
| Gateway 只信任 Kong operator | API 未经 Kong 不向外暴露 | 配置 Ingress 和 Core IAM admin policy |
| 无真实多实例部署 | 使用两个独立 fake Sidecar 验证路由 | dev 部署多个独立 PVC/Service 实例 |
| 列表不提供健康状态 | 只返回静态配置派生信息 | 如有需要另行设计可观测性接口 |
| plaintext 内部 gRPC | 保持当前集群可信网络模型 | 跨信任域时引入 mTLS/JWT |
| Audit 失败不可安全重试 mutation | 返回明确提示，调用方先刷新 | Portal 实现冲突/状态刷新体验 |
| Global 位于 Projects API 前缀下 | 通过 ProjectRef 明确 scope | Portal 以 Projects/Global 页签区分 |

## 22. 最终决策摘要

- 第二阶段使用统一的 Agent Gateway `AgentService`，不修改 Sidecar contract；
- Gateway 请求保持扁平并增加 `agent_id`，response 直接复用 Sidecar Project entity；
- Entry、File、Move 和 Delete API 统一增加 `/projects` 路径前缀；
- `agentRuntime.agents.<agent_id>.sidecar` 决定一个 Agent 是否支持 Project 管理；
- Gateway 为每个启用 Project 管理的 Agent 持有独立 Sidecar Project Client；
- ListAgents 返回静态配置派生信息，不探测 Sidecar、不暴露 endpoint；
- 业务操作不预检查 Health、不查询 Task、不自动重试；
- Kong/Core IAM 是唯一 admin 授权点，Gateway 只要求并信任 `X-Operator`；
- 成功 mutation 后同步调用 Core Server `CreateAuditLog`；
- Core contract 增加 `AUDIT_LOG_TYPE_AGENT_PROJECT = 18`；
- Audit `object_id` 使用 `agent_id`，details 只保存必要的 Project/Entry 定位信息；
- 失败操作不写 Audit，Audit 中不记录 result、duration、request ID、revision 或正文；
- Sidecar 成功而 Audit 失败时返回 Internal，不回滚也不重试 Sidecar；
- metrics 与普通 access log 不等同于 Core Audit；
- 本阶段只设计 Gateway、contract 和 Core Audit 接入，不包含 Portal、TypeScript Client、
  Kong/IAM 配置、app-delivery 或任何环境部署。
