Skip to main content

agent-capability-negotiation

Zero-dependency Python plugin for canonical capability manifests and 3-step pre-delegation negotiation handshake.

Before a parent AI agent delegates a task, it has no standardized way to know what a subagent can actually do. This plugin solves pre-delegation capability blindness with a signed manifest schema and a CAPABILITY_QUERY → CAPABILITY_ADVERTISE → CAPABILITY_BIND handshake.

pipeline-status python license


Quick Start

pip install agent-capability-negotiation

Create a signed capability manifest:

from agent_capability_negotiation import create_manifest

manifest = create_manifest(
    agent_id="subagent-abc123",
    tools=["read_file", "write_file", "bash"],
    scope={"read": ["/project/**"], "write": ["/project/src/**"], "forbid": []},
    model="claude-sonnet-4-20250514",
    context_window_tokens=200000,
    skills=["python", "git"],
    ttl_seconds=3600,
    secret_key="shared-secret",
)
print(manifest.to_dict())

Negotiate a capability binding (parent side):

# 1. Query a subagent
python -m agent_capability_negotiation negotiate query \
  --parent-id parent-xyz --subagent-id subagent-abc123

# 2. (Subagent responds with advertise + signed manifest)
# 3. Bind agreed scope
python -m agent_capability_negotiation negotiate bind \
  --negotiation neg.json --agreed-scope-read /project/** \
  --agreed-scope-write /project/src/**

# 4. Check compatibility before binding
python -m agent_capability_negotiation negotiate diff-report \
  --manifest manifest.json --requires-tools read_file,write_file,bash \
  --requires-scope-read /project/**

⚡ Performance & Benchmarks

This is a zero-dependency, stdlib-only library. It performs pure in-memory JSON/HMAC operations with no I/O, network, or external process overhead. No comparative benchmark against alternatives applies — there are no comparable alternatives on PyPI.

Operation Latency
create_manifest (sign) < 0.1 ms
verify_manifest (HMAC check) < 0.1 ms
tokenize_manifest (base64) < 0.1 ms
diff_report (5 tools, 3 scopes) < 0.5 ms

Local replication: python3 benchmarks/run_benchmark.py


Why agent-capability-negotiation?

The problem: Before delegating to a subagent (via delegate_task, kanban_create, Codex subagent, etc.), a parent agent cannot discover the subagent's actual capabilities. This leads to:

  • Blind delegation — task fails at runtime because the subagent lacks a required tool
  • Capability drift — subagent tool bindings differ from what the parent assumed
  • No pre-flight signal — existing solutions bind scope only after the subagent is already running

Existing tools that don't solve this:

  • subagent-delegation-contract (cycle_45): post-delegation scope binding, not pre-delegation discovery
  • Cursor/Copilot workspace permissions: static allowlists, not negotiated per-delegation
  • multi-agent-protocol (npm): message routing schema, not capability negotiation

Trade-off decisions:

  • Zero runtime dependencies (stdlib only) — no install friction, no supply-chain risk
  • HMAC-SHA256 signatures using stdlib hmac + hashlib — no cryptography package needed
  • TTL on manifests — prevents stale capability info from causing runtime failures
  • Structured diff report — human + machine-readable incompatibility signal before binding

Key Features

  • Canonical capability manifest — signed JSON declaring tools, scoped permissions, model version, context window, skill tags
  • 3-step negotiation handshake — CAPABILITY_QUERY → CAPABILITY_ADVERTISE → CAPABILITY_BIND (or NEGOTIATE_REJECT)
  • HMAC-SHA256 signed manifests — stdlib only, prevents spoofing
  • Capability diff report — compare task requirements against manifest, surface [OK] / [WARN] / [ERROR] per requirement
  • Manifest TTL — re-advertise after long-running tasks or config changes
  • Base64 token round-trip — serialize manifests for transport over text-only channels
  • Zero runtime dependencies — pure Python stdlib

API Reference

Manifest API (agent_capability_negotiation.manifest)

from agent_capability_negotiation import (
    create_manifest,
    verify_manifest,
    manifest_from_dict,
    manifest_to_dict,
    diff_manifests,
    tokenize_manifest,
    untokenize_manifest,
    is_valid_tool_token,
    is_valid_path_glob,
    scope_covers,
    CapabilityManifest,
    ManifestError,
    SignatureError,
    ExpiredError,
)

# Create and sign a manifest
manifest = create_manifest(
    agent_id="subagent-abc123",
    tools=["read_file", "write_file", "bash"],
    scope={"read": ["/project/**"], "write": ["/project/src/**"], "forbid": ["/project/secrets/**"]},
    model="claude-sonnet-4-20250514",
    context_window_tokens=200000,
    skills=["python", "git"],
    ttl_seconds=3600,
    secret_key="shared-secret",
)

# Verify a manifest
ok, reason = verify_manifest(manifest, secret_key="shared-secret")
# -> (True, "signature ok, TTL ok, schema version compatible")

# Diff two manifests
diff = diff_manifests(manifest_a, manifest_b)
# -> DiffReport with per-tool and per-scope findings

# Token round-trip (base64-encoded signed blob)
token = tokenize_manifest(manifest, secret_key="shared-secret")
restored = untokenize_manifest(token, secret_key="shared-secret")
assert restored.agent_id == manifest.agent_id

# Validation helpers
is_valid_tool_token("read_file")      # True
is_valid_tool_token("bash:git")        # True (scoped tool)
is_valid_path_glob("/project/**/*.py")  # True
scope_covers(["/project/**"], "/project/src/app.py")  # True

