Skip to main content

CI License: Apache-2.0 Python 3.12 Docs Discord

Know what your agents can do. Before they do it.

ControlLoop is a local-first scanner and deterministic CI gate for what your AI agents can actually do. It reads the files that define an agent, builds one graph of its capabilities, compares it to the state you approved, and fails the build when the agent's authority grows past the ceiling you declared. It never runs your code and never sends your source anywhere.

Play the one-minute overview: ControlLoop blocks a pull request that widens an agent's authority

Watch: the gate in a minute · Read: the example walkthrough

Try it in 30 seconds

No install and no account: with uv on your machine, uvx runs the CLI straight from PyPI. Point it at the public reference agent, on a branch where a pull request adds a Claude Code SessionStart hook:

git clone -b scenario/session-start-harvester-hook https://github.com/controlloop-security/controlloop-reference-agent
cd controlloop-reference-agent
uvx controlloop gate scenarios/storefront-agent-config
! WORST REACHABLE  code-execution: Claude Code can invoke PostToolUse command
  hook (Edit|Write)

✕ DEPLOYMENT BLOCKED

Authority:
  • This change gives Claude Code the power to run `node .github/setup.js` on
    every session start (`.claude/settings.json`, hook `SessionStart`). No
    approval is recorded.

• GAINED  code-execution: Claude Code invokes SessionStart command hook

✕ HIGH     policy.agent-hook-command-change: Added: Hook on event 'SessionStart'
  for host 'Claude Code' runs `node .github/setup.js`.
    at .claude/settings.json

Suppressions: none declared

Completeness enforcement: warn (default)
Evidence: evidence.json

Exit code 1. The gate compared the branch with main, the way CI compares a pull request with its base, and named the new power in one sentence. The reference agent has a branch for each kind of change: a widened Bash(*) allow rule, an MCP package that changed version, an agent workflow given id-token: write, and more. To gate your own pull requests, add the GitHub Action.

ControlLoop CLI

An agent's real authority is spread across an agent card, an MCP config, an OpenAPI spec, an IAM policy, and, for most agents, the framework code itself: a CrewAI crew, a LangGraph graph, an OpenAI Agents SDK handoff chain. The same goes for the coding agents that work on your repository. Claude Code, Codex, Gemini CLI, Cursor, Kiro, Roo Code, and Cline are configured by committed files, and so are the workflows that run an agent in CI. No single file tells you that one pull request just gave an agent the ability to move money, or to run a script on every session start.

How it works. ControlLoop walks the repository and reads only committed files. Adapters turn them into an Agent Capability Bill of Materials (ACBOM): agents, tools, resources, identities, and permissions, with evidence for every node and edge. The graph is diffed against the baseline you approved, classified by a material capability taxonomy, judged by 24 policy rules and the capability ceiling you declared, and the verdict is written to an evidence artifact. No model decides anything; the same inputs always produce the same verdict. Secrets, prompts, environment values, and file contents never reach the output.

Development preview. init, bom, diff, scan, gate, rules, and example work and are covered by tests. Every tagged release is published to PyPI, the Homebrew tap, the container registry, and GitHub Releases as standalone binaries.

Features

What it reads

What it tells you

  • 🎯 The blast radius, first. Every report opens with the single worst thing the agent can do right now: ! WORST REACHABLE financial-action: … can invoke Issue a refund. Material capability taxonomy.
  • 🗣️ A plain-language authority statement for every change to an agent's configuration: This change gives Claude Code the power to run node .github/setup.js on every session start. Authority statement.
  • 🔀 A capability diff, not a file diff: what became newly reachable against the baseline you approved, by class. Capability diff.
  • 📏 A capability ceiling that blocks authority nobody anticipated, through routes no rule matches, and cannot be suppressed. Capability ceiling.
  • 📚 24 policy rules with a plain message, a remediation, and a stable documentation link on every finding. Policy rules.
  • 🧾 An evidence artifact on every gate run, including the failing ones, plus JSON and SARIF 2.1.0 output. Evidence artifact format.

