Productionizing AI coding agents — parsing, command knowledge, policy enforcement, and hooks for safe agent tool execution.
prodagent is a Rust workspace that gates AI coding agent tool use through a layered security pipeline. A shell command enters as raw text, gets parsed into a structured AST, classified by a knowledge base of known commands, and evaluated against a configurable policy engine that returns allow/ask/deny decisions. The entire stack is fail-closed: unknown commands require confirmation, compound commands take the strictest segment, and parse errors never produce an allow.
The primary binary is tool-gate, a Claude Code PreToolUse hook that evaluates Bash commands through this pipeline and responds with permission decisions.
| Crate | Type | Purpose |
|---|---|---|
prodagent-types |
lib | Shared foundation types — Word, WrapperSpec, SubcommandPattern, compiled default configs |
agent-shell-parser |
lib | Tree-sitter-bash parsing — shell tokenization, compound commands, substitutions, wrapper resolution |
agent-command-knowledge |
lib | Command taxonomy — effect classification, subcommand maps, flag schemas, env gates, path specs |
prodagent-policy |
lib | Policy engine — maps classified commands to Allow/Ask/Deny decisions with configurable rules |
prodagent-config |
lib | Three-tier config cascade (defaults / user / project) via figment with monotonicity enforcement |
prodagent-tool-gate |
bin | PreToolUse hook binary — the main entry point for gating agent Bash commands |
agent-jj |
bin | Claude Code hooks for jj-colocated repos — git guard, workspace creation, cleanup |
prodagent-proofs |
lib | Kani formal verification harnesses — 23 proofs for policy security invariants |
prodagent-types
|
agent-shell-parser
|
agent-command-knowledge
|
prodagent-policy ---------- prodagent-proofs
|
prodagent-config
|
prodagent-tool-gate
agent-jj (depends on agent-shell-parser only)
Five-layer separation:
-
Types (
prodagent-types) — Foundational types shared across the workspace.Wordtokens with domain-specific classification,WrapperSpecdefinitions for transparent command wrappers (sudo, env, nohup, etc.),SubcommandPatternvalidated newtypes, and compiled default wrapper specs. Usescamino::Utf8PathBuffor path types throughout. -
Parsing (
agent-shell-parser) — Tokenize shell commands via tree-sitter-bash AST walks into structured segments. Handles compound commands (&&,||,;,|), command substitutions ($(...)vs$((...))), wrapper chains (sudo env git commit), redirections, and environment assignments with substitution tracking. Pure AST-based — no byte-level scanning. -
Knowledge (
agent-command-knowledge) — Classify commands by effect (ReadOnly < Mutating < Unknown). Embedded defaults cover git, cargo, gh, kubectl, and 50+ common commands with subcommand-level granularity, flag schemas, affected path specs, and environment gates. User config extends/overrides viaKnowledgeOverlay. -
Policy (
prodagent-policy) — The security-critical decision layer. Maps classified command effects toAllow < Ask < Denydecisions using configurable rules. Handles wrapper escalation, compound command aggregation (strictest wins), escalation flags, redirect detection, env gate evaluation with opaque value handling, and affected path extraction. Configurable opaque env ceiling controls the decision for unknown environment values (default: Ask). -
Configuration (
prodagent-config) — Three-tier cascade via figment:- Defaults — compiled into the binary
- User —
~/.config/prodagent/config.toml - Project —
.prodagent/config.toml(per-repo)
A monotonicity invariant ensures project config can only tighten policy (escalate toward Deny), never weaken it. Validated at load time.
The Effect enum is ordered fail-closed: Unknown is the most restrictive, so aggregation via max never underestimates risk.
The prodagent-proofs crate contains 23 Kani proof harnesses verifying security invariants:
- Policy monotonicity — compound command evaluation can only escalate, never relax
- Parse error fail-closed — parser errors never produce an Allow decision
- Opaque env ceiling — unknown environment values respect the configured ceiling
- Decision ordering — reflexivity, antisymmetry, transitivity, and security ordering of the decision lattice
- Deny absorption — Deny survives all aggregation operations
- Rust toolchain >= 1.93
- cargo-nextest for running tests
- jj-cli >= 0.40.0 (for
agent-jjonly) - Kani (for proof harnesses only)
cargo install --path crates/prodagent-tool-gatecargo install --path crates/agent-jjjust installCreate ~/.config/prodagent/config.toml to customize policy:
[policy.defaults]
read_only = "allow" # default
mutating = "ask" # default
unknown = "ask" # defaultPer-repo overrides go in .prodagent/config.toml (can only tighten, not relax).
Use tool-gate --dump-config to inspect the merged three-tier configuration. Use tool-gate --dump-ast <command> to see how a command is parsed.
No configuration needed — it detects jj-colocated repos automatically.
Add to ~/.claude/settings.json:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{ "type": "command", "command": "tool-gate" }
]
}
]
}
}Optional flags: --escalate-deny converts all Deny decisions to Ask (debugging escape hatch).
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{ "type": "command", "command": "agent-jj guard" }
]
}
],
"WorktreeCreate": [
{
"hooks": [
{ "type": "command", "command": "agent-jj workspace" }
]
}
],
"WorktreeRemove": [
{
"hooks": [
{ "type": "command", "command": "agent-jj cleanup" }
]
}
]
}
}just check # fmt + clippy + nextest
just test # nextest only
just fmt # formatTests use rstest for parameterized cases and proptest for property-based testing.
MIT OR Apache-2.0