Negotiation API (agent_capability_negotiation.negotiation)

from agent_capability_negotiation import (
    issue_query,
    advertise,
    bind,
    reject,
    status_of,
    diff_report,
    build_diff_report,
    Negotiation,
    NegotiationState,
    NegotiationMessage,
    Requirement,
    DiffFinding,
    DiffReport,
)

# Step 1: parent issues a CAPABILITY_QUERY
neg = issue_query(parent_id="parent-xyz", subagent_id="subagent-abc123")
# neg.state == NegotiationState.QUERY_SENT

# Step 2: subagent advertises its manifest
advertise(neg, manifest)
# neg.state == NegotiationState.ADVERTISED

# Step 3: parent binds agreed scope
bind(neg,
     agreed_scope_read=["/project/**"],
     agreed_scope_write=["/project/src/**"],
     agreed_forbid=["/project/secrets/**"],
     binding_ttl_seconds=3600,
     secret_key="shared-secret")
# neg.state == NegotiationState.BOUND

# Reject at any step
reject(neg, reason="scope too restrictive", party="subagent")
# neg.state == NegotiationState.REJECTED

# Query current status
status = status_of(neg)
# {"state": "BOUND", "binding_id": "bind-xyz", "expires_at": "..."}

# Diff task requirements against a manifest
report = build_diff_report(manifest, [
    {"kind": "tool", "name": "read_file"},
    {"kind": "tool", "name": "bash:git"},
    {"kind": "scope", "mode": "read", "patterns": ["/project/**"]},
])
# report.summary() ->
# [OK]   read_file         — supported
# [WARN] bash:git          — NOT advertised (available: bash without git scope)
# [OK]   scope read        — /project/** satisfied
# RECOMMENDATION: use git CLI wrapper instead of bash:git

CLI Reference

Module entry point

python -m agent_capability_negotiation [--version] [--help]

manifest subcommands

