Enterprise agentic AI infrastructure · internal reporting

The weekly report writes itself

Companies are not throwing away their ETL. They are attaching an agentic layer to the end of it — one that retrieves its own context, calls governed internal endpoints for facts, verifies every number against the source of truth, and delivers a finished report to Outlook before anyone opens a laptop on Monday.

Time to a new question
weeks → hours
Report assembly
manual → planned
Numbers shipped unverified
zero

The restructure

Agents don’t replace your ETL. They replace the analyst-shaped gap at the end of it.

The extract and load stages are the part of the stack that already works. What doesn’t scale is the last mile — a person reading six dashboards on Friday afternoon and typing a summary from memory. That is the step being rebuilt.

Trigger

A nightly DAG runs the same tasks it ran last year.

The same weekly cron fires, but the run plans its own task graph from the report spec.

Scope

Fixed columns, decided in a design doc months ago.

Scope is derived per run — a launch week pulls different evidence than a quiet one.

A new question

Ticket → new dbt model → new dashboard tile. Two to six weeks.

One new tool on an endpoint that already exists, reviewed once. Hours.

Output

A dashboard someone has to remember to open.

A written narrative in the inbox, with the chart and the workbook attached.

Failure mode

Quietly stale. Nobody notices until a number is wrong in a board deck.

The run blocks. An unreconciled figure is not allowed to ship.

Cost shape

Recompute everything, whether or not anyone asks.

Retrieve what the question needs; cache the rest between runs.

The framework

A reference architecture

An agent runtime on servers you own, self-hosted inference beside it, and a hybrid data estate it is only ever allowed to touch through typed endpoints.

AGENT RUNTIME — KUBERNETES · SERVERS YOU OWN · ONE POD PER RUNPLATFORM SERVICESEGRESS — THE ONLY EXTERNAL CALLINTERNAL MCP GATEWAY — TYPED TOOLS · AGENT-SCOPED AUTH · THE ONLY DOOR TO DATASYSTEMS OF RECORD — HYBRID ESTATE · CLOUD + ON-PREM · NOTHING MIGRATESVector storepgvector · embeddingsmetric defs · past reportsSnowflakefinance marts · dbt modelscloud warehouseSQL Server · ERPlegacy finance · on-premread-only mirrorPostgres OLTPorders · live app stateRDS read replicaInternal app servicesbilling-svc · inventory-svcgRPC · VMs you ownKafkaCDC · usage eventsself-hosted clusterS3 / Delta Lakeraw + curated parquetbatch recordsTelemetryPrometheus · cloud billingSLOs · GPU util · spendretrieval.mcphybrid search · rerankACL-filtered chunksfinance.mcpclose · budget · GLrow-level scopedops.mcporders · billing · stockread-only · row capsevents.mcpusage · CDC windowspre-aggregatedtelemetry.mcpspend · SLOs · incidentsrate-limitedvLLM inferencellm-d · owned GPU nodesmodel registry · KV cacheAgent identityservice accounts · RBACVault · scoped tokensObservabilityOpenTelemetry tracestoken metering · evalsSchedulercron · Mon 06:00Context assemblerRAG · prompt injectionPlannerspec → task graphFinance agentclose · budgetOperations agentorders · fulfilmentPlatform agentusage · spend · SLOsReducermerges artifactsVerificationreconcile · citeOutlookGraph sendMail · Mon 07:00AUDIT — every prompt, tool call, token and citation traced via OpenTelemetry and recorded for replay

Scroll the diagram horizontally to follow the flow.

01

The estate you already run

The starting point is honest: the data lives in eight places, not one. A cloud warehouse and an on-prem SQL Server ERP that predates it; an OLTP read replica and proprietary gRPC services on VMs you own; streams, a lake, telemetry. Nothing migrates — the agents go to the data, because after twenty years the data was never coming to them.

02

Internal MCP endpoints, one per domain

Instead of handing a model eight sets of credentials, each domain publishes a small MCP server with a few typed, reviewed tools. finance.mcp fronts both the Snowflake marts and the legacy ERP, so the agent never needs to know which decade a number lives in. Authentication, row-level scoping and rate limits are enforced here — one security review per endpoint, not one per prompt.

03