How it behaves

  • 🔒 Local and offline. Core scanning works with the network disabled. Nothing is executed: no MCP server is started, no endpoint is contacted. Privacy, security, and limitations.
  • 🧮 Deterministic. Same inputs, same pinned versions, same digest, same verdict. Unknown is ranked worse than known, never safer.
  • 🙈 Nothing protected leaves. Source, prompts, credentials, environment values, and customer data never appear in output, logs, errors, identity material, or artifacts.
  • ⚙️ CI-native. A GitHub Action and a pre-commit hook run the same gate.
  • 🤖 Callable from your agent. controlloop mcp serves gate, diff, bom and rules as read-only tools to any MCP host over stdio, with no network and no writes. The local MCP server.

Note: the agent hosts whose project configuration is read are Claude Code, Codex, Gemini CLI, Cursor, and Kiro, each pinned to its documentation as read on a stated date (controlloop --version prints it). MCP declarations are also read from VS Code, Roo Code, Cline, Continue, Windsurf, and opencode.

Quick start

Installation

Install the CLI from PyPI with uv:

uv tool install controlloop
controlloop --version

pipx install controlloop works too. To run it once without installing, use uvx controlloop. With Homebrew, on macOS (Apple Silicon) or Linux:

brew install controlloop-security/controlloop/controlloop

Without Python, the shell installer downloads and verifies the standalone binary for your machine:

curl -fsSL https://raw.githubusercontent.com/controlloop-security/controlloop/main/install.sh | bash

In a container, with no network:

docker run --rm --network none -v "$PWD:/work:ro" \
  ghcr.io/controlloop-security/controlloop:0.6.3 scan /work

Every install path, with its verification step, is in Installation. To run from source instead:

git clone https://github.com/controlloop-security/controlloop
cd controlloop
uv sync --locked --all-groups
uv run controlloop --version
ControlLoop 0.6.3
Supported OASF versions: none yet
Agent host key sets (documentation read on): claude-code 2026-10-17, codex 2026-10-17, cursor 2026-10-17, gemini-cli 2026-10-17, kiro 2026-10-17, roo-code 2026-10-29
Rule pack: controlloop-community 0.9.0 (24 rules)

Requirements

  • Python 3.12 for the PyPI package; uv installs it for you. Homebrew, the shell installer, and the container need no Python.
  • Git, for the automatic merge-base baseline that diff and gate use
  • macOS or Linux. Windows is unsupported.

First run

Point it at a repository containing an agent:

controlloop scan /path/to/your/agent-repo

Nothing to try it on? The public reference agent is a realistic, deliberately imperfect agent built for exactly this:

git clone https://github.com/controlloop-security/controlloop-reference-agent
controlloop scan controlloop-reference-agent/scenarios/order-support-agent
! WORST REACHABLE  financial-action: widgetworks-order-support-agent can invoke
  Issue a refund

✓ PASSED

• material change: not evaluated

! MEDIUM   policy.undeclared-tools: Tool 'Look up an order' is reachable but not
  declared in controlloop.yaml.
    at agent-card.json

! MEDIUM   policy.undeclared-tools: Tool 'Reply to customer' is reachable but
  not declared in controlloop.yaml.
    at agent-card.json

! MEDIUM   policy.undeclared-tools: Tool 'Issue a refund' is reachable but not
  declared in controlloop.yaml.
    at agent-card.json

Suppressions: none declared

Exit code 0. New here? controlloop example prints a six-step walkthrough that matches the example walkthrough guide.

The commands

Command Use it to
scan Full report for a human: blast radius, verdict, changes, findings
gate The same analysis for CI: one verdict, one exit code, an evidence artifact every time
diff See what changed against a baseline, as capabilities rather than files
bom Generate or validate the capability graph (ACBOM), natively or as CycloneDX
init Draft a controlloop.yaml from what is actually in the repository
rules List the rule pack in use and what each rule looks for
example Print a guided walkthrough

scan is what you run at your desk. gate is what you run in CI. Every command and flag is in the CLI reference.

Use it inside your agent

The ControlLoop plugins let a coding agent run this CLI and explain its answer when you ask "what does this change let my agents do?" They run the gate locally, quote its verdict and authority statements, link each finding's documentation, and edit nothing. They ship no hooks and start nothing when a session opens.

Host Install
Claude Code /plugin install controlloop --marketplace controlloop-security/controlloop-plugins, then /controlloop:review
Codex codex plugin marketplace add controlloop-security/controlloop-plugins, then codex plugin add controlloop@controlloop
Gemini CLI gemini extensions install https://github.com/controlloop-security/controlloop-plugins
Cursor From the Cursor Marketplace, or import the repository as a team marketplace
GitHub Copilot copilot plugin install controlloop@controlloop after copilot plugin marketplace add controlloop-security/controlloop-plugins; in VS Code, Chat: Install Plugin from Source
Any MCP host controlloop mcp: gate, diff, bom and rules as read-only local tools

