Overview
The AI agent — Synapse — is a two-speed nervous system. It connects to the sidecar via Server-Sent Events and wakes on three triggers. Each wake first passes through a deterministic gate (decideCycle) that routes the cycle to one of two tiers: a Reflex fast-path that submits with
no model call, or a Cortex ReAct loop (the LLM) for anything that needs judgment.
tx_enqueued
A new user transaction arrived in the queue.
bundle_settled(failed)
A bundle terminated with a failure class that requires a retry decision.
30s watchdog
Safety net for SSE reconnect gaps — agent checks for any missed work.
Two-tier router (Reflex / Cortex)
Most submissions need no judgment: a stable floor with a fresh transaction has one correct move — bid the known clearing floor — and paying for LLM inference on that path is latency for nothing. So every wake first hits a gate that routes it to the cheapest tier that can decide it correctly.Reflex — System 1
Deterministic fast-path, no model call (~0 ms). Fires only when every trivial-case check
holds (fresh tx, stable floor, Jito leader, no unrecovered failure, oracle fresh) and submits at
the recent clearing floor.
Cortex — System 2
The LLM ReAct loop (~4.2 s decision). Any ambiguity escalates here — a retry, a moving floor,
a near-TTL transaction, a cold start, an unrecovered failure.
Invalid),
and the resulting bundle_settled(failed) routes its retry to the Cortex. The cost of an optimistic
fast-path is bounded by one free rejection — never a lamport.
The tier is a runtime toggle, not a fork. cortex is the default (a bare deployment reasons on
every cycle); flip to reflex live with no restart:
AGENT_DECISION_MODE=reflex. Each landing records which tier produced
it on the lifecycle entry (decision_mode, and escalated in reflex mode).
The Cortex cycle (System 2)
When the gate routes a wake to the Cortex, this is the loop that runs (the Reflex fast-path skips all of it). Data gathering is parallelized out of the LLM hot path. Before the model is invoked, the outer loop fetchesgetState, getTip, getLifecycle, and
getPendingTxs in a single Promise.all and injects the results directly
into the cycle prompt. The agent reads pre-loaded context and goes straight
to the decision — typically two tool calls total.
Tip selection
The agent reads five percentiles on the live Jito tip oracle (p25 … p99) and opens at the
lowest tip recent history shows landing — often the observed minimum, not a high percentile —
because a fee_too_low rejection pays no tip and is free information. It escalates only on that
free failure, climbing one tier per consecutive fee_too_low:
This is not hardcoded logic — the agent reads the lifecycle history and reaches this conclusion through its reasoning trace. The trace is written to the lifecycle entry so every decision is auditable.
Example reasoning trace from a real cycle:
“fee_too_low at p75=5000. Pattern: 2 consecutive fee_too_low. Oracle p99=1,000,000. Tx age=34/50 slots near TTL. Escalating to p95=100,000. If this fails: p99.”
The clean boundary
The architectural invariant that makes the system independently upgradeable:Rust never calls an LLM
All execution — signing, serialization, Jito submission — happens in the
Rust sidecar. The agent is an external observer that issues commands.
Agent never touches a keypair
The agent calls
POST /internal/submit with a tip amount and list of
tx_ids. The sidecar does the rest.Configuring the model
The agent model is configured via environment variables in the agent process:AGENT_DECISION_MODE (cortex is the default — a bare
deployment reasons on every cycle; set reflex for the fast-path). It’s runtime-flippable via
sinker.setMode() or POST /internal/mode — no restart.
Retry race condition
A non-obvious concurrency hazard: thebundle_settled(failed) SSE event fires while the original submit cycle is still executing. The agent handles this with a pendingRetry field:
finally block with zero additional delay.