Base Contracts
Required for AmetystCreditToken. This address is baked into the
constructor and becomes the only account allowed to mint. The prefilled value is
just a default — overwrite it whenever the minter key has been rotated.
It must be the address that actually calls mint, which is the
ZeroDev kernel address — not the EOA. Both admin-api and credential-api load
MINTER_PRIVATE_KEY into a ZeroDev kernel (entryPoint 0.7, KERNEL_V3_3,
ECDSA validator, default index), so the on-chain caller is that kernel's smart-account
address. Paste the EOA here by mistake and every mint reverts with
“ACT: caller is not the minter”. There is no way to fix that in place: you would have
to redeploy the token, swap base_contracts_references, and migrate every
merchant's payment preference again.
Required for FacilitatorsRegistry. This address becomes the
registry's admin, and setFacilitatorFor /
removeFacilitatorFor — the two calls this app makes on behalf of a
merchant that cannot sign for itself — are onlyAdmin. So it must be the
address admin-app signs with, or the Set Facilitator button reverts
NotAdmin on every merchant.
admin is set in the CONSTRUCTOR. Afterwards it can only
be changed by the current admin calling proposeAdmin followed by
acceptAdmin from the new one. Get it wrong and there is no in-place fix:
you have to redeploy the registry, swap base_contracts_references, and
re-do every merchant's facilitator assignment.
Heads-up on this app's signer. Every on-chain action here builds a
fresh, randomly generated EOA and wraps it in a new ZeroDev kernel
(src/chain/kernel.ts), so admin-app has no stable signing address today.
Until that changes, put an address you control and can sign with here — not an
address this app produced.
Test Accounts
Policies
Deploy Policy Contract
Rules
Policies
Facilitator Registry
Credits & Design Partners
Every Allowed email gets access + free credits and is marked a design partner by default, so Allowed ⊆ Design partners: an allowed partner is always a design partner, but a design partner is not necessarily allowed.
Allowed
Access allowlist
Allowed emails
| Status | Design partner | Actions | |
|---|---|---|---|
| No allowed emails yet | |||
Waitlist
| Design partner | Actions | |
|---|---|---|
| No waitlisted emails | ||
Design partners
Partners
Design Partners
| Partner ID | Credits | ||
|---|---|---|---|
| No design partners yet | |||
Task Proposals
Partner
Proposals history
No proposals have been made to this partner yet.
| Date | Kind | Task | What | By | Status | Decided at |
|---|
Tasks
| Slug | Description | Files | |
|---|---|---|---|
| Load a partner's tasks to begin | |||
Author Proposal
Memory
Records, docs and blobs written into the partner's workspace when the proposal is accepted, in row order. Profile is the namespace: shared (all profiles) or one of the partner's seats; custom… takes an apikey:<id> seat the list did not show.
Docs are materialized into every fire of their seat (shared docs for every seat) as <key>.md; the memory manifest above decides whether a key is shared by the workspace or owned per member. Records are read/appended with the task memory tools; blobs are the archive.
Summary
Stateful Integrations (Connections)
Registering a connector writes four artifacts in one transaction: the capability row, the connector spec, the composition-instructions card and one skill row per operation. A connector with 0 skills or no instructions is invisible to getAllowlist however correct its spec is — both are shown in the table below. A connector with no source descriptor (none in the Source column) is a third way to be invisible: cli 0.2.101+ drops a sourceless entry from the user's catalog, so the provider looks healthy here and does not exist for the client. To edit a registered connector, click its Edit button: the whole row loads into the register form below, and Register re-registers it in place (the endpoint is a whole-row upsert). A row registered before the card brief was stored comes back with the five brief boxes blank — restate them once.
Registered
| Slug | Name | Base URL | Skills | Instructions | Source | Edit |
|---|---|---|---|---|---|---|
| Refresh to load registered connectors | ||||||
Register Connector
Intake
Request shape
Source
Card brief
Preflight
This connector's canned answer to “what if I am not connected?” — stored once beside the connector, read by the cli every time a task starts, so five tasks that need this provider stop carrying five drifting copies of it. Leave all four blank for a row with no answer yet: that is a working row, the cli falls back to the task's own prose. Re-registering restates it — this endpoint is a whole-row upsert, so Edit loads the stored answer back into these boxes and a submit that blanked them would clear it.
Transactions
One partner's spend: settled rows, pending consumes and projected credit mints, in one union. Amounts are base units — the decimal shown is exact (BigInt), and the raw integer is on the cell's tooltip. The provider filter takes a bucket key (provider:<name>, bucket:legacy, bucket:top-up), which is why its options come from the totals grid rather than from typed-in names.
Partner
Transactions
| Date (UTC) | Provider | Type | Product | Amount | Status | Source |
|---|---|---|---|---|---|---|
| Pick a partner and load their transactions | ||||||
Totals — day × provider
| Day (UTC) | Provider | Amount | Rows |
|---|---|---|---|
| Pick a partner and load their transactions | |||
Sign-up bonuses
Credits minted once, when a workspace finalizes: one amount for every new workspace, and a different one for a workspace whose profile is flagged as a design partner. Saving changes nothing retroactively — it arms every future finalize, which is why the save is confirmed on prod even though it writes no chain.
The bonuses
Whole credits only, zero or more — a decimal or a negative is refused. 0 turns a bonus off and is a supported setting, not a mistake. A workspace flagged as a design partner receives the design partner amount instead of the sign-up amount, never both.
Pro plan
0 = no credit included. Whole euros only, from 0 to 1000. The credit is granted to each Pro seat every time its subscription is paid and adds to the balance. Saving changes nothing already granted — it applies to future payments, which is why the save is confirmed on prod.
Billing alerts
What Stripe reported about a payment that needs an operator: a dispute, a fraud warning, a reversal that could not take all the credit back, a payment that could not be credited. A dispute or a fraud warning also freezes the workspace for new payments; the credit it already holds stays spendable. Acknowledge what you have looked at; lift a freeze once the dispute is acknowledged.
Euro-dollar rate
Press Refresh to load.
Tool prices are provider prices in US dollars read as euro, so the margin on tools is the rate minus 1. It has to cover card fees and conversion: below the threshold tools are sold at a loss, and the line above turns into a warning.
Open alerts
Press Refresh to load.
| When | Kind | Workspace | Stripe object | Details |
|---|
Frozen workspaces
Every workspace that may not start a new payment now, whether or not it still has an open alert. A freeze set by a fraud warning is lifted only here.
Press Refresh to load.
| Since | Reason | Workspace |
|---|
Plans
What each workspace plan grants. One saved cell changes what every workspace on that plan may do, within about a minute — which is why the save is confirmed on prod even though it writes no chain.
Entitlements
Press Refresh to load.
| Feature | Kind | Pay per use | Pro | Enterprise |
|---|
Change one cell
A flag is true or false. A capacity or a window is a whole number, 0 or more, or the word unlimited — an empty box is refused, never read as unlimited. A floor is a whole number, 1 or more. A choice is standard or custom. Click a cell to fill the form with it.
AI prices
What each AI plan costs per month and what each model costs per million tokens, in EUR. The services read these lists as they are, so a saved row is the price from then on — which is why a save is confirmed on prod.
Plans, per month
Press Reload to load.
| Tool | Plan | Label | EUR / month | |
|---|---|---|---|---|
Models, per million tokens
Press Reload to load.
Held changes
The last check would have moved these prices by a lot, so it left them as they are. Apply writes the proposed prices; Dismiss hides the row until the next check.
| Model | Input | Output | Cache read | Cache write | Input move |
|---|
Press Reload to load.
| Model | Input | Output | Cache read | Cache write | |
|---|---|---|---|---|---|
Amounts are in EUR, 0 or more, with at most 6 decimals (200, 3.75). An empty box is refused, never read as 0; a model with no cache pricing takes 0. Adding a tool + plan or a model that already exists replaces its row.
Customer plans
The plan each workspace is on. Pick a plan on a row and press its Save to move that one workspace; the row then shows what the server stored. The expiry is never changed here. Under each workspace, one row per member with their own plan and where it came from (subscription / operator / —); Make Pro or Make Pay per use moves that one member. A member whose Pro is paid by the workspace subscription cannot be changed here.
Workspaces
Press Refresh to load.
| Company | Plan | Valid until | Change plan |
|---|
Endpoint Pricing (Merchant Economics)
Per endpoint: the price we charge, the cost we pay upstream, the markup between them, and what the last 30 days of calls were worth.
Every amount is base units (×106). The decimal shown is exact (BigInt); the raw integer is on the cell's tooltip. Editing the markup shows the price that would be written — ceil(cost × (10000 + bps) / 10000) — before anything is saved or applied.
A dash is not a zero. — means the value has never been recorded (or could not be read); 0.000000 and 0% mean it was recorded and it is zero. Most of the pass-through fleet really does run at exactly 0% markup — that is the finding this table exists to make visible, and it must not look like missing data.
Recording and applying are two acts. Save writes the cost and markup and moves no price. Apply derives the price from the saved row and writes products.price and the bazaar row in one transaction, with an audit entry. Verify asks the live staging merchant what it actually quotes. Writes are staging-only server-side.
Provider
The picker counts each provider's endpoints running at 0% markup, code-pinned, in bazaar-drift and uncosted, so the providers worth opening are visible before opening one. Each line is named; its commerceInfoId is on the line's tooltip. Typing above narrows the list by substring across the name, the slug and the id — so a pasted id lands too. Filtering never changes what is selected: the provider currently loaded stays in the list even when it does not match, because a picker that silently re-pointed at another merchant would put one merchant's prices under another's name.
Suggested allowance
A buyer's deferred allowance is drained one endpoint price at a time, so it has to be a multiple of the LCM of every price this merchant charges — or the buyer ends with a remainder no call can spend. The markup you just saved may have moved that LCM. Below is the server's re-aligned proposal; Apply on any row writes the price and this allowance in one transaction. You may overwrite the number — a value that does not align is flagged in red and still allowed, because only you know whether it is meant.
Endpoints
| Endpoint | Current price | Upstream cost | Markup % | New price | 30d calls | 30d revenue | Est. margin | Status | Actions | |
|---|---|---|---|---|---|---|---|---|---|---|
| Pick a provider and load its endpoints | ||||||||||
Est. margin is (price − recorded cost) × calls. Estimated is load-bearing: no measured per-call upstream cost is persisted anywhere yet, so for a pass-through endpoint this is 0 because cost was recorded as equal to price — which is the real finding, not a missing number. It is — when no cost is recorded.
Price changes
| Applied | Endpoint | Old price | New price | Markup | By | Verification | Actions |
|---|---|---|---|---|---|---|---|
| Pick a provider and load its endpoints | |||||||
A revert does not rewrite the row it undoes: it writes a new row pointing at the original and stamps the original's revertedAt. Verification has three states — verified, checked and failed, and never checked. A failed check stamps its result and leaves the timestamp empty, so a change whose live 402 quoted the wrong amount never reads as merely un-examined.
Merchant Integration (x402)
How to onboard a stateless x402 merchant — a pay-per-call provider with no user account or stored state on our side. This is the manual runbook; every step below happens in the tools it names, not in this console.
- Scaffold the proxy. Add the provider to
merchant-fixturesfollowing the existing per-provider pattern (one proxy service wrapping the upstream API, prices declared per endpoint). - Deploy it on Railway. The FIRST deploy of a new merchant is manual (create the service, set its env); after that, every merge to main redeploys it automatically.
- Route it. Add the merchant's route in
merchants-routerso its slug resolves to the deployed proxy. - Register it in the supplier app. Sign up / sign in on the supplier app with the merchant's own account (name, email, password), then register the commerce info and every endpoint. Composition instructions MUST list the endpoints under an
## Endpointsheading — atoms outside it are invisible togetAllowlist. - Products & pricing. Declare each product's price and (if deferred) the suggested allowance — base units, ×106.
- Whitelist on-chain — LAST. The
payTowhitelist entry is always the final step, only after everything above is verified. Never whitelist first. - Smoke test. From a staging MCP session:
getAllowlistmust surface the provider, and one realspendper payment shape (exact and deferred) must succeed end to end.
Full runbook with copy-paste values: eng-onboard-merchant (operator skill) and the loop-merchants queue for autonomous intake.
Ametyst Agent — getAllowlist
When a customer's agent calls getAllowlist, the Ametyst Agent chooses the skill with Jev from this catalog: every skill of the platform with a short line, an excerpt and a domain. Sync catalog reads every skill of the platform and rewrites only the new and changed ones (a few cents each); unchanged skills cost nothing. Reclassify all asks Jev the domain of every skill again, for after the domains changed: it only proposes moves, checks them on the test set, and writes nothing until you apply. Staging only.
How it works, and what you can do by hand
1. ANSWER (under a second, at every request)
agent: "what do I use for X?"
|
v
Jev picks the DOMAIN --> Jev picks the SKILL inside it
|
| at the same time: "is X the same kind of request as an
| EXAMPLE approved for this member / workspace / everybody?"
v
yes (85% or more) -> the example's skill, alone
no -> Jev's own choice
|
v
answer to the agent + one row in "Latest decisions"
2. WATCH (by itself, always)
the agent uses a skill --> written next to the decision ("What the agent used next"):
followed / another one proposed / another app
3. LEARN (once a day, or "Run review now")
decisions that ended with another app
| Jev groups the ones that asked the same kind of thing
v
3 cases in 2 sessions? --> a PROPOSAL: "requests like this -> this skill"
| for one member / one workspace / everybody
| tried on the test set: dropped if it answers worse
v
YOU: Approve / Refuse --> it becomes an EXAMPLE --> back to 1
| You want to | Where | What it does |
|---|---|---|
| Correct an answer now, without waiting for it to repeat | Latest decisions → Right skill was another, then Examples → Add example | From the next request of that kind, Jev answers the skill you wrote. Choose who it is for: one member, one workspace, everybody. |
| Take a correction back | Examples → Deactivate · Proposals → Undo | The example stops being used at once. Jev answers as before. |
| Decide on what the review found | Proposals → Approve / Refuse | Approve creates the example. Before approving you can widen who it is for and rewrite the request in general words. |
| Look for repeated mistakes now | Proposals → Run review now | Same as the daily review. It proposes only a mistake seen 3 times in 2 sessions. |
| Protect answers that are right | Latest decisions → Use as test | Adds the request and its skill to the test set. Every proposal and every reclassify is checked against it. |
| Bring in new or changed skills | Catalog → Sync catalog | Reads the platform, rewrites only what is new or changed. |
| Fix skills filed in the wrong domain | Catalog → Reclassify all, then Proposed moves → Apply selected / Undo | Only after the domains changed. Nothing is written until you apply. An example does not fix a wrong domain: this does. |
| Fix one skill filed in the wrong domain | Domains → Show skills or Search skills, then Move | Moves that skill at once and shows how the test set answers before and after. Moving it to other takes it out of every answer. |
| Add a kind of request the catalog does not have | Domains → Add domain | Creates the domain, empty. It starts to count when a skill is moved into it, by hand or by a reclassify. |
| Reword what a domain means | Domains → Edit | Jev reads the new text from the next request. The test set is shown before and after, and Put back restores the old text. |
| Remove a domain nobody uses | Domains → Delete | Only a domain with no skill in it. other is never deleted. |
Limits: it learns only from workspaces whose sessions reach the gateway and that share more than the minimal depth. The daily review runs only when the gateway has REVIEW_HOUR_UTC set. Nothing is ever applied without your approval.
Catalog
Press Refresh to load.
Proposed moves
Press Refresh to load.
| Apply | From → to | Skills | Which ones (hover for all) |
|---|
Proposals
Press Refresh to load.
| For | Request (editable before approving) | Right skill | Evidence | Tests | Status |
|---|
Examples
Press Refresh to load.
| For | Request | Right skill | Origin | Used |
|---|
Test set
Press Refresh to load.
| Intent | Right skill | Added |
|---|
Latest decisions
Press Refresh to load.
| When | Intent | Verdict | Domain | Confidence | Calls | ms | What the agent used next |
|---|
Domains
A domain is what the caller wants, never the vendor. Jev picks a domain first, then a skill inside it. Changes here are live at once. A new domain is not offered until a skill is in it: move one in below, or run Reclassify all. A skill moved to other is never offered.
| Domain | Skills | What it means |
|---|
Press Show skills on a domain, or search, to move a skill by hand.
| Skill | Line | Domain | Move to |
|---|
Ametyst Agent — Session advice
While a member's agent works, the Ametyst Agent looks at the conversation every 20 events and on each prompt, and says one line when it sees a known waste (retry too fast, same search rephrased, paid after a free search, …) or when a prompt needs an app and getAllowlist was not called. The line reaches the agent at its next action as Ametyst Agent: …. Whether it was followed is read from what the conversation did next: Followed (the waste stopped, or getAllowlist was called), Ignored (it went on), No later activity (nothing more within 30 minutes), Not delivered (the cli never took it). Staging only.
How it works, and what you can do by hand
1. LOOK (while a member's agent works; the line arrives about 20 seconds after the fact)
what the agent does (prompts, tool calls, results) --> stored by the gateway
|
| every 20 events, or 5 events older than 2 minutes, and on each prompt
v
Jev reads the last 40 events against the PATTERNS of that member:
everybody's + its workspace's + its own, minus the ones MUTED for it
|
+-- a pattern at 75% or more (50% while the agent is stuck or wasting) --> ONE LINE
+-- a prompt that needs an app, and no getAllowlist in the next 5 events --> the REMINDER
+-- anything else --> silent
|
v
"Ametyst Agent: ..." at the agent's next action + one row in "Lines said"
2. WATCH (by itself, from what the conversation does next)
the waste stopped, or getAllowlist was called --> Followed
it went on --> Ignored
nothing more within 30 minutes --> No later activity
the cli never took the line --> Not delivered
3 Ignored in a row, same member, same pattern --> MUTED for that member, by itself
(a Followed in between resets the count)
3. LEARN (once a night, or "Run review now")
a pattern said 10 times or more in 30 days, followed less than 1 time in 5
--> a PROPOSAL to retire it
a waste seen 3 times in 2 conversations, and no pattern for it
| a model writes the pattern from those sessions (no e-mail, URL, path or long number)
| Jev tests it: recognised on its own cases, no test window changing verdict
v
--> a PROPOSAL to add it, for that workspace (or everybody when 2 workspaces saw it)
YOU: Approve / Refuse / Undo --> it becomes a PATTERN (or one is switched off) --> back to 1
| You want to | Where | What it does |
|---|---|---|
| See what was said to whom, and whether it worked | Lines said | One row per line, newest first, with its outcome. Hover the pattern for Jev's numbers. |
| Teach it a waste it does not know, now | Patterns → Add pattern | For one workspace, one member or everybody. Said from the next look. The same id as a general pattern rewords it for that level. |
| Stop a pattern for everybody | Patterns → Deactivate | No longer offered to Jev; it stays listed. |
| Stop a line for one member or one workspace | Muted → Mute | That pattern (or reminder) is no longer said to them. Everybody else still hears it. |
| Give a muted line back | Muted → Unmute | Said again from the next look. An automatic mute then counts again from zero. |
| Decide on what the night found | Proposals → Approve / Refuse | Approve of an add creates the pattern (you may first choose Everybody and edit the line); of a retire, switches the pattern off. |
| Take a decision back | Proposals → Undo | The pattern an approval created is switched off; the one it retired comes back. |
| Look for new waste now | Proposals → Run review now | Same as the nightly review. The line above the table says what the run found and why something was dropped. |
Limits: it looks only at workspaces that share the full session with the gateway. A line can use only what the window holds: a pattern whose text needs something the code cannot read from it stays unsaid. The nightly review runs only when the gateway has REFLEX_REVIEW_HOUR_UTC set; without a writer key it proposes only retirements. Nothing here changes a threshold: the outcomes do.
Lines said
Loading…
| When | Workspace | Member | Pattern | Line said | Outcome |
|---|
Patterns
What the Ametyst Agent can recognise and say, at three levels: everybody (the catalog), one workspace, one member. The most specific wins: a pattern added for a workspace with the id of a general one rewords it there; a new id adds one. A pattern switched off is no longer offered and stays listed.
Loading…
| For | Pattern | Line said | Offered | Origin | Last 30 days |
|---|
Muted
Who no longer hears which pattern. Automatic: the same line ignored three times in a row by one member (a followed line in between resets the count). By hand: added here for a member or a whole workspace; reminder is the getAllowlist reminder. Unmute brings the line back; an automatic mute then counts again from zero.
Loading…
| Who | Pattern | How | Since | State |
|---|
Proposals
Once a night the Ametyst Agent reviews what it said and what it saw. Retire: a pattern said at least 10 times in 30 days and followed fewer than one time in five. Add: a waste it saw three times in two conversations with no pattern for it; a model writes the pattern from those sessions and it is tested before it reaches this table (recognised on its own cases, no test window changing verdict). Nothing is applied until you approve it; an add for one workspace may be approved for everybody, and its line edited first. Undo reverses an approval.
Loading…
| Kind | For | Pattern | Signature | Line | Cases | Tests | State |
|---|
Treasury
The treasury account's money, for invoicing: current balance, every on-chain transfer in and out, and who the counterparty was. Every amount is base units (×106), shown as an exact decimal — the CSV carries both forms. A counterparty label is operator-curated: merchants' payTo addresses are not recorded off-chain anywhere, so name them here (click Label on a row) and the name sticks for every future transfer.
Press Refresh to load.
Older transfers exist that are not shown — the page budget ran out. Narrow the period, or export what is loaded and note the cut.
| Time | Dir | Counterparty | Label | Amount | Tx |
|---|