Faithful extracts (with provenance headers) from the private EuFund codebase: the phase-gated agent runtime, tool allowlists and write gates, evidence retrieval with citation validation, and tiered multi-provider routing with cross-provider failover. Fixtures are synthetic. The full platform (96 API routes, 48 migrations, 1,900+ tests) is private; this exhibit exists so the controls can be read and run in ten minutes.
flowchart LR
D[discovery] --> R[research] --> S[structuring] --> W[drafting] --> V[review]
W -->|write tools, allowWrites gate| G[write gate]
R & S --> E[evidence retrieval] --> C[citation validation]
subgraph routing
T[tier: critical/standard/budget/qa/research] --> P[provider chain with failover]
end
Run: npm ci && npm test (57 tests). Typecheck: npm run typecheck. Deployment pipeline reference: docs/cloudbuild.production.yaml (Cloud SQL backup → migration job → Cloud Run → health gate; identifiers replaced — see below).
Status: implementation evidence. The production service was decommissioned in 2026.
| Area | File | What it demonstrates |
|---|---|---|
| Phase machine | src/agent/transitions.ts |
Monotonic phase transitions (discovery → research → structuring → drafting → review); backwards SET_PHASE is rejected, not just ignored client-side. |
| Tool allowlist | src/agent/tool-allowlist.ts |
Every tool name classified as read / rule / write / not-available, with a distinct message for tools that are simply unknown vs. ones that are known but out of scope for this runtime. |
| Write gate | src/agent/write-gate.ts |
Write tools (save_section_draft, freeze_outline, ...) are blocked unless the caller has explicitly opted into the write rollout — independent of the tool allowlist. |
| Evidence retrieval | src/retrieval/store.ts, src/retrieval/evidence.ts |
An in-memory vector store (cosine similarity + metadata filtering) feeding numbered evidence, plus the same citation-range check the platform's compliance validator applies: a citation is only valid if it points at evidence the model was actually shown. |
| Model routing | src/routing/model-routing.ts |
Task → tier → provider/model resolution (critical / standard / budget / qa / research), with the tier-selection logic (interactive-vs-background section generation, importance escalation) kept intact. |
| Provider failover | src/routing/retry.ts |
An ordered provider chain where a timing-out or failing provider triggers a fresh, state-isolated attempt on the next provider. |
| Pricing | src/routing/pricing-table.ts |
The per-model USD/million-token rate table the resolved routing decisions are billed against. |
- No committed vectors: tests build embeddings with a deterministic bag-of-words embedder (
tests/helpers/embedder.ts) so the retrieval logic runs without a provider. - The database layer (Drizzle schema, migrations, Postgres).
- Real provider clients (Anthropic, OpenAI, Google, Perplexity SDKs) —
ProviderandEmbedderare the injected interfaces those clients would implement. - The Next.js application (routes, UI, auth, billing).
- The MCP server / tool-definition layer (Zod schemas,
zodToJsonSchema, the Anthropic Messages API wiring) that surfaces these tools to a model in production.
Every file under src/ opens with:
// Extracted from EuFund @ b7d807ad (private repo). Synthetic fixtures only.
| Exhibit file | Private source |
|---|---|
src/agent/types.ts |
app/src/lib/ai/agent/types.ts (trimmed to the fields applyTransition reads/writes) |
src/agent/transitions.ts |
app/src/lib/ai/agent/transitions.ts |
src/agent/tool-allowlist.ts |
app/src/lib/ai/agent/managed/tools.ts (name sets) + app/src/lib/ai/agent/managed/executor.ts:79-89 (allowlist check) |
src/agent/write-gate.ts |
app/src/lib/ai/agent/managed/executor.ts:91-100 (write rollout gate) |
src/retrieval/store.ts |
app/src/lib/vectors/store.ts (in-memory implementation, cosine similarity, flat-filter matching; Qdrant backend and getVectorStore() cache dropped) |
src/retrieval/evidence.ts |
app/src/lib/ai/agent/services/evidence.ts (evidence-shaping only) + app/src/lib/ai/compliance-validator.ts:240 (citation range filter) |
src/routing/model-routing.ts |
app/src/lib/ai/model-routing.ts (ROUTING_DEFAULTS, mapTaskToTier, resolveAgentModel; user-preference override, escalation and the DB-backed context loader dropped) |
src/routing/retry.ts |
app/src/lib/ai/providers/retry.ts (withRetry) + app/src/lib/ai/providers/types.ts:103-113 (fallback chain), generalised from one primary+fallback pair to an ordered provider list |
src/routing/pricing-table.ts |
app/src/lib/ai/cost/pricing-table.ts (data only, verbatim) |
docs/cloudbuild.production.yaml |
repo root cloudbuild.production.yaml, with the GCP project number, Cloud SQL connection name, internal IP, GCS bucket name, a Cloud Run revision hash, and an email address replaced by placeholders |
Where a DB call, provider SDK, or environment-config import had to be removed, it was replaced with an injected interface (Embedder, Provider, a plain embed(text) function) rather than re-implemented — the surrounding logic (cosine ranking, phase ordering, tier mapping, citation range check, failover semantics) is otherwise unchanged from the private source.
fixtures/calls.json — 8 synthetic EU funding calls (id, title, programme, summary, eligibility criteria, a link to the real official programme page). No deadlines are included; none were invented. Tests embed fixture text with a small deterministic bag-of-words embedder (tests/helpers/embedder.ts) rather than a real provider, so runs are hermetic.
npm ci
npm test # vitest run — 57 tests
npm run typecheck