# An agent that's safe on a live store

By [Anubhav Choubey](https://anubhavchoubey.com/) · AI & Agents · 2026-11-04 · 4 min read

> How I built an in-dashboard AI agent for live stores. Read-only analysis enforced in the pod, writes held for approval, and a memory nobody can forge.

```stats
Read-only by control, not by prompt · writes wait for a human · per-store memory · labels from verified tokens
```

## The problem

An AI agent inside a store's dashboard is useful because it can reach real things: sales data, pages, settings, connected tools. That is also what makes it dangerous. The store is live. A wrong write is not a failed test, it is a broken checkout.

The usual answer is a line in the prompt: "you are read-only, ask before changing anything". A prompt is a request, not a control. I wanted every safety promise the agent makes to be backed by something the model cannot talk its way past.

## Read-only, enforced in the pod

The agent has two modes. Analysis answers questions. The builder changes code. Analysis turns run a coding agent in the same kind of pod as the builder, so "read-only" had to mean the pod itself could not write.

No single switch was enough. Turning off the shell still left the patch tool able to edit files. A read-only sandbox blocked writes but also cancelled the MCP calls analysis depends on. Only the combination closed every write path and still let read tools through: shell off, sandbox read-only, MCP calls allowed explicitly.

The service that starts the turn decides the mode itself, and it fails closed. A request with no known mode is refused, not quietly run as read-only, so an old client can never land a builder request in the wrong place.

```callout tip
If a safety rule lives only in the prompt, write down which component would stop the model if it ignored it. If the answer is "nothing", it is not a rule yet.
```

## Writes wait for a human

For tool calls, I built a per-user agent token. It carries that user's own permissions, cut down to a fixed agent ceiling, plus a marker saying "this call comes from the agent". The MCP edge strips any client-sent marker and stamps it from the verified token.

Every tool call is classified, and the classification fails closed. A tool is a read only if it explicitly says so. An unannotated tool is a write. A tool from a merchant's own external server is never a read, whatever it claims.

```text
agent ──▶ MCP edge ──read──▶ tool
              │
            write
              ▼
   sealed + pending ──▶ approval card ──approve──▶ re-check ──▶ run once
```

A held write is sealed with its exact arguments and recorded as pending, and the downstream tool never sees it. When a permitted user approves, the edge re-checks the store, the requester and a hash of the arguments, then sends it exactly once. A timeout after sending becomes "outcome unknown", which is never retried blindly. Two charges for one click is worse than one honest "we don't know".

## From analysis into the builder

Analysis often ends with "you should change this". A "Build this" button creates a new builder chat, as the user, through the builder's own routes. An idempotency key means one click makes at most one chat, even when requests race. A review step shows the full request and a plain warning that the builder can change the store.

## A read-only scan that raises findings

A scheduled scan looks for things worth a merchant's attention: a sales drop on one device, a cache problem, a deploy that lined up with a change in traffic. It runs under its own identity with no write permission, and the MCP edge refuses any non-read call from it before it reaches a tool.

It reads only from an analytics replica, never the primary. When the data looks untrustworthy, for example order stats lagging behind, a detector stops and leaves open findings alone instead of marking them resolved. When a user asks "why?" about a finding, the follow-up chat gets a token limited to viewing analytics, even for a store owner.

## A memory per store, with a label nobody can forge

Agents forget. A store's history of decisions should not. I built a memory MCP that lets AI clients record, search, supersede and retract decisions and constraints about a store, next to a timeline of deploys.

- **Content is immutable.** A change is a supersede in one transaction, so two concurrent edits cannot both win.
- **Identity is read from each request**, never cached per session, so a long-lived connection cannot leak across stores.
- **The client label comes from the verified token.** The edge drops any client-sent label and stamps the real one, so a record always shows which client wrote it.

## What I'd do differently

- **Build the classification first.** Holds, scans and memory all depend on "is this a read?". I would build and test that one function before anything else.
- **Check both regions on day one.** A label stripped in one region but not the other is not stripped. The pre-flight that checks both should come with the feature, not after it.

## Takeaway

Treat the prompt as a hint and the infrastructure as the rule. Read-only is a property of the pod, approval is a property of the MCP edge, and identity comes from a verified token. Then the agent can be as helpful as it likes, because the dangerous parts don't depend on its good behaviour.
