Files
support_backend/specs/005-ai-support/plan.md
T
saqib mirandClaude Sonnet 5 eceb00632d docs: correct 005-ai-support design to reuse the existing ticket state machine
003-ticketing's ticket-state-machine.ts already defines AI_ANALYZING/
AI_TROUBLESHOOTING/AI_VERIFYING/AI_RESOLVED/HUMAN_ESCALATION ticket
statuses, clearly authored anticipating this feature. Corrects the plan
before implementation: AISupportSession.status now drives Ticket.status
through the existing ticketsService.updateStatus (reusing its optimistic
concurrency), instead of an isolated status field the rest of the system
never sees. Also clarifies that knowledge retrieval is an in-process
service call through knowledge's index.ts, not an HTTP loopback.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-02 16:55:01 +05:30

168 lines
11 KiB
Markdown

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