Documentation

Agents and tools

The tools the agent can call, and how to extend them.

The commerce agent answers questions about a founder’s own business. It cannot answer from general knowledge, and it cannot answer from nothing: every claim it makes has to be traceable to a tool result it was handed in the same run. This page documents the eight tools it can call, what each one actually reads, and the evidence each returns.

The agent runs behind POST /api/intent with surface set to home or supplier. Its engine identifier is commerce-reasoning-v1, which is also the identifier under which its runs are recorded and benchmarked — see the benchmark.

The loop

Every run begins with the business tool already executed and its result in evidence as e1, before the model is asked anything. The model then returns a decision object, validated against a strict schema:

{
  "kind": "tools" | "answer" | "route" | "clarify",
  "action": "…" | null,
  "message": "…",
  "tool_calls": [ { "name": "revenue", "product_id": null, "economics": null } ],
  "evidence_ids": ["e1"],
  "uncertainties": ["…"],
  "remember": []
}

A decision of kind tools requests at most four tool calls and may not also navigate; a decision of any other kind may not carry unexecuted tool calls. The same tool and product pair may not be requested twice in one decision. Each execution returns a result in a fixed envelope:

{
  "tool": "revenue",
  "status": "ok" | "unavailable",
  "observed_at": "2026-09-05T…Z",
  "source": "revenue",
  "data": { … }
}

A tool that fails does not abort the run. It returns status: "unavailable" with an explanation in data, so the agent knows the difference between a figure that is zero and a figure it could not read — which is exactly the distinction that makes an assistant trustworthy or not.

The tools

business

Establishes what this account actually owns. Returns the claimed product count and up to twenty products, each with its catalogue name and the build status read from its store manifest.

Reads: The claims table, the product catalogue and each store's manifest on disk.

Returns: product_count, products[] of { id, name, build_status, note }, products_truncated, and scope — 'products claimed by this authenticated account'.

Stated limitations:

  • A build status that cannot be read is reported as unknown, not as unpublished.
  • Each product carries the note that a built storefront does not establish domain publication, paid ads, inventory or revenue.
  • The list stops at twenty and sets products_truncated rather than silently trimming.

revenue

Summarises the confirmed payment ledger per store and per currency. The tool refuses the data outright if the account named in the response is not the authenticated one — an identity check on the way back, not only on the way out.

Reads: GET /api/revenue on the engine, then re-verifies the account identity in the response.

Returns: period, amount_unit, currencies keyed by ISO code, stores[] of { product_id, currency, revenue_minor, confirmed_sales, last_sale_at }, counting_basis, and an explicit profit: null and available_cash: null.

Stated limitations:

  • Currencies are kept separate; no exchange rate is applied.
  • Amounts are provider minor units — the tool states that not every currency has two decimals.
  • Revenue is not profit. Unpaid checkouts and customer names are excluded.
  • Payouts and available cash were not observed; recorded revenue must not be equated with cash available to spend.
  • Product costs, fulfilment, fees, refunds, chargebacks, taxes, overhead and ad spend are unreconciled, so net profit is unknown.
  • A duplicate store/currency summary raises rather than being added or discarded, because an ambiguous ledger must not silently produce a total.

store_funnel

Fourteen days of funnel counts per store: sessions, cart, checkout and tracked paid. Rates are computed only when the counts are internally consistent — sessions ≥ cart ≥ checkout ≥ paid — so an inconsistent tracking set produces nulls rather than a plausible-looking percentage.

Reads: GET /api/editor/metrics?product_id=…&days=14 for each store.

Returns: window_days, sessions, cart_sessions, checkout_sessions, tracked_paid_sessions, event_order_consistent, the three stage rates, and evidence_strength.

Stated limitations:

  • Under a hundred sessions the tool labels the sample small and says to investigate before claiming a cause; above it, the label is 'descriptive observation; not a causal experiment'.
  • Paid browser events are unverified; the payment ledger is the authority on actual sales.
  • A store whose metrics could not be fetched is returned with status 'unavailable' and the note that it must not be treated as zero.
  • A drop between stages does not prove why people left or establish an expected conversion rate.

supplier

Reports which supplier the account is connected to, and whether it is the default.

Reads: GET /api/supplier-connection.

Returns: status, kind, name, default, connected_at, and explicit nulls for verified_quotes, inventory and delivery_promise.

Stated limitations:

  • A selected supplier is not a verified quote, reserved stock, a completed sourcing action or a delivery guarantee. The tool returns that sentence with every result.

product_evidence

Returns up to eight catalogue evidence rows per product with their claim, source, URL and date, and computes each row's age in days at request time.

