# Travel Planner AI Agent — Product & Technical Design Specification **Status:** Design approved **Date:** 2026-08-17 **Target:** Self-hosted, Docker Compose, TeamCity deployment **Primary stack:** Angular PWA, NestJS, PostgreSQL, Redis/BullMQ, Mistral --- ## 1. Product vision Build a self-hosted travel-planning application for multiple trips. The application combines a conventional structured travel planner with an AI travel agent. The system must support long-running trip planning over months or years. It should not only suggest activities and assemble daily itineraries, but proactively identify items that should be booked, registered for, confirmed, or researched early. It must then monitor these items and notify users by in-app notification, email, and PWA web push when action is needed. The first reference scenario is a group trip to Slovenia in October 2027 around the Ljubljana Marathon. The product must remain generic and support arbitrary future trips. ### 1.1 Product principles 1. The database is the source of truth; the LLM is not. 2. The AI agent reasons, recommends, and calls domain-specific tools; it never receives raw database, shell, arbitrary HTTP, or filesystem access. 3. Web research is configurable per trip and may be disabled to control cost. 4. Structured external APIs such as routing, places, geocoding, or weather remain available independently of web research. 5. The LLM may always use model knowledge, but potentially stale information must be labeled as unverified. 6. Confirmed bookings and locked itinerary items are protected from autonomous changes. 7. Critical changes require explicit user/owner confirmation. 8. Background agent runs have fewer permissions than interactive runs. 9. All important agent actions are auditable and reversible where practical. 10. The system degrades gracefully if Mistral or another external provider is unavailable. --- ## 2. MVP scope ### 2.1 Included in MVP #### Authentication and users - OIDC SSO via an existing Identity Provider. - Authorization Code Flow with PKCE for the Angular PWA. - Access-token validation in NestJS using OIDC discovery/JWKS. - No local passwords. - Local user record keyed by stable OIDC `sub`. - Persistent user travel preferences. - Per-user notification preferences. #### Trips - Multiple trips per user. - Owner and invited members. - Travelers without user accounts, including children/infants. - Trip-specific preference overrides. - Per-trip web-research switch. - Per-trip periodic agent review switch. #### Planning - Day-by-day itinerary. - Itinerary items with times, locations, types, notes, and lock state. - Drag-and-drop UI. - Manual planning and AI planning. - AI can optimize a day or an entire trip while respecting locked items and bookings. - Before/after preview for protected or significant changes. - Undo support for reversible AI changes. #### Activities - Activity ideas and shortlist. - AI-generated suggestions. - Activity status: idea, shortlisted, planned, booked, rejected, done. - Group voting: like / neutral / dislike. - Agent scoring for group fit, child friendliness, budget fit, travel effort, uniqueness, booking urgency. - Provenance and verification status for relevant facts. #### Research - Model knowledge is always available. - Optional web research per trip. - Structured-provider access independent of web-research setting. - Research facts stored with source type, confidence, retrieval time, and validity. - Web sources stored separately. - Old facts can be superseded instead of silently overwritten. #### Watch items and proactive monitoring - Manual and agent-created watch items. - Booking windows. - Registration windows. - Deadlines. - Availability checks. - Information-confirmation checks. - Dynamic `nextCheckAt` rather than fixed high-frequency polling. - Background trip review. - Email, web push, and in-app notifications. - Notification severity and deduplication. #### Bookings - Manual booking entry. - PDF/JPEG/PNG attachment upload. - AI-assisted extraction into a booking draft. - User confirmation before extracted data becomes authoritative. - Confirmed bookings become hard planning constraints. #### Budget - Overall trip budget. - Budget categories. - Estimated and actual known costs. - Agent considers budget in recommendations. - No expense splitting in MVP. #### UI - Trip overview. - Trip dashboard. - Itinerary. - Ideas/activities. - Bookings. - Budget. - Watch items. - Group/travelers. - Integrated travel-agent chat. - Planning-mode emphasis before a trip. - Travel-mode emphasis during a trip. - Installable mobile-friendly PWA. ### 2.2 Explicit non-goals for MVP - Direct hotel, flight, train, Airbnb, or Booking.com purchase flows. - Automatic purchases or cancellations. - Payment processing. - Gmail/email booking ingestion. - Expense splitting / Splitwise clone. - Public social profiles or feeds. - Native Android/iOS applications. - Custom map/navigation engine. - Kubernetes. - Microservice architecture. - Multiple LLM providers implemented simultaneously. - Fully autonomous critical changes. --- ## 3. Reference scenario: Slovenia 2027 A golden/evaluation trip must represent: - Destination: Slovenia. - Approximate dates: October 2027. - Group: 4–6 adults plus one small child. - Rough structure: about one week in a high-quality nature accommodation, followed by Friday–Tuesday in Ljubljana. - Main event: Ljubljana Marathon. - Assumed candidate date: 17 October 2027, but it must remain unverified until officially confirmed. - City accommodation may be split across multiple apartments. - Agent must consider the child, group size uncertainty, marathon preparation/recovery, transport time, budget, and booking urgency. - Agent should research candidate nature regions when web research is enabled. - Agent should watch the official marathon date and registration opening. - Agent should notify by email and PWA push when action becomes relevant. This scenario is used as a golden test case throughout implementation. --- ## 4. High-level architecture ```text Browser / Angular PWA | | HTTPS + OIDC v Edge reverse proxy (only published Docker port) | +--> Angular static files | +--> NestJS API (internal Docker network only) | +--> PostgreSQL +--> Redis / BullMQ +--> local file storage | +--> Mistral +--> Web research provider +--> Places / routing / weather providers Redis / BullMQ | v NestJS Worker (internal only) | +--> background trip reviews +--> watch checks +--> notification delivery +--> document processing ``` The backend is a modular monolith. The API process and worker process share domain/application libraries but have separate entry points. --- ## 5. Domain model ### 5.1 User ```text User - id: UUID - externalSubjectId: string unique - displayName: string - email: string - createdAt - updatedAt ``` The application stores no password. ### 5.2 UserPreference Contains persistent structured travel preferences plus optional free-text notes. Suggested fields: - preferredPace - preferredBudgetLevel - maxWalkingDistanceKm - preferredStartTime - childFriendlyPreferred - interests[] - notes ### 5.3 Trip ```text Trip - id - name - description - ownerId - startDate - endDate - status - planningStage - currency - version - createdAt - updatedAt ``` Suggested statuses: - DRAFT - PLANNING - BOOKING - UPCOMING - ACTIVE - COMPLETED - ARCHIVED ### 5.4 TripSettings ```text TripSettings - tripId - webResearchEnabled - periodicAgentReviewEnabled - notificationEmailEnabled - notificationPushEnabled - defaultResearchDepth - defaultPlanningStyle ``` Possible future fields, not enforced in initial MVP: - maxResearchCallsPerDay - maxAgentBudgetPerMonth ### 5.5 TripMember Links authenticated application users to trips. ```text TripMember - id - tripId - userId - role: OWNER | MEMBER - status: INVITED | ACTIVE | DECLINED - joinedAt ``` ### 5.6 Traveler Represents people who actually travel, including people without accounts. ```text Traveler - id - tripId - linkedUserId? - displayName - travelerType: ADULT | CHILD | INFANT - createdByUserId ``` ### 5.7 TripInvitation ```text TripInvitation - id - tripId - email - invitedByUserId - tokenHash - expiresAt - acceptedAt? ``` Invitation does not replace authentication. Recipient authenticates through the IdP before joining. ### 5.8 TripPreferenceOverride Trip-specific override of user/traveler preferences. Precedence: 1. Trip-specific override. 2. Persistent user preference. 3. Application default. ### 5.9 Location ```text Location - id - name - latitude - longitude - address? - city? - region? - country? - externalPlaceId? ``` ### 5.10 Activity ```text Activity - id - tripId - title - description - locationId? - category - estimatedDurationMinutes? - estimatedPrice? - bookingRequired? - bookingRecommendation? - status - createdByUserId? - createdByAgent: boolean - version ``` Statuses: - IDEA - SHORTLISTED - PLANNED - BOOKED - REJECTED - DONE ### 5.11 ActivityScore Agent assessment, never presented as objective fact. ```text ActivityScore - activityId - groupFit - childFriendliness - budgetFit - travelEffort - uniqueness - bookingUrgency - explanation - generatedAt ``` ### 5.12 ActivityVote ```text ActivityVote - activityId - userId - vote: LIKE | NEUTRAL | DISLIKE ``` ### 5.13 ItineraryDay ```text ItineraryDay - id - tripId - date - title? - notes? - version ``` ### 5.14 ItineraryItem ```text ItineraryItem - id - itineraryDayId - type - startTime? - endTime? - locationId? - activityId? - bookingId? - title - notes? - locked: boolean - source - sortOrder - version ``` Types: - ACTIVITY - TRANSPORT - ACCOMMODATION - MEAL - EVENT - FREE_TIME - NOTE ### 5.15 Booking ```text Booking - id - tripId - type - title - provider? - confirmationNumber? - startDateTime - endDateTime? - locationId? - totalPrice? - currency? - status - cancellationDeadline? - notes? - version ``` Types: - ACCOMMODATION - FLIGHT - TRAIN - CAR_RENTAL - ACTIVITY - EVENT - RESTAURANT - OTHER Statuses: - DRAFT - RESERVED - CONFIRMED - CANCELLED - COMPLETED ### 5.16 Attachment ```text Attachment - id - bookingId? - tripId - filename - mimeType - storagePath - uploadedAt ``` ### 5.17 ResearchFact ```text ResearchFact - id - tripId - subjectType - subjectId - factType - value (structured JSON where appropriate) - sourceType - confidence - verified - researchedAt - validUntil? - status ``` Source types: - MODEL_KNOWLEDGE - WEB - STRUCTURED_API - USER - DOCUMENT - BOOKING Statuses: - CURRENT - SUPERSEDED - INVALIDATED ### 5.18 ResearchSource ```text ResearchSource - id - researchFactId - url - title? - publisher? - retrievedAt ``` ### 5.19 WatchItem ```text WatchItem - id - tripId - title - description - type - subjectType? - subjectId? - status - priority - targetDate? - nextCheckAt? - requiresWebResearch - createdByAgent - createdAt - updatedAt ``` Types: - FIXED_DATE - BOOKING_WINDOW - REGISTRATION - AVAILABILITY - PRICE - DEADLINE - REMINDER - INFORMATION_UPDATE - INFORMATION_CONFIRMATION - WEB_CONDITION Statuses: - PENDING - WAITING - CHECK_DUE - ACTION_AVAILABLE - ACTION_RECOMMENDED - NOTIFIED - COMPLETED - SUSPENDED - FAILED ### 5.20 TripBudget / BudgetItem ```text TripBudget - tripId - totalBudget - currency ``` ```text BudgetItem - id - tripId - category - description - estimatedAmount? - actualAmount? - bookingId? - activityId? - sourceType? ``` Categories: - ACCOMMODATION - TRANSPORT - FOOD - ACTIVITIES - SHOPPING - OTHER ### 5.21 Notification ```text Notification - id - userId - tripId - severity - title - message - deduplicationKey - readAt? - createdAt ``` Channels are modeled separately as deliveries so one notification can be sent via several channels. ### 5.22 NotificationDelivery ```text NotificationDelivery - id - notificationId - channel: IN_APP | EMAIL | WEB_PUSH - status - attemptedAt? - deliveredAt? - failureReason? ``` ### 5.23 PushSubscription ```text PushSubscription - id - userId - endpoint - publicKey - authSecret - createdAt - lastUsedAt? ``` ### 5.24 PendingAgentAction ```text PendingAgentAction - id - tripId - requestedByUserId? - actionType - payload - explanation - consequence - expiresAt - status - createdAt ``` Statuses: - PENDING - APPROVED - REJECTED - EXPIRED - EXECUTED - FAILED ### 5.25 AgentRun ```text AgentRun - id - tripId - initiatedByUserId? - mode - provider - model - status - startedAt - finishedAt? - inputTokenCount? - outputTokenCount? - estimatedCost? - webResearchCalls - toolCallCount - errorCode? ``` Modes: - INTERACTIVE - PLANNING - BACKGROUND_REVIEW Statuses: - RUNNING - COMPLETED - PARTIALLY_COMPLETED - FAILED - CANCELLED ### 5.26 AgentAction ```text AgentAction - id - agentRunId - tripId - userId? - actionType - description - input - output - status - requiresConfirmation - createdAt ``` ### 5.27 AuditEntry ```text AuditEntry - id - tripId - actorType: USER | AGENT | SYSTEM - actorUserId? - action - entityType - entityId - metadata - createdAt ``` --- ## 6. Authentication and authorization ### 6.1 OIDC - Angular uses Authorization Code + PKCE. - NestJS validates bearer tokens using the configured issuer discovery metadata and JWKS. - Stable `sub` maps to `User.externalSubjectId`. - API secrets remain server-side. ### 6.2 Roles MVP roles: - OWNER - MEMBER Owner permissions include trip deletion, trip settings, membership management, and approval of owner-protected agent actions. Members may view, plan, vote, add activities, interact with the agent, and manage their own preferences/notifications. ### 6.3 Authorization sequence Every write follows: ```text Authenticated user -> trip membership/role check -> agent tool policy (if agent initiated) -> application business validation -> confirmation policy -> transaction/persistence ``` The LLM never decides whether authorization is bypassed. --- ## 7. AI provider architecture ### 7.1 Provider abstraction MVP provider: Mistral. Domain and agent logic must depend on an internal interface rather than Mistral-specific classes. Conceptual interface: ```text LlmProvider - complete(...) - completeStructured(schema, ...) - runWithTools(toolDefinitions, ...) - getCapabilities() ``` Potential capabilities: - TOOL_CALLING - STRUCTURED_OUTPUT - VISION - LONG_CONTEXT Future providers may implement the same interface without changing travel-domain logic. ### 7.2 Agent execution modes #### Interactive Triggered by user chat. Can use normal read and safe-write tools and request confirmation for protected actions. #### Planning Triggered by explicit larger planning requests. May create or reorganize multiple unlocked items. Significant/protected changes require confirmation. #### Background review Triggered by jobs. Allowed to research, update facts, create watch items, create activity ideas, and create notification candidates. It cannot change confirmed bookings, locked itinerary items, or budget limits. --- ## 8. Agent tool model ### 8.1 Core rule Tools are domain-specific. Never provide generic: - SQL execution. - Shell execution. - Arbitrary HTTP request. - Arbitrary filesystem access. - Direct database access. - Direct send-email/send-push. - Purchase/payment. - Permission escalation. ### 8.2 Tool metadata Each tool must carry policy metadata: ```text - name - category - requiresUserContext - requiresTripContext - requiresWebResearch - requiresConfirmation - allowedInInteractiveMode - allowedInPlanningMode - allowedInBackgroundMode - auditLevel ``` `AgentToolRegistry.getAvailableTools(context)` returns only tools allowed for the current user, trip settings, and execution mode. ### 8.3 Context tools - `get_trip_context` - `get_trip_members` - `get_itinerary` - `get_activities` - `get_bookings` - `get_budget_status` - `get_watch_items` - `get_research_facts` ### 8.4 Activity tools - `create_activity_idea` - `update_activity` - `score_activity` - `reject_activity` ### 8.5 Planning tools - `create_day_plan` - `add_itinerary_item` - `move_itinerary_item` - `remove_itinerary_item` - `lock_itinerary_item` - `request_unlock_itinerary_item` - `check_budget_impact` Optimization is an agent workflow composed from tools, not a privileged direct database operation. ### 8.6 Places and routing tools - `search_places` - `geocode_location` - `calculate_route` - `calculate_route_matrix` These remain usable even when web research is disabled. ### 8.7 Weather tool - `get_weather` The provider result must distinguish actual forecast data from historical/climatological estimates. Future-date estimates must never be described as real forecasts. ### 8.8 Web research tools Only registered when `TripSettings.webResearchEnabled == true`: - `search_web` - `fetch_web_source` Research purpose enum: - OPENING_HOURS - BOOKING_WINDOW - PRICE - AVAILABILITY - EVENT_DATE - ACTIVITY_DISCOVERY - GENERAL_RESEARCH Preferred source priority: 1. Official organizer/provider. 2. Official tourism/public authority. 3. Structured data provider. 4. Reliable secondary source. 5. General travel portal/blog. ### 8.9 Research fact tools - `save_research_fact` - `invalidate_research_fact` ### 8.10 Watch tools - `create_watch_item` - `update_watch_item` - `complete_watch_item` - `schedule_next_watch_check` - `request_delete_watch_item` ### 8.11 Budget tools - `add_budget_estimate` - `update_budget_estimate` - `check_budget_impact` Actual costs may only come from user input, confirmed booking data, or an explicitly trusted extraction flow. ### 8.12 Booking tools - `create_booking_draft` - `extract_booking_from_document` - `request_confirm_booking` - `request_modify_confirmed_booking` ### 8.13 Notification tool Agent receives `create_notification_candidate`, not `send_email` or `send_push`. Delivery channel is chosen by `NotificationPolicyService` using severity, user preferences, and trip preferences. ### 8.14 Confirmation tool - `request_user_confirmation` Protected actions create a `PendingAgentAction` rather than directly mutating protected state. --- ## 9. Agent safety and validation ### 9.1 Backend-enforced policies The following must not depend only on prompt instructions: - Lock protection. - Confirmed booking protection. - Role authorization. - Web-research enablement. - Background-mode write restrictions. - Budget-change confirmation threshold. - Trip date boundaries. - Optimistic locking/version checks. ### 9.2 Structured output All agent-to-application mutation plans and tool arguments must be schema-validated before execution. Invalid or unknown tool calls are rejected and logged. ### 9.3 Provenance Relevant facts must expose origin and verification state. UI labels should distinguish: - model knowledge / not current-verified; - structured API data; - web-verified data; - user-provided data; - document-derived data; - confirmed booking data. ### 9.4 Hallucination handling If a provider/tool fails, no synthetic replacement fact may be stored as verified. Unknown values stay unknown. --- ## 10. Context construction `AgentContextBuilder` creates task-specific context rather than serializing the whole database. Example for "plan Tuesday": - Trip summary. - Tuesday itinerary. - Adjacent-day constraints if relevant. - Travelers and relevant preferences. - Confirmed bookings. - Locked items. - Budget status. - Relevant activity shortlist. - Relevant research facts. - Trip settings. Exclude unrelated historic notifications, old audit events, old runs, and unrelated trips. --- ## 11. Background jobs and proactive behavior ### 11.1 Queue architecture Use Redis + BullMQ. Job types: - `trip-review` - `watch-check` - `notification-delivery` - `document-processing` - optional long-running `agent-task` Scheduler only enqueues due work; workers execute it. ### 11.2 Periodic trip review For future trips with `periodicAgentReviewEnabled`, run a lightweight periodic review, initially weekly by default. Purpose: - Detect missing important watch items. - Detect upcoming planning gaps. - Decide whether a specific future research action should be scheduled. - Avoid blanket web research on every run. ### 11.3 Watch-item scheduling Use `nextCheckAt`, not high-frequency polling. The watch strategy may set checks months apart early in planning and more frequently as the relevant time approaches. ### 11.4 Web research disabled If a due watch requires web research while trip web research is disabled: - do not invoke a web provider; - retain the watch; - surface that verification is suspended/unavailable; - avoid silent failure. ### 11.5 Notification severity Suggested defaults: - INFO: in-app only. - MEDIUM: in-app, optional push based on preference. - HIGH: in-app + push + email. - URGENT: in-app + push + email. ### 11.6 Deduplication Every event-like notification has a stable deduplication key such as: `watch::registration-open` Retries must not generate duplicate messages. --- ## 12. User interface ### 12.1 Main information architecture Trip navigation: - Overview - Itinerary - Ideas - Bookings - Budget - Watches - Group Agent is accessible globally within a trip as a side panel on desktop and full-screen sheet on mobile. ### 12.2 Trip list Each trip card shows: - title/destination; - date range; - traveler/member count; - planning progress; - outstanding urgent actions. ### 12.3 Trip dashboard Primary question: "Where are we and what needs attention next?" Show: - next action/urgent watch item; - planning progress; - major trip segments; - budget summary; - pending votes/decisions; - upcoming booking/research tasks. ### 12.4 Itinerary - Day-based layout. - Drag-and-drop. - Visible lock icons. - AI optimize-day / optimize-trip actions. - Before/after diff for significant changes. - Undo for reversible actions. ### 12.5 Ideas Cards show: - title/category; - estimated duration/cost; - group score; - child suitability; - votes; - rationale; - verification/provenance summary; - add-to-itinerary action. ### 12.6 Activity detail Show each important fact with source/verification state and research timestamp where available. ### 12.7 Watches Groups: - action required; - being monitored; - later; - completed/paused. Users can pause, reactivate, mark complete, edit next-check preference, configure recipients, or delete watches manually. ### 12.8 Bookings Show confirmed vs draft/unbooked status prominently. Upload confirmation documents and review extracted fields before confirmation. ### 12.9 Budget Show total, planned/estimated, actual known, remaining, and categories. ### 12.10 Planning vs travel mode Before the trip, emphasize planning, watches, ideas, budget, and bookings. During active trip dates, emphasize today, next item, location/navigation handoff, tickets/bookings, and agent. --- ## 13. REST API Use REST for MVP. Base routes: ```text /api/v1/users/me /api/v1/users/me/preferences /api/v1/users/me/notification-settings /api/v1/trips /api/v1/trips/:tripId /api/v1/trips/:tripId/settings /api/v1/trips/:tripId/members /api/v1/trips/:tripId/travelers /api/v1/trips/:tripId/invitations /api/v1/trips/:tripId/activities /api/v1/trips/:tripId/itinerary /api/v1/trips/:tripId/bookings /api/v1/trips/:tripId/budget /api/v1/trips/:tripId/watch-items /api/v1/trips/:tripId/notifications /api/v1/trips/:tripId/agent /api/v1/trips/:tripId/pending-agent-actions ``` ### 13.1 Agent chat `POST /api/v1/trips/:tripId/agent/messages` Response model should support: - assistant message; - actions executed; - resources changed; - pending confirmations; - run ID. Streaming should use Server-Sent Events initially. Stream only user-safe progress events such as "checking routes" or "researching ticket information"; never expose private model chain-of-thought. --- ## 14. File handling ### 14.1 Storage abstraction ```text FileStorageProvider -> LocalFileStorageProvider (MVP) ``` Possible later implementations: S3/MinIO. ### 14.2 Allowed uploads MVP: - PDF - JPEG - PNG Enforce: - max file size; - MIME/type validation; - generated internal storage IDs; - no user-provided filesystem paths; - trip authorization before access. Uploaded content extraction creates drafts, not authoritative booking changes. --- ## 15. Error handling and resilience Error categories should include: - VALIDATION_ERROR - AUTHORIZATION_ERROR - CONFLICT_ERROR - LLM_ERROR - LLM_RATE_LIMIT - LLM_INVALID_OUTPUT - EXTERNAL_API_ERROR - EXTERNAL_API_RATE_LIMIT - WEB_RESEARCH_ERROR - JOB_ERROR - NOTIFICATION_ERROR - DOCUMENT_PROCESSING_ERROR ### 15.1 Partial success An agent run may complete partially. If routing fails but other planning works, return the plan with transfer times explicitly unverified rather than failing the entire request. ### 15.2 Retry Retries belong in infrastructure/job handling, not in LLM reasoning. Use bounded retries with configurable exponential/backoff delays. ### 15.3 Idempotency Background mutations and notifications must use stable idempotency/deduplication keys. ### 15.4 Transactions Multi-entity mutations must use PostgreSQL transactions. ### 15.5 Optimistic locking Important collaborative entities include a `version`. On version mismatch, reject with conflict and reload before retrying agent/user mutation. --- ## 16. Logging, tracing, and usage ### 16.1 Structured logs Log structured technical metadata, e.g.: - run ID; - trip ID; - user ID where applicable; - provider/model; - tool name; - status; - duration; - usage/cost where provided; - error category. Do not dump all prompts, document content, or model output into ordinary logs. ### 16.2 Technical agent trace Trace: - context build; - provider call; - tool calls; - tool result status; - durations; - mutations; - notification candidates. Do not persist private chain-of-thought. ### 16.3 Usage accounting Capture provider usage when available: - input/output tokens; - estimated LLM cost; - web-research call count/cost; - external provider call count/cost where practical. This supports future per-trip/month cost visibility. ### 16.4 Health checks - `/health/live`: process alive. - `/health/ready`: PostgreSQL, Redis, required config. External AI/research-provider outage must not make the core travel app unavailable. --- ## 17. Testing strategy ### 17.1 Unit tests Deterministic rules: - action policy; - confirmation policy; - notification policy; - booking-window calculator; - budget calculations; - trip-date validation; - lock enforcement. ### 17.2 Integration tests - PostgreSQL repositories/services. - Redis/BullMQ jobs. - OIDC guard behavior using test fixtures/mocks. - file storage abstraction. ### 17.3 Agent tool tests Each tool tested without a live LLM. Examples: - moving a locked item creates pending action and does not mutate itinerary; - `search_web` is absent from registry when trip web research is disabled; - background mode cannot modify a confirmed booking. ### 17.4 LLM contract tests Mock `LlmProvider` responses including: - valid tool call; - invalid schema; - unknown tool; - missing required field; - malformed structured output; - rate limit/error. ### 17.5 Agent evaluations Golden scenarios test semantic requirements, not exact text. Examples: - no locked item moved during "optimize everything"; - no current registration date invented when web research is disabled; - child/group constraints considered; - marathon recovery/preparation considered; - budget constraint respected; - research fact labeled correctly by provenance. ### 17.6 E2E flows At minimum: 1. OIDC login -> create trip -> add traveler -> add activity -> agent plan -> itinerary update. 2. Create watch -> background check -> notification -> web push/in-app state. 3. Upload booking -> extract draft -> confirm -> item becomes protected planning constraint. --- ## 18. Backend project structure Recommended monorepo: ```text travel-planner/ ├── frontend/ ├── backend/ │ ├── apps/ │ │ ├── api/ │ │ └── worker/ │ └── libs/ │ ├── auth/ │ ├── users/ │ ├── trips/ │ ├── activities/ │ ├── itinerary/ │ ├── bookings/ │ ├── budget/ │ ├── agent/ │ ├── research/ │ ├── watch-items/ │ ├── notifications/ │ ├── audit/ │ └── infrastructure/ ├── docker/ ├── docs/ ├── compose.yml ├── .env.example └── README.md ``` Module layering should be practical, not ceremonial: ```text api/controller -> application service -> domain rules -> repository/provider abstraction -> infrastructure implementation ``` Agent tools call application services, never repositories directly. --- ## 19. External provider abstractions Required interfaces: ```text LlmProvider -> MistralLlmProvider WebResearchProvider -> configured implementation PlacesProvider -> configured implementation RoutingProvider -> configured implementation WeatherProvider -> configured implementation MailProvider -> SmtpMailProvider PushProvider -> WebPushProvider FileStorageProvider -> LocalFileStorageProvider ``` The app should start with optional integrations disabled if their required configuration is absent, except for integrations explicitly marked required for that environment. --- ## 20. Docker networking and single published port ### 20.1 Hard requirement **Exactly one TCP port from the application Compose stack may be published to the Docker host.** Only the edge/reverse-proxy service may contain a Compose `ports:` mapping. All other services must use Docker-network DNS and internal container ports only. ### 20.2 Recommended topology ```text Host / LAN / Internet | | exactly one published TCP port v +---------------------+ | edge / reverse proxy| | TLS + static Angular| +----------+----------+ | | Docker user-defined bridge network | +------+------+-------+-------+ | | | | v v v v api worker postgres redis | | +---- outbound access ----> Mistral / providers / SMTP ``` ### 20.3 Port behavior - Default public mapping: `${APP_HTTPS_PORT:-443}:443`. - No host mapping for Angular separately. - No host mapping for NestJS. - No host mapping for PostgreSQL. - No host mapping for Redis. - No host mapping for the worker. - No debug/admin port published in production. The edge service serves the Angular production bundle and proxies `/api/*` and SSE endpoints to `api:3000`. ### 20.4 TLS Because a second public HTTP port is prohibited, production must not require port 80. Supported deployment choices: 1. Mount a certificate/key managed outside this stack; or 2. use a TLS certificate flow that can operate without publishing port 80. The exact certificate mechanism is deployment configuration, not application-domain logic. ### 20.5 Example Compose shape This is illustrative, not the final implementation: ```yaml services: edge: image: ${REGISTRY}/travel-edge:${IMAGE_TAG} ports: - "${APP_HTTPS_PORT:-443}:443" depends_on: api: condition: service_healthy networks: [travel] api: image: ${REGISTRY}/travel-api:${IMAGE_TAG} expose: - "3000" networks: [travel] worker: image: ${REGISTRY}/travel-worker:${IMAGE_TAG} networks: [travel] postgres: image: postgres:${POSTGRES_IMAGE_TAG} expose: - "5432" networks: [travel] redis: image: redis:${REDIS_IMAGE_TAG} expose: - "6379" networks: [travel] networks: travel: driver: bridge ``` Do not use `network_mode: host`. ### 20.6 Internal accessibility vs outbound egress The Docker network must allow API/worker outbound connections to configured external providers while not publishing their inbound ports to the host. --- ## 21. Production Docker services Recommended production services: ```text edge api worker postgres redis ``` The Angular application is compiled into the edge image and served as static assets. This removes the need for a second runtime frontend container and guarantees that the edge proxy remains the sole public entry point. Persistent volumes: - PostgreSQL data. - Upload storage. - Optional edge TLS/certificate storage if managed by the stack. Redis is not the system of record. --- ## 22. TeamCity CI/CD design TeamCity is the deployment orchestrator. ### 22.1 Deployment model Recommended model: - TeamCity build agents build and test immutable images. - Images are tagged with an immutable revision identifier (for example VCS revision/build number combination). - Images are pushed to a private/container registry. - A dedicated TeamCity deployment configuration/job deploys to the self-hosted target server over SSH. - The target host contains Compose configuration and environment/secrets, but application images come from the registry. This keeps build and deployment concerns separate and makes rollback possible without rebuilding. ### 22.2 Pipeline stages #### Stage 1 — Validate - Checkout. - Backend dependency install. - Frontend dependency install. - Static analysis/lint. - Unit tests. - Build/type checks. #### Stage 2 — Integration/evaluation - Start ephemeral PostgreSQL/Redis test dependencies. - Run backend integration tests. - Run agent-tool contract tests with mocked LLM/provider implementations. - Run selected golden-scenario evaluations that do not require paid external providers by default. Paid/live-provider smoke tests must be opt-in rather than run for every commit. #### Stage 3 — Build images Build: - `travel-edge` containing Angular production bundle + reverse proxy config. - `travel-api`. - `travel-worker`. Use the same source revision/tag for all application images. #### Stage 4 — Push images - Authenticate to configured registry using TeamCity protected credentials/connection. - Push immutable image tags. - Optionally update a human-readable environment tag only after successful deployment. #### Stage 5 — Deploy On the target host via SSH: 1. Acquire deployment lock / ensure only one production deployment runs at once. 2. Write or export the desired immutable `IMAGE_TAG` (not secrets). 3. `docker compose pull` application images. 4. Run database migrations using the new API image in a one-off non-public container. 5. `docker compose up -d --remove-orphans`. 6. Wait for container health. 7. Call the application readiness endpoint through the single edge port. 8. Run a small post-deploy smoke test. 9. Mark deployment successful. Do **not** routinely execute `docker compose down` before deployment; preserve volumes and minimize avoidable downtime. ### 22.3 Migration rule Database migrations are versioned and run before the new application version is considered healthy. Production ORM auto-sync must be disabled. Migration design should favor backward-compatible expand/contract changes when a schema change cannot safely be applied atomically with deployment. ### 22.4 Rollback Keep the previously deployed image tag. If post-deploy health fails: - restore previous application image tag; - run `docker compose up -d` with previous images; - report failure in TeamCity. Automatic database downgrade is **not** assumed. Schema migrations must therefore be planned so the previous application can continue to run after a failed deployment whenever feasible. ### 22.5 Secrets Secrets must not be baked into images or committed to source control. Examples: - `MISTRAL_API_KEY` - OIDC settings/secrets as applicable - SMTP credentials - web-research provider key - routing/places/weather provider keys - VAPID private key - database credentials Preferred production pattern: - secrets stored on the deployment host in protected environment/secret files with restricted permissions; - TeamCity references deployment credentials through protected parameters/connections; - TeamCity does not echo secret values in build logs. ### 22.6 Deployment concurrency Production deployment configuration must allow only one active deployment at a time. ### 22.7 Artifact/version identity Expose application version/build metadata in a safe endpoint or UI diagnostics panel: - source revision; - TeamCity build number; - deployment timestamp; - schema/migration version if practical. This aids support and rollback analysis. --- ## 23. Configuration Representative configuration: ```text DATABASE_URL= REDIS_URL= OIDC_ISSUER= OIDC_CLIENT_ID= OIDC_AUDIENCE= MISTRAL_API_KEY= MISTRAL_MODEL= SMTP_HOST= SMTP_PORT= SMTP_USER= SMTP_PASSWORD= WEB_PUSH_PUBLIC_KEY= WEB_PUSH_PRIVATE_KEY= WEB_RESEARCH_PROVIDER= WEB_RESEARCH_API_KEY= PLACES_PROVIDER= PLACES_API_KEY= ROUTING_PROVIDER= ROUTING_API_KEY= WEATHER_PROVIDER= WEATHER_API_KEY= APP_HTTPS_PORT=443 IMAGE_TAG= POSTGRES_IMAGE_TAG= REDIS_IMAGE_TAG= ``` Production deployment must set `POSTGRES_IMAGE_TAG` and `REDIS_IMAGE_TAG` to exact, reviewed image tags rather than floating tags such as `latest`. Only public browser-safe OIDC configuration may be embedded in the Angular bundle. Provider secrets remain server-side. --- ## 24. Database migrations and backups - Versioned migrations only. - Disable schema auto-synchronization in production. - Backup PostgreSQL and upload data. - Redis does not contain unique system-of-record data. - Backup/restore procedure must be documented and periodically tested. --- ## 25. Recommended implementation phases Do not ask a coding agent to build the complete product in one prompt. ### Phase 1 — Foundation - Monorepo structure. - Docker development stack. - PostgreSQL/Redis. - NestJS API + worker skeleton. - Angular PWA skeleton. - Edge single-port topology. - TeamCity build/deploy skeleton. - Health endpoints. ### Phase 2 — Auth and trip core - OIDC. - User profile/preferences. - Trip CRUD. - Membership/invitations. - Travelers. - Trip settings. ### Phase 3 — Activities and itinerary - Activities. - Votes. - Itinerary/day/items. - Locking/versioning. - Angular trip UI and drag/drop. ### Phase 4 — Agent foundation - `LlmProvider` abstraction. - Mistral provider. - structured output/tool-call abstraction. - `AgentRun`/`AgentAction`. - Context builder. - Tool registry. - basic interactive agent. ### Phase 5 — Policies and planning tools - action policy. - pending confirmations. - itinerary mutation tools. - audit. - undo model. - planning-run flow. ### Phase 6 — Research/providers - research facts/sources. - optional web-research provider. - places/geocoding/routing/weather provider abstractions. - UI provenance. ### Phase 7 — Watch system and worker - watch items. - scheduler. - BullMQ processors. - trip review. - background restrictions. ### Phase 8 — Notifications - in-app. - SMTP. - web push/PWA subscription. - severity/deduplication. ### Phase 9 — Bookings and documents - bookings. - local file storage. - uploads. - extraction draft. - confirmation/protected-booking constraints. ### Phase 10 — Budget - budget model/UI. - budget agent tools. - impact validation. ### Phase 11 — UX completion - dashboard. - watches view. - ideas/details. - planning/travel-mode emphasis. - responsive PWA polish. ### Phase 12 — Evaluation and hardening - golden scenarios. - resilience tests. - security review. - concurrency tests. - deployment/rollback rehearsal. - backup/restore rehearsal. --- ## 26. Acceptance criteria for the MVP The MVP is considered successful when the following end-to-end scenario works: 1. A user signs in via the existing OIDC provider. 2. The user creates a Slovenia 2027 trip. 3. The user adds 4–6 adult travelers and one child without requiring accounts for every traveler. 4. The user enables web research for the trip. 5. The agent can suggest regions/activities and clearly distinguish model knowledge from verified facts. 6. The group can shortlist and vote on activities. 7. The agent can create a realistic day plan while respecting preferences, child constraints, budget, travel times, confirmed bookings, and locks. 8. The user can manually move unlocked itinerary items via drag/drop. 9. The agent cannot autonomously move a locked/confirmed item; it creates a pending confirmation instead. 10. The agent or user can create a watch for the Ljubljana Marathon date/registration. 11. A background worker can process the due watch. 12. If web research is disabled, the web tool is unavailable and no unverified current date is presented as fact. 13. When a watched condition becomes actionable, a deduplicated in-app notification is created and high-severity notifications are delivered by email and PWA push according to preferences. 14. A booking PDF/image can be uploaded and extracted into a draft; the user must confirm it before it becomes authoritative. 15. Confirmed bookings affect planning as protected constraints. 16. Budget data is shown and considered by agent recommendations. 17. Mistral outage does not prevent users from viewing/editing stored trips. 18. Only one TCP port is published from the production Docker Compose stack, owned by the edge service. 19. PostgreSQL, Redis, API, and worker have no host-published ports. 20. TeamCity can build, test, publish, migrate, deploy, health-check, and roll back application images without rebuilding. --- ## 27. Architecture invariants for implementation agents Any coding AI or developer implementing this specification must preserve these invariants unless the design is explicitly changed: 1. PostgreSQL is the source of truth. 2. No direct LLM database/shell/arbitrary HTTP access. 3. Domain tools call application services, not repositories directly. 4. Web research tool is not registered when disabled for the trip. 5. Structured APIs are separately configurable from web research. 6. Locked/confirmed resources are protected by deterministic backend rules. 7. Background runs have strictly reduced write permissions. 8. AI output is schema-validated before mutation. 9. Facts retain provenance and verification state. 10. Critical actions require explicit confirmation. 11. Notifications are delivered by policy services, not directly by the LLM. 12. Background work is queued and idempotent. 13. External-provider failure does not corrupt stored trip state. 14. Only the edge service publishes a host port in production Compose. 15. All other services communicate only over Docker networking and may use outbound egress as needed. 16. Production deployment is driven by TeamCity using immutable image versions and versioned database migrations. 17. Secrets are never committed or embedded in frontend/application images. --- ## 28. Design status and next step The product and architecture design is complete enough to proceed to an implementation plan. The next artifact should break the implementation into small, testable tasks and coding-agent prompts, beginning with **Phase 1 — Foundation**, rather than generating the entire system at once.