Tutorial 55: Agent Control Specification
August 4, 2026 · View on GitHub
Time: 20 minutes · Level: Intermediate · Prerequisites: Python 3.11+, a repository checkout, and
opaonPATH
What you will build
Build an ACS policy enforcement point for an email tool. The host sends ACS a complete snapshot before and after the tool call, then enforces the returned verdict.
!!! tip "Prefer a runnable repository example?"
Start with examples/acs-email-tool in a repository checkout. It
demonstrates the canonical Python host path through AgentControl,
HostSession, and SnapshotBuilder without OPA. This tutorial adds a Rego
policy to expose the native manifest and policy-input contract.
You will create:
- a flat ACS manifest
- a Rego policy bundle
- a Python host that calls
AgentControl.run_tool() - three outcomes:
allow,transform, anddeny
!!! important "Public Preview"
ACS is vendored into AGT under policy-engine/ as the AGT 5.0 policy layer. The APIs and manifest shape may change before GA.
How ACS fits in AGT
ACS makes the policy decision. Your application or adapter enforces it.
Host adapter -> snapshot -> ACS runtime -> verdict -> host enforcement
Each evaluation includes its full context: the intervention point, tool call, tool result, labels, and metadata. ACS retains no session state between calls.
Step 1: Install the Python SDK from the repo
From the repository root:
cd policy-engine
python -m pip install ./sdk/python
The agent-control-specification distribution builds the native Rust core with
maturin when installed from source. It includes AgentControl, HostSession,
and SnapshotBuilder for Python hosts.
OPA-backed Rego examples require the opa CLI on PATH.
Step 2: Create the tutorial workspace
mkdir -p /tmp/acs-email-tutorial/policy
cd /tmp/acs-email-tutorial
Step 3: Write the ACS manifest
Create manifest.yaml:
agent_control_specification_version: "0.3.1-beta"
metadata:
name: acs-email-tutorial
policies:
email_policy:
type: rego
bundle: ./policy
query: data.agent_control_specification.email_policy.verdict
intervention_points:
pre_tool_call:
policy_target: $.tool_call.args
policy_target_kind: tool_args
tool_name_from: $.tool_call.name
policy:
id: email_policy
post_tool_call:
policy_target: $.tool_result.value
policy_target_kind: tool_result
tool_name_from: $.tool_call.name
policy:
id: email_policy
tools:
send_email:
type: Tool
id: send_email
clearance: internal
The manifest binds the same Rego policy at two intervention points:
| Intervention point | What ACS evaluates |
|---|---|
pre_tool_call | The outbound tool arguments before the email tool runs |
post_tool_call | The tool result before it returns to the caller |
Step 4: Write the Rego policy
Create policy/email_policy.rego:
package agent_control_specification.email_policy
import rego.v1
default verdict := {"decision": "allow"}
verdict := {
"decision": "deny",
"reason": "external_recipient_blocked",
"message": "Messages to external recipients are blocked."
} if {
input.intervention_point == "pre_tool_call"
input.tool.name == "send_email"
endswith(input.policy_target.value.to, "@example.net")
}
verdict := {
"decision": "transform",
"reason": "redact_tracking_token",
"message": "Tracking token redacted before tool execution.",
"transform": {
"path": "$policy_target.body",
"value": "Your case is ready. Tracking token: [REDACTED]"
}
} if {
input.intervention_point == "pre_tool_call"
input.tool.name == "send_email"
contains(input.policy_target.value.body, "TRACK-")
}
The policy returns:
| Input | Verdict |
|---|---|
| Normal internal email | allow |
| Internal email with a tracking token | transform |
External @example.net recipient | deny |
Step 5: Write the host
Create run.py:
import asyncio
from pathlib import Path
from agent_control_specification import AgentControl, AgentControlBlocked
ROOT = Path(__file__).parent
async def send_email(args):
return {"sent": True, "to": args["to"], "body": args["body"]}
async def main():
control = AgentControl.from_path(str(ROOT / "manifest.yaml"))
allowed = await control.run_tool(
"send_email",
{"to": "customer@example.com", "body": "Your case is ready."},
send_email,
tool_call_id="email-1",
)
print(allowed.value)
transformed = await control.run_tool(
"send_email",
{
"to": "customer@example.com",
"body": "Your case is ready. Tracking token: TRACK-123",
},
send_email,
tool_call_id="email-2",
)
print(transformed.value)
try:
await control.run_tool(
"send_email",
{"to": "partner@example.net", "body": "Hello."},
send_email,
tool_call_id="email-3",
)
except AgentControlBlocked as exc:
print(exc.result.verdict.reason)
asyncio.run(main())
run_tool() evaluates pre_tool_call before execution and post_tool_call
after the tool returns.
Step 6: Run it
python run.py
Expected output:
{'sent': True, 'to': 'customer@example.com', 'body': 'Your case is ready.'}
{'sent': True, 'to': 'customer@example.com', 'body': 'Your case is ready. Tracking token: [REDACTED]'}
external_recipient_blocked
The second call applies the transform verdict before execution. The third
blocks the tool after a deny verdict.
Step 7: Inspect the policy input shape
ACS policies evaluate a canonical policy input. For pre_tool_call, the Rego
policy receives fields like:
{
"intervention_point": "pre_tool_call",
"policy_target": {
"path": "$.tool_call.args",
"kind": "tool_args",
"value": {
"to": "customer@example.com",
"body": "Your case is ready."
}
},
"tool": {
"name": "send_email",
"id": "send_email",
"clearance": "internal"
}
}
The input may include more snapshot, annotation, and manifest-derived fields. Read only the canonical fields the policy needs; do not depend on host-local state.
Step 8: Try a fail-closed case
Change the manifest so tool_name_from points at a missing path:
tool_name_from: $.tool_call.missing_name
Run the host again. ACS fails closed with a runtime error verdict instead of allowing the call.
Malformed manifests, missing paths, policy dispatcher failures, and invalid
transform targets produce deny verdicts with reserved runtime-error reasons.
Next steps
- Run the framework-neutral AGT host example at
examples/acs-email-tool. - Inspect the ACS plus ATR annotator example.
- Read the Agent Control Specification package page.
- Compare Rego and Cedar in OPA / Rego / Cedar Policies.
- Add human review with Approval Workflows.
- Review policy composition with Policy Composition.