A weir is a low structure in a river that controls the flow without stopping it. Water still moves. It just moves at the right level, in the right direction. That’s the design goal.
Why this exists
AI is good at what it does. But when I gave it shell access, even controlled shell access, it does what any capable agent does with a shell. It solves problems by routing around constraints.
That’s not a failure of the agent. It’s a failure of the interface. A shell is an invitation to be clever. When you hand an agent /bin/sh -c and a problem, it will find a path. It doesn’t matter if the path is fragile, non-portable, or violates boundaries you thought were implicit. The shell doesn’t encode boundaries. It encodes capability.
I needed something that encodes boundaries and tells the agent where to go instead.
Shell was never the right automation tool
This isn’t really an AI insight. Shell has been the wrong tool for automation for a long time. We’ve had better options for years: typed configuration languages, orchestration frameworks, structured APIs. Shell is great for interactive use, for exploring and experimenting, for one-off operations where a human provides the judgment that the tool lacks. That’s what it was designed for.
Automation wants something different. Automation wants predictable interfaces, typed inputs, validated outputs, observable execution. Shell gives you a string that might do anything. That’s fine when the operator understands the context. It’s not fine at scale, and it’s not fine when the operator is an agent making dozens of calls per session.
AI is making this more visible because agents hit the problem harder and faster than scripts do. An agent with shell access generates novel command strings from context, strings nobody reviewed, nobody tested, nobody would have written by hand. The failure modes aren’t the same as shell scripts failing. They’re weirder, less predictable, and harder to audit after the fact.
The answer isn’t to restrict the agent. The answer is to give it better tools.
The destination: deterministic code, not novel commands
The point of constraining shell access isn’t just safety. It’s to push the agent toward a fundamentally better pattern: writing typed, validated, deterministic code that executes through a state machine.
When an agent generates a shell command, that command is novel every time. Nobody reviewed it, nobody tested it, it exists for one invocation and then it’s gone. When an agent writes a swamp extension instead, it produces something durable: a typed model with input schemas, output schemas, pre-flight checks, and versioned data. That extension runs the same way every time. It can be tested. It can be shared. Other agents can use it without reinventing it.
The shift is from “the agent executes” to “the agent authors infrastructure that then executes deterministically.” The AI’s value moves from the runtime into the build phase. That’s the same insight the swamp community arrived at from the other direction: deterministic beneath stochastic, the agent’s creativity captured in validated tooling rather than spent on throwaway commands.
Weir’s constraints make this destination obvious by making the alternative uncomfortable. You can run a few read commands through the whitelist. Anything more complex, anything you need repeatedly, belongs in an extension. The tool guides you there by saying no to everything else.
Why MCP (honestly)
MCP has earned a stigma, and understandably so. The protocol launched and immediately attracted a wave of poorly-built tools. Thin wrappers over APIs that didn’t need wrapping, with bad security boundaries and no clear value over calling the API directly. The spread of bad MCP tools has been unfortunate, and it’s colored how people think about the protocol itself.
But the protocol itself is fine. It’s JSON-RPC over stdio. It describes tools with schemas, lets the agent know what exists and how to use it. That part works well, genuinely well. The agent reads the schema, understands the constraints, knows what parameters are required. The protocol’s design is better than its reputation suggests.
I didn’t choose MCP because I’m enthusiastic about the ecosystem. I chose it because most agent CLI tools, claude-code being the prime example, don’t let you specify a custom shell for the agent. If I could have said “use this binary instead of /bin/sh for command execution,” that would have been enough. But that option doesn’t exist. MCP was the path that let me control what the agent can reach.
It turned out to be more useful than I expected. The tool descriptions are the agent’s first contact with the interface. They teach it what’s available and how to use it before it makes a single call. That’s valuable in a way a shell prompt never is.
No shell, not a shell
Weir doesn’t invoke a shell. At all. When the agent sends echo hello | cat, weir tokenizes that into echo with arguments ["hello", "|", "cat"]. The pipe is a literal string, not an operator. Shell metacharacters have no special meaning because there’s no shell to interpret them.
This matters because the alternative, pattern-matching an allowlist against shell syntax, is a parser-differential problem. Claude Code does this reasonably well. It splits on &&, ||, ;, | and validates each subcommand independently. But you’re always one edge case away from a bypass. A new shell feature, an unusual quoting trick, a heredoc. The surface area of shell syntax is enormous and adversarial.
Weir sidesteps it entirely. No shell, no shell operators, no parsing ambiguity. The command is tokenized, the basename is checked against the whitelist, and exec runs the binary directly. The security boundary is structural rather than pattern-matched.
What weir does
Weir is an MCP server. It exposes two real tools, run and find, and one honeypot.
run executes commands, but only commands on an explicit whitelist, and optionally only specific subcommands of those commands. You can whitelist git but restrict it to diff, log, and status. The agent can read but can’t write. The boundary is in the tool, not in a prompt instruction you hope the model follows.
find is a built-in, read-only filesystem query. No -exec, no -delete, no shell evaluation. Pure query. It exists so agents can explore the filesystem without needing shell access or a real find in the whitelist.
Everything else is denied. But the denial isn’t a dead end. It’s a redirect.
Guided denial
When the agent hits a wall, weir tells it where to go.
If a command isn’t whitelisted, the error says “try swamp extension search <thing>”. If a subcommand is restricted, the error names what’s allowed and suggests the extension registry. If the agent reaches for shell_exec, the honeypot catches it and asks the agent to explain what it needed.
That last one is the feedback loop. The agent’s explanation of what it wanted gets logged. Over time, those logs become a roadmap for what structured tooling is missing.
Why swamp
Swamp is where recurring capability lives. When an agent needs something regularly, like git operations across repos, forge API access, or infrastructure queries, that capability should be a typed model with schema validation. Not a shell command the agent constructs from memory.
The pattern is simple. Weir provides the escape hatch: a few read commands, a build tool, search. Swamp extensions provide the real surface area. If you find yourself wanting to add more commands to weir’s whitelist, that’s the signal. The work belongs in a swamp extension, not a longer allowlist.
Tonight my agent needed git operations in a repo that wasn’t the working directory. Weir’s git was restricted to read-only subcommands from the server’s cwd. The agent flailed for ten minutes trying workarounds. Then it searched the swamp extension registry, found @twonines/git-workspace, pulled it, and had typed status, diff, commit, and push operations across any repo in thirty seconds.
The constraint worked. Not by blocking the agent, but by guiding it to better tooling that already existed.
Metrics
Weir records every tool invocation to daily JSONL files. Command, allowed or denied, duration, output lines, filters applied, exit codes. The goal isn’t surveillance. It’s understanding what agents actually do so the tooling can improve.
We’re collecting this data and already learning from it. The agent hit the git subcommand restriction five times before finding the right path. With better messaging, which we added the same night based on what the metrics showed, that should drop to one. The data told us where the friction was. The fix was immediate.
The right shape for metrics, what to collect, what to surface, how to act on it, is still forming. But the principle is clear: the tool should improve by watching itself be used.
The honeypot
Weir registers a tool called shell_exec that looks exactly like unrestricted shell access. Full description, proper schema, everything an agent expects from a shell tool. It does nothing except capture what the agent tried to run and ask it to explain what it needed.
Agents trained on tool-use datasets have shell_exec in their muscle memory. They’ll reach for it before reading other tools’ descriptions carefully. The honeypot catches that reflex and redirects it, while logging what the agent actually needed. It’s research disguised as an attack surface.
The name
A weir doesn’t dam the river. It doesn’t stop flow. It sets the level and shapes the direction. Water still moves, just where it should.
That’s what I want from AI tooling. Not restriction for its own sake. Not unlimited access where every action is a risk. Just clear boundaries that carry enough information to make the right path obvious.
The constraint is the architecture.
Weir is open source. Around 800 lines of Go with no dependencies beyond the standard library.