Development

Giving an Agent a Read-Only Finance Tool First

Antonio Evans10 min read

A three-step staircase labeled Query, Draft, Write.
Key points

Key points

  1. 1Start the agent with a read-only finance tool so mistakes cost an inaccurate answer, not a bad journal entry.
  2. 2Collect three kinds of evidence before widening access: query accuracy against a known answer key, draft quality from human reviewers, and permission checks proven by denial tests.
  3. 3Add write actions one at a time, each behind human approval, an idempotency key and a tested rollback path.
  4. 4Instrument every tool call with tracing and structured errors from day one, because you cannot promote what you cannot audit.

Why reads first is a control decision, not a courtesy

A read-only agent can observe and summarize. A write-capable agent can create, modify, delete and trigger state changes in systems other teams depend on, which makes the permission a question about whether the agent may change production state at all, not about convenience (NHIMG). That reframing matters for finance because the riskiest failures are not obviously wrong. They are believable entries that survive review, propagate downstream and become the new source of truth.

Finance systems make this sharper than most domains. A wrong journal misstates one entity in one period and can be reversed, but a wrong account mapping misstates every period and every group report built on it (Keboola). Read-only scope keeps the agent on the reversible side of that line while you measure it.

There is also a measurement problem that read-only access solves cheaply. Employees report high rates of relying on AI output without checking it, so "looks right" is not evidence. The KPMG and University of Melbourne study of more than 48,000 people in 47 countries found that 66% of employees who use AI at work said they had relied on its output without evaluating its accuracy (Keboola). If your reviewers behave like that, a successful pilot proves very little. You need evidence that does not depend on reviewer attention: answer keys, denial tests and traces.

A useful decision rule for the whole program: promote a capability only when the cost of its worst plausible failure is smaller than the value of the work it removes. For reads, the worst case is a wrong answer a person can disregard. For a posted entry, the worst case is a restatement. That asymmetry is why the stages below are sequential rather than parallel.

Stage one: queries, and the evidence that a read tool is trustworthy

Stage one is a tool that answers questions over finance data and does nothing else. The design goal is narrow, typed tools rather than a broad query surface. Practitioner guidance is blunt about this: a tool like get_invoice_by_id(invoice_id) is far safer than a generic query_database(sql) wrapper that hands raw SQL execution to a model (MLflow). Narrow tools are also what make promotion measurable, because you can score each one separately.

Collect four pieces of evidence during stage one.

  1. An answer key. Take 50 to 100 real questions finance staff already answer by hand, and record the correct answers with the date, entity and reporting period they apply to. Re-run the set on every prompt or tool change. Volume matters less than coverage of edge cases: multi-entity consolidation, a reversed accrual, a period with a restatement.
  2. Query traces. Each tool call should record the tool name, the exact parameters, the rows or records returned, the latency and a trace identifier that ties it to the user request. Traces are what let you answer why and how something happened, not just what happened (Azure).
  3. Error rates by tool and by error class. Do not collapse every failure into "failed". A permission denial, a malformed parameter and a timeout mean different things and point at different fixes.
  4. User feedback with a reason code. A thumbs down without a category is noise. Ask reviewers to pick from a short list such as wrong period, wrong entity, wrong aggregation, missing exclusion.

Error responses deserve their own design. HTTP status codes cannot always convey enough to be helpful to a non-human consumer, which is exactly what an agent is. RFC 9457 defines a standard JSON problem detail so the agent receives a machine-readable type, title, detail and instance instead of a prose blob it must guess at (RFC 9457). In practice this changes agent behavior: a detail of "account 4400 has no balance in period 2024-11 for entity E02" lets the agent retry correctly or report the gap. Swagger's survey of common anti-patterns notes that inconsistent error formats raise integration time and cost, and that the problem gets worse with AI consumers, which need precision rather than variety (Swagger).

If you build on a framework, the plumbing may already exist. Spring Framework renders RFC 9457 responses from a ProblemDetail return value or a @ControllerAdvice handler, and sets the media type to application/problem+json during content negotiation (Spring). RFC 9457 obsoletes the earlier RFC 7807, so start from the current specification rather than the older one (RFC 7807).

A minimal read tool with a typed input and a structured failure looks like this:

def get_balance(entity_id: str, account: str, period: str) -> dict:
    rows = ledger.query(entity_id=entity_id, account=account, period=period)
    if not rows:
        return {"status": 404, "type": "https://api.example.com/problems/no-balance",
                "title": "No balance for account in period",
                "detail": f"account {account} has no posted balance for {entity_id} {period}"}
    return {"status": 200, "balance": sum(r.amount for r in rows), "rows": len(rows)}

The exit criterion for stage one is concrete: the answer key passes at the bar you set with finance, every failure has a class and an owner, and a sample of traces lets a reviewer reconstruct any answer from the data the tool actually read.

Stage two: drafts, where the agent proposes and never executes

