Tool Contracts¶
Tool contracts define the formal interface between edge nodes and the gateway in Adaptive Sentience. They provide type-safe, versioned APIs for tool execution with built-in capability requirements and schema validation.
Overview¶
Problem: In distributed systems, tools running on different nodes need clear interface definitions to ensure correct usage, prevent errors, and enable automated validation.
Solution: Tool contracts provide JSON Schema-based interface definitions that are:
- Type-safe: Input and output validated against schemas
- Versioned: Track tool evolution over time
- Capability-gated: Explicit permission requirements
- Self-documenting: Machine-readable metadata
- Deterministic: Optional guarantee of reproducible execution
Core Concepts¶
What is a Tool Contract?¶
A tool contract is a formal specification that defines:
- Tool Identity: Unique identifier and version
- Input Schema: Expected parameters and types
- Output Schema: Return value structure
- Capability Requirements: Which permissions are needed
- Execution Properties: Deterministic vs. non-deterministic
- Metadata: Description, documentation, examples
Tool contracts enable the gateway to:
- Route requests to nodes that have the required tool
- Validate inputs before sending to edge nodes
- Verify outputs match expected schema
- Enforce capability-based authorization
- Cache results for deterministic tools
Tool Contract Structure¶
Basic Contract Format¶
{
"tool_id": "tool:pii_redact",
"name": "pii_redact",
"version": "1.0.0",
"description": "Redact PII from text using regex patterns",
"input_schema": {
"type": "object",
"properties": {
"text": {
"type": "string",
"description": "Text to redact PII from"
},
"redact_emails": {
"type": "boolean",
"description": "Whether to redact email addresses",
"default": true
},
"redact_phones": {
"type": "boolean",
"description": "Whether to redact phone numbers",
"default": true
},
"replacement": {
"type": "string",
"description": "Replacement text for redacted PII",
"default": "[REDACTED]"
}
},
"required": ["text"]
},
"output_schema": {
"type": "object",
"properties": {
"redacted_text": {
"type": "string",
"description": "Text with PII redacted"
},
"redactions": {
"type": "array",
"description": "List of redactions performed",
"items": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": ["email", "phone"]
},
"count": {
"type": "integer",
"minimum": 0
}
},
"required": ["type", "count"]
}
}
},
"required": ["redacted_text", "redactions"]
},
"required_capabilities": ["tool:pii_redact"],
"deterministic": true,
"tags": ["pii", "privacy", "redaction"]
}
Contract Fields¶
| Field | Required | Description |
|---|---|---|
tool_id |
Yes | Unique identifier (convention: tool:name) |
name |
Yes | Tool name (lowercase, underscores) |
version |
Yes | Semantic version (major.minor.patch) |
description |
Yes | Human-readable description |
input_schema |
Yes | JSON Schema for input validation |
output_schema |
Yes | JSON Schema for output validation |
required_capabilities |
Yes | List of required capability tokens |
deterministic |
No | Whether execution is deterministic (default: false) |
tags |
No | Searchable metadata tags |
examples |
No | Example input/output pairs |
Defining Tool Schemas¶
JSON Schema Basics¶
Tool contracts use JSON Schema for input/output validation.
Simple Schema Example:
{
"input_schema": {
"type": "object",
"properties": {
"url": {
"type": "string",
"format": "uri",
"description": "URL to fetch"
},
"timeout": {
"type": "integer",
"minimum": 1,
"maximum": 60,
"default": 10,
"description": "Timeout in seconds"
}
},
"required": ["url"]
}
}
This schema requires:
- url: A string in URI format (required)
- timeout: An integer between 1-60, defaults to 10 (optional)
Supported JSON Schema Features¶
Adaptive Sentience supports JSON Schema Draft 7:
Type Validation:
{
"type": "string", // string, number, integer, boolean, array, object, null
"minimum": 0, // For numbers
"maximum": 100,
"pattern": "^[A-Z]", // Regex for strings
"format": "email" // Common formats: email, uri, date-time, ipv4, ipv6
}
Required Fields:
{
"type": "object",
"properties": {
"name": {"type": "string"},
"age": {"type": "integer"}
},
"required": ["name"] // age is optional
}
Arrays:
Nested Objects:
{
"type": "object",
"properties": {
"user": {
"type": "object",
"properties": {
"name": {"type": "string"},
"email": {"type": "string", "format": "email"}
},
"required": ["name", "email"]
}
}
}
Enums:
Complex Schema Example¶
{
"input_schema": {
"type": "object",
"properties": {
"document": {
"type": "object",
"properties": {
"content": {
"type": "string",
"minLength": 1,
"maxLength": 100000
},
"format": {
"type": "string",
"enum": ["text", "markdown", "html"],
"default": "text"
}
},
"required": ["content"]
},
"options": {
"type": "object",
"properties": {
"max_summary_length": {
"type": "integer",
"minimum": 50,
"maximum": 500,
"default": 200
},
"include_keywords": {
"type": "boolean",
"default": false
},
"language": {
"type": "string",
"pattern": "^[a-z]{2}$",
"default": "en"
}
}
}
},
"required": ["document"]
},
"output_schema": {
"type": "object",
"properties": {
"summary": {
"type": "string",
"minLength": 1
},
"keywords": {
"type": "array",
"items": {
"type": "string"
}
},
"confidence": {
"type": "number",
"minimum": 0,
"maximum": 1
}
},
"required": ["summary", "confidence"]
}
}
Capability Requirements¶
Single Capability¶
Most tools require a single capability:
Multiple Capabilities¶
Tools that access sensitive resources may require multiple capabilities:
The capability token must have all required capabilities for execution to proceed.
Capability Naming Conventions¶
| Pattern | Example | Use Case |
|---|---|---|
tool:<name> |
tool:pii_redact |
Tool execution permission |
resource:<name> |
resource:production_db |
Resource access |
action:<name> |
action:approve_payment |
Privileged operation |
admin:* |
admin:user_management |
Administrative tasks |
Versioning¶
Semantic Versioning¶
Tool contracts use Semantic Versioning 2.0.0:
- MAJOR: Incompatible API changes (e.g., removed field, changed behavior)
- MINOR: Backward-compatible new features (e.g., new optional field)
- PATCH: Backward-compatible bug fixes
Examples:
1.0.0 → 1.0.1 # Bug fix in redaction logic (patch)
1.0.1 → 1.1.0 # Added optional 'redact_ssn' field (minor)
1.1.0 → 2.0.0 # Changed output format (major - breaking)
Version Selection¶
When calling a tool, clients can specify version requirements:
Version matching strategies:
"1.2.3": Exact version"1.2.x": Any patch version of 1.2"1.x": Any minor/patch version of 1.x"latest": Most recent version (default)
Multiple Versions¶
Edge nodes can register multiple versions of the same tool:
# Edge node supports both v1 and v2
toolhost.register_tool(pii_redact_v1)
toolhost.register_tool(pii_redact_v2)
The gateway routes to the best matching version based on: 1. Client's version requirement 2. Available nodes 3. Node preference (if specified)
Deterministic Execution¶
What is Deterministic Execution?¶
A tool is deterministic if it produces the same output for the same input every time.
Deterministic examples:
- pii_redact: Regex-based redaction (no randomness)
- json_validate: Schema validation (pure function)
- unit_convert: Mathematical conversion
Non-deterministic examples:
- llm_summarize: Uses AI model (output varies)
- fetch_url: Network calls (content changes over time)
- current_time: Time-dependent
Declaring Determinism¶
Benefits of Deterministic Tools¶
- Caching: Gateway can cache results and skip re-execution
- Idempotency: Retries are safe
- Reproducibility: Audit trails can be replayed
- Testing: Easier to write deterministic tests
Idempotency Caching¶
For deterministic tools, the gateway computes a cache key:
If a matching cache entry exists, the gateway returns the cached result without executing the tool.
Example:
# First call executes on edge node
result1 = call_tool("unit_convert", {"value": 100, "from": "km", "to": "mi"})
# Second identical call returns cached result
result2 = call_tool("unit_convert", {"value": 100, "from": "km", "to": "mi"})
Registering Tools with Edge Nodes¶
Python Tool Registration¶
Step 1: Define the Tool Function
from typing import Dict, Any
def pii_redact(args: Dict[str, Any]) -> Dict[str, Any]:
"""Redact PII from text."""
text = args["text"]
redact_emails = args.get("redact_emails", True)
redact_phones = args.get("redact_phones", True)
replacement = args.get("replacement", "[REDACTED]")
redactions = []
# Email redaction
if redact_emails:
import re
email_pattern = r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b'
email_matches = re.findall(email_pattern, text)
if email_matches:
text = re.sub(email_pattern, replacement, text)
redactions.append({"type": "email", "count": len(email_matches)})
# Phone redaction
if redact_phones:
phone_pattern = r'\b\d{3}[-.\s]?\d{3}[-.\s]?\d{4}\b'
phone_matches = re.findall(phone_pattern, text)
if phone_matches:
text = re.sub(phone_pattern, replacement, text)
redactions.append({"type": "phone", "count": len(phone_matches)})
return {
"redacted_text": text,
"redactions": redactions
}
Step 2: Create Tool Specification
from edge_node.toolhost import ToolSpec
INPUT_SCHEMA = {
"type": "object",
"properties": {
"text": {"type": "string"},
"redact_emails": {"type": "boolean", "default": True},
"redact_phones": {"type": "boolean", "default": True},
"replacement": {"type": "string", "default": "[REDACTED]"}
},
"required": ["text"]
}
OUTPUT_SCHEMA = {
"type": "object",
"properties": {
"redacted_text": {"type": "string"},
"redactions": {
"type": "array",
"items": {
"type": "object",
"properties": {
"type": {"type": "string"},
"count": {"type": "integer"}
}
}
}
},
"required": ["redacted_text", "redactions"]
}
pii_redact_spec = ToolSpec(
name="pii_redact",
version="1.0.0",
description="Redact PII from text using regex patterns",
required_capability="tool:pii_redact",
callable=pii_redact,
input_schema=INPUT_SCHEMA
)
Step 3: Register with ToolHost
from edge_node.toolhost import ToolHost
# Initialize tool host
toolhost = ToolHost(node_id="local:abc123")
# Register tool
toolhost.register_tool(pii_redact_spec)
# Output:
# [TOOLHOST] Registered tool: pii_redact v1.0.0 (requires tool:pii_redact)
Full Edge Node Example¶
#!/usr/bin/env python3
"""Custom edge node with PII redaction tool."""
import os
from edge_node.toolhost import ToolHost, ToolSpec
from edge_node.node import EdgeNode
# Tool implementation
def pii_redact(args):
# ... implementation from above ...
pass
# Tool specification
pii_redact_spec = ToolSpec(
name="pii_redact",
version="1.0.0",
description="Redact PII from text using regex patterns",
required_capability="tool:pii_redact",
callable=pii_redact,
input_schema={
"type": "object",
"properties": {
"text": {"type": "string"}
},
"required": ["text"]
}
)
# Initialize edge node
node_port = int(os.getenv("NODE_PORT", "8000"))
node = EdgeNode(port=node_port)
# Register tool
node.toolhost.register_tool(pii_redact_spec)
# Start node (joins mesh and listens for requests)
node.start()
Run the edge node:
Tool Discovery¶
How Tools are Discovered¶
- Edge Node Registration: When an edge node starts, it broadcasts its available tools via UDP multicast
- Gateway Catalog: The gateway builds a catalog of all available tools across the mesh
- Client Discovery: Clients query the gateway to discover available tools
Discovery API¶
Get Available Tools:
Response:
{
"local": {
"node_id": "gateway:abc123",
"tools": []
},
"mesh": [
{
"node_id": "local:def456",
"node_type": "macos",
"http_url": "http://192.168.1.100:8000",
"tools": [
{
"name": "pii_redact",
"version": "1.0.0",
"description": "Redact PII from text using regex patterns",
"required_capability": "tool:pii_redact",
"deterministic": true
}
]
}
]
}
Tool Metadata¶
Each discovered tool includes:
name: Tool nameversion: Semantic versiondescription: Human-readable descriptionrequired_capability: Permission requirementsdeterministic: Whether execution is deterministichas_schema: Whether input schema is available
Executing Tools¶
Basic Tool Execution¶
curl -X POST http://localhost:8787/v1/tool_call \
-H "Content-Type: application/json" \
-d '{
"target": {"kind": "local"},
"tool_name": "pii_redact",
"tool_args": {
"text": "Contact john@example.com at 555-123-4567"
},
"token": {
"capabilities": ["tool:pii_redact"]
}
}'
Response:
{
"ok": true,
"verified": true,
"result": {
"redacted_text": "Contact [REDACTED] at [REDACTED]",
"redactions": [
{"type": "email", "count": 1},
{"type": "phone", "count": 1}
]
},
"execution_path": ["local:def456"],
"degraded": false,
"trace_id": "trace:1a2b3c4d",
"latency_ms": 15
}
Specifying Tool Version¶
Targeting Specific Nodes¶
{
"target": {
"kind": "node_id",
"node_id": "local:def456"
},
"tool_name": "pii_redact",
"tool_args": { ... }
}
Schema Validation¶
Input Validation¶
Before executing a tool, the edge node validates input arguments against the schema:
def call_tool(tool_name, args, token):
spec = self.tools[tool_name]
# Validate against input schema
valid, error = self._validate_schema(spec, args)
if not valid:
return {
"ok": False,
"error": f"Schema validation failed: {error}"
}
# Execute tool
result = spec.callable(args)
return {
"ok": True,
"result": result
}
Example validation error:
Output Validation¶
Output validation ensures tools return the expected structure:
# Tool returns invalid output
result = {"wrong_field": "oops"}
# Validation catches the error
{
"ok": false,
"error": "Output validation failed: 'redacted_text' is a required property"
}
Disabling Validation¶
For development, validation can be disabled:
toolhost = ToolHost(
node_id="local:abc123",
validate_schemas=False # Skip validation (not recommended for production)
)
Advanced Topics¶
Custom Validation Logic¶
Beyond JSON Schema, tools can implement custom validation:
def pii_redact(args):
# Custom validation
text = args["text"]
if len(text) > 100000:
raise ValueError("Text too long (max 100KB)")
if not text.strip():
raise ValueError("Text cannot be empty")
# ... rest of implementation ...
Raised exceptions are caught and returned as errors:
Tool Examples and Documentation¶
Include example input/output in the contract:
{
"tool_id": "tool:pii_redact",
"examples": [
{
"input": {
"text": "Contact john@example.com",
"redact_emails": true
},
"output": {
"redacted_text": "Contact [REDACTED]",
"redactions": [{"type": "email", "count": 1}]
}
}
]
}
These examples are used for: - Auto-generated documentation - Automated testing - Client code generation
Tool Hashing for Auditability¶
Edge nodes compute a hash of each tool for audit trails:
def _get_tool_hash(spec):
content = f"{spec.name}:{spec.version}:{spec.required_capability}"
return hashlib.sha256(content.encode()).hexdigest()[:8]
This hash is included in execution evidence:
Schema Evolution¶
When evolving schemas:
-
Adding optional fields: MINOR version bump
-
Making fields required: MAJOR version bump
-
Removing fields: MAJOR version bump
- Changing types: MAJOR version bump
Best Practices¶
1. Use Descriptive Names¶
# Good
ToolSpec(name="pii_redact", ...)
# Bad
ToolSpec(name="redact", ...) # Too vague
ToolSpec(name="piiRedactionTool", ...) # Use snake_case
2. Provide Rich Descriptions¶
{
"description": "Redact PII from text using regex patterns. Supports email and phone number detection. NOT production-grade - use specialized PII services for production."
}
3. Use Specific Schemas¶
{
// Good - specific constraints
"properties": {
"email": {
"type": "string",
"format": "email",
"maxLength": 254
}
},
// Bad - too loose
"properties": {
"email": {"type": "string"}
}
}
4. Include Examples¶
Always include at least one example input/output pair.
5. Version Deliberately¶
Don't bump major version unnecessarily. Use minor versions for backward-compatible additions.
6. Test Schema Validation¶
Write tests that verify schema validation catches invalid inputs:
def test_schema_validation():
result = toolhost.call_tool(
"pii_redact",
{"invalid": "field"},
token
)
assert not result["ok"]
assert "required property" in result["error"]
7. Document Required Capabilities¶
Clearly document what each capability grants access to:
# Good
required_capability="tool:pii_redact" # Grants permission to redact PII
# Bad
required_capability="admin" # Too broad, unclear what this allows
8. Use Determinism Correctly¶
Only mark tools as deterministic if they truly are:
# Deterministic - pure function
ToolSpec(name="unit_convert", deterministic=True)
# Non-deterministic - network call
ToolSpec(name="fetch_url", deterministic=False)
Troubleshooting¶
Tool Not Discovered¶
Symptom: Gateway doesn't see your tool in the catalog.
Solutions:
-
Check UDP multicast: Ensure UDP port 9999 is open
-
Verify registration: Check edge node logs for:
-
Manual refresh: Restart the gateway or trigger a mesh scan:
Schema Validation Errors¶
Symptom: Tool execution fails with "Schema validation failed".
Solutions:
- Check required fields: Ensure all required fields are provided
- Verify types: String vs. number vs. boolean
-
Test schema separately:
-
Use online validator: jsonschemavalidator.net
Version Mismatch¶
Symptom: Gateway can't find requested version.
Solutions:
-
Check available versions:
-
Use flexible version matching:
"1.x"instead of exact version - Update edge node: Deploy newer version to mesh
Capability Token Rejected¶
Symptom: "Missing required capability: tool:pii_redact"
Solutions:
-
Add capability to token:
-
Check capability name: Must match exactly (case-sensitive)
Reference¶
ToolSpec Python Class¶
@dataclass
class ToolSpec:
name: str # Tool name
version: str # Semantic version
description: str # Human-readable description
required_capability: str # Required capability token
callable: Callable[[Dict, Dict], Dict] # Tool implementation
input_schema: Optional[Dict] = None # JSON Schema for input
output_schema: Optional[Dict] = None # JSON Schema for output
deterministic: bool = False # Is execution deterministic?
tags: List[str] = field(default_factory=list) # Searchable tags
examples: List[Dict] = field(default_factory=list) # Example I/O
Tool Call Request Schema¶
{
"target": {
"kind": "local" | "mesh" | "node_id",
"node_id": "local:abc123" // Required if kind=node_id
},
"tool_name": "pii_redact",
"tool_version": "1.x", // Optional
"tool_args": {
// Tool-specific arguments
},
"token": {
"capabilities": ["tool:pii_redact"],
"expires_at": "2024-12-31T23:59:59Z" // Optional
},
"job_id": "job:unique123" // Optional, for idempotency
}
Tool Call Response Schema¶
{
"ok": true,
"verified": true,
"result": {
// Tool-specific output
},
"error": null, // Present if ok=false
"execution_path": ["local:def456"],
"degraded": false,
"trace_id": "trace:1a2b3c4d",
"latency_ms": 15,
"tool_hash": "a1b2c3d4",
"tool_version": "1.0.0"
}
Next Steps¶
- Capability Tokens - Understand authorization model
- Execution Evidence - Audit trails and signatures
- Trust Pairing - Secure node communication
- Offline Operation - Store-and-forward delivery