6.8 KiB
Agent Platform — Project Root Instructions
Project Context
Enterprise AI agent platform powering three production scenarios:
- Presales — proposal documents (Word/Excel) in ~30 min from days
- Security — incident reports from hours to minutes
- Knowledge QA — citation-backed answers, ≥90 % reference coverage
Tech Stack (all versions locked in uv.lock)
- Orchestration : langgraph ≥ 0.3.0 StateGraph + interrupt/resume for human-in-the-loop
- LLM cloud : anthropic ≥ 0.50.0 → claude-sonnet-4-6-20260217
- LLM local : Qwen2.5-14B / 7B via OpenAI-compatible HTTP endpoint
- Vector DB : pymilvus ≥ 2.4.0 + BGE-M3 embeddings + BGE-reranker
- API : fastapi ≥ 0.115.0 + uvicorn[standard]
- ORM : sqlalchemy[asyncio] ≥ 2.0.30 + asyncpg ≥ 0.29.0
- Migrations : alembic ≥ 1.13.0
- Observability : opentelemetry-sdk ≥ 1.27.0 + prometheus exporter + grafana dashboards
- Auth : python-jose[cryptography] + passlib[bcrypt]
- Encryption : cryptography ≥ 42.0.0 (AES-256-GCM, audit payloads)
- Docs : python-docx ≥ 1.1.2
Repository Layout
agent-platform/
├── core/
│ ├── engine/ # LangGraph: state, 5 nodes, graph builder
│ ├── profile/ # SOP YAML schema (Pydantic), loader, registry
│ ├── tools/ # BaseTool, ToolResult, ToolExecutor, registry
│ ├── llm/ # ClaudeClient, PromptBuilder
│ ├── verify/ # EvidenceScorer
│ ├── audit/ # AuditLogger (3-tier), Sanitizer, Encryptor
│ ├── observability/ # OTel setup, decorators, CostTracker, dashboards/
│ └── router/ # ModelRouter
├── api/
│ ├── routes/ # health, chat, approve, trace
│ ├── contracts/ # Pydantic req/resp models, permissions matrix
│ └── middleware/ # tracing, auth (JWT), rbac, rate_limit
├── profiles/
│ ├── presales/prompts/
│ ├── security_analysis/prompts/
│ └── knowledge_qa/prompts/
├── db/
│ ├── base.py
│ ├── models.py
│ ├── repositories/
│ └── migrations/versions/
├── tests/
│ ├── unit/
│ ├── integration/
│ ├── contracts/ # real Claude API; @pytest.mark.claude_api
│ └── fixtures/
├── scripts/
│ ├── setup_env.sh
│ ├── health_check.sh
│ ├── seed_data.py
│ └── gen_audit_key.py
├── docker/
│ ├── Dockerfile.api
│ ├── otel-collector-config.yml
│ └── prometheus.yml
├── .env.example
├── tests/.env.test # committed; no real secrets
├── pyproject.toml
├── uv.lock # MUST be committed; MUST pass uv sync --frozen
├── alembic.ini
├── Makefile
└── docker-compose.yml
Critical Architecture Rules (NEVER Violate)
R1 — Tool Whitelist Enforcement
ToolExecutor.execute_tool() checks profile.allowed_tools BEFORE
calling any tool.
Violation → raise ToolNotAllowedError(tool_name, profile_name)
+ write AuditEvent(event_type="whitelist_violation").
Tool implementations NEVER perform whitelist checks themselves.
R2 — Exception Boundary
| Layer | Raises? | On failure |
|---|---|---|
| ToolExecutor / Act node | YES — policy | ToolNotAllowedError |
| BaseTool.execute() | NEVER | ToolResult(success=False) |
| LangGraph nodes | NEVER escape | set state.error → END |
R3 — State Serialization
AgentState is a BaseModel (fully JSON-serializable at all times).
Large objects (documents, embeddings, raw HTML) → stored in DB /
object-store; only UUID / string references live inside state.
Validate with AgentState.validate_serializable(state) after every
node that mutates state.
R4 — Approval Gates
Any tool whose ToolDeclaration.requires_approval is True
MUST call interrupt(ApprovalPayload(...)) before execution.
Resume via Command(resume=ApprovalDecision(...)).
There are NO exceptions to this rule.
R5 — Audit Completeness
Every tool execution — success, failure, AND skipped — MUST produce
an AuditEvent written by AuditLogger.
A missing audit entry is a P0 production bug.
audit_event rows are append-only; enforced by DB trigger
trg_audit_event_no_mutation.
R6 — Sensitive Data
Sanitizer.mask() MUST be applied to all user inputs before they
appear in logs, audit payloads, or API responses.
Patterns:
- Phone
1[3-9]\d{9}→138****8888 - ID card 18-digit →
****19700101**** - password / key fields →
[REDACTED]
R7 — Async Discipline
No synchronous blocking inside any async def.
CPU-bound operations → asyncio.to_thread().
Every external I/O call carries an explicit timeout= parameter.
No asyncio.create_task() fire-and-forget without error collection.
R8 — Decimal for Money
All cost / fee / score calculations use Decimal with explicit
precision and ROUND_HALF_UP.
DB columns: Numeric(12,6) for USD, Numeric(12,4) for CNY,
Numeric(5,4) for evidence scores.
NEVER store or compute these values as float.
Settings Access Pattern
# CORRECT — lazy singleton, test-safe
from core.config import get_settings
s = get_settings()
# WRONG — instantiated at import time, breaks test monkeypatching
from core.config import settings # this symbol does not exist
Gate Definitions
Every task MUST pass its gate before being declared complete.
Static Gate (no Docker required)
uv sync --frozen
uv run ruff check . --select E,F,I --quiet
uv run mypy core/ api/ --ignore-missing-imports --no-error-summary
uv run pytest tests/unit/ -x -q --tb=short
Enhanced Gate (Docker required)
docker compose up -d --wait
uv run alembic upgrade head
uv run pytest tests/integration/ -x -q --tb=short
bash scripts/health_check.sh
Testing Standards
- Unit tests : zero external I/O; mock DB, HTTP, Claude, Milvus, Redis
- Integration : real services via testcontainers or running Docker stack
- Contract : real Claude API;
@pytest.mark.claude_api; skipped automatically ifANTHROPIC_API_KEYis unset - Coverage gate:
--cov-fail-under=80acrosscore/andapi/ - Secret injection: via
tests/conftest.pysettings_overridefixture; NEVERos.environassignment in test files directly
Commit Convention
feat(engine): implement interrupt/resume approval in act_node
fix(verify): correct EWMA weight formula in EvidenceScorer
test(api): add integration test for /approve expired-token path
chore(deps): add alembic, cryptography to pyproject.toml
refactor(tools): extract _resolve_params to standalone module