It's all too common — a viral tweet describing how AI agents with poorly defined guardrails deleted production, wiped a critical database, or caused some other disastrous occurrence. Agents need guardrails: this includes permissions to do the job, but also blockers that limit the reach of the agent, thereby preventing accidental disasters that fuel viral tweets.
Cognous' Open Control Stack is a framework for wrapping AI agents in guardrails, providing access where needed, but blocking the boundaries that cannot be crossed.
In our introductory blog post, our agent drafted a cringey follow-up email and "helpfully" sent it to a company's entire list of top customers. Nobody approved that send, and the human marketing team had to drop everything and work to mitigate the damage.
Open Control Stack is structured as four layers: Declare → Control → Replay → Evidence.
We built the first layer in The Manifest That Keeps Your AI Agent Honest, the Agent Action Manifest. That's the Declare layer — a JSON file that works as the allow/block list for our agent:
- Pull the top customer: allowed
- Draft a reply: draft-first
- Pull contract details: blocked
- Send the email: needs approval
But that's all a manifest is — a list. Nothing reads it and nothing enforces it. We need something that takes that list and actually applies it to the agent while it's running.
That's the Agent Control Plane — the Control layer. This post is about what it actually does when the agent tries something.
What Sits Beside The Agent
The Control Plane doesn't run the agent, and it isn't a framework. It sits next to whatever is already generating the agent's behavior — OpenClaw, Hermes, a custom loop, whatever — and turns every proposed action into a recorded decision before that action is treated as real.
The agent's job is small in this picture — it proposes. The Control Plane does the rest: it decides, and it documents. Propose is the only row the agent touches. Decide is the gate ruling allow, block, or escalate. Document is what gets written down once the decision's made. The agent doesn't get a say in the outcome — it just gets to ask.
The pattern, end to end:
Propose
The agent wants to do something. The Control Plane frames it, and a proposal gets built.
- Agent Task: The agent kicks off the run. In our use case: "review a customer account and follow up."
- Frame: The Control Plane frames the execution context for the run — the task, actor, environment, allowed tools, blocked tools, policy version. This gets locked in at the start and becomes immutable for the life of the run.
- Action Proposal: The agent's ask — this tool, this action type, this target, this payload, this reason. Recorded before anything executes — that's the point of the proposal steps. If the decision gate blocks it next, nothing downstream ever sees a real tool call, but the attempt is still on the record.
Decide
The Control Plane decides whether that proposal is actually okay. This is where the manifest's declarations actually take effect: what it declared is what ends up in the frame's allowed and blocked lists, and the frame is what the gate checks against. Two of the six rules below check something separate from the frame: authority — whether this actor currently has permission to write, or to send things externally. Authority is granted per run, on top of the frame, and it's what rules 3 and 5 are checking for.
-
Policy Gate: Decides what happens to the proposal. Six rules, checked top to bottom, first match wins:
- tool is explicitly blocked →
block - tool is not in the allowed list →
escalate - action type is
external_send→allowif authority for it exists, otherwiseblock - action type is
read(and passed rules 1–2) →allow - action type is
write→allowif authority for it exists, otherwiseescalate - anything else →
escalate
- tool is explicitly blocked →
- Policy Decision + Evaluation Trace: The gate's verdict, plus the rule-by-rule path it took to reach it.
-
Allow / Block / Escalate: The three possible verdicts. Only
allowlets the proposal go on to actually touch a tool.
Document
Once a decision is made, the Control Plane writes down what happened, what the decision was, and how it was made.
- Reliance Record: Once an action is allowed and actually executed, a record of what the agent depended on to produce its output — a tool, a database, a file, an API, user input. Blocked and escalated actions never execute, so there's nothing to record reliance on — dependence doesn't exist until an action actually runs.
- Run Record: Every proposal, decision, trace, block, and reliance from this run, assembled into one object.
- Replay Bundle: The run record packaged up for later, portable inspection.
Running The Scenario For Real
Let's take three of those actions from the manifest and actually run them through the gate: pull_top_customers, draft_reply, and send_email. All three are on this run's allowed-tools list. pull_contract_details is also declared in the frame's blocked-tools list, matching the manifest — but the task is "review a customer account and follow up," which never calls for contract details, so the agent never proposes it. It's declared, not exercised: nothing for the gate to evaluate here.
Starting the run:
This is the Control Plane spinning up: RunRecorder() creates the recorder instance, and start_run() builds the frame — the same frame from the Propose section above, locking in the task, actor, environment, allowed tools, blocked tools, and policy version for everything that follows.
from agent_control_plane import RunRecorder
recorder = RunRecorder()
run_id = recorder.start_run(
task="Review a customer account and follow up.",
actor="support-agent-v1",
environment="production",
allowed_tools=["pull_top_customers", "draft_reply", "send_email"],
blocked_tools=["pull_contract_details"],
policy_version="v1.0",
)
Proposing and Deciding One Action:
Here's the pattern for a single action, pull_top_customers. The same process repeats for the other two actions — omitted here for brevity:
- Propose the action with the
propose_actionmethod. This is stored in the variablea1. - Evaluate the proposal with
evaluate_action. This returns two values: the decision, and, if the action is blocked, a blocked-action record.
a1 = recorder.propose_action(
tool_name="pull_top_customers", action_type="read",
target="crm:top_customers", payload={"limit": 25},
reason="Identify the customer's account tier before drafting a reply.",
)
d1, _ = recorder.evaluate_action(a1)
pull_top_customers is not blocked, so we receive only the decision, and the unused second value gets thrown away with _. d1 holds the decision: the verdict (allow, block, or escalate), which rule produced it, and why. Here's what the gate decides across all three actions:
| Action | Type | Rule | Result |
|---|---|---|---|
pull_top_customers |
read | 4 | allow |
draft_reply |
write | 5 | escalate |
send_email |
external_send | 3 | block |
Missing authority is what stops two of the three from just allowing. draft_reply escalates and send_email blocks — same problem, nobody granted permission for this run — but different severity, because the two actions aren't equally reversible. A blocked send is stopped outright: once an email's out, it's out. An escalated write just doesn't execute yet. Nothing about the original decision ever changes — but if someone grants write authority before this run ends, a fresh proposal for the same action would clear rule 5 as allow.
The evaluation trace is where the reasoning survives, not just the verdict. Here's the trace for the send_email decision:
{
"rules_evaluated": [
{"rule_name": "blocked_tool_policy", "matched": false, "result": "none"},
{"rule_name": "unknown_tool_policy", "matched": false, "result": "none"},
{
"rule_name": "external_send_authority_policy",
"matched": true,
"result": "block",
"reason": "Action type 'external_send' requires an authority record with scope 'external_send', which was not found."
}
],
"final_result": "block"
}
A reviewer looking at this trace can tell exactly which check failed and why — not just that the action was blocked. That matters because send_email is on the allowed-tools list; nothing about the tool itself was forbidden. The trace is what proves the block was about missing authority, not a banned tool.
Worth noting:
draft_replyescalating isn't a lesser outcome than blocking. It's easy to read "escalate" as a downgrade of "block," but it's really the gate saying it doesn't have enough information to decide safely, so a human should. Grantwriteauthority to the run, and the same action clears rule 5 asallowinstead — the rule didn't change; what the actor is authorized to do did.
Documenting The Proposal And Decision
After a decision has been made, the Cognous Control Plane documents what occurred during the process. All of the information is collated and stored in one place — not grabbed from logs scattered across different tools and pieced together by timestamp.
The first step is the Reliance Record. Recall that this only documents actions that actually ran — in this case, pull_top_customers:
recorder.record_reliance(
source_name="pull_top_customers",
source_type="tool",
scope="customer tier and account fields",
referenced_action_id=a1.action_id,
)
This doesn't get written to a file by itself — it's held in memory as part of the run, alongside every proposal, decision, and trace, until the whole thing gets closed out and exported:
recorder.complete_run("Draft prepared and escalated for review. Email send was blocked, not attempted.")
recorder.export_json("run_record.json")
bundle = recorder.generate_replay_bundle()
export_json() writes everything — proposals, decisions, traces, the one blocked-action record, that one reliance record — to a RunRecord JSON file. Retrieving the reliance record later is just reading that file and looking at its reliance_records array.
Reading The Results
Writing the record is only half the story — you also need a way to check it. That's acp, the Control Plane's own command-line tool, shipped in the same repo: github.com/cogno-us/cognous-agent-control-plane.
Running it against the run record we just exported:
acp validate-run run_record.json
RunRecord 79ad6860-03de-4136-9828-d4e40fbd05f4: valid (0 errors, 0 warnings)
Validation here isn't checking whether the run succeeded — it's checking that the record is internally consistent: every decision references an action that actually exists, every blocked action corresponds to a decision that actually says block. It's a check on the evidence, not on the outcome.
The replay bundle gets the same treatment:
acp validate-replay replay_bundle.json
ReplayBundle 535c72f8-efee-4144-9b78-7f7fff73133d: valid (0 errors, 0 warnings)
What This Deliberately Doesn't Do
The Control Plane only enforces the rules and authority it's actually been given — it doesn't infer intent, and it doesn't fill gaps with a best guess. A permission that was never granted shows up as an escalation or a block, not a pass. It's not a compliance system, and it's not a guarantee that the agent's output was correct. It's the layer that turns "the agent tried to do X" into a recorded, deterministic, inspectable decision, every time.
It also doesn't decide what happens after a decision is made. The gate doesn't open a ticket, notify anyone, or track whether an escalation ever gets reviewed. Whether a human looks at the draft, whether the agent retries, whether authority eventually gets granted — that's handled by whatever's orchestrating the agent, not by the Control Plane.
Next Up
We now have a run: three proposed actions, one blocked, one escalated, one allowed, each with a trace and a fingerprint, packaged into a replay bundle that validates clean. That bundle is a record of one run.
The Cognous Open Control Stack has four steps: Declare → Control → Replay → Evidence.
In The Manifest That Keeps Your AI Agent Honest, we declared in the manifest what was allowed and what was not. In this post, we walked through the Control Plane — it runs next to your agentic framework to allow, escalate, or block actions. The Control Plane records the results from every action taken.
Next, we look at the last two steps: Replay and Evidence. These are the tools that will save you when auditors are knocking at the door, or you need to prove you've redacted customer data, or just prove it hasn't been tampered with since it was generated. That's the Agent Replay Bundle, and we'll cover it in our next post.
In the meantime, the Control Plane repo, examples, schemas, and test suite are live now: github.com/cogno-us/cognous-agent-control-plane. Clone it, run examples/simple_agent_run.py, and watch the gate make the call.

Top comments (0)