Two-call reasoning design (structured-output diagnosis, then a separate knowledge-grounded reasoning/tool call), confidence-band policy as a DB- configurable gate applied by app code, a deterministic tool-policy gate that never reads AI free text, an app-owned runbook step index, and a fail-closed placeholder verification tool mirroring the existing malware-scanner precedent. Real Anthropic Claude integration per explicit product decision. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
4.5 KiB
Contract: AI Support Sessions, Tools, and Confidence Policy
All admin routes gated by fastify.authenticate (research.md — known limitation inherited from
002/003/004). Session routes are not admin routes — they're called by the ticket-owning caller
(customer-facing surface, matching 003-ticketing's POST/GET .../messages pattern) and carry no
additional gate of their own in this feature.
Session lifecycle (internal trigger, not a public route)
A session is not started via an explicit "start" endpoint — TicketsService. createFromInboundRequest (003) enqueues a QueueName.AI_SESSION job on new-ticket creation
(research.md "Session triggering"); the worker runs the first diagnosis turn and, on the
"proceed"/"ask" branches, writes the AI's first message onto the ticket the same way any
subsequent turn does.
Session turns
POST /tickets/:ticketId/ai-session/messages— body{ message: string }. Records the customer's reply as anAIInteraction(role: customer) and aTicketMessage(type: CUSTOMER_MESSAGE, same as any other customer message), runs the next reasoning turn synchronously, and returns the AI's resulting turn.404if no active session exists for this ticket (FR-001 — a session that already ended doesn't silently restart).GET /tickets/:ticketId/ai-session— returns the current (or most recent) session's status, latest diagnosis, and interaction history — the read path a future agent/customer UI (Phase 8/10) would call; not itself a reasoning trigger.
Response shape (both the async first-turn write and the sync .../messages response)
{
"sessionId": "...",
"status": "analyzing | troubleshooting | verifying | resolved | escalated | ended_by_agent",
"diagnosis": { "product": "...", "feature": "...", "problemType": "...", "severity": "...", "confidence": 0.0, "possibleCauses": ["..."] },
"message": "the AI's customer-facing text for this turn, if any",
"escalation": { "summary": "...", "stepsAttempted": ["..."], "confidence": 0.0 }
}
escalation is present only when status becomes escalated this turn (FR-021).
Tool actions (read-only audit surface)
GET /tickets/:ticketId/ai-session/actions— lists everyAIAction(+ itsAIActionResultif one exists) for the ticket's session(s), in order — the durable, auditable record FR-013 requires, independently inspectable from the conversation transcript.
Confidence policy admin config
PUT /admin/products/:externalProductId/ai-policy— body{ categoryId?, highThreshold, lowThreshold, maxClarifyingQuestions }. Upserts the(productId, categoryId)row (research.md "most-specific-match fallback").400ifhighThreshold <= lowThreshold.GET /admin/products/:externalProductId/ai-policy— returns every configured row for this product (including thecategoryId: nullproduct-wide row, if set) plus the system-wide defaults that would apply to an unconfigured category.
Guarantees (callable contract)
- A ticket never has two active AI sessions at once —
POST .../messagesagainst a ticket whose session already ended returns404, never silently opening a new one (FR-001). - A diagnosis below the configured low threshold escalates on that same turn — never a proceed/ask outcome for a confidence value the policy says should escalate (FR-004, SC-002).
- A high-risk tool proposal is never auto-executed —
GET .../actionsfor a session that proposedoverrideTicketPriorityalways showsevaluationOutcome: pending_approvalwith noAIActionResult, regardless of the diagnosis's confidence or the AI's own stated justification (FR-012, SC-003). statusonly ever becomesresolvedalongside a passingverifyProductResolutionresult on the same session — never from customer-reply content alone (FR-018, SC-004).- Changing
AIConfidencePolicyvia the admin endpoint applies to the very next diagnosis for that product/category — no caching, no propagation delay (FR-005, SC-005). - An escalated turn's response always includes a non-empty
escalation.summaryandstepsAttempted— a human agent picking up the ticket never has to re-derive what happened from the raw transcript alone (FR-021, SC-006). - When
activeRunbookKeyis set, the step index only ever advances by exactly one per completed step, forward —GET /tickets/:ticketId/ai-sessionnever shows acurrentStepIndexthat skipped or moved backward relative to the runbook's authoredstepsorder (FR-015, SC-007).