cocottetech/@platform/codebase/@features/ai-copilot/docs/provider-ai-context.contract.md
2026-05-18 19:58:47 -07:00

8 KiB
Raw Blame History

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):

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.


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). 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,00016,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:

interface ContextProvider<T> {
  providerKey: string;             // e.g. 'calendar', 'inbox-unread'
  fetchFresh(session: ProviderSessionContext): Promise<T>;
  getCached(session: ProviderSessionContext): Promise<T | null>;
  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.
  • 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.
  • 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) defers this.