| Tool | Purpose | How to Use |
|---|---|---|
| 37-data-model | Single source of truth for all project entities | GET http://localhost:8010/model/projects/33-eva-brain-v2 |
| 29-foundry | Agentic capabilities (search, RAG, eval, observability) | C:\eva-foundry\eva-foundation\29-foundry |
| 48-eva-veritas | Trust score and coverage audit | MCP tool: audit_repo / get_trust_score |
| 07-foundation-layer | Copilot instructions primer + governance templates | MCP tool: apply_primer / audit_project |
Agent rule: Query the data model API before reading source files.
Invoke-RestMethod "http://localhost:8010/model/agent-guide" # complete protocol
Invoke-RestMethod "http://localhost:8010/model/agent-summary" # all layer countsEnterprise Virtual Assistant - AI Brain Service
Domain: Jurisprudence, case law search, document ingestion
Stack: Python 3.10+, FastAPI, Azure OpenAI, Azure AI Search, Azure Cosmos DB
Status: Code complete ? Sprint 6 Deployment in progress | see STATUS.md
Last Updated: February 25, 2026 @ 09:00 ET
EVA Brain v2 is composed of two FastAPI microservices:
| Service | Port | Purpose |
|---|---|---|
eva-brain-api |
8001 | Chat, RAG, document ingestion, sessions, assistants |
eva-roles-api |
8002 | RBAC resolution, persona switching, cost tag evaluation |
All protected eva-brain-api routes call eva-roles-api to validate the actor session and resolve features. Both services are fully present on disk (Sprint 0, Feb 19 ? see STATUS.md).
| Service | Resource | Purpose |
|---|---|---|
| Azure OpenAI | marco-sandbox-openai |
Chat completions, embeddings |
| Azure AI Foundry | marco-sandbox-foundry |
Agent framework, model deployments (gpt-4o, gpt-4o-mini, text-embedding-ada-002) |
| Azure AI Search | marco-sandbox-search |
Vector, keyword and hybrid search over jurisprudence indexes |
| Azure Cosmos DB | marco-sandbox-cosmos |
Sessions, jobs, audit logs, documents |
| Azure Blob Storage | marcosand20260203 |
Document upload (raw), processed content, audit trail |
| Azure Storage Queues | marcosand20260203 |
9-queue pipeline for document processing |
| Azure Document Intelligence | marco-sandbox-docint |
PDF parsing, layout and table extraction |
| Azure Computer Vision | marco-sandbox-aisvc |
Image OCR and captioning |
| Azure Text Analytics | (sandbox cognitive) | Entity extraction, PII detection |
| Azure Translator | (sandbox cognitive) | EN/FR translation |
| Azure API Management | marco-sandbox-apim |
Rate limiting, product tiers, auth gateway |
| Azure Monitor / App Insights | marco-sandbox-appinsights |
Metrics, traces, logs (OpenTelemetry) |
X-Actor-OID -- AAD Object ID of the acting user
X-Correlation-ID -- Request tracing ID (propagated through all services)
X-Acting-Session -- Validated session token from eva-roles-api
X-Caller-App -- Calling application identifier (default: "unknown")
X-Environment -- Deployment environment (default: "dev")
Every protected endpoint is decorated with @require_feature(FeatureID.*).
The decorator calls eva-roles-api to validate the session and check feature access.
A 403 is returned if the persona does not have the required feature.
Public endpoints (config, health, translations) bypass RBAC.
No prompts, responses, or document text are ever stored in logs or audit records.
Only hashes (SHA-256, 16-char), counts, scores, durations, and IDs are logged.
Use hash_content() and create_safe_result_summary() from app.models.agent.
Every agent invocation includes CostTags (phase, task, project, costcenter, persona_id, actor_oid).
These are used for FinOps charge-back and Azure cost management.
Defined in services/eva-brain-api/config/personas.yml:
| Persona | Tools | Indexes | Max tokens/turn | Autonomy |
|---|---|---|---|---|
legal-researcher |
hybrid_search, semantic_search, keyword_search, chat_completion, log_audit, check_quality | case-law, statutes | 4,000 | high |
legal-clerk |
keyword_search, chat_completion | case-law | 1,000 | low |
admin |
all 7 tools | case-law, statutes, policies | 8,000 | high |
| Group | Endpoints | Status |
|---|---|---|
| Chat & RAG | POST /v1/chat, /v1/chat/rrr, /v1/chat/rtr, /v1/chat/work, /v1/chat/hybrid, /v1/chat/ungrounded, /v1/chat/read-retrieve-read, GET /v1/chat/approaches | ? Route present |
| Retrieve | POST /v1/retrieve | ? Route present |
| Sessions | POST/GET/PATCH/DELETE /v1/sessions, /v1/sessions/{id} | ? Route present |
| Documents | GET/PATCH/DELETE /v1/documents/*, /v1/documents/{id}/chunks | download |
| Ingest | POST /v1/ingest/upload | url, GET /v1/ingest/status/{id} |
| Tags | GET /v1/tags, /v1/tags/{tag}, /v1/tags/{tag}/documents | ? Route present |
| Search | Saved searches and history | ? Route present |
| Admin | POST /v1/users, GET /v1/groups | ? Route present |
| Config | GET /v1/config/info | features |
| Health | GET /v1/health, /v1/health/background | ? Route present |
| RBAC (roles) | POST /v1/roles/acting-as, GET /v1/roles/context | features |
| Assistants | POST /v1/assistant/math/, /v1/assistant/tabular/ | ? Implemented (Sprint 5) |
| Logs | GET /v1/logs/audit/, /v1/logs/content/ | ? Implemented (Sprint 5) |
| Translations (write) | PUT/PATCH/DELETE /v1/config/translations/{lang} | ? Implemented (Sprint 5) |
| Settings | GET/PATCH /v1/settings | ? Implemented (Sprint 5) |
| Apps Registry | GET/POST/PATCH/DELETE /v1/apps, /v1/apps/{id}/disable | export |
| Scrum (partial) | GET /v1/scrum/dashboard, /v1/scrum/summary | [CODED] on disk (Sprint 7, untested) |
| Projects Plane | GET /v1/projects, /v1/projects/{id | blocked |
| WBS / Critical Path | GET /v1/wbs, /v1/wbs/{id | critical-path |
| ADO Sync (pull) | POST /v1/ado/sync, GET /v1/ado/sync/status | [PLANNED] Sprint 7 |
| ADO Sync (push) | PATCH /v1/ado/wi/{tag}/state, POST /v1/ado/wi | [PLANNED] Sprint 8 |
| Evidence Proxy | GET /v1/evidence/{id | by-wi |
Implemented: 60/60. Planned Sprint 7: +13. Planned Sprint 8: +12.
Queue-based pipeline using 9 Azure Storage Queues, 11-step state machine:
Upload -> Route -> Parse -> Enrich Text -> Enrich Images -> Chunk -> Embed -> Index -> Update Status -> Log -> Notify
5 document processors: PDF (Azure Document Intelligence), DOCX, XLSX, TXT, Image (Azure Computer Vision)
3 enrichment services: Translation (EN/FR), Entity Extraction, PII Redaction
3 chunking strategies: semantic, fixed (750 tokens + 50 overlap), paragraph
Batch embedding: 16 texts per Azure OpenAI request
Performance target: <2 min upload-to-index for 10-page PDF
All pipeline service files are present on disk. The 6 ingest route endpoints are present in app/routes/ingest.py.
The agent framework is in app/agents/. Three files are on disk:
conversation_agent.py--ConversationAgent.invoke(), multi-turn orchestration, 8-hour TTLtools.py--AgentTools, 7 registered tools (hybrid_search, semantic_search, keyword_search, chat_completion, log_audit, check_quality, get_rbac_context)persona_constraints.py--PersonaConstraintsService, loads fromconfig/personas.yml
Agent models are in app/models/agent.py (540 lines, fully present):
ToolInvocation, ThoughtTrace, ReasoningTrace, ConversationTurn, ConversationContext, PersonaConstraints, AgentResponse
The Azure AI Foundry client is in app/clients/foundry_client.py.
Copy and populate all 20+ variables. Required variables (see tests/conftest.py for full list):
# Azure OpenAI
AZURE_OPENAI_ENDPOINT=https://marco-sandbox-foundry.cognitiveservices.azure.com/
AZURE_OPENAI_DEPLOYMENT=gpt-4o
AZURE_OPENAI_API_KEY=...
AZURE_OPENAI_API_VERSION=2024-02-01
# Azure AI Search
AZURE_SEARCH_ENDPOINT=https://marco-sandbox-search.search.windows.net
AZURE_SEARCH_INDEX=case-law
AZURE_SEARCH_API_KEY=...
# Azure Cosmos DB
AZURE_COSMOS_ENDPOINT=https://marco-sandbox-cosmos.documents.azure.com:443/
AZURE_COSMOS_KEY=...
AZURE_COSMOS_DATABASE=statusdb
# Azure Storage
AZURE_STORAGE_ACCOUNT_NAME=marcosand20260203
AZURE_STORAGE_ACCOUNT_KEY=...
AZURE_STORAGE_CONTAINER=upload
# Azure Cognitive Services
AZURE_COMPUTER_VISION_ENDPOINT=...
AZURE_COMPUTER_VISION_KEY=...
AZURE_TRANSLATOR_ENDPOINT=...
AZURE_TRANSLATOR_KEY=...
AZURE_TRANSLATOR_REGION=canadacentral
AZURE_TEXT_ANALYTICS_ENDPOINT=...
AZURE_TEXT_ANALYTICS_KEY=...
# App
ROLES_API_URL=http://localhost:8002
ENVIRONMENT=dev
LOG_LEVEL=INFO
APPINSIGHTS_CONNECTION_STRING=...cd services/eva-brain-api
pip install -r requirements.txt
pytest -v --cov=app tests/Current state: 577/577 tests passing, 72% coverage.
33-eva-brain-v2/
.github/
copilot-instructions.md
contracts/
schemas/ -- JSON Schema contracts (6 files)
docs/
agent-framework-guide.md
api-migration-phase-2.md
eva-faces-integration-guide.md
performance-benchmarks.md
runbooks/
services/
eva-brain-api/
app/
agents/ -- ConversationAgent, AgentTools, PersonaConstraintsService
clients/ -- FoundryClient (Azure AI Projects SDK)
decorators/ -- feature_guard.py (@require_feature)
exceptions.py -- EVAException hierarchy
middleware/ -- rbac.py (RBACMiddleware)
models/
agent.py -- All agent Pydantic models
cost.py -- CostTags
features.py -- FeatureID enum
routes/ -- All 11 route files present
services/ -- All service files present
utils/
config/
personas.yml
openapi/
agent-schemas.yaml
tests/ -- 577 tests, 72% coverage
eva-roles-api/ -- Full service, port 8002
translations/
en.json
fr.json
README.md -- This file
STATUS.md -- Current implementation status
PLAN.md -- Sprint plan (Sprint 6 active)
ACCEPTANCE.md -- Acceptance criteria and sign-off
FOUNDRY-DEPLOYMENT-GUIDE.md -- Sprint 6 deployment reference
FOUNDRY-QUICK-REFERENCE.md -- Azure CLI quick reference
DEPLOY-VIA-CODESPACES.md -- Deployment via GitHub Codespaces
The most expensive mistake a Copilot agent makes in this repo is reading source files to answer questions the model already knows.
Beforegrep_search, beforefile_search, beforeread_file? checkhttp://localhost:8010/model/{layer}/. That is a 3-line query. The alternative is a 10-turn spiral.
37-data-model (port 8010) is the semantic repository for the entire EVA ecosystem ? a Siebel-style object model with 27 layers, query-over-HTTP via FastAPI, and Siebel audit columns (modified_by, row_version, source_file) on every write. It already catalogs brain-v2's services, personas, feature flags, containers, and endpoints.
| Layer | What it tracks | Objects |
|---|---|---|
| L0 services | eva-brain-api (port 8001), eva-roles-api (port 8002) |
2 |
| L1 personas | admin, legal-researcher, legal-clerk, machine-agent, auditor |
5 |
| L2 feature_flags | action.chat, action.retrieve, action.ingest, action.search, action.admin.* |
10 |
| L3 containers | sessions, jobs, audit_logs, documents, translations, settings, apps |
13 |
| L4 endpoints | 60 brain-v2 endpoints (52 implemented, rest stub/planned) | 60 |
| L5 schemas | Request/response shapes for all 60 endpoints | 36 |
| L9 infrastructure | marco-sandbox-* Azure resources, APIM, Key Vault secrets |
23 |
| L25 projects | 33-eva-brain-v2 project record (maturity, ADO epic, pbi_done/total, depends_on) |
1 |
| L26 wbs | WBS deliverable nodes for brain-v2 sprints | n |
Note: The
eva-brain-apiservice record was updated Feb 25 (row_version=5): status=running, notes=Sprint 5 complete (577/577 tests, 60/60 endpoints, POC trusted-subsystem verified Feb 24). Commit clean: violations=0, exported=3932.
Current state: app/models/features.py (FeatureID enum) and config/personas.yml are both maintained manually. If they diverge from 37-data-model/model/feature_flags.json, the authoritative source is unclear.
Integration: At eva-roles-api startup, rbac_service.py could call:
# Option: query 37-data-model at startup to build persona ? features map
GET http://localhost:8010/model/personas/ # ? list with feature_flags[]
GET http://localhost:8010/model/feature_flags/ # ? each with personas[]This makes config/personas.yml and FeatureID the runtime cache and 37-data-model the source of truth, eliminating drift. The model already has the correct mapping.
When: Can be done in Sprint 6 or Sprint 7 ? no new endpoints needed in brain-v2.
The three new feature flags that Sprint 7 and Sprint 8 require must be added to 37-data-model before @require_feature will work:
| Flag | Sprint | Personas | Write cycle |
|---|---|---|---|
action.programme |
7 | pm_viewer, admin, auditor | PUT /model/feature_flags/action.programme ? verify ? export ? assemble |
action.ado_sync |
7 | admin, machine-agent | same |
action.ado_write |
8 | admin, machine-agent | same |
Block: brain-v2's
@require_feature("action.programme")decorator will return 403 for everyone until this record exists in 37-data-model and is synced toeva-roles-api's feature map.
This is the most critical integration. The /v1/projects, /v1/wbs/*, and /v1/scrum/* endpoints that brain-v2 is building in Sprint 7 are APIM-facing proxies over 37-data-model's L25 and L26 layers. Brain-v2 does not own this data ? it reads from and writes back to 37-data-model.
APIM ? brain-v2 /v1/projects ? GET http://localhost:8010/model/projects/
APIM ? brain-v2 /v1/wbs ? GET http://localhost:8010/model/wbs/
APIM ? brain-v2 /v1/wbs/critical-path ? compute from model/wbs/ + GET /model/graph?...
The new service file project_plane_service.py (Sprint 7) must be a typed client over the model API, not over raw JSON files.
Every ADO sync write from POST /v1/ado/sync or POST /v1/ado/wi must follow the API-first write protocol (captured in copilot-instructions.md):
1. PUT /model/work_items/{wi_tag} # stamps modified_at, modified_by, row_version
2. GET /model/work_items/{wi_tag} # verify field round-trips correctly
3. POST /model/admin/export # materialize audit trail ? model/*.json
4. assemble-model.ps1 # rebuild eva-model.json
Never write directly to model/*.json files ? doing so bypasses row_version and makes GET /model/admin/audit blind to the change. (This anti-pattern was recorded in 37-data-model/STATUS.md, Feb 22, 2026.)
Brain-v2's GET /v1/health/background should include a model_api field:
# In app/routes/health.py background health:
model_api_ok = await model_client.ping() # GET http://localhost:8010/health
return { "status": "ok", "model_api": "ok" if model_api_ok else "unavailable" }If 37-data-model is down, PM Plane endpoints should return 503 + Retry-After, not 500.
Before modifying any container field in brain-v2 (e.g. adding a field to sessions, renaming a field in jobs):
# Which endpoints and screens break if I change the jobs container?
Invoke-RestMethod "http://localhost:8010/model/impact/?container=jobs"This is the 37-data-model equivalent of a FK constraint check ? run it before every schema migration PR.
Sprint 7 should introduce services/eva-brain-api/app/services/model_api_client.py ? a lightweight httpx.AsyncClient wrapper over port 8010:
class ModelAPIClient:
base_url: str = "http://localhost:8010"
async def get_projects(self, maturity: str | None = None, category: str | None = None) -> list[dict]
async def get_wbs(self, level: str | None = None, project_id: str | None = None) -> list[dict]
async def get_wbs_critical_path(self) -> list[dict]
async def get_feature_flags(self) -> list[dict]
async def put_work_item(self, wi_tag: str, payload: dict) -> dict # write cycle step 1
async def export(self) -> None # write cycle step 3
async def ping(self) -> boolROLES_API_URL already follows this pattern; add MODEL_API_URL=http://localhost:8010 as a new env var with the same fallback logic.
31-eva-faces-- Frontend UI components (React/TypeScript) ? consumes brain-v2 API (see Downstream Dependencies below)29-foundry-- EVA Foundry Library: reusable agent skills, MCP servers, Python tools, Prompty templates ? consumes brain-v2 API as a proxy for PM agents (see Downstream Dependencies below)63-factory-context-auditor-- Workspace context auditing and repo-briefingNote: Project 63 is now the Factory Context Auditor, not a higher-level agent orchestration repo. Any future agent-orchestration assets should originate from
29-foundryrather than Project 63.EVA-Jurisprudence-SecMode-Info-Assistant-v1.2-- Production EVA JP deployment
eva-faces is a consumer of brain-v2's REST API. The table below tracks which eva-faces pages depend on which brain-v2 endpoint group, and when brain-v2 will deliver them.
| eva-faces Page / Component | brain-v2 Endpoint(s) | Brain Sprint | Status |
|---|---|---|---|
TranslationsPage |
PUT/PATCH/DELETE /v1/config/translations/{lang} |
Sprint 5 | ? Done (Feb 20) |
SettingsPage |
GET/PATCH /v1/settings |
Sprint 5 | ? Done (Feb 20) |
AppsPage |
GET/POST/PATCH/DELETE /v1/apps |
Sprint 5 WI-6 | ? Done (Feb 20) |
AuditLogsPage |
GET /v1/logs/audit/* |
Sprint 5 | ? Done (Feb 20) |
| All data-plane pages (any live call) | Both services deployed to Azure + APIM routing | Sprint 6 | ?? In progress (Feb 22 ?) |
ProjectPortfolioPage (WI-F1?F6) |
GET /v1/projects, GET /v1/projects/{id}, GET /v1/projects/blocked, GET /v1/projects/by-stream |
Sprint 7 | ?? Planned |
WBSTreePage + CriticalPathPanel (WI-F10?F18) |
GET /v1/wbs, GET /v1/wbs/{id}, GET /v1/wbs/critical-path, GET /v1/wbs/by-sprint |
Sprint 7 | ?? Planned |
SprintBoardPage + SprintSummaryStrip + VelocityChart (WI-F19?F26) |
GET /v1/scrum/dashboard, GET /v1/scrum/summary, GET /v1/scrum/velocity, GET /v1/scrum/products |
Sprint 7 | ?? Planned |
DomainAssistantChatPage (Phase 3) |
POST /v1/chat (SSE streaming) + GET/POST/DELETE /v1/sessions + GET /v1/chat/approaches |
Sprint 6 (deploy) | ?? Planned (live after Sprint 6 Azure deploy) |
EvidenceViewerPanel (WI-F27+, Epic 4) |
GET /v1/evidence/{id}, GET /v1/evidence/by-wi/{tag}, GET /v1/runs, GET /v1/evidence/summary |
Sprint 8 | ?? Planned |
RbacPage / RbacRolesPage |
GET /v1/roles/personas, POST /v1/roles/acting-as, GET /v1/roles/features (eva-roles-api) |
Sprint 1 | ? Done (Feb 19) |
Roadmap for eva-faces unblocking:
Sprint 6 (active) ? Both services deployed + APIM routing ? all live calls become possible
Sprint 7 (planned) ? PM Plane API live ? ProjectPortfolioPage, WBSTreePage, SprintBoardPage
Sprint 7 (planned) ? Scrum Dashboard + ADO Pull Sync ? SprintBoardPage live data
Sprint 8 (planned) ? Evidence Integration ? EvidenceViewerPanel live
RBAC flags that must be added to 37-data-model before eva-faces can use them: action.programme (Sprint 7), action.ado_sync (Sprint 7), action.ado_write (Sprint 8).
29-foundry's PM agents use brain-v2 as a data proxy ? all PM Plane API calls from foundry agents go through brain-v2 rather than calling 37-data-model directly. The table below lists foundry's runtime tool calls.
| 29-foundry Agent / Tool | brain-v2 Endpoint(s) | Brain Sprint | Status |
|---|---|---|---|
pm-agent.py ? tool get_projects() (WI-A2) |
GET /v1/projects |
Sprint 7 | ?? Planned |
pm-agent.py ? tool get_wbs() (WI-A3) |
GET /v1/wbs |
Sprint 7 | ?? Planned |
pm-agent.py ? tool get_critical_path() (WI-A4) |
GET /v1/wbs/critical-path |
Sprint 7 | ?? Planned |
pm-agent.py ? tool get_sprint_status() (WI-A5) |
GET /v1/scrum/summary, GET /v1/scrum/dashboard |
Sprint 7 | ?? Planned |
pm-agent.py ? tool get_evidence_summary() (WI-A6) |
GET /v1/evidence/summary |
Sprint 8 | ?? Planned |
pm-agent-server.py HTTP wrapper (WI-A9) |
Full brain-v2 API on port 8001 (sandbox) | Sprint 6 deploy | ?? Planned |
ado-sync-agent.py ? ADO pull sync (WI-A12) |
POST /v1/ado/sync, GET /v1/ado/sync/status |
Sprint 7 | ?? Planned |
sprint-planner-agent.py ? tool create_work_item() (WI-A22) |
POST /v1/ado/wi |
Sprint 8 | ?? Planned |
Structural (non-runtime) dependency: 29-foundry also imports ConversationAgent and AgentTools from brain-v2 as reference patterns into copilot-skills/azure-plane/eva-brain-v2/ and agent-framework/. This is a one-time code import, not a live call.
Roadmap for 29-foundry unblocking:
Sprint 6 (active) ? brain-v2 deployed to sandbox ? pm-agent-server.py can point at real endpoint
Sprint 7 (planned) ? PM Plane API (/v1/projects, /v1/wbs, /v1/scrum/*) live ? WI-A2?A6, WI-A9, WI-A12 unblocked
Sprint 8 (planned) ? Evidence + ADO Push (/v1/evidence/*, /v1/ado/wi) live ? WI-A6 (evidence_summary), WI-A22 unblocked