AXL is the communication fabric for peer-to-peer agent debate. Bull and bear agents publish arguments to the judge via AXL, enabling decentralized message routing without a centralized broker.
Without AXL, agent communication would collapse into:
- In-process function calls (not peer-to-peer)
- Centralized message broker (not decentralized)
AXL provides:
- Peer-to-Peer Routing: Three local AXL nodes route messages between agents
- Cryptographic Verification: Messages signed and verified (sender authenticity)
- Topology Observability: Debate communication pattern is visible and auditable
- Decentralization: No single point of failure in message routing
Convexa runs three local AXL nodes in separate processes:
┌─────────────────────────────────────────────────────┐
│ Bull Agent │
│ (Groq LLM + market data reasoning) │
│ │
│ publish_to_judge(round_argument) │
└──────────────┬────────────────────────────────────┘
│ HTTP POST
┌──────────────▼────────────────────────────────────┐
│ Bull AXL Node (Port 8001) │
│ - Listens for agent messages │
│ - Routes to judge node │
└──────────────┬────────────────────────────────────┘
│ AXL Peer Mesh
├──────────────────┐
┌──────────────▼────────────────────▼──────────────┐
│ Judge AXL Node (Port 8003) │
│ - Receives routed messages │
│ - Inbox accessible to judge agent │
└──────────────┬────────────────────────────────────┘
│
┌──────────────▼────────────────────────────────────┐
│ Judge Agent │
│ (Groq LLM + conviction scoring) │
│ │
│ wait_for_both_arguments() │
│ poll_inbox() │
└──────────────────────────────────────────────────┘
Stored in axl-nodes/{bull,bear,judge}/node-config.json:
{
"name": "bull-node",
"port": 8001,
"peers": [
{"address": "127.0.0.1:8003", "publicKey": "judge-pubkey"}
],
"dataDir": "./data",
"logLevel": "info"
}Each node configuration includes:
- Name: Unique node identifier
- Port: HTTP listen port
- Peers: Other nodes in mesh
- Data Directory: Node state and logs
- Log Level: Debug verbosity
def publish_to_judge(round_num, argument_text):
"""Publish bull argument to judge via AXL."""
message = {
"type": "ARGUMENT",
"sender": "bull",
"round": round_num,
"argument": argument_text,
"timestamp": datetime.now().isoformat()
}
response = requests.post(
"http://127.0.0.1:8001/publish",
json=message
)
return response.status_code == 200Flow:
- Bull agent generates argument via Groq LLM
- Sends HTTP POST to local AXL node (port 8001)
- AXL node routes message to judge node
- Judge node stores in inbox
Identical to bull, but:
- Sends to AXL port 8002 (bear node)
- Message tagged with
"sender": "bear"
def wait_for_both_arguments():
"""Wait for bull and bear arguments from AXL inbox."""
bull_arg = None
bear_arg = None
while bull_arg is None or bear_arg is None:
messages = poll_inbox()
for msg in messages:
if msg["sender"] == "bull":
bull_arg = msg
elif msg["sender"] == "bear":
bear_arg = msg
time.sleep(1)
return bull_arg, bear_arg
def poll_inbox():
"""Fetch pending messages from judge AXL node."""
response = requests.get(
"http://127.0.0.1:8003/inbox"
)
return response.json()["messages"]Behavior:
- Polls judge node inbox every 1 second
- Waits for both bull and bear arguments
- Timeout: 30 seconds (abort if missing argument)
def publish_verdict(round_num, conviction_score, reasoning):
"""Publish judge verdict back to orchestrator."""
verdict = {
"type": "VERDICT",
"round": round_num,
"conviction": conviction_score, # -100 to +100
"reasoning": reasoning,
"timestamp": datetime.now().isoformat()
}
# Write to judge AXL node's outbox
response = requests.post(
"http://127.0.0.1:8003/publish",
json=verdict
)
return verdictVerdict is routed back to orchestrator via AXL.
Managed in agents/orchestrator.py:
def start_axl_nodes():
"""Spin up three local AXL nodes."""
bull_proc = subprocess.Popen(
["./axl-nodes/axl", "-config", "axl-nodes/bull/node-config.json"],
stdout=open("data/logs/axl_bull.log", "w"),
stderr=subprocess.STDOUT
)
bear_proc = subprocess.Popen(
["./axl-nodes/axl", "-config", "axl-nodes/bear/node-config.json"],
stdout=open("data/logs/axl_bear.log", "w"),
stderr=subprocess.STDOUT
)
judge_proc = subprocess.Popen(
["./axl-nodes/axl", "-config", "axl-nodes/judge/node-config.json"],
stdout=open("data/logs/axl_judge.log", "w"),
stderr=subprocess.STDOUT
)
return bull_proc, bear_proc, judge_proc
def health_check_axl_nodes():
"""Verify all nodes are running and responsive."""
for port in [8001, 8002, 8003]:
try:
response = requests.get(f"http://127.0.0.1:{port}/health")
assert response.status_code == 200
except Exception as e:
print(f"AXL node on port {port} unhealthy: {e}")
return False
return True
def shutdown_axl_nodes(procs):
"""Gracefully stop all AXL nodes."""
for proc in procs:
proc.terminate()
proc.wait(timeout=5)| Node | Port | Purpose |
|---|---|---|
| Bull | 8001 | Bull agent publishes arguments |
| Bear | 8002 | Bear agent publishes arguments |
| Judge | 8003 | Judge receives and processes messages |
All messages follow standard AXL envelope:
{
"type": "ARGUMENT|VERDICT",
"sender": "bull|bear|judge",
"round": 1,
"timestamp": "2026-05-03T10:00:00Z",
"signature": "0x...",
"payload": { /* sender-specific */ }
}Signature: Ed25519 signature of message payload, verified by recipient
tail -f data/logs/axl_bull.log
tail -f data/logs/axl_bear.log
tail -f data/logs/axl_judge.log# Fetch pending messages in judge inbox
curl http://127.0.0.1:8003/inbox
# Publish test message to bull node
curl -X POST http://127.0.0.1:8001/publish \
-H "Content-Type: application/json" \
-d '{"type":"TEST", "sender":"test"}'curl http://127.0.0.1:8001/health
curl http://127.0.0.1:8002/health
curl http://127.0.0.1:8003/healthExpected response: {"status": "healthy", "peers": 2}
If a node crashes mid-debate:
- Orchestrator detects health check failure
- Attempts graceful restart
- If restart fails 3x, aborts debate and shuts down
Judge waits max 30 seconds for arguments:
- If bull message arrives but bear doesn't → timeout
- Judge publishes "INCONCLUSIVE" verdict
- Debate proceeds but conviction not updated
Local nodes all on same machine, so no partitions in normal demo mode. In distributed setup, AXL consensus would handle partition recovery.
- Message Publish Latency: 10-50ms (local HTTP)
- Message Delivery: <100ms (peer mesh routing)
- Judge Inbox Poll: 50ms per poll (1 poll/sec = 5% overhead)
Total round latency breakdown:
- Bull reasoning: 2-4 seconds
- Bull publish: 0.05s
- Bear reasoning: 2-4 seconds
- Bear publish: 0.05s
- Judge reasoning: 2-4 seconds
- Judge verdict: 0.05s
- Total per round: 6-12 seconds
Test AXL message flow:
python scripts/test_axl_messaging.pyThis:
- Starts three nodes
- Publishes test messages
- Verifies inbox reception
- Shuts down cleanly
AXL provides:
- Decentralization: No centralized message broker
- Observability: Agent communication visible and auditable
- Authenticity: Cryptographically verified messages
- Modularity: Agents interact via standard HTTP endpoints
- Scalability: Mesh routing scales better than centralized queues
Without AXL, Convexa loses the peer-to-peer property that makes agent debate trust-minimized. With it, the debate is fully decentralized and observable.