Skip to main content

Context Memory System — Architecture

Passive intelligence layer that listens to Slack channel messages, extracts structured facts using Claude Haiku, stores them as vector-embedded observations, and synthesizes per-entity summaries nightly. The bot uses these at query time to inject relevant organizational context into LLM prompts.

1. Data Flow — End to End


2. Entity Relationship Diagram


3. Ingestion Detail

Message Flow

  1. Bot receives every Slack channel message (not DMs)
  2. Fires POST /context/ingest with organizationId, channelId, channelName, platformUserId, messageTs, threadTs, text
  3. Message inserted into channel_messages with processedAt = null
  4. URLs extracted from message text via regex, cleaned of Slack formatting (<url|display> -> url)
  5. Each URL queued as process-url BullMQ job -> Jina Reader API fetches content -> Claude Haiku summarizes -> stored as observation

Batch Triggers

TriggerConditionMechanism
Volume-based>= 20 unprocessed messagesImmediate process-batch BullMQ job
Timer-based30 minutes since last messageDelayed BullMQ job (deterministic jobId per org, no stacking)

Extraction Pipeline

  1. Fetch unprocessed messages (limit BATCH_SIZE, default 20)
  2. Resolve platformUserId -> user names via platform_identities + users tables
  3. Group messages by channel, format with timestamps and display names
  4. Send to Claude Haiku 4.5 with extraction prompt
  5. Parse JSON array of { entityType, entityId, category, observation, confidence }
  6. For each observation: generate embedding via OpenAI, insert into context_observations
  7. Mark source messages as processed (processedAt = now)
  8. Evaluate proactive actions via context-actions.ts rules

4. Query Flow — How the Bot Retrieves Context

Context String Format

User profile: **Role & Responsibilities**: ... **Current Focus**: ... **Patterns**: ...

Relevant observations:
- [commitment] Committed to delivering the client report by Friday EOD (2026-03-28)
- [blocker] Blocked on design assets from external vendor (2026-03-27)
- [handoff] Took over newsletter formatting from Rohan (2026-03-26)
Capped at ~2000 characters (~500 tokens) to avoid bloating the LLM prompt.

5. Entity Types and Observation Categories

Entity Types

Observation Categories

CategoryDescriptionConfidence ThresholdProactive Action
commitmentUser promised to deliver something> 0.85Auto-create task
blockerUser is blocked on something> 0.7Create blocker record
handoffWork transferred between people> 0.75DM receiving user
deadline_mentionDeadline referenced in conversation> 0.8Alert ops channel
risk_signalMissed deadlines, scope changes> 0.7Create blocker, notify ops
mood_signalFrustration, burnout, excitement> 0.7Flag to ops channel
status_updateSubstantive progress update> 0.5None
referenceURL content or web search result0.7-0.8None

Confidence Calibration

  • 0.9+: Directly stated fact (“I finished the newsletter”)
  • 0.7-0.9: Strong inference (“Rohan always does QC after content — likely a handoff pattern”)
  • 0.5-0.7: Weak inference (tone-based mood signals)
  • Below 0.5: Not stored

6. Nightly Summarization

Summary Storage

  • summaryType = "profile" — one per entity, upserted nightly
  • Unique index on (entity_type, entity_id, summary_type) — ensures one profile per entity
  • Embedding generated for the summary text itself (enables semantic search on summaries)
  • Old summaries are overwritten, not versioned

7. Cleanup & Retention

TableRetentionRationale
channel_messages7 days (configurable via CONTEXT_RAW_RETENTION_DAYS)Temporary buffer, facts already extracted
context_observationsIndefinite (soft-delete via superseded_by)Core knowledge graph
context_summariesIndefinite (overwritten nightly)Entity profiles, always current

8. Backfill Architecture

For new orgs or channels, historical messages can be backfilled from Slack:
  • Fetches last 1 year of history
  • 2-second delay between Slack API calls (rate limit safe)
  • Thread replies fetched for threaded messages
  • Uses backfill_state table to track progress per channel

9. Auto Mood Detection

Context observations with category = "mood_signal" feed into automatic mood scoring:

10. API Endpoints

MethodEndpointAuthPurpose
POST/context/ingestAPI KeyIngest a channel message
POST/context/browseAPI KeyFetch + summarize a URL
POST/context/searchAPI KeyWeb search + store results
GET/context/statsJWT / API KeyDashboard stats (counts, categories)
GET/context/observationsJWT / API KeyList observations (filterable)
GET/context/summariesJWT / API KeyList entity summaries
GET/context/:entityType/:entityIdJWT / API KeyGet entity context (summary + observations)
GET/context/entity/:entityType/:entityId/timelineJWT / API KeyFull entity timeline
POST/context/observationsJWT / API KeyManually create observation
DELETE/context/observations/:idJWT / API KeyDelete observation
POST/context/observations/:id/supersedeJWT / API KeySoft-delete (supersede)
POST/context/backfillJWT / API KeyTrigger historical backfill
GET/context/backfill/statusJWT / API KeyCheck backfill progress

11. Environment Variables

VariableRequiredDefaultPurpose
ANTHROPIC_API_KEYYesClaude Haiku for extraction & summarization
OPENAI_API_KEYYestext-embedding-3-small for 1536-dim vectors
JINA_API_KEYNoURL content fetching via Jina Reader API
TAVILY_API_KEYNoWeb search via Tavily
CONTEXT_BATCH_SIZENo20Messages per extraction batch
CONTEXT_RAW_RETENTION_DAYSNo7Days to keep raw channel_messages

12. Key Files

FilePurpose
packages/db/src/schema/channel-messages.tsRaw message buffer schema
packages/db/src/schema/context-observations.tsExtracted facts + embeddings schema
packages/db/src/schema/context-summaries.tsEntity profile summaries schema
apps/api/src/modules/context/context.service.tsCore service: ingest, query, summarize, cleanup
apps/api/src/modules/context/context-processor.service.tsBullMQ worker: batch extraction, URL processing, backfill
apps/api/src/modules/context/context-embeddings.service.tsOpenAI embedding generation + pgvector similarity search
apps/api/src/modules/context/context-web.service.tsJina URL fetch + Tavily web search
apps/api/src/modules/context/context-actions.service.tsProactive action evaluation
apps/api/src/modules/context/context-backfill.service.tsBackfill status tracking
apps/api/src/modules/context/context.controller.tsREST API endpoints
packages/rules/src/context-actions.tsRule engine: observation -> proactive action mapping
apps/api/src/modules/jobs/dynamic-cron.service.tsCron registration for daily summary + cleanup

13. Database Indexes

-- channel_messages
CREATE INDEX channel_messages_org_processed_idx ON channel_messages(organization_id, processed_at);
CREATE UNIQUE INDEX channel_messages_org_channel_ts_idx ON channel_messages(organization_id, channel_id, message_ts);

-- context_observations
CREATE INDEX context_obs_entity_idx ON context_observations(entity_type, entity_id, category);
CREATE INDEX context_obs_org_entity_idx ON context_observations(organization_id, entity_type);
CREATE INDEX context_obs_expires_idx ON context_observations(expires_at);
-- HNSW index on embedding column for cosine similarity search

-- context_summaries
CREATE UNIQUE INDEX context_summaries_entity_type_idx ON context_summaries(entity_type, entity_id, summary_type);
-- HNSW index on embedding column
Requires pgvector/pgvector:pg16 Docker image and CREATE EXTENSION vector migration.