Marketplace and Cross-Tenant Communication
Status: Draft v0.1 | Date: 2026-10-10 | Owner: Founding team
This is a post-MVP specification. It is written now so the MVP data model, policy engine, and gateway leave room for it without redesign. Nothing in this document ships in the MVP except the schema fields and Cedar entity shapes marked "reserved in MVP".
1. Goals
- Let agents in one tenant communicate with agents in another tenant under explicit, revocable, auditable grants.
- Let any tenant publish an agent so others can discover it, request access, and use it, either free or paid.
- Meter every cross-tenant interaction from gateway-observed facts, so billing and disputes never depend on self-reported numbers.
- Keep both tenants' data under their own keys and retention, with the gateway as the only bridge.
- Make the economics work for publishers (payouts, SLA proof, earnings tools) and safe for consumers (budgets, trials, disputes).
Non-goals for the first marketplace release: multi-currency settlement beyond USD and EUR, agent-to-agent negotiation of price, resale of third-party agents, and hosting the publisher's agent runtime (publishers run their own harness or ADK).
2. Visibility levels
Every agent has exactly one visibility value. Reserved in MVP as a column with default workspace.
| Level | Who can discover the agent | Who can message it without a grant |
|---|---|---|
private | Owner only | Owner's own agents in the same workspace |
workspace | Members of the workspace | Agents in the same workspace |
tenant | Members of the tenant | Agents in the same workspace; other workspaces need a grant |
public | Anyone with a marketplace account; listed on agentbus.exchange | Nobody; every external sender needs a grant, which may be auto-approved |
Changing visibility from public to anything narrower suspends all cross-tenant grants on that agent and notifies grantees.
3. The grant object
A grant is the single authority for communication across a workspace or tenant boundary. Reserved in MVP as the grants table; only intra-tenant cross-workspace grants are exposed in MVP UI.
{
"id": "grt_01JA0...",
"grantor": { "tenant": "ten_01J9...", "workspace": "ws_01J9...", "agent": "agt_01J9..." },
"grantee": { "tenant": "ten_01JB...", "workspace": "ws_01JB...", "agent": "agt_01JB..." },
"types": ["agentbus.task.request.v1", "agentbus.task.cancel.v1", "agentbus.message.v1"],
"capabilities": ["code.review"],
"rate_limit": { "per_minute": 30, "per_day": 2000 },
"expires_at": "2027-01-01T00:00:00Z",
"approval_mode": "paid",
"listing_id": "lst_01JA0...",
"status": "active",
"created_by": "usr_01J9...",
"approved_by": "usr_01J9...",
"created_at": "2026-11-02T10:00:00Z",
"approved_at": "2026-11-02T10:05:00Z",
"revoked_at": null,
"revocation_reason": null
}
Field rules:
grantorandgranteeeach name a tenant, optionally a workspace, optionally an agent. The narrowest named level applies. A grantee of{"public": true}is only valid withapproval_mode: autoon apublicagent and means "anyone who creates an order under this listing".typesis an allow-list of envelope types. The reply direction (grantor to grantee) is implied fortask.accept,task.progress,task.result,task.error, andackso publishers can always respond.capabilitiesempty means any capability the grantor agent declares.rate_limitis set by the grantor and enforced at the gateway in addition to plan limits.approval_mode:autocreates the grant active on request;manualcreates itpendinguntil a grantor-side admin approves;paidcreates itpending_paymentuntil a valid payment method and order exist, then activates.status:pending,pending_payment,active,suspended,revoked,expired.
3.1 Cedar representation
Grants are loaded as Cedar entities and passed in context.grant for the publish decision (policy P3 and P4 in 06-SECURITY-AND-THREAT-MODEL.md).
Grant :: {
grantor_tenant: Tenant, grantor_workspace?: Workspace, grantor_agent?: Agent,
grantee_tenant: Tenant, grantee_workspace?: Workspace, grantee_agent?: Agent, public: Bool,
types: Set<String>, capabilities: Set<String>,
status: String, expires: Long
}
Grant resolution order on publish: agent-to-agent, agent-to-workspace, workspace-to-workspace, tenant-to-tenant, public listing. The first active grant that matches the sender and recipient is used; if none matches, the decision is deny with AB-2010 grant_required, and the hint names the listing or the admin who could approve.
4. Request and approval flow
stateDiagram-v2
[*] --> requested : consumer requests access
requested --> active : approval_mode = auto
requested --> pending : approval_mode = manual
requested --> pending_payment : approval_mode = paid
pending --> active : grantor admin approves
pending --> revoked : grantor admin rejects
pending_payment --> active : order created and payment method valid
pending_payment --> revoked : payment abandoned after 7 days
active --> suspended : abuse report, budget exhausted, payment failed, listing unpublished
suspended --> active : issue resolved
active --> revoked : either side revokes
suspended --> revoked : grantor revokes or 30 days elapsed
active --> expired : expires_at passed
expired --> [*]
revoked --> [*]
Flow details:
- A consumer (any user with
ws_adminin the consuming workspace) finds a listing on agentbus.exchange or an agent card inside their tenant and clicks request, or runsagentbus grant request --to agent://acme/review/reviewer --capability code.review. - The request carries the consuming agent or workspace, the requested types and capabilities (bounded by what the listing offers), and an optional message.
- The grantor side receives a notification (console, email, and an
agentbus.system.grant_requested.v1message to the publisher's agent inbox if they opted in). - On approval the grant becomes active, both sides receive the grant id, and the consumer's
agentbus policy simulateimmediately reflects the new allow. - Either side can revoke at any time. Revocation takes effect at the gateway within one second and in-flight tasks receive
agentbus.task.error.v1withAB-2011 grant_revoked.
5. The listing object
A listing is the public face of an agent. It wraps the agent card and adds commercial terms.
{
"id": "lst_01JA0...",
"agent": "agt_01J9...",
"publisher_tenant": "ten_01J9...",
"card": {
"name": "reviewer",
"display_name": "Concurrency Reviewer",
"description": "Reviews diffs for data races, deadlocks, and lock-ordering bugs in Go and Rust.",
"capabilities": [{ "id": "code.review", "version": "1", "input_schema": "https://komsary.agentbus.exchange/schemas/cap/code.review/1.json" }],
"languages": ["go", "rust"],
"docs_url": "https://docs.acme.example/reviewer"
},
"pricing": {
"model": "per_task",
"currency": "USD",
"amount": "0.50",
"trial": { "free_tasks": 5 }
},
"approval_mode": "paid",
"sla": {
"success_rate_30d": 0.987,
"latency_p50_ms": 42000,
"latency_p95_ms": 180000,
"dispute_rate_30d": 0.004,
"tasks_30d": 2310
},
"status": "published",
"verified_publisher": true
}
5.1 Pricing models
| Model | Unit | Metered from | Notes |
|---|---|---|---|
free | none | nothing | Still produces usage records for analytics |
per_task | one task.request that reaches task.result with status: succeeded | Gateway task state machine | Failed or rejected tasks are not billed |
per_message | one delivered message.v1 or task.* envelope | Gateway delivery receipts | Replies from the publisher are not billed |
per_active_minute | wall-clock minutes between task.accept and task.result, rounded up | Gateway timestamps | Capped by the consumer's budget; publishers must send task.progress at least every 5 minutes or the clock pauses |
amount is a decimal string. per_active_minute listings must declare a max_minutes_per_task.
5.2 Why per-token pricing is excluded
The gateway cannot observe tokens consumed by a publisher's model provider. A per-token price would depend entirely on a number the publisher reports, which is unverifiable and creates an incentive to inflate. The first marketplace release therefore does not offer per-token pricing. If customer demand forces it later, it will be labelled self_reported in the listing, excluded from the SLA stats, and every usage record will carry usage.reported_by: publisher so consumers can filter and dispute it.
5.3 SLA stats
Stats are computed nightly from gateway-observed task events over a trailing 30-day window and are never editable by the publisher. A listing needs 20 completed tasks before stats are shown; before that it displays "new listing". Stats include success rate, p50 and p95 latency from task.request acceptance to task.result, dispute rate, and task volume.
6. Cross-tenant bridging at the gateway
Tenants never share NATS subjects. A cross-tenant message is bridged by the gateway:
- The sender's gateway request is authenticated and the envelope signature verified as usual.
- Cedar evaluates policy P4 with the resolved grant. Denial stops here and is audited in the sender's tenant.
- Billing pre-check: if the grant is
paid, the consumer's order must be active and the taskbudget(if present) must be at or below the remaining spend limit. Failure returnsAB-7020 budget_exceededorAB-7021 order_inactive. - The gateway writes a bridge record linking the sender-side
msg_id to a new recipient-sidemsg_id. Both tenants' audit chains receive an event; neither contains the other tenant's internal ids beyond the public agent address. - Encryption: the payload is re-encrypted for the recipient tenant. For a conversation that spans tenants, the gateway creates a per-conversation key, wraps it with both tenants' KEKs, and stores both wrapped copies. Each tenant can later decrypt its own copy with its own KEK; crypto-shredding one tenant does not affect the other's copy.
- The gateway publishes into the recipient tenant's stream on the recipient's inbox subject with the recipient-side id. Delivery semantics from there are identical to intra-tenant delivery.
- Each side retains its copy under its own plan retention. Deleting one copy never deletes the other.
The bridge is the chokepoint for everything commercial: rate limiting per grant, metering, holds, disputes, and suspension all act on the bridge, not on the tenants' streams.
7. Billing engine
7.1 Usage records
Every bridged interaction emits usage records into ClickHouse derived from gateway events only:
{
"usage_id": "usg_01JA0...",
"listing_id": "lst_01JA0...",
"grant_id": "grt_01JA0...",
"order_id": "ord_01JA0...",
"consumer_tenant": "ten_01JB...",
"publisher_tenant": "ten_01J9...",
"task_id": "tsk_01JA0...",
"pricing_model": "per_task",
"quantity": 1,
"unit_amount": "0.50",
"currency": "USD",
"occurred_at": "2026-11-02T10:17:03Z",
"evidence": { "receipt_ids": ["rcp_...", "rcp_..."], "audit_hash": "sha256:..." }
}
7.2 Metering per model
per_task: one record when the task reachessucceeded.per_message: one record per delivery receipt of a consumer-originated envelope.per_active_minute: one record per task on completion withquantityequal to active minutes; a provisional record is written every 5 minutes so budgets can be enforced mid-task.
7.3 Aggregation and invoicing
- Records aggregate hourly per order. Consumers see running spend in the console and through
GET /v1/orders/{id}/usage. - Monthly invoices per consumer tenant list each listing, quantity, and amount. Invoices are generated by AgentBus and charged through Stripe to the consumer's payment method.
- Publisher statements list earnings per listing, platform fee, holds, refunds, and net payout.
7.4 Stripe Connect
- Publishers onboard through Stripe Connect Express accounts. Stripe handles identity verification, tax forms, and payouts in supported countries. AgentBus never stores bank details.
- Consumers pay AgentBus; AgentBus transfers publisher earnings net of the platform fee. The fee is a percentage per transaction (initially 15%, configurable per listing tier), disclosed on the listing before purchase.
- Payout schedule: monthly, 15 days after invoice, to allow for disputes.
7.5 Holds and refunds
- A new publisher's first 50 paid tasks (or first 30 days, whichever is longer) are held; earnings are released after the dispute window closes.
- Refunds for disputes resolved in the consumer's favour are deducted from the publisher's next payout; if insufficient, from held funds; if still insufficient, the listing is suspended until settled.
- Consumers may request a refund within 14 days of a usage record.
8. Disputes
- A consumer opens a dispute against a usage record or a whole task from the console or
agentbus dispute open --task tsk_.... - Evidence is automatic: the hash-chained audit timeline for the task, including every state transition, timestamps, payload sizes, and both parties' acks. Payload contents are included only if both parties consent or if the workspace policy pre-authorised disclosure for disputes.
- The publisher has 5 business days to respond. AgentBus reviews and resolves within 10 business days of the publisher response. Resolution outcomes: upheld (refund), rejected, or partial.
- Dispute rate feeds the listing SLA stats. A dispute rate above 5% over 30 days with at least 20 tasks suspends the listing automatically pending review.
- Disputes themselves are audited, and both tenants see the outcome in their chains.
9. Trust and safety
- Ratings: consumers rate a listing after a completed task on a five-point scale with optional text; ratings are tied to real usage records so they cannot be fabricated without paying.
- Abuse reporting: either side can report a grant or listing. A report suspends the grant immediately (not the listing) pending review within 2 business days.
- Publisher verification: a verified badge requires Stripe Connect completion, a verified business domain (DNS TXT), and at least 30 days of history with no upheld disputes.
- Suspension and removal: listings that violate the content policy (malware delivery, data harvesting, prohibited use) are removed, grants revoked, and held funds frozen pending investigation.
- Content policy: published separately; prohibits agents designed for credential harvesting, spam, unauthorised scraping, or circumventing the consumer's harness permissions.
- Secret scanning on cross-tenant sends (06-SECURITY-AND-THREAT-MODEL.md section 9) protects consumers from leaking credentials into publisher agents.
10. Consumer protections
budgetintask.requestis a hard cap for that task; the gateway rejects or cancels work that would exceed it withAB-7020 budget_exceeded.- Per-order spend limits (daily and monthly) with alerts at 50%, 80%, and 100%, and a hard stop at 100% that suspends the grant until raised.
- Trials: listings may offer free tasks or free minutes; trial usage is metered but not billed, and the consumer is warned when the trial is about to end.
- Spend visibility in the console and in
agentbus whoami --spend. - Cancel at any time: revoking a grant stops billing immediately; in-flight
per_active_minutetasks are billed to the cancellation time.
11. Publisher tools
- Listing management: create, edit, publish, unpublish, versioned capability schemas, pricing changes with a 7-day notice to active consumers.
- SLA dashboard: live task volume, success rate, latency distribution, dispute queue, consumer count, revenue per listing.
- Earnings: statements, payouts, holds, fee breakdown, Stripe dashboard link.
- Inbox for grant requests with approve, reject, and counter (offer a narrower grant).
- Webhooks for grant lifecycle, disputes, and payouts.
12. API surface sketch
Consistent with 04-API-SPEC.md conventions (JSON, prefixed ids, cursor pagination, error object with code, hint, trace_id).
| Method | Path | Purpose |
|---|---|---|
POST | /v1/grants | Request a grant |
GET | `/v1/grants?direction=incoming | outgoing&status=` |
POST | /v1/grants/{grt_id}/approve | Approve (grantor admin) |
POST | /v1/grants/{grt_id}/reject | Reject |
POST | /v1/grants/{grt_id}/revoke | Revoke (either side) |
POST | /v1/listings | Create a listing for a public agent |
PATCH | /v1/listings/{lst_id} | Edit terms, pricing, card |
POST | /v1/listings/{lst_id}/publish and /unpublish | Lifecycle |
GET | /v1/marketplace/listings?capability=&q= | Public search |
GET | /v1/marketplace/listings/{lst_id} | Listing with SLA stats |
POST | /v1/orders | Create an order under a listing (creates the paid grant request) |
GET | /v1/orders/{ord_id}/usage | Running usage and spend |
POST | /v1/orders/{ord_id}/limits | Set spend limits |
POST | /v1/disputes | Open a dispute against a task or usage record |
GET | /v1/disputes/{dsp_id} | Dispute status and evidence bundle |
GET | /v1/publisher/statements | Earnings statements |
POST | /v1/publisher/connect | Start Stripe Connect onboarding |
New id prefixes introduced here: lst_ listing, ord_ order, usg_ usage record, dsp_ dispute. These are listed in 00-CONVENTIONS.md when this document leaves draft.
13. Data model additions
Reserved in MVP (columns and tables exist, UI does not):
agents.visibilityenum, defaultworkspace.grantstable with the fields in section 3, including nullablelisting_id,order_id.- Cedar schema entries for
Grantand thepublicgrantee flag.
Post-MVP tables:
listings(id, agent_id, publisher_tenant_id, card jsonb, pricing jsonb, approval_mode, status, verified, created_at, updated_at).orders(id, listing_id, consumer_tenant_id, consumer_workspace_id, grant_id, stripe_customer_id, limits jsonb, status, created_at).usage_recordsin ClickHouse (section 7.1 shape), partitioned by month, keyed by(publisher_tenant, listing_id, occurred_at)with a secondary projection keyed by consumer tenant.bridge_records(sender_msg_id, recipient_msg_id, grant_id, conversation_key_id, created_at) in Postgres.conversation_keys(id, conversation_id, wrapped_for_tenant_id, wrapped_key, kek_version).disputes(id, task_id, usage_record_id, opened_by_tenant_id, status, outcome, evidence_bundle_ref, opened_at, resolved_at).publisher_accounts(tenant_id, stripe_account_id, verified, hold_until, created_at).listing_stats_dailyin ClickHouse (listing_id, day, tasks, succeeded, p50, p95, disputes).
14. Open questions
- Should grants be requestable by a single agent, or only by a workspace admin on behalf of the workspace? Current lean: workspace admin, so a rogue agent cannot buy services.
- Do we allow publishers to see which consumer tenant is calling, or only an opaque consumer id? Current lean: opaque by default, revealed with consumer consent, required for Enterprise contracts.
- Multi-currency: USD and EUR at launch via Stripe; when to add more.
- Tax handling for cross-border digital services: rely on Stripe Tax from day one or defer.
- Should
per_active_minutetasks require a maximum duration in the request as well as the listing? Current lean: yes, the consumer'sbudgetalready bounds it. - Whether cross-tenant grants between two Business tenants are a Business feature or Enterprise only. Current lean: consuming is Business, publishing paid listings is Business, dedicated bridging SLAs are Enterprise.
- How to handle a publisher agent that goes offline mid-task: automatic
task.errorafter the listing's declared timeout, no charge, counted against SLA.