Compute Contracts - Pre-Alpha

This is a spec for ATProto accounts to request and advertise (compute) resources.

This spec is for you if you run an organization and want to leverage decentralized identity to allow access to your org's compute (and in the future more) resources to your members and their workloads/agents.

The flow is: Request For Proposal -> Bid -> Accept -> Receipt

Quickstart

curl -fsSL https://deno.land/install.sh | sh

git clone --recursive https://github.com/publicdomainrelay/org-root-dispatcher-typescript
cd org-root-dispatcher-typescript/atproto-market

Ephemeral Accounts with association records

Defaults to --policy only-me , so make sure you associate the bidder and requester with the same ATproto account when prompted to scan QR code / see link to qr.fedfork.com in logs.

Bid on compute contracts

deno run -A hono-bidder/mod.ts \
  --compute-provider-local

Request compute

deno run -A request-vm-ssh/mod.ts

BlueSky Accounts via QR Code Session Transfer

Both operators scan the QR codes from their own processes using the did-key-associator webapp at https://qr.fedfork.com .

Bids on compute contracts

deno run -A hono-bidder/mod.ts \
  --atproto-oauth-qr \
  --atproto-handle bob.bsky.social \
  --compute-provider-local \
  --policy tangled-vouch \
  --serve-port 0 \
  --no-ingress-proxy \
  --firehose-mode subscriberepos

Request Compute

deno run -A request-vm-ssh/mod.ts \
  --atproto-oauth-qr \
  --atproto-handle alice.bsky.social \
  --policy tangled-vouch \
  --no-ingress-proxy \
  --firehose-mode subscriberepos

Secrets

To send secrets to the VM without putting them in cloud-init (public ATproto data), just pass a JSON file to --secrets :

For example, if you wanted to send your https://cocore.dev token to a new VM you could do it like so:

# Set ATP_HANDLE and ATP_PASSWORD for goat account login or provide like so:
goat account login -u "aliceoa.bsky.social" -p "aaaa-aaaa-aaaa-aaaa"

TOKEN=$(goat account service-auth \
  --lxm dev.cocore.account.createApiKey \
  --aud did:web:appview.cocore.dev)

PI_CONFIG=$(curl -sS -X POST \
  -H "Authorization: Bearer ${TOKEN}" \
  -H "Content-Type: application/json" \
  'https://appview.cocore.dev/xrpc/dev.cocore.account.createApiKey' \
  -d '{"name":"my-pi"}' | jq -r '{"apiKey": .secret}')

deno run -A request-vm-ssh/mod.ts \
  --atproto-oauth-qr \
  --atproto-handle aliceoa.bsky.social \
  --no-ingress-proxy \
  --firehose-mode subscriberepos \
  --policy tangled-vouch \
  --secrets <(jq -n \
    --arg pi "${PI_CONFIG}" \
    --arg provider "cocore" \
    --arg model "mlx-community/Ornith-1.0-9B-4bit" \
    '[
      {
        path: "/root/.pi/agent/cocore-config.json",
        value: $pi,
      },
      {
        "path": "/root/.pi/agent/settings.json",
        "value": {
          "defaultProvider": $provider,
          "defaultModel": $model
        }
      }
    ]')

Here's a quick jq example of using env var names as arguments instead of writing each one out (then you'd pass --secrets /tmp/secrets.json ):

jq -n --args '[$ARGS.positional[] | {path: ("/root/secrets/" + ascii_downcase), value: env[.]}]' \
  SOME_API_KEY OTHER_ENV_VAR > /tmp/secrets.json

Passing secrets this way works by leveraging Workload Identity Federation. We thereby avoid storing anything sensitive in the public compute.vm record.

Protocol

sequenceDiagram
    autonumber
    actor Alice
    actor Bob

    rect rgb(240, 248, 255)
        Note over Alice,Bob: Request For Proposal (RFP)
        Alice->>Bob: createRecord: compute.vm
(CPUs, Memory, Disk, etc.) Alice->>Bob: createRecord: rfp
(strongRef to compute.vm) end rect rgb(245, 245, 245) Note over Alice,Bob: Bid Bob->>Alice: createRecord: bids.free (or x402)
(determine price) Bob->>Alice: createRecord: bid
(strongRef to RFP + payment type) end rect rgb(240, 248, 255) Note over Alice,Bob: Accept Note over Alice: Payment Settlement Flow (NOP if bids.free) Alice->>Bob: createRecord: accept
(strongRef to RFP + Bid + receipt-of-payment) end rect rgb(245, 245, 255) Note over Alice,Bob: Receipt Note over Bob: Provision Compute Bob->>Alice: createRecord: receipt
(strongRef to RFP + Bid + Accept) Bob->>Alice: createRecord: compute.events.vm.onNetwork
(address: public IP or proxied FQDN) Bob->>Alice: createRecord: market.event
(strongRef to onNetwork + receipt) end Note over Alice: Connect to compute at/via address

