agent governance tutorial May 10, 2026

Governing Claude agents with 10 lines of YAML

The Anthropic Agent SDK builds Claude-powered agents in Python. flux7-mesh governs them with YAML policies. MeshHooks connects the two in 3 lines of code. Full walkthrough, 5 minutes.

While building flux7-mesh (a governance proxy for agent tool calls) and learning the Anthropic Agent SDK, I wanted to make mesh7 a native part of the Claude ecosystem. The Agent SDK ships with a hooks system that intercepts tool calls before execution. I wrote a small integration layer called MeshHooks that plugs mesh7's policy engine into those hooks. This tutorial shows how to set it up.

1 Install

You need two things: the mesh7 binary (Go) and the Python SDK.

# mesh7 binary
go install github.com/KTCrisis/flux7-mesh/cmd/mesh7@latest

# Python SDK with Agent SDK support
pip install flux7-mesh[anthropic]

2 Define your tools

For this example, the agent has three tools: reading files, writing files, and deleting files.

tools.py
import os

def read_file(path: str) -> str:
    """Read a file and return its contents."""
    with open(path) as f:
        return f.read()

def write_file(path: str, content: str) -> str:
    """Write content to a file."""
    with open(path, "w") as f:
        f.write(content)
    return f"Written to {path}"

def delete_file(path: str) -> str:
    """Delete a file."""
    os.remove(path)
    return f"Deleted {path}"

Nothing special here. Plain Python functions, no framework dependency, no decorator. mesh7 doesn't care how you implement your tools. It only sees their names and parameters when they're called.

3 Write the mesh7 config

In Agent SDK mode, mesh7 doesn't proxy the tool calls. It doesn't need to know your tools or how to reach them. It acts purely as a policy engine: the hooks send a tool name and parameters, mesh7 evaluates them against the rules, and returns a decision via POST /decide.

config.yaml
listen: ":9090"

policy:
  default_action: human_approval

  rules:
    - agent: "file-agent"
      tool: "read_file"
      action: allow

    - agent: "file-agent"
      tool: "write_file"
      action: allow
      conditions:
        - param: path
          pattern: "/tmp/*"

    - agent: "file-agent"
      tool: "delete_file"
      action: deny

The 10 lines that matter are these three rules, each binding an agent identity to a tool with an action:

Anything not matched by a rule falls back to human_approval, which means the call is queued and waits for a human to approve or deny it. This fallback acts as a safety net, because a new tool or a call you didn't anticipate won't execute silently.

4 Wire MeshHooks into your agent

MeshHooks connects your Agent SDK agent to the policy engine in three lines.

agent.py
from anthropic.agent import Agent, ToolDefinition
from mesh7 import MeshHooks
from tools import read_file, write_file, delete_file

# Connect to mesh7
hooks = MeshHooks(agent="file-agent")

# Define tools for the Agent SDK
tools = [
    ToolDefinition(
        name="read_file",
        description="Read a file and return its contents",
        input_schema={
            "type": "object",
            "properties": {"path": {"type": "string"}},
            "required": ["path"],
        },
        function=read_file,
    ),
    ToolDefinition(
        name="write_file",
        description="Write content to a file",
        input_schema={
            "type": "object",
            "properties": {
                "path": {"type": "string"},
                "content": {"type": "string"},
            },
            "required": ["path", "content"],
        },
        function=write_file,
    ),
    ToolDefinition(
        name="delete_file",
        description="Delete a file",
        input_schema={
            "type": "object",
            "properties": {"path": {"type": "string"}},
            "required": ["path"],
        },
        function=delete_file,
    ),
]

# Create the agent with governance hooks
agent = Agent(
    model="claude-sonnet-4-6",
    tools=tools,
    hooks=hooks.agent_sdk_hooks(),
)

result = agent.run("Read /tmp/notes.txt, then write a summary to /tmp/summary.txt")