Screenshots

Every block in this README is real output, recorded against the public reference agent with the version above. Nothing is staged.

A pull request adds a tool the agent's ceiling prohibits. The blast radius got worse, the ceiling breach blocks, and the evidence artifact is written:

! WORST REACHABLE  financial-action: widgetworks-order-support-agent can invoke
  Issue a refund

✕ DEPLOYMENT BLOCKED

• GAINED  financial-action: widgetworks-order-support-agent invokes
  customer-payment-methods
• GAINED  sensitive-data-access: widgetworks-order-support-agent invokes
  customer-payment-methods
• GAINED  write-access: widgetworks-order-support-agent invokes
  customer-payment-methods

✕ CRITICAL policy.capability-ceiling: agent 'widgetworks-order-support-agent'
  reaches 'customer-payment-methods', which is a prohibited resource or external
  destination under its capability ceiling
    at agent-card.json

controlloop gate on scenarios/order-support-agent with a branch that adds customer-payment-methods. Exit code 1.

A pull request adds a Claude Code hook. The gate says what that lets the agent do before anything else:

✕ DEPLOYMENT BLOCKED

Authority:
  • This change gives Claude Code the power to run `node .github/setup.js` on
    every session start (`.claude/settings.json`, hook `SessionStart`). No
    approval is recorded.

• GAINED  code-execution: Claude Code invokes SessionStart command hook

✕ HIGH     policy.agent-hook-command-change: Added: Hook on event 'SessionStart'
  for host 'Claude Code' runs `node .github/setup.js`.
    at .claude/settings.json

controlloop gate on scenarios/storefront-agent-config, branch scenario/session-start-harvester-hook. Exit code 1. The GitHub Action posts the same statement as one pull-request comment.

Examples

The common uses, each with the real command and its real output.

Scan a repository

controlloop scan .

The report above: blast radius first, then the verdict, the material change against the baseline, every finding with its rule and evidence path, and the suppressions in force. How to read each line is in Terminal report format.

See what a pull request changed

controlloop diff . --baseline main
CONTROLLOOP / CAPABILITY DIFF

• BASELINE   explicit git revision: revision main (4c258dc876245856f97786c05f51157ee68e8e25)
• SCOPE      full -- nodes, edges, and effective authority

• ADDED    node        clp:tool:sha256:ccc68b72…  customer-payment-methods
• ADDED    edge        clp:edge:sha256:0234aa41…
• ADDED    capability  clp:agent:sha256:2321fe51…  invokes  clp:tool:sha256:ccc68b72…

3 changes: 3 added

Without --baseline, diff uses the committed .controlloop/baseline.json if there is one, else the merge-base with your default branch. diff <baseline.json> <current.json> compares two ACBOM files directly.

Gate in CI, with evidence

controlloop gate . --default-branch main \
  --evidence-path evidence.json --sarif-path controlloop.sarif

gate returns one of four verdicts and always writes the evidence artifact, including on a failing run, which is exactly when you need it:

Verdict Exit Meaning
✓ PASSED 0 Nothing blocking, nothing warned
! PASSED WITH WARNINGS 0 Findings below the blocking threshold
! REVIEW REQUIRED 3 A human must look: analysis was incomplete under strict enforcement, or (Team) a grant is not on the approved list
✕ DEPLOYMENT BLOCKED 1 A blocking finding

gate needs real git history for an automatic baseline, so check out with fetch-depth: 0. The full exit-code table is at the end of this file.

Catch a change to a coding agent's configuration

The second screenshot above. Any change to .claude/settings.json, .mcp.json, .codex/config.toml, .gemini/settings.json, .cursor/hooks.json, .kiro/, a plugin, a skill, a subagent, or a workflow that runs an agent produces an authority statement. Same name, different package? That is a changed grant too:

This change gives Claude Code the power to start MCP server `catalog-docs` by
running `npx -y @widgetworks-example/catalog-docs-mcp@1.5.0`, whose declaration
or tool list changed (`.mcp.json`, server `catalog-docs`). No approval is
recorded.

From scenario/mcp-package-drift, where catalog-docs moves from 1.4.2 to 1.5.0 under the same name. The limits of what the scanner can and cannot see are in Agent authority limits.

