# Testril agent reference Implementation reference observed 2026-10-07. Deployed tools/list and server instructions take precedence. # Agent reference Source: https://testril.si/agents/ Testril is an MCP service for on-demand blockchain data. An agent discovers a metric template, binds its scope, checks coverage, materializes missing blocks, reads values, and verifies their sources. **A Universe of Blockchain Data.** This reference describes the public tool contract observed in the implementation on 2026-10-07. The deployed server's `tools/list`, tool schemas, instructions, chain inventory, and payment offers take precedence. The supplied captures identify `https://dev.testril.ai/mcp` with testnet payments; confirm the intended deployment with the operator before connecting. The marketing website is moving to `https://testril.si/`. The service remains at `https://dev.testril.ai/mcp` during this website migration; do not substitute the new website domain for the MCP endpoint. ## Release review / 2026-10-07 The local implementation was reviewed at revision `309a912ab11ebb5b1bdd114a7d43c251e532cc43`, with no uncommitted backend changes. The review covered public MCP registrations, argument types, instructions, handlers, relevant test sources, and the matching REST surface. A read-only MCP initialization and `tools/list` check at `https://dev.testril.ai/mcp` confirmed the 10 public tool names and documented input-field inventory; the server instructions were also reviewed. The endpoint reported version `0.6.0` and negotiated protocol `2025-06-18`. This verifies the development endpoint’s advertised interface, not a production designation or successful execution of every workflow. No metric work or payment was requested. Verification fingerprints are included in the [tool reference JSON](/agents/mcp-tools.json). This release adds the static `/demos/` catalog and navigation link, and fixes local preview port selection. Demo applications remain in the separate demos repository. Historical Workflow captures remain unchanged. The live server advertises the same public tool names and input fields as the reviewed implementation; this does not establish its exact deployed revision. The live instructions now advertise declared bindings and dependency-aware materialization, capabilities not established by the reviewed local revision. The affected guides describe those advertised capabilities; their execution was not tested during this read-only review. Another deployment difference remains: live schemas still contain unsigned formats such as `uint64`, while the local implementation sanitizes those formats and retains nonnegative integer constraints. Clients should use the deployed schemas and account for validator warnings until that implementation change is deployed. The blog recounts its author's October 1–5 runs; those historical observations are separate from this interface review. ## Start here 1. [Connect and discover the contract](/agents/connection/). 2. [Run the catalog-first quickstart](/agents/quickstart/). 3. [Look up the 10 public MCP tools](/agents/mcp-tools/). 4. [Handle outcomes and recovery](/agents/outcomes/). 5. [Understand payment and execution](/agents/payments/). 6. [Verify provenance](/agents/provenance/). ## Reference library - [Identities, parameters, and block windows](/agents/data-contract/). - [Coverage, asynchronous jobs, and retention](/agents/coverage/). - [Resolve a question with ask](/agents/authoring/). - [Reusable agent recipes](/agents/recipes/). - [Data liquidity, claim rights, and rewards](/agents/rewards/). ## Machine-readable entry points - [llms.txt](/llms.txt): compact navigation and integration constraints. - [llms-full.txt](/llms-full.txt): the complete reference as plain text. - [MCP tool reference JSON](/agents/mcp-tools.json): documented tools, input fields, and inspect subjects. This is an editorial reference, **not an exported runtime JSON Schema**. - [Existing product content JSON](/agents/site-content.json): homepage copy, visible FAQ, and blog summaries. This is editorial context; the live tool contract remains authoritative. - [Captured Workflow examples](/agents/workflow-examples.md) and [scene JSON](/agents/workflow-examples.json): recorded testnet request/response pairs, error cases, and settlement observations. These snapshots are not runtime schemas. - Every page has a Markdown counterpart: replace its trailing slash with `.md`. For example, `/agents/quickstart.md`. ## Agent operating rules Discover the catalog before binding. Never infer a chain, wallet, contract, time window, price, or missing parameter from an unrelated example. Read `outcome` before using any result. Respect the operator's metric choice and spending authorization. Keep payment receipts and the exact block windows used for verification. Never send a private key in tool arguments. The main website preserves its existing product narrative. This reference is the detailed integration surface; the homepage Workflow replays captured exchanges, including shortened identifiers and omitted signatures. Its archived messages are not the current deployed schema. --- # Connect an agent Source: https://testril.si/agents/connection/ ## Obtain the endpoint The supplied 2026-10-01 captures use `https://dev.testril.ai/mcp`, with Base Sepolia testnet USDC for payments. See the [captured Workflow examples](/agents/workflow-examples.md). This identifies the recorded service; confirm the intended deployment and access requirements with the operator before integration. `` in generic examples is a placeholder; do not infer an endpoint from the website domain. The implementation exposes an HTTP MCP route at `/mcp`. A self-hosted daemon's documented local default is `http://127.0.0.1:8000/mcp`. This loopback URL refers to your own machine, not the hosted Testril service. ## Discover the live contract Use your MCP client's connection lifecycle, then request `tools/list`. Save the advertised tool names, input schemas, output schemas, and server instructions. The implementation supports the legacy initialize lifecycle as well as a stateless protocol generation; let a compatible client negotiate its supported version instead of guessing transport metadata. The expected public tools are `health_check`, `inspect`, `bind`, `ask`, `materialize`, `pay_quote`, `read`, `provenance`, `stop`, and `claim_rewards`. `quote` is an internal retired entry point; request prices through `materialize` or `read`. Administrative operations are outside this public reference; do not infer access from the public MCP endpoint. If the deployed list differs, use the deployed schemas and report the mismatch to the operator. Do not call an unadvertised tool. The release review on 2026-10-07 connected to the recorded development endpoint, which reported version `0.6.0`, and confirmed these 10 tool names and their documented input-field inventory through `tools/list`. That read-only check does not establish mainnet payment support or end-to-end metric execution. Live instructions also advertise declared bindings and dependency-aware materialization beyond the reviewed local revision. Live schemas still contain unsigned formats such as `uint64` that the reviewed local implementation removes; account for schema-validator warnings until that change is deployed. See the [release review](/agents/). ## First calls ```json {"tool":"health_check","arguments":{}} ``` ```json {"tool":"inspect","arguments":{"subject":"chains"}} ``` ```json {"tool":"inspect","arguments":{"subject":"functions"}} ``` The `{tool, arguments}` objects throughout this reference are **documentation notation**, not raw MCP transport messages. Invoke the named tool with those arguments through your client. Chain support is deployment-specific: a marketing coverage number does not establish which RPC providers a particular server has configured. ## Optional REST discovery The daemon also serves a read-oriented `/v1` API and an OpenAPI document at `/v1/openapi.json`. Use the service's advertised origin and the `data_endpoints` returned by tools when present. Do not construct routes from the homepage demo. Authoring and other writes stay on MCP. ## Record an integration fingerprint Store the endpoint, negotiated protocol version, discovered tool schemas, configured chain IDs, and the time of discovery. Recheck after server upgrades. Keep authentication and RPC credentials outside logs and prompts; inspect and provenance data should be sufficient without exposing a provider's secret URL. --- # Catalog-first quickstart Source: https://testril.si/agents/quickstart/ ## 1. Discover the chain and template Call `inspect` with `subject: "chains"`, then `subject: "functions"`. Confirm the requested chain is configured. Select a template whose semantics match the operator's request. For example, **if advertised**: ```json {"tool":"inspect","arguments":{"subject":"function","function_slug":"erc20.token_balance"}} ``` Read its `params` and `row` schemas, descriptions, and defaults. The catalog's Function names are metric templates; they are not MCP tools. ## 2. Bind the scope The following addresses and chain are placeholders. Substitute operator-supplied values, and use exactly the parameter names advertised by the selected template. ```json {"tool":"bind","arguments":{"function_slug":"erc20.token_balance","chain_id":1,"params":{"tokens":[""],"owners":[""]}}} ``` Proceed only on `outcome: "success"`. Save the returned `bound_function_id`. Binding creates an identity and scope, not an indexed dataset. `created: false` does not prove the requested window is covered. ## 3. Check coverage and choose the window ```json {"tool":"inspect","arguments":{"subject":"bound_function","bound_function_id":""}} ``` Read `materialized`, `materializing`, and `active_jobs`. Choose a requested half-open block interval `[from_block, to_block)` using the correct chain's `head` and the operator's intended period. The head is not a finality guarantee. Example block numbers below illustrate syntax; they do not prescribe a useful query window. ## 4. Materialize missing blocks ```json {"tool":"materialize","arguments":{"bound_function_id":"","from_block":100,"to_block":110}} ``` If a quote is returned, inspect `outcome`, `lines`, `amount`, and `accepts`. Obtain or confirm spending authorization. Prepare the signed payload for an offered rail using the server's instructions. ```json {"tool":"pay_quote","arguments":{"quote_id":"","payload":""}} ``` `payload` is JSON **text inside a string**, not a private key. It can be omitted only on a mock settlement deployment. Save the payment ID and receipt. ```json {"tool":"materialize","arguments":{"payment_id":""}} ``` A successful accepted job can still be running. Inspect the returned `job.job_id` and the bound function's coverage before reading. Start paid execution within the quote's 120-second lifetime. ## 5. Read values ```json {"tool":"read","arguments":{"bound_function_id":"","from_block":100,"to_block":110}} ``` Reading has its own pay gate. If it returns `payment_required`, settle that read quote, then invoke `read` with **only** its returned `payment_id`. A successful read returns bare `values`, one row per block and key with all its attributes, typed by the Function’s `row` schema from inspect. Within fully covered blocks, absent rows follow the Function's declared emission rules; an unavailable window is not a zero balance. ## 6. Verify the source window Use the same bound identity and exact half-open window; there is no attribute selector: ```json {"tool":"provenance","arguments":{"bound_function_id":"","from_block":100,"to_block":110}} ``` Keep the result identity, exact window, values, source block hashes, and applicable digest together. See [provenance verification](/agents/provenance/) for limits and interpretation. --- # MCP tool reference Source: https://testril.si/agents/mcp-tools/ The deployed `tools/list` schemas are authoritative. Unknown fields are refused by the documented argument types. Optional does not mean meaningful in every combination: identity, window, and subject rules still apply. ## health_check Arguments: `{}`. Shallow process/connect diagnostics: configured chain IDs, version, catalog counts, and payment rail. It does not probe dependency health. A health response is not proof that a particular metric window is available. ## inspect Arguments: `subject` plus only that subject's relevant fields. Metadata and inventory, not metric values. Subject-specific stray fields are rejected. | Subject | Required extra fields | Optional extra fields | Purpose | | --- | --- | --- | --- | | `functions` | None | None | Catalog template slugs | | `function` | `function_slug` | None | Template description, params schema, row schema, and live declared bindings | | `bound_functions` | None | `function_slug` | Live identities; grouped inventory or filtered IDs | | `bound_function` | `bound_function_id` | None | Scope, materialized/materializing ranges, active jobs | | `jobs` | None | None | Job inventory | | `job` | `job_id` | None | State, paid window, block progress, errors | | `chains` | None | None | Configured chains and heads | | `chain` | `chain_id` | None | One chain's landmarks | | `channels` | `payer` | `network` | Buyer's open batch-settlement escrow channels | | `nft` | `wallet` | None | Wallet's claim-right accounting and purchases | | `pricing` | None | None | Published meter rates | `payer` and `wallet` are public `0x` addresses, not signing keys. The optional channel `network` is a CAIP-2 identifier such as `eip155:84532`; use a value actually offered by the service. ## bind Required: `function_slug` (string), `chain_id` (unsigned integer). Optional: `params` (object; defaults to empty). Template-specific requirements come from `inspect subject:function`. Returns a scope-bound identity, canonical debug name, template, chain, parameters, and `created`. Rebinding the same scope is idempotent. No block window belongs in this call; no metric values are returned. ## ask Required: `metric` (string, at most 500 characters), `chain_id` (unsigned integer). Optional: `function_slug` (string), `params` (object, defaults to empty), and paired `from_block`/`to_block` (unsigned integers). Selects an existing catalog Function (or a declared binding, per the live 0.6.0 instructions) and binds the question's scope. It authors nothing. An unsupported question returns `error` / `unsupported` with `needs`. A success carries the bound-function card, description, row schema, `created`, `follow`, and retention disclosure; it is not a metric value series. A `suggested` response asks for parameters or a window. Merge the operator's answer under the field named by `resume` and repeat. See [question resolution](/agents/authoring/). `bind_prompt` and the old `address`, `context`, `shape`, and `explain` inputs are no longer advertised. ## materialize Unpaid arguments: `bound_function_id`; optional paired `from_block`/`to_block`; optional `extra_cache_days` (unsigned integer, default 0). For an explicit workflow, state both block ends. Omitted or zero extra cache days adds no extra retention line; the implementation includes a 24-hour land stamp. Computes and caches unpaid holes, excluding existing coverage and open paid jobs. An unpaid request freezes work and returns `payment_required` when chargeable holes remain. A paid execution uses **only `payment_id`**; restated work fields do not replace the frozen scope. Paid execution returns `success`, `result: "materializing"`, and a `job` handle. Inspect its `job_id` until done or failed. Coverage and progress remain on `inspect`. For a Function that carries state across blocks, the window is quoted, billed, and walked from its contract's creation block. A window ending at or before creation is refused. A future tail waits until the chain head reaches those blocks; a stateful job can remain queued until its preceding coverage is complete. ## pay_quote Required: `quote_id` (string). Optional: `payload` (string). Settles a quote frozen by `materialize` or `read`, returning `payment_id` and settlement information. Real x402 settlement expects signed v2 payment JSON serialized as text. Omit `payload` only on mock. Never send a private key or a caller-invented amount. The payment ID is usable only within that quote's lifetime. See [payments](/agents/payments/). ## read Either `payment_id` alone, or `bound_function_id` with optional `key` (object mapping key-part names to strings) and paired `from_block`/`to_block` (unsigned integers). Omit both block ends to request the latest **materialized** block, which need not be the chain head. Every returned row includes all its attributes. `key` filters a leading run of the key parts listed after `block` in the Function's row schema; it does not change the bound scope. For example, use `{"token":""}` only when `token` is the first advertised key part. Unknown names, skipped leading parts, and malformed typed values are refused before quoting. `attributes` and `entity` are retired inputs. Returns bare `values`, typed by the Function’s `row` schema from inspect, never a coverage inventory and never new indexing. There is no `cursor` or `limit` argument. Split deliberate block windows when needed. A not-fully-covered request returns `range_unavailable`; it does not silently answer only the covered prefix. ## provenance Required in a usable request: `bound_function_id` (string), `from_block` and `to_block` (unsigned integers). Expand the exact contributing source blocks for the bound function and window. No `attribute` argument is accepted. Compare the recomputed digest with the corresponding compact `ChainRange.digest` or REST row's `provenance.digest` where available. The response is bounded to at most 10,000 contributing blocks; narrow larger requests. An empty `blocks` list is an empty enumeration; an absent evidence field must not be treated as an empty result. ## stop Required: `bound_function_id`, `wallet`, and `quote_id` (strings). The wallet must be the current NFT owner of that quote's claim-right, checked at the chain tip when a collection is configured. Cancels the named in-flight materialize quote and attempts to refund unspent blocks. Delivered rows and other jobs remain. Inspect returned `jobs`, refunds, errors, and any `unstopped` rows; success alone does not establish that every refund settled. ## claim_rewards Required: `wallet` (string). Claims that wallet's eligible unclaimed USDC atoms, using current claim-right ownership. The minimum is 10,000 atoms ($0.01); amounts below it return claimed `"0"` and `reason: "below_minimum"` without a transfer. Inspect its `nft` card before and after claiming. This operation pays existing eligible rewards; it does not promise earnings or create a claim-right simply from a data read. See [tool reference JSON](/agents/mcp-tools.json) for the same input-field inventory in a machine-readable form. --- # Identities and data contracts Source: https://testril.si/agents/data-contract/ ## Three names with different jobs A **Function** is a reusable catalog template, identified by `function_slug` such as `erc20.holder_balance`. A **Bound Function** is that computation with its chain and scope parameters filled, identified by a content-addressed `bound_function_id` beginning `sha256:`. An **MCP tool** is a verb such as `bind`, `materialize`, or `read`. Pass the returned bound-function ID unchanged. A readable `name`, metric phrasing, or function slug cannot stand in for it. Two phrasings may resolve to one identity; one template bound to two contracts can produce two different identities. ## Scope is structural Obtain template parameter names from `inspect subject:function`. Pass addresses in typed fields or `params`, not hidden in explanatory prose. Set `chain_id` explicitly. There is no default chain. Use the exact JSON field names from the deployed schema: unknown fields are rejected. The reviewed live 0.6.0 instructions advertise Function dependencies in `params`: a dependency can be supplied as nested scope parameters or as a declared binding name discovered through `inspect subject:function`. Use only the structure and names advertised for that Function. A declared binding name is not a substitute for the returned `bound_function_id` in data tools. `bind` takes scope, not a block range. `ask` returns a resolution, not a metric value series. ## Every explicit block window is half-open `[from_block, to_block)` includes the start and excludes the end. `[100, 110)` contains ten blocks; block 110 is not included. A single block `n` is `[n, n+1)`. State both ends or omit both where the tool permits omission. One end alone is invalid. Never treat block numbers as timestamps or add ranges from different chains. A materialize window may extend beyond the current chain head; that tail waits for those blocks to exist. A read or provenance request requires full coverage of its stated window. For a wall-clock request, first discover chain landmarks. The chain card includes `blocks_per_day` and either `head` or `unavailable`. A block-rate estimate is not an exact timestamp-to-block conversion; define the actual block boundaries before spending. The head does not establish finality. ## Schema once, values many times A successful `read` returns bare `values`, one object per block and row key, with every attribute of that row. Obtain their types and key-part names from the Function’s `row` schema through inspect. Preserve declared types; do not round large integer strings through floating-point arithmetic. A sparse row, an empty result, and missing coverage are distinct states. Within fully materialized coverage, a block with no row means the Function emitted nothing there; consult its description before interpreting that absence. Inspect the actual row schema and prefer exact integer fields for accounting when supplied. Human-readable floating-point units can lose precision above 2^53. The optional `read.key` is an object of string values for a leading run of row-key parts. It filters stored rows without rebinding scope. `read.attributes`, `read.entity`, and `provenance.attribute` are no longer accepted. Provenance uses the bound identity and exact window. ## Keep useful agent state Persist the endpoint, chain, function slug, bound-function ID, scope parameters, requested window, selected measures, quote/payment/job IDs, settlement receipts, and source verification evidence. Store secrets separately. This small state record lets another agent resume a workflow without reconstructing intent from prose. --- # Outcomes and recovery Source: https://testril.si/agents/outcomes/ Always inspect the tool result's top-level `outcome` before reading its payload. A valid MCP response is not necessarily completed work. | Outcome | Interpretation | Next action | | --- | --- | --- | | `success` | The verb succeeded or work was accepted | Interpret that verb's fields; poll an accepted async job | | `payment_required` | A priced offer has been frozen | Confirm scope and spending authority; pay, then execute using only payment ID | | `suggested` | A choice is required; no bound result yet | Show options and basis; repeat with the selected field named by `resume` | | `incomplete` | An answer is pending or incomplete where the verb supports it | Keep the diagnostic; do not treat it as completed metric data | | `error` | Product refusal or failure | Inspect `kind`, `message`, optional `not` and `remedy`; repair the specific issue | Product errors normally arrive with MCP `isError: false`. Transport failures are different: inspect MCP error/connection status and do not assume an interrupted charge or accepted job was rolled back. ## range_unavailable `read` does not index and refuses a window that is not fully covered. Its `materialized` and `materializing` geometry is clipped to the requested range. If missing blocks are already in flight, inspect their jobs and wait. Otherwise materialize the remaining holes. Use the full bound-function inspect card for the complete range ledger. A provenance coverage error reports its available range lists according to that tool's contract. Do not blindly reuse the read-specific clipping assumption for other tools. ## invalid_params Check the deployed schema, identity type, exact field spelling, required subject fields, paired block ends, address shape, and declared measure names. Remove retired `cursor` and `limit`. Do not add generic fields such as `query` or `sql` to tools that do not advertise them. `read` accepts row-key filtering through `key`; its old `attributes` and `entity` inputs are retired. `provenance` no longer accepts `attribute`. Current `read` outcomes are `payment_required`, `success`, and `error`. ## suggested The server supplies the answer's target field through `resume`. Merge the chosen answer into the original call instead of replacing the whole request. Do not automatically choose a different metric, window, or economic tradeoff. The operator's prior explicit choice can supply the answer; otherwise request it. ## Interrupted paid work Keep the frozen quote and payment IDs. Inspect coverage and job state before retrying or paying again. Quote expiry does not cancel a background job already started. Repeating an unpaid request does not refresh its expiry. Inspect job errors and refund records; see [payments](/agents/payments/). ## Final answer quality Report only successful data, with chain, scope, exact block window, measure meanings, and verification evidence. If coverage or assembly failed, report that limitation explicitly. Do not invent a value from the metric's name, marketing copy, or the animated homepage demo. --- # Payment and execution Source: https://testril.si/agents/payments/ ## Freeze, authorize, settle, execute Both `materialize` and `read` use the same handshake: 1. Request the unpaid work with its scope and window. 2. On `payment_required`, read the quote ID, itemized `lines`, server-set `amount`, and `accepts`. 3. Confirm that spending is authorized for this scope and amount. Present unresolved choices to the operator. Mock payments follow the same consent workflow. 4. Settle with `pay_quote` using the frozen quote ID and a payload for an advertised rail. 5. Save the payment ID and receipt. Invoke the original verb with **only `payment_id`**. A quote is not the result; a settlement receipt is not the result; an accepted job is not necessarily completed data. ## The quote clock Every quote expires 120 seconds after freeze, whether paid or unpaid. Pay **and start execution** within that interval. Repeating a pending request does not extend the clock. After expiry, request a new quote; inspect prior work and settlements first to avoid duplicate spending. An already-started paid background job continues after ticket expiry. ## Amounts and rails are discovered Use `inspect subject:pricing` for published meters and the actual quote for this operation's price. Do not hardcode rates, payment networks, contracts, or pay-to addresses from an example. Chain of computation and payment network are separate choices. The real-payment payload is a signed x402 v2 object serialized as JSON text in `pay_quote.payload`. It is not a base64 blob, a private key, or a custom amount field. Follow the server's payment instructions and selected `accepts` row. Omit the payload only when the deployment explicitly uses mock settlement. ## Batch settlement For an offered batch rail, inspect `subject: "channels"` with the payer address and optional advertised CAIP-2 network. Reuse eligible open channel state according to the server's instructions. A first deposit and a later voucher are different actions. Obtain authorization for the full deposit commitment, not just the small individual query price. Channel fields such as balances and cumulative amounts are decimal strings. Keep precise integer arithmetic. Withdrawal is a separate on-chain escrow workflow; Testril has no MCP withdrawal tool. Use the delay and contract details advertised by the live service. ## Materialize pricing and retries Materialize buys unpaid holes, excluding durable coverage and open paid ranges. `extra_cache_days > 0` adds retention beyond the included stamp. Read has its own query pricing; cached computation does not imply free reads. For a Function that carries state across blocks, materialization is quoted, billed, and walked from its contract's creation block. Read the actual frozen window and line items; a later requested start does not remove that prerequisite work. Paid materialization starts an asynchronous job. Inspect the returned job until done or failed, and reconcile its error and refund records before retrying. Do not blanket-repay every error or assume a settlement receipt means the data is complete. ## Agent expense record Log quote scope, lines, amount, rail, time frozen, settlement state/reference, payment ID, job ID, and final coverage. Log signed payloads only under the operator's security policy; avoid leaking keys or reusable authorizations into ordinary diagnostic output. --- # Coverage, jobs, and retention Source: https://testril.si/agents/coverage/ ## Inspect before filling `inspect subject:bound_function` is the coverage ledger. Its `materialized` ranges describe durable data; `materializing` describes paid in-flight work; `active_jobs` identifies open jobs. `created`, a metric name, or a previous successful bind does not establish coverage. Materialize only missing blocks. With `[100, 115)` already cached, a request for `[97, 118)` has six uncovered blocks: `[97, 100)` and `[115, 118)`. Existing paid in-flight ranges also count toward the exclusion. Read the returned quote rather than pricing the full requested window yourself. ## Follow asynchronous work A paid materialize response returns `result: "materializing"` and a `job` handle. Poll `inspect subject:job` with its `job_id`. The job card describes its own paid half-open window, `blocks_done`, `blocks_total`, and diagnostic fields where present. States include `queued`, `running`, `partial`, `waiting_blocks`, `done`, and `failed`. A partial job can make a durable prefix visible; reading the full original window still requires full coverage. A future tail waits for the chain head to reach it instead of being fabricated. A Function with memory across blocks is billed and walked from its contract's creation block, even when the requested start is later. Its job can stay queued until every preceding block from creation is covered. Check the quote's actual window and lines before authorizing payment. The reviewed live 0.6.0 instructions advertise dependency-aware indexing: a Function that reads another Function cannot claim blocks its dependency has not materialized. Materialization quotes needed blocks across both and walks the dependency first; dynamically discovered dependency keys are walked from contract creation. Inspect the actual quote and jobs before paying or reading. This behavior was advertised, not executed, in the release review. Use bounded polling and a backoff appropriate to the chain. Do not repeatedly materialize a window while an active paid job already covers its holes. If a process or client restarts, inspect the durable state before resuming. ## Latest materialized is a precise request A `read` with neither block end asks for the latest materialized block. It does not promise latest chain head. For a time-sensitive decision, compare its resolved range with the chain's `head` and materialize a deliberate updated window when authorized. ## Retention and cache titles The implementation includes a 24-hour land stamp for new paid coverage; additional days are specified with `extra_cache_days` when freezing materialize work. Actual range titles and expiry are inspectable on the bound-function card. A titled range can carry `cache_expiry_time` as a Unix-seconds string; untitled ranges may omit it. Expiry belongs to each range, not one global wallet timer. Adjacent ranges may have separate titles and expiry records. Do not assume all historic values remain available forever. ## Stop a specific paid window ```json {"tool":"stop","arguments":{"bound_function_id":"","wallet":"","quote_id":""}} ``` This targets a named in-flight materialize quote. The wallet must currently hold that quote's claim-right; the original payer may no longer be its owner. When a collection is configured, ownership is checked at the chain tip. Examine each returned job, `unspent_blocks`, refund, error, and any `unstopped` row. A refund failure can leave an indicated window active and paid; reconcile against inspect and accounting before retrying. Delivered rows remain available. --- # Every Action Leaves a Trace Source: https://testril.si/agents/provenance/ **Every Action Leaves a Trace.** For an agent, provenance is a usable evidence record, not an adjective attached to an answer. ## Ask for the exact identity and window After a successful read, keep its bound-function ID, returned field names, and resolved half-open block interval. Invoke `provenance` with the same identity and window. It takes no `attribute` or `key` selector; the evidence describes the bound function's window. A different window is a different evidence request. ```json {"tool":"provenance","arguments":{"bound_function_id":"","from_block":100,"to_block":110}} ``` Provenance is a separate tool call. Do not claim that every read automatically contains the expanded source-block list. ## What to preserve The implementation expands contributing blocks into an ordered list with block numbers and hashes, a block count, and a recomputed digest when the contributing list is non-empty. An empty `blocks` list means enumeration found nothing; missing `blocks` means evidence is not enumerated. Do not invent absent digests. When a result supplies a compact digest-bound `ChainRange`, compare the expanded `digest` with that corresponding `ChainRange.digest`. A REST data row carries `provenance.digest` for its own block; expand `[block, block+1)` to compare it. Persist the identity, chain, measure, window, compact record where supplied, expanded source list, digest, and comparison outcome. Preserve the algorithm and encoding supplied by the contract if independently recomputing; an invented concatenation of hashes is not a valid verification procedure. ## Canonical source-digest encoding The observed implementation sorts contributing pairs by ascending block number, then hashes UTF-8 lines in this exact form: ```text {chain_id}:{block_number}:{lowercase_0x_block_hash}\n ``` The result is `sha256:` followed by lowercase SHA-256 hex. The newline is one actual LF byte per pair, including the final pair. Chain ID and block number are decimal integers. Use the exact contributing set returned by the contract; do not add every block in the enclosing span. A verifier can reproduce that digest locally: ```python import hashlib def source_digest(chain_id, blocks): ordered = sorted(blocks, key=lambda block: block["number"]) encoded = "".join( f"{chain_id}:{block['number']}:{block['block_hash'].lower()}\n" for block in ordered ).encode("utf-8") return "sha256:" + hashlib.sha256(encoded).hexdigest() ``` This verifies encoding consistency for the supplied list. For independent source verification, obtain the corresponding canonical block hashes independently as well. **Two range conventions differ:** tool requests use the exclusive `to_block`, while a compact `ChainRange` citation's `from` and `to` are the **inclusive minimum and maximum contributing block numbers**. Its `block_count` is the actual contributing count and may be smaller than the enclosing span because of gaps. Do not pass the citation's inclusive end directly into a half-open tool request. ## Source evidence and computation evidence Matching source hashes establishes the requested source evidence's consistency with its digest. It does not, by itself, prove the metric definition matches the operator's intent or independently replay the computation. Distinguish source verification, definition review, and independent recomputation in the final answer. ## Missing coverage and empty blocks A block with no matching event can be legitimate evidence. A missing block is unavailable coverage. Do not conflate them or collapse both into a zero value. Provenance requires its stated window to be covered; inspect the reported geometry and complete missing work before asserting verification. ## Bounded expansion Expansion is limited to 10,000 contributing blocks per response. For larger requests, use smaller meaningful windows and retain the boundaries of each verification record. Do not claim that independently expanded subwindows match one whole-window digest unless the contract supplies a valid composition rule. ## Credentials stay out of evidence Agent-facing source descriptions should identify providers or chains without exposing credential-bearing RPC URLs. Block numbers, hashes, function identity, and reproducible definitions are more useful evidence than a secret endpoint string. --- # Resolve a question with ask Source: https://testril.si/agents/authoring/ ## Two routes to a bound function Inspect the catalog with `inspect subject:functions`. For a known Function, inspect its `params` and `row` schemas and call `bind`. For a question in words, `ask` selects a catalog Function and binds its scope. The reviewed live 0.6.0 instructions also advertise selection of a declared binding: a named scope already supplied by the catalog. Inspect the Function to discover its declared bindings; do not invent addresses to resolve a brand name. It authors nothing. A question that no catalog Function answers returns `error` / `unsupported`, naming what it `needs`. `bind_prompt` is no longer advertised. The older authoring fields `address`, `context`, `shape`, and `explain` are not accepted by `ask`. This page retains its historical URL so existing links still work. ## A deliberate ask ```json {"tool":"ask","arguments":{"metric":"","chain_id":1,"function_slug":"","params":{"":""},"from_block":100,"to_block":110}} ``` Only `metric` and `chain_id` are required by the wire schema. `function_slug`, `params`, and paired block ends are optional inputs. Replace placeholders with the selected Function's advertised scope fields. The question is limited to 500 characters. An address not supplied by the operator must not be invented. ## A suggestion is a question A `suggested` outcome writes nothing. Its `kind` is `params` or `window`; show the returned `card`, chain facts, and missing parameters where provided. Merge the operator's answer into the original request under the field named by `resume`, then repeat. Never choose a period or economic tradeoff merely because it looks reasonable. Supply both block ends together. ## Examine the resolution On success, preserve the returned card's bound-function identity and scope, `description`, `row`, `created`, `follow`, and retention disclosure. `name` is a debug label, not an identity accepted by data tools. `follow.already_indexed` and an optional checkpoint do not prove full coverage of your requested window. Inspect coverage, materialize missing blocks, settle authorized quotes, inspect jobs, read values, and request provenance. Resolution does not deliver a paid read or bypass the usual execution path. Unsupported questions require an appropriate catalog capability; repeating the request cannot author one. --- # Agent recipes Source: https://testril.si/agents/recipes/ These recipes describe orchestration. Template availability, parameters, chains, prices, and ranges must be discovered from the live service. All example IDs and addresses are placeholders. ## Read a token balance 1. Discover `erc20.token_balance` if it is present; inspect its bind parameters. A single token and wallet use `tokens` and `owners` arrays. 2. Bind the explicit token, wallet, and configured chain. 3. Inspect coverage and the chain head, then select one block as `[n, n+1)`. Confirm finality separately if the application requires it. 4. Materialize a missing block under authorized spending. 5. Read the balance through its own pay gate. 6. Report the declared value type and block; request provenance for the same bound identity and window. A balance read at the latest materialized block is convenient, but a trading decision may require newer coverage. Compare landmarks before describing it as current. ## Measure transfers over a period Discover a transfer-volume template, such as `erc20.token_volume` if advertised. Inspect its exact scope holes and definition. Establish block boundaries for the requested period, bind the contract and chain, then use inspect/materialize/read. Do not substitute transferred amount for trade volume, wallet net flow, or unique senders without a matching definition. If the template does not express the requested quantity, use [question resolution](/agents/authoring/) to check catalog support; unsupported requests cannot create a new Function. ## Resolve ownership If `erc721.owner` is advertised, inspect its token/contract parameters before binding. Read the explicit block window and preserve the chain and token scope. Ownership at a historical block is not ownership at the current head. ## Produce an auditable report For each measure, retain its bound identity, operator's intended definition, exact window, returned field type and values, payment receipt, and provenance record. State which source digests were compared. Keep unsupported interpretations out of the numerical result. ## Resume after interruption Recover stored quote, payment, and job IDs. Inspect the bound-function geometry and any known job. If already running, wait; if done, read. If the quote expired before execution, request a new quote and reconcile the previous settlement before paying. Treat timeout as unknown state until inspected. ## Read only the needed rows Use `read.key` to restrict a leading run of key parts advertised by the Function's row schema. For example, `{"token":""}` applies when `token` is the first key part. Every matching row includes all its attributes. Narrow an explicit window when useful. `attributes`, `entity`, `cursor`, and `limit` are retired inputs. Do not assume a key filter changes the price; the quote decides. ## Reuse another agent's work Share the endpoint and bound-function identity with its chain/scope record. Inspect coverage independently and read the required window. Shared materialization reduces repeated computation; reads still use the current read pay gate. Keep any claim-right ownership separate from the identity of an agent that happens to query the dataset. --- # Data liquidity and rewards Source: https://testril.si/agents/rewards/ Testril's data-liquidity model connects paid materialization, titled coverage, subsequent paid reads, and wallet accounting. For agents, the useful questions are: what was bought, what is still covered, what was earned, and what can be claimed? ## Inspect a wallet's accounting ```json {"tool":"inspect","arguments":{"subject":"nft","wallet":""}} ``` The claim-right card includes `purchases` and lifetime totals such as `paid`, `refunded`, `cost`, `earned`, `claimed`, `unclaimed`, and `pnl`. Monetary atom values are decimal strings; preserve exact integer arithmetic. A purchase can appear at payment before data delivery. Inspect the relevant job and coverage separately. Each purchase records its quote ID, bound-function ID, original requested window, and accounting. The original window may include blocks already covered even though only unpaid holes were bought. Do not equate requested block count with charged block count. Purchase earnings and read counts follow the blocks actually delivered. When historical attribution cannot be reconstructed, a purchase omits `earned`, `pnl`, and `reads`. Sum known purchase earnings plus wallet `earned_unattributed` to reconcile lifetime `earned`; missing attribution does not forfeit unclaimed rewards. ## Data availability is a different ledger Expiry and materialized geometry live on `inspect subject:bound_function`. Wallet accounting is not a guarantee that all purchased blocks are currently available. An empty wallet card can have zero totals and no token ID; the token ID is omitted until minted. When an NFT collection is configured, the card follows the current owner at the chain tip. An ownership lookup failure produces `unavailable`, not fabricated zero totals. The original payer may no longer hold the claim-right. ## Claim existing rewards ```json {"tool":"claim_rewards","arguments":{"wallet":""}} ``` Inspect unclaimed amounts first. Keep the actual claim response and re-inspect accounting afterward. The implementation handles eligible USDC atoms; it does not promise that a newly bought window will earn enough to cover its cost. The minimum claim is 10,000 atoms ($0.01). Below that minimum, the response reports claimed `"0"`, `reason: "below_minimum"`, and no transfer. Claims follow current claim-right ownership; an unreadable ownership record is a refusal, not proof that no rewards exist. ## Paid reads and shared value A paid read of titled coverage can contribute to holder accounting according to the implementation's pricing and overlap rules. A quote alone is not earned revenue. Coverage overlap, retention, prices, and the actual completed paid read matter. Use the current card and settlement records as evidence instead of projecting a yield from a marketing tagline.