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:
Consecutive fee_too_low failures | Tip selected |
|---|---|
| 0 | lowest recent clearing tip (probe from below) |
| 1 | p75 |
| 2 | p95 |
| ≥ 3 | p99 |
“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.