From State Machines to Gates: How We Simplified Execution Governance
We built a full state machine transition system — three Django models, an N×N transition matrix, exception tables, approval gates. It was technically correct. It was also the wrong answer. Here's the story of how we threw it out and replaced it with two fields per state.
The Problem We Set Out to Solve
AI agents and bots are doing real work now. Not "summarize this document" work — real operational work. Moving tickets between states. Triggering deployments. Escalating incidents. Closing loops that used to require a human clicking buttons in a dashboard.
That's powerful. It's also dangerous if nobody's controlling what these agents can and can't do.
Imagine a bot that can move a ticket straight from "New" to "Closed" without it ever being worked. Or one that marks something "Approved" without a human ever reviewing it. These aren't hypothetical scenarios — they're the kind of things that happen when you give autonomous systems access to workflows without governance.
The traditional answer to this is state machines with defined transitions. You map out every valid path through your workflow, and anything not explicitly allowed is denied. It's a well-understood pattern. So that's what we built.
What We Built First (The Complex Way)
Our first implementation had three models at its core:
- StatusDefinition — the states themselves (New, In Progress, Review, Completed, etc.)
- StatusTransition — the allowed paths between states (New → In Progress, In Progress → Review, etc.)
- TransitionException — overrides for specific users or conditions
Every valid path from state A to state B had to be explicitly defined in the transition table. If you had 8 states and wanted most of them to be reachable from most others, you were looking at potentially dozens of transition records. An N×N matrix where N is your number of states.
Each transition could specify whether it was available to bots, humans, or both. Need a transition that's normally bot-only but one specific project manager should also be able to use it? That's what TransitionException was for.
Then we added approval gates. Some transitions — particularly ones where a bot was moving something into a consequential state like "Deployed" or "Approved" — needed a human to sign off first. So we built an approval system layered on top of the transition system.
The UI reflected this complexity. We had tabs within tabs: a Diagram view showing the state machine visually, a States tab to define your statuses, a Transitions tab for the N×N matrix, and an Exceptions tab for the overrides.
It worked. It was technically correct. Every edge case was handled. And configuring it felt like programming a PLC.
The Moment of Clarity
We were onboarding a new team and walked them through setting up their workflow governance. Fifteen minutes in, we watched the team lead's eyes glaze over as we explained the difference between a transition exception and an approval gate.
That night, we stepped back and asked ourselves a simple question: what are people actually trying to express when they configure governance?
They're not thinking about paths. They're not thinking about which state leads to which other state and who can traverse each edge in the graph. They're thinking about gates.
"Who should be allowed to move things to Completed?" — that's the real question.
Not "what transitions lead to Completed, and who can use each one, and what exceptions exist for each transition." That's the same question buried under three layers of indirection.
The transition matrix was an answer to a question nobody was asking. People don't think in edges. They think in destinations. "Who can enter this state?" and "Where can you come from to get here?" — those are the natural questions. Everything else is implementation detail.
What We Built Instead (The Simple Way)
Two fields per state. That's the entire new system.
- × StatusDefinition
- × StatusTransition (N×N matrix)
- × TransitionException
- × Approval gates (separate system)
- × 6 API endpoints
- × Tabs within tabs UI
- ✓ Who can enter — Everyone, or pick specific users
- ✓ Allowed from — Any state, or pick source states
- ✓ 2 API endpoints
- ✓ One clean view
Who can enter: defaults to Everyone. Want to restrict a state? Pick the specific users or roles who should be able to move things there.
Allowed from: defaults to Any State. Want to enforce a pipeline? Pick which states something must be in before it can move here.
No transition table. No exception records. No separate approval gate concept.
Want approval-style control? Only allow specific humans to enter "Completed." Bots can do everything up to that point, but a human has to move it across the finish line. That's not a special approval gate feature — it's just setting "who can enter" on one state.
Want a strict pipeline where things must go New → In Progress → Review → Done in order? Set "allowed from" on each state to only allow the previous one. No transition matrix needed.
Want it wide open because your team is small and you trust each other? Leave everything on "Everyone" and "Any State." Zero configuration required.
Same outcomes. Every scenario the old system handled, the new system handles. A fraction of the complexity.
Why Simpler Wins for Governance
Here's the uncomfortable truth about governance tools: the ones that are hard to configure don't get configured.
We've seen this across the industry. Teams buy sophisticated governance platforms, go through a painful setup process, get something "close enough" configured, and then never touch it again — even as their workflows evolve and the governance rules become stale.
Unconfigured governance is no governance. A beautifully designed transition matrix that nobody updates when new states are added is worse than useless — it's a false sense of security.
The best governance is the kind people actually use. And people use things that are easy to understand and quick to change.
Our competitors show you dashboards full of flowcharts and compliance metrics. They give you powerful, flexible configuration systems that can model any workflow imaginable. And then they wonder why adoption stalls after the initial setup.
We give you two dropdowns per state.
"Others observe. We enforce. And we make enforcement simple enough that you'll actually do it."
Technical Details (for the Engineers)
If you want the nuts and bolts, here's what changed under the hood.
The old system had three Django models — StatusDefinition, StatusTransition, and TransitionException — each with their own ViewSet and serializer. Validation logic had to check transitions first, then look for exceptions, then evaluate approval gates. The flow was roughly:
- Is there a StatusTransition from current state to target state?
- Does the user type (bot/human) match the transition's allowed user types?
- If not, is there a TransitionException that grants this specific user access?
- Is there an approval gate on this transition? If so, has it been satisfied?
That's four checks in sequence, hitting three different tables, with branching logic at each step.
The new system adds two many-to-many fields directly on the existing StatusDefinition model: allowed_users (who can enter) and allowed_from_statuses (valid source states). Validation is roughly ten lines:
- Does the target state have
allowed_usersset? If yes, is the current user in that list? If no entries, everyone is allowed. - Does the target state have
allowed_from_statusesset? If yes, is the current state in that list? If no entries, any source state is allowed.
Two checks. One table. No branching.
The migration was non-destructive. We kept the old models as deprecated — they still exist in the codebase and the data is preserved. The new fields were added alongside them. Teams can migrate at their own pace, and we built a one-click migration tool that reads the old transition matrix and infers the equivalent gate configuration.
The API surface shrank from six endpoints (CRUD for transitions, CRUD for exceptions, plus the approval gate endpoints) to two: the existing status definition endpoint now includes the gate fields, and a validation endpoint to test whether a proposed state change would be allowed.
What This Means for You
If you're already using OpenWeave: your state machine configuration just got dramatically simpler. The old transition-based system still works — nothing breaks. But we'd encourage you to try the new gate-based approach. Most teams find they can replicate their existing governance rules in a few minutes, and the result is much easier to maintain.
If you're evaluating governance tools: this is what we think governance should feel like. Not a project unto itself. Not something that requires a consultant to set up. Two questions per state: who can enter, and from where? That's governance you'll actually maintain.
If you're building your own system: before you build a transition table, ask yourself whether you actually need to model edges — or just gates. In our experience, the vast majority of real-world governance requirements are about controlling entry to states, not controlling the paths between them. Start with gates. You can always add transitions later if you genuinely need them. (You probably won't.)
OpenWeave is execution governance for autonomous systems — bots, agents, and AI doing real work inside your workflows. Learn more →