Agents are workloads — they run on your metal

Every run is a short-lived pod on the internal Kubernetes cluster, and every model call goes to a vLLM serving layer on GPU nodes you own — prompts and financials never leave the building, and you produce tokens instead of renting them. Each agent gets its own service identity from Vault, scoped to exactly the endpoints its domain allows, while OpenTelemetry traces every prompt, tool call and token onto a meter.

04

Context assembly: retrieval plus prompt injection

At 06:00 Monday a cron fires inside the cluster — the run begins with a goal, not a script. Before any reasoning, hybrid retrieval pulls the definitions of record from the vector store: how this company computes net revenue retention, last week's report, the SOP that governs a deviation. A template then injects the invariants — fiscal calendar, the run's RBAC scope, and the hard rule that every number carries a citation.

05

Plan, then fan out to domain agents

The planner turns the report spec into a task graph and hands each branch to an agent pod that holds only its own domain's tools — finance, operations, platform. They run in parallel, in separate contexts, each reaching down through the gateway with typed calls. This is the load-bearing decision for cost and reliability: a finance agent that has never seen the ops toolset is cheaper, faster, and far less likely to wander.

06

Reduce, then refuse to trust the model

Agents return compact artifacts — a figure, its units, the query that produced it — never raw rows. The reducer merges them, then verification re-runs each headline number against the source of truth and asserts equality. A figure that cannot be reconciled, or that arrives without a citation, blocks the send rather than shipping with a caveat.

07

One weekly artifact, in the inbox

The composed report crosses the only boundary this system ever crosses: Microsoft Graph delivers it to Outlook at 07:00 Monday, as a real email with the workbook attached. Everything upstream ran on your own servers; the finished artifact is also embedded back into the vector store, where it becomes next week's context.

The mechanisms

Six pieces, each boring on purpose

None of these are exotic. The difficulty is not in any one of them — it is in refusing to skip the dull ones once a demo starts working.

01RAG over a vector store

Retrieval, before any reasoning

An agent that guesses what your company means by net revenue retention is worse than useless. Embed the things that define your business — metric definitions, schema docs, SOPs, and every report you have already shipped — and retrieve them first. Hybrid search (lexical union vector, rank-fused, then re-ranked) beats pure similarity on internal corpora, where the exact metric name matters more than the vibe of the sentence.

retrieval.search(
  query  = "net revenue retention — definition of record",
  spaces = ["metric-defs", "sop", "reports/2026-*"],
  mode   = "hybrid",      # BM25 ∪ ANN, reciprocal-rank fused
  k = 12, rerank = "cross-encoder", top_n = 4,
  filter = {"tenant": ctx.tenant, "acl": ctx.groups},
)
→ 4 chunks · 1,840 tokens · each carries doc_id + revision
02Querying cloud databases

Facts come from the warehouse, never from memory

Retrieval supplies definitions. It must not supply numbers. Every figure in the report is produced by a query executed at run time against a governed model — Snowflake for the finance marts, a Postgres read replica for live operational state, Parquet in the lake for the long tail. The agent chooses which to call; it never chooses what the number is.

finance.close_summary(period = "2026-W33")
  ├─ snowflake  fct_gl_entries         governed dbt model
  ├─ postgres   ops.invoice_state      read replica · 30s lag
  └─ s3         batch_records/2026/33  parquet · curated zone

→ {"revenue": 4182640.00, "unit": "USD",
   "as_of": "2026-08-17T23:59Z", "rows_scanned": 812443}
03Tool calling

A typed contract, not a text box

Tools are declared with strict schemas and validated before execution, so a malformed period or an out-of-range segment fails at the boundary instead of inside a query. Keep the surface small and the names boring: an agent handed sixty tools picks badly far more often than one handed six.

{
  "name": "revops.pipeline_snapshot",
  "description": "Pipeline + ARR for one fiscal week.",
  "input_schema": {
    "type": "object",
    "properties": {
      "period":  {"type": "string", "pattern": "^\\d{4}-W\\d{2}$"},
      "segment": {"enum": ["smb", "mid", "ent"]}
    },
    "required": ["period"],
    "additionalProperties": false
  }
}
04System prompt injection

The invariants are not left to chance

