Files
group_fqcd_jr/docs/superpowers/plans/2026-09-09-agent-platform-implementation.md
T

214 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Agent Platform Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 将 v3.1 Agent 底座和 05 接口规范落成可恢复、可测试、与 HTTP/SSE 解耦的 MVP 实现。
**Architecture:** API 层只负责 JWT、请求映射和 View;`AgentRunApplicationService` 负责受理、幂等和 Outbox;Worker 通过 `AgentExecutor` 调用 `AgentFactory`/`BaseAgent.execute()`。MySQL 是运行状态、最终消息和审计权威源,Redis 只做缓存和通知,SSE 从运行查询投影输出结果级恢复流。
**Tech Stack:** Python 3.11+、FastAPI、Pydantic v2、SQLAlchemy 2、Alembic、MySQL 8、Redis、pytest、pytest-asyncio、httpx;模型、Milvus、Neo4j 通过 Service Protocol/Port 注入。
**Spec:** `docs/05-接口文档.md`、`docs/01-通用Agent平台开发设计.md`、`docs/02-数据库建表设计.md`、`docs/03-平台端到端流程文档.md`、`AGENTS.md`
## Global Constraints
- 数据库允许新增表和字段,禁止重命名、删除或改变已有表及已有字段定义。
- MVC+S 固定为 Controller -> Service -> Repository -> Model;Agent 属于 Service 层。
- Agent 必须由 `AgentFactory` 创建并继承 `BaseAgent`,不得绕过公共鉴权、记忆、模型、工具、合规、审计和事件流程。
- `agent_run` 是运行主键;`trace_id` 只用于追踪;不得复用 `trace_id` 代替 `run_id`。
- 最终助手消息、审计、幂等完成状态、`agent_run=succeeded` 和领域 Outbox 必须在同一事务提交。
- 记忆提取事件必须在 `complete_run()` 同一事务写入 Outbox,请求线程不得提交后直接调用记忆提取。
- BaseAgent 只返回传输无关的 `RunProgressEvent`;SSE 由 View 适配,MVP 只实现结果级恢复。
- 场内基金模拟交易是当前业务范围;场外运营独立建表、独立接口。
---
### Task 1: 项目骨架与公共契约
**Files:**
- Create: `pyproject.toml`
- Create: `app/core/contracts.py`
- Create: `app/core/errors.py`
- Create: `app/core/context.py`
- Create: `tests/unit/core/test_contracts.py`
- Create: `tests/unit/core/test_errors.py`
**Interfaces:**
- Produces `AgentRequest`, `AgentRequestMetadata`, `RequestContext`, `AgentDefinition`, `ResolvedAgentConfig`, `RunProgressEvent`, `AgentResult`, `DomainEvent` and `AgentError` classes matching 01 contract IDs.
- Also produces immutable transport-neutral `RunAccepted`, `RunCancellation` and `RunView` projection dataclasses used by the application and View layers.
- [ ] **Step 1: Write failing validation tests** for forbidden metadata keys, blank messages, invalid idempotency keys, confidence outside `0..1`, and extra request fields.
- [ ] **Step 2: Run** `python -m pytest tests/unit/core/test_contracts.py -q`; expect validation failures before implementation.
- [ ] **Step 3: Implement** Pydantic/dataclass contracts with `ConfigDict(extra="forbid")`; keep HTTP DTOs separate from internal contracts.
- [ ] **Step 4: Add** stable error codes and public messages; errors must not contain stack traces or provider responses.
- [ ] **Step 5: Run** `python -m pytest tests/unit/core -q` and `python -m ruff check app tests`.
### Task 2: 数据库迁移与 Repository
**Files:**
- Create: `alembic/versions/20260909_agent_platform_v31.py`
- Create: `app/model/agent_run.py`
- Create: `app/model/platform_entities.py`
- Create: `app/repository/agent_run_repository.py`
- Create: `app/repository/idempotency_repository.py`
- Create: `app/repository/outbox_repository.py`
- Create: `tests/integration/repository/test_agent_run_repository.py`
**Interfaces:**
- `AgentRunRepository.create_queued(...) -> AgentRun`
- `AgentRunRepository.claim(run_id, worker_id, lease_seconds) -> AgentRun | None`
- `AgentRunRepository.request_cancel(run_id, user_id) -> AgentRun`
- `IdempotencyRepository.claim(scope, request_hash) -> ClaimResult`
- `OutboxRepository.append(event: DomainEvent) -> None`
- `ClaimResult` is a typed result with `decision`, `record_id`, `existing_run_id` and `request_hash`; `AgentRun` is the SQLAlchemy model for `agent_run`.
- [ ] **Step 1: Write migration tests** asserting creation of `agent_run`, ten platform increment tables, foreign keys, status check, unique `run_id`, unique `idempotency_id`, and lease indexes.
- [ ] **Step 2: Run** `alembic upgrade head` against an empty MySQL test schema and inspect with SQLAlchemy Inspector.
- [ ] **Step 3: Implement** `agent_run` migration exactly as 02 §8.4; do not alter baseline tables.
- [ ] **Step 4: Implement** repositories with conditional updates for leases, cancellation and terminal states.
- [ ] **Step 5: Test** duplicate claims, expired lease takeover, concurrent completion and forbidden terminal-state transitions.
- [ ] **Step 6: Run** `alembic downgrade -1` in a disposable schema and verify no baseline table or column changed.
### Task 3: JWT、统一信封和 API DTO
**Files:**
- Create: `app/api/dependencies/auth.py`
- Create: `app/api/schemas/envelopes.py`
- Create: `app/api/schemas/agent_runs.py`
- Create: `app/api/mappers/agent_run_mapper.py`
- Create: `tests/api/test_auth.py`
- Create: `tests/api/test_envelopes.py`
**Interfaces:**
- `build_request_context(request) -> RequestContext`
- `AgentRunCreateRequest`
- `success_envelope(data, trace_id)`
- `error_envelope(code, message, trace_id, field_errors)`
- [ ] **Step 1: Test** missing token, invalid signature, wrong issuer/audience, expiry and revoked `jti` return `401`.
- [ ] **Step 2: Test** roles, customer assignment and data scope are reloaded server-side and cannot be supplied by request body or metadata.
- [ ] **Step 3: Implement** JWT dependency and trace middleware; always return `X-Trace-ID`.
- [ ] **Step 4: Implement** JSON DTO validation, cursor validation, decimal-string serialization and `BIGINT` string serialization.
- [ ] **Step 5: Run** `python -m pytest tests/api -q`.
### Task 4: Agent 运行受理、幂等和 Outbox
**Files:**
- Create: `app/service/agent_run_application_service.py`
- Create: `app/service/run_dispatch_port.py`
- Create: `app/api/controllers/agent_runs.py`
- Create: `tests/service/test_agent_run_application_service.py`
- Create: `tests/api/test_agent_runs_create.py`
**Interfaces:**
- `AgentRunApplicationService.accept(payload, context) -> RunAccepted`
- `AgentRunApplicationService.request_cancel(run_id, context, idempotency_key) -> RunCancellation`
- `RunDispatchPort.enqueue(run_id) -> None`
- `RunAccepted` and `RunCancellation` are the immutable result dataclasses from Task 1; controllers never return ORM objects.
- [ ] **Step 1: Write failing transaction tests** for user message + idempotency + `agent_run` + `agent.run_requested` atomicity.
- [ ] **Step 2: Test** same key/same hash returns original run; same key/different hash returns `409 IDEMPOTENCY_CONFLICT`.
- [ ] **Step 3: Implement** `accept()`; do not execute model or Agent in the HTTP request.
- [ ] **Step 4: Implement** cancellation with `cancel_requested` conditional update and `agent.run_cancel_requested` Outbox.
- [ ] **Step 5: Add** controller routes and verify response is `202` with `run_id`, `trace_id`, `status_url`, `events_url`.
- [ ] **Step 6: Run** `python -m pytest tests/service/test_agent_run_application_service.py tests/api/test_agent_runs_create.py -q`.
### Task 5: AgentFactory、BaseAgent.execute 与 Worker
**Files:**
- Create: `app/service/agent/base.py`
- Create: `app/service/agent/factory.py`
- Create: `app/service/agent/executor.py`
- Create: `app/worker/agent_run_worker.py`
- Create: `tests/contract/test_registered_agents.py`
- Create: `tests/worker/test_agent_run_worker.py`
**Interfaces:**
- `AgentFactory.create(agent_type, context) -> BaseAgent`
- `BaseAgent.execute(request, context) -> AsyncIterator[RunProgressEvent]`
- `AgentExecutor.execute_run(run_id) -> None`
- `AgentRunWorker.claim_and_execute() -> None`
- [ ] **Step 1: Write contract tests** proving every registered Agent subclasses BaseAgent, declares valid definition, and cannot override `execute` or public governance methods.
- [ ] **Step 2: Test** Worker lease claim, retry after expiry, same `run_id` reuse and cancellation checkpoint.
- [ ] **Step 3: Implement** BaseAgent seven-step flow with injected Services; no HTTP/SSE imports.
- [ ] **Step 4: Implement** Worker exception translation to `AgentError`; retry recoverable failures and terminalize non-recoverable failures.
- [ ] **Step 5: Run** `python -m pytest tests/contract tests/worker -q`.
### Task 6: complete_run、事务 Outbox 和结果查询
**Files:**
- Create: `app/service/agent_persistence_service.py`
- Create: `app/service/run_query_service.py`
- Create: `app/api/controllers/run_queries.py`
- Create: `tests/service/test_agent_persistence_service.py`
- Create: `tests/api/test_run_queries.py`
**Interfaces:**
- `AgentPersistenceService.complete_run(..., memory_extraction_requested: bool) -> int`
- `RunQueryService.get(run_id, context) -> RunView`
- `RunView` is the persisted HTTP-neutral projection from Task 1; its `result` is populated only from database rows.
- [ ] **Step 1: Write a transaction test** that fails if assistant message, audit, idempotency completion, `agent_run=succeeded`, or Outbox is missing after rollback.
- [ ] **Step 2: Test** `memory.extraction_requested` is present in the same transaction when requested and absent otherwise.
- [ ] **Step 3: Implement** domain event construction with `event_id`, `aggregate_type`, `occurred_at`, and `trace_id`.
- [ ] **Step 4: Implement** result projection from persisted message/run rows; never read Worker memory for GET responses.
- [ ] **Step 5: Run** `python -m pytest tests/service/test_agent_persistence_service.py tests/api/test_run_queries.py -q`.
### Task 7: 结果级恢复 SSE 与会话接口
**Files:**
- Create: `app/view/sse_view.py`
- Create: `app/api/controllers/run_events.py`
- Create: `app/api/controllers/conversations.py`
- Create: `app/api/schemas/conversations.py`
- Create: `tests/api/test_run_events.py`
- Create: `tests/api/test_conversations.py`
**Interfaces:**
- `SseView.stream(run_id, context) -> StreamingResponse`
- `GET /api/v1/agent-runs/{run_id}/events`
- `POST/GET /api/v1/conversations...` routes listed in 05 §7
- [ ] **Step 1: Test** SSE headers, event order, 15-second comment heartbeat, terminal `done/error`, and no `tools/delta/done` before persistence success.
- [ ] **Step 2: Test** reconnect after disconnect rebuilds complete result from `agent_run` and `conversation_message`; do not implement event replay.
- [ ] **Step 3: Implement** View adapter from `RunProgressEvent` to 01 §12 events; BaseAgent remains HTTP-free.
- [ ] **Step 4: Implement** session ownership, message cursor pagination, closure, feedback and public handover request mapping.
- [ ] **Step 5: Run** `python -m pytest tests/api/test_run_events.py tests/api/test_conversations.py -q`.
### Task 8: 平台管理面与配置发布
**Files:**
- Create: `app/api/controllers/admin_config.py`
- Create: `app/api/controllers/admin_models.py`
- Create: `app/api/controllers/admin_prompts.py`
- Create: `app/api/controllers/admin_intents.py`
- Create: `app/api/controllers/admin_audit.py`
- Create: `app/service/config_release_service.py`
- Create: `tests/api/test_admin_config.py`
**Interfaces:**
- Routes and permissions exactly match 05 §9 and §19 entries A001-A033.
- `ConfigReleaseService.validate/review/activate/rollback` are separate methods with maker-checker enforcement.
- [ ] **Step 1: Test** creator cannot review own release, invalid release cannot activate, and `If-Match` conflict returns `409`.
- [ ] **Step 2: Test** active template/intent uniqueness and fallback endpoint foreign-key/order validation.
- [ ] **Step 3: Implement** draft/review/activate/rollback service transactions and audit records.
- [ ] **Step 4: Implement** route DTOs without exposing secrets, internal URLs or old `fallback_endpoint_ids`.
- [ ] **Step 5: Run** `python -m pytest tests/api/test_admin_config.py -q`.
### Task 9: OpenAPI、契约测试和故障演练
**Files:**
- Create: `openapi/agent-platform-v1.yaml`
- Create: `tests/contract/test_openapi_contract.py`
- Create: `tests/security/test_data_scope.py`
- Create: `tests/recovery/test_agent_run_recovery.py`
- Modify: `docs/05-接口文档.md` only when the HTTP contract intentionally changes
- [ ] **Step 1: Generate OpenAPI from the same DTO/router source and compare paths, methods, required headers and response envelopes with 05 §19.**
- [ ] **Step 2: Test** cross-customer access returns `404`, JWT failures return `401`, and Agent cannot execute write tools.
- [ ] **Step 3: Test** Worker crash, lease takeover, Redis loss, Outbox retry/dead-letter and client disconnect.
- [ ] **Step 4: Run** `python -m pytest -q`, `python -m ruff check app tests`, and `python -m mypy app`.
- [ ] **Step 5: Run** empty-schema migration and upgrade-schema migration in disposable MySQL databases; verify 49-table count and immutable baseline definitions.