Metadata-Version: 2.4
Name: agent-capability-negotiation
Version: 0.1.0
Summary: Canonical capability manifest schema + 3-step pre-delegation negotiation handshake with HMAC-signed manifests, capability diff, and TTL. Zero runtime dependencies.
Author: repo-factory
License: MIT
Project-URL: Homepage, https://github.com/prasad-a-abhishek/agent-capability-negotiation
Project-URL: Issues, https://github.com/prasad-a-abhishek/agent-capability-negotiation/issues
Keywords: agent,subagent,delegation,capability,negotiation,hmac,manifest,ai,ai-agents,ai-coding
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.8
Description-Content-Type: text/markdown
License-File: LICENSE
Dynamic: license-file

# 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](https://img.shields.io/badge/pipeline-327%20tests%20passing-brightgreen)](#)
[![python](https://img.shields.io/badge/python-3.8%2B-blue)](#)
[![license](https://img.shields.io/badge/license-MIT-blue)](#)

---

## Quick Start

```bash
pip install agent-capability-negotiation
```

**Create a signed capability manifest:**

```python
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):**

```bash
# 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`)

```python
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`)

```python
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

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

### `manifest` subcommands

```bash
# 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

```bash
# 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
```

```bash
# 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](LICENSE).
