Skip to content

Quickstart

Install

AgentGuard needs Python 3.11 or later.

pip install agentguard-oss

The package is agentguard-oss; the import is agentguard. Framework adapters are optional extras: agentguard-oss[langchain], [openai-agents], [adk], [mcp], or [all].

Try the demo

agentguard demo --scripted

The demo gives an agent a calculator to fix. The repository's README contains a prompt injection asking it to steal ~/.ssh/id_rsa. AgentGuard lets it read the README, blocks the SSH-key read and the curl exfiltration, allows the fix and a real unit test, and asks for approval before the push to main. Nobody is there to approve in the scripted demo, so the push is denied; add --interactive to approve or reject it in the terminal. With Ollama and a Gemma model installed, agentguard demo runs a live Gemma agent instead.

Guard your first tool

Save this as quickstart.py and run it from an empty directory:

"""AgentGuard quickstart. Run it from any directory: python quickstart.py"""

from pathlib import Path

from agentguard import Guard, GuardDenied

workspace = Path("workspace")
workspace.mkdir(exist_ok=True)
(workspace / "notes.txt").write_text("hello from the workspace\n", encoding="utf-8")

guard = Guard(
    {
        "version": 1,
        "defaults": {"effect": "deny"},
        "rules": [
            {"capability": "filesystem.read", "paths": ["./workspace/**"], "effect": "allow"},
            {"capability": "shell.execute", "effect": "ask"},
        ],
    },
    audit="quickstart-audit.jsonl",  # Keep the audit log outside the agent's workspace.
)


@guard.tool(capability="filesystem.read")
def read_file(path: str) -> str:
    """Read a UTF-8 text file."""
    return Path(path).read_text(encoding="utf-8")


@guard.tool(capability="shell.execute")
def run(cmd: str) -> str:
    """Run a shell command. Never reached here: shell asks, and no approver is set."""
    raise AssertionError("unreachable")


# Give the agent only the guarded functions. Each call is checked, then audited.
print(read_file("workspace/notes.txt"), end="")

for name, arguments in [("read_file", {"path": "~/.ssh/id_rsa"}), ("run", {"cmd": "ls"})]:
    try:
        guard.call(name, arguments)  # The same path an agent framework uses.
    except GuardDenied as exc:
        print(f"Denied {name}: {'; '.join(exc.decision.reasons)}")

print("Decisions:", guard.summary)

Output:

hello from the workspace
Denied read_file: Policy default: deny; Sensitive credential file access
Denied run: Policy rule 2: ask; Capability baseline: 40; Approval rejected, unavailable, insufficient, or timed out
Decisions: {'allow': 1, 'ask': 1, 'deny': 1}

Three things happened:

  1. read_file("workspace/notes.txt") matched the filesystem.read rule and ran.
  2. Reading ~/.ssh/id_rsa matched no rule (default deny) and is a credential file, which is always denied. The function never ran.
  3. run is marked ask. No approval provider is configured, so asking means deny.

Look at the audit log

agentguard verify-log --audit quickstart-audit.jsonl
agentguard logs --audit quickstart-audit.jsonl --decision deny

Every call left proposed, denied or authorized → executing → executed → observed events, chained by SHA-256 hashes. See Audit log.

Move the policy to a file

policy.yaml
version: 1
defaults:
  effect: deny
rules:
  - capability: filesystem.read
    paths: ["./workspace/**"]
    effect: allow
  - capability: shell.execute
    effect: ask
guard = Guard("policy.yaml", audit="agentguard.jsonl")

Check it and ask how it decides a call before running anything:

agentguard check-policy policy.yaml
agentguard explain policy.yaml shell.execute --arg cmd="git push origin main"

Next steps