Diego Souza · AI Engineer

All projects

SupportOps

A support platform where AI can draft a refund, but only a second person can approve it.

verified offline, live smoke pendingLast verified: 2026-10-31 · Commit 6e51483

TL;DR

Problem
Support automation crosses tenant data, tools, background work and money. A model that can approve its own refund is a liability, so the system has to make that impossible by construction, not by prompt.
My role
I designed and built the API, the retrieval, the approval flow, the accounted live path and the console, with coding agents under a spec-first harness.
Stack
Python, FastAPI, PostgreSQL, pgvector, Redis, MCP, Keycloak, React, TypeScript, Fly.io, Cloudflare Pages
Result
179 tests passed in its latest recorded suite run, and 17 passed for the live ledger and key failover, run against PostgreSQL 16. The live demo itself has not been measured yet.

Tickets arrive, a team of agents triages them against tenant-scoped knowledge, and every answer cites its sources. Money is the line the model cannot cross: it proposes, a deterministic gate checks, and a human decides. The requester cannot approve their own refund. Open the console and try to.

See it run

The live demo runs this system against a real model and shows every step.

Live demo

How it works

A refund that needs a second personFlow: Ticket, Tenant from token, Tenant-scoped search, Four agents, Budget ledger, Refund gate, Second person, Simulated refundTicketTenant from tokenTenant-scoped searchFour agentsBudget ledgerRefund gateSecond personSimulated refundA refund that needs a second personFlow: Ticket, Tenant from token, Tenant-scoped search, Four agents, Budget ledger, Refund gate, Second person, Simulated refundTicketTenant from tokenTenant-scoped searchFour agentsBudget ledgerRefund gateSecond personSimulated refund

The whole flow. Press play or next to walk through it.

Text version of this diagram
  1. Ticket P06. The console sends the ticket with the user's token.
    • then Tenant from token
  2. Tenant from token P05. Tenant and role are read from the token; the request cannot choose them.
    • then Tenant-scoped search
  3. Tenant-scoped search P05. Retrieval only sees the ticket's tenant, and answers cite sources.
    • then Four agents
  4. Four agents P01. Classifier, researcher, billing and responder, with a step limit.
    • then Budget ledger
  5. Budget ledger P05. The cost is reserved in Postgres before the call and fails closed at the ceiling.
    • then Refund gate (proposal)
  6. Refund gate P05. The proposal becomes an approval request through a deterministic tool.
    • then Second person
  7. Second person P06. An approver other than the requester decides.
    • then Simulated refund (approved)
  8. Simulated refund P05. An approved request creates a simulated refund. Nothing real moves.

Decisions

Tenant and role come from the token

The API reads tenant and role from the signed token. A browser token can carry scopes its user's role must not use, so scopes are narrowed by role on the server.

Code: supportops_platform/api/app.py

Rejected: Trusting a tenant id or scope list sent by the client.

The model proposes; a gate and a person decide

A refund request goes through a deterministic tool and becomes an approval that a different principal must decide. The amount comes from that tool, not from the model.

Code: supportops_platform/service_runtime.py

Rejected: Letting model output set the amount or approve.

Reserve the cost before the call

Each model call reserves its worst-case cost in a Postgres ledger with one atomic statement that also checks the ceiling. A rate-limit answer moves to the next key and bills nothing; an uncertain call keeps its reservation.

Code: supportops_platform/agents/demo_live.py

Rejected: Adding up spend after the fact.

Results

Measured

Measured
MetricValueModeSource
P05 test suite at phase 033179 tests passedfake: control logic.planning/phases/033-console-blockers/VERIFY.mdCommit d859a50 · 2026-10-31 · Exit code 0
demo-live-v1 ledger and failover tests against PostgreSQL 16 Fake provider transport. No model was contacted.17 passedservices, fake modelartifacts/evidence/demo-live-v1/pytest-demo-live-postgres.txtCommit ffcdb81 · 2026-10-31 · Exit code 0
Requester cannot approve their own refund; a tenant cannot read another tenant's ticket2 passedfake: control logicartifacts/evidence/portfolio-w7/dual-control-tests.txtCommit 6e51483 · 2026-10-31 · Exit code 0

Not measured, so not claimed

  • Semantic quality of the answers. Retrieval fixtures prove control flow, not meaning.
  • Cost and latency of the live demo. No public live run exists yet.
  • Throughput under load.

Failure gallery

What happened
The first accounted path stopped all later work after any failed attempt.
Cause
It was built for one reviewed local operation, where a failure should block until a person looks. A public service cannot work that way.
What changed
The public path releases the reservation when a provider answered with an error, and keeps it only when the outcome is unknown.
What happened
A clean clone could not install the project.
Cause
An ignore file excluded every locally built wheel, including the ones the lock file points to.
What changed
The five wheels the lock names are tracked and their hashes checked against the lock.
What happened
Triage failed from the console with a generic worker failure.
Cause
The worker runs on the caller's scopes, and the console did not request the scopes its tools need.
What changed
Console scopes now match the agent's scopes, with a note to update both when a tool is added.

Validate it yourself

git clone https://github.com/DiegoVSouza/supportops-platform.git
cd supportops-platform
uv sync --locked && uv run --locked pytest

Expected: The suite passes. The service gate (`scripts/check_full.py`) needs Docker.

What I would do differently

The refund amount is a fixed tool value. I would let the proposal carry a bounded amount read from the ticket, so the model's work shows up in the money decision.

Next project

Foundation and live evals