The MeshHooks(agent="file-agent") constructor creates a set of hooks that the Agent SDK will call before every tool execution. Under the hood, MeshHooks sends the tool name and its parameters to mesh7's POST /decide endpoint, which evaluates the YAML policy and returns allow, deny, or ask. The agent ID "file-agent" is what links this agent instance to the rules you wrote in the config.

The hooks.agent_sdk_hooks() call returns a dict that the Agent SDK understands natively. It registers a PreToolUse hook on all tools (you can narrow the scope with a regex via tool_matcher if needed). The tool definitions and the agent creation are standard Agent SDK patterns, nothing mesh7-specific.

5 Run it

Two terminals:

# Terminal 1 — start mesh7
mesh7 --config config.yaml
# Terminal 2 — run the agent
python agent.py

Watch the mesh7 logs. You'll see every tool call evaluated against your policy:

INFO  tool_call agent=file-agent tool=read_file decision=allow     latency=0ms
INFO  tool_call agent=file-agent tool=write_file decision=allow    latency=0ms  path=/tmp/summary.txt
INFO  tool_call agent=file-agent tool=delete_file decision=deny    latency=0ms

If Claude tries to delete a file, the hook returns deny before the function is ever called. The agent receives a denial reason in the hook response, so it knows the action was blocked and can adapt its plan.

What happens under the hood

PYTHON PROCESS Claude MeshHooks PreToolUse hook Tool fn() tool call allow mesh7 POST /decide policy engine trace store config.yaml HTTP /decide allow / deny / ask
MeshHooks delegates policy evaluation to mesh7. The hook is a thin HTTP call.

The flow, step by step:

  1. Claude decides to call write_file with {"path": "/tmp/summary.txt", ...}
  2. The Agent SDK fires the PreToolUse hook before execution
  3. MeshHooks sends POST /decide to mesh7 with the tool name, params, and agent identity
  4. mesh7 evaluates the YAML policy: write_file is allowed for file-agent when path matches /tmp/*
  5. mesh7 returns allow. MeshHooks translates to the Agent SDK format
  6. The tool function executes. mesh7 logs a structured trace

One design choice worth noting: if mesh7 is unreachable, MeshHooks fails closed and defaults to deny. The idea is that if the governance layer is down, the agent shouldn't keep running ungoverned. You can change this with fail_action="allow" if you prefer leniency in development, but for production agents, closed is the safer default.

Human approval

We've used allow and deny so far, which are immediate decisions. But human_approval is the more interesting case: the agent wants to do something, and mesh7 decides a human should validate it first.

When a tool call hits a human_approval rule, mesh7 queues the request. The hook blocks, and the agent waits. On the other side, a human can resolve the pending approval through several channels:

Once resolved, mesh7 unblocks the hook and returns the decision to the agent. If the human approved, the tool executes normally. If denied, the agent gets a denial reason and adapts.

The interesting part is what happens over time: if you connect flux7-memory, mesh7 records every approval as a queryable fact. After 3 consistent approvals for the same agent+tool pattern with 0 rejections, mesh7 starts auto-approving that pattern. Hence the governance becomes less intrusive over time, and it only relaxes patterns that a human has already validated.

Going further

This tutorial covers the simplest case. Everything below can be added through YAML and config alone, without touching agent code:

FeatureConfig changeEffect
Human approvalaction: human_approvalTool call queued, agent waits, human approves/denies in console
Rate limitingrate_limit: {max: 10, window: 60s}Max 10 calls per minute per tool
Temporal grantsmesh7 grant create ...Temporary override: allow delete_file for the next 30 minutes
Auto-approveConnect flux7-memoryAfter 3 human approvals for the same pattern, auto-approve future calls
Multi-agentDifferent agent: per ruleEach agent gets its own permissions, same mesh7 instance
JWT authauth.jwt in configAgent identity from IdP tokens instead of strings

All of this is policy and configuration. Your Python agent code stays the same. Therefore the governance layer evolves independently from the agent code, and it can live in a different repo, owned by a different team, with its own deploy cycle. A platform engineer can tighten a policy or add rate limiting without redeploying the agent, and a developer can add new tools without waiting for a policy review.