From somnia-agents-skills
Comprehensive Somnia Agents knowledge — invoke decentralized AI / oracle / web-extraction agents from smart contracts via the SomniaAgents platform. Covers the request lifecycle, two-pot gas model, Majority vs Threshold consensus, callback handling, receipts, and the 3 base agents (JSON API Request, LLM Inference, LLM Parse Website). Use when writing contracts that call agents, sizing deposits, decoding responses, or debugging stuck requests.
How this skill is triggered — by the user, by Claude, or both
Slash command
/somnia-agents-skills:somnia-agentsThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
You are now working in the context of **Somnia Agents** — Somnia's on-chain platform for invoking decentralized, auditable compute jobs (oracles, LLM inference, web extraction) from smart contracts. Agents run on a subset of validator nodes that reach consensus on the result, just like a normal contract call. This document is your complete reference — use it to write correct integrations and gi...
You are now working in the context of Somnia Agents — Somnia's on-chain platform for invoking decentralized, auditable compute jobs (oracles, LLM inference, web extraction) from smart contracts. Agents run on a subset of validator nodes that reach consensus on the result, just like a normal contract call. This document is your complete reference — use it to write correct integrations and give accurate guidance.
Companion skills (in this bundle):
somnia-agents-json-fetch,somnia-agents-llm-inference,somnia-agents-llm-parse-website— per-agent ABI deep-divessomnia-agents-invoke— interactive CLI to invoke any agent without writing a contract
!cat references/network-config.json
Default to testnet unless the user explicitly asks for mainnet. Always confirm before deploying to mainnet. Agent IDs are the same on both networks; only the platform contract addresses differ.
!cat references/agents.json
The 3 base agents above are the ones currently exposed by the platform. Their ABIs are stable on both networks. For each agent, the agentId is what you pass to createRequest, and pricePerAgent is the per-subcommittee-member execution reward you must include in msg.value (see Gas Model below).
The Agent Explorer is the canonical browse / invoke UI:
It also generates ready-to-paste Solidity and TypeScript snippets for any agent method.
A Somnia Agent is a decentralized, sandboxed compute container addressable by agentId. Smart contracts invoke agents the same way they'd call any other contract — with an ABI-encoded payload — except execution happens off the EVM, on validator nodes, and the result comes back asynchronously via a callback.
dApp ──createRequest{value: deposit}──► SomniaAgents ──RequestCreated event──► validator subcommittee
│
executes agent
│
submitResponse
│
dApp ◄─handleResponse(responses, status)── SomniaAgents ◄──── consensus reached ────┘
+ rebate (any unspent budget)
Asynchronous by design. createRequest returns a requestId — the actual result arrives later in a callback. Track pending requests in your contract.
EVM-native interface. Agent calls are just ABI calldata. Use abi.encodeWithSelector, viem's encodeFunctionData, ethers, etc.
Deterministic AI. LLM agents run with fixed seeds and temperature=0, so multiple validators independently produce byte-identical outputs — that's how consensus is reached on AI results.
1. createRequest{value: deposit}(agentId, callbackAddr, callbackSelector, payload)
│ Platform splits msg.value into operations reserve + agent reward pot.
│ Elects subcommittee (default size 3). Emits RequestCreated.
▼
2. Validator subcommittee picks up RequestCreated, executes the agent off-chain.
│ Each runner calls submitResponse(requestId, result, receipt, success, executionCost).
│ Each submission is gas-refunded from the operations reserve.
▼
3. When `threshold` matching results arrive (Majority) or `threshold` successful results (Threshold):
│ Platform calls handleResponse(requestId, responses[], status, details) on your callback.
│ Pays each elected member median(executionCost). Rebates leftovers to requester.
│ Emits RequestFinalized.
▼
4. Receipt is uploaded to the off-chain receipt service (one per validator, subjective).
Terminal statuses: Success (2), Failed (3), TimedOut (4). Default timeout is 15 minutes.
Request and Response structsstruct Request {
uint256 id;
address requester;
address callbackAddress;
bytes4 callbackSelector;
address[] subcommittee;
Response[] responses;
uint256 responseCount;
uint256 failureCount;
uint256 threshold;
uint256 createdAt;
uint256 deadline;
ResponseStatus status;
ConsensusType consensusType;
uint256 remainingBudget; // escrow remaining
uint256 perAgentBudget; // cap per elected member, fixed at creation
}
struct Response {
address validator;
bytes result; // ABI-encoded return value(s) of the agent function
ResponseStatus status; // Success or Failed
uint256 receipt; // off-chain receipt id (currently 0 on-chain)
uint256 timestamp;
uint256 executionCost; // self-reported, capped at perAgentBudget
}
enum ResponseStatus { None, Pending, Success, Failed, TimedOut }
enum ConsensusType { Majority, Threshold }
The vendored Solidity interface lives at references/interfaces/IAgentRequester.sol.
When you call createRequest{value: deposit}, the contract splits the deposit into two virtual pots — held in the same remainingBudget but with separate purposes:
| Pot | Size | Funds |
|---|---|---|
| Operations reserve | minPerAgentDeposit × subcommitteeSize (≈ 0.01 × subSize) | Per-runner gas refunds, callback gas, finalisation overhead, keeper refund on timeout |
| Agent reward pot | msg.value − reserve | Median of self-reported executionCost paid to every elected subcommittee member |
The contract floor is exactly the operations reserve:
require(msg.value >= minPerAgentDeposit * subcommitteeSize, InsufficientDeposit(...));
Sending only the floor is not enough. If msg.value == reserve, then perAgentBudget = 0. Runners watching RequestCreated see that cap, decide it's not worth their compute, and skip the request — it sits idle and times out.
The practical formula:
msg.value ≥ getRequestDeposit() + pricePerAgent × subcommitteeSize
For default subcommitteeSize = 3:
| Agent | Reserve | Reward pot | Practical msg.value |
|---|---|---|---|
| JSON API Request | 0.03 | 0.03 × 3 = 0.09 | 0.12 |
| LLM Inference | 0.03 | 0.07 × 3 = 0.21 | 0.24 |
| LLM Parse Website | 0.03 | 0.10 × 3 = 0.30 | 0.33 |
(Whole tokens — SOMI on Mainnet, STT on Testnet. Prices come from references/agents.json.)
Sending more than the listed price is fine — runners are paid min(reportedExecutionCost, perAgentBudget), so any margin is rebated to the requester on finalisation.
receive()Rebates and timeout refunds are pushed automatically on finalisation. If your contract has no receive() external payable (and no fallback() accepting value), the transfer fails silently — emitted as NativeTransferFailed(recipient, amount) — and the funds stick in the platform contract.
receive() external payable {}
| Type | Finalisation | When to use |
|---|---|---|
Majority (0) | threshold validators return byte-identical result | Deterministic computation: math, exact API lookups, deterministic LLM (temperature=0) |
Threshold (1) | threshold validators return any successful result | Non-deterministic: price feeds (median), random numbers (XOR), sensor readings (average) |
createRequest always uses Majority. For Threshold (or custom subcommittee size / threshold / timeout) use:
function createAdvancedRequest(
uint256 agentId,
address callbackAddress,
bytes4 callbackSelector,
bytes calldata payload,
uint256 subcommitteeSize, // ≤ operator-configured max (typically 10)
uint256 threshold, // ≤ subcommitteeSize; 3-of-5 is a sane default
ConsensusType consensusType,
uint256 timeout // seconds
) external payable returns (uint256 requestId);
Threshold tips:
threshold == subcommitteeSize — a single validator failure prevents finalisation.responses[i].status == Success before decoding.getAdvancedRequestDeposit(subSize) for the matching reserve floor.Recipe (Bitcoin price oracle, JSON API agent, default consensus):
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {
IAgentRequester,
IAgentRequesterHandler,
Response,
Request,
ResponseStatus
} from "./IAgentRequester.sol";
interface IJsonApiAgent {
function fetchUint(string calldata url, string calldata selector, uint8 decimals)
external returns (uint256);
}
contract PriceOracle is IAgentRequesterHandler {
IAgentRequester public immutable platform;
uint256 public constant JSON_API_AGENT_ID = 13174292974160097713;
uint256 public constant SUBCOMMITTEE_SIZE = 3;
uint256 public constant PRICE_PER_AGENT = 0.03 ether;
uint256 public latestPrice;
mapping(uint256 => bool) public pendingRequests;
constructor(address platform_) { platform = IAgentRequester(platform_); }
function requestPrice() external payable returns (uint256 requestId) {
bytes memory payload = abi.encodeWithSelector(
IJsonApiAgent.fetchUint.selector,
"https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=usd",
"bitcoin.usd",
uint8(8)
);
uint256 deposit = platform.getRequestDeposit() + PRICE_PER_AGENT * SUBCOMMITTEE_SIZE;
require(msg.value >= deposit, "Underfunded");
requestId = platform.createRequest{value: deposit}(
JSON_API_AGENT_ID, address(this), this.handleResponse.selector, payload
);
pendingRequests[requestId] = true;
}
function handleResponse(
uint256 requestId,
Response[] memory responses,
ResponseStatus status,
Request memory /* details */
) external override {
require(msg.sender == address(platform), "Only platform");
require(pendingRequests[requestId], "Unknown request");
delete pendingRequests[requestId];
if (status == ResponseStatus.Success && responses.length > 0) {
latestPrice = abi.decode(responses[0].result, (uint256));
}
}
receive() external payable {}
}
The full step-by-step walkthrough (encoding, sizing, decoding, receipts) lives in references/quickstart.md.
require(msg.sender == address(platform), "Only platform");mapping(uint256 => bool) pendingRequests (or richer state) — never act on a callback without a matching pending entry.Success, Failed, TimedOut. Decoding responses[0].result on a non-Success response panics.responses.length before indexing — even on Success, it's safer to check.receive() to accept rebates.// Single value
uint256 v = abi.decode(responses[0].result, (uint256));
string memory s = abi.decode(responses[0].result, (string));
// Multiple values (matches the agent function's return tuple)
(uint256 a, string memory b) = abi.decode(responses[0].result, (uint256, string));
// Threshold consensus — iterate
for (uint256 i = 0; i < responses.length; i++) {
if (responses[i].status == ResponseStatus.Success) {
uint256 val = abi.decode(responses[i].result, (uint256));
// aggregate (median, XOR, average...)
}
}
You don't normally call agents directly from off-chain — the platform contract is the entry point, so you typically deploy a wrapper contract. The exception is testing and exploration: use the somnia-agents-invoke action skill (or the Agent Explorer web app) to fire a one-shot request without deploying anything.
When you do call from TypeScript, you'll send a transaction to SomniaAgents.createRequest(...) and listen for RequestCreated (to get requestId) and RequestFinalized (to know when consensus settled). Decode the on-chain Request.responses via getRequest(requestId) for the actual result data.
Use the network config:
import { createPublicClient, createWalletClient, http } from 'viem';
const network = 'testnet'; // or 'mainnet'
const config = JSON.parse(readFileSync('references/network-config.json', 'utf8'))[network];
const platformAddr = config.contracts.SomniaAgents;
Every execution produces an off-chain receipt — a step-by-step audit trail of what one validator did to compute the result. The receipt is subjective (timing differs per node); only the final result is consensused. Useful for debugging, transparency, and showing reasoning chains for LLM agents.
| Network | Receipts API | Web view |
|---|---|---|
| Mainnet | https://receipts.mainnet.agents.somnia.host/agent-receipts?contractAddress=<somniaAgents>&requestId=<id> | https://agents.somnia.network/receipts/<id> |
| Testnet | https://receipts.testnet.agents.somnia.host/agent-receipts?contractAddress=<somniaAgents>&requestId=<id> | https://agents.testnet.somnia.network/receipts/<id> |
curl -s "https://receipts.testnet.agents.somnia.host/agent-receipts?contractAddress=<somniaAgents>&requestId=<id>" | jq
Common step types: request_received, request_decoded, http_request, http_response, llm_request, llm_response, reasoning, value_extracted, response_encoded, error.
The on-chain
Response.receiptfield is currently always0— receipts live off-chain only. Don't gate logic on it.
| Error | Selector signature | What it means / fix |
|---|---|---|
InsufficientDeposit(sent, required) | 0xa6c0d9c9-style | msg.value below the operations-reserve floor. Use getRequestDeposit() + pricePerAgent × subSize. |
AgentNotFound(agentId) | — | The agentId doesn't exist in AgentRegistry. Double-check against the Explorer. |
SubcommitteeSizeExceedsMax(size, max) | — | Custom subcommitteeSize exceeds the operator's cap (typically 10). Lower it. |
InvalidThreshold(threshold, subSize) | — | threshold > subcommitteeSize or threshold == 0. Pick 0 < threshold ≤ subcommitteeSize. |
NotEnoughActiveMembers(available, required) | — | Not enough registered runners for the requested subcommittee size. Use a smaller subcommitteeSize. |
InvalidTimeout() | — | timeout == 0. Pass a positive duration in seconds. |
RequestTimedOut(requestId) / status TimedOut | — | Most often: floor-only deposit → runners skipped. Refund the rebate, retry with pricePerAgent × subSize on top. |
| Callback never fires | — | Either request still pending (check getRequest(id).status), or your callback reverted (check the platform tx logs for CommitteeDepositFailed / no callback emit). |
| Rebate never arrives | — | Callback contract has no receive() / fallback() — NativeTransferFailed was emitted. Add receive() and recover via off-chain coordination if amount is large. |
The Solidity interface in references/interfaces/IAgentRequester.sol lists all custom errors with NatSpec.
getRequestDeposit() returns the operations-reserve floor (minPerAgentDeposit × subSize). Sending exactly this amount makes perAgentBudget = 0 and runners skip the request. Always add pricePerAgent × subcommitteeSize on top.
If you call createAdvancedRequest with a non-default subcommitteeSize, use getAdvancedRequestDeposit(subSize) for the floor — the default getRequestDeposit() reflects the platform default, not your override.
Agent IDs are the same on Mainnet and Testnet, but SomniaAgents and AgentRegistry addresses are not. Always read addresses from references/network-config.json, never hardcode.
createRequest returns a requestId immediately. The actual result lands in your callback later (typically seconds for JSON Fetch, longer for LLM agents). Don't try to read latestPrice or similar in the same transaction that called createRequest.
receive()Without receive() external payable, your callback contract can't accept the rebate. The platform emits NativeTransferFailed and the funds stay parked in the platform contract until off-chain recovery.
responses[0].result blindlyAlways check status == Success and responses.length > 0 before decoding. Decoding empty bytes panics; decoding a Failed response yields garbage.
handleResponse is external; without require(msg.sender == address(platform)) anyone can call it and corrupt your state.
Today's runner stack uses fixed per-type prices (0.03 / 0.07 / 0.10). On-chain price discovery is on the roadmap — until then, undershooting the per-agent price guarantees a timeout regardless of how generous the operations reserve is.
| Need to... | Read |
|---|---|
| Step-by-step walkthrough | references/quickstart.md |
| Full Solidity interface | references/interfaces/IAgentRequester.sol |
| Platform ABI | references/abi/AgentRequester.json |
| Per-agent ABI + IDs + prices | references/agents.json |
| Network config | references/network-config.json |
| JSON API agent deep-dive | somnia-agents-json-fetch skill |
| LLM Inference deep-dive | somnia-agents-llm-inference skill |
| LLM Parse Website deep-dive | somnia-agents-llm-parse-website skill |
| Invoke from CLI without contract | somnia-agents-invoke skill |
npx claudepluginhub emrestay/somnia-agents-skills --plugin somnia-agents-skillsProvides behavioral guidelines to reduce common LLM coding mistakes, focusing on simplicity, surgical changes, assumption surfacing, and verifiable success criteria.
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Creates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.