Tell ControlLoop what your tools do

This is the step people miss. Discovery reads your agent card and your OpenAPI spec, but nothing in a repository states that issuing a refund moves money. Without that, every capability is unclassified and every scan reports no material capability is reachable. Declare it in controlloop.yaml:

schema_version: "1.1.0"
owner: support-eng

tool_capabilities:
  - tool: Issue a refund        # the DISCOVERED tool name, not its id
    financial_action: true
    read_only: false
    sensitivity: confidential
  - tool: Purge ticket history
    destructive: true

Match the name discovery found, which for an A2A skill is its card name, not its id. Run controlloop bom and read the node names if you are unsure; a declaration naming a tool that does not exist is reported, not silently ignored. Every boolean also accepts "unknown", which ranks worst of all. Manifest schema.

Declare a ceiling

The ceiling is the part that catches what nobody anticipated:

ceilings:
  - agent: widgetworks-order-support-agent
    prohibited_resources:
      - customer-payment-methods
    prohibited_delegation_targets:
      - unapproved-agent

Any reachable capability that breaches it blocks the gate, even with every pattern rule suppressed. A ceiling breach cannot be suppressed. That is the first screenshot above. Capability ceiling.

Draft the manifest from the repository

controlloop init . --dry-run
# controlloop.yaml
# ControlLoop security manifest -- generated by `controlloop init`.
...
schema_version: "1.1.0"

# TODO: owner
# ControlLoop cannot determine who is accountable for this
# repository's agents. Replace the placeholder below with an
# identifier for the responsible team or individual (for
# example: "platform-security" or "jane.doe").
owner: "TODO-owner"

trust_boundaries:
  # Discovered candidate agents (from repository discovery):
  #   - invoice-agent  (tools observed: Send an invoice)
  - name: "TODO-trust-boundary-name"

Every field is present even where ControlLoop could not determine a value; those are marked TODO with a comment saying what to supply. init never overwrites an existing manifest.

Build and validate the capability graph

controlloop bom . -o acbom.json
controlloop bom acbom.json --validate
✓ VALID  acbom.json is a valid ACBOM

For the reference agent that is 16 nodes and 8 edges, with a content digest the evidence artifact records. --format cyclonedx writes a CycloneDX 1.6 projection instead.

Machine-readable output

--format json on scan gives the complete report; on gate it is the evidence artifact itself, byte for byte. Every finding carries its own remediation and a stable documentation URL:

{
  "rule_id": "policy.undeclared-tools",
  "severity": "medium",
  "confidence": "high",
  "message": "Tool 'Look up an order' is reachable but not declared in controlloop.yaml.",
  "change_state": "not-compared",
  "remediation": "Declare this tool in controlloop.yaml (for example, an approvals entry naming its capability), or remove the agent's access to it if it was not intentional.",
  "documentation_url": "https://docs.controlloop.ai/reference/policy-rules#undeclared-tools"
}

--format sarif gives a SARIF 2.1.0 log for code-scanning tools, with the blast-radius statement as runs[0].properties.controlloop.blastRadius:

{
  "version": "2.1.0",
  "tool": "controlloop",
  "results": 3,
  "first_result": { "ruleId": "policy.undeclared-tools", "level": "warning" },
  "blastRadius": [
    "! WORST REACHABLE  financial-action: widgetworks-order-support-agent can invoke Issue a refund"
  ]
}

(Summarized from the real document: the version, the tool name, the result count, the first result, and the property.)

See the rules

controlloop rules
• RULE PACK  controlloop-community 0.9.0 (24 rules)
• FINGERPRINT  169a7ceb80ffac4ccdb2ab95bc7491cb296f8736ded076f666a3020d3f1a342e

✓ policy.undeclared-tools  [active, optional]
    A tool an agent can reach, directly or transitively, that the repository's
    controlloop.yaml never declares.

✓ policy.destructive-or-financial-action  [active, mandatory]
    A change that gives an agent a destructive or financial action it did not
    previously have, against the approved baseline.
…

Run it as a GitHub Action

- uses: actions/checkout@v7.0.1
  with:
    fetch-depth: 0          # gate needs real history for a baseline
- uses: controlloop-security/controlloop@v0.6.3   # a release tag or commit SHA
  with:
    path: ${{ github.workspace }}