Compute Contract Flow (High level)

The compute contract flow can be executed purely over the firehose/relays. That's what we'll cover in this section. For more details see the open-architecture repo's COMPUTE_CONTRACT_FLOW_MAP.md .

All market.* records use https://badge.blue signatures, not shown here for brevity.

Alice createRecord's the type of compute she wants

$type: com.publicdomainrelay.temp.compute.vm
cpus: 2
mem: 4G
disk: 40G
network: 500G
role: my-cool-role
user_data: |
  #cloud-config
  runcmd:
    - [sh, -c, "echo hello world"]
location:
  country: USA
  region: west

Alice createRecord's a Request For Proposal ( rfp ) strongRef erencing the compute she wants.

$type: com.publicdomainrelay.temp.market.rfp
payload:
  $type: com.atproto.repo.strongRef
  uri: at://did:plc:alice/com.publicdomainrelay.temp.compute.vm/3mm3dolfolz2c
  cid: bafyrei...vm

Bob decides what he wants Alice to pay for the compute and createRecord's the payment type for his bid.

$type: com.publicdomainrelay.temp.market.bids.free
# Can also do x402 but haven't tested this in a while:
# $type: com.publicdomainrelay.temp.market.bids.x402
# cost: 0.10
# currency: USDC
# frequency: hourly
# prepay: true
# url: https://compute-contract.bob.example/receipt

Bob createRecord's a Bid ( bid ) strongRef erencing the compute she wants.

$type: com.publicdomainrelay.temp.market.bid
rfp:
  $type: com.atproto.repo.strongRef
  uri: at://did:plc:alice/com.publicdomainrelay.temp.market.rfp/3mm3doliee72s
  cid: bafyrei...rfp
payload:
  $type: com.atproto.repo.strongRef
  uri: at://did:plc:bob/com.publicdomainrelay.temp.market.bids.x402/3mm4...
  cid: bafyrei...x402

Alice settles the payment and gets a payment receipt according to settlement sub-flow. If bids.free , then that's a NOP.

Alice then createRecord's an Accept ( accept ) record which wraps her proof-of-payment receipt (only if not bids.free )

$type: com.publicdomainrelay.temp.market.accept
rfp:
  $type: com.atproto.repo.strongRef
  uri: at://did:plc:alice/com.publicdomainrelay.temp.market.rfp/3mm3doliee72s
  cid: bafyrei...rfp
bid:
  $type: com.atproto.repo.strongRef
  uri: at://did:plc:bob/com.publicdomainrelay.temp.market.bid/3mm4...
  cid: bafyrei...bid

Finally, Bob provisions the compute and issues a Receipt ( receipt ) when he sees the Accept.

$type: com.publicdomainrelay.temp.market.receipt
rfp:
  $type: com.atproto.repo.strongRef
  uri: at://did:plc:alice/com.publicdomainrelay.temp.market.rfp/3mm3doliee72s
  cid: bafyrei...rfp
bid:
  $type: com.atproto.repo.strongRef
  uri: at://did:plc:bob/com.publicdomainrelay.temp.market.bid/3mm4...
  cid: bafyrei...bid
accept:
  $type: com.atproto.repo.strongRef
  uri: at://did:plc:alice/com.publicdomainrelay.temp.market.accept/3mlagijgoeb23
  cid: bafyrei...accept

Alice then awaits the onNetwork event

$type: com.publicdomainrelay.temp.compute.events.vm.onNetwork
address: did-key-zq3shzasuf3hvonhlzf7z52hanqxknlrncr1in8njd3ymzfnl.xrpc.fedproxy.com
$type: com.publicdomainrelay.temp.market.event
payload:
  cid: bafyreihp5j7tc6xbj4yqnzanwjsqfqasazqr6cbdthuwni3xdrgy2ymmeq
  uri: at://did:plc:5svqtrhheairglgiiyvutzik/com.publicdomainrelay.temp.compute.events.vm.onNetwork/3mqqgq57oss26
  $type: com.atproto.repo.strongRef
receipt:
  cid: bafyreicpqkmnigboxueqaa2y6lvn7b5qjaiw46jb6mqgymlahpl3v5fal4
  uri: at://did:plc:5svqtrhheairglgiiyvutzik/com.publicdomainrelay.temp.market.receipt/3mqqgpxvfls26
  $type: com.atproto.repo.strongRef