Bidder architecture
Host readings on 2026-10-22 from the installed bidder on this host, hat-9 and hat-7 (release binary
sha256 30ab2e465a080bbe47ea16e085cdd99d10c640f4c90d438484363a443b31bd92); the source claims were
rechecked against commit 1cad64b0 on 2026-10-26.
Shape
A bidder is one installed application. It has one socialweb-computer core process, one kcp
logical cluster, one OpenBao server and its own PDS. It does not join a market network and it does
not run a separate application per component.
systemd --user (account swc-bidder, uid 947 / 988 / 964)
socialweb.slice
├─ socialweb-computer-bidder@<handle>.service core: appctl, componentcore, HostProcess supervisor
├─ socialweb-kcp-<instance>-kine@a.service kine on sqlite
├─ socialweb-kcp-<instance>-front.service L4 front for kcp
├─ socialweb-kcp-<instance>@a.service kcp API server slot a
└─ socialweb-hp-<instance>-bao-bidder.service OpenBao HostProcess
core (one process)
├─ devcore: lock, kcp slot lifecycle, bootstrap, members
├─ appctl: MarketBidder root, child objects, status, reconcile loop
├─ componentcore: Bidder, GuestControlPlane, Pds, PolicyEngine, Service/Exposure/ComponentLink
├─ hostprocess supervisor: bao-bidder and ComputeVM units
└─ PolicyEngine runner + StateSync workers
The installed state root is /var/lib/socialweb-computer/swc-bidder (or the path passed to
install bidder). Its kcp handle is under handles/<hash>/kcp; the current bidder uses
handles/53eff7074d/.
kcp layout
Observed root workspaces:
| workspace | type | role | phase |
|---|---|---|---|
socialweb-provider | universal | API schemas, exports and provider service accounts | Ready |
app | socialweb-application | the bidder's application objects and components | Ready |
The root workspace also holds the kcp bootstrap objects and the MarketBidder root. Namespaces in
the root and app workspaces are default and kcp-system (the latter from kcp itself).
The socialweb-application type binds applications, policy, computevms, hostprocesses,
clusters, atprotoidentities, marketidentities, computecontracts, exposures, pds and
events. The bidder set additionally installs the computevms schema/export and the root binding,
because the bidder owns ComputeVM objects.
Observed root objects on this host:
| kind/name | phase | note |
|---|---|---|
MarketBidder/app | Ready | profile prod-qr, DID did:plc:vxd7pmlhw6bnpyu6wngewthn |
MarketIdentity/bidder | Ready | localRepo, association RequireApproval, approved operator DID |
OpenBaoServer/bidder | Ready | https://127.0.0.1:45457, one node, initialized and unsealed |
Pds/pds | Ready | app-owned PDS, endpoint http://pds.pds |
Bidder/bidder | Ready | market loop and guest launch |
GuestControlPlane/guest-kcp | Ready | relay, OIDC, sshsig, on-network and host keys |
PolicyEngine/policy-engine | Running | in-process policy engine |
AtprotoRecord/association | Pending | association record declaration |
AtprotoRecordWatch/association | — | watches the operator repo |
EventStream/watch-association | Running | feeds the watch |
HostProcess/bao-bidder | Running | OpenBao |
HostProcess/bootstrap-kcp-a, bootstrap-kine-a, bootstrap-front | Running | kcp bootstrap units |
Every component has a Service and an Exposure:
| Service | target | Exposure |
|---|---|---|
svc-bidder-bidder | Bidder/bidder | ingress bobvmbuilder.bsky.social through IngressProxy/ingress |
svc-guestcontrolplane-guest-kcp | GuestControlPlane/guest-kcp | ingress through IngressProxy/ingress |
svc-pds-pds | Pds/pds | local http://127.0.0.1:20000; ingress through IngressProxy/ingress |
Application components do not bind their own public sockets. The component runtime registers
handlers in-process; the local Exposure controller leases a loopback port from the port registry
(pds is 20000 here). The prod-qr and market-network profiles default spec.ingress to
xrpc-subscription with ingressProxyHost: xrpc.fedproxy.com and IngressProxy/ingress; the PDS
ingress Exposure is fail-closed until that provider is Ready. A bidder joined to a market network
inherits MarketNetworkValue.ingress and its dispatcher instead.
Identity and association
install bidder creates the PDS account and writes the DID into MarketIdentity/bidder, the
session into the bidder's OpenBao at bidder/data/identity/bidder/pds-session, and the PDS repo key
at bidder/data/identity/bidder/keys. The bidder's own repo is the PDS, not the operator's repo.
Association is one com.publicdomainrelay.temp.badgeBlueKeys record in the operator's repo; the
verified operator DID lands in MarketIdentity.status.associations. The record shape is:
{
"$type": "com.publicdomainrelay.temp.badgeBlueKeys",
"keyId": "did:plc:<bidder>",
"challenge": "did:plc:<operator>",
"service": "bidder_associate",
"name": "bidder",
"createdAt": "2026-10-22T00:41:02Z"
}
scripts/associate-bidder.ts writes that record from a mode-0600 credentials file without printing
the handle or password. The operator's AtprotoRecordWatch/association verifies the claim and the
identity controller writes the operator DID into status.associations, setting Associated=True
with reason Associated. With spec.association: Skip it sets Associated=True and reason
AssociationSkipped. A manual patch of spec.approvedAssociations is a declarative workaround, not
the accepted path:
sudo kubectl --kubeconfig "$K" patch marketidentity bidder --type=merge \
-p "{\"spec\":{\"approvedAssociations\":[\"$OPERATOR_DID\"]}}"
The bidder's own AtprotoRecord/association is applied into the app's PDS by the AtprotoRecord
controller; the record is listed by com.atproto.repo.listRecords.
Live bidder instances
| host | state root | uid | bidder DID | association | mode |
|---|---|---|---|---|---|
| this host | /var/lib/socialweb-computer/swc-bidder | 947 | did:plc:vxd7pmlhw6bnpyu6wngewthn | Associated, operator did:plc:ocnuqjlzgohypj3zoznermms | RequireApproval |
| hat-9 | /var/lib/socialweb-computer/swc-bidder | 988 | did:plc:cjuftobfmmmdkmblcl4h54eb | Associated, same operator | RequireApproval |
| hat-7 | /var/lib/socialweb-computer/swc-bidder | 964 | did:plc:exjutifioeu2p7oorbdrex2o | AssociationSkipped | Skip |
All three run the release binary at
bin/30ab2e465a080bbe47ea16e085cdd99d10c640f4c90d438484363a443b31bd92 and report
MarketBidder/app Ready. Each has its own PDS, OpenBao, DID and host guest image
bddc484c610e956fb7e198281cf4a19dc8c95fc441c0575f49d2a33de0c74021.
Association-record state:
- this host:
at://did:plc:vxd7pmlhw6bnpyu6wngewthn/com.publicdomainrelay.temp.badgeBlueKeys/association, cidbafyreiapgmzf3cgdmpxefzonba52xk5lo6kojkd2eijyiq2yg6jtmirjpm - hat-9:
at://did:plc:cjuftobfmmmdkmblcl4h54eb/com.publicdomainrelay.temp.badgeBlueKeys/association, cidbafyreiebup7ijio2u73l2sgz4bfpginggbqdjxiqxfspjg5tzcw2da5czm
Install, upgrade, delete
install bidder is an account install. It creates the swc-bidder account, subordinate ID
ranges, state dir, binary slot, config, guest image, systemd user units, linger and kcp bootstrap.
upgrade --binary --sha256 switches bin/current, restarts the core and keeps bin/previous.
install bidder again reconciles the unit files and host contract. uninstall bidder --purge
stops the app units, removes the state dir (including the PDS repo and OpenBao) and deletes the
account; --force is only for live VM units.
The prod-qr profile uses a local repo and an app-owned PDS. A fresh install therefore creates a
new DID and needs the one association claim; an upgrade keeps the state, DID, repo and association.
Known gaps
- Ingress Exposures are Pending on a standalone bidder with no
IngressProxy. AtprotoRecordWatch/associationcan reportinvalid repo opwhile the PDS stream is consumed; the association state above does not depend on that watch.
Inspect
K=$(sudo sh -c 'ls /var/lib/socialweb-computer/swc-bidder/handles/*/kcp/admin.kubeconfig' | head -1)
sudo kubectl --kubeconfig "$K" get workspaces,marketbidder,marketidentity,openbaoserver
sudo kubectl --kubeconfig "$K" get bidder,guestcontrolplane,pds,policyengine,service,exposure -o wide
sudo kubectl --kubeconfig "$K" get hostprocess,atprotorecord,atprotorecordwatch,eventstream -o wide
sudo kubectl --kubeconfig "$K" get marketidentity bidder -o jsonpath='{.spec}{"\n"}{.status}{"\n"}'
The kcp kubeconfig is readable only by root on an installed host; the core's health and unit state
are under systemctl --user for the swc-bidder account.