The action runs gate, fails the check on a blocking verdict, uploads SARIF to code scanning, writes a check summary, and keeps one pull-request comment with the authority statement current across pushes. GitHub Action reference.

Run it before every commit

repos:
  - repo: https://github.com/controlloop-security/controlloop
    rev: <pinned tag or commit SHA>
    hooks:
      - id: controlloop-gate

The hook runs the same gate before each commit and finishes within its documented time budget. Pre-commit hook.

Exit codes

Code Name Meaning
0 PASS No blocking finding
1 POLICY_FAIL A blocking finding
2 INVALID_INPUT Bad manifest, bad path, unreadable ACBOM
3 INCOMPLETE_ANALYSIS REVIEW REQUIRED: incomplete analysis under strict enforcement, or an unapproved grant on a Team gate
4 ENTITLEMENT Team only: not entitled, token rejected, or platform unreachable with team_fail_mode = "closed"
5 INTERNAL_ERROR A bug. Please report it

Team

Want Team for your organization? Team is set up directly with each organization. Get in touch and we will get you started. Nothing in this section changes how the free CLI behaves: with team_enabled at its default of false, the CLI makes no network request.

The free CLI earns trust one repository at a time. Team turns the same gate into a shared control point across your private repositories:

  • One policy across every repo. Your organization publishes a bundle; repositories may tighten it, never weaken it.
  • An approved-components list. Every hook, MCP server, plugin, or permission rule a pull request adds is judged against what your organization approved, by package, version range, digest, and pin. A grant that is not on the list is Review required until someone approves it from the queue.
  • Exceptions with an owner and an expiry. A build that passed yesterday fails today with the date, not a mystery.
  • Every agent, hook, and MCP server in one inventory, with the sanction state of each.
  • Signed evidence for any period, for the auditor and for your tooling.

The Agent authority view: every grant across the fleet, who holds it, and whether it is sanctioned

Every grant across the fleet, which host holds it, and whether your organization approved it.

The review queue: approve a grant pinned to the exact version it runs, approve it by pattern, or decline

An unapproved MCP server waits here. Approve it pinned to the exact version that was reviewed, or by pattern, and the next gate passes.

The Team gate on a real repository, with the real output of a sanctioned grant, an unapproved one, and no published bundle, is in Your first Team gate. To set up Team for your organization, get in touch.

Documentation

Start with the documentation index.

Support and questions

Have a question, need help getting set up, or want to share how you use ControlLoop? Join us on Discord.

Discord

Community

Contributing

See CONTRIBUTING.md.

For maintainers

The lockfile is authoritative. Do not install unpinned project dependencies outside uv.

uv sync --locked --all-groups
uv run ruff format --check .
uv run ruff check .
uv run mypy
uv run pytest
uv build

uv run pytest takes roughly two to three minutes locally, including real PyInstaller builds and a wall-clock performance benchmark that is sensitive to machine load.

License

Apache License 2.0. See NOTICE for attributions.

Metadata

Release files for controlloop 0.6.3

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for controlloop 0.6.3
File Size Uploaded Provenance Actions
controlloop-0.6.3.tar.gz 1.0 MB With provenance Details for controlloop-0.6.3.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for controlloop 0.6.3
File Interpreter ABI Platform Actions
controlloop-0.6.3-py3-none-any.whl Python 3 none any Details for controlloop-0.6.3-py3-none-any.whl

Total release size: 1.6 MB

Release files / controlloop-0.6.3.tar.gz

Download URL controlloop-0.6.3.tar.gz
Size 1.0 MB
Tags Source
SHA-256 checksum
How to use checksums
85a3cfc283f9f7a60f26473e7c47c422db10327dd481a4e76b64f7fb23082298
BLAKE2b-256 checksum
How to use checksums
f9d0daa520e1bf0be2aafb4a45a14d51f06210850eab5b67d87d4ecc9a099462
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 30, 2026.

Transparency log

Release files / controlloop-0.6.3-py3-none-any.whl

Download URL controlloop-0.6.3-py3-none-any.whl
Size 619.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
9551e5b923945cecaf88fb006ccdcb80d17599e2c2b5b487085e9b748d0349cb
BLAKE2b-256 checksum
How to use checksums
973f28032479aa222c24444f6d99c52479432e2980ce3a7b066459ad133d59a4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 30, 2026.

Transparency log

Security for controlloop 0.6.3

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for controlloop-0.6.3.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 30, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page