Anything that must be true of every run belongs in the system prompt, injected from real state at assembly time — the fiscal calendar, who is asking, what they are cleared to see, the house style, and the rules that have no exceptions. This is also where you draw the line the model may not cross: report the gap, never estimate the number.

<runtime_context>
  fiscal_calendar: 4-4-5 · FY starts Feb 1
  requester:       {{user.email}}
  visibility:      {{user.rbac_groups}}   # scopes every tool
  period:          2026-W33  (Aug 11 – Aug 17)
  house_style:     past tense · no adjectives · 2dp
</runtime_context>
<rules>
  Every figure MUST cite the tool call that produced it.
  If a tool fails, report the gap. Never estimate.
</rules>
05Internal MCP endpoints

One security review per endpoint, not per prompt

Each domain publishes its own MCP server exposing a handful of reviewed, read-scoped tools. That is what makes this governable: authentication, row-level scoping, rate limits and audit logging live at the endpoint, so adding a capability is a pull request against a service your platform team already owns — not a new pathway for a model to reach your database.

finance.mcp    9 tools   auth: SSO + service scope
  close_summary        read   ✔ audited
  budget_vs_actual     read   ✔ audited
  gl_drilldown         read   ✔ audited   row-cap 50k
revops.mcp     6 tools
quality.mcp    7 tools   ⚠ GxP: signer identity required
infra.mcp      5 tools
retrieval.mcp  3 tools   hybrid search · rerank · cite
06Verification and citation

The model proposes; SQL disposes

Before anything is sent, every headline figure is recomputed against the source of truth and asserted equal — not similar, equal. A number that cannot be reconciled, or that arrives without a citation, blocks the run and pages an owner. This single gate is what moves an agentic report from a demo to something a CFO will read.

assert reconcile(
  claim  = report.figures["revenue"],
  source = snowflake("""select sum(amount) from fct_gl_entries
                        where fiscal_week = '2026-W33'"""),
  tolerance = 0.00,
)

✔ 12/12 figures reconciled   ✔ 12/12 cited   → cleared
✘  1/12 unreconciled         → run blocked · owner paged

Tool chaining

The context window is a budget, and raw data will spend all of it

Most agentic reporting projects fail here rather than at the modelling. One honest query against a real fact table returns more tokens than the window holds, and everything downstream inherits the mess.

One context, everything in it

232k / 200k tokens

Tools return rows. The agent reads them.

0context limit
  • System prompt2k
  • 48 tool definitions9.4k
  • Retrieved documents6.2k
  • Raw query results — 812k rows214k

Run dies at step 4 — 232k of a 200k window.

Chained, with compaction

12.4k / 200k tokens

Tools return artifacts. Sub-agents keep their own contexts.

0context limit
  • System prompt2k
  • 6 scoped tool definitions1.4k
  • Reranked context — top 41.8k
  • 3 sub-agent artifacts4.2k
  • Draft narrative3k

Run completes — 12.4k, about 6% of the budget.

Fan out into fresh contexts

Each domain agent starts clean and sees only its own tools. Nothing accumulates across branches, so cost grows with the number of questions — not with the square of them.

Return artifacts, not rows

A tool answers with a figure, its units, and the query that produced it. Row caps are enforced at the endpoint, so an agent cannot accidentally ask for a million records.

Pass large results by reference

Anything genuinely big is written to object storage and handed on as a URI. The next hop fetches it with a tool if it truly needs it, which it usually does not.

Compact between hops

When a branch does need several turns, it summarises its own transcript to a fixed budget before continuing, so a long chain has a bounded ceiling rather than a growing one.

The endpoint

07:00 Monday, in Outlook

Everything upstream — the retrieval, the endpoints, the fan-out, the reconciliation — exists to make one email defensible enough to send without a human reading it first.

OutlookInbox · Focused

Weekly operating review — 2026-W33

Reporting Agent→ exec-staff · Mon 07:00

Revenue finished the week at plan. The gap in new pipeline is concentrated in mid-market and traces to a single stalled renewal cohort rather than a broad slowdown. One quality deviation was opened and closed within the week.

