◀ Title screen◀ All entries
  • 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.

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.

Building something like this? Send a message ▶