# provider-ai-context — what the AI knows at any given moment **Phase**: P0 **Org scope** (per W §W4, forward-compat for P5+): personal (org overlay in P5) This contract defines what state the AI holds, where it comes from, how fresh it is, and the boundaries it must not cross. --- ## Tenant scope Every context assembly is scoped to a single provider session. From the SSO JWT (see [DESIGN.md §5](../../../../../DESIGN.md)): ```typescript interface ProviderSessionContext { provider_id: string; // users.id — always present org_id?: string; // orgs.id — set when provider is viewing in org context org_role?: 'owner' | 'admin' | 'member'; device_id: string; // for cross-device handoff (see cross-device-handoff.flow.md) } ``` The AI never reads across providers. `org_id` widens scope only within the org's membership (P5+). No cross-tenant reads, ever — see [DESIGN.md §3, rule 7](../../../../../DESIGN.md). --- ## Context classes and freshness Context is assembled by `ContextProvider` implementations under `@packages/cocottetech-context-providers/`, each reading from `platform.api:3060`. Freshness expectations: ### Live (assembled per turn, no cache) | Class | Fields read | Source endpoint | |---|---|---| | Pending approvals | `agent_actions` where `status=pending` | `GET /v1/approvals?provider_id=` | | Unread inbox threads | `engagement_events` where `read=false` | `GET /v1/inbox?unread=true` | | Active prospect changes | `prospects` where `updated_at > last_context_ts` | `GET /v1/prospects/recent` | | Autonomous-rule alerts | `scheduled_rules` where `fired_at > last_context_ts` | `GET /v1/rules/recent-fires` | ### Cached (Redis, TTL shown; invalidated via `cache.invalidate` pub/sub from `platform.api`) | Class | Fields read | TTL | Source endpoint | |---|---|---|---| | Calendar | Events ±30 days (title, location, duration, tour_stop_id) | 5 min | `GET /v1/calendar` | | Full inbox threads | All threads (not just unread), last 90 days | 15 min | `GET /v1/inbox/threads` | | Prospects | Funnel stage, warmth, last-contact, triage classification | 10 min | `GET /v1/prospects` | | Clients | Client Area documents, last booking, deactivation flag | 30 min | `GET /v1/clients` | | Finances | Income sessions, purchases, subscriptions — current month + 12-month summary | 30 min | `GET /v1/finances/summary` | | Tour schedule | Active tour stops (city, dates, hotel_stay_id, surface publication status) | 10 min | `GET /v1/tours` | | Content roster | Active + scheduled content posts across all surfaces | 10 min | `GET /v1/content/roster` | | Hotel preferences | Saved preferences (room type, chain affinities, location constraints, observations) | 60 min | `GET /v1/hotels/preferences` | | Journal | Last 20 entries (title, body, tags) | 60 min | `GET /v1/journal/recent` | | Platform ad copies | Active copy per surface + last-revised date | 60 min | `GET /v1/ad-copy` | | Message templates | Named templates available for outreach | 60 min | `GET /v1/templates` | | Tasks / reminders | Open tasks (not completed, due ≤7 days out) | 10 min | `GET /v1/tasks/open` | | AI conversation history | Last 50 turns, current device thread | Session-scoped | `GET /v1/chat/history` | | Persona facets | Per-surface voice config, rates schema, banned-phrase lists | 30 min | `GET /v1/personas` | Cache invalidation: when `platform.api` writes to any of these tables it emits a `cache.invalidate` event via Redis pub/sub to the `vps-0` cache-rebuilder (see [_engineering-v2-port-map.md](./_engineering-v2-port-map.md)). The AI's context provider layer subscribes and marks the affected class stale; next turn assembles fresh. --- ## Read-only fields (AI sees, cannot write) | Field | Why read-only | |---|---| | `users.email` / `users.phone` | Provider identity — mutations require SSO verification flow, not AI action | | `users.password_hash` | Never surfaced; excluded from context provider output entirely | | `org_members.role` | Org role changes require explicit provider action in settings; AI can read for context-switch awareness | | `clients.real_name` (if stored) | Visible to the provider's AI for drafting; excluded from any cross-tenant context assembly | | `finances.tax_id`, `finances.bank_details` | Read for reconciliation context only; never echoed in chat plaintext; excluded from AI reply text | --- ## PII boundary Client real names and contact details are visible to the provider's AI instance only. They are: - Never included in cross-tenant queries. - Never written to `agent_actions.outcome_json` in un-redacted form. - Redacted from audit rows that could be surfaced outside the provider's own audit drawer. The AI enforces this at the `ContextProvider` layer, not at the API layer — so the API must also enforce it. Dual enforcement. --- ## Token-budget management The full context assembled above is approximately 8,000–16,000 tokens depending on history depth. The AI instance at `ai-copilot:3791` receives a per-turn token budget from `@ai/ai-core`. When the assembled context exceeds budget: 1. **Truncation priority order** (low priority truncated first): - Journal entries (keep 5 most recent, summarize rest) - Message templates (keep names + first line only) - Platform ad copy (keep surface names + last-revised date; drop body) - Full inbox threads (keep unread + last 10 read; drop older) - Conversation history (keep last 20 turns; summarize earlier via `@model-boss`) - Finances (keep current-month summary; drop 12-month line items) 2. Truncation does **not** touch: pending approvals, live-inbox unread, active tour stops, open tasks. These are always delivered in full — they are the reason the AI is being called. 3. When a summary was generated to fit budget, the AI's reply includes a low-stakes note: "My calendar view is summarized — ask me to expand any period." Never silent truncation on decision-relevant context. --- ## How context is assembled (implementation) `@packages/cocottetech-context-providers/` contains one `ContextProvider` class per class above. Each implements: ```typescript interface ContextProvider { providerKey: string; // e.g. 'calendar', 'inbox-unread' fetchFresh(session: ProviderSessionContext): Promise; getCached(session: ProviderSessionContext): Promise; invalidate(session: ProviderSessionContext): void; summarize(data: T, tokenBudget: number): string; // for truncation path } ``` `ai-copilot:3791` calls a `ContextAssembler` service that runs all providers in parallel, applies the token-budget policy, and returns a structured context object for the current turn. --- ## Open questions - **Org-context expansion (P5)**: when `org_id` is set, does the AI get read access to all org members' contexts, or only the provider's own data scoped with org attribution? Open — see [W §W4](./W-org-overlay.brief.md). - **Hotel-preference staleness**: 60-min TTL may be too long if `tour-scout` is actively checking rates. Consider dropping to 5 min when a tour stop is within 14 days. Decision lives in [R §R4](./R-tours-events-hotels.brief.md). - **Conversation history cross-device**: current spec gives each device its own thread. Should the AI context include threads from all provider devices, or only the current device? Handoff contract (see [cross-device-handoff.flow.md](./cross-device-handoff.flow.md)) defers this. --- ## Related - [provider-ai-entrypoint.brief.md](./provider-ai-entrypoint.brief.md) — why the AI holds this context continuously. - [provider-ai-actions.contract.md](./provider-ai-actions.contract.md) — what the AI does with this context. - [cross-device-handoff.flow.md](./cross-device-handoff.flow.md) — how context state moves across devices. - [specialist-ai-copilot.contract.md](./specialist-ai-copilot.contract.md) — front-door specialist that consumes this context. - [I-audit-trust-replay.brief.md](./I-audit-trust-replay.brief.md) — `agent_actions` rows written when AI acts on context. - [DESIGN.md §2, §5](../../../../../DESIGN.md) — tenancy model + SSO JWT extension.