Designing a Control Tower: from metrics to AI recommendations

Most finance and operations teams do not lack data. They lack a timely, trusted answer to three questions: what is going wrong, who should care, and what should they do about it?

I designed a Control Tower platform to answer those questions for a large operation. This article walks through the core concepts behind it and, more importantly, why each one is modelled the way it is. I'll use finance examples throughout (payments, invoices, cost centres and business units), but the same model works wherever you have many data sources, many teams, and a need to act on numbers before they become incidents.

Each layer has one job, and each is configured, not coded.

The one principle behind everything

Every behaviour should be a row of configuration, not a release of code.

A new data source, a new KPI, a new threshold for a new team, a new action the AI may suggest: each of these should be added through a setup wizard, validated, and picked up by the running system. That principle explains almost every modelling decision below. When something is configuration, it needs a clear concept, a clear boundary, and a clear contract with the next layer.

The platform is organised as Sense → Reason → Act:

  • Sense: data sources, metrics, KPIs and alert rules answer "What is happening?"
  • Reason: an AI agent answers "What should we do?"
  • Act: tickets, notifications and escalations, with a human gate where it matters.

1. A metric is one measurable quantity on one source

The most important definition in the whole system is the humble metric. It is tempting to jump straight to KPIs, but KPIs are where business meaning lives, and meaning changes often. A metric is deliberately boring: one number, from one source, computed one way.

A metric is defined by five things:

  1. Source: a query against a registered data source, or a stream of events.

  2. Filter: which rows or events count.

  3. Grouping: per entity (a payment, an invoice, a journal entry) and per dimension (cost centre, business unit, counterparty).

  4. Aggregation: count, sum, average, minimum, maximum, distinct count, or latest value.

  5. Window: none, rolling, tumbling, or session-based.

Whatever the source looked like, everything downstream sees the same shape.

Defining a metric in the setup wizard: source type, entity, filter, aggregation and field. Identifying names are blurred.

Why model it this way? Because it isolates the messiness of sources. Each source has its own schema, its own SQL dialect, its own quirks. A metric absorbs all of that and emits a generic row: metric, entity or dimension cell, value, time. Nothing downstream ever needs to know whether a number came from a lake house, a relational database or an event stream. When a source changes its table structure, you edit one metric's query. You don't touch a KPI or a rule.

2. KPIs are built from metrics, and computed per cell

A KPI is a business indicator. It is composed from metrics, and those metrics may come from different sources. A payment failure rate can divide a count of failed payments from a data lake by the total payments recorded in a ledger database.

The platform supports a small, closed set of KPI types:

  • Ratio: metric A divided by metric B (rates and percentages).

  • Composite: a formula across several metrics.

  • Delta: the change since the previous computation.

  • Moving average: a value smoothed over N periods.

  • Absolute: a single metric used as is.

  • Custom query: when the source can compute the KPI directly and efficiently.

KPIs may combine metrics from different sources and are computed for every dimension cell.

A ratio KPI in the wizard: numerator and denominator metrics, alert direction, target, interval, and the levels it is computed at. Business-specific values are blurred.

The dashboard for one persona: KPIs in a hierarchy, each with its value, target, breach state and 24-hour trend.

The second key idea is the cell. A KPI isn't one number; it is a number per slice: per cost centre, per business unit, for the whole group, or per counterparty within a business unit. Each KPI declares the grains it is computed at. I add a crossing such as counterparty × business unit only when a team actually needs that view. Otherwise the number of cells grows multiplicatively and nobody looks at most of them.

Why? Because the people who act on numbers each own a slice. If a KPI isn't computed at the grain someone owns, no alert can ever reach the right person. The KPI also carries its target and direction ("lower is worse"), so the dashboard, the rules and the AI agent all read it the same way.

3. Many data sources, one contract

Data sources are registered per tenant under a named connection code. A metric refers to the connection code, never to a host or a password.

Each registration carries the operational behaviour of that source:

  • Access mode, normally read-only, enforced before any query runs.
  • Capabilities: what the source is allowed to be used for.
  • Pool size, query timeout, and batch or streaming reads, tuned per source.
  • Credentials kept outside the configuration database.

Metrics name a connection; the registry decides how to reach it safely.

One detail I'm particularly glad we built: one circuit breaker per database technology. A slow cloud lakehouse warming up must not trip the breaker for the relational database that the rest of the KPIs depend on.

Why? Adding a new kind of source (we have added relational, columnar-analytics and cloud-lakehouse sources) becomes a driver plus a registry entry. No metric, KPI or rule changes.

4. Stateless and stateful consumption

Data turns into signals in three different ways, and each is chosen per use case. Treating them as one leads to either slow alerts or fragile streaming code.

Three ways data becomes a signal, each with its own trade-offs.

Stateless, per message: entity state evaluation. Each event is judged on its own: is this entity now in a bad state?

  • One consumer per tenant, bound from that tenant's configuration and added or removed at runtime.
  • Duplicate events are filtered before evaluation.
  • Entities that stop sending events are caught by a staleness check on a timer.
  • This path scales simply by adding consumers.

Stateful, across messages: windowed aggregation. Many events are combined into a number over time.

  • This needs state: a stream processor with a local state store, or a scheduled query over stored events.
  • It brings real obligations: grace periods for late events, and replays and restarts that must not double-count.

Pull, on a schedule: query on a cadence. For most sources the simplest correct answer is to run the metric's query every N minutes.

  • Each KPI declares its own interval and how long it is expected to take.
  • A cluster-wide lock per KPI guarantees that two replicas never compute the same KPI twice.
  • Large results are streamed in chunks instead of loaded at once.