Stage two lets the agent produce work product: a proposed journal, a suggested account mapping, a draft variance commentary. The rule is that the draft lands in a safe place, meaning a draft record, a parked journal, a staging table or a branch, never the ledger or production rules (Keboola). Read-only versus write-back is the distinction that decides whether AI reduces the workload or only describes it: a read-only tool that flags fifty exceptions leaves fifty jobs on the team's plate, while a draft-capable tool leaves fifty reviews (ChatFin).

Drafts are where you can measure judgment cheaply, because you compare a proposal against what a person would have done. Track four numbers per draft type: acceptance rate with no edit, acceptance with edits, rejection, and the size of the edit (line count or amount changed). A draft type that is accepted only after heavy edits is not ready for execution even if acceptance looks high.

Human-in-the-loop here has a specific meaning worth holding to. The agent proposes an action and pauses, a person reviews the context and approves or rejects, and the agent resumes only on approval (Permit). Frameworks support the pause in different ways; the comparison below is useful when choosing one.

ApproachHow the pause worksGood fit for a finance draft stage
LangGraph interruptinterrupt() pauses the graph mid-execution and waits for human inputLong-running agent graphs with several checkpoints
CrewAIhuman_input flag plus a human toolSmall multi-agent teams with role-based steps
HumanLayer@require_approval() decorator on the function that would actA single sensitive function such as posting an entry
Plain approval queueAgent writes a draft record, a workflow tool routes itTeams that already run maker-checker in an ERP

Source: Permit.

The failure mode to watch in stage two is approval fatigue. Evidence for an auditor in a human-in-the-loop control is one approval record per action; in a human-on-the-loop control it is sample results plus override and reversal rates (Keboola). If your approvers are clicking approve in under a few seconds on a routine basis, the gate is decorative and you should either add sampling or move the action to a lower-consequence mode.

The three stages side by side

Use this as a checklist before each promotion. The middle column is the evidence; the right column is what makes the evidence usable.

StageCapabilityEvidence to collect before promotingPermission check that proves the boundary
One: queriesRead data, answer questionsAnswer key pass rate on 50 to 100 known questions; traces with parameters and returned rows; error rate by tool and class; feedback reason codesAttempt a write and confirm a structured 403 that names the missing scope
Two: draftsPropose entries, mappings, commentaryAcceptance, edit, rejection rates per draft type; median edit size; time to decision; reviewer override reasonsAttempt to post directly and confirm the draft endpoint rejects it
Three: writesExecute approved actionsIdempotency replay test; rollback rehearsal on a copy; approval record completeness; reversal rate in productionAttempt to exceed the delegated amount limit and confirm refusal

The right column is the part teams skip. A permission you have not seen deny is a permission you have not tested. Run denial tests on a schedule, not once, and record the result alongside the passing tests so an auditor sees both.

Stage three: writes, one action at a time, with approval and rollback

Adding write capability changes the trust boundary, so scope each write to the smallest possible action set with explicit validation before commit (NHIMG). Before granting any write, answer four questions for that specific action: what exact system can change, what object type can change, what validation runs before commit, and what rollback path exists. A practical ordering is dry runs first, then staging writes, then low-value production writes below a delegated amount, then routine writes.

Two technical controls do most of the work.

Idempotency. Networks fail in ways that leave the caller unsure whether an operation ran. Stripe's design notes describe the three cases: the connection fails before the request arrives, the call fails midway, or the call succeeds but the response is lost (Stripe). A timeout means "I do not know what happened", not "it failed" (dev.to). Client-generated keys carried in an Idempotency-Key header let the server recognize a retry and return the stored result instead of repeating the side effect (Stripe API). The key must be created before the first request, not during a retry, and the stored response should include the status code and body so a duplicate returns exactly what the original returned (Zuplo).

Approval that executes exactly what was seen. One approval covers one action, and the system should confirm that the posting or job matches what was approved (Keboola). That means the approval record must contain the entry lines or configuration diff, the data and definition versions, and the exception flags, not a summary.

A server-side check for a draft-posting endpoint:

def post_entry(key, payload):
    row = keys.get(user=payload.user, key=key)          # atomic unique insert
    if row and row.status == "succeeded":
        return row.stored_response                       # replay, no second posting
    if row and row.payload != payload:
        return problem(422, "key reused for a different entry")
    if not approval.exists(key) or approval.diff(key) != payload.lines:
        return problem(403, "approved change does not match payload")
    result = erp.post(payload)
    keys.save(key, payload, result)
    return result

Two operational notes. First, an agent that appears read-only to users may still write through plugins or orchestration middleware, so audit indirect paths as well as direct ones (NHIMG). Second, permissions set during development tend to persist into production, so never grant a broad write scope because it is convenient for testing (MLflow).

Instrumenting all three stages without a rewrite

Instrumentation set up in stage one pays off twice, because promotion decisions rest on traces you already collect. Observability for agents extends the usual metrics, logs and traces with two additions: evaluations and governance, so you can assess whether the agent resolved intent and used tools well, and whether it operated within policy (Azure). The practical test of a good trace is simple: when a tool call causes an unintended change, the logs should tie the action to a specific invocation, its parameters and its permissions. Without that, the action appears as "something changed" with no attributable chain (Rubrik).

