Files
support_backend/specs/005-ai-support/plan.md
T

168 lines
11 KiB
Markdown
Raw Normal View History

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