pigeonlabsHQ/pigeon

Delegated authority for AI agents. A Pigeon Pass says what an agent may do.

36

stars

18

commits

Python

primary language

Sep 6, 2026

updated

README

Pigeon

Your agent spawned a sub-agent and handed it the same API key. That sub-agent can now deploy to production, read the payments database, and merge to main.

Pigeon stops that. You hand the child a Pigeon Pass: a narrowed, signed credential for what it may do, not a copy of everything you can do.

Install

Python 3.12 or newer.

git clone https://github.com/pigeonlabsHQ/pigeon.git
cd pigeon
pip install .

The whole idea, in 20 lines

from pigeon import grant, verify

authority = grant(
    subject="agent:deployer",
    capabilities=["deploy"],
    resources=["environment:staging"],
)

allowed = verify(authority, action="deploy", resource="environment:staging")
assert allowed.allowed

denied = verify(authority, action="deploy", resource="environment:production")
assert not denied.allowed
assert denied.reason_code == "RESOURCE_NOT_ALLOWED"
print(denied.reason_code, denied.message, denied.details)

verify never returns a bare boolean. A denial includes a reason code, a message, and the comparison that failed (requested vs allowed).

Try it without writing that yourself:

python examples/01_infrastructure.py
python demo/agent.py

Where it goes in an agent

There is no Pigeon server to connect to. You change two places you already have:

  1. Spawn. Where you would have copied an API key into a sub-agent, call delegate(...) and give the child a Pass.
  2. Tool. Where the side effect happens (deploy, query, MCP tool), call verify(...) and do not run the tool if it is denied.

Keep the real secret on the runner. The child carries the Pass.

from pigeon import delegate, grant, verify, DelegationError

parent = grant(
    subject="agent:orchestrator",
    capabilities=["deploy", "open_pr"],
    resources=["environment:staging", "repo:acme/api"],
    constraints={"max_deploys_per_hour": 3},
)

worker = delegate(
    parent,
    subject="agent:pr-bot",
    capabilities=["open_pr"],
    resources=["repo:acme/api"],
    constraints={"max_deploys_per_hour": 3},  # cannot drop a parent constraint
)

result = verify(worker, action="open_pr", resource="repo:acme/api")
assert result.allowed

denied = verify(worker, action="deploy", resource="environment:staging")
assert denied.reason_code == "CAPABILITY_NOT_GRANTED"

try:
    delegate(worker, "agent:rogue", ["open_pr", "deploy"], ["repo:acme/api"])
except DelegationError as exc:
    assert exc.reason_code == "PRIVILEGE_ESCALATION"

A child cannot add capabilities, widen resources, raise a bound, or drop a parent constraint. If Pigeon cannot prove the child is narrower, it rejects.

If the runner never calls verify, the Pass is decoration.

MCP middleware

This is an enforcement point, not part of the protocol. The client mints a narrower Pass per tool call. The server verifies it before the tool runs.

from pigeon import grant
from pigeon.integrations.mcp import execute_tool, pass_for_tool

parent = grant(
    subject="agent:github",
    capabilities=["create_issue", "merge_pr"],
    resources=["mcp:github"],
)

tool_pass = pass_for_tool(parent, "create_issue", "mcp:github")

def create_issue(*, title, body):
    return {"created": True, "title": title}

ok = execute_tool(tool_pass, "create_issue", "mcp:github",
                  {"title": "bump deps", "body": "automated"}, create_issue)
assert ok["allowed"]

no = execute_tool(tool_pass, "merge_pr", "mcp:github",
                  {"title": "nope", "body": "nope"}, create_issue)
assert no["reason_code"] == "CAPABILITY_NOT_GRANTED"

Identity tells you who the agent is. Authority tells you what it may do.

CLI

pigeon keygen
pigeon inspect pass.json

What this is not

Pigeon is a small primitive. It is not a platform, a policy engine, an identity provider, or a key custodian. It does not stop prompt injection. It bounds blast radius along the dimensions you put on the Pass, and only those.

  • Protocol: SPEC.md
  • Limits: SECURITY.md
  • More scripts: examples/ (infrastructure, data, code, then payments)

Contributors

buttermilkcx

18 commits

