# 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.