← Our services Agentic AI · technically

Agentic AI is many things working together, and most of them are engineering.

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.

The stack

What we build on.

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.

LayerWhat we use
Agent frameworkMastraLangChain
Systems of recordPayload CMSRuby on Rails
KnowledgeVector databasesGraph databasesClassifiersOntology builders
Remote sandboxesE2BModalDaytona
Agent EvalsMastra evalsLangChain agent evalsObservability traces
FrontendNext.jsReactAI SDKSvelteKitAndroidiOS
DeploymentsAWSCloudflareMastra

Agent framework

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.

What we expect from an agent framework
  • Typed tools with schemas, so a call is validated rather than hoped for
  • Workflows as a first-class thing, not prompt chaining by another name
  • Memory scoped to a thread and a resource, persisted where we choose
  • Delegation to sub-agents without hand-rolling an orchestrator
  • The model swappable without rewriting the agent around it
  • Every step inspectable — real traces, not print statements
  • Evaluation in the same toolchain as the agent, not a side project
  • A programming language our team is already fluent in
  • An ecosystem that supports rich front-ends, not just a chat box
  • No claim on our data: it runs on our infrastructure, against our database
Why Mastra fits us in practice

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.

Systems of record

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.

What we run, and why

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.

Knowledge

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.

  • Vector search inside the same database as the records pgvector
  • Entities, predicates and claims above the chunks OntologyClaims
  • Reconciliation runs that resolve conflicting claims Reconciliation runs
  • File extraction with ingestion held for review ExtractionReview queue
  • Open questions recorded rather than guessed at Open questions
  • A curated corpus behind retrieval and evaluation Query sets
On the choice of store

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.

Remote sandboxes

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.

  • One sandbox per execution, destroyed when it ends Ephemeral runs
  • Credentials held outside and injected at the boundary Scoped credentials
  • Filesystem and network limited to what the task needs Egress rules
  • Quota and metering before cost becomes a surprise Quota
  • Whatever comes back treated as untrusted until validated Validation
Vendors, behind a contract

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.

Agent Evals

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.

What we expect from agent evaluation
  • Graded on the decision, not the phrasing
  • Run against the real agent and a real database, not mocks
  • Comparable between two runs, so a change shows up as a difference
  • Cheap enough to run often, and sampled where it is not
  • Live traffic scored, not only a fixture set
  • Failures readable by a person, not just a red tick
  • Living in the same repository as the agent it grades
What we run

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.

Frontend

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.

What the agent emits
{
  "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"
}
What the person sees
  • Tonight, 8:40pm PVR Phoenix · 3.1km · 6 seats
  • Tonight, 9:15pm INOX Nexus · 3.8km · 12 seats
  • Tomorrow, 7:00pm Cinepolis · 2.4km · 21 seats
  • Structured, typed results rather than free text ZodStructured output
  • Rendered by product components, not markdown ReactAI SDK
  • Streamed into a live interface as it resolves Streaming
  • Every result traceable to the record behind it Record refs
  • The same payload rendered natively on phones AndroidiOS
Web and native, one payload

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.

Deployments

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.

What we expect from a deployment target
  • Long-running requests without an arbitrary gateway timeout
  • Scale to zero when nothing is happening, and out again when it is
  • Background and scheduled work as a first-class thing, not a cron hack
  • Secrets and config per environment, never in the repository
  • Logs, traces and cost visible per run, not per month
  • A rollback that takes a minute and not a redeploy of everything
  • Regions we can choose, when the data is not allowed to leave one
What we deploy on

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.

Where this comes from

We run a multi-tenant agent platform of our own.

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.

What we have built on it
How we work

An AI-native team, with a factory behind it.

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 works
Factory

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.

Prefer to start async?

hello@thoughtfulrobots.ai

Hyderabad · Remote

Questions before you book?

Read the FAQ

Follow along

START A CONVERSATION

Tell us about the work.

A few lines about your product and where AI might belong. We usually reply within one business day.