Simple Agent Example
Complexity: Beginner Time to Complete: 15 minutes Prerequisites: Python 3.11+, Docker
This example demonstrates the simplest possible AgentWeave agent: a single agent with one capability that echoes back messages. Perfect for understanding AgentWeave basics.
What You'll Learn
- How to define an agent with the
SecureAgentbase class - Using the
@capabilitydecorator to expose functionality - Agent configuration with SPIFFE identity
- Running an agent with SPIRE and OPA
- Testing agent capabilities
Architecture
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
┌─────────────────────────────────────┐
│ Client (CLI/HTTP) │
└────────────┬────────────────────────┘
│
│ HTTPS + mTLS
│
┌────────────▼────────────────────────┐
│ Echo Agent │
│ │
│ Capability: "echo" │
│ - Receives message │
│ - Returns same message │
│ │
│ Security: │
│ - SPIFFE ID verification │
│ - OPA policy check │
│ - Audit logging │
└────────┬────────────────────────────┘
│
┌────┴────┐
│ SPIRE │ ← Provides identity
│ OPA │ ← Enforces policy
└─────────┘
Complete Code
Agent Implementation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
# echo_agent.py
"""
Simple Echo Agent - Demonstrates AgentWeave basics.
This agent has a single capability: echo messages back to the caller.
All security (identity, mTLS, authorization) is handled by the SDK.
"""
import asyncio
from typing import Dict, Any
from agentweave import SecureAgent, capability
from agentweave.types import TaskResult, Message, TextPart
class EchoAgent(SecureAgent):
"""
An agent that echoes messages back.
This demonstrates:
- Minimal agent implementation
- @capability decorator for exposing functionality
- Type-safe message handling
- Automatic security enforcement
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._echo_count = 0 # Track number of echoes
@capability("echo")
async def echo(self, message: str, metadata: Dict[str, Any] = None) -> TaskResult:
"""
Echo a message back to the caller.
Security automatically enforced by SDK:
- Caller's SPIFFE ID verified
- OPA policy checked before this runs
- Request/response logged for audit
Args:
message: The message to echo
metadata: Optional metadata to include in response
Returns:
TaskResult with the echoed message
"""
self._echo_count += 1
# Get caller information from request context
# This is automatically populated by the SDK
caller_id = self.context.caller_spiffe_id
# Log for observability
self.logger.info(
"Echo request received",
extra={
"caller": caller_id,
"message_length": len(message),
"echo_count": self._echo_count
}
)
# Build response
response_text = f"[Echo #{self._echo_count}] {message}"
if metadata:
response_text += f"\nMetadata: {metadata}"
# Return as A2A TaskResult
return TaskResult(
status="completed",
messages=[
Message(
role="assistant",
parts=[TextPart(text=response_text)]
)
],
artifacts=[
{
"type": "echo_stats",
"data": {
"original_message": message,
"echo_count": self._echo_count,
"caller": caller_id
}
}
]
)
@capability("stats")
async def get_stats(self) -> TaskResult:
"""
Get agent statistics.
Returns:
TaskResult with agent stats
"""
return TaskResult(
status="completed",
messages=[
Message(
role="assistant",
parts=[
TextPart(
text=f"Echo Agent Statistics\n"
f"Total echoes: {self._echo_count}\n"
f"Agent ID: {self.spiffe_id}"
)
]
)
]
)
async def main():
"""Run the echo agent."""
# Load configuration from file
# This includes identity, authorization, transport settings
agent = EchoAgent.from_config("config/agent.yaml")
# Start the agent server
# This will:
# 1. Connect to SPIRE to get SVID
# 2. Start HTTPS server with mTLS
# 3. Publish Agent Card at /.well-known/agent.json
# 4. Listen for incoming tasks
await agent.run()
if __name__ == "__main__":
asyncio.run(main())
Configuration File
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# config/agent.yaml
agent:
name: "echo"
trust_domain: "agentweave.io"
description: "Simple echo agent for testing and demonstration"
capabilities:
- name: "echo"
description: "Echo messages back to caller"
input_modes: ["text/plain", "application/json"]
output_modes: ["text/plain", "application/json"]
- name: "stats"
description: "Get agent statistics"
input_modes: []
output_modes: ["application/json"]
identity:
provider: "spiffe"
spiffe_endpoint: "unix:///run/spire/sockets/agent.sock"
allowed_trust_domains:
- "agentweave.io"
authorization:
provider: "opa"
opa_endpoint: "http://localhost:8181"
policy_path: "agentweave/authz"
default_action: "deny"
audit:
enabled: true
destination: "stdout" # For demo; use file:// in production
transport:
tls_min_version: "1.3"
peer_verification: "strict"
connection_pool:
max_connections: 10
idle_timeout_seconds: 30
server:
host: "0.0.0.0"
port: 8443
protocol: "a2a"
observability:
metrics:
enabled: true
port: 9090
logging:
level: "INFO"
format: "json"
Authorization Policy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# config/policies/authz.rego
package agentweave.authz
import rego.v1
# Default deny - nothing allowed unless explicitly permitted
default allow := false
# Allow any agent in our trust domain to call echo
allow if {
is_same_trust_domain
input.action == "echo"
}
# Allow any agent in our trust domain to get stats
allow if {
is_same_trust_domain
input.action == "stats"
}
# Helper: Check if caller is in same trust domain
is_same_trust_domain if {
caller_domain := extract_trust_domain(input.caller_spiffe_id)
caller_domain == "agentweave.io"
}
# Helper: Extract trust domain from SPIFFE ID
extract_trust_domain(spiffe_id) := domain if {
parts := split(spiffe_id, "/")
domain := parts[2] # spiffe://trust-domain/...
}
Infrastructure Setup
Docker Compose
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
# docker-compose.yaml
version: '3.8'
services:
# SPIRE Server - Issues SVIDs
spire-server:
image: ghcr.io/spiffe/spire-server:1.9.0
hostname: spire-server
volumes:
- ./spire/server.conf:/opt/spire/conf/server/server.conf:ro
- spire-server-data:/opt/spire/data
command: ["-config", "/opt/spire/conf/server/server.conf"]
networks:
- agentweave
healthcheck:
test: ["CMD", "/opt/spire/bin/spire-server", "healthcheck"]
interval: 10s
timeout: 5s
retries: 5
# SPIRE Agent - Provides Workload API
spire-agent:
image: ghcr.io/spiffe/spire-agent:1.9.0
hostname: spire-agent
depends_on:
spire-server:
condition: service_healthy
volumes:
- ./spire/agent.conf:/opt/spire/conf/agent/agent.conf:ro
- /var/run/docker.sock:/var/run/docker.sock:ro
- spire-agent-socket:/run/spire/sockets
command: ["-config", "/opt/spire/conf/agent/agent.conf"]
networks:
- agentweave
healthcheck:
test: ["CMD", "/opt/spire/bin/spire-agent", "healthcheck"]
interval: 10s
timeout: 5s
retries: 5
# OPA - Policy enforcement
opa:
image: openpolicyagent/opa:0.62.0
hostname: opa
volumes:
- ./config/policies:/policies:ro
command:
- "run"
- "--server"
- "--addr=0.0.0.0:8181"
- "/policies"
ports:
- "8181:8181"
networks:
- agentweave
healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://localhost:8181/health"]
interval: 10s
timeout: 5s
retries: 5
# Echo Agent
echo-agent:
build:
context: .
dockerfile: Dockerfile
depends_on:
spire-agent:
condition: service_healthy
opa:
condition: service_healthy
volumes:
- spire-agent-socket:/run/spire/sockets:ro
- ./config:/etc/agentweave:ro
ports:
- "8443:8443" # Agent API
- "9090:9090" # Metrics
networks:
- agentweave
environment:
- AGENTWEAVE_CONFIG=/etc/agentweave/agent.yaml
volumes:
spire-server-data:
spire-agent-socket:
networks:
agentweave:
driver: bridge
SPIRE Configuration
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# spire/server.conf
server {
bind_address = "0.0.0.0"
bind_port = "8081"
trust_domain = "agentweave.io"
data_dir = "/opt/spire/data"
log_level = "DEBUG"
}
plugins {
DataStore "sql" {
plugin_data {
database_type = "sqlite3"
connection_string = "/opt/spire/data/datastore.sqlite3"
}
}
KeyManager "memory" {
plugin_data {}
}
NodeAttestor "join_token" {
plugin_data {}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# spire/agent.conf
agent {
data_dir = "/opt/spire/data"
log_level = "DEBUG"
server_address = "spire-server"
server_port = "8081"
socket_path = "/run/spire/sockets/agent.sock"
trust_domain = "agentweave.io"
}
plugins {
NodeAttestor "join_token" {
plugin_data {}
}
KeyManager "memory" {
plugin_data {}
}
WorkloadAttestor "docker" {
plugin_data {}
}
}
Running the Example
Step 1: Clone and Setup
1
2
3
4
5
6
# Clone examples repository
git clone https://github.com/agentweave/examples.git
cd examples/simple-agent
# Install dependencies
pip install -r requirements.txt
Step 2: Start Infrastructure
1
2
3
4
5
6
7
8
9
10
11
12
# Start SPIRE and OPA
docker-compose up -d spire-server spire-agent opa
# Wait for services to be healthy
docker-compose ps
# Register the echo agent workload with SPIRE
docker-compose exec spire-server \
/opt/spire/bin/spire-server entry create \
-spiffeID spiffe://agentweave.io/agent/echo \
-parentID spiffe://agentweave.io/agent/spire-agent \
-selector docker:label:com.docker.compose.service:echo-agent
Step 3: Run the Agent
1
2
3
4
5
# Option 1: Run with Docker Compose
docker-compose up echo-agent
# Option 2: Run locally (requires SPIRE agent socket access)
python echo_agent.py
Step 4: Test the Agent
1
2
3
4
5
6
7
8
9
10
11
12
13
# Install AgentWeave CLI
pip install agentweave-cli
# Call the echo capability
agentweave call \
--target spiffe://agentweave.io/agent/echo \
--capability echo \
--data '{"message": "Hello, AgentWeave!"}'
# Get agent statistics
agentweave call \
--target spiffe://agentweave.io/agent/echo \
--capability stats
Expected Output
Echo Capability Response
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
{
"status": "completed",
"messages": [
{
"role": "assistant",
"parts": [
{
"type": "text",
"text": "[Echo #1] Hello, AgentWeave!"
}
]
}
],
"artifacts": [
{
"type": "echo_stats",
"data": {
"original_message": "Hello, AgentWeave!",
"echo_count": 1,
"caller": "spiffe://agentweave.io/client/cli"
}
}
]
}
Agent Logs
1
2
3
4
5
6
7
8
{
"timestamp": "2025-12-07T10:30:15Z",
"level": "INFO",
"message": "Echo request received",
"caller": "spiffe://agentweave.io/client/cli",
"message_length": 20,
"echo_count": 1
}
Metrics
1
2
3
4
5
6
7
# HELP agentweave_tasks_total Total tasks processed
# TYPE agentweave_tasks_total counter
agentweave_tasks_total{capability="echo",status="completed"} 1.0
# HELP agentweave_task_duration_seconds Task processing duration
# TYPE agentweave_task_duration_seconds histogram
agentweave_task_duration_seconds_bucket{capability="echo",le="0.01"} 1.0
Key Takeaways
What the SDK Did Automatically
- Identity Management
- Connected to SPIRE agent
- Fetched X.509 SVID
- Set up automatic rotation
- Security
- Established mTLS connection
- Verified caller's SPIFFE ID
- Checked OPA policy before executing capability
- Logged all requests for audit
- Communication
- Started HTTPS server
- Published Agent Card at
/.well-known/agent.json - Handled A2A protocol encoding/decoding
- Observability
- Exposed Prometheus metrics
- Structured JSON logging
- Request tracing
What You Wrote
- Business logic (12 lines in
echo()method) - Configuration (YAML)
- Authorization policy (Rego)
Next Steps
- Add More Capabilities: Extend with
@capabilitydecorators - Multi-Agent: See Multi-Agent Example to call other agents
- Custom Policies: Learn Authorization Guide
- Production Deploy: See Kubernetes Deployment
Troubleshooting
Agent Won't Start
1
2
3
4
5
6
7
# Check SPIRE agent is running
docker-compose ps spire-agent
# Check SVID was issued
docker-compose exec spire-server \
/opt/spire/bin/spire-server entry show \
-spiffeID spiffe://agentweave.io/agent/echo
Authorization Denied
1
2
3
4
5
6
7
8
# Test OPA policy directly
curl -X POST http://localhost:8181/v1/data/agentweave/authz \
-d '{
"input": {
"caller_spiffe_id": "spiffe://agentweave.io/client/test",
"action": "echo"
}
}'
Connection Refused
1
2
3
4
5
# Check agent is listening
netstat -tlnp | grep 8443
# Check firewall/Docker network
docker-compose logs echo-agent
Complete Code: GitHub Repository