bitpro가 원문에서 글자만 모은 내용이에요. 표·그림·수식은 빠지거나 깨질 수 있어요. 전체 54쪽 중 51쪽
testnet.agents.somnia.host';
const platform = '0x037Bb9C718F3f7fe5eCBDB0b600D607b52706776';
const requestId = '<your-request-id>'; // returned by platform.createRequest
// Step 1 — manifest
const manifest = await (await fetch(
`${baseUrl}/agent-receipts?contractAddress=${platform}&requestId=${requestId}`
)).json();
// → { contractAddress, requestId, receipts: [<gcs url>, ...], count }
// Step 2 — per-validator receipts
const receipts = await Promise.all(
manifest.receipts.map(url => fetch(url).then(r => r.json()))
);
console.log(`${receipts.length} validator receipts for request ${requestId}`);
```
Both query parameters are required. Omitting `contractAddress` returns:
```json
HTTP 400 { "error": "Missing contractAddress query parameter" }
```
### Quick triage with `type=minimal`
For lightweight triage — "did this resolve, did the subcommittee agree, what was the error?" — the receipts service also exposes a `type=minimal` mode that returns all per-validator receipt JSON **inline in a single call**, with no second-step GCS fetch needed.
```
GET {baseUrl}/agent-receipts?contractAddress=<platform>&requestId=<id>&type=minimal
```
The minimal response:
* Inlines every validator's receipt JSON in `receipts[]` (objects, not GCS URLs).
* Caps long string fields (HTML page text, model output, raw responses) at 1024 characters with a `<truncated, fetch the receipt URL for complete contents>` marker.
* Adds derived `consensusType`, `requestDetails`, and `count` at the top level for convenience.
Use it for triage and dashboards. If a field you need was truncated, fall back to the default (URL manifest) mode and fetch the receipt's GCS URL directly for the full content.
## Receipt Structure
### Top-level fields
A single per-validator receipt looks like this:
```json
{
"requestId": "<requestId>",
"agentId": "<agentId>",
"contractAddress": "<platform address>",
"agentRunnerAddress": "<validator address>",
"consensusType": 0,
"requestDetails": {
"requester": "<caller address>",
"callbackAddress": "<callback address>",
"callbackSelector": "0x........",
"threshold": 2,
"subcommitteeSize": 3,
"timeoutSeconds": 300
},
"status": "success",
"startedAt": "2026-01-15T10:30:00.000Z",
"completedAt": "2026-01-15T10:30:09.800Z",
"elapsedMs": 9800,
"agentImageUri": "https://storage.googleapis.com/somnia-agents-artifacts/agents/<agent>/<hash>.tar",
"request": "0x...",
"response": { "result": "0x..." },
"agentReceipt": {
"steps": [ /* see below */ ],
"llmUsage": { "promptTokens": 0, "completionTokens": 0, "totalTokens": 0, "requests": 0, "streamingRequests": 0 },
"bandwidthUsage": { "bytesIn": 0, "bytesOut": 0, "requests": 0 },
"request": "0x...",
"result": "0x..."
}
}
```
| Field | Purpose |
| ----------------------------------------------- | ----------------------------------------------------- |
| `status` | `success`, `failed`, or `timeout` |
| `elapsedMs` | End-to-end duration on this validator |
| `agentRunnerAddress` | Which validator produced this receipt |
| `agentImageUri` | Pinned artifact bundle the agent ran from (auditable) |
| `requestDetails.subcommitteeSize` / `threshold` | Consensus config in force for this request |
| `consensusType` | `0` = Majority, `1` = Threshold |
| `agentReceipt.steps` | Per-agent execution stages (see below) |
| `agentReceipt.llmUsage` | Token counts for cost analysis |
| `agentReceipt.bandwidthUsage` | Outbound HTTP volume |
Failed requests follow the same envelope but add a top-level `errorMessage` and omit `agentReceipt` and `response`. Always check `status` before reading `agentReceipt`.
### Step names by agent
Step names live under `agentReceipt.steps` and **vary by agent type**:
| Agent | Step sequence |
| ----------------- | ------------------------------------------------------------------------------------------------------------ |
| JSON API Request | `request_received → request_decoded → handler_started → handler_completed → response_encoded` |
| LLM Inference | `request_received → request_decoded → handler_started → llm_response → handler_completed → response_encoded` |
| LLM Parse Website | `request → fetch → convert → extract` |
Each step entry includes `name`, `started_at`, `status`, and an `inputs` object (and `output` where applicable). LLM-backed agents may also include auto-injected `reasoning`, `answerable`, and `confidence_score` (0–100) fields in the structured-extraction step. For LLM Inference requests where chain-of-thought was enabled (via the `chainOfThought` argument), the `llm_response` step also surfaces the model's `thinking` output.
## Using Receipts for Debugging
### Tracing failures
When a request fails or times out, check `status` and the top-level `errorMessage`. For successes that returned an unexpected result, walk `agentReceipt.steps` — each step's `status` and any embedded output show exactly where execution diverged. For HTTP-based agents (JSON API Request, Parse Website), `bandwidthUsage.requests` is a quick sanity check that an outbound call happened at all.
### Checking LLM reasoning
For LLM agents, the `agentReceipt.steps` entry covering the model call carries the inputs and the structured output. When the request opted into chain-of-thought (`chainOfThought = true`), the `thinking` field on the `llm_response` step surfaces the model's intermediate reasoning. The auto-injected `confidence_score` is useful for understanding low-confidence answers.
### Verifying external data (JSON API Request)
The `handler_completed` step records the upstream response payload and the value extracted via the selector path — useful for confirming that the JSON shape on the API hasn't changed underneath you.
### Verifying scraped content (LLM Parse Website)
Parse Website's `fetch` step records the URL(s) loaded; the `convert` step records the markdown handed to the model; the `extract` step records the structured-output result. If extraction quality is low, the `convert` output usually shows why (collapsed structure, missing sections, dynamic content not rendered).
### Monitoring cost
Both `llmUsage` and `bandwidthUsage` are aggregated per validator on every receipt. Summing across requests gives a reliable per-agent cost profile (especially for LLM Inference and Parse Website, where token usage drives the bulk of work).
## Next Steps
* [Gas Fees](/agents/invoking-agents/gas-fees) — Understand costs
# (Advanced) Custom Consensus
By default, Somnia Agents uses **Majority** consensus — the request finalizes when a threshold of validators return byte-identical results. This works well for deterministic agents (math, API lookups with exact values, etc.) but breaks down when each validator is expected to return a *different* value.
**Threshold** consensus solves this. Instead of requiring matching results, it finalizes as soon as the threshold number of validators respond successfully — regardless of whether their results agree. Your callback receives *all* individual results, and you aggregate them however you like.
## Consensus Types
| Type | Finalization Condition | `responses[]` in Callback |
| --------------- | ------------------------------------------------------- | ---------------------------------------------------------------------------------------- |
| `Majority` (0) | `threshold` validators return the **same** result | Responds with all responses; at least `threshold` will match on the same `.result` value |
| `Threshold` (1) | `threshold` validators return **any** successful result | All responses (check `.status` to filter successful ones) |
## Using Threshold Consensus
Call `createAdvancedRequest` instead of `createRequest`:
```solidity
platform.createAdvancedRequest{value: deposit}(
agentId,
address(this),
this.handleResponse.selector,
payload,
5, // subcommitteeSize
3, // threshold — finalize after 3 of 5 respond
ConsensusType.Threshold, // don't require matching results
1 minutes // timeout
);
```
The `threshold` parameter controls how many successful responses are needed before finalization. A typical choice is 3 of 5 — this tolerates up to 2 validator failures while still collecting enough data points for meaningful aggregation.
```solidity
// Get the required deposit for a custom subcommittee size
uint256 deposit = platform.getAdvancedRequestDeposit(subcommitteeSize);
```
## Example: Price Oracle (Median)
Each validator fetches a price independently. Results may differ slightly. Your contract takes the median to filter outliers.
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
enum ConsensusType { Majority, Threshold }
enum ResponseStatus {
None, // 0 - Default zero value (uninitialized storage)
Pending, // 1 - Awaiting responses
Success, // 2 - Consensus reached normally
Failed, // 3 - Validators reported failure
TimedOut // 4 - Request timed out
}
struct Response {
address validator;
bytes result;
ResponseStatus status;
uint256 receipt;
uint256 timestamp;
uint256 executionCost;
}
struct 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;
uint256 perAgentBudget;
}
interface IAgentRequester {
function createAdvancedRequest(
uint256 agentId,
address callbackAddress,
bytes4 callbackSelector,
bytes calldata payload,
uint256 subcommitteeSize,
uint256 threshold,
ConsensusType consensusType,
uint256 timeout
) external payable returns (uint256 requestId);
function getAdvancedRequestDeposit(uint256 subcommitteeSize) external view returns (uint256);
}
interface IPriceAgent {
function getPrice(string calldata pair) external returns (uint256);
}
contract MedianPriceOracle {
IAgentRequester public platform;
uint256 public constant PRICE_AGENT_ID = 12345; // Replace with actual agent ID from the web app
uint256 public constant SUBCOMMITTEE_SIZE = 5;
uint256 public constant COST_PER_AGENT = 0.03 ether; // JSON Fetch (see Gas Fees)
uint256 public latestPrice;
constructor(address _platform) {
platform = IAgentRequester(_platform);
}
function requestPrice(string calldata pair) external payable returns (uint256) {
bytes memory payload = abi.encodeWithSelector(
IPriceAgent.getPrice.selector,
pair
);
// Safe deposit: contract floor + per-agent execution reward.
uint256 reserve = platform.getAdvancedRequestDeposit(SUBCOMMITTEE_SIZE);
uint256 reward = COST_PER_AGENT * SUBCOMMITTEE_SIZE;
uint256 deposit = reserve + reward;
return platform.createAdvancedRequest{value: deposit}(
PRICE_AGENT_ID,
address(this),
this.handlePrice.selector,
payload,
SUBCOMMITTEE_SIZE,
3, // Finalize after 3 of 5 respond
ConsensusType.Threshold,
1 minutes // Timeout
);
}
function handlePrice(
uint256 requestId,
Response[] memory responses,
ResponseStatus status,
Request memory details
) external {
require(msg.sender == address(platform), "Only platform");
if (status != ResponseStatus.Success || responses.length == 0) return;
// Decode all prices from successful responses and take the median
uint256 count;
uint256[] memory prices = new uint256[](responses.length);
for (uint256 i = 0; i < responses.length; i++) {
if (responses[i].status == ResponseStatus.Success) {
prices[count] = abi.decode(responses[i].result, (uint256));
count++;
}
}
// Resize array to actual count
assembly { mstore(prices, count) }
if (count > 0) latestPrice = _median(prices);
}
function _median(uint256[] memory values) internal pure returns (uint256) {
// Insertion sort (fine for small arrays)
for (uint256 i = 1; i < values.length; i++) {
uint256 key = values[i];
uint256 j = i;
while (j > 0 && values[j - 1] > key) {
values[j] = values[j - 1];
j--;
}
values[j] = key;
}
return values[values.length / 2];
}
receive() external payable {}
}
```
Taking the median of independent price observations naturally filters outliers and manipulation — with 3 of 5 results, the median remains accurate even if one validator returns a garbage value.
## Example: Random Oracle (XOR)
Each validator returns a random `uint256`. Your contract XORs all values together to produce a combined random number. This is secure as long as **at least one** responding validator provides a truly random value — even if the others are compromised or predictable, the XOR output remains unpredictable.
```solidity
interface IRandomAgent {
function random() external returns (uint256);
}
contract RandomOracle {
IAgentRequester public platform;
uint256 public constant RANDOM_AGENT_ID = 67890; // Replace with actual agent ID from the web app
uint256 public constant SUBCOMMITTEE_SIZE = 5;
uint256 public constant COST_PER_AGENT = 0.03 ether; // adjust to match the agent type (see Gas Fees)
mapping(uint256 => address) public requesters;
event RandomGenerated(uint256 indexed requestId, uint256 value);
constructor(address _platform) {
platform = IAgentRequester(_platform);
}
function requestRandom() external payable returns (uint256 requestId) {
bytes memory payload = abi.encodeWithSelector(
IRandomAgent.random.selector
);
// Safe deposit: contract floor + per-agent execution reward.
uint256 reserve = platform.getAdvancedRequestDeposit(SUBCOMMITTEE_SIZE);
uint256 reward = COST_PER_AGENT * SUBCOMMITTEE_SIZE;
uint256 deposit = reserve + reward;
requestId = platform.createAdvancedRequest{value: deposit}(
RANDOM_AGENT_ID,
address(this),
this.handleRandom.selector,
payload,
SUBCOMMITTEE_SIZE,
3, // Finalize after 3 of 5
ConsensusType.Threshold,
1 minutes // Timeout
);
requesters[requestId] = msg.sender;
}
function handleRandom(
uint256 requestId,
Response[] memory responses,
ResponseStatus status,
Request memory details
) external {
require(msg.sender == address(platform), "Only platform");
if (status != ResponseStatus.Success || responses.length == 0) return;
// XOR all successful values — secure if at least one provider is honest
uint256 combined = 0;
for (uint256 i = 0; i < responses.length; i++) {
if (responses[i].status == ResponseStatus.Success) {
combined ^= abi.decode(responses[i].result, (uint256));
}
}
emit RandomGenerated(requestId, combined);
}
receive() external payable {}
}
```
## When to Use Which
| Use Case | Consensus | Why |
| ----------------------------------------- | --------- | ----------------------------------------------------------------------- |
| Deterministic computation (math, hashing) | Majority | All validators should return the exact same result |
| API lookups with exact values | Majority | Same endpoint, same response |
| Deterministic LLM inference | Majority | Fixed seed + temperature 0 produces identical outputs across validators |
| Price feeds / continuous values | Threshold | Slight differences expected; median filters outliers |
| Random number generation | Threshold | Values *must* differ; XOR combines them securely |
| Weather data / sensor readings | Threshold | Approximate values; average or median for accuracy |
| Non-deterministic LLM inference | Threshold | Natural variance in outputs; application decides how to reconcile |
## Threshold Tips
* A threshold of 3 out of 5 is a good default — it tolerates up to 2 failures while still collecting enough data for aggregation.
* Higher thresholds (e.g., 4 of 5) give more data points but are more likely to time out if a validator fails.
* Avoid setting `threshold = subcommitteeSize` — a single validator failure would prevent finalization entirely.
* The `responses[]` array in your callback contains all validator responses — check each `responses[i].status` to filter for successful ones.
* `getAdvancedRequestDeposit(subSize)` returns the **operations-reserve floor** (`minPerAgentDeposit × subSize`). That's the minimum `msg.value` the contract will accept, and it only covers gas/callback/upkeep — the agent pot is `msg.value − floor`. Deposit more than the floor to actually pay runners for execution work. See [Gas Fees](/agents/invoking-agents/gas-fees) for the full fund-flow model.
* Unused funds are rebated to the requester after finalisation.
## Next Steps
* [Invoking from Solidity](/agents/invoking-agents/from-solidity) — Basic request/callback pattern
* [Gas Fees](/agents/invoking-agents/gas-fees) — Understand costs and deposits
# Gas Fees
Invoking Somnia Agents requires payment in **SOMI** (mainnet) or **STT** (testnet). This page explains how the deposit you send is split, where every wei ends up, and what to send for `msg.value`. Amounts in the tables below are quoted in whole tokens — read them as SOMI on mainnet or STT on testnet; the split and rebate logic is identical on both networks.
## TL;DR
* `getRequestDeposit()` returns the **operations-reserve floor** (gas refunds, callback gas, finalisation overhead). It is **not** the deposit you should send in practice — pay it only and your request will time out.
* Send `msg.value = floor + (per_agent_price × subcommitteeSize)`, where `per_agent_price` matches the agent type (table below). The contract takes no fee; whatever isn't spent on operations or paid to agents is rebated.
* Today, runners have fixed per-type prices baked into their software (see [Current Per-Agent Prices](#current-per-agent-prices)). If you don't pay at least `subcommitteeSize × per_agent_price` on top of the floor, runners will skip your request and it will time out.
## Two-Pot Model
When you call `createRequest{value: deposit}`, the contract immediately splits the deposit into two virtual pots — both are held in the same `remainingBudget`, but they have separate purposes:
| Pot | Size | Funds |
| ---------------------- | --------------------------------------- | ----------------------------------------------------------------------------------------------------- |
| **Operations reserve** | `minPerAgentDeposit × subcommitteeSize` | Per-runner gas refunds, callback gas, finalisation overhead, keeper refunds for timed-out requests |
| **Agent reward pot** | `msg.value − reserve` | Median execution-cost payment to every elected subcommittee member, routed via the committee contract |
The reserve is the floor the contract enforces:
```solidity
require(msg.value >= minPerAgentDeposit * subcommitteeSize, InsufficientDeposit(...));
```
If you deposit *exactly* the minimum, `perAgentBudget = 0` — and since agents watching `RequestCreated` see that cap up front, rational runners will skip the request entirely. The contract won't reject your call, but the request will almost certainly sit idle and time out. Deposit **more than** the floor if you want agents to actually do the work.
The per-agent share of the reward pot is computed once at request creation, stored on the `Request`, and emitted in `RequestCreated`:
```solidity
perAgentBudget = (msg.value − minPerAgentDeposit × subcommitteeSize) / subcommitteeSize
```
`perAgentBudget` is the cap on what any single runner can claim — a runner's self-reported `executionCost` is clamped to `perAgentBudget` at submission.
## Current Per-Agent Prices
Runners today use fixed per-type prices in their software (no on-chain price discovery yet — that will change). If your `perAgentBudget` is below the price for the agent type you're invoking, runners will see the `RequestCreated` event, decide it's not worth their compute, and skip the request — it will sit idle and time out.
| Agent Type | Per-Agent Price | Notes |
| ------------------------------------------- | --------------- | ---------------------------------------------------- |
| **JSON API Request** (`json-fetch`) | **0.03 SOMI** | Cheapest — single HTTP call, minimal compute |
| **LLM Inference** (`llm-inference`) | **0.07 SOMI** | GPU-backed model, dominant cost is token throughput |
| **LLM Parse Website** (`llm-parse-website`) | 프로젝트로 돌아가기