knotless: runs on an atlibs composition
A knotless repository picks an atlibs composition; knotless-run runs the repo's CI on it:
rent a VM on the socialweb.computer market → check out the commit → run the steps → release.
Status 2026-11-10: works end to end, live, on atlibs 0.3.0.
Where
| What | Where |
|---|---|
| Deployed | https://knotless.socialweb.computer (serve + pages), https://knotless-runner.socialweb.computer (runner, per-composition OAuth); deploy: this repo |
| Runner + Executor UI | fork johnandersen777.bsky.social/knotless, branch runner-atlibs-composition @ ed991eb5 (22 commits on upstream main; fork only, no PRs) |
| Design + decisions | that branch: docs/design/atlibs-compositions.md |
| The composition | bobvmbuilder.bsky.social/socialweb-computer-cicd |
| Its source | atlibs-socialweb-computer-requester: compositions/socialweb-computer-cicd.compose.ts → committed composition.json |
| Scratch experiment | branch runner-socialweb-computer (kept, not developed) |
git clone https://knotless.dev/johnandersen777.bsky.social/knotless
git -C knotless checkout runner-atlibs-composition
less knotless/docs/design/atlibs-compositions.md
How a run goes
push → knotless-run
getRecord com.fedfork.exec.binding/<repo key> (else /self) ← the owner's binding, pinned by CID
trust check: composition + every module publisher ∈ --trust-publisher
load composition at its CID (atlibs-host, in process, Rust)
prepare (once per owner) → vm.provision → workload.run (checkout + steps via the process contract) → vm.release (always)
→ dev.knotless.check success | failure
The composition (atlibs 0.3.0)
// compositions/socialweb-computer-cicd.compose.ts (excerpt)
const machine = c.instance("machine", requester, {
session: "requester",
input: { bidders, kind: "vm", cpus, mem, ssh_key: host("sshPublicKey"), shell: true, iroh: true, via: "iroh", release: false },
});
const workload = c.instance("workload", cicdWorkload);
for (const step of ["start", "write", "read"] as const)
c.route(workload, `computer.socialweb.cicd.process.${step}`, process_, `the job's ${step} on the VM`);
c.export("computer.socialweb.vm.prepare", machine, "bind_key");
c.export("computer.socialweb.vm.provision", machine); // the runner calls the exports
c.export("computer.socialweb.vm.release", machine);
c.export("computer.socialweb.cicd.workload.run", workload);
K=https://knotless.dev/johnandersen777.bsky.social
git clone -q $K/atlibs && git -C atlibs checkout -q 88b2ba8 # v0.3.0, the sibling compose.ts imports
git clone -q $K/atlibs-socialweb-computer-requester && cd atlibs-socialweb-computer-requester
node --test compositions/compose.test.mjs # fails if a composition.json isn't what its compose.ts writes
atlibs compose check compositions/socialweb-computer-cicd.composition.json
Routes reach the target's implements (no entry); instance input is typed by module.json config.
Bind a repository
Copy-paste block (validated): design doc § Bind a repository. Result:
com.fedfork.exec.binding/<repo key> in the owner's repo (an org's repo: the org's account; self = every repo).
Settings › Executor (repo or account) does the same from the UI.
Live evidence
| Run | Push | VM | Result |
|---|---|---|---|
| first green (OAuth per composition) | 7ebc5c5 | hat-9 | steps ok; release bug → fixed |
| release fixed | 00df7b3 | hat-9 | success 59 s, vm.terminated on its own, records deleted |
| atlibs 0.3.0 model | 861f3813 | hat-9 | success 57 s, released, records deleted |
final composition bafyreiag5yjb… | ef96ee9f | hat-7 | success 46 s, released, records deleted |
deployed runner, composition bafyreifzmo3b… (requester dab378f8, user-data c605eb61) | 325f96d | hat-7 | success 52 s, released (fc-lpfuqerea3de-ed3fca28), no round records in the owner's account |
Rows 1–4 as aliceoa.bsky.social/kc-ci-test, runner on hat-8; row 5 as aliceoa.bsky.social/kc-ci-demo on https://knotless-runner.socialweb.computer.
Decided
- NSIDs:
com.fedfork.exec.*(atlibs, sandboxed: binding, composition);computer.socialweb.*(external compute:vm.provision/release/prepare,cicd.process.*). - Rents with the repo owner's own session (they pay; their vouches count). OAuth per composition; app-password fallback.
- Binding lookup: getRecord at the repo key, then
self. NeverlistRecords. Precedence: workflow > repo > account. - Publisher allowlist (
--trust-publisher, DIDs), compositions pinned by CID; default net denylist (loopback, link-local, metadata, private ranges). - Workload modules run the steps over a generic process contract (
exec/start/read/write/kill/list); MCP is only an adapter. - The workload fetches the commit from the owner's PDS; private repos get a narrower read-only per-repo grant.
- In process Rust:
atlibs-host+atlibs-irohas rev-pinned crates; no Node, no CLI, no extra binary. - One attestation key per owner, not per composition.
- Compositions are written in code (TS / Rust), JSON generated + committed (atlibs 0.3.0).
Open
- Pulls / merge queue: refused (their merged commit is on no remote).
- OAuth session is per composition CID: a republish means signing in again (live runs used the app-password fallback after repins).
- Orgs that delegated before
c9b05996must delegate again to bind (repo:com.fedfork.exec.binding). - The runner doesn't log the release result; termination is seen in the bidder's log.
Runner deploy is manual: deployed as a systemd-managed container on the gateway (this repo).: fixed in the fork (/oauth/bothad norepo:dev.knotless.runnerKey6879ad7e, bots only, no one else's login behind); a runner's client-side warn on the 25 sawaitSpaceWriteslong poll fixed (25b8fb0c).- Shots don't cover the Executor page's lookup view.
- Workloads can fetch only
iroh://(covers the net denylist). - Upstream: none (fork only, by policy).
- The runner image needs
dumbpipe(0.40.1) and ansshclient:atlibs-irohreaches a rented machine with them. The fork's Dockerfile ships neither; this repo'sDockerfileadds both. - The fork's
atlibs-host/atlibs-irohturn onserde_json/preserve_orderfor the whole workspace:knotless-triage'squestions_from_labelsanda_decision_model_triages_issuesexpect sorted keys and fail. Fork-introduced; not fixed. - With those crates beside it, rustls had both ring and aws-lc-rs and knotless-serve's Jetstream panicked: fixed in the fork (
ed991eb5, ring picked inCtx::new).
Fork tests
cargo test --workspace alone fails ~100 tests, the same at the base: each test binary starts its own PDS with 32 accounts and the big ones run out (index out of bounds: the len is 32, then PoisonError). Run them as the Justfile does, against one shared PDS (Node 24, .node-version; zoekt-webserver on PATH for code search):
just pds & # harness/pds.mjs, accounts made as asked
just t --workspace --no-fail-fast
On hat-8 at ff309352: 728 passed, 5 failed: the two triage tests above, searching_code (no zoekt), binary_lifecycle and forks_pulls_reviews_and_merges (not chased). CI runs them through harness/cov.mjs against the spaces alpha.