Why separate them? Entity-level problems (this one payment is stuck) and aggregate problems (this business unit's failure rate is degrading) are different questions. An aggregate can look healthy while one important entity is failing, and a single bad entity says nothing about a trend. The platform keeps both paths and lets them share the same rules, routing and actions.

5. Alert rules: when, for whom, and how it ends

An alert rule is not just a threshold. It combines detection, routing and lifecycle in one definition:

  • What triggers it. A KPI threshold (a cell crosses a line), an anomaly (a KPI moves away from its learned normal), an entity state (one entity is in a bad state), or a forecast (an ML model predicts a breach before it happens).
  • For whom. Thresholds can be set per persona, so a cost-centre owner and a business-unit controller can have different lines for the same KPI. They can also be set per dimension member, so each business unit gets its own line. Each level has soft (early warning) and hard thresholds, severity tiers and channels.
  • How it is grouped. Repeated breaches are debounced. A rule can open one ticket per breach, or one cohort ticket that collects every entity that enters the breach.
  • How it ends. An alert closes by condition (N consecutive healthy periods), by time, or by an event.

Early warning comes from two places. Soft thresholds warn when a KPI is getting close to its hard line. On top of that, a few ML models are built specifically to give early warning: they forecast that a KPI or an entity is likely to breach before it does, and that prediction raises an alert of its own, which the AI agent picks up in forecasting mode.

A rule is detection, routing and lifecycle in one definition.

An alert rule in the wizard: scope band, rule source type, target dimension and level, KPI dimensions and an optional metric-floor guard. Business-specific values are blurred.

Two design choices matter more than they look:

  • Escalation is a state change on the same alert, not a new alert. This keeps history, tickets and metrics coherent.
  • Closure is a first-class part of the rule. Most alerting systems are good at opening alerts and bad at closing them. If closure isn't modelled, people stop trusting the dashboard.

One alert: severity and type, observed value against target, a closure form, the event timeline and the breach history.

The same alert lists every affected entity and the single ticket that groups them.

6. Personas: ownership as data

A persona is a role anchored to a dimension and a level: a cost-centre owner owns the organisation at cost-centre level, a business-unit controller owns it at business-unit level, and a group finance head owns the whole group. A persona is not a list of users. Users are assigned to a persona and a member, for example "Business-unit controller — Business unit Retail".

A persona carries:

  • Parent persona, on the same dimension and at a strictly coarser level. This chain is the escalation path.
  • Visible KPIs: what its users see on the dashboard.
  • Approval authority: the highest risk level of AI-suggested action it may approve.
  • Channels: dashboard, email, ticket queue.

Alerts route to the persona that owns the cell; people change, the model does not.

A persona in the wizard: dimension, level, parent persona, visible KPIs and approval authority. Business-specific values are blurred.

Assigning a user to a persona and the dimension members they cover. Business-specific values are blurred.

Why? Organisations re-org; slices of the business don't. Anchoring ownership to dimension × level lets an alert on a cell find its owner automatically, find the next owner up when nobody acts, and survive people joining and leaving without touching a single rule.

7. AI recommendations, with a human gate

The AI layer sits beside deterministic detection; it never replaces it. Rules decide that something is wrong. The agent helps decide what to do.

  1. A new alert creates a work request on a queue. The request can be triage for a fresh alert, escalation when an alert sat unattended, or forecasting when a breach is predicted.
  2. The agent gathers evidence by calling tools: the entity's status, its alert history, a KPI summary, a rule check and the escalation path. Each tool is individually authorised.
  3. A self-hosted LLM writes a recommendation, the reasoning in plain language, and a confidence level.
  4. Safety gates always apply. Low confidence hands the decision to a human, and tool rounds and token budgets are bounded.
  5. A persona with enough authority approves, the action runs, and the outcome is measured later against configured success criteria.

Evidence, reasoning, a confidence, and a human with the authority to say yes.

A real recommendation: the model was unavailable, so the agent fell back to a human at 30% confidence instead of guessing.

The recommendations are a small, explicit set: escalate, notify, monitor, no action, escalate to a human, and close. What the agent may recommend, and whether it may act alone, is configured per alert and dimension: allowed action, confidence threshold, auto-execute, risk level, approver, approval timeout and success criteria. The agent keeps a short memory note per entity, so the next run builds on the last decision instead of starting over.

Why this shape? It makes the AI auditable and reversible. Every recommendation has evidence, a reason, a confidence and an approver. Trust is earned one action type at a time, by moving it from "human approves" to "auto-execute" through configuration, not code.

What I would tell anyone building one

  1. Define the metric before the KPI. A boring, generic metric is what makes everything else portable.
  2. Model ownership as data. Dimension × level personas route alerts better than any distribution list.
  3. Design closure, not just detection. An alert that never closes erodes trust faster than one that never opens.
  4. Keep entity-level and aggregate-level detection separate, and let them share routing and actions.
  5. Put AI behind evidence, confidence and authority. Let automation grow by configuration as it earns trust.
  6. If an option is in the setup wizard, something must act on it. Every setting should be either wired to behaviour or removed; a setting that silently does nothing is worse than no setting.

A Control Tower is less about dashboards than about decisions: getting the right number to the right owner, early enough, with a sensible next step attached. Get the concepts right and the dashboards almost build themselves.


I designed this platform end to end. If you are building something similar, I'd be glad to compare notes in the comments.

I designed and built this platform. I used an AI assistant to help structure the draft and produce the diagrams from my design notes and the codebase; I reviewed and edited every section, and the screenshots are from the running system with business details blurred.