Decorators Module
Decorators Module
The decorators module (agentweave.decorators) provides security decorators that enable declarative security controls on agent methods, including capability registration, peer verification, and audit logging.
Decorators
@capability
1
def capability(name: str, description: Optional[str] = None)
Decorator to register a method as an agent capability.
This decorator:
- Registers the method in the capability registry
- Auto-generates capability metadata
- Wraps the method with authorization checks
Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
name |
str |
required | The name of the capability |
description |
str |
None |
Optional description (defaults to method docstring) |
Usage:
1
2
3
4
5
6
from agentweave import SecureAgent, capability
class DataAgent(SecureAgent):
@capability("search", description="Search the database")
async def search(self, query: str) -> dict:
return {"results": [...]}
Notes:
- The decorated method must be async
- The method is automatically discovered by
SecureAgent.register_capabilities() - Authorization checks are performed automatically when the capability is invoked
- The capability name should be lowercase with underscores (validated by
Capabilitymodel)
Example with Multiple Capabilities:
1
2
3
4
5
6
7
8
9
10
11
12
class DataAgent(SecureAgent):
@capability("search", description="Search the database")
async def search(self, query: str) -> dict:
return {"results": [...]}
@capability("process", description="Process data")
async def process(self, data: dict) -> dict:
return {"status": "processed"}
@capability("aggregate", description="Aggregate results")
async def aggregate(self, items: list) -> dict:
return {"total": sum(items)}
@requires_peer
1
def requires_peer(spiffe_pattern: str)
Decorator to restrict a capability to specific SPIFFE ID patterns.
This decorator enforces that only callers matching the given SPIFFE ID pattern can invoke the capability. Supports wildcards using fnmatch syntax.
Parameters:
| Parameter | Type | Description |
|---|---|---|
spiffe_pattern |
str |
SPIFFE ID pattern (e.g., "spiffe://domain/agent/*") |
Pattern Syntax:
*- Matches any sequence of characters?- Matches any single character[seq]- Matches any character in seq[!seq]- Matches any character not in seq
Usage:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
from agentweave import SecureAgent, capability, requires_peer
class SecureDataAgent(SecureAgent):
@capability("search")
@requires_peer("spiffe://agentweave.io/agent/*")
async def search(self, query: str) -> dict:
# Only agents in agentweave.io trust domain can call this
return {"results": [...]}
@capability("delete_data")
@requires_peer("spiffe://agentweave.io/agent/admin-*")
async def delete_data(self, id: str) -> dict:
# Only admin agents can call this
return {"deleted": id}
Stacking Order:
This decorator should be used in combination with @capability and placed after it in the decorator stack:
1
2
3
4
@capability("delete_data") # First
@requires_peer("spiffe://...") # Second
async def delete_data(self, id: str) -> dict:
pass
Raises:
| Exception | Description |
|---|---|
PermissionError |
If caller's SPIFFE ID doesn't match the pattern |
PermissionError |
If no request context is available |
Examples:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# Allow any agent in the trust domain
@capability("public_data")
@requires_peer("spiffe://agentweave.io/agent/*")
async def get_public_data(self) -> dict:
return {"data": "public"}
# Allow only specific agent
@capability("admin_action")
@requires_peer("spiffe://agentweave.io/agent/admin")
async def admin_action(self) -> dict:
return {"status": "ok"}
# Allow agents with specific prefix
@capability("worker_task")
@requires_peer("spiffe://agentweave.io/agent/worker-*")
async def worker_task(self, task: dict) -> dict:
return {"result": "processed"}
# Allow multiple patterns (stack multiple decorators)
@capability("cross_domain")
@requires_peer("spiffe://agentweave.io/agent/*")
@requires_peer("spiffe://partner.io/agent/*")
async def cross_domain_task(self) -> dict:
return {"status": "ok"}
@audit_log
1
def audit_log(level: str = "info")
Decorator to enforce audit logging for capability calls.
This decorator logs:
- Caller identity
- Action (capability name)
- Result (success/failure)
- Timing information
Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
level |
str |
"info" |
Logging level ("debug", "info", "warning", "error") |
Usage:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
from agentweave import SecureAgent, capability, audit_log
class AuditedAgent(SecureAgent):
@capability("delete_data")
@audit_log(level="warning")
async def delete_data(self, id: str) -> dict:
# This call will be audit logged at WARNING level
return {"deleted": id}
@capability("sensitive_operation")
@audit_log(level="error")
async def sensitive_operation(self) -> dict:
# Critical operations logged at ERROR level
return {"status": "completed"}
Audit Log Format:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
"task_id": "550e8400-e29b-41d4-a716-446655440000",
"caller": "spiffe://agentweave.io/agent/caller-agent",
"action": "delete_data",
"success": True,
"duration_ms": 123.45
}
# On error:
{
"task_id": "550e8400-e29b-41d4-a716-446655440000",
"caller": "spiffe://agentweave.io/agent/caller-agent",
"action": "delete_data",
"success": False,
"duration_ms": 45.67,
"error": "Database connection failed"
}
Stacking Order:
This decorator can be stacked with @capability and @requires_peer:
1
2
3
4
5
@capability("admin_delete") # First
@requires_peer("spiffe://.../admin-*") # Second
@audit_log(level="warning") # Third
async def admin_delete(self, id: str) -> dict:
pass
Valid Levels:
| Level | Use Case |
|---|---|
"debug" |
Development and troubleshooting |
"info" |
Normal operations (default) |
"warning" |
Sensitive operations (data modification) |
"error" |
Critical operations (security-relevant) |
Raises:
| Exception | Description |
|---|---|
ValueError |
If an invalid log level is provided |
Examples:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# Standard audit logging
@capability("update_user")
@audit_log(level="info")
async def update_user(self, user_id: str, data: dict) -> dict:
return {"updated": user_id}
# High-priority audit for sensitive operations
@capability("delete_user")
@audit_log(level="warning")
async def delete_user(self, user_id: str) -> dict:
return {"deleted": user_id}
# Critical security operations
@capability("grant_admin_access")
@requires_peer("spiffe://agentweave.io/agent/security-admin")
@audit_log(level="error")
async def grant_admin_access(self, user_id: str) -> dict:
return {"granted": user_id}
Functions
get_registered_capabilities
1
def get_registered_capabilities() -> dict[str, CapabilityMetadata]
Get all registered capabilities.
Returns: dict[str, CapabilityMetadata] - Dictionary mapping capability names to their metadata
Example:
1
2
3
4
5
6
from agentweave.decorators import get_registered_capabilities
# After defining capabilities
capabilities = get_registered_capabilities()
for name, metadata in capabilities.items():
print(f"{name}: {metadata.description}")
CapabilityMetadata Structure:
1
2
3
4
5
6
7
@dataclass
class CapabilityMetadata:
name: str
description: Optional[str]
handler: Optional[Callable]
requires_peer_patterns: list[str]
audit_level: Optional[str]
clear_capability_registry
1
def clear_capability_registry() -> None
Clear the capability registry.
This is primarily useful for testing purposes to ensure a clean state between tests.
Example:
1
2
3
4
5
from agentweave.decorators import clear_capability_registry
# In test teardown
def teardown():
clear_capability_registry()
Decorator Stacking
When using multiple decorators, follow this order:
1
2
3
4
5
@capability("name", description="...") # 1. Always first
@requires_peer("spiffe://...") # 2. Peer verification
@audit_log(level="warning") # 3. Audit logging
async def my_capability(self, ...) -> ...:
pass
Execution Order (when method is called):
@audit_log- Start timing and setup audit@requires_peer- Verify caller's SPIFFE ID@capability- Check OPA authorization- Method executes
@audit_log- Log result and duration
Complete Example
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
from agentweave import SecureAgent, capability, requires_peer, audit_log
class DataManagementAgent(SecureAgent):
"""Agent for managing sensitive data with comprehensive security."""
@capability("search", description="Search the database")
@requires_peer("spiffe://agentweave.io/agent/*")
async def search(self, query: str) -> dict:
"""Anyone in the trust domain can search."""
results = await self._search_db(query)
return {"results": results}
@capability("update_data", description="Update existing data")
@requires_peer("spiffe://agentweave.io/agent/editor-*")
@audit_log(level="info")
async def update_data(self, id: str, data: dict) -> dict:
"""Only editor agents can update data."""
await self._update_db(id, data)
return {"updated": id}
@capability("delete_data", description="Delete sensitive data")
@requires_peer("spiffe://agentweave.io/agent/admin-*")
@audit_log(level="warning")
async def delete_data(self, id: str) -> dict:
"""Only admin agents can delete data."""
await self._delete_from_db(id)
return {"deleted": id}
@capability("grant_access", description="Grant access to data")
@requires_peer("spiffe://agentweave.io/agent/security-admin")
@audit_log(level="error")
async def grant_access(self, user_id: str, resource_id: str) -> dict:
"""Only the security admin agent can grant access."""
await self._grant_access_db(user_id, resource_id)
return {"granted": True}
# Helper methods (not capabilities)
async def _search_db(self, query: str) -> list:
return []
async def _update_db(self, id: str, data: dict) -> None:
pass
async def _delete_from_db(self, id: str) -> None:
pass
async def _grant_access_db(self, user_id: str, resource_id: str) -> None:
pass
# Run the agent
if __name__ == "__main__":
agent = DataManagementAgent.from_config("config.yaml")
agent.run()
Best Practices
1. Always Use @capability First
1
2
3
4
5
6
7
8
9
10
11
# Correct
@capability("search")
@requires_peer("spiffe://...")
async def search(self, query: str) -> dict:
pass
# Incorrect - won't work properly
@requires_peer("spiffe://...")
@capability("search")
async def search(self, query: str) -> dict:
pass
2. Use Descriptive Capability Names
1
2
3
4
5
6
7
# Good
@capability("search_users", description="Search for users in the directory")
@capability("delete_expired_data", description="Delete data past retention period")
# Avoid
@capability("do_stuff", description="Does stuff")
@capability("x", description="Something")
3. Apply @audit_log to Sensitive Operations
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Always audit data modifications
@capability("update_user")
@audit_log(level="info")
async def update_user(self, user_id: str, data: dict) -> dict:
pass
# Always audit deletions
@capability("delete_user")
@audit_log(level="warning")
async def delete_user(self, user_id: str) -> dict:
pass
# Always audit access grants
@capability("grant_admin")
@audit_log(level="error")
async def grant_admin(self, user_id: str) -> dict:
pass
4. Use Specific SPIFFE Patterns
1
2
3
4
5
6
7
# Good - specific patterns
@requires_peer("spiffe://agentweave.io/agent/admin-*")
@requires_peer("spiffe://agentweave.io/agent/data-processor")
# Avoid - too permissive
@requires_peer("spiffe://*")
@requires_peer("*")
See Also
- Agent Module - Agent classes and lifecycle
- Context Module - Request context management
- Security Guide - Security best practices