# Create a signed capability manifest
python -m agent_capability_negotiation manifest create \
  --agent-id subagent-abc123 \
  --tools read_file,write_file,bash \
  --scope-read /project/** \
  --scope-write /project/src/**,/project/tests/** \
  --forbid /project/secrets/** \
  --model claude-sonnet-4-20250514 \
  --context-window-tokens 200000 \
  --skills python,git \
  --ttl-seconds 3600 \
  --secret-key shared-secret

# Verify a manifest
python -m agent_capability_negotiation manifest verify \
  --manifest manifest.json --secret-key shared-secret

# Diff two manifests
python -m agent_capability_negotiation manifest diff \
  --manifest-a a.json --manifest-b b.json

# Serialize to base64 token
python -m agent_capability_negotiation manifest tokenize \
  --manifest manifest.json --secret-key shared-secret

# Deserialize from base64 token
python -m agent_capability_negotiation manifest untokenize \
  --token-file token.txt --secret-key shared-secret

negotiate subcommands

# Emit CAPABILITY_QUERY (parent -> subagent)
python -m agent_capability_negotiation negotiate query \
  --parent-id parent-xyz --subagent-id subagent-abc123

# Attach manifest (subagent response to CAPABILITY_QUERY)
python -m agent_capability_negotiation negotiate advertise \
  --negotiation query.json --manifest manifest.json --secret-key shared-secret

# Confirm CAPABILITY_BIND (parent)
python -m agent_capability_negotiation negotiate bind \
  --negotiation advertised.json \
  --agreed-scope-read /project/** \
  --agreed-scope-write /project/src/** \
  --agreed-forbid /project/secrets/** \
  --binding-ttl-seconds 3600 \
  --secret-key shared-secret

# Reject negotiation
python -m agent_capability_negotiation negotiate reject \
  --negotiation neg.json --reason "scope too restrictive" --party parent

# Show negotiation status
python -m agent_capability_negotiation negotiate status --negotiation neg.json

# Diff task requirements against manifest
python -m agent_capability_negotiation negotiate diff-report \
  --manifest manifest.json \
  --requires-tools read_file,write_file,bash:git \
  --requires-scope-read /project/** \
  --requires-scope-write /project/src/** \
  --human

Plugin Scripts

For Hermes/Claude Code plugin integration:

plugins/agent-capability-negotiation/
├── plugin.json              # Plugin manifest
├── hooks.json               # Pre-delegation handshake hook
├── rules/negotiation-protocol.md  # Schema + negotiation states
├── skills/agent-capability-negotiation/SKILL.md  # Behavioral runbook
└── scripts/
    ├── capability_manifest.py   # Standalone manifest CLI
    └── negotiate.py             # Standalone negotiation CLI
# Smoke tests for plugin scripts
bash plugins/agent-capability-negotiation/scripts/capability_manifest.py --help
bash plugins/agent-capability-negotiation/scripts/capability_manifest.py --version
bash plugins/agent-capability-negotiation/scripts/negotiate.py --help
bash plugins/agent-capability-negotiation/scripts/negotiate.py --version

Limitations

  • HMAC key distribution is out of scope — both parties must share a secret key via a channel this plugin does not manage
  • Manifest transport is not handled — the base64 token must be transmitted by an external channel (stdout, file, message bus)
  • Non-repudiation is not provided — HMAC only gives integrity, not authorship proof; use asymmetric signing if non-repudiation is required
  • Context window units are self-reported by the subagent and not independently verified
  • Path glob scope uses simple fnmatch-style patterns; complex filesystem ACLs require OS-level enforcement beyond this plugin
  • Clock skew can cause legitimate manifests to appear expired if parties' system clocks differ significantly

Non-Goals

  • Post-delegation scope drift detection (see subagent-delegation-contract, cycle_45)
  • Message routing or multi-agent coordination protocols
  • Asymmetric-key signature schemes
  • Automatic key exchange or negotiation protocol establishment
  • Integration with specific agent frameworks beyond the plugin interface

License

MIT License — see LICENSE.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

agent_capability_negotiation-0.1.0.tar.gz (37.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

agent_capability_negotiation-0.1.0-py3-none-any.whl (22.3 kB view details)

Uploaded Python 3

File details

Details for the file agent_capability_negotiation-0.1.0.tar.gz.

File metadata

File hashes

Hashes for agent_capability_negotiation-0.1.0.tar.gz
Algorithm Hash digest
SHA256 c34e7863159ad0293cbc14239c4ce14bdad0b22e524a95454fd23bf5f8d44e31
MD5 1c244bf08643c36e1987cb5667419791
BLAKE2b-256 6f8a50d80a23414da8c3e1e3c7327df41be06b0e0ed9fc4e3c1c377ec7bfb8da

See more details on using hashes here.

File details

Details for the file agent_capability_negotiation-0.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for agent_capability_negotiation-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 1f20e32e79266dd6f14f1cf55ad839e2cbf35a093cad2fe16dc1e6709f85348a
MD5 d7042c4695ff18976cfca5ae9c33481c
BLAKE2b-256 87a35881d3e44713b1c5868aa1170f68d2247fd2a9c2b8dc2e22d652d0943528

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page