Case study · agentic system design

Cashflow Copilot

A three-agent system that turns a raw bank CSV into a categorized ledger, a 30/60/90-day cash forecast, and a short list of proposed actions — with two mandatory human approval gates standing between every AI suggestion and anything that touches money or a customer inbox. This page is a working build of that design, run entirely in your browser against sample data.

Prompt engineeringMulti-agent orchestrationTool / API integrationsHuman-in-the-loop
Run the live demoRead the system prompts
// System design

Sequential orchestration, explicit pause nodes.

A hand-rolled TypeScript state machine — not a black box. Every transition out of a gate is triggered by a human action, never a timer or a model.

input
CSV upload / mock data
Transaction[]
agent
IngestCategorizeAgent
categorize + flag
⏸ pause for human
Gate 1 — Human approval
editable table, approve/edit
agent
CashForecastAgent
30/60/90-day forecast
agent
ActionAgent
ranked proposals + email draft
⏸ pause for human
Gate 2 — Human approval
approve / edit / reject per card
output
Execute approved only
send email · log ledger note
output
Weekly Brief
Markdown, generated from state
output
Audit log
every step + every decision
Shared state object
type RunState = {
  status: "idle" | "categorizing"
    | "awaiting_categorization_approval"
    | "forecasting" | "awaiting_action_approval"
    | "executing" | "completed";
  rawTransactions: Transaction[];
  categorizedTransactions: CategorizedTransaction[];
  forecast: CashForecast | null;
  proposedActions: ActionProposal[];
  auditLog: AuditEntry[];
};
Why two gates, not one

Gate 1 is low-stakes — recategorizing a transaction has no external effect, so a simple approve/edit table is enough. Gate 2 guards anything that sends an email or moves money, so it's a stronger per-action gate: approve, edit the draft, or reject — individually, not as a batch. Nothing in EXECUTE_ACTIONS runs while any action is still pending.

// Live demo

Watch the run, end to end.

Sample data: a Stripe-connected bank feed for Juniper & Co. Consulting. Nothing here calls a real model or sends a real email — the state machine and UI are real.

● Cashflow Copilot · run state
status: idle
Idle
IngestCategorizeAgent
Categorize + flag
Idle
CashForecastAgent
30/60/90-day forecast
Idle
ActionAgent
Propose next steps
Idle
Orchestrator
Execute + generate brief

Load a sample bank feed to kick off the IngestCategorizeAgent. Two approval gates stand between here and anything customer-facing.

Nothing yet — load sample data to start a run.

    // Prompt engineering

    Every agent's system prompt, unabridged.

    Structured-JSON output, mandatory confidence scoring, current-date awareness, and a conservative-tone constraint on anything financial — the same requirements a production build enforces.

    model: claude-sonnet · system prompt
    You are the Ingest & Categorize agent inside Cashflow Copilot, a
    tool for small-business owners. Today's date is {{current_date}}.
    
    ROLE
    Given a list of raw bank transactions, assign each one a spending/revenue
    category and flag anything a careful bookkeeper would double-check.
    
    CONSTRAINTS
    - Output ONLY valid JSON matching the CategorizedTransaction[] schema below.
      No prose, no markdown fences.
    - Never invent a transaction that wasn't in the input. Never drop one.
    - Assign a confidence score in [0, 1] for every category you assign. Scores
      below 0.8 MUST be reviewed by a human before anything downstream trusts
      them — say so is implicit in the score, don't add a disclaimer field.
    - Flag duplicates (same amount + same/adjacent date), anomalies (>20% off
      the category's trailing average when history is available), and likely
      missing invoices (unmatched client names with no corresponding payment).
    - This is categorization support, not accounting or tax advice. If asked to
      editorialize, decline and stick to the schema.
    
    OUTPUT SCHEMA
    {
      "id": string,
      "category": string,
      "confidence": number,
      "flag"?: "anomaly" | "duplicate" | "missing_invoice",
      "flagNote"?: string
    }[]
    
    FEW-SHOT
    Input:  { "id": "tx-11", "date": "2026-08-09", "description": "Adobe CC — annual", "amount": -599 }
    Output: { "id": "tx-11", "category": "Software & Subscriptions", "confidence": 0.93 }
    // Tool / API integrations

    Agents call tools. Tools don't call agents.

    Arithmetic, CSV parsing, and email sending are deterministic code — the model proposes, structured tools execute, and only after a human says go.

    IngestCategorizeAgent

    CSV Parser

    Normalizes uploaded bank CSVs (or pasted rows) into the shared Transaction shape before any model call.

    CashForecastAgent

    Balance & Ratio Calculator

    Deterministic TS functions for running balance, % of revenue, and burn rate — kept out of the model to avoid arithmetic hallucination.

    CashForecastAgent

    Rolling Forecast Engine

    Projects 30/60/90-day balance from categorized inflow/outflow patterns and surfaces the first day balance goes negative.

    IngestCategorizeAgent

    Anomaly & Duplicate Detector

    Flags same-day/same-amount duplicates, missing invoice matches, and category outliers for human review.

    ActionAgent

    Email Drafting (Resend sandbox)

    Generates a customer-facing reminder draft; sending is a separate, explicitly-approved tool call — never automatic.

    Orchestrator

    Weekly Brief Generator

    Renders the approved run state into a Markdown (and optionally PDF) Financial Health Brief after Gate 2 clears.

    This is how I design agentic systems for clients — orchestration first, human control non-negotiable.

    SubscribeBack to Mir's Studio