Aug 11 – Aug 17
Revenue[1]$4,182,640+2.4%
New pipeline[2]$6,914,200−8.1%
Net revenue retention[3]112.6%+0.9pt
Open deviations[4]0−1
Cloud spend[5]$61,480−4.2%
12 of 12 figures reconciled against source and cited.
W33-operating-review.xlsx412 KB
POST https://graph.microsoft.com/v1.0/users/{svc}/sendMail
Authorization: Bearer {app-only token · scope Mail.Send}
Idempotency-Key: weekly-operating-review:2026-W33

{ "message": {
    "subject": "Weekly operating review — 2026-W33",
    "toRecipients": [{"emailAddress": {"address": "exec-staff@…"}}],
    "body": { "contentType": "HTML", "content": "<rendered/>" },
    "attachments": [{
      "@odata.type": "#microsoft.graph.fileAttachment",
      "name": "W33-operating-review.xlsx",
      "contentBytes": "<base64>"
    }]
  },
  "saveToSentItems": true }

→ 202 Accepted · delivered Mon 07:00:04Z · 12/12 figures cited

The inbox is the only dashboard with universal adoption

A report that arrives is read; a report that must be visited is not. Ending the pipeline at Outlook rather than at another internal tool is the difference between a system people rely on and one they forget exists by March.

Sends are idempotent per period

The key is the report name plus the fiscal period, so a retry after a timeout cannot deliver the same review twice. This matters more than it sounds — a duplicated board summary erodes trust faster than a late one.

Gaps are reported, never filled

If quality.mcp is down when the run fires, the email says which section is missing and why. An agent that quietly interpolates around a broken endpoint is a liability, not a feature.

Replies become the next question

A reply routes back in as a follow-up run against the same context and citations, which is where the compounding actually comes from: the weekly report stops being an artifact and starts being a conversation.

Implementation

A twelve-week path, with exit criteria

Every phase ends in something checkable. The order matters more than the durations — grounding before fan-out, and gates before delivery.

00Week 1–2

Pick the report someone already writes by hand

Not the most valuable report — the most repetitive one, where a named person spends Friday afternoon assembling the same figures. You need a human baseline to argue with, and you need an owner who will tell you when the draft is wrong.

Exit: a written spec of the report, and the last twelve editions collected.

01Week 2–5

Stand up one MCP endpoint properly

Choose the domain with the cleanest governed models and publish five to eight read-only tools behind SSO, with row caps and audit logging from the first commit. Resist the urge to expose a generic run_sql tool — it will pass the demo and fail the security review.

Exit: an endpoint your platform team owns, with an on-call rotation.

02Week 5–7

Ground it before you make it clever

Embed metric definitions, schema documentation, the relevant SOPs and those twelve prior reports. Wire hybrid retrieval with a re-ranker and confirm, by hand, that asking for a definition returns the definition of record rather than a plausible neighbour.

Exit: retrieval precision measured on a held-out set of real questions.

03Week 7–10

Fan out, with artifact contracts

Add the planner and one agent per domain, each with its own context and only its own tools. Fix the artifact schema now — figure, units, as-of timestamp, and the tool call that produced it — because retrofitting citations onto a working pipeline is far more painful than starting with them.

Exit: a full run completes inside a fixed token budget, reproducibly.

04Week 10–12

Gate it, and let the gate block you

Reconciliation against source, citation enforcement, and an eval harness that replays all twelve historical weeks and diffs against what the human actually wrote. The first time the gate blocks a run you will want to disable it. That is the moment the project either becomes trustworthy or does not.

Exit: zero unreconciled figures across a twelve-week replay.

05Week 12+

Deliver, then widen one domain at a time

Turn on the Graph send with the human owner still writing their version in parallel for a few weeks. When they stop bothering, you are done — and the next domain is a new endpoint against an architecture that already exists, not another project.

Exit: the human baseline is retired by the person who wrote it.

And four things to refuse

  • Write-scoped tools in v1. Read-only until the reconciliation gate has survived a full quarter.
  • A chat box as the first surface. Ship the scheduled artifact first; conversation is the second product, not the first.
  • Letting the agent define metrics. It retrieves definitions of record — it does not get a vote on what NRR means.
  • One giant endpoint. The blast radius of a mistake should be one domain, not the whole warehouse.

Join the waitlist

No spam. We’ll only email you about early access.