Strategy Advanced 10 min read
Decision Trees for Agents
Algorithmic decision trees for AI agents to navigate protocol selection, error handling, and multi-protocol workflows.
decision-trees algorithms automation workflow
Decision Trees for Agents
This guide provides implementable decision trees that agents can use to make autonomous protocol decisions. Each tree is designed for deterministic execution without human intervention.
Core Decision Trees
1. Payment Protocol Decision Tree
┌──────────────────────────────────┐
│ PAYMENT REQUIRED │
└───────────────┬──────────────────┘
│
▼
┌──────────────────────────────────┐
│ Amount > 10M sats? │
│ (>$10,000) │
└───────────────┬──────────────────┘
│ │
YES NO
│ │
▼ ▼
┌─────────────┐ ┌──────────────────────────────────┐
│ BITCOIN │ │ Recipient has Lightning? │
│ (secure) │ └───────────────┬──────────────────┘
└─────────────┘ │ │
YES NO
│ │
▼ ▼
┌──────────────────────────────────┐ ┌─────────────┐
│ Amount < 100k sats? │ │ BITCOIN │
│ (<$100) │ │ (fallback) │
└───────────────┬──────────────────┘ └─────────────┘
│ │
YES NO
│ │
▼ ▼
┌─────────────┐ ┌──────────────────────────────────┐
│ LIGHTNING │ │ Urgency = instant? │
│ (optimal) │ └───────────────┬──────────────────┘
└─────────────┘ │ │
YES NO
│ │
▼ ▼
┌─────────────┐ ┌─────────────┐
│ LIGHTNING │ │ BITCOIN │
│ (fast) │ │ (cheaper) │
└─────────────┘ └─────────────┘
Implementation:
def payment_decision_tree(
amount_sats: int,
recipient_has_lightning: bool,
urgency: str # 'instant', 'fast', 'standard'
) -> tuple[str, str]:
"""
Determine optimal payment protocol.
Returns:
tuple: (protocol, reason)
"""
# Large amounts: always Bitcoin
if amount_sats > 10_000_000: # >$10,000
return ("bitcoin", "large_amount_security")
# No Lightning: use Bitcoin
if not recipient_has_lightning:
return ("bitcoin", "recipient_no_lightning")
# Small amounts: Lightning preferred
if amount_sats < 100_000: # <$100
return ("lightning", "micropayment_optimal")
# Medium amounts: depends on urgency
if urgency == "instant":
return ("lightning", "speed_required")
# Default to Bitcoin for medium, non-urgent
return ("bitcoin", "settlement_preferred")
2. Communication Protocol Decision Tree
┌──────────────────────────────────┐
│ COMMUNICATION REQUIRED │
└───────────────┬──────────────────┘
│
▼
┌──────────────────────────────────┐
│ Needs payment? │
└───────────────┬──────────────────┘
│ │
YES NO
│ │
▼ ▼
┌────────────────────────┐ ┌──────────────────────────────────┐
│ Use Nostr + Zaps │ │ Is broadcast? │
│ (NIP-57) │ └───────────────┬──────────────────┘
└────────────────────────┘ │ │
YES NO
│ │
▼ ▼
┌──────────────────────────────────┐ ┌──────────────────┐
│ NOSTR kind:1 │ │ Needs encryption?│
│ (public note) │ └─────────┬────────┘
└──────────────────────────────────┘ │ │
YES NO
│ │
▼ ▼
┌───────────────────┐ ┌──────────┐
│ NOSTR NIP-44/NIP-59│ │ NOSTR │
│ (encrypted DM) │ │ (plain) │
└───────────────────┘ └──────────┘
3. Error Recovery Decision Tree
┌──────────────────────────────────┐
│ OPERATION FAILED │
└───────────────┬──────────────────┘
│
▼
┌──────────────────────────────────┐
│ Error type = timeout? │
└───────────────┬──────────────────┘
│ │
YES NO
│ │
▼ ▼
┌────────────────────────┐ ┌──────────────────────────────────┐
│ Retry with │ │ Error type = no_route? │
│ exponential │ └───────────────┬──────────────────┘
│ backoff │ │ │
└────────────────────────┘ YES NO
│ │
▼ ▼
┌────────────────────────┐ ┌──────────────────────────────────┐
│ Try different │ │ Error type = insufficient? │
│ route or fall │ └───────────────┬──────────────────┘
│ back to Bitcoin │ │ │
└────────────────────────┘ YES NO
│ │
▼ ▼
┌─────────────────────────┐ ┌──────────────┐
│ Split payment (MPP) │ │ Log and │
│ or use smaller channel │ │ escalate │
└─────────────────────────┘ └──────────────┘
Implementation:
async def error_recovery_tree(
error: Exception,
operation: str,
protocol: str,
retry_count: int = 0
) -> tuple[str, dict]:
"""
Handle operation errors with recovery strategies.
Returns:
tuple: (action, params)
"""
error_type = classify_error(error)
max_retries = 3
if error_type == "timeout":
if retry_count < max_retries:
delay = (2 ** retry_count) * 1000 # Exponential backoff
return ("retry", {"delay_ms": delay, "retry_count": retry_count + 1})
return ("escalate", {"reason": "max_retries_exceeded"})
if error_type == "no_route" and protocol == "lightning":
if retry_count < 2:
return ("retry_different_route", {"exclude_nodes": get_failed_nodes()})
return ("fallback", {"protocol": "bitcoin", "reason": "routing_failed"})
if error_type == "insufficient_funds":
if protocol == "lightning":
return ("split_payment", {"max_per_part": get_max_channel_capacity()})
return ("escalate", {"reason": "insufficient_funds"})
return ("escalate", {"reason": f"unknown_error: {error_type}"})
def classify_error(error: Exception) -> str:
"""Classify error into known categories."""
error_msg = str(error).lower()
if "timeout" in error_msg or "timed out" in error_msg:
return "timeout"
if "no route" in error_msg or "route not found" in error_msg:
return "no_route"
if "insufficient" in error_msg or "not enough" in error_msg:
return "insufficient_funds"
if "invalid" in error_msg:
return "invalid_input"
return "unknown"
4. Protocol Health Check Decision Tree
┌──────────────────────────────────┐
│ CHECK PROTOCOL HEALTH │
└───────────────┬──────────────────┘
│
▼
┌──────────────────────────────────┐
│ Bitcoin API responding? │
└───────────────┬──────────────────┘
│ │
YES NO
│ │
▼ ▼
┌────────────────────────┐ ┌──────────────────────────────────┐
│ Check Lightning node │ │ Use backup API endpoints │
└─────────────┬──────────┘ └───────────────┬──────────────────┘
│ │
▼ ▼
┌──────────────────────────────────┐ ┌──────────────┐
│ Lightning node synced? │ │ Backup ok? │
└───────────────┬──────────────────┘ └──────┬───────┘
│ │ │ │
YES NO YES NO
│ │ │ │
▼ ▼ ▼ ▼
┌──────────┐ ┌─────────────┐ ┌─────────┐ ┌──────────┐
│ CHECK │ │ WAIT/RESTART│ │CONTINUE │ │ BITCOIN │
│ NOSTR │ │ LN NODE │ │BITCOIN │ │ OFFLINE │
└──────────┘ └─────────────┘ └─────────┘ └──────────┘
5. Multi-Protocol Workflow Decision Tree
┌──────────────────────────────────┐
│ COMPLEX OPERATION │
│ (e.g., pay + communicate) │
└───────────────┬──────────────────┘
│
▼
┌──────────────────────────────────┐
│ Requires atomicity? │
└───────────────┬──────────────────┘
│ │
YES NO
│ │
▼ ▼
┌────────────────────────┐ ┌──────────────────────────────────┐
│ Use single protocol │ │ Execute in parallel │
│ (usually Lightning) │ └───────────────┬──────────────────┘
└────────────────────────┘ │
▼
┌──────────────────────────────────┐
│ Payment + Message scenario │
└───────────────┬──────────────────┘
│ │
PAYMENT_FIRST MSG_FIRST
│ │
▼ ▼
┌──────────────────────────────────┐ ┌────────────────────────┐
│ 1. Send Lightning payment │ │ 1. Post Nostr note │
│ 2. Post confirmation on Nostr │ │ 2. Include payment │
│ with payment proof │ │ request (LNURL) │
└──────────────────────────────────┘ └────────────────────────┘
Composite Decision Engine
class ProtocolDecisionEngine:
"""
Unified decision engine for protocol selection.
"""
def __init__(self):
self.health_cache = {}
self.last_health_check = 0
async def decide(
self,
operation: str,
params: dict
) -> dict:
"""
Main entry point for protocol decisions.
Args:
operation: 'payment', 'message', 'verify', 'store'
params: Operation-specific parameters
Returns:
dict with 'protocol', 'reason', 'fallback'
"""
# Ensure health data is fresh
await self._refresh_health_if_stale()
if operation == "payment":
return await self._decide_payment(params)
elif operation == "message":
return await self._decide_message(params)
elif operation == "verify":
return self._decide_verify(params)
elif operation == "store":
return self._decide_store(params)
else:
return {"error": f"Unknown operation: {operation}"}
async def _decide_payment(self, params: dict) -> dict:
amount = params.get("amount_sats", 0)
urgency = params.get("urgency", "standard")
recipient_ln = params.get("recipient_has_lightning", True)
# Apply decision tree
protocol, reason = payment_decision_tree(
amount_sats=amount,
recipient_has_lightning=recipient_ln,
urgency=urgency
)
# Check protocol health
if not self.health_cache.get(protocol, {}).get("healthy", False):
# Fallback logic
if protocol == "lightning":
return {
"protocol": "bitcoin",
"reason": "lightning_unhealthy",
"fallback": True,
"original_choice": protocol
}
return {
"protocol": protocol,
"reason": reason,
"fallback": False
}
async def _decide_message(self, params: dict) -> dict:
encrypted = params.get("encrypted", False)
has_payment = params.get("include_payment", False)
if has_payment:
return {
"protocol": "nostr",
"subprotocol": "nip-57", # Zaps
"reason": "payment_included"
}
if encrypted:
return {
"protocol": "nostr",
"subprotocol": "nip-44",
"reason": "encryption_required"
}
return {
"protocol": "nostr",
"subprotocol": "kind-1",
"reason": "standard_message"
}
def _decide_verify(self, params: dict) -> dict:
"""Verification always uses Bitcoin."""
return {
"protocol": "bitcoin",
"reason": "verification_source_of_truth"
}
def _decide_store(self, params: dict) -> dict:
"""Storage decision based on permanence needs."""
permanent = params.get("permanent", False)
if permanent:
return {
"protocol": "bitcoin",
"reason": "immutable_storage",
"method": "op_return"
}
return {
"protocol": "nostr",
"reason": "distributed_storage"
}
async def _refresh_health_if_stale(self):
import time
if time.time() - self.last_health_check > 60: # 1 minute cache
self.health_cache = await check_all_protocols_health()
self.last_health_check = time.time()
Decision Logging
For auditability, log all decisions:
import json
from datetime import datetime
def log_decision(
operation: str,
params: dict,
decision: dict,
execution_result: dict | None = None
) -> None:
"""Log decision for audit trail."""
log_entry = {
"timestamp": datetime.utcnow().isoformat(),
"operation": operation,
"params": params,
"decision": decision,
"result": execution_result
}
# Append to decision log
with open("decisions.jsonl", "a") as f:
f.write(json.dumps(log_entry) + "\n")
Machine-Readable Summary
{
"topic": "decision-trees",
"audience": "ai-agents",
"decision_trees": [
"payment_protocol",
"communication_protocol",
"error_recovery",
"health_check",
"multi_protocol"
],
"implementation_language": "python",
"key_thresholds": {
"large_payment_sats": 10000000,
"micropayment_sats": 100000,
"max_retries": 3,
"health_cache_seconds": 60
},
"fallback_chain": ["lightning", "bitcoin", "escalate"]
}