DEV Community

Cover image for Price the chain before hop three bills you.
Alex
Alex

Posted on Originally published at forge.modelmarket.dev

Price the chain before hop three bills you.

Price the chain before hop three bills you.

One capability has one price on the row. A chain does not. An earlier hop can succeed and settle, and the next hop can be the one that stops the walk. Anything after that was never called. The bill is the surprise. The price of the whole chain is knowable before you press Run.

HEPHAESTUS is the forge for that.

Landing: forge.modelmarket.dev. The page: modelmarket.dev/studio. No account. The function that costs a graph is estimateBlueprint in hephaestus/src/estimate.ts. No dependencies. No DOM.


Read the header before Run

The studio does not open on an empty canvas. It discovers a chain from the live catalogue: a hop whose declared output fills the next hop's required fields, wired by those field names. If nothing pairs, it opens on one priced hop instead of three red errors.

Start and Result are markers on the canvas. They are not hops. They are not billed. Only capability nodes travel to the executor.

The header is the estimate: a dollar sum, a hop count, and a latency written with ≥. Steps run one after another, so that latency is a floor. It is the number a parallel executor could not beat, and it is not a forecast. A capability with no declared latency counts as zero on that path and is named separately, so the floor is not inflated by a guess.

Money is summed in integer micro-dollars, one million per dollar. The catalogue's real prices are fractions of a cent. Adding them as floats drifts in the digits the total is made of. An unpriced capability is counted and named. Its cost is unknown. It is not zero, and it is left out of the sum.

The price used for a hop is the routed price when the hub routes to a peer, otherwise the price the provider asks. Estimating only the base price under-quotes every federated hop by the routing fee.

An estimate is not a quote. It is the signed manifest at the moment you read it. A provider can reprice before the run.

The page refuses a graph the executor cannot run. At most 16 capabilities. A hop may wait on several parents, and exactly one of those connections may carry data. A second data parent used to mean "whichever hop the sort finished last," which could be drawn, priced, and fed from the wrong upstream.


What one finished chain recorded

This bill is public. Two hops, both successful, both payer: "trial":

magic-ai-factory.com/ai-market/pipelines/tr_6dcceda217c3

The steps on that object:

  • gaia.weather.read@v1 — price_usd 0.001
  • gaia.verify@v1 — price_usd 0.002

total_usd is 0.003. blame is null, because nothing failed. Those figures are what that trace recorded. They are not today's price list. Open the studio and read the header.

payer on a hop is local, trial, channel, or unpaid. A trial run is not signed evidence that the factory bought the hop. The hub meters a renewing allowance against the visitor id the browser sends. The executor does not attach its own credential to an unauthenticated Run.

While this was written, a fresh visitor's live quota was 5 trials in an hourly window. That meter is a trial, not an account. A hop that composes its answer with a paid model is outside it and comes back asking for payment. With no allowance and no channel of your own, the hop fails with a reason. The estimate still says what it would have cost.


When a hop breaks, the bill names that hop

at_fault, not_at_fault, not_executed, total_usd.

The executor walks the graph in dependency order. A hop that does not return success stops the walk. Hops that already returned success stay on the bill, and their prices are what total_usd sums. Hops that had not started are listed under not_executed. They were not called.

The blame block is hop-level:

{
  "policy": "hop-level",
  "at_fault": {"id": "…", "capability_id": "…", "status_code": 500},
  "not_at_fault": ["…"],
  "not_executed": ["…"]
}
Enter fullscreen mode Exit fullscreen mode

A public example of that block is a single hop that returned HTTP 502 and no price, so the total stayed 0:

magic-ai-factory.com/ai-market/pipelines/tr_2419b38af676

at_fault is gaia.weather.read@v1. not_at_fault and not_executed are empty, because nothing succeeded before it and nothing was waiting after it. When a later hop is the one that breaks, the earlier ids are the ones cleared, and the rest are named as not executed.

One refusal happens before a provider is called. If a ${hop.field} reference cannot be filled from what the earlier hop actually returned, that step is recorded with status 0 and price 0. The literal ${…} is not posted to a paid provider.


The signature covers the bill, not the wrapper

The executor signs the bill with Ed25519. The signed bytes are the UTF-8 of one JSON object: keys sorted, a comma between members, a colon after a key, no spaces. The signature field is not in that object. The signature value is base64. The function is sign_payload in signing.py.

GET /ai-market/pipelines/{trace_id} returns that object and then adds protocol_version. That field is not inside the signed object. Drop it, and drop signature, before you treat the remainder as what was signed.

GET /ai-market/pipelines is a different document. It is a projection: cost, hops, status, blame. It drops channel_id and each hop's receipt_nonce. A nonce is the lookup key for a public receipt, and the list is not a feed of those keys. The projection says where the signed bill lives. It is not the signed artefact.

The factory's signing key is signer_public_key on magic-ai-factory.com/.well-known/ai-market.json. Pin the key you verify against. A key carried inside a document is a claim.

A signed bill proves what this executor recorded. It does not prove the hop's answer was true. That is a different capability, and you can put one in the graph. The two-hop bill above is a read followed by a verify.

The page is a convenience. It can put the same request JSON on the clipboard. The hub serves the studio because the hub's CORS is fail-closed: a page on another origin cannot read the catalogue at all.

Top comments (0)