Let's find the work AI should be doing in your organization.
Whether you are exploring an idea or improving a system already in use, we’ll help you decide what is worth doing next.
An agentic system is not one thing. It is retrieval that resolves to real rows, tools with contracts the model cannot talk its way around, memory that survives a restart, grounded answers you can trace back to a record, traces that show every model call and handoff on one timeline, and scoring that catches a regression before a customer does. The applied AI is the interesting part — the engineering underneath is what decides whether it survives contact with production.
We choose from first principles: what a layer has to guarantee, before the framework or vendor that provides it. The tools below are where that thinking has landed in practice — not a constraint we would carry into your codebase.
| Layer | What we use |
|---|---|
| Agent framework | MastraLangChain |
| Systems of record | Payload CMSRuby on Rails |
| Knowledge | Vector databasesGraph databasesClassifiersOntology builders |
| Remote sandboxes | E2BModalDaytona |
| Agent Evals | Mastra evalsLangChain agent evalsObservability traces |
| Frontend | Next.jsReactAI SDKSvelteKitAndroidiOS |
| Deployments | AWSCloudflareMastra |
We do not start from a framework. We start from what this layer has to guarantee before anything is built on it, and then check which framework actually does those things rather than describing them. What we are buying is the agent lifecycle — the loop, the tool contracts, memory, traces, evaluation and the path to running under load. That is undifferentiated work: every team building agents needs it, and none of it is the thing your business is actually paying for. A framework that owns it well is a framework that leaves us on your business logic, from first commit through to production.
Mastra answers that list without the project bending around it. Tools are typed with Zod and validated on the way in, workflows are real steps with retries and branches, and memory is thread and resource scoped in our own Postgres. Traces cover every model and tool call, scorers live in the same project as the agent, and switching model provider is a config line rather than a rewrite. It is TypeScript end to end, so the same team and the same types run from the agent through to the React front-end, and it self-hosts, so nothing about our data leaves our stack. Where a project needs something else, that list above is still what we would judge the alternative by.
None of this means Mastra or nothing. We pick up projects already running on something else, and we are useful in them quickly — the checklist travels, and the hard part was never the framework.
A system of record is neither new nor exotic. It is a database, and the application framework that owns it — and most businesses we work with already have one, usually holding the most correct data they possess. An agent does not change that arrangement. It reads from the record, proposes changes to it, and something with a schema decides whether those changes land. A prompt can be talked out of a rule; a schema cannot.
Our team has run Rails and Payload CMS at scale for years, so those are what we reach for when the record layer is ours to build. Rails where the domain is heavy and the business rules run deep; Payload where the same TypeScript types should carry from the database through to the front-end. Both give us the same things: collections defined once and generated to types, schema changes that ship as reviewable migrations, access control enforced on the record rather than described in a prompt, and an admin surface the people who own the data can actually use.
How the agent reaches any of it is a separate decision, and rarely a hard one: direct database queries where the record layer is ours, a REST or GraphQL API where it belongs to another team, RPC or a message queue where the call has to be asynchronous, and an MCP server where we want the access itself to be typed and permission-scoped. The agent does not care which — it gets a tool with a contract, and the contract is what we design.
We are not tied to either. Where a business already has its system of record, we build against it rather than around it — the point was never which framework holds the data, only that something other than the model owns the truth.
Retrieval gets you relevant text. It does not get you a defensible answer. Above the vector layer we keep an ontology — entities, predicates and claims — so an answer resolves to something that can be pointed at, disagreed with and corrected.
We reach for pgvector and the tooling around Postgres for most of what we build, because keeping the embeddings beside the records they describe removes a whole class of drift: one database, one backup, one access model, and no second system to keep in step. It is a default rather than a position — where the shape of the data genuinely wants a dedicated vector store or a graph database, we will use one.
It matters less than it sounds. Your knowledge already lives somewhere, and we can work against it where it is. The work is rarely moving the data; it is assembling the right context for the agent — deciding what it should see for a given question, in what order, with what permissions, and what it should be told when the honest answer is that the knowledge is not there.
Some agent work has to actually run something — process an uploaded file, execute code the model just wrote, drive a tool that was never designed to be called by a model. That needs somewhere isolated and disposable: not your infrastructure, not ours, and not a long-lived box accumulating state between runs.
We have run this on E2B, and Modal and Daytona solve the same problem with different tradeoffs around cold start, runtime limits and pricing. Which one suits a project is worth deciding deliberately, but we do not let the choice reach into the agent: providers register against an interface rather than being called directly, and swapping one is a configuration change rather than a rewrite.
The boundary is the part we actually care about. A sandbox that runs anything, holds credentials and talks to the open internet is not a boundary, whoever is hosting it.
We are not tied to a sandbox vendor, and we would not ask you to be. What we hold to is that executed code runs somewhere it can do no damage, and that revoking a capability never means rewriting the agent that used it.
An agent you cannot measure is one you cannot change. Ordinary tests do not help much here: the same input can produce different words every run, so asserting on strings either fails constantly or asserts nothing worth knowing. What has to be graded is the decision — which tool the agent called, with what arguments, in what order, and whether the answer it gave was actually supported by what it retrieved.
Two layers. Scorers run against live traffic and grade grounding rather than tone — hallucination, faithfulness, answer relevancy and tool-call accuracy — sampled by environment so development sees everything and production pays for a fraction of it. Underneath them, evaluation specs boot the actual agent against a seeded Postgres with pgvector and Redis behind it, and assert the decisions it made. No mocked model, no stubbed retrieval.
Those specs run in CI and keep every agent response as an artifact, so two runs can be compared rather than argued about. Traces and structured logs cover the rest: when a scorer drops, the run that caused it is still there to open.
This is what makes a model upgrade a measurement instead of a coin flip. The tooling is replaceable — the discipline is not, and it is the first thing we look for in a codebase we are asked to take over.
A chat box is the laziest possible interface to an agent. Our agents do not only answer in paragraphs — they return structured, typed results, and the product renders them with its own components: cards, comparisons, forms, a map, a filter the person can adjust and ask again from. Generative UI, but assembled from components we already own rather than markup a model invented. Prose is the thinnest layer, added last, over data that was already decided, and that one decision is what makes the surface interchangeable: the agent never knows what is drawing it.
{
"type": "options",
"items": [
{ "id": "evt_8814", "score": 0.92 },
{ "id": "evt_3120", "score": 0.87 },
{ "id": "evt_5507", "score": 0.81 }
],
"reason": "within 4km · tonight · seats left"
}On the web that means Next.js and React, or SvelteKit where the surface is content-heavy and should ship as static as possible. On phones it means the same payload arriving in a native view — Android and iOS drawing the result with platform components rather than a web view pretending to be an app.
None of that is a port. Because the agent emits data rather than formatted text, adding a surface is a rendering problem and nothing more: no second prompt, no parallel agent, no markdown being parsed on the far end and hoped over.
Code picks the options. The model only writes the words around them — and if you already have a front-end, that is the one we build into rather than replace.
An agent is a long-running, bursty, stateful thing that calls slow external services and occasionally costs real money per request. That is not a normal web workload, and pretending otherwise is where most agent projects meet production badly. What we deploy has to survive traffic it did not expect, fail without taking the product with it, and be cheap enough to leave running.
AWS where the workload is heavy or the compliance boundary is drawn tightly and everything has to sit inside it. Cloudflare where the surface should be close to the person using it — static front-ends, workers at the edge, and the parts that should cost nothing when idle. Mastra Cloud where an agent is the whole product and running the orchestration ourselves would be work without a payoff.
Most projects end up using more than one of these, and that is fine: the boundary between them is a deployment decision rather than an architectural one, because the agent, the records and the evaluation suite do not know or care which one is hosting them.
We deploy into your account, under your billing, with your names on the resources. Where a platform team already owns this, we hand them something that fits their pipeline rather than asking for an exception to it.
None of this is a reference architecture. It is how our own product works — a multi-tenant SaaS platform built on the same stack, where every tenant gets an isolated space, its own agent profiles and chat sessions, a knowledge layer with an ontology over its documents, and sandboxed execution metered against its own quota.
Running it ourselves is why the seven layers above are opinionated rather than balanced. Each one is a position we arrived at by having to operate it, upgrade it under real traffic, and answer for it when something went wrong.
Team Foundry is our software factory — what the team uses day in, day out to deliver projects. It accelerates the work and validates it: every change arrives with the checks it passed, the session that produced it, and a person accountable for it.
How Team Foundry worksWhether you are exploring an idea or improving a system already in use, we’ll help you decide what is worth doing next.
Questions before you book?
Read the FAQFollow along