Fixed tool list
The agent can only call what we wrote. Nothing is discovered at runtime, so the list of things it can do is the list you reviewed.
An agent here is not a chatbot. It is a loop that reads your data, decides, and acts through a fixed set of tools, with a person at the gate for anything it cannot undo. You see every step. You keep control.
And what it is not
Definition
So we define it before we use it. This is what you get when we say agent, and what you do not.
An agent is a program that runs a language model in a loop. Each turn, the model reads the state of the task, picks one tool from a list we wrote, and calls it. The tool runs real code against your systems and returns a result. The model reads that result and picks the next tool.
The loop ends when the task is done or when a rule says stop. A rule can be a budget, a failed check, or a step that needs a person. The record of every turn is the audit trail.
The rules that make it safe
The agent can only call what we wrote. Nothing is discovered at runtime, so the list of things it can do is the list you reviewed.
Each tool carries its own credential with the narrowest scope that does the job. The agent never holds an admin token, so a bad decision cannot reach past the tool that made it.
The plan is formed from reads. Writes come last, and there are few of them.
Anything it cannot undo waits for a person: money, mail to a customer, a deletion. The gate is a tool call like any other, so it is logged like any other.
Every write carries a key, so a retry never doubles an order. The same call twice has the same result as once.
The same agent runs with its writes stubbed. You read the trace before it touches production.
A fixed set of cases with known right answers. The agent runs them on every change, and a drop blocks the release.
Tokens, tool calls, wall clock, and dollars, each with a limit per run. When one is reached the run stops, and a person reads why.
Every run is a readable record. What it saw, what it chose, what it did, what it cost.
Illustrative
This is what one run looks like in the audit log. The case is invented. The shape is real.
Reads the case.
{ id: "00041822", account: "ACC-7731", opened: "09:12",
subject: "Customer reports a duplicate charge on invoice INV-20418." }Reads the account.
{ id: "ACC-7731", name: "Northline Dental", plan: "Standard",
billing_contact: "Dana R." }Lists the charges on the account for the last 30 days.
[ { charge: "ch_5f21", invoice: "INV-20418", amount: 1240.00, at: "09:04:11" },
{ charge: "ch_5f3c", invoice: "INV-20418", amount: 1240.00, at: "09:07:38" } ]Looks up the refund policy for duplicate charges.
"Duplicate charges within 24h: refund without review up to $500. Above $500, approval required."
Two charges of $1,240.00 on one invoice, three minutes apart. That is a duplicate. The amount is above $500, so the refund needs a person.
duplicate: true amount: 1240.00 threshold: 500.00 next: approval
Drafts the reply to the customer. Nothing is sent yet.
"Hi Dana, you were charged twice for INV-20418 on Oct 14. We are refunding the second charge of $1,240.00. It should reach your statement within 5 business days. Sorry for the trouble."
Asks a person to approve the refund. The evidence goes with the request.
{ action: "billing.refund", charge: "ch_5f3c", amount: 1240.00,
evidence: ["ch_5f21", "ch_5f3c", "policy:duplicate-24h"], draft: "reply.draft#1" }
-> { request: "APR-2291", status: "pending", assigned_to: "Billing lead" }Waiting for approval. Assigned to: Billing lead.
Approved by J. Ortiz, 14 min
Issues the refund. The key makes a retry safe.
{ charge: "ch_5f3c", amount: 1240.00, approval: "APR-2291",
idempotency_key: "refund:ch_5f3c:APR-2291" }
-> { refund: "re_9a04", status: "succeeded" }Posts the reply to the case and sets it to Solved.
{ case: "00041822", body: "reply.draft#1", status: "Solved" }
-> { ok: true, posted: "09:28" }And where they do not
The work has a clear done state, a bounded set of systems, a high volume of similar cases, and a cost of error you can state.
One of these is true. We say so in the survey, and we build the cheaper thing.
Handed over
Source, in your repository, in a language your team reads.
Each tool is a small, tested function with its own credential and its own log line.
The cases, the expected results, and the runner, wired into your CI.
How to pause it, how to roll it back, how to read a trace, who gets paged.
Runs, cost, approvals waiting, failures, by day.
Answered plainly
The one that passes the evals at the lowest cost. The agent is written so the model is a setting, not a dependency, and we change it when a cheaper one passes.
We have no vendor to sell you.
No. It reads your data at run time through the tools, and nothing is kept to train on.
The model does see what a tool returns, for the length of that call, so the provider matters. We write the agent so the provider is your choice, including one that runs inside your own tenant.
The fixed tool list, the approval gate, the budgets, and the evals. It cannot call a tool we did not write. It cannot spend past the limit. It cannot make a change it cannot undo without a person saying yes.
Beside it, under its rules. It runs as a service the org calls, or on a schedule. The tools talk to the org through the API with a dedicated integration user, so the org's permissions apply and every change is on the record under the agent's own name.
The dry run is the first thing we build. A small agent with a handful of tools reaches a supervised production run in a few short cycles, and you see the trace at every one.
We set a budget per run before it goes live, and the dashboard shows the spend by day. The dry run gives you the first real number before anything touches production.
We show you the number rather than promise one.
Have a case in mind?
Name the systems it touches and how often it runs. We say whether an agent fits, or whether a plain flow would do it for less.