Versioning the tool contract is the other piece of insurance. Versioning lets you add or change behavior without breaking existing consumers, and it lets you retire an insecure version deliberately rather than by surprise. The common schemes are the version in the URI, in a custom header, or as a query parameter (Akamai). For an agent tool, header versioning keeps the URI stable while you test a stricter schema, and URI versioning makes the change obvious in a trace. Either works; what fails is changing a tool's schema silently while an agent holds a cached description of it.

If you are wiring several model providers and want tool-call telemetry in one place, Quanta Inference exposes text, image, video, audio and embedding models through one OpenAI-compatible key, which keeps the logging surface uniform when you swap the model behind the agent (https://quantaintelligence.ai/inference).

A worked example with numbers

Consider a mid-sized group with three entities on three ERPs. Suppose the read-only agent answers 400 finance questions a week and the answer key has 80 items. In week one the key passes 62 of 80. The failure breakdown shows 11 wrong period, 5 wrong entity, 2 aggregation errors. You fix period handling first because it accounts for more than half the misses, and by week three the key passes 78 of 80, with the two remaining misses on a restated entity that you flag as out of scope.

Stage two produces 300 draft accruals a week. Reviewers accept 180 with no edit, accept 96 with edits, and reject 24. Median edit size on the accepted-with-edits group is two lines, and those edits are almost always the same account, which tells you the mapping rule is wrong for one supplier, not that the agent reasons badly. After you fix the rule, the no-edit group rises to 260 and the rejection count falls to 9. That is the evidence that carries into stage three.

Stage three begins with one action, a draft accrual below a delegated amount that posts after approval. The replay test fires every new endpoint three times with the same key: one posting, two stored replays. The rollback rehearsal on a copy shows the reversal posts cleanly inside the period. Only then does the action go live, and the first two weeks are sampled at 20% rather than reviewed fully, using the on-the-loop evidence model (Keboola).

If the last step is building the approval UI and the audit view around this workflow, Quanta can take the description and build it with you (https://quantaintelligence.ai/).

FAQ

FAQ

How long should the read-only stage last?

Judge it by evidence, not by calendar. The stage can close when the answer key passes at your agreed bar, every failure class has an owner, and a reviewer can reconstruct a sample of answers from the traces alone. A small scope with a clean answer key usually gets there faster than a broad one.

Do we need both structured errors and traces?

They answer different questions. RFC 9457 problem details tell the agent what went wrong so it can recover or escalate, while traces tell your team what the agent read and why (RFC 9457). Without the first, the agent guesses. Without the second, you guess.

What is the smallest safe first write action?

A reversible one with a small, bounded effect, behind a single approval and one idempotency key. A draft accrual that a controller approves and that can be reversed in the same period is a better start than anything that changes mapping rules, because a wrong mapping misstates every period built on it (Keboola).

How do we stop approvals from becoming rubber stamps?

Track time to decision and override rates, and sample actions rather than reviewing all of them once volume is high. The supported evidence for an auditor then becomes sample results plus override and reversal rates instead of a per-action signature, which is honest about the level of oversight actually applied (Keboola).

Can an agent be read-only and still cause damage?

Yes, through indirect paths. An agent that looks read-only to users may write through plugins, indirect APIs or orchestration middleware, and write permissions are typically discovered as a problem only after data has already changed (NHIMG). Audit the whole call graph, including credentials the agent can reach, before you call a tool read-only.

Sources

Sources

  1. What happens when an AI agent is allowed write access instead of ...
  2. What Is Human-in-the-Loop AI in Finance? | Keboola
  3. AI Agent Tool Use Best Practices for Practitioners - MLflow
  4. Top 5 agent observability best practices for reliable AI
  5. RFC 9457: Problem Details for HTTP APIs
  6. Problem Details (RFC 9457): Doing API Errors Well - Swagger
  7. Error Responses :: Spring Framework
  8. RFC 7807: Problem Details for HTTP APIs
  9. Read-Only AI vs Write-Back Finance Agents: Why It Matters - ChatFin
  10. Human in the Loop AI Agents: Approval Workflows That Work
  11. Designing robust and predictable APIs with idempotency - Stripe
  12. Idempotency Keys - The Double-Charge Trap
  13. Idempotent requests | Stripe API Reference
  14. Implementing Idempotency Keys in REST APIs - Zuplo
  15. Agent Observability: How to Monitor AI Agents - Rubrik
  16. What Is API Versioning? - Akamai
About the author

About the author

Antonio Evans
Antonio Evans

Antonio Evans is the founder of Quanta Intelligence and a fractional CTO who builds HIPAA-ready AI systems for health and life-science companies. Before that he was technical co-founder of FriendsEAT for twenty years, CEO of Socialdraft, and a data engineer at IBM. Antonio lives with his dog Santino in Lisbon, Portugal.

All articles