pigeonlabsHQ/pigeon

Delegated authority for AI agents. A Pigeon Pass says what an agent may do.

36

stars

18

commits

Python

primary language

Sep 6, 2026

updated

README

Pigeon

Your agent spawned a sub-agent and handed it the same API key. That sub-agent can now deploy to production, read the payments database, and merge to main.

Pigeon stops that. You hand the child a Pigeon Pass: a narrowed, signed credential for what it may do, not a copy of everything you can do.

Install

Python 3.12 or newer.

git clone https://github.com/pigeonlabsHQ/pigeon.git
cd pigeon
pip install .

The whole idea, in 20 lines

from pigeon import grant, verify

authority = grant(
    subject="agent:deployer",
    capabilities=["deploy"],
    resources=["environment:staging"],
)

allowed = verify(authority, action="deploy", resource="environment:staging")
assert allowed.allowed

denied = verify(authority, action="deploy", resource="environment:production")
assert not denied.allowed
assert denied.reason_code == "RESOURCE_NOT_ALLOWED"
print(denied.reason_code, denied.message, denied.details)

verify never returns a bare boolean. A denial includes a reason code, a message, and the comparison that failed (requested vs allowed).

Try it without writing that yourself:

python examples/01_infrastructure.py
python demo/agent.py

Where it goes in an agent

There is no Pigeon server to connect to. You change two places you already have:

  1. Spawn. Where you would have copied an API key into a sub-agent, call delegate(...) and give the child a Pass.
  2. Tool. Where the side effect happens (deploy, query, MCP tool), call verify(...) and do not run the tool if it is denied.

Keep the real secret on the runner. The child carries the Pass.

from pigeon import delegate, grant, verify, DelegationError

parent = grant(
    subject="agent:orchestrator",
    capabilities=["deploy", "open_pr"],
    resources=["environment:staging", "repo:acme/api"],
    constraints={"max_deploys_per_hour": 3},
)

worker = delegate(
    parent,
    subject="agent:pr-bot",
    capabilities=["open_pr"],
    resources=["repo:acme/api"],
    constraints={"max_deploys_per_hour": 3},  # cannot drop a parent constraint
)

result = verify(worker, action="open_pr", resource="repo:acme/api")
assert result.allowed

denied = verify(worker, action="deploy", resource="environment:staging")
assert denied.reason_code == "CAPABILITY_NOT_GRANTED"

try:
    delegate(worker, "agent:rogue", ["open_pr", "deploy"], ["repo:acme/api"])
except DelegationError as exc:
    assert exc.reason_code == "PRIVILEGE_ESCALATION"

A child cannot add capabilities, widen resources, raise a bound, or drop a parent constraint. If Pigeon cannot prove the child is narrower, it rejects.

If the runner never calls verify, the Pass is decoration.

MCP middleware

This is an enforcement point, not part of the protocol. The client mints a narrower Pass per tool call. The server verifies it before the tool runs.

from pigeon import grant
from pigeon.integrations.mcp import execute_tool, pass_for_tool

parent = grant(
    subject="agent:github",
    capabilities=["create_issue", "merge_pr"],
    resources=["mcp:github"],
)

tool_pass = pass_for_tool(parent, "create_issue", "mcp:github")

def create_issue(*, title, body):
    return {"created": True, "title": title}

ok = execute_tool(tool_pass, "create_issue", "mcp:github",
                  {"title": "bump deps", "body": "automated"}, create_issue)
assert ok["allowed"]

no = execute_tool(tool_pass, "merge_pr", "mcp:github",
                  {"title": "nope", "body": "nope"}, create_issue)
assert no["reason_code"] == "CAPABILITY_NOT_GRANTED"

Identity tells you who the agent is. Authority tells you what it may do.

CLI

pigeon keygen
pigeon inspect pass.json

What this is not

Pigeon is a small primitive. It is not a platform, a policy engine, an identity provider, or a key custodian. It does not stop prompt injection. It bounds blast radius along the dimensions you put on the Pass, and only those.

  • Protocol: SPEC.md
  • Limits: SECURITY.md
  • More scripts: examples/ (infrastructure, data, code, then payments)

Contributors

buttermilkcx

18 commits

Languages

Python

100.0%