You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The Azure FinOps agent treats every conversation as if it knows nothing about the user's organization. Users in large enterprises have domain-specific context that would dramatically improve the quality of the agent's analysis — subscription-to-application mappings, cost center ownership hierarchies, SLA/SLO/RTO/RPO targets, preferred analysis patterns, tagging conventions, and custom instructions. Today, users must repeat this context in every conversation, which is tedious, error-prone, and doesn't scale across teams.
What users want to tell the agent once and have it remember:
"Subscription abc-123 runs our Patient Portal app (Tier 1, RTO 4h, RPO 1h). Subscription def-456 is the Discovery research platform (Tier 3, no SLA)."
"Our cost centers are mapped by the CostCenter tag. The finance team owns CC-1000 through CC-1099. Engineering owns CC-2000+."
"When analyzing costs, always group by the ApplicationPortfolio tag first, then by Environment (prod/staging/dev). Ignore sandbox subscriptions matching rg-sandbox-*."
"Our budget cycle runs April–March (fiscal year). Q1 is Apr–Jun."
"Flag any resource in westeurope that costs more than $500/month — we're migrating out of that region."
Root cause analysis based on the current codebase:
The system prompt is static and generic — CopilotSessionFactory.cs defines a ~3,000-word system prompt that covers response formatting, tool usage, and FinOps frameworks. It contains zero organization-specific context. The prompt is compiled once at startup and shared across all users.
Per-request context injection is limited to connection state and uploads — ChatEndpoints.cs prepends two dynamic blocks to every user prompt: a [CONTEXT: ...] block with Azure connection status, and an [UPLOADED FILES ...] block listing session file uploads. There is no mechanism to inject persistent organizational knowledge.
Uploaded files are ephemeral — UploadedFileTools.cs stores user-uploaded files in /tmp with a 30-minute TTL. Files are cleared on chat reset and lost on container restart. Users cannot persist reference data (subscription mappings, cost center tables, analysis templates) across sessions.
No per-user or per-tenant knowledge store — there is no equivalent of GitHub Copilot's .github/copilot-instructions.md or custom instructions. The agent has no way to learn and retain organizational context.
No tool for querying knowledge — even if knowledge were stored, the LLM has no tool to look up organizational context during a conversation. When a user asks "what's the cost of the Patient Portal app?", the LLM cannot resolve "Patient Portal" to a set of subscriptions without the mapping being provided in-session.
Dependency: This issue builds on the persistent user sessions issue. Deterministic Entra ID-based user identity is required so that knowledge articles are scoped to the correct user/tenant and persist across sessions.
Proposed solution
A three-layer approach: a persistent knowledge store with CRUD API, context injection into every LLM prompt, and an LLM tool for querying knowledge during conversations.
Layer 1: Knowledge store (KnowledgeStore)
New file:src/Dashboard/Services/KnowledgeStore.cs
A file-based, per-user store for organizational knowledge articles. Each article is a named document containing structured or unstructured text that the LLM can reference.
Data model:
publicclassKnowledgeArticle{publicstringId{get;set;}// Auto-generated 8-char hexpublicstringTitle{get;set;}// Short name: "Subscription Mappings", "Cost Analysis Instructions"publicstringCategory{get;set;}// "subscriptions", "cost_centers", "instructions", "architecture", "sla", "custom"publicstringContent{get;set;}// The actual knowledge text (markdown, CSV, JSON, or free text)publiclongUserId{get;set;}// Entra OID-derived deterministic IDpublicDateTimeCreatedUtc{get;set;}publicDateTimeUpdatedUtc{get;set;}publicboolActive{get;set;}=true;// Soft-delete: inactive articles are excluded from context}
Each article stored as {id}.json — individual files enable atomic updates without locking the entire store.
The ~/ path resolves to /home/ on Azure App Service (durable across restarts) or $TEMP for local dev.
Size constraints:
Maximum 20 knowledge articles per user — prevents unbounded context growth.
Maximum 10,000 characters per article — keeps individual articles within a reasonable LLM context window.
Maximum 50,000 characters total across all articles per user — ensures the combined context injection doesn't exceed ~12,500 tokens (~25% of a 50K-token context window), leaving room for the system prompt, conversation history, and tool results.
Implementation:
publicstaticclassKnowledgeStore{privatestaticstringUserDir(longuserId)=>Path.Combine(Environment.GetEnvironmentVariable("HOME")??Path.GetTempPath(),"finops-agent-knowledge",userId.ToString());publicstaticList<KnowledgeArticle>ListForUser(longuserId){ ...}publicstaticKnowledgeArticle?Get(longuserId,stringid){ ...}publicstaticKnowledgeArticleSave(longuserId,KnowledgeArticlearticle){ ...}publicstaticboolDelete(longuserId,stringid){ ...}/// <summary>/// Returns the concatenated context block for all active articles,/// formatted for LLM injection. Called on every chat request./// </summary>publicstaticstringBuildContextBlock(longuserId){vararticles=ListForUser(userId).Where(a =>a.Active).ToList();if(articles.Count==0)return"";varsb=newStringBuilder();sb.AppendLine("[ORGANIZATIONAL KNOWLEDGE — The user has provided the following reference information about their environment. Use this to enrich your analysis, resolve entity names, apply their preferred conventions, and follow their instructions. This knowledge persists across sessions.]");foreach(vararticleinarticles.OrderBy(a =>a.Category)){sb.AppendLine($"\n### {article.Title} ({article.Category})");sb.AppendLine(article.Content);}returnsb.ToString();}}
Layer 2: Inject knowledge context into every LLM prompt
File:src/Dashboard/AI/ChatEndpoints.cs
Insert the knowledge context block between the connection context and the user's prompt, following the same pattern used for uploaded files.
Implementation — add after the existing uploadsContext construction (around line 115):
// Surface persistent organizational knowledge for this uservarknowledgeContext=KnowledgeStore.BuildContextBlock(userId);prompt=string.IsNullOrEmpty(uploadsContext)?string.IsNullOrEmpty(knowledgeContext)?$"{connectionContext}\n{prompt}":$"{connectionContext}\n{knowledgeContext}\n{prompt}":string.IsNullOrEmpty(knowledgeContext)?$"{connectionContext}\n{uploadsContext}\n{prompt}":$"{connectionContext}\n{knowledgeContext}\n{uploadsContext}\n{prompt}";
The resulting prompt structure for each chat request becomes:
[CONTEXT: User IS connected to Azure. Available APIs: ...]
[ORGANIZATIONAL KNOWLEDGE — ...]
### Subscription Mappings (subscriptions)
abc-123 = Patient Portal (Tier 1, RTO 4h)
def-456 = Discovery Platform (Tier 3)
...
### Cost Analysis Instructions (instructions)
Always group by ApplicationPortfolio tag first...
...
[UPLOADED FILES IN THIS SESSION — ...]
{user's actual question}
This injection happens per-request (not baked into the system prompt), so knowledge updates take effect immediately without session recycling.
Layer 3: Knowledge CRUD API
New file:src/Dashboard/Endpoints/KnowledgeEndpoints.cs
REST API for managing knowledge articles:
GET /api/knowledge — list all articles for current user (title, category, id, active, updatedUtc)
GET /api/knowledge/{id} — get full article content
POST /api/knowledge — create a new article (body: { title, category, content })
PUT /api/knowledge/{id} — update an existing article (body: { title?, category?, content?, active? })
DELETE /api/knowledge/{id} — permanently delete an article
POST /api/knowledge/import — import knowledge from an uploaded file (CSV/JSON/TXT)
All endpoints require an authenticated session. Articles are scoped by the deterministic Entra OID-derived userId — users can only access their own knowledge.
The POST /api/knowledge/import endpoint enables bulk import from structured files:
CSV/TSV with subscription_id, application, tier, owner columns → auto-creates a "Subscription Mappings" article formatted as a markdown table.
JSON with key-value or array structure → auto-creates an article with the JSON formatted for LLM readability.
TXT/MD file → creates an article with the raw text as content.
This reuses the existing file upload infrastructure (UploadedFileTools.RegisterAsync for parsing) but persists the extracted content as a knowledge article instead of a temporary file.
Layer 4: LLM tool for querying knowledge
New file:src/Dashboard/AI/Tools/KnowledgeTools.cs
An AI-callable tool that lets the LLM explicitly look up knowledge articles during a conversation.
publicclassKnowledgeTools{privatereadonlylong_userId;publicKnowledgeTools(longuserId)=>_userId=userId;publicIEnumerable<AIFunction>Create(){yieldreturnAIFunctionFactory.Create(QueryKnowledge,"QueryKnowledge",@"Look up the user's organizational knowledge base. Use this when you need to resolveentity names (app names → subscriptions), check SLA/SLO targets, find cost center owners,or apply user-defined analysis instructions. The knowledge base contains persistent referencedata the user has provided about their organization.Modes: list — list all knowledge article titles and categories search — search article content for a keyword/phrase (param: query) get — get full content of a specific article (param: id)Use 'search' when the user mentions an application name, team, cost center, or conventionthat isn't in the current conversation — it may be defined in their knowledge base.");}privatestringQueryKnowledge([Description("Mode: 'list', 'search', or 'get'")]stringmode,[Description("Search query string (for 'search' mode) or article ID (for 'get' mode)")]string?param=null){vararticles=KnowledgeStore.ListForUser(_userId).Where(a =>a.Active).ToList();returnmode.ToLowerInvariant()switch{"list"=>JsonSerializer.Serialize(articles.Select(a =>new{a.Id,a.Title,a.Category,contentPreview=a.Content.Length>100?a.Content[..100]+"...":a.Content})),"search"when!string.IsNullOrWhiteSpace(param)=>JsonSerializer.Serialize(articles.Where(a =>a.Content.Contains(param,StringComparison.OrdinalIgnoreCase)||a.Title.Contains(param,StringComparison.OrdinalIgnoreCase)).Select(a =>new{a.Id,a.Title,a.Category,a.Content})),"get"when!string.IsNullOrWhiteSpace(param)=>KnowledgeStore.Get(_userId,param)is{}article?JsonSerializer.Serialize(new{article.Id,article.Title,article.Category,article.Content}):JsonSerializer.Serialize(new{error="Article not found"}),
_ =>JsonSerializer.Serialize(new{error="Invalid mode. Use 'list', 'search', or 'get'."})};}}
Register the tool in CopilotSessionFactory.cs alongside other per-user tools, passing the userId.
Add a "Knowledge Base" section to the existing sidebar (below the maturity scores section) for managing knowledge articles.
Implementation:
Sidebar section with a book/brain icon labeled "Knowledge Base". Shows a count badge of active articles.
List view: clicking the section expands to show article titles with category badges and edit/delete icons.
Add article: a modal/panel with fields for title (text input), category (dropdown: subscriptions, cost_centers, instructions, architecture, sla, custom), and content (textarea with markdown support). Submit calls POST /api/knowledge.
Edit article: same modal pre-filled with existing data. Submit calls PUT /api/knowledge/{id}.
Import from file: a "Import" button that opens a file picker (CSV/JSON/TXT). Calls POST /api/knowledge/import.
Toggle active/inactive: a toggle switch on each article that calls PUT /api/knowledge/{id} with { active: false/true }. This lets users temporarily disable an article without deleting it.
Character count: show {current}/{max} character count on the content textarea. Disable submit when over 10,000 chars.
System prompt update
File:src/Dashboard/AI/CopilotSessionFactory.cs
Add a brief instruction to the system prompt so the LLM knows to use knowledge context:
## Organizational Knowledge
When the user's prompt includes an [ORGANIZATIONAL KNOWLEDGE] block, treat it as ground truth about their environment. Use it to:
- Resolve application names to subscription IDs
- Apply their tagging conventions and cost center mappings
- Follow their analysis instructions and reporting preferences
- Reference their SLA/SLO/RTO/RPO targets when assessing risk
- Respect their fiscal calendar and budget cycle definitions
If the user asks about an entity you don't recognize, call QueryKnowledge('search', entityName) to check their knowledge base before asking them.
Files to Modify
File
Change
src/Dashboard/AI/ChatEndpoints.cs
Insert KnowledgeStore.BuildContextBlock(userId) into the prompt construction pipeline between connection context and uploads context
src/Dashboard/AI/CopilotSessionFactory.cs
Add "Organizational Knowledge" section to system prompt; register KnowledgeTools as per-user tool
src/Dashboard/Program.cs
Register KnowledgeEndpoints via app.MapKnowledgeEndpoints()
New Files to Create
File
Purpose
src/Dashboard/Services/KnowledgeStore.cs
Per-user file-based knowledge article persistence with BuildContextBlock() for prompt injection
src/Dashboard/Endpoints/KnowledgeEndpoints.cs
REST API for knowledge CRUD: list, get, create, update, delete, import
src/Dashboard/AI/Tools/KnowledgeTools.cs
LLM-callable tool for searching/retrieving knowledge articles during conversation
1. Embed knowledge in the system prompt (baked at session creation)
Inject knowledge into the system prompt in CopilotSessionFactory.cs when creating the Copilot session, rather than per-request. Rejected because the Copilot session is long-lived (recycled only on token expiry, ~1 hour). Knowledge updates would not take effect until the session is recreated. Per-request injection in ChatEndpoints.cs ensures knowledge changes are immediately available.
2. RAG with vector embeddings (Azure AI Search / FAISS)
Implement Retrieval-Augmented Generation: embed knowledge articles as vectors, store in Azure AI Search or a local FAISS index, and retrieve relevant chunks per query. Rejected because (1) it adds a significant dependency (Azure AI Search at ~$70/mo or FAISS library + embedding model), (2) the knowledge base is small (≤50KB per user, ≤20 articles) — full injection into the context window is feasible and more reliable than retrieval, (3) vector search adds latency per request, and (4) the LLM may miss relevant context if the retrieval step doesn't surface the right chunks. Full injection ensures the LLM always has complete organizational context. RAG should be reconsidered if knowledge bases grow beyond 100KB per user.
3. Fine-tune a custom model per tenant
Fine-tune an Azure OpenAI model with organization-specific data. Rejected because fine-tuning is expensive ($25+/hour for training), requires a separate model deployment per tenant, takes hours to days to process, cannot be updated in real-time, and is designed for learning patterns (writing style, domain terminology), not for storing factual reference data (subscription mappings, cost center tables) that change frequently.
4. Store knowledge in Azure Cosmos DB
Use Cosmos DB as the persistence layer for knowledge articles. Rejected because it adds ~$25/mo infrastructure cost for a simple key-value use case, and the App Service /home directory already provides durable file storage at zero additional cost. The knowledge store has low throughput (a few reads/writes per session, not thousands per second) and small data size (≤50KB per user), making file-based storage the right fit.
5. Store knowledge in the browser (localStorage)
Persist knowledge articles in the browser's localStorage, similar to the conversation history approach in the sessions issue. Rejected because knowledge must be accessible to the backend (for prompt injection into the LLM context), not just the frontend. localStorage is client-side only — the server cannot read it. Server-side storage is required so the ChatEndpoints.cs prompt construction can inject knowledge into every request.
6. Use the existing file upload mechanism for persistent knowledge
Let users upload CSV/JSON files via the existing upload endpoint and treat them as persistent knowledge. Rejected because uploaded files have a 30-minute TTL, are stored in /tmp (volatile), are cleared on chat reset, and are designed for ad-hoc analysis, not persistent reference data. The knowledge store needs different lifecycle semantics: long-lived, survives restarts, persists across sessions, and is injected into every conversation. The import endpoint (POST /api/knowledge/import) bridges the two — users can upload a file that gets converted into a persistent knowledge article.
7. Per-tenant shared knowledge (admin-managed)
Allow a tenant admin to define knowledge articles shared across all users in the tenant. Deferred — this is a valuable future feature (e.g. a central team maintains the subscription-to-app mapping for everyone), but it requires tenant-level RBAC, admin role detection, and careful access control design. The per-user knowledge base is the right starting point. Shared tenant knowledge can be added as a second tier, with per-user articles taking precedence over tenant-level defaults.
8. GitHub Copilot-style instruction files in a git repo
Let users point the agent at a git repository containing .md instruction files. Rejected because it introduces a dependency on git/GitHub access from the container, requires additional OAuth scopes (repo access), adds latency to fetch files on every request, and assumes users store their organizational context in git (most don't — it's in SharePoint, Confluence, or people's heads). The in-app knowledge editor is more accessible.
What problem are you trying to solve?
The Azure FinOps agent treats every conversation as if it knows nothing about the user's organization. Users in large enterprises have domain-specific context that would dramatically improve the quality of the agent's analysis — subscription-to-application mappings, cost center ownership hierarchies, SLA/SLO/RTO/RPO targets, preferred analysis patterns, tagging conventions, and custom instructions. Today, users must repeat this context in every conversation, which is tedious, error-prone, and doesn't scale across teams.
What users want to tell the agent once and have it remember:
abc-123runs our Patient Portal app (Tier 1, RTO 4h, RPO 1h). Subscriptiondef-456is the Discovery research platform (Tier 3, no SLA)."CostCentertag. The finance team owns CC-1000 through CC-1099. Engineering owns CC-2000+."ApplicationPortfoliotag first, then byEnvironment(prod/staging/dev). Ignore sandbox subscriptions matchingrg-sandbox-*."westeuropethat costs more than $500/month — we're migrating out of that region."Root cause analysis based on the current codebase:
The system prompt is static and generic —
CopilotSessionFactory.csdefines a ~3,000-word system prompt that covers response formatting, tool usage, and FinOps frameworks. It contains zero organization-specific context. The prompt is compiled once at startup and shared across all users.Per-request context injection is limited to connection state and uploads —
ChatEndpoints.csprepends two dynamic blocks to every user prompt: a[CONTEXT: ...]block with Azure connection status, and an[UPLOADED FILES ...]block listing session file uploads. There is no mechanism to inject persistent organizational knowledge.Uploaded files are ephemeral —
UploadedFileTools.csstores user-uploaded files in/tmpwith a 30-minute TTL. Files are cleared on chat reset and lost on container restart. Users cannot persist reference data (subscription mappings, cost center tables, analysis templates) across sessions.No per-user or per-tenant knowledge store — there is no equivalent of GitHub Copilot's
.github/copilot-instructions.mdor custom instructions. The agent has no way to learn and retain organizational context.No tool for querying knowledge — even if knowledge were stored, the LLM has no tool to look up organizational context during a conversation. When a user asks "what's the cost of the Patient Portal app?", the LLM cannot resolve "Patient Portal" to a set of subscriptions without the mapping being provided in-session.
Dependency: This issue builds on the persistent user sessions issue. Deterministic Entra ID-based user identity is required so that knowledge articles are scoped to the correct user/tenant and persist across sessions.
Proposed solution
A three-layer approach: a persistent knowledge store with CRUD API, context injection into every LLM prompt, and an LLM tool for querying knowledge during conversations.
Layer 1: Knowledge store (
KnowledgeStore)New file:
src/Dashboard/Services/KnowledgeStore.csA file-based, per-user store for organizational knowledge articles. Each article is a named document containing structured or unstructured text that the LLM can reference.
Data model:
Storage:
~/finops-agent-knowledge/{userId}/{id}.json— individual files enable atomic updates without locking the entire store.~/path resolves to/home/on Azure App Service (durable across restarts) or$TEMPfor local dev.Size constraints:
Implementation:
Layer 2: Inject knowledge context into every LLM prompt
File:
src/Dashboard/AI/ChatEndpoints.csInsert the knowledge context block between the connection context and the user's prompt, following the same pattern used for uploaded files.
Implementation — add after the existing
uploadsContextconstruction (around line 115):The resulting prompt structure for each chat request becomes:
This injection happens per-request (not baked into the system prompt), so knowledge updates take effect immediately without session recycling.
Layer 3: Knowledge CRUD API
New file:
src/Dashboard/Endpoints/KnowledgeEndpoints.csREST API for managing knowledge articles:
All endpoints require an authenticated session. Articles are scoped by the deterministic Entra OID-derived userId — users can only access their own knowledge.
The
POST /api/knowledge/importendpoint enables bulk import from structured files:subscription_id,application,tier,ownercolumns → auto-creates a "Subscription Mappings" article formatted as a markdown table.This reuses the existing file upload infrastructure (
UploadedFileTools.RegisterAsyncfor parsing) but persists the extracted content as a knowledge article instead of a temporary file.Layer 4: LLM tool for querying knowledge
New file:
src/Dashboard/AI/Tools/KnowledgeTools.csAn AI-callable tool that lets the LLM explicitly look up knowledge articles during a conversation.
Register the tool in
CopilotSessionFactory.csalongside other per-user tools, passing the userId.Layer 5: Frontend knowledge management UI
File:
src/Dashboard/frontend/src/components/ChatView.vueAdd a "Knowledge Base" section to the existing sidebar (below the maturity scores section) for managing knowledge articles.
Implementation:
POST /api/knowledge.PUT /api/knowledge/{id}.POST /api/knowledge/import.PUT /api/knowledge/{id}with{ active: false/true }. This lets users temporarily disable an article without deleting it.{current}/{max}character count on the content textarea. Disable submit when over 10,000 chars.System prompt update
File:
src/Dashboard/AI/CopilotSessionFactory.csAdd a brief instruction to the system prompt so the LLM knows to use knowledge context:
Files to Modify
src/Dashboard/AI/ChatEndpoints.csKnowledgeStore.BuildContextBlock(userId)into the prompt construction pipeline between connection context and uploads contextsrc/Dashboard/AI/CopilotSessionFactory.csKnowledgeToolsas per-user toolsrc/Dashboard/Program.csKnowledgeEndpointsviaapp.MapKnowledgeEndpoints()New Files to Create
src/Dashboard/Services/KnowledgeStore.csBuildContextBlock()for prompt injectionsrc/Dashboard/Endpoints/KnowledgeEndpoints.cssrc/Dashboard/AI/Tools/KnowledgeTools.csFrontend Changes
src/Dashboard/frontend/src/components/ChatView.vueAcceptance Criteria
KnowledgeStorepersists articles to~/finops-agent-knowledge/{userId}/as individual JSON files, surviving container restartsKnowledgeStore.BuildContextBlock(userId)returns a formatted[ORGANIZATIONAL KNOWLEDGE ...]block injected into every chat prompt[CONTEXT: ...]and[UPLOADED FILES ...]blocksQueryKnowledgeLLM tool supportslist,search(keyword match), andget(by ID) modesGET /api/knowledgereturns all articles for the authenticated user (title, category, id, active, updatedUtc)POST /api/knowledgecreates a new article;PUT /api/knowledge/{id}updates;DELETE /api/knowledge/{id}deletesPOST /api/knowledge/importaccepts CSV/JSON/TXT file upload and creates a knowledge article from the contentPUTwithout deletion; inactive articles are excluded from context injectionQueryKnowledgetoolReferences
Area
New AI tool (Azure / Graph / Log Analytics)
Alternatives considered
1. Embed knowledge in the system prompt (baked at session creation)
Inject knowledge into the system prompt in
CopilotSessionFactory.cswhen creating the Copilot session, rather than per-request. Rejected because the Copilot session is long-lived (recycled only on token expiry, ~1 hour). Knowledge updates would not take effect until the session is recreated. Per-request injection inChatEndpoints.csensures knowledge changes are immediately available.2. RAG with vector embeddings (Azure AI Search / FAISS)
Implement Retrieval-Augmented Generation: embed knowledge articles as vectors, store in Azure AI Search or a local FAISS index, and retrieve relevant chunks per query. Rejected because (1) it adds a significant dependency (Azure AI Search at ~$70/mo or FAISS library + embedding model), (2) the knowledge base is small (≤50KB per user, ≤20 articles) — full injection into the context window is feasible and more reliable than retrieval, (3) vector search adds latency per request, and (4) the LLM may miss relevant context if the retrieval step doesn't surface the right chunks. Full injection ensures the LLM always has complete organizational context. RAG should be reconsidered if knowledge bases grow beyond 100KB per user.
3. Fine-tune a custom model per tenant
Fine-tune an Azure OpenAI model with organization-specific data. Rejected because fine-tuning is expensive ($25+/hour for training), requires a separate model deployment per tenant, takes hours to days to process, cannot be updated in real-time, and is designed for learning patterns (writing style, domain terminology), not for storing factual reference data (subscription mappings, cost center tables) that change frequently.
4. Store knowledge in Azure Cosmos DB
Use Cosmos DB as the persistence layer for knowledge articles. Rejected because it adds ~$25/mo infrastructure cost for a simple key-value use case, and the App Service
/homedirectory already provides durable file storage at zero additional cost. The knowledge store has low throughput (a few reads/writes per session, not thousands per second) and small data size (≤50KB per user), making file-based storage the right fit.5. Store knowledge in the browser (localStorage)
Persist knowledge articles in the browser's localStorage, similar to the conversation history approach in the sessions issue. Rejected because knowledge must be accessible to the backend (for prompt injection into the LLM context), not just the frontend. localStorage is client-side only — the server cannot read it. Server-side storage is required so the
ChatEndpoints.csprompt construction can inject knowledge into every request.6. Use the existing file upload mechanism for persistent knowledge
Let users upload CSV/JSON files via the existing upload endpoint and treat them as persistent knowledge. Rejected because uploaded files have a 30-minute TTL, are stored in
/tmp(volatile), are cleared on chat reset, and are designed for ad-hoc analysis, not persistent reference data. The knowledge store needs different lifecycle semantics: long-lived, survives restarts, persists across sessions, and is injected into every conversation. The import endpoint (POST /api/knowledge/import) bridges the two — users can upload a file that gets converted into a persistent knowledge article.7. Per-tenant shared knowledge (admin-managed)
Allow a tenant admin to define knowledge articles shared across all users in the tenant. Deferred — this is a valuable future feature (e.g. a central team maintains the subscription-to-app mapping for everyone), but it requires tenant-level RBAC, admin role detection, and careful access control design. The per-user knowledge base is the right starting point. Shared tenant knowledge can be added as a second tier, with per-user articles taking precedence over tenant-level defaults.
8. GitHub Copilot-style instruction files in a git repo
Let users point the agent at a git repository containing
.mdinstruction files. Rejected because it introduces a dependency on git/GitHub access from the container, requires additional OAuth scopes (repo access), adds latency to fetch files on every request, and assumes users store their organizational context in git (most don't — it's in SharePoint, Confluence, or people's heads). The in-app knowledge editor is more accessible.