Off-chain · TypeScript
Watcher
Subscribes to LendingPool events over WSS, mirrors active borrower balances in memory, and polls health factor. When a position drops below threshold it submits the Scorer prompt through flagPosition.
Sentinel
Sentinel coordinates four specialised agents around a single on-chain orchestrator. Two of them run as TypeScript binaries where milliseconds win MEV races; the other two run as Somnia native agents where validator consensus wins trust. Every transition lands as an event on chain.
Layout
The split is deliberate. Detection and execution need to win speed races. Scoring and routing need to win trust battles. Sentinel does not collapse those into a single design.
Off-chain · TypeScript
Subscribes to LendingPool events over WSS, mirrors active borrower balances in memory, and polls health factor. When a position drops below threshold it submits the Scorer prompt through flagPosition.
On-chain · Somnia native
Receives the position snapshot inside an inferNumber payload and returns a uint256 risk score in [0, 10000]. Below-threshold scores cancel the case without penalty.
On-chain · Somnia native
Receives the scored position and returns the debt-to-cover amount in the debt asset's base units. Bounded by the reserve's close-factor cap so the Executor cannot over-liquidate.
Off-chain · TypeScript
Watches the Coordinator's Routed event and races every other Executor in the network to call execute(caseId). The first transaction in earns the settlement reward for the operator.
Off-chain TypeScript binaries handle the latency-sensitive ends of the pipeline. The Watcher subscribes to LendingPool events over WSS and reacts to a position drifting below its health threshold inside a single block. The Executor races every other Executor in the network to call execute(caseId) the moment a case is Routed - whoever lands first earns the settlement fees for the operator.
Somnia native agents handle the trust-sensitive middle. Scorer and Router are invoked through ISomniaAgents.createRequest; a randomly elected three-validator subcommittee runs the inference, reaches consensus, and ships the result back as a callback transaction. The on-chain Response receipt is the trust anchor third parties cite.
Pipeline
The Coordinator advances each case through a deterministic state machine. Reaching the next state requires either a validator callback or a permissionless on-chain push - never a private off-chain decision.
None ─► Flagged ─(Scorer callback)► Scored ─(advanceToRouter)► Routed ─(execute)► Executed
│
└─(below-threshold score or revert)──► CancelledflagPosition opens a case and submits the Scorer prompt to the Somnia platform. After validator consensus the handleScorerResponse callback transitions to Scored or Cancelled (low score, agent error, or empty result). Any keeper - typically the same Watcher that flagged the position - then calls advanceToRouter to dispatch the Router request. After the Router callback the case is Routed and gated for execution.
The Coordinator is a payload pass-through. Callers build the bytes off-chain in the exact shape the configured Somnia base agent expects. The agent today is llm-inference, which accepts an inferNumber(string,string,int256,int256,bool) calldata payload and returns a clamped 32-byte int256.
// Scorer payload: range [0, 10000]
encodeInferNumber({
prompt: buildScorerPrompt(positionSnapshot),
system: "Return a single integer between minValue and maxValue.",
minValue: 0n,
maxValue: 10_000n,
});
// Router payload: range [1, debtBalance * closeFactorBps / 10000]
encodeInferNumber({
prompt: buildRouterPrompt({ ...positionSnapshot, score }),
system: "Return a single integer between minValue and maxValue.",
minValue: 1n,
maxValue: closeFactorCap,
});NegativeAgentResponse. The full encoder ships with the off-chain agent runtime under packages/agents/src/prompts/payload.ts and is shared between the Watcher and any keeper that calls advanceToRouter.Lending
Sentinel ships with a minimal lending pool: one collateral reserve, one debt reserve, no interest accrual. Plain Solidity with the same liquidation math production protocols use.
The pool tracks one collateral asset (WETH) and one debt asset (USDC) on the deployed testnet instance. Each reserve carries its own liquidation threshold, liquidation bonus, and close factor. The Coordinator can be re-pointed at additional reserves by the protocol owner without redeploying.
| Reserve | Liquidation threshold | Liquidation bonus | Close factor |
|---|---|---|---|
| WETH | 75% | 5% | - |
| USDC | - | - | 50% |
Health factor is computed against the on-chain oracle:
HF = (collateralValueUSD × LT_bps / 10_000) / debtValueUSD
A position is liquidatable when HF < 1.
The Coordinator's flagPosition reverts above 1 with PositionHealthy(hf).A liquidation seizes collateral worth debtToCover × (1 + bonus) and capped at the borrower’s remaining collateral. The Executor cannot cover more than debtBalance × closeFactorBps / 10_000 in a single pass - the Router enforces this in its own output range, and the LendingPool double-checks it at execute time.
Payout
Every successful case routes its seized collateral through a single Splitter contract. The split is deterministic, on-chain, and pull-payment - no off-chain bookkeeping, no escrow contract upgrade path.
60%
Split four ways among Watcher, Scorer, Router and Executor of the case. Reputation-weighted; equal split when all scores tie.
30%
Owned by the AgentRegistry deployer at testnet. Held in the Splitter until withdrawn through a pull payment.
10%
Reserved for community-submitted specialist agents. Held by the Splitter; release rules live with the AgentRegistry owner.
Trust
Each agent ID has two counters on the Reputation contract: successes and failures. The score is a clamped affine of the two. The Coordinator is the only writer; every transition that closes a case credits all four participating agents.
function recordSuccess(uint256 agentId) external onlyCoordinator;
function recordFailure(uint256 agentId) external onlyCoordinator;
function performanceOf(uint256 agentId) external view
returns (uint256 successes, uint256 failures, uint256 score);
// score = max(0, successes * REWARD - failures * PENALTY)The Splitter reads the reputation score when distributing the agent tranche, weighting payouts toward operators that have historically settled cases without faulting. Agents with zero score still earn at parity if no other agent has a positive score - this seeds the system fairly for new joiners.
Try it
The Case Console panel on the dashboard exposes every transition as a single signature. Connect the deployer wallet to unlock the oracle and flag actions; any wallet can deposit, borrow, repay and withdraw.
Step 01
Open Position
Reset the oracle to $3,000, mint 10 WETH from the faucet, deposit, then borrow 15,000 USDC. Five sequential signatures, finishes in seconds.
Step 02
Crash Oracle
Drop the WETH oracle to the price that puts the connected wallet's HF at 0.85. Computed from live position state, so it works for any borrower regardless of how much collateral they have accumulated.
Step 03
Trigger flagPosition
Submit the Scorer prompt. The Coordinator dispatches a Somnia createRequest; the validator subcommittee callback lands within seconds, transitioning the case to Scored.
Step 04
Advance to Router
Push the case forward. The Coordinator dispatches the second createRequest; the Routed callback lands within seconds, locking in the debt-to-cover.
Step 05
Execute
Settle the case. LendingPool.liquidate runs, the seized collateral is forwarded to the Splitter, and Reputation credits +100 to all four participating agents.
Step 06
Close Position
Clear any leftover debt and withdraw the remaining collateral. The console mints any USDC shortfall from the faucet, approves the pool, repays in full, then withdraws every unit of WETH - up to four signatures, each awaited before the next so the solvency post-check on withdraw always passes.
Step 07
Reset Oracle
Restore the WETH price to $3,000 between runs so the next case starts from a clean slate.
Scored events directly and calls advanceToRouter without manual intervention. The Executor binary watches Routed and races to settle. Both processes are shipped under packages/agents in the source tree.FAQ