Mazaal AI
A usage ledger for a production AI agent platform
October 2024–April 2026
The product
Mazaal is an AI agent platform: teams build agents in natural language, ground them in knowledge bases with vector search, wire them into workflows, and deploy the same agent across sixteen channels — web widget, Slack, email, WhatsApp, Instagram, voice, SMS and more — over fifty models and a large connector catalogue.
Every one of those surfaces creates AI usage. That is what made the metering subsystem a horizontal product boundary rather than one more screen in the navigation.
What I worked on
I was Tech Lead and Platform Architect on the team from October 2024 to April 2026, across agent, knowledge, tool, extension, widget, and operational surfaces. This page covers one subsystem, where I owned the design and a substantial share of the implementation: credits and usage. Runtime, provider, and product decisions around it stayed team work.
The rule the system had to defend
The inherited model was easy to state:
one AI request = one credit
It was disconnected from the product's economics. A short call to a cheap model and a long streamed response from an expensive one cost the same. Two simultaneous requests could read the same balance. A provider could burn tokens and then fail before the user saw a result. Organisation credits could be pooled while support still needed to know which person spent them.
What looked like a counter in the UI was pricing, accounting, concurrency, recovery, and customer trust compressed into one mutable row.
So the design started from a rule rather than a schema:
Every movement of credit must have an owner, a reason, a price basis, and a recovery path.
A balance answers none of those. It only shows the latest projection. The source of truth had to be the ordered history behind it — reservation, finalised usage, refund, reset, purchase, grant, expiration, operator adjustment.
The failure case that shaped it
Not a successful chat. A stream that dies halfway: the user starts a request, the system confirms value is available, the model consumes provider tokens, the connection drops, no result arrives.
Deducting only at the end lets concurrent requests overspend. Deducting everything up front charges for a result that never came. Updating a balance without an immutable event leaves support unable to explain or repair either outcome.
Reservation and finalisation therefore had to be separate operations, with compensating entries for work that needed to be returned.
Two shortcuts that were rejected
Keep the balance row and add an audit log. The smallest change, and it creates two competing truths — the mutable balance and the explanatory log. When they drift, reconciliation is guesswork.
Let the payment provider own the model. Stripe-like systems are good at money, subscriptions, and invoices. They do not know the product meaning of model tokens, internal credits, organisation pools, partial AI execution, or a failed tool call.
Architecture
The charging path resolved five pieces of context before value moved: the initiating user, the organisation that owned or pooled the balance, the model and provider actually used, the applicable pricing version, and the product operation that created the usage.
Inside one Prisma transaction the service locked the relevant balance rows in a consistent order, recorded the usage or ledger entry, and updated the projection. Refunds added compensating entries rather than deleting history. Pricing data was versioned, so an old event did not silently change meaning when model prices moved. Estimated or reserved cost stayed separate from finalised provider usage, which let a failed stream release unused value while preserving the execution that had actually happened.
Concurrency was a product rule
The dangerous race is simple: two workers read the same available credit and both decide they can spend it.
PostgreSQL row locks put the check and the update inside one serialisable business action. Consistent lock ordering reduced deadlock risk when both organisation-level and user-level records were involved. Concurrency tests exercised simultaneous deductions, insufficient funds, refunds, and pooled usage rather than only the happy path.
The user-facing promise — you cannot spend more credit than you own — depended on that database behaviour. It was not an implementation detail.
What operations could answer afterwards
The old support question was "why is this balance wrong?" The new questions had answers: which user and organisation initiated the usage, which model and pricing version calculated the charge, whether value was reserved, finalised, or refunded, which operator changed a balance and why, and whether the projection still reconciled with the ledger.
Admin and support surfaces could show the trail instead of editing an unexplained number. Reconciliation became a repeatable check rather than a manual reconstruction after a complaint.
Result
Mazaal moved from arbitrary per-call subtraction to usage that can be priced by model, attributed to a user and an organisation, protected under concurrency, refunded without erasing history, and audited by operators.
The moment a customer can exchange money for AI usage, credits stop being gamification and become a financial promise embedded in product behaviour. The ledger invariant, the failure path, and the reconciliation model belong in the design before the pricing page does.