Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
143 changes: 143 additions & 0 deletions docs/user_guide/en/agentid_identity.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
# AgentID Identity Verification

ChatDev agents collaborate as CEO, CTO, Programmer, Tester — but by default have no verifiable identity. [AgentID](https://getagentid.dev) adds cryptographic identity verification so agents can prove who they are before trusting each other with sensitive operations.

## Why Identity Matters

In a multi-agent pipeline, any agent could be compromised or impersonated. AgentID solves this by issuing ECDSA P-256 certificates per agent and providing a verification API that other agents can call at runtime.

**Use cases:**
- Verify the Programmer before accepting its code
- Gate the Code Reviewer on a minimum trust score
- Audit which verified agents participated in a build
- Discover agents by capability (e.g. "find me a deployment agent")

## Setup

### 1. Get an API Key

Sign up at [getagentid.dev](https://getagentid.dev) and create an API key.

### 2. Set Environment Variables

```bash
export AGENTID_API_KEY="aid_your_key_here"
# Optional: override the API endpoint
# export AGENTID_BASE_URL="https://your-instance.example.com/api/v1"
```

### 3. Install httpx

The AgentID tools require `httpx`:

```bash
pip install httpx
```

## Available Tools

All tools are in `functions/function_calling/agentid.py`:

| Tool | Auth Required | Description |
|------|:---:|-------------|
| `verify_agent_identity` | No | Verify an agent's cryptographic identity |
| `register_agent_identity` | Yes | Register a new agent and get a certificate |
| `discover_agents` | No | Search the registry by capability |
| `send_verified_message` | Yes | Send a verified message between agents |

## Usage in Workflows

### Add Tools to Agent Nodes

```yaml
- id: Programmer
type: agent
config:
provider: openai
name: gpt-4o
role: "You are Programmer..."
tooling:
- type: function
config:
tools:
- name: verify_agent_identity
- name: register_agent_identity
```

### Identity Gate Pattern

Add an Identity Verifier node at the start of your pipeline that checks all agents before allowing work to proceed:

```yaml
- id: Identity Verifier
type: agent
config:
provider: openai
name: gpt-4o
role: "Verify all agent identities before the pipeline starts..."
tooling:
- type: function
config:
tools:
- name: verify_agent_identity
```

Then use edge conditions to gate downstream nodes:

```yaml
edges:
- from: Identity Verifier
to: Programmer
condition:
type: keyword
config:
any: ["VERIFIED"]
```

### Edge Conditions

Three identity-aware edge conditions are available in `functions/edge/conditions.py`:

- `identity_verified` — True when output contains `"verified": true`
- `identity_not_verified` — True when verification failed
- `trust_score_above_threshold` — True when trust score >= 0.7

## Example Workflow

See `yaml_instance/chatdev_with_identity.yaml` for a complete example that:

1. Verifies all agents before coding begins
2. Gates the Programmer on successful verification
3. Allows the Code Reviewer to re-verify the Programmer's identity
4. Routes to a final output only after reviewed by verified agents

## Architecture

```
User Task
┌──────────────────┐
│ Identity Verifier │ ← verify_agent_identity()
│ (gate node) │
└────────┬─────────┘
│ VERIFIED
┌──────────────────┐
│ Programmer │ ← writes code (verified identity)
└────────┬─────────┘
┌──────────────────┐
│ Code Reviewer │ ← can re-verify Programmer
└────────┬─────────┘
Output
```

## Reference

- [AgentID Documentation](https://getagentid.dev/docs)
- [AgentID GitHub](https://github.com/haroldmalikfrimpong-ops/getagentid)
- [Issue #587](https://github.com/OpenBMB/ChatDev/issues/587)
28 changes: 28 additions & 0 deletions functions/edge/conditions.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,31 @@ def should_stop_loop(data: str) -> bool:
if verdict is None:
return False
return verdict in {"STOP", "DONE"}


# -- AgentID identity conditions --

def identity_verified(data: str) -> bool:
"""Return True when agent output contains a successful identity verification."""
lower = data.lower()
return '"verified": true' in lower or '"verified":true' in lower


def identity_not_verified(data: str) -> bool:
"""Return True when agent output indicates identity verification failed."""
return not identity_verified(data)


def trust_score_above_threshold(data: str) -> bool:
"""Return True when a verified agent's trust score is >= 0.7."""
import json as _json
try:
result = _json.loads(data)
return float(result.get("trust_score", 0)) >= 0.7
except (ValueError, TypeError, _json.JSONDecodeError):
# Try to extract from text output
import re
match = re.search(r'"trust_score"\s*:\s*([\d.]+)', data)
if match:
return float(match.group(1)) >= 0.7
return False
192 changes: 192 additions & 0 deletions functions/function_calling/agentid.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
"""AgentID identity verification tools for ChatDev agents.

Provides cryptographic identity verification using the AgentID protocol
(https://getagentid.dev). Enables ChatDev agents to verify each other's
identities before delegating tasks or sharing sensitive data.

Environment variables:
AGENTID_API_KEY -- Bearer token for authenticated endpoints (register, connect).
AGENTID_BASE_URL -- Override the default https://getagentid.dev/api/v1 endpoint.
"""

import json
import os
from typing import Optional

import httpx

_BASE_URL = os.getenv("AGENTID_BASE_URL", "https://getagentid.dev/api/v1").rstrip("/")
_TIMEOUT = 15


def _auth_headers() -> dict:
key = os.getenv("AGENTID_API_KEY", "")
if not key:
raise ValueError(
"AGENTID_API_KEY environment variable is required. "
"Get one at https://getagentid.dev"
)
return {"Authorization": f"Bearer {key}"}


def verify_agent_identity(agent_id: str) -> str:
"""Verify an AI agent's cryptographic identity via AgentID.

Use this before trusting another agent with sensitive operations
like code execution, deployment, or data access. Returns the
agent's verification status, trust score, certificate validity,
and capabilities.

This is a public endpoint -- no API key is required.

Args:
agent_id: The AgentID identifier to verify (e.g. "agent_abc123").

Returns:
A JSON string with verification results including
``verified``, ``trust_score``, ``certificate``, and ``capabilities``.
"""
try:
resp = httpx.post(
f"{_BASE_URL}/agents/verify",
json={"agent_id": agent_id},
timeout=_TIMEOUT,
follow_redirects=True,
)
data = resp.json()
if resp.status_code >= 400:
return json.dumps({"error": data.get("error", f"HTTP {resp.status_code}")})
return json.dumps(data, indent=2, default=str)
except Exception as exc:
return json.dumps({"error": str(exc)})


def register_agent_identity(
name: str,
description: str = "",
capabilities: str = "",
) -> str:
"""Register a new agent with AgentID and obtain a cryptographic certificate.

The certificate contains an ECDSA P-256 key pair and a unique
``agent_id`` that other agents can use to verify this agent.

Requires the AGENTID_API_KEY environment variable.

Args:
name: Human-readable name for the agent (e.g. "Programmer", "Code Reviewer").
description: Short description of the agent's purpose.
capabilities: Comma-separated capability tags (e.g. "coding,review,testing").

Returns:
A JSON string with ``agent_id``, ``certificate``, and key material.
"""
try:
caps = [c.strip() for c in capabilities.split(",") if c.strip()] if capabilities else []
resp = httpx.post(
f"{_BASE_URL}/agents/register",
json={
"name": name,
"description": description,
"capabilities": caps,
"platform": "chatdev",
},
headers=_auth_headers(),
timeout=_TIMEOUT,
follow_redirects=True,
)
data = resp.json()
if resp.status_code >= 400:
return json.dumps({"error": data.get("error", f"HTTP {resp.status_code}")})
return json.dumps(data, indent=2, default=str)
except Exception as exc:
return json.dumps({"error": str(exc)})


def discover_agents(
capability: str = "",
owner: str = "",
limit: int = 20,
) -> str:
"""Search the AgentID registry for verified agents by capability.

Useful for finding agents that can handle a specific task
(e.g. "coding", "review", "testing", "deployment").

This is a public endpoint -- no API key is required.

Args:
capability: Filter by capability keyword.
owner: Filter by owner or organisation name.
limit: Maximum number of results (1-100).

Returns:
A JSON string with a list of matching agents and their trust scores.
"""
try:
params: dict = {"limit": min(int(limit), 100)}
if capability:
params["capability"] = capability
if owner:
params["owner"] = owner
resp = httpx.get(
f"{_BASE_URL}/agents/discover",
params=params,
timeout=_TIMEOUT,
follow_redirects=True,
)
data = resp.json()
if resp.status_code >= 400:
return json.dumps({"error": data.get("error", f"HTTP {resp.status_code}")})
return json.dumps(data, indent=2, default=str)
except Exception as exc:
return json.dumps({"error": str(exc)})


def send_verified_message(
from_agent: str,
to_agent: str,
payload: str,
message_type: str = "request",
) -> str:
"""Send a cryptographically verified message between two agents.

Both agents' identities are verified before delivery. Returns a
trust check indicating whether both parties are verified and a
recommendation for data exchange safety.

Requires the AGENTID_API_KEY environment variable.

Args:
from_agent: The agent_id of the sending agent.
to_agent: The agent_id of the receiving agent.
payload: JSON string with the message payload.
message_type: Message type: "request", "response", or "notification".

Returns:
A JSON string with delivery status and trust check results.
"""
try:
payload_dict = json.loads(payload) if payload else {}
except json.JSONDecodeError:
return json.dumps({"error": "payload must be a valid JSON string"})

try:
resp = httpx.post(
f"{_BASE_URL}/agents/connect",
json={
"from_agent": from_agent,
"to_agent": to_agent,
"message_type": message_type,
"payload": payload_dict,
},
headers=_auth_headers(),
timeout=_TIMEOUT,
follow_redirects=True,
)
data = resp.json()
if resp.status_code >= 400:
return json.dumps({"error": data.get("error", f"HTTP {resp.status_code}")})
return json.dumps(data, indent=2, default=str)
except Exception as exc:
return json.dumps({"error": str(exc)})
Loading