Quickstart (prod): Infra and compute against prod bidders
- the same Infra / EphemeralInfra objects as quickstart-local.md, rented from prod bidders with a real atproto account
- prod means: prod bidders (each runs
bidder-tangled-vouch), plc.directory, the did:key ingress relay at xrpc.fedproxy.com, the relay at relay.mini-cloud-0002.chadig.com, irc.fedfork.com for atirc, a real atproto account - what works today: the browser page (section 3), the CLI per contract (section 4), the agent join (section 5), tenant clusters
through the prod gateway (section 6), a long-lived requester from the CLI that
kubectl applyworks against (section 7,run requester) - also on main: that requester installed as a systemd unit of its own account (section 7a,
install requester) or as a HostProcess of an account's core (section 7b); the atirc agent from a local PDS or a pdsAccount identity is built (section 5) and not yet proven on prod - also on main: your own tenant cluster in the requester kcp (section 8,
run requester --with-gateway, plan 0039 item C-GATEWAY-COMPOSE), proven on a local network; a node from a remote prod bidder is the lead's run - acceptance: acceptance/0012-infra-quickstart.md "Prod Step For The Agent Join" and "Prod Readings"; acceptance/0013-mixed-prod.md for ../examples/infra/mixed-prod.yaml on a bidder you configured; the runbook rules of acceptance/README.md apply (announce on atirc first, release what you rent, no secret on any terminal)
flowchart TD
ACC["your atproto account<br/>(OAuth QR / browser, or app password)"] --> V["vouches: you -> bidder operator,<br/>bidder operator -> you"]
V --> PAGE["browser page shell.socialweb.computer<br/>kubectl terminal, page's own kcp"]
V --> CLI["CLI per contract<br/>run request --hold [--agent]"]
V --> GW["prod gateway XRPC<br/>createCluster / getKubeconfig"]
V --> LL["run requester<br/>long-lived kcp, ready file, kubectl apply"]
PAGE --> HELD["ComputeContract Held on a prod bidder"]
CLI --> HELD
LL --> HELD
GW --> K8S["tenant k3s, nodes rented through the gateway Infra"]
HELD --> AG["agent VM: requester mints at irc.fedfork.com,<br/>guest.agentStart, nick joins"]
1. Who Runs This
- you, with your own account, from your own host or browser; prod bidders and prod services are shared, so follow the acceptance rules: one driver per account, announce before a run, release everything you rent
- never set
policy: open: the defaultrequester-tangled-vouch(requester) andbidder-tangled-vouch(every prod bidder) always run - credentials stay in owner-only files, sourced into the one command that needs them; never print a token, a password or a sign-in URL
2. Vouches On Prod
Both policies read sh.tangled.graph.vouch records (rkey = the subject DID, record.subject = that DID, any kind but
denounce). On prod they are records in people's own repos, written with any atproto client (goat record create):
| direction | who writes | record |
|---|---|---|
| bidder operator -> you | the bidder operator, in the operator's repo | rkey = your DID |
| you -> bidder operator (or the bidder DID) | you, in your repo | rkey = the operator's DID |
- read a bidder's operator from its MarketIdentity
status.approvedAssociations, or ask the operator - the bidder runs the RFP's attached policy as itself before it bids, so without the operator's record you see
no bids received within 30sand, on the bidder,policy rejected ... not vouched - verdicts are cached 5 minutes, denials too: write the records first
- check a record:
goat get "at://<operator did>/sh.tangled.graph.vouch/<your did>" | jq -r .kindprintsvouch - when you operate the bidder yourself (
install bidder, bidder.md), you write the operator record from the operator account; ../examples/infra/vouch-bidder-operator.yaml applies in the bidder kcp of a bidder installed with--bidder-operator-signin <operator handle>: the operator signs in once by QR (scope:sh.tangled.graph.vouchwrites only) and the record is written to the operator's repo (bidder.md, Operator sign-in)
3. Browser Page: kubectl Terminal
The page shell.socialweb.computer runs a kcp and the requester in the browser (comms Firehose, sealed secrets fallback).
Its kubectl terminal applies the same files into the page's own kcp (acceptance/0009-browser-page.md).
- sign in with your account (OAuth)
- in the terminal, paste the file with a heredoc (the page terminal has no repo checkout, so
-f agent-vm.yamlfinds no file):kubectl apply -f - <<EOF+ the contents ofexamples/infra/agent-vm.yaml(orvm.yaml) +EOF - read
kubectl get ephemeralinfras.socialweb.computer -o yamluntil the contract is Deployed - the Agent button sends
guest.agentStart;ssh <contract>opens a shell from the page terminal kubectl patch ... state Releasedreleases it
-
proven on the page:
agent-vm.yamlandvm.yaml, released bykubectl patch ... state Released;mixed.yaml(with the DenoSandboxsbx1) and release by deleting the object are not yet proven on the page -
page-rc142 and later write the ssh key and the agent secrets bundle for a kubectl-applied contract; on page-rc141 an
agentcontract failed at once andssh <contract>had no key -
last pass: page-rc142, account hat, EphemeralInfra
qsprod, Held in 41 s,hat-<rkey>joined, revoked, then Released (acceptance 0012 "Prod Readings")
4. CLI Per Contract: run request
socialweb-computer run request brings up a kcp of its own for one invocation, composes a MarketRequester root, rents one
contract and keeps the invocation under --state-dir (runbooks 0003,
0004).
SWC=socialweb-computer
R=~/.tmp/req; mkdir -p "$R"; chmod 700 "$R"
NAME=qs-vm-$(date +%s)
$SWC run request --state-dir "$R" --name "$NAME" --hold \
--tunnel ssh:22:webrtc --transport webrtc --exec "uname -a; id" \
-- --atproto-oauth-qr > "$R/$NAME.log" 2>&1 &
# sign in from the pendingAction url in the log (QR or a scripted sign-in); never paste it anywhere
$SWC run request ssh "$NAME" --state-dir "$R" --transport webrtc --exec "uname -m"
$SWC run request release "$NAME" --state-dir "$R"
- identity:
-- --atproto-oauth-qr(QR),-- --atproto-oauth(browser), or an existing account with an app password (-- --atproto-identity-mode pdsAccount --atproto-handle <handle> --atproto-pds-url <pds>, the password inATPROTO_PASSWORDin the environment of that one command;run requesthas no--atproto-passwordoption) run request list,run request resume <name>,run request release <name>manage the invocations it recorded- one invocation is one contract; there is no
kubectl applyof an Infra into it (section 7)
5. Agent VM: The atirc Join
The mint needs a requester identity irc.fedfork.com resolves and that signs service auth (lxm chat.atirc.mint, aud
did:web:irc.fedfork.com#atirc). irc.fedfork.com checks the JWT against the DID document from plc.directory, then the
handle: the first of up to 3 alsoKnownAs aliases whose DNS TXT _atproto.<handle> (else
https://<handle>/.well-known/atproto-did) names the DID. An agent token is leased, so irc.fedfork.com also reads the
receipt and the bidder's vm.terminated from the requester's PDS: that PDS must be reachable from the internet.
| identity | signs the mint with | log requester_service_auth atircSigning |
|---|---|---|
OAuth (--atproto-oauth-qr, --atproto-oauth, the page, the iOS app) | the OAuth session; its granted scope holds rpc:chat.atirc.mint | oauth |
existing account with an app password (--atproto-identity-mode pdsAccount) | the owner MarketIdentity (getServiceAuth on that account's PDS) | owner-identity |
local PDS (localRepo) | its own Pds component, only when the DID is on https://plc.directory and the Pds is public through its ingress (a ComponentLink Requester -> Pds pds.serviceauth); otherwise none, with the reason in atircReason | local-pds or none |
- the market callbacks (submitBid, submitEvent, the RFP and accept signatures) are unchanged: this source signs only the atirc mint
- a
spec.agentcontract whose requester handle is.local(the defaultlocalRepohandle<workspace>.requester.local) or does not resolve to the requester DID is Failed before its RFP, withrefused: the atirc agent needs a requester handle that resolves to <did>: ...; start the requester with --atproto-handle <a domain you control> ...; a plain VM contract of the same requester still runs - a
spec.agentcontract whosespec.secretsRefpath holds no bundle gets one from the requester: one entry atagent.envFile(ATIRC_TOKEN=, mode 0600), loggedrequester_agent_bundle_placeholder; no bao step is needed (the bao commands of quickstart-local.md still work and win, a bundle that is there is kept). The path must be undercontracts/of the requester mount
NAME=qs-agent-$(date +%s)
$SWC run request --state-dir "$R" --name "$NAME" --hold --agent -- --atproto-oauth-qr > "$R/$NAME.log" 2>&1 &
# sign in, wait for Held and requester_atirc_minted in the log
$SWC run request agent "$NAME" --state-dir "$R" # guest.agentStart
# watch your channel with your own atirc token for the JOIN of <label>-<rkey>; prompt "<label>-<rkey>: uname -m"
$SWC run request release "$NAME" --state-dir "$R"
- the requester mints a leased token
<label>-<rfp rkey>.<handle>after the verified receipt and writes it into the bundle (/etc/atirc/atirc.env, 0600); release revokes it before the bidder writesvm.terminated - last pass: CLI, account alice,
aliceoa-<rkey>joined and answeredx86_64over atirc; revoked before vm.delete
6. Tenant Kubernetes Clusters: The Prod Gateway
The prod gateway at k8s.socialweb.computer owns tenant clusters. With service auth from your account (runbook
0001 section 3; CL, W and the xrpc shell function over goat come from its
"Variables" and "Service auth for the gateway" blocks, sourced first):
xrpc procedure createCluster name="$CL"
xrpc query listClusters | jq -r --arg n "$CL" '.clusters[] | select(.name==$n) | .phase' # until Ready
xrpc procedure getKubeconfig name="$CL" ttlSeconds:=28800 | jq -r .kubeconfig > "$W/kubeconfig"
chmod 600 "$W/kubeconfig"
kubectl --kubeconfig "$W/kubeconfig" apply -f examples/infra/k8s-cluster.yaml
- a pod that does not fit makes a NodeClaim; the gateway's Infra
nodes-<hex>rents the node from a prod bidder - your own tenant cluster without the prod gateway: section 8 (
run requester --with-gateway)
7. A Long-Lived Requester From The CLI: run requester
socialweb-computer run requester (plan 0039 item A-RUN-REQUESTER) brings up the
same MarketRequester root as run request in a kcp of its own and keeps it up: no contract of its own, every listener on
port 0, state under <state-dir>/requesters/<name>. You kubectl apply Infra, EphemeralInfra or ComputeContract objects
against it, and its requester rents them from prod bidders under the default policy.
ST=~/.tmp/lr; mkdir -p "$ST"; chmod 700 "$ST"
$SWC run requester --name me --state-dir "$ST" -- --atproto-oauth-qr > "$ST/me.log" 2>&1 &
# sign in from the pendingAction url in the log (QR or scripts/oauth-signin.ts); never paste it anywhere
R=$ST/requesters/me/ready.json
until [ -s "$R" ]; do sleep 1; done # written atomically once the root is Ready
eval "$($SWC run requester env me --state-dir "$ST")" # KUBECONFIG of the requester workspace, the DID
kubectl get marketrequesters.socialweb.computer app -o jsonpath='{.status.phase} {.status.did}{"\n"}'
sed "s|ssh-ed25519 AAAAREPLACE-WITH-YOUR-PUBLIC-KEY you@laptop|$(cat "$(jq -r .sshPublicKey "$R")")|" \
examples/infra/vm.yaml | kubectl apply -f - # pin a bidder with bidderDids when you need one
kubectl wait infras.socialweb.computer/quickstart-vm --for=jsonpath='{.status.phase}'=Deployed --timeout=3m
$SWC run requester ssh me --state-dir "$ST" --contract infra-quickstart-vm-vm1 --exec 'cat /root/quickstart'
$SWC run requester ssh me --state-dir "$ST" --contract infra-quickstart-vm-vm1 --transport webrtc --exec 'uname -m'
kubectl delete infras.socialweb.computer quickstart-vm # releases the contract first
kill -INT %1 # Ctrl-C: stops the kcp, removes the ready file, keeps the state
- identity, after
--(the MarketRequesterSpec options ofrun request, parsed by the same code):--atproto-oauth-qr,--atproto-oauth, or an existing account with an app password (--atproto-identity-mode pdsAccount --atproto-handle <handle> --atproto-pds-url <pds>, the password inATPROTO_PASSWORDin the environment of that one command, which puts it into the requester's own OpenBao, or laterrun requester password <name>on stdin); without one it is a local PDS (localRepo,--identity <name>before--reuses a stored one); comms, ingress, requester ingress and--secrets-fallbacktoo. A contract option (--vm-name,--tunnel,--hold, ...) is refused: contracts are kubectl objects here - the ready file (
<dir>/ready.json, and a second copy at--ready-file <path>): written to<path>.tmpthen renamed, mode 0600, once the root is Ready; rewritten on every restart and when a field changes; removed on a clean stop.{schema: 1, name, pid, startedAt, kcpUrl, kubeconfig, workspace, did, handle, identityMode, pdsUrl, ingress {pds, requester}, sshPublicKey}: the bound kcp front, the path of a kubeconfig for the requester's workspace, the public ingress URLs from the Exposure status, the path of the requester's ssh public key; never a token, key or password. The same JSON (plus mode, network, instance, readyFile, restarted, seconds) is the summary on stdout - foreground only: it blocks until Ctrl-C (or SIGTERM), then stops its kcp and keeps its state;
--fgis accepted (the installed unit form). A secondrun requesteron the same name is refused while the first holds<dir>/owner.lock - restart: the same command (or the same command with nothing after
--) adopts the units and keeps the DID, the vault and every contract; a Held contract stays Held across the restart and ssh works again (the A-RUN-REQUESTER reading below; fixed in rc158, proven on prod in acceptance 0013 A).<dir>/requester.jsonrecords the first start's network,--identityand options; a restart with other options is refused (start a new name) run requester env <name>printsKUBECONFIG,SOCIALWEB_KCP_HOST,SOCIALWEB_KCP_WORKSPACE, the DID and the ssh public key path from the ready file (--json);run requester ssh <name> --contract <cc>is therun request sshcode over any Held contract, with the requester's own key andStrictHostKeyChecking=yesagainst the guest's reported host keys (--transport webrtcneeds a webrtc tunnel on the contract, as invm.yaml)- vouches: as section 2; the requester's own record for the bidder can be the
vouch.yamlAtprotoRecord applied into this kcp (its repo isrepo-<did with - for :>) - proven (2026-11-01, own market network,
--network, bidder policybidder-tangled-vouch): ready 11 s,vm.yamlDeployed 20 s after the apply, ssh over iroh and webrtc printedquickstart-vm-ok; Ctrl-C and restart: same DID, ready file rewritten in 1.4 s, contract still Held, ssh again. The standalone path against prod bidders with an OAuth QR sign-in is the lead's run (acceptance rules)
Ingress options per identity
Two ingresses, two jobs. Both go after -- (rows of deploy/apps/option-map.yaml, app market-requester); the ready
file reports both public URLs (ingress.pds, ingress.requester).
- PDS ingress (
spec.ingress, a local PDS only): makes the DID document'satproto_pdsendpoint, the relay crawl, the bidders' record reads and irc.fedfork.com's lease reads reach the local PDS - requester ingress (
spec.requesterIngress, every identity): submitBid, submitEvent and getSecrets; the agent'sATIRC_TOKENreaches the guest through getSecrets
| identity | PDS ingress | requester ingress | agent mint |
|---|---|---|---|
| OAuth QR / browser | none (the account's PDS is public) | as below | mints |
| pdsAccount (app password) | none (the account's PDS is public) | as below | mints through the owner MarketIdentity |
| localRepo (own Pds) | --ingress-xrpc-host xrpc.fedproxy.com (the did:key ingress relay; the standalone default, public URL https://<did label>.xrpc.fedproxy.com); --ingress-provider opentunnel --ingress-opentunnel-command ... --ingress-opentunnel-route-name ...; --ingress-public-host <host> (with --ingress-relay-url <your relay>) for https://<did label>.<host> behind your own edge | as below | mints only with the DID on plc.directory (standalone default --plc-directory-url https://plc.directory), that PDS ingress Ready, and --atproto-handle <a domain you control> whose DNS TXT _atproto.<handle> or https://<handle>/.well-known/atproto-did names the DID |
Requester ingress, every identity:
| option | effect |
|---|---|
(none, comms XRPC or Both) | the app ingress (standalone: xrpc-subscription through xrpc.fedproxy.com) |
--requester-ingress-provider webrtc (--requester-ingress-webrtc-stun stun:...) | bids, events and getSecrets over a webrtc datachannel |
--requester-ingress-provider xrpc-subscription, opentunnel or iroh | the same endpoints through that provider |
--comms-mode Firehose | no inbound endpoint at all: add --secrets-fallback Sealed, or a contract with spec.secretsRef (every agent contract) fails with "secrets need the requester ingress (getSecrets) or secretsFallback: Sealed" |
- a localRepo requester with a public handle, its PDS behind xrpc.fedproxy.com and the agent:
$SWC run requester --name me --state-dir "$ST" -- --atproto-handle me.example.org(the defaults give plc.directory and xrpc.fedproxy.com); publishdid=<the ready file's did>as DNS TXT_atproto.me.example.orgbefore the first agent contract; the core log showsrequester_service_auth ... "atircSigning":"local-pds" - proven (2026-11-01, own market network, localRepo with the default
.localhandle, bidder policybidder-tangled-vouch):atircSigningnone("the DID is on the network PLC"), nopds.serviceauthlink;agent-vm.yamlwith no bao step: placeholder bundle written (/etc/atirc/atirc.env, 0600), contract Failedrefused: ... --atproto-handle ...with no RFP;vm.yamlof the same requester Deployed in 22 s, ssh printedquickstart-vm-ok. A real mint from a local PDS is the lead's prod run
7a. Installed: install requester (a systemd user unit of its own account)
install requester (plan 0039 B-INSTALL-REQUESTER, decisions D2 and D9) is the one explicit root step, the same steps as
install bidder: it creates the account, writes the units, config.json and the requester record, enables linger and
starts the unit. The unit runs the same process, run requester --name %i --fg, with host-backend systemd-user from
config.json: its core places the requester's kine, front and kcp slot as user units of that account
(socialweb-kcp-req-<sha256(name)[:10]>-*) and its HostProcesses and OpenBao beside them, all in socialweb.slice.
sudo $SWC install requester --name me --dry-run # prints every step; writes nothing
sudo $SWC install requester --name me # account swc-requester, unit socialweb-computer-requester@me
sudo $SWC install requester --account swc-req-t1 --name me -- --atproto-oauth-qr # name the account; options after --
ST=/var/lib/socialweb-computer/swc-requester
sudo -u swc-requester $ST/bin/current/socialweb-computer run requester signin me --state-dir $ST # the sign-in URL on stdout
sudo journalctl _UID=$(id -u swc-requester) _SYSTEMD_USER_UNIT=socialweb-computer-requester@me.service -f # its log, as root
R=/var/lib/socialweb-computer/swc-requester/requesters/me/ready.json
sudo -u swc-requester kubectl --kubeconfig "$(sudo jq -r .kubeconfig "$R")" get marketrequesters.socialweb.computer app
- account:
swc-requesterby default;--account <name>creates that account when no install record and no user of that name exist; an existing account needs--adopt-account(refused otherwise, as for a bidder). The install record is/var/lib/socialweb-computer/install-<account>.jsonwith rolerequesterand the names --name(repeatable, comma-separated) names each instance; each is the unitsocialweb-computer-requester@<name>.service(Type=notify, Slice=socialweb.slice, Restart=always, Delegate=yes) with its state in<state>/requesters/<name>. The unit sends READY=1 only after the ready file is written, soTimeoutStartSecis the ready budget plus one hour for a person to sign in; a start that times out restarts and shows a new sign-in URL.installwaits for that start- options after
--are the MarketRequesterSpec options ofrun requester, parsed by the same code, written once per name into<state>/requesters/<name>/requester.json(the first start's options; a second install keeps the file and reports it FOUND). With no identity option the identity is--atproto-oauth-qr(D9):run requester signin <name>(as the account or root) reads the MarketIdentityrequesterfrom the requester's own kcp and prints itspendingAction.urlon stdout (phase and expiry on stderr,--jsonfor the object; nothing when Ready); drive it withscripts/oauth-signin.ts --url "$(... run requester signin me ...)".installprints both commands before it starts the unit. The unit's log has the same URL, but only root reads it: the account is a system account (uid under 1000), journald stores its user units' lines in the system journal, andjournalctl --useras the account opens no file; as root,journalctl --user-unitmatches root's own uid, so name_UID=<uid> _SYSTEMD_USER_UNIT=<unit> - an existing account with an app password (
-- --atproto-identity-mode pdsAccount --atproto-handle <h> --atproto-pds-url <pds>): the password never goes intoconfig.json, a flag, a unit or the requester record. After the unit started, as the account,socialweb-computer run requester password me --state-dir <state> < <file>reads it once from stdin and writes it into the requester's own OpenBao (identity/requester/keys, the SecretRef the MarketIdentity's keysRef names); the composer, waiting on "its account password", goes on config.json:host-backend systemd-user,state-dir,asset-cache,ready-timeout,network-timeout(andunit-prefixwhen given); never a password or a token- second install: every step FOUND;
upgrade --binary <new> --sha256 <sum> --state-dir <state>as the account switchesbin/current, runs the new binary'scheck-host -role requester(the role comes from the install record, never read as a bidder) and restarts every enabledsocialweb-computer-requester@unit;rollbackthe same way. The kcp units and the vault keep running across that restart; the core adopts them, so the DID and every Held contract survive - reboot: linger starts the user manager, and the enabled kcp units and the requester unit come up again
uninstall requester --account <a>(root) disables and stops the units, removes the unit files, linger and the drop-in, and keeps the state dir (the DID, the vault, the contracts) unless--purgecheck-host -role requester: the checks every role has (systemd, delegation, linger, cgroup v2, socket path, boot id, oomd); no kvm, no subids- proven (2026-11-01, this host as root, throwaway account
swc-req-t1, localRepo against an own market network's PLC):--dry-runprinted 33 steps; the install created the account and was active 25 s afterenable --now, ready file andkubectl get marketrequestersReady as the account; with no identity option the identity was oauthQr, AwaitingScan with a pendingAction URL, and the unit stayed activating (no READY before the ready file); a second install was all FOUND;upgradeas the account read the role from the record, restarted only the requester unit, kept the DID and left the kcp and OpenBao units adopted (same InvocationID); a restart ofuser@<uid>(the reboot path through linger) was Ready again in 10 s with the same DID;run requester passwordwrote the vault path;uninstallkept the state dir and the account,--purgeremoved both.install bidder --dry-runandinstall gateway --dry-runprint the same steps before and after this change. A real reboot and an OAuth QR install against prod bidders are the lead's runs
7b. Same account: a kubectl-applied HostProcess (no root, no new user)
When the account already runs a core (an installed requester, or a dev host instance), a second requester needs no root
and no new account: apply examples/infra/requester-hostprocess.yaml into
that core's kcp. The core places it as one more unit (placement supervisor) in socialweb.slice; it runs the same process,
run requester --name lr2 --state-dir <dir> --fg, which brings up its own kcp under that unit.
A=swc-requester; ST=/var/lib/socialweb-computer/$A # the account whose core places it
K=$(sudo jq -r .kubeconfig "$ST/requesters/me/ready.json") # that core's kcp (the installed requester me)
P=socialweb-hp-req-$(printf %s me | sha256sum | cut -c1-10)- # the HostProcess prefix of the installed requester me
sed -e "s|REPLACE-HP-PREFIX|$P|" -e "s|REPLACE-STATE-DIR|$ST/lr2|g" examples/infra/requester-hostprocess.yaml \
| sudo -u $A kubectl --kubeconfig "$K" apply -f -
sudo -u $A kubectl --kubeconfig "$K" get hostprocess requester-lr2 -o jsonpath='{.status.phase}{"\n"}' # Running once the ready file is written
sudo -u $A kubectl --kubeconfig "$K" delete hostprocess requester-lr2 # SIGTERM: stops its kcp and keeps its state dir
-
every command runs as the account (
sudo -u $A): the kubeconfig is 0600 to it, and the unit runs as it, so the state dir must be one it can write (here under its own state dir). As a normal account that runs its own core, dropsudo -u $Aandsudo, and use a dir of that account forREPLACE-STATE-DIR -
the unit must start with the core's HostProcess prefix (
<unit-prefix>hp-<key>-; an installed requester's key isreq-<sha256(name)[:10]>, a dev instance's is its short id: read it fromkubectl get hostprocesses), or the core refuses it ("is not under") -
no root prunes it: a root renders and checks only its own set's HostProcesses by name (
compose.SetWorkloads,compose.CheckWorkloads), adopts only objects labelled with its owner, andstatus.gconly counts an unowned HostProcess as an orphan; it never deletes one. The core adopts the unit by name and InvocationID across its own restart -
the sign-in URL:
run requester signin lr2 --state-dir <dir>, or the unit's journal (journalctl --user -u <unit>for a normal account; for a system account such as swc-requester only root reads it,journalctl _UID=<uid> _SYSTEMD_USER_UNIT=<unit>); a password never goes into the argv -
proven (2026-11-01, as test-0001, own market network, localRepo against the network PLC): applied into the core of an installed-form requester, the HostProcess was Running with its ready file after 18 s, in
socialweb.slice, and its kubeconfig read the MarketRequester Ready; a restart of that core left it adopted (same InvocationID) with the core's kcp, kine and OpenBao units;kubectl deletestopped it in under 1 s, removed the ready file and kept its state dir
Still planned in plan 0039:
- its live pass: ../examples/infra/mixed-prod.yaml (vm1, sbx1, agent1 pinned to one bidder, no policy field) by acceptance acceptance/0013-mixed-prod.md (item ACC39-MIXED-PROD; written, not yet run on prod)
8. Your Own Tenant Cluster: run requester --with-gateway
run requester --with-gateway (plan 0039 item C-GATEWAY-COMPOSE) composes the gateway into the requester's own kcp. One
identity: the MarketRequester's Requester rents the cluster's nodes under your DID and the default policy; there is no
second sign-in, no gateway PDS, no gateway Requester and no web app.
$SWC run requester --name me --state-dir "$ST" --with-gateway -- --atproto-oauth-qr > "$ST/me.log" 2>&1 &
until jq -e .gateway "$ST/requesters/me/ready.json" >/dev/null 2>&1; do sleep 1; done
$SWC run requester cluster me --cluster c1 --state-dir "$ST" # account + home workspace and the Cluster
until TK=$($SWC run requester kubeconfig me --cluster c1 --state-dir "$ST" 2>/dev/null); do sleep 2; done # once the Cluster is Ready
kubectl --kubeconfig "$TK" apply -f examples/infra/k8s-cluster.yaml
kubectl --kubeconfig "$TK" wait --for=condition=Ready pod -l app=quickstart-k8s --timeout=5m
kubectl --kubeconfig "$TK" logs deploy/quickstart-k8s # quickstart-k8s-ok
kubectl get infras.socialweb.computer # nodes-<hex>, in the requester workspace
kubectl --kubeconfig "$TK" delete deployment quickstart-k8s # the node scales to zero, its contract Released
- what it composes: a Gateway root
gatewayof profilerequesterwithspec.requesterRefnaming the MarketRequesterapp, in the requester's own workspace. It composes no PlanAccount, Pds, Requester, identity or web app of its own, only a nodes-only GatewayAPI (spec.nodesOnly): the NodeClaim binding and the meshd and node-agent downloads, nothing else is served. The NodeClaim controller writes the cluster's Infranodes-<hex>into the requester workspace, the requester runs its ComputeContracts as your DID withrequester-tangled-vouch, the node join bundles go to the requester's own vault (requester/contracts/<contract>/bundle, served by getSecrets), and the Infra record mirrors to your repo (computer.socialweb.infra/nodes-<hex>, Retain: never deleted) - the cluster provider runs in the
run requesterprocess and serves only the clusters of your DID; in a local market network--with-gatewayis refused when the network has a gateway child (dev up --with-gateway), whose provider serves every Cluster of the network's kcp - the cluster's k3s, kine, OpenBao, front and links are
HostProcesses of the requester's core (
kubectl get hostprocesses.socialweb.computer), with state under<instance state>/clusterprovider(the ready file'sgateway.clusters) - a Cluster lives in a home workspace
root:<account>:<cluster>of your DID:run requester clustermakes the account workspace, its AtprotoIdentity, the home workspace and the Cluster{account: <did>, running: true}; delete the Cluster there to tear it down - the ready file is written only once the Gateway is Ready too, with
gateway {workspace, name, clusters}in its first write, sorun requester clusterright afteruntil [ -s ready.json ]finds it;requester.jsonrecordswithGateway, so a restart keeps the gateway and the clusters - preflight: cgroup v2, user manager delegation, one subuid/subgid range and setuid newuidmap/newgidmap (the
check-host -role gatewayfixes); a dev build or a release build (k3s and external-secrets are embedded) - proven (2026-11-01, own market network,
--network, bidder policybidder-tangled-vouch, both vouches): Gateway Ready 3 s after the requester,run requester clusterReady in 17 s with 11 HostProcesses in the requester workspace, the NodeClaim's Infranodes-<hex>inroot:<requester>and its contract Held in 21 s (owner DID, attachedrequester-tangled-vouch, SecretsApplied GetSecrets), the pod loggedquickstart-k8s-ok, the node scaled to zero and the contract Released 2.5 min after the Deployment was deleted, the Infra record stayed in the repo; Ctrl-C and restart with nothing after--: the same DID, the gateway Ready 9 s after the start, the Cluster still Ready, k3s/readyzok, every cluster HostProcess adopted by unit name and InvocationID but the cluster OpenBao (restarted in place). The first node join of a fresh network waited about 4 min for the bidder's guest OIDC discovery to report its configured issuer (it reportedhttps://127.0.0.1:0until then) - a node on a remote bidder: the node fetches
socialweb-meshdandsocialweb-nodefrom<base>/downloads/<sha256>/<name>and checks the sha256 its contract pins (spec.nodeJoin.meshd,nodeAgent). The base is--node-download-base(envSOCIALWEB_NODE_DOWNLOAD_BASE), by default the public bucket the bidders already read guest images from, where CI publishes the pinned downloads tree by sha256 (thepublishjob, prefixdownloads/, content-addressed: an object is never replaced). With--networkthe default is unused and the node fetches from the requester's local Exposure, as the network's bidders share its host; a dev build whoserust/orcmd/socialweb-nodediffers from a published commit has pins the bucket does not hold. Proven on a local network (2026-11-01): with the flag the node contract'snodeJoinnames the bucket and the pins of the released tree, the node's bootstrap reached<bucket>/downloads/<sha256>/socialweb-node-x86_64(HTTP 404 until CI publishes it), and a guest fetched a published object from the bucket and matched its manifest sha256. Proven on prod (rc159, acceptance 0013 C): the node of clusterc1on hat-7 (a remote bidder) fetched both from the bucket and joined; the pod ran - the node reaches the k3s API and the cluster OpenBao over iroh: each is a dumbpipe
listen-tcpHostProcess of the requester's core whose ticket names the public n0 relay and the host's addresses, so a node on any host dials it through the relay when no direct path opens (a guest cannot reach its own bidder host's addresses through the fence, so the same-host proof already takes an internet path); the node's join bundle comes by getSecrets over the requester ingress, and the cluster OpenBao checks the node's token against the bidder's public OIDC issuer
Not Supported On Prod Today
| gap | where it is planned |
|---|---|
| an atirc agent from a local PDS identity: built, not yet proven against irc.fedfork.com (a pdsAccount identity is proven on rc161) | plan 0039 D-AGENT-IDENTITY |
vouch-bidder-operator.yaml on an installed bidder whose operator did not set --bidder-operator-signin, or before the QR page (qr.fedfork.com) asks for repo:sh.tangled.graph.vouch | operator setting; the QR page's scope |
deno on a prod bidder unless its operator ran run bidder --compute-kinds vm,deno or install bidder --compute-kinds vm,deno; pin such a bidder with bidderDids | operator setting |