# Implementation Plan: AI Support Agent **Branch**: `005-ai-support` | **Date**: 2026-09-02 | **Spec**: [spec.md](./spec.md) **Input**: Feature specification from `specs/005-ai-support/spec.md` ## Summary Populate the remaining `ai-support` submodules (`sessions`, `tools`, `troubleshooting`, `escalation` — `knowledge` already exists from 004) with the AI reasoning loop: a session starts per ticket, produces a structured, knowledge-grounded diagnosis via a real Anthropic Claude call, applies a configurable confidence-band policy to decide proceed/ask/escalate, executes permission-and-risk-gated tool proposals through a deterministic policy layer (never the model's own judgment), walks an application-controlled runbook step sequence when one matches, and only marks a ticket AI-resolved on real tool-verified evidence. Per explicit product decision, this feature integrates a real LLM provider from the start — no mock/pluggable-interface phase. ## Technical Context **Language/Version**: TypeScript 5.4 / Node.js 20+. **Primary Dependencies**: `@anthropic-ai/sdk` (new — real LLM calls, research.md), `zod` (tool input schemas, structured-output schema for diagnosis, admin config validation), Prisma (new models), BullMQ (new `AI_SESSION` queue/worker — reuses existing `queueManager`, no new dependency). **Storage**: PostgreSQL via Prisma (new `AISupportSession`, `AIDiagnosis`, `AIInteraction`, `AIAction`, `AIActionResult`, `AIKnowledgeReference`, `AIConfidencePolicy` models per `docs/06-database-schema.md`, refined in data-model.md). Redis/BullMQ for the async first-turn job, reusing existing infrastructure. **Testing**: Vitest — unit tests for the confidence-band decision function, the deterministic tool-policy gate, and the runbook step-advancement logic (all pure, extractable functions, unlike 004's thin-Prisma-query situation); integration tests against a real Postgres **and a real Anthropic API call** for the full session flow (quickstart.md scenarios) — this is the first feature in this codebase whose integration tests have a real external-network dependency and a real per-run cost, not just Docker-local infra. Per the constitution's Testing gate, this feature also adds the two required standing E2E scenarios: (A) AI resolves directly, (B) AI escalates to human — both were previously unimplementable (no AI session existed) and are added now. **Target Platform**: Same Fastify modular monolith. New submodules: `src/modules/ai-support/{sessions,tools,troubleshooting,escalation}/` (standard module shape, research.md "Module placement"). New infra: `src/infrastructure/ai/` (Anthropic client singleton). New job: `src/jobs/ai-session/` (registered in `src/bootstrap/queue.bootstrap.ts`). Modifies `src/modules/ticketing/tickets/service/tickets.service.ts` — two hooks: enqueue on ticket creation (research.md "Session triggering"), and end any active `AISupportSession` inside `updateStatus` when a human actor moves the ticket (research.md "AISupportSession.status drives Ticket.status", FR-023). Also modifies `prisma/schema.prisma`. **Discovered during this planning pass**: 003-ticketing's `ticket-state-machine.ts` already defines the exact `AI_ANALYZING → AI_TROUBLESHOOTING → AI_VERIFYING → AI_RESOLVED` / `HUMAN_ESCALATION` states this feature drives — this feature reuses that state machine and `ticketsService.updateStatus` directly rather than introducing a parallel one. **Project Type**: Backend service — single project. **Performance Goals**: Not throughput-sensitive at this phase (one ticket, one session, turns paced by human/customer reply cadence) — but every reasoning call is real LLM latency (seconds), which is exactly why the first turn is queued (research.md) rather than synchronous with ticket creation. **Constraints**: MUST NOT let AI free-text influence tool permission/risk/escalation decisions (FR-024, doc 11 §A4); MUST NOT mark a ticket AI-resolved without tool-verified evidence (FR-018); MUST NOT let the model choose or reorder runbook steps (FR-015); MUST cap clarifying questions (FR-009) and, per doc 11 §B2, cap reasoning turns/tool-call iterations per session to prevent a runaway loop. **Scale/Scope**: One reasoning agent (not a multi-agent registry), four new submodules, a small fixed tool registry (4 real tools + 1 intentionally-pending-approval high-risk tool). Explicitly excludes: semantic/vector retrieval, product-signal webhook verification, model routing/fallback, cost dashboards, localization, idle-session timeout (see spec.md Assumptions). ## Constitution Check *GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.* | Principle / Section | Check | Result | |---|---|---| | I. SaaS Is the Sole Identity & Access Authority | Sessions/diagnoses/actions reference `Ticket`/`Product` (SupportHub's own domain) only; no SaaS identity data is duplicated. | PASS | | II. Configuration Over Hardcoding | Confidence thresholds are DB-configurable per product/category (`AIConfidencePolicy`, FR-005); the model name and reasoning effort are env-configurable (research.md), not inline string literals. | PASS | | III. Layered Architecture With Enforced Module Boundaries | Four new submodules follow the standard shape; `troubleshooting` reaches `knowledge`'s `Runbook` data only through `knowledge`'s public `index.ts`, never a deep import. | PASS | | IV. AI Recommends, Deterministic Policy Decides | This is the central principle this feature exists to implement: `evaluateToolProposal` (research.md) is the one shared, model-output-blind gate every tool call passes through; confidence-band policy is applied to the diagnosis's numeric score by application code, never by asking the model what it thinks should happen next. | PASS | | V. Evidence-Based Verification | `resolved` status is guarded on a structured `AIActionResult` from `verifyProductResolution`, never on interaction/message content (research.md "Verification and resolution"). | PASS | | VI. Durable Audit & History | Every `AIDiagnosis` is append-only (never overwritten); every `AIAction` records its evaluation outcome even when nothing executes; `AIKnowledgeReference` records exactly what knowledge the AI was shown. | PASS | | VII. Concurrency-Safe, Durable Job Handling | The first-turn job runs through the existing durable BullMQ `queueManager` (survives a process restart — not an in-memory timer); "one active session per ticket" (FR-001) is enforced as a repository-level check analogous to 003's optimistic-concurrency pattern. | PASS | | VIII. Problem and Ticket Are Separate, Related Entities | `AISupportSession` attaches to `Ticket` (per doc 06), and reads `Problem` for diagnosis context — doesn't collapse the two. | PASS | | Technology & Platform Constraints | Adds exactly one new dependency, `@anthropic-ai/sdk` — the one genuinely new capability this phase requires; no other new runtime dependency. | PASS | | Testing gate — AI tool-permission tests | Directly required by the constitution's Testing section, not just this feature's own FRs — see quickstart Scenario 3 and tasks.md. | Addressed in Phase 3 (US3) tests | | Testing gate — two standing E2E scenarios (AI-resolves, AI-escalates) | Both were impossible before this feature (no AI session existed anywhere in the codebase) — added here as the constitution requires. | Addressed in Phase 6 (Polish) | No violations requiring Complexity Tracking justification. The one deliberately-incomplete piece (`overrideTicketPriority` staying `pending_approval` forever, with no approval UI yet) is an explicitly documented known limitation, not a silent gap — same class as `fastify.authenticate`. ## Post-Design Constitution Re-check All gates above remain PASS after Phase 1 design (research.md, data-model.md, contracts/, quickstart.md). Worth calling out explicitly against Principle IV: the two-call design (classify+diagnose, then separately reason/act — research.md) means the confidence-band policy sits in application code *between* two model calls, not inside a prompt instruction hoping the model applies its own policy correctly — this is what makes Principle IV a mechanical guarantee here rather than a hope. ## Project Structure ### Documentation (this feature) ```text specs/005-ai-support/ ├── plan.md # This file ├── research.md # Phase 0 output ├── data-model.md # Phase 1 output ├── quickstart.md # Phase 1 output ├── contracts/ # Phase 1 output └── tasks.md # Phase 2 output (/speckit-tasks — not created here) ``` ### Source Code (repository root) ```text supporthub-api/ ├── prisma/ │ └── schema.prisma # MODIFIED — add AISupportSession, AIDiagnosis, │ AIInteraction, AIAction, AIActionResult, │ AIKnowledgeReference, AIConfidencePolicy ├── src/ │ ├── infrastructure/ │ │ └── ai/ # NEW — Anthropic client singleton, model/effort │ │ └── anthropic.client.ts config resolved from env (research.md) │ ├── jobs/ │ │ └── ai-session/ # NEW — worker for the queued first-turn diagnosis │ │ └── index.ts │ ├── bootstrap/ │ │ └── queue.bootstrap.ts # MODIFIED — register the new AI-session worker │ └── modules/ │ ├── ticketing/ │ │ └── tickets/ │ │ └── service/ │ │ └── tickets.service.ts # MODIFIED — enqueue AI_SESSION job on new ticket │ └── ai-support/ │ ├── knowledge/ # existing (004) — untouched │ ├── sessions/ # NEW │ │ ├── controller/ routes/ schema/ repository/ service/ types/ mapper/ │ │ │ constants/ index.ts │ ├── tools/ # NEW │ │ ├── controller/ routes/ schema/ repository/ service/ types/ mapper/ │ │ │ constants/ index.ts # constants/tool-registry.ts — the fixed tool set │ ├── troubleshooting/ # NEW │ │ └── service/ types/ index.ts # no own routes — invoked by sessions' service │ └── escalation/ # NEW │ └── service/ types/ index.ts # no own routes — invoked by sessions' service └── tests/ ├── unit/ai-support/ # confidence-band decision, tool policy gate, │ runbook step-advancement (pure functions) └── integration/ # full session flow against real Postgres + real Anthropic API (quickstart.md scenarios) ``` **Structure Decision**: Single project. `troubleshooting` and `escalation` are internal-only submodules (service logic `sessions` calls through their `index.ts`, per Principle III) rather than exposing their own routes — neither has an independent HTTP surface in spec.md's requirements; both are invoked as part of a session turn. This mirrors how `messages`/ `attachments` in 003-ticketing are separate modules from `tickets` but still ultimately driven through the same request. ## Complexity Tracking *No constitution violations — table intentionally omitted.*