Skip to main content
Back to blog

Why Bash Is Half My Tool Calls

6 min read

I dumped every tool call my AI made into a Postgres lake and counted them. 49,030 tool calls across 342 sessions. One tool accounts for nearly half.

It's not Read. It's not Edit. It's not some clever MCP integration I built. It's Bash23,979 calls, about 49% of everything.

That number stopped me. Not because it's surprising in hindsight, but because of what it says about how agents actually do work versus how we keep imagining they will.

The Distribution

Here's the top of the table, straight from the lake:

Tool Count
Bash 23,979
Read 6,828
Edit 5,337
store_memory 2,233
Write 1,646
TodoWrite 1,345
ToolSearch 1,090
Grep 783
Agent 652
query_memory 524

Bash is bigger than every other tool combined and then some. Read, the runner-up, is less than a third of it. The whole file-IO trio — Read, Edit, Write — adds up to 13,811, still short of Bash on its own.

This is the workload of someone building infrastructure. Homelab is 62% of my 145,481 telemetry rows, and infra work is shell work: kick off a build, check process state, tail a log, restart a daemon, run a migration, curl an endpoint to see if it 200s. Every one of those is a Bash call. The agent isn't reasoning in the abstract — it's poking a live system and reading what falls out.

The Shell Is the Universal Interface

Here's the part worth naming, because it's easy to miss when you're staring at a vendor's tool catalog.

Every model provider wants to sell you structured tools. Typed inputs, typed outputs, a clean schema, a nice function signature. And those tools are good — Read, Edit, and my memory layer are all in that table, all earning their place. But when an agent needs to do something nobody pre-built a tool for, it doesn't stop. It shells out.

That's what 49% Bash means. It means roughly half the time, the most efficient path to the next action was a command line, not a bespoke tool. Want to know what's running? ps, forge status, launchctl list. Want to know if the fix worked? Run it. Want to chain three operations? && them. The shell is the one interface that can reach anything on the machine without somebody first defining a tool for it.

I run a fleet of 32 autonomous workers — security review, code review, auto-fix, research scouts, an app generator that writes Swift and builds it in a loop. None of that runs on a clever proprietary protocol. It runs on processes the agent can start, inspect, and kill from a shell. The shell is the lowest common denominator, which is exactly why it's the highest-leverage one. It composes with everything because it predates everything.

The Lesson for Anyone Building Agent Tooling

If you're building tools for agents, the Bash number is a warning and a gift at the same time.

The warning: if your structured tool is even slightly more annoying than just running a command, the agent will route around it. I have a Grep tool — 783 calls. I have ToolSearch for loading MCP schemas — 1,090 calls. Useful, real, but rounding error next to Bash. Any tool that makes the agent think "I could just shell this" is a tool that's going to lose that fight a lot of the time. Friction is the whole game. A tool that's slower, narrower, or harder to compose than a shell command isn't a tool the agent will reach for — it's a tool the agent will reluctantly tolerate.

The gift: you don't have to pre-build a tool for everything. The instinct when you see an agent struggle is to add another typed integration. But the data says the agent already has a universal escape hatch, and it's using it constantly. The right move is often not "build a tool for this task" — it's "make the shell environment the agent operates in clean, safe, and well-instrumented." Good aliases. Predictable paths. Commands that fail loud instead of failing silent. Status commands that return parseable output. You're not building tools; you're building a habitat.

This is also why sandboxing matters more than tool design for safety. If half of agent activity is arbitrary shell, your safety story can't live in the schema of individual tools — it has to live at the boundary of what the shell can touch. Lock the blast radius, not the function signature. The structured tools were never where the risk lived.

What I'd Tell My Earlier Self

When I started building agent infrastructure, I over-indexed on tools. I thought a mature agent system was a rich catalog of clean, typed integrations, and that Bash was the crude fallback you'd grow out of as the catalog filled in.

The lake says the opposite. The catalog grew. The MCP servers multiplied. My memory layer, my worker runtime, my code-intelligence tools all came online. And Bash stayed at half. It didn't shrink as I got more sophisticated. It held, because sophistication didn't change the underlying truth: the work is "do a thing to a real system and look at what happened," and the shell is the shortest distance between intent and that loop.

The agents that feel capable aren't the ones with the most tools. They're the ones that can drop to a shell, run something, read the output, and adjust — fast, in a tight loop, without ceremony. The tool catalog is the icing. The command line is the cake.

23,979 Bash calls is what real agentic work looks like once you stop watching the demo and start reading the receipts. It's not glamorous. It's ps and curl and && and grep, ten thousand times over. But that unglamorous loop — act on the system, read the result, act again — is the actual engine. Everything else is scaffolding around it.

If you're building agent tooling and want to compare notes on where the structured-tool line actually falls, I'm at reed@grainlabs.io.