Context Engine
Captured idea
A context provider exposed through an MCP interface. Ask a question; the engine gathers and serves the relevant context from across your work world.
The Concept
The context engine sits behind an MCP interface so any MCP-capable client (Claude, agents, IDEs) can consume it as a context provider.
The retrieval model has two axes — determinants shape what “related” means; sources are where the data lives:
Determinants (shape retrieval)
- Social graph — the determinant of relatedness. The engine knows who you are, who you work with, and what you work on; every retrieval is parameterized by the asker’s position in the graph (filters, boosts, scoping, disambiguation). Not a source alongside the others — a cross-cutting relevance layer over all of them. It is derived from the sources (Teams participants, work-item assignments, commit authorship, org chart) and additionally produces answer content of its own: person pointers with provenance.
- Focus — what the asker is currently looking at (dashboard, open file, active channel), passed by the client per query.
(Earlier framing was a “6-pronged approach” with the social graph as one prong — overstated; the social graph is not a peer of the sources, it determines how they’re queried.)
Sources (where data lives)
- Azure (DevOps) / Monday — tasks and work items
- Wikis — documented knowledge
- Teams — messages and channels
- Code bases — repositories and source (incl. dashboard definitions)
- Derived collections — glossary mined from the above; scanner outputs (later)
Example Queries (PoC demo script)
These are the questions the engine must answer well — each stresses a different part of the design:
- “I need to do an integration with a new partner, where do I do this?”
Procedural fusion: wiki (how-to docs) + code (existing partner integrations as examples) + tasks (previous onboarding work items) + social graph (“Pieter did the last two — ask him”). A person pointer is a first-class answer type, not just a ranking signal.
Refinement: a bare name is a dead end — the person pointer must arrive with its evidence attached: the graph edges that made them the answer (work items closed, commits authored, wiki pages written, each linked to the artifact), recency (“three weeks ago” ≠ “in 2023”, stale edges decay), conversation starters drawn from the artifacts, and fallbacks if the person has left (graph tracks org membership via Azure AD). The social graph is a source that produces answer content of its own — and that content is provenance. Receipts make wrong inferences visible and correctable. - “When looking at this dashboard, how is this graph value calculated?”
Deictic — “this” can’t be resolved from text. The MCP tool needs a focus parameter:get_context(question, focus)where the client passes what the user is looking at (dashboard URL, open file, active channel). Focus is the second determinant alongside the social graph. Dashboard definitions (Grafana JSON, Power BI measures, SQL) are code-shaped → ingest into the code collection. - “What does this abbreviation mean?”
Breaks pure vector search — abbreviations have no semantic content to embed, and meanings differ per team. Needs: hybrid sparse/keyword matching, a derived glossary collection mined from definition patterns in wikis/Teams, and social-graph disambiguation (your team’s meaning ranks first).
Common thread: all three are short, context-poor questions that only work because the engine knows who is asking and what they’re looking at.
Boundary case: “Are there any security issues that need highlighting?”
Handled partially — and it defines the engine’s boundary:
- In scope (retrieval): scoped to a focus (“…with this integration?”), the engine surfaces recorded security knowledge — security-tagged work items, pentest findings in the wiki, Teams threads flagging concerns, dependency-scan reports. The engine retrieves; the client LLM judges and highlights.
- Out of scope (discovery): the engine is not a scanner. Discovering new vulnerabilities belongs to analysis tools (Defender, Snyk, SonarQube, CodeQL). Resolution: ingest scanner outputs as another source — then the question is answerable from recorded knowledge again.
- Boundary rule: the engine knows what the org knows; it doesn’t find out new things.
- Meta-issue: this question is a security risk to the engine — an NL-queryable index of code + Teams + security findings is an attack map. Per-user ACLs must propagate from every source into the index (payload filters keyed to asker identity) and be enforced on every query. Non-negotiable, not a later bolt-on.
MCP Return Shape — the Context Bundle
Principle: return evidence, not answers — the agent synthesizes; the engine provides auditable raw material.
get_context(question, focus?, max_tokens?) → one structured-markdown bundle:
- Header:
interpreted_as(engine’s reading of the query — lets the agent detect misinterpretation and re-query), asker, sources consulted. - Evidence items
[E1]…: per item — source type, title, author, timestamp, short snippet,ctx://URI. Stable IDs so other sections can cite by reference. - People
[P1]…: person + confidence + why (edges citing evidence IDs) + recency + fallback person. - Gaps: which sources returned nothing — load-bearing for hallucination prevention (“no wiki hits” is information; silence invites confabulation).
- More: count of withheld items +
expand_context(id)drill-down tool (or MCP resources at thectx://URIs) — progressive disclosure keeps multi-source fan-out from flooding the agent’s window;max_tokenssets the budget per call.
Design notes:
- Structured markdown over nested JSON — better LLM ergonomics for a reasoning consumer.
- ACL filtering happens before bundle assembly, keyed to asker identity; withheld content is invisible, not counted (even a count leaks).
Governance & Permissions (hard requirement)
Four-layer model:
- Identity from the auth layer, never from tool args. MCP OAuth 2.1 → Entra ID token → asker identity + group claims. Agents act on-behalf-of the user (OBO); autonomous agents get their own scoped service principals. A
user=parameter is spoofable by prompt injection — disallowed by design. - Two planes. Ingestion plane (privileged): connectors read in-scope content and export each item’s ACL into the payload (
allowed_principals, sensitivity label, ACL version). Query plane (trimmed): resolve asker’s principal set (user + expanded groups, short-TTL cache), filterallowed_principals ∩ asker_principals ≠ ∅inside the vector search — post-filtering leaks via timing/counts. Plane separation defends against the confused-deputy failure (engine’s god-mode service account answering for a restricted user). - Early binding + targeted late binding. Index-time ACLs for speed; query-time re-verification against the source only for sensitive items; webhook-driven ACL re-sync where available. Define an explicit revocation SLA (“reflected within N minutes”) as a governance commitment.
- Derived data inherits permissions. Social-graph edges and glossary entries carry the evidence IDs they were derived from; visible only if the asker can see supporting evidence. Provenance receipts double as the permission mechanism for derived knowledge — otherwise the graph leaks private-channel facts (“Pieter works on X”).
Governance checklist:
- Audit log — asker, interpreted query, evidence returned, items withheld (auditors see withheld counts; users never do)
- Deletion propagation — source deletions (retention, GDPR erasure) cascade to index + graph + glossary; the index must not outlive its sources
- Sensitivity labels — respect Purview/MIP; “Highly Confidential” may mean never-indexed, not indexed-and-trimmed
- Scope governance — admin-approved allowlist of projects/channels/repos; never “everything the service account reaches”
- Source admission checklist — no source gets a connector without passing the gate (see Source admission checklist below): exportable per-item ACLs, deletion feed, stable IDs/URLs, timestamps, Entra-mappable authors, named demo question it improves. Failing = inadmissible, not “harder”
- Aggregation risk — permitted items can compose into something sensitive; mitigate via audit review + rate limits; residual risk, be honest about it
PoC cut-line: identity-from-token + payload ACL filtering from day one (retrofitting trimming is the classic project-killer). Stage late binding, labels, deletion propagation behind it.
Open questions: derived-edge visibility — ≥1 visible evidence item (lean) vs all (conservative, guts the graph)? Single-tenant per org vs multi-tenant (changes isolation fundamentally)?
Conflict resolution: confidentiality vs correctness
The tension: trimming silently degrades answers — the current truth may live in a project the asker can’t see, so the agent confidently synthesizes from outdated visible sources. Worse than “access denied”: confident wrongness with an invisible cause.
- Rule 0 — confidentiality wins, no side channels. Engine behavior must be indistinguishable whether hidden content exists or not (the “M&A test”): no hints, no withheld counts, no confidence dips, no timing differences. The conflict cannot be resolved inside the query.
- Resolution is a workflow, not a filter — routed to people who hold the rights:
- Owner-side disclosure — notify the owner of trimmed-but-highly-relevant content: “a question your project answers came up; share / reach out / ignore.” Disclosure decided by the side with permission. Asker just gets a colleague pinging them later.
- Referral via public structure — “ask Pieter” only if his relevance has visible receipts or is org-chart-public (“Platform team owns integrations”). Same rule as derived-edge visibility.
- Demand analytics — report “top trimmed-out results” to content owners/admins (aggregate, audit plane only). Most conflicts are accidental restriction, not secrecy; the engine heals broken information architecture structurally.
- Honest hedging from visible evidence only — “newest doc is 14 months old” is computed without reference to hidden content, so it exists in both worlds and leaks nothing.
- Tier nuance: at the highest sensitivity label even owner-notification goes silent (query patterns flowing toward hidden content are themselves a leak) — only security audit sees it.
Candidate Additional Sources
Admission rule: a source earns its connector (connectors = 80% of cost) only if it adds unique content, strengthens a determinant (graph/time/authority), or improves a demo question.
Tier 1:
- Calendar + meeting transcripts — top pick; feeds everything: who-meets-whom is the highest-fidelity social-graph signal; transcripts capture decisions made out loud and never written down (= where supersession happens: “let’s stop doing X”); dated + attributed → slots into the fresh/low-authority supersession corner of the matrix; calendar = trigger infra for proactive delivery later. Same Graph API as Teams.
- SharePoint / OneDrive docs — the long tail: specs, decks (“the architecture decision on slide 14”); in Microsoft shops often more documented knowledge than the wiki. Heavy extraction lift.
- Email — uniquely holds external-party context (actual partner correspondence). Governance-heaviest source; scope ruthlessly: shared/team mailboxes first, personal opt-in or never.
Tier 2:
- Service catalog / API registry (Backstage, APIM) — small data, system-of-record for ownership → authority + reliable referral-via-public-structure backbone.
- ITSM / incidents — operational memory (“has this broken before?”); postmortems = high-authority precedent.
- CRM — the other half of the partner question: who the partner is, relationship history, contacts.
- CI/CD releases — time-dimension anchor: code says what exists; deployments say what’s true in production since when; release notes = dated supersession records.
Tier 3 (determinant fuel):
- HR / Entra org data — formal org chart (referrals), leaver status (fallbacks), tenure (cheap knowledge-state proxy for the parked dimension).
Skip: finance/ERP (governance cost ≫ value), support tickets (different product), Viva Engage (modality covered).
Source admission checklist (governance gate)
- Per-item exportable ACLs?
- Change/deletion feed (webhooks)?
- Stable IDs + URLs? (receipts depend on them)
- Usable timestamps (created/modified/validated)?
- Authors mappable to Entra identities?
- Named demo question it improves?
Failing the checklist = inadmissible (breaks provenance, trimming, or deletion propagation), not merely “harder”.
Enhancement Dimensions (beyond sources / relevance / governance)
Ordered by impact:
-
Time (promote to full determinant) — knowledge supersedes: wiki said A (2024), Teams decided B (last month). Recency decay per source type (chat ages in weeks, wikis in years), “as-of” awareness, supersession edges (mined or declared). First to add — directly attacks confident-wrongness.
-
Authority / canonicity — trustworthy ≠ relevant. Authority signals per item: source-type hierarchy, author role, reviewed/endorsed flags, link frequency. On conflict, surface the contradiction, never silently pick — “wiki says A [high authority, 14mo]; thread says B [low authority, 2wk]” is evidence-not-answers applied to disagreement.
Parked for later (decided 2026-06-11 — focus on time + authority first): -
Asker’s knowledge state — novelty as relevance. The graph implies what you already know → newcomer gets basics, the code’s author gets only the delta. Same question, correctly different bundles per person. Most natural extension of “the engine knows who you are.”
-
Feedback loop — track expand/cite/usefulness signals → tune fusion ranking. Eval harness: golden questions (the three example queries) + expected evidence, run on every change. The dimension that makes the others improvable.
-
Proactive delivery — invert the trigger: calendar/sprint/meeting events instead of queries (“meeting Contoso at 14:00 — open WI, last thread, unresolved cert issue”). Most differentiating; easiest to make annoying — build after reactive is good.
-
Write-back flywheel — the engine sees where knowledge gaps are (asked often, answered badly, agent synthesized well). Capture syntheses as draft wiki pages routed to an owner for review (never auto-publish). With demand analytics: the engine heals the knowledge base, not just consumes it.
Deep-dive: Time
- Time behaves differently per source — no single decay formula: chat = true decay (half-life in weeks); tasks = state-driven (open is current regardless of age; closed decays into precedent); wiki = barely decays in relevance but accrues staleness risk (rank with warning, don’t sink); code = HEAD never ages (time applies to commits/PRs, not the artifact).
- Supersession as explicit edges — declared (deprecated flags, “replaced by” links) and mined (“as of May we use Y”, newer doc with heavy topical overlap, decision messages). Mined = inference → confidence + receipts, like all derived data. Superseded items stay retrievable, tagged
superseded_by → [E_n]. - Temporal intent classification — current-state questions (“where do I…?”) apply decay hard; historical/why questions (“why did we choose Kafka?”) invert it — the original decision thread is the gold. Classified in
interpreted_as. Old ≠ wrong when the question is about provenance. - Mechanics — timestamps in payloads; all time math in the post-retrieval re-rank stage (score × per-source decay), never baked into vector similarity.
Deep-dive: Authority
- Signal stack (cheap → interesting): source-type prior (reviewed wiki > work item > chat) → endorsements (PR approvals, page owners, pinned) → reference frequency (how often the org links it) → author-on-topic.
- Authority is topic-relative, and the social graph computes it — Pieter is authoritative on partner integrations, not dashboards; that’s the graph’s who-works-on-what data. The graph is both the relevance determinant and the authority function. Authority claims need visible receipts (derived-data rule applies).
- “Last validated” ≠ “created” — a 2020 page reviewed last month is fresh-canonical. Validation timestamps are where time and authority merge into one signal.
Time × Authority matrix (drives conflict handling)
| Fresh | Stale | |
|---|---|---|
| High authority | Trust — lead with it | Flag: “canonical, but 14mo old” |
| Low authority | Supersession candidate: “team may have moved on” | Decays out |
Off-diagonal = the wiki-vs-Teams conflict. Bundle presents both items with their coordinates (age, authority, last-validated, superseded_by); the agent reasons — the engine makes disagreement legible, never picks the winner. v0 conflict proxy: same topic + both high-relevance + opposite corners → flag “potentially conflicting”. Semantic contradiction detection (NLI) = later.
PoC cut: timestamps + source-type priors + decay-at-re-rank = day one. Supersession mining, graph-derived topic authority, conflict flagging = phase 2.
Why It’s Different
Plain RAG retrieves by similarity. The context engine retrieves by relevance to you: the social graph (and current focus) determine what “related” means for this asker, then retrieval fans out across the integrated sources under that lens.
Questions to Explore
- Are there determinants beyond social graph + focus? (Candidates: time/recency, the task at hand, role?)
- How is the social graph built and kept fresh — mined from Teams/DevOps activity, or declared?
- How does the MCP interface expose results — resources, tools, or both? One
get_context(question)tool vs. per-source tools? - Ranking/fusion: how do results from multiple sources get merged into one coherent context bundle within a token budget?
- Privacy and access control: the engine knows a lot — how is per-user scoping enforced across integrations?
PoC Stack (C# core, TS edges)
Principle: C# is the engine, TypeScript is the glass. Microsoft-ecosystem problem → C# has first-class everything: official MCP SDK (ModelContextProtocol NuGet, MS + Anthropic), Microsoft.Identity.Web (Entra tokens, group claims, OBO ≈ config not code), Graph SDK + Azure DevOps client libs (.NET-native), official Qdrant.Client.
ContextEngine.sln
├─ ContextEngine.Mcp ASP.NET Core + MCP SDK + Identity.Web (query plane)
├─ ContextEngine.Ingestion Worker Service + connectors (Graph, DevOps)
├─ ContextEngine.Core bundle assembly, fusion, time/authority re-rank, ACL filters
├─ ContextEngine.Graph social graph + audit (EF Core/Postgres, recursive CTEs)
└─ docker-compose.yml qdrant + postgres
TypeScript (React/Next.js): admin/governance UI (audit viewer, demand analytics, scope allowlist) + demo/debug client (renders bundles — evidence cards, people receipts, gaps; invaluable when tuning decay). One legit backend-JS slot later: Node sidecar for tree-sitter symbol chunking (JS bindings mature, .NET ones rough); v0 uses file-level chunks in C#.
Rule: core never splits across runtimes — JS only talks to the C# API, never Qdrant/graph directly.
Build order: ① MCP skeleton + Entra auth, hardcoded bundle (riskiest integration first) → ② DevOps connector → Qdrant with ACL payloads → ③ real trimmed get_context → ④ Teams connector + graph mining → ⑤ demo client. ①–③ = demoable governed engine; ④ makes it this engine.
Zero-budget dev environment (no enterprise Azure access)
- Identity: free standalone Entra ID tenant — no subscription/credit card. Free tier includes app registrations, OAuth2/OIDC issuance, test users, security groups, group claims, OBO. Create Stephan/Pieter/Sarah test users in different groups → exercise full trimming (different users, different bundles). Microsoft.Identity.Web code is identical to enterprise deployment — only the directory is small. (M365 Dev Program E5 sandbox was restricted in 2024 — not available, and not needed for auth.)
- Portability hedge: Keycloak in Docker — forces the right abstraction: engine consumes “validated OIDC token with identity + group claims”, not “Entra”.
JwtBearerspeaks to both; enterprise Entra becomes a config swap. - Azure DevOps Services: free for 5 users — real work items, wikis, repos, project-level ACLs to vary across test users. Primary connector develops against the genuine API + permission model. The zero-budget jackpot.
- Teams: the gap (Graph API needs M365 licenses) → fixture-backed connector: synthetic channel/thread JSON shaped like Graph responses, behind the same
ITeamsConnectorinterface; hand-author the demo-script conversations (Pieter cert thread, abbreviation definition, supersession decision). Swap for real Graph connector later, nothing downstream changes. - Embeddings: local — Ollama (
nomic-embed-text/bge-m3) over HTTP, or in-process ONNX MiniLM. Qdrant + Postgres already free in Docker.
Meta-point: the $0 constraint forces the architecture we wanted anyway — OIDC abstraction + connector ports. The PoC is the real engine pointed at a small directory and one fixture, not a toy needing a rewrite.
Next Actions
- Specify how the social graph parameterizes retrieval (filters vs boosts vs query expansion — per source)
- Sketch the MCP surface (tools/resources) for a v0
- Pick one integration (probably Azure DevOps) + social graph for a thin prototype