Reads: The product catalogue on disk.

Returns: products[] of { id, name, evidence[], opportunity_window, note }. Each evidence row carries age_days and a freshness label.

Stated limitations:

  • Freshness is derived, not asserted: over thirty days old is labelled 'needs rechecking', otherwise 'dated catalogue evidence; not reverified during this request'. An unparseable date is 'unknown'.
  • A URL is only passed through when it actually parses as http or https.
  • Catalogue claims are evidence to examine, not proof of present demand, profit or supplier availability.
  • A product not in the catalogue is returned as status 'not_in_catalog' rather than omitted.

outcomes

The last twenty outcomes the founder reported against their own claims: whether it sold, what was spent and what came in.

Reads: The claim_facts table, scoped to this account's email.

Returns: outcomes[] of { product_id, reported_sold, reported_spend_usd, reported_revenue_usd, reported_at }, plus basis and limitations.

Stated limitations:

  • The basis field says it plainly: founder-reported outcomes, not independently verified sales or ad spend.
  • The tool states these are not randomised experiments and are not evidence of a causal uplift; they are for revisiting an earlier hypothesis.

economics

Unit economics: gross margin, contribution before advertising and break-even acquisition cost. Inputs are validated against what the founder actually said, so the agent cannot calculate on numbers it invented.

Reads: Nothing. It calculates from inputs the founder supplied, after validating that they were quoted.

Returns: currency, gross_margin_percent, contribution_before_ads, break_even_cpa, missing_costs[].

Stated limitations:

  • Mixing tax-inclusive and tax-exclusive amounts fails with reason_code 'mixed_tax_basis' rather than producing a number. The agent then asks for one consistent basis instead of answering.
  • missing_costs names what is still needed rather than assuming a default for it.
  • Break-even acquisition cost is stated as a ceiling before fixed overhead and income tax, not as a spending recommendation.

supplier_order

Parses a quoted supplier order — minimum quantity, unit price, freight — out of what the founder wrote, and computes the order from it. It is the only tool that takes no product identifier and no economics input; its schema forbids both.

Reads: The founder's own messages in this conversation.

Returns: The parsed order and its derived totals, sourced from the founder's own words.

Stated limitations:

  • It works on quoted terms only. Terms it cannot parse are not guessed; the agent is required to clarify rather than invent a total.

What the agent is forbidden to say

Validation runs over the model’s answer before it reaches the founder, and rejects several classes of response outright. These are not style preferences; a rejected decision is re-attempted or replaced by the deterministic fallback.

  • An answer may not claim a mutation happened. Any assertion that the agent paused, launched, published, ordered, bought, paid, sent, contacted, changed, updated or deleted something is rejected, because no store, ad, payment or communication mutation runs in this loop at all.
  • A break-even, unit-economics, gross-margin or contribution question containing numbers may not be answered without an economics result. The agent must use the tool or ask for the missing inputs.
  • A quoted supplier order with a minimum, a freight figure and a per-unit price may not be totalled in prose. It requires a supplier_order result.
  • Navigation is refused unless the founder actually asked for it. A question phrased as a question is answered in place; only an explicit instruction — open, show me, take me to, go to, edit, add, find, manage, create, change, update, view, launch, publish, source — may return kind: "route".
  • Only evidence identifiers the agent was actually handed may be cited. An unknown identifier is rejected.

How tool diagnostics reach you

The response carries only the evidence the answer actually cited — the agent’s evidence list is filtered against the identifiers it referenced, so a tool that ran but did not inform the answer does not appear as if it had. Each surviving entry is reduced to id, source, status and observed_at: what was read, whether reading it worked, and when.

Two further fields tell you how much to trust the answer. uncertainties lists up to four things the agent could not establish. degraded is true when the model path failed entirely and the reply came from the deterministic fallback instead — which composes a sentence from whatever economics evidence exists, or, failing that, says it could not complete the analysis and that nothing in the store was changed. A degraded answer is still an honest one; it is simply not a reasoned one, and it says so.

run_id identifies the run and engine names the version that produced it, so an answer can be traced back to a recorded run rather than argued about.

Extending the tools

The tool set is a closed union in the schema, not a registry the model can add to. A new tool is a new member of TOOL_NAMES, a branch in the executor that returns the standard envelope, and — this is the part that matters — its own stated limitations attached to its own result. A tool that returns a number without saying what the number excludes is the failure mode this design exists to prevent.

Read the commerce-v1 methodology for how these behaviours are scored, or the API reference for the endpoints the tools read.

Next: API reference · Guides · Concepts