Docs menu

SDK reference

Integration SDK

Two packages for a tool that runs agents, such as DSH. @layerfs/runtime manages a LayerFS installation; @layerfs/backend reads what a host shows in its own views. Agents don't import either.

What a host does

An integration host decides how its sessions map to LayerFS projects, starts Init, shows history and Workspace state, and keeps the installation tidy. Its agents still do their work through@layerfs/agent; a host passes them the LAYERFS_* settings.

A host needs toIt calls
Know what's installed and what's missingruntime.status
Import a folder as a project, with progressruntime.project.start_init, get_init
List and remove projectsruntime.project.list, remove
Draw a project's history and Workspacesruntime.project.get_state, backend.store.*
Show calls as they happenruntime.events.subscribe
Show a Workspace's changes, run a dev serverbackend.daemon.workspace.*, backend.daemon.process.*
Clean up after crashesruntime.sandbox.reclaim, sweep, list_leftovers, clean_leftovers
See what the home holds in Dockerruntime.sandbox.list (each Sandbox's scratch), runtime.volume.list, remove

@layerfs/runtime

The runtime opens on a LayerFS home, not on a project, so it works before the first project exists, after one is removed and while the worker is down. Agents' Bash calls use thelayerfs command, the checkout's api/agent/cli/ folder over @layerfs/agent; the runtime has no command of its own (its older lfs command is retired). Link it from the checkout like the other packages:npm install "file:$LAYERFS_REPO/api/runtime" "file:$LAYERFS_REPO/api/backend".

import { LayerFSRuntime } from "@layerfs/runtime";

const runtime = await LayerFSRuntime.open({ home, image, bundle });   // a LayerFS home, not a project
try {
  const { missing } = await runtime.status();                          // settings still missing, if any
  const job = await runtime.project.start_init("shop", "/src/shop", { timeout: 600_000 });
  console.log(await runtime.project.get_init(job.id));                 // { step, scanned, totals, imported, … }
  for await (const event of runtime.events.subscribe({ project: "shop" })) {
    console.log(event.actor, event.method, event.ms);                  // one line per call, from every process
  }
} finally {
  await runtime.close();
}
LayerFSRuntimeAnswers
LayerFSRuntime.open(settings)A runtime on one LayerFS home. A missing setting doesn't refuse: status().missing lists it
runtimeAnswers
runtime.status(){ platform, missing, docker, worker, disk, sweep }; sweep is the open-time sweep: null, { state: "running" } or { state: "done" | "failed", at, … }
runtime.close()Waits for the open-time sweep, then closes its project clients; Workspaces stay mounted. On Windows it drains and ends the worker it started
runtime.projectAnswers
runtime.project.list()[{ name, volume, image, source, created, initialized }]
runtime.project.start_init(name, folder, { image, timeout }?)A job. It checks the name, folder, image and timeout first, then counts the source, makes the volume and runs one Init
runtime.project.get_init(job){ step, scanned, totals, imported, project, error, ended }
runtime.project.get_state(name, since?)One project for a host's view: its history, Sandboxes and their Workspaces under one version; { kind: "unchanged" } when since is still current
runtime.project.remove(name, { confirm, stopSandboxes })Deletes the project's volume and home entry. confirm must equal the name; Busy while a container uses the volume unless stopSandboxes
runtime.workerAnswers
runtime.worker.start()macOS and Windows: starts or reuses the LayerFS home's worker, and stops no other container. Linux has no worker: refused InvalidInput
runtime.worker.restart({ confirm: "restart" })Restarts the worker. It cuts off every project's Store calls, so it needs the confirmation
runtime.sandboxAnswers
runtime.sandbox.reclaim(name){ reclaimed: [ids] }: the project's Sandboxes that closed themselves
runtime.sandbox.list_leftovers()Containers of this home that belong to no project, or were left stopped
runtime.sandbox.clean_leftovers(names)Removes the named leftovers, each once
runtime.sandbox.list()[{ name, project, sandbox, state, running, startedAt, finishedAt, image, ports, scratch, owned }]: this home's Sandbox containers, running or not; scratch is the bytes commands wrote outside every Workspace. It changes nothing
runtime.sandbox.sweep(){ reclaimed: { project: [ids] }, failed: [{ project, code, message }] }: reclaim for every project of the home with a stopped Sandbox. open runs it once unless sweep: false
runtime.volumeAnswers
runtime.volume.list()[{ name, size, containers, owner, project, live, removable }]: every Docker volume, with owner "this", "other" or "unknown"
runtime.volume.remove(names){ results: [{ name, ok, message? }] }: removes only volumes list marks removable (this home's, no project's, used by no container), one docker volume rm each
runtime.eventsAnswers
runtime.events.subscribe({ project, since }?)An async iterator of trace lines from every process in this home, each with its actor

start_init passes timeout unchanged to LayerFS.init, so an omitted one gets its 300 s. A failed Init removes the volume and home entry it made; an uncertain one (Unknown, or any failure on Windows) keeps everything and is recorded in init-failures.jsonl in stateDir. Init labels the project's volume layerfs.role=store, layerfs.home andlayerfs.project, which is how volume.list tells whose it is.

Settings

A missing setting doesn't refuse open; status().missing lists it.

SettingMeaning
homeRequired: the LayerFS home, an absolute folder
imageThe Sandbox image digest, sha256:…
bundleA sealed Linux build folder; macOS and Windows need it for the worker
daemonThe Linux daemon; default the bundle's
docker, volumesDocker Engine socket, and on Linux Docker's volumes folder
containerPrefixContainer name prefix; default layerfs-
traceDir, stateDirTrace files and the runtime's own records; default traces/ and .runtime/ in the home
sweepDefault true: open runs one sandbox.sweep in the background, reported by status().sweep
wslWindows: { distribution, user, node, home } of the WSL2 side

Platforms

PlatformHow the runtime runs
LinuxStore calls run in this process; there is no worker. Init installs the Store under Docker's volumes folder, so the process must be able to write it
macOSThe worker is one container per home, <prefix>worker (label layerfs.role=worker), running the bundle's real SDK. Init runs in a container of its own with the source folder mounted read-only. The worker's secret reaches docker run through its environment, never its command line
WindowsThe worker is a Node process in a WSL2 distribution; Init runs in the worker. close() drains and ends it

The worker route is open to any integration. On macOS and Windows the runtime hands its worker's endpoint to@layerfs/agent (useWorker, without changing the environment), so every client made afteropen in its process uses the worker; a client made before keeps its in-process binding. One worker per process: a second runtime of another home is refused. Another process, such as an agent's command, uses the worker when its LAYERFS_WORKER_ENDPOINT names the endpoint file, <home>/worker/endpoint.json; the integration that runs it sets that. The worker checks every file of the bundle's manifest.jsonbefore it listens.

@layerfs/backend

The backend wraps an open agent client. store.* calls read the project's Store and need no Sandbox;daemon.* calls need the Sandbox running, else NoSuchSandbox. Background processes run through the Sandbox's ordinary command launcher. Thelayerfs-sdk-observe skill covers these calls and the call trace.

import { LayerFS } from "@layerfs/agent";
import { LayerFSBackend } from "@layerfs/backend";

const layerfs = await LayerFS.open("shop");
const backend = LayerFSBackend.of(layerfs);                 // opens nothing of its own
const history = await backend.store.project.get_history();  // Branches, newest Commits, Layers, version
const storage = await backend.store.project.get_storage();
const proc = await backend.daemon.process.start(ws, "npm run dev");   // a background process in a Workspace
await backend.daemon.process.cancel(proc.process);
LayerFSBackendAnswers
LayerFSBackend.of(layerfs)A backend over an open @layerfs/agent client. It opens nothing and is refused once the client is closed
backend.store.projectAnswers
backend.store.project.get_history(since?)Branches, newest Commits, Layers and a version, for a history view
backend.store.project.get_storage()The Store's sizes: logical, canonical, stored, metadata, file, free and their ratios
backend.store.project.get_comparable(ref){ commit, root, exact }: the Commit with the same files as a Layer, so it can be diffed; or NotComparable
backend.store.project.list_overwritten(layer, pageSize?, pagePos?)What a publish overwrote on main
backend.store.branchAnswers
backend.store.branch.list_commit_storage(branch, pageSize?, pagePos?)Each Commit's new bytes
backend.store.fsAnswers
backend.store.fs.get_storage(ref, path)One file's sizes
backend.store.fs.list_revisions(ref, path, pageSize?, pagePos?)The Commits that changed one file
backend.daemon.workspaceAnswers
backend.daemon.workspace.status(ws){ base, baseLayer, branchHead, headMovedSinceMount, activity, overlay, branch }
backend.daemon.workspace.list_changes(ws, pageSize?, pagePos?)What changed in a mounted Workspace since its mount
backend.daemon.workspace.get_storage(ws)The Workspace overlay's sizes
backend.daemon.processAnswers
backend.daemon.process.start(ws, command)Starts a background process, such as a dev server, and answers its id
backend.daemon.process.list(pageSize?, pagePos?)Every Sandbox's background processes, each with its sandbox
backend.daemon.process.cancel(process)Stops one background process
backend.daemon.sandboxAnswers
backend.daemon.sandbox.get_log(sandbox, { lines }?){ sandbox, container, text, bytes }: the daemon's last lines (1 to 2000, at most 64 KiB)

No UI in LayerFS

LayerFS ships no UI and serves no HTTP. Its packages are calls that take data and return data; each integration builds its own routes, authentication and views on top of them. DSH's LayerFS tab is one such view, drawn fromruntime.project.get_state, the backend's reads and runtime.events.subscribe. SeeLayerFS × DSH.