From 147e41c888b073012577850939d14b7896605784 Mon Sep 17 00:00:00 2001 From: haroldmalikfrimpong-ops Date: Tue, 31 Mar 2026 03:48:40 +0100 Subject: [PATCH] feat: add AgentID cryptographic identity verification for agents Adds identity verification tools so ChatDev agents (CEO, Programmer, Reviewer, etc.) can prove their identity before collaborating on sensitive tasks. Closes #587. - functions/function_calling/agentid.py: 4 tools (verify, register, discover, connect) loadable via FunctionManager - functions/edge/conditions.py: 3 identity-aware edge conditions (identity_verified, identity_not_verified, trust_score_above_threshold) - yaml_instance/chatdev_with_identity.yaml: example workflow with identity gate pattern - docs/user_guide/en/agentid_identity.md: integration documentation Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/user_guide/en/agentid_identity.md | 143 +++++++++++++++++ functions/edge/conditions.py | 28 ++++ functions/function_calling/agentid.py | 192 +++++++++++++++++++++++ yaml_instance/chatdev_with_identity.yaml | 162 +++++++++++++++++++ 4 files changed, 525 insertions(+) create mode 100644 docs/user_guide/en/agentid_identity.md create mode 100644 functions/function_calling/agentid.py create mode 100644 yaml_instance/chatdev_with_identity.yaml diff --git a/docs/user_guide/en/agentid_identity.md b/docs/user_guide/en/agentid_identity.md new file mode 100644 index 0000000000..5cb27f1e33 --- /dev/null +++ b/docs/user_guide/en/agentid_identity.md @@ -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) diff --git a/functions/edge/conditions.py b/functions/edge/conditions.py index 609f0cee01..8c971bbfbb 100755 --- a/functions/edge/conditions.py +++ b/functions/edge/conditions.py @@ -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 diff --git a/functions/function_calling/agentid.py b/functions/function_calling/agentid.py new file mode 100644 index 0000000000..6ce5c23462 --- /dev/null +++ b/functions/function_calling/agentid.py @@ -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)}) diff --git a/yaml_instance/chatdev_with_identity.yaml b/yaml_instance/chatdev_with_identity.yaml new file mode 100644 index 0000000000..9478778c04 --- /dev/null +++ b/yaml_instance/chatdev_with_identity.yaml @@ -0,0 +1,162 @@ +version: 0.4.0 +vars: + COMMON_PROMPT: >- + ChatDev is a software company powered by multiple intelligent agents with + cryptographic identity verification via AgentID. Each agent must verify + the identity of collaborating agents before trusting their output. +graph: + id: chatdev_identity_demo + description: >- + Demonstrates AgentID identity verification in a ChatDev pipeline. + The Identity Verifier checks each agent before allowing them to + collaborate. Unverified agents are blocked from the pipeline. + is_majority_voting: false + start: + - USER + - Identity Gate Prompt + + nodes: + - id: USER + type: passthrough + config: {} + + - id: Identity Gate Prompt + type: literal + config: + content: >- + Before any agent can participate in this software development + pipeline, verify their identity using the AgentID tools. + + 1. Call verify_agent_identity for each agent_id in the pipeline. + 2. Only allow agents with verified=true and trust_score >= 0.7. + 3. Output a JSON summary: {"all_verified": true/false, "agents": [...]}. + + If all agents are verified, output "VERIFIED: All agents passed + identity checks" at the end. + role: user + + - id: Identity Verifier + type: agent + config: + provider: openai + base_url: ${BASE_URL} + api_key: ${API_KEY} + name: gpt-4o + role: >- + ${COMMON_PROMPT} + + You are the Identity Verifier. Your sole responsibility is to + verify the cryptographic identity of every agent in the pipeline + using the AgentID protocol. Call the verify_agent_identity tool + for each agent_id you are given. Summarise the results and clearly + state whether all agents passed verification. + tooling: + - type: function + config: + tools: + - name: verify_agent_identity + - name: discover_agents + - name: register_agent_identity + params: + temperature: 0.1 + max_tokens: 1000 + + - id: Programmer + type: agent + config: + provider: openai + base_url: ${BASE_URL} + api_key: ${API_KEY} + name: gpt-4o + role: >- + ${COMMON_PROMPT} + + You are Programmer. You write code to fulfil the user's task. + Your identity has been cryptographically verified via AgentID. + You can write Python, JavaScript, and other languages. + tooling: + - type: function + config: + tools: + - name: save_file + - name: read_file_segment + - name: describe_available_files + - name: list_directory + - name: verify_agent_identity + params: + temperature: 0.3 + + - id: Code Reviewer + type: agent + config: + provider: openai + base_url: ${BASE_URL} + api_key: ${API_KEY} + name: gpt-4o + role: >- + ${COMMON_PROMPT} + + You are Code Reviewer. You review code written by the Programmer + for correctness, security, and quality. Your identity has been + cryptographically verified via AgentID. + + Before reviewing, you may verify the Programmer's identity using + verify_agent_identity to ensure the code came from a trusted source. + tooling: + - type: function + config: + tools: + - name: read_file_segment + - name: describe_available_files + - name: list_directory + - name: verify_agent_identity + params: + temperature: 0.2 + + - id: FINAL + type: passthrough + config: {} + + edges: + # Identity gate: verify agents before coding begins + - from: Identity Gate Prompt + to: Identity Verifier + condition: "true" + + # Only proceed to Programmer if identity checks pass + - from: Identity Verifier + to: Programmer + condition: + type: keyword + config: + any: + - "VERIFIED" + none: [] + regex: [] + case_sensitive: false + + # Pass user task context to Programmer + - from: USER + to: Programmer + trigger: false + carry_data: true + keep_message: true + + # Programmer output goes to Code Reviewer + - from: Programmer + to: Code Reviewer + condition: "true" + carry_data: true + + # Pass user task context to Code Reviewer + - from: USER + to: Code Reviewer + trigger: false + carry_data: true + keep_message: true + + # Code Reviewer output is the final result + - from: Code Reviewer + to: FINAL + condition: "true" + carry_data: true