Hard limits for AI agents: phases, transactions, budget gates and proof traces. One file you copy into your project.
Quick start · How it works · Rules · Integrations · Comparison · Docs
🌐 Visual explainer · 🎮 Interactive demo · 📄 Full article
Your agent just mass-emailed 10,000 customers with a hallucinated discount.
It had the tool. It had the permission. Nobody told it to stop.
Agent frameworks hand out tools and optimize for capability. Permission is left to the system prompt, and a system prompt is a suggestion: it can be ignored, overridden, or hallucinated around. When the model holds DELETE FROM users as a callable function, "please be careful" is not a control.
| Gap in your framework | What goes wrong |
|---|---|
| No lifecycle | The agent writes before it finishes reading |
| No transactions | Step 2 of 3 fails, step 1 sticks, your data is inconsistent |
| No budget control | You learn about the 400-call loop from the invoice |
| No audit trail | Logs say what happened, never why it was allowed |
Shape closes all four from outside the agent. What you get:
- 🚦 Phases: EXPLORE and DECIDE are read only. A write tool in the wrong phase raises
PhaseErrorinstead of logging a warning. - ⛓️ Transactions:
commit()buffers compensations. When step 3 fails, steps 1 and 2 roll back in reverse order. - 💸 Budget gates: 50% spent sends a degrade signal, 75% blocks commits and forces re-evaluation, 100% stops everything.
- 📈 Spike detection: an optional cost-per-second threshold pauses the agent that discovers infinite loops before your invoice does.
- 📜 Rule DSL:
BLOCK send_email WHEN phase IS NOT commitreads like the policy it implements. No Cedar, no Rego, no policy server. - 🙋 Approval hooks:
REQUIRE APPROVAL FOR * WHEN tool IS irreversibleroutes the call to a human callback before it runs. - 🧾 Proof traces: every call records the phase check, budget state and rule matches that allowed it. Denials get traces too.
- 🪶 Zero dependencies: 509 lines of stdlib Python in one file. Auditable in one sitting.
The longer argument, with the industry context, is in CONCEPT.md.
Requires Python 3 and nothing else:
shape.pyimports onlyre,time,dataclasses,datetime,enumandtyping.
curl -LO https://raw.githubusercontent.com/vidanov/shape/main/shape.py
# or clone and: cp shape.py your_project/No package manager involved. Vendoring one file is the entire supply chain.
from shape import Agent, ToolEffect
agent = Agent("support-bot", budget=5.00)
agent.tool("lookup_customer", effect=ToolEffect.READ, fn=lookup_fn)
agent.tool("update_record", effect=ToolEffect.REVERSIBLE, fn=update_fn, compensation=undo_fn)
agent.tool("send_email", effect=ToolEffect.IRREVERSIBLE, fn=email_fn)
agent.rules("""
BLOCK send_email WHEN phase IS NOT commit
BLOCK * WHEN budget ABOVE 90%
""")
# EXPLORE: read tools only. Calling send_email here raises PhaseError.
with agent.explore() as ctx:
customer = ctx.call("lookup_customer", id="C-1234")
# COMMIT: writes unlock, wrapped in a transaction.
with agent.commit() as tx:
tx.call("update_record", cost=0.01, id="C-1234", status="welcomed")
tx.call("send_email", cost=0.10, to=customer["email"], template="welcome")
# if send_email raises, update_record is compensated automaticallyRun the shipped demos to see the traces for yourself:
python3 examples/customer_service.py # refund flow: 5 governed calls, 1 transaction
python3 examples/manufacturing.py # PLC writes behind approval gates
python3 -m pytest test_shape.py -q # 62 tests- Every
ctx.call()andtx.call()passes the same gate, in order: phase check, budget gate, rule DSL. The outcome lands in a proof trace whether the call ran or was blocked. commit()opens a transaction. Calls with side effects buffer their compensation functions, and an exception inside the block aborts and runs them in reverse order. Compensation is best effort: a failing compensation is logged, not raised.- Cost is recorded after a call executes, like a credit card: the purchase that maxes you out goes through, the next one declines. Fixed costs come from
cost=on the call, real costs from acost_fnthat reads the tool's response. - Blocked is loud. A denied call raises
RuleViolation(orApprovalRequired), so the calling code cannot quietly continue down a path governance already refused.
Concept-by-concept docs: phases · effects · transactions · budget · rules · traces · architecture
Rules are plain lines of text, so the person who owns the policy can review the policy. This is what ships in the Kiro integration:
BLOCK fs_write WHEN phase IS NOT commit
BLOCK * WHEN budget ABOVE 90%
REQUIRE APPROVAL FOR * WHEN tool IS irreversible
FLAG * WHEN time OUTSIDE 06:00-22:00
Four actions (BLOCK, ALLOW, FLAG, REQUIRE APPROVAL FOR), conditions on phase, tool effect, budget and time, combined with WHEN, AND and UNLESS. The whole parser is about 50 lines of shape.py; the grammar and operator table are in docs/rules.md.
Shape also runs outside your code, as pre-tool-use hooks for CLI agents. The hook evaluates phase, budget and rules before the CLI executes anything:
| Agent | Folder | Wire-up |
|---|---|---|
| Kiro CLI | kiro/ |
agent config + PreToolUse/PostToolUse hooks |
| Claude Code | claude/ |
.claude/settings.json hooks |
| OpenAI Codex CLI | codex/ |
config-based hooks |
Each folder is the setup I landed on after wiring Shape into that CLI: a gate.py, a trace.py, a sample rules file, and the settings snippet that registers them. Sessions start in EXPLORE (read only); you unlock writes explicitly with shape-transition commit.
What hooks can and cannot enforce (Architecture A)
Hook mode gives you phase enforcement, budget gates, rules and traces, but the CLI still executes tools itself. That means:
- no transactional atomicity, each tool call stands alone
- no compensation or rollback on failure
- budget is estimated per call, not billed cost
- phase transitions are manual (
shape-transition explore|decide|commit)
For the full model including transactions, import Shape as a library and let it orchestrate the agent (Architecture B). The trade-offs are laid out in docs/architecture.md.
If your framework calls Python functions, Shape can stand in front of them:
from shape import Agent, ToolEffect, wrap_tool
agent = Agent("my-agent", budget=5.00)
governed_search = wrap_tool(agent, "search", search_fn, ToolEffect.READ)
governed_email = wrap_tool(agent, "send_email", email_fn, ToolEffect.IRREVERSIBLE)Works with Strands, LangGraph, CrewAI or raw Python. Recipes per framework: docs/framework-integration.md.
Metering real LLM cost. The model itself burns tokens outside your tool costs, so bedrock.py turns each Bedrock Converse response into an exact charge (pricing table for Claude 4, Nova, Mistral Large 3 and DeepSeek v3.2, April 2026):
from bedrock import bedrock_cost, converse
agent.tool("call_llm", effect=ToolEffect.READ,
fn=lambda **kw: converse(client, MODEL_ID, kw["messages"]),
cost_fn=bedrock_cost) # budget gates now fire on real dollarsThere is also a guide for pairing Shape with AgentCore's agent wallets: docs/agentcore-payments.md.
| Demo | What it shows |
|---|---|
examples/customer_service.py |
Refund flow: explore reads order and policy, commit refunds, updates and emails in one transaction, $0.61 of a $2.00 budget |
examples/manufacturing.py |
PLC speed change: irreversible writes behind an approval callback, after-hours calls flagged by a time OUTSIDE 06:00-22:00 rule |
Both print the same proof traces you saw in the animation above. Run them before wiring Shape into anything real.
| Capability | Galileo | AWS AgentCore | Atomix | Shape |
|---|---|---|---|---|
| Phase enforcement | ✗ | ✗ | ✗ | ✓ |
| Transactional tool calls | ✗ | ✗ | ✓ (paper) | ✓ |
| Compensation / rollback | ✗ | ✗ | partial | ✓ |
| Cost as control signal | ✗ | ✗ | ✗ | ✓ |
| Proof traces | ✗ | ✗ | ✗ | ✓ |
| Readable rule DSL | ✗ | Cedar | ✗ | ✓ |
| Dependencies | many | AWS SDK | n/a | zero |
And the honest half of the table, where Shape is the wrong choice:
| You need | Use instead |
|---|---|
| Org-wide policy across many services and teams | AgentCore with Cedar |
| Dashboards, evals, drift detection | Galileo Agent Control |
| Distributed transactions across services | A workflow engine; Shape compensations are in-process and best effort |
| A non-Python agent library | Shape's library is Python only (the CLI hooks work with any agent that supports them) |
Rule of thumb: Shape fits when one process runs one agent and you want its blast radius capped this afternoon, not after a platform rollout.
| Concept | One-liner | Docs |
|---|---|---|
| Phases | EXPLORE, DECIDE, COMMIT: controls when the agent may act | docs/phases.md |
| Effect classification | READ / REVERSIBLE / IRREVERSIBLE, declared per tool | docs/effects.md |
| Transactions | All-or-nothing commits with automatic compensation | docs/transactions.md |
| Budget gates | Cost as a control signal, thresholds at 50/75/100% | docs/budget.md |
| Rule DSL | Governance rules readable by non-engineers | docs/rules.md |
| Proof traces | A structured record of why each call was allowed | docs/traces.md |
| Architectures | Hook mode vs orchestrator mode, and when each fits | docs/architecture.md |
shape.py the whole library: 509 lines, stdlib only
bedrock.py real cost tracking for Bedrock Converse responses
test_shape.py 62 tests covering phases, budget, rules, transactions
examples/ runnable demos (customer service, manufacturing)
kiro/ claude/ codex/ hook packages for CLI coding agents
docs/ concept docs plus the images on this page
site/ the explainer site (GitHub Pages)
Useful PRs, roughly in order of impact:
- A hook package for another CLI agent. Copy the
kiro/pattern:gate.py,trace.py, a rules file, a config snippet. - More models in the
bedrock.pypricing table, or acost_fnfor another provider. - New DSL conditions (day of week, per-tool call counts) with tests in
test_shape.py.
Run python3 -m pytest test_shape.py -q before opening the PR; all 62 should stay green.
MIT, see LICENSE.
- writing-craft-skill: teach AI agents to write well using classic copywriting craft plus AI anti-pattern detection
- aws-architecture-diagram-skill: generate AWS architecture diagrams as draw.io files with 270+ verified icons