Pranshu Pandey← all writing
20 February 20262 min read

An unattended shell with good grammar

The moment you hand an agent real tools, you have shipped something with opinions and access.

An agent that can only talk is a demo. An agent that can read your database, call internal services and run code is genuinely useful — and is also production software whose control flow is being decided, token by token, by a model. Both are true simultaneously, and the second is where nearly all the engineering lives.

Tools are an API for a very literal reader

A tool definition gets read by something with no context, no institutional memory, and a powerful bias toward the first plausible option. Design accordingly:

  • Name by intent, not implementation. `find_customer` beats `query_users_table`.
  • Put the constraints in the description. If a date must be ISO, that is where it says so.
  • Few well-shaped tools over many overlapping ones — ambiguity gets resolved by guessing.
  • Write errors that explain what to do differently. Something will read them and try again.

A protocol like MCP earns its keep here by making the tool layer a real boundary with a schema, rather than a pile of function signatures injected into a prompt. The same tools then work across clients, and you can test them without a model anywhere near the loop.

Now assume it will do the worst available thing

Given tools, an agent will eventually call the destructive one at the wrong moment. Not from malice — from a plausible-looking sequence of tokens that led there. Design as though that is certain, because across enough runs it is.

python@tool(scopes={"reports:read"}, timeout=10, dry_run_supported=True)
async def build_report(ctx: Ctx, spec: ReportSpec) -> Report:
    # the tool cannot widen its own permissions; ctx is issued per-session
    return await reports.build(spec, actor=ctx.actor)

The controls that matter are unglamorous: per-session scopes instead of ambient credentials, timeouts on everything, allow-lists for network egress, and a hard wall between tools that read and tools that write. Anything irreversible gets a confirmation owned by a human, not by the model.

Sandboxes are for the code path

The moment an agent can execute user-defined workflows you are running untrusted code, and it deserves exactly the treatment untrusted code has always deserved: isolated runtime, no ambient filesystem, no implicit credentials, explicit resource limits. This is a solved problem everywhere else. The novelty of AI does not buy an exemption.

Capability without a boundary is not an agent. It is a shell with nobody watching and unusually good grammar.