Files
travel-planner/docs/superpowers/specs/travel-agent-design-spec.md
Bastian Wagner feff7c717e initial
2026-08-17 12:49:36 +02:00

1893 lines
42 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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: 46 adults plus one small child.
- Rough structure: about one week in a high-quality nature accommodation, followed by FridayTuesday 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:<watchId>: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 46 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.