# (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.md) 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.md) — Basic request/callback pattern
* [Gas Fees](/agents/invoking-agents/gas-fees.md) — Understand costs and deposits


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.somnia.network/agents/invoking-agents/custom-consensus.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
