Agents
Resolve
Resolve turns an intent into an explained plan. It never spends money and never signs anything.
const plan = await tollex.resolve({
need: "token balance of a wallet",
input: { address: wallet.address },
constraints: { maxCost: usdg("0.001"), receiptRequired: true },
});
plan.selected?.toolId; // the capability that would be bought
plan.plan.reasons; // why it won, in plain language
plan.plan.rejectedBy; // e.g. { PRICE_LIMIT: 9, NOT_RELEVANT: 21 }Eligibility comes first
Every capability is checked against the hard constraints before anything is ranked. A candidate that fails any check is rejected and keeps a reason code. Price and relevance cannot rescue it. Requirements that depend on measurement fail when the measurement is missing: a provider nobody has timed cannot satisfy a latency requirement.
| Code | Why the candidate was rejected |
|---|---|
NETWORK_INCOMPATIBLE | No supported network is in the allowed list. |
ASSET_INCOMPATIBLE | The required asset is not accepted on an allowed network. |
SCHEME_INCOMPATIBLE | The capability's payment scheme is not one the agent can use. |
PRICE_LIMIT | The route's maximum exceeds maxCost (or the live price does, checked by the client). |
PROVIDER_BLOCKED / PROVIDER_NOT_ALLOWED | Provider rules. |
TOOL_NOT_ALLOWED / CATEGORY_NOT_ALLOWED | Tool and category rules. |
SIDE_EFFECTS | The capability writes more than the agent accepts. |
RECEIPT_REQUIRED | A receipt is required and the capability returns none. |
EVIDENCE_REQUIRED | Required execution evidence is missing. |
HEALTH_REQUIREMENT | Failing health check, or success rate below minimum or unmeasured. |
LATENCY_REQUIREMENT | Observed p95 above the limit, or no observations. |
STALE_DISCOVERY | Terms and health not observed recently enough. |
NOT_RELEVANT | Nothing in the capability matches the need. |
INPUT_INVALID | The supplied input fails the capability's schema. |
POLICY_REJECTED | Client side: the signing policy refuses the live payment terms. |
NOT_PAYABLE | Client side: the capability did not return valid x402 terms. |
plan.rejectedBy counts rejected candidates by their first failing check. Economic checks run before relevance, so a rejection names its economic reason when there is one.
Ranking eligible candidates
Eligible candidates are scored on six components, each between 0 and 1, and every component is returned with the plan:
score = 0.40 relevance + 0.20 price + 0.15 reliability
+ 0.10 latency + 0.10 evidence + 0.05 freshness
relevance overlap of the need with name, tags, category, description
price cheapest eligible maximum scores 1
reliability observed success rate after 5+ calls, otherwise 0.5
latency 1 - p95 / (maxLatencyMs or 5000 ms), unobserved 0.5
evidence share of evidence kinds the capability attaches
freshness 1 - age / (requiredFreshnessMs or 1 h), unknown 0.5
ties: lower maximum price, then lower observed p95, then idThe result is deterministic: the same catalogue, observations and clock give the same plan. Resolve does not need a language model. If one is used to reorder candidates, it only ever sees the eligible list, and its answer is filtered back to that list.
This is capability routing: choosing the best eligible capability under the constraints you declared. It is not best execution in the securities sense and carries no such guarantee.
What the client adds
Catalogue prices are not trusted for the final decision. The TypeScript client fetches the live x402 terms for the leading candidates and runs them through your signing policy. The selected route is the first candidate that passes both. Its verdicts are in checks, with POLICY_REJECTED, PRICE_LIMIT or NOT_PAYABLE where a candidate failed.
What comes back
resolutionId: send it with the purchase and the operator’s console links the plan to the payment.plan.selected: the capability, its route (scheme, maximum amount, network, asset) and any preconditions such as a Permit2 allowance.plan.reasons: plain-language reasons built from the reported fields.plan.evaluations: eligible candidates in rank order, then rejected ones with their codes.quote: withquote: trueand an input, a hash-bound quote for the selected route.