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>
11 KiB
Implementation Plan: AI Support Agent
Branch: 005-ai-support | Date: 2026-09-02 | Spec: 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)
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)
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.