feat(ai-session): add FlowPilot AI-powered troubleshooting sessions

Implements Phase 1 of the FlowPilot-First pivot — the core AI session
experience where engineers describe a problem and FlowPilot guides them
through structured diagnosis with selectable options, free-text escape
hatches, and auto-generated documentation on resolution.

Backend: AISession + AISessionStep models, FlowPilot Engine (LLM
orchestration with structured JSON output), Flow Matching Engine v1
(semantic + keyword + recency scoring), 8 API endpoints with auth,
rate limiting, and AI quota enforcement.

Frontend: Intake screen, conversational session view with sidebar,
step cards with options/actions/resolution suggestions, resolve/escalate
modals, documentation view with rating, session history integration,
and /pilot route with sidebar navigation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-18 14:27:36 +00:00
parent 44eb48e457
commit 5494816b06
29 changed files with 3647 additions and 5 deletions

View File

@@ -1,7 +1,7 @@
import uuid
from datetime import datetime, timezone
from typing import Optional, Any, TYPE_CHECKING
from sqlalchemy import String, Text, DateTime, ForeignKey, Boolean, Integer, Index, CheckConstraint
from sqlalchemy import String, Text, DateTime, ForeignKey, Boolean, Integer, Float, Index, CheckConstraint
from sqlalchemy.orm import Mapped, mapped_column, relationship
from sqlalchemy.dialects.postgresql import UUID, JSONB
from app.core.database import Base
@@ -161,6 +161,25 @@ class Tree(Base):
comment="Provenance metadata from .rfflow file import"
)
# Flow matching (FlowPilot AI sessions)
origin: Mapped[Optional[str]] = mapped_column(
String(20), nullable=True,
comment="manual | ai_generated | ai_enhanced"
)
source_session_id: Mapped[Optional[uuid.UUID]] = mapped_column(
UUID(as_uuid=True), nullable=True,
)
match_keywords: Mapped[Optional[list[Any]]] = mapped_column(
JSONB, nullable=True,
comment="Keywords for FlowPilot flow matching"
)
success_rate: Mapped[Optional[float]] = mapped_column(
Float, nullable=True,
)
last_matched_at: Mapped[Optional[datetime]] = mapped_column(
DateTime(timezone=True), nullable=True,
)
# Relationships
author: Mapped[Optional["User"]] = relationship("User", foreign_keys=[author_id], back_populates="trees")
team: Mapped[Optional["Team"]] = relationship("Team", back_populates="trees")