Lace / docs

Runtime and application integration

A Lace runtime owns participants, validated record storage, interlace driving, and app-facing operations. This guide helps choose the smallest current runtime and grow from local memory to durable and connected use. Policy remains a separate input: it selects checked records for convergence.

For the shared cross-runtime contract, use Lace-100 · Developer Surfaces. This page is a path through that surface, not another API specification.

Current release posture

Public first contact currently uses a Lace source checkout:

git clone https://codeberg.org/rs0/lace.git
cd lace

The repository can build local native archives and an installable JavaScript package archive. These are local release artifacts; they do not imply that @lace/lace-js is published in the npm registry. See packages/README.md for artifact commands and contents. Pin the exact artifacts an application tests: Lace is an early draft with no compatibility guarantee.

Choose the smallest runtime

Application host Start with Current guide or companion
Rust, synchronous/local lace-single with Lace::empty_memory() First interlace
Rust on Tokio lace-tokio rust-tokio-tcp-fs
Browser @lace/lace-js/browser shared-todo-list
JavaScript without browser storage @lace/lace-js memory runtime First interlace and the @lace/lace-js package guide
Node with durable storage @lace/lace-js/node @lace/lace-js package guide
Python application or automation repository-local Python package First interlace and the Python runtime guide
Persistent interlace endpoint laced laced(1) and laced.conf(5)
Record-oriented shell work lace CLI CLI guide

Python remains active work in progress; its README identifies implemented and not-yet-implemented surfaces. Do not choose it on the assumption that it already matches every transport available in Rust.

laced is a persistent Lace participant and interlace endpoint. It is not a static-file host, application command server, or generic database server. Serve browser assets and application APIs with ordinary application infrastructure.

Step 1: prove the local record model in memory

Before adding a filesystem, browser persistence, or transport, prove one useful application action locally:

  1. create a validated Record;
  2. store it through public store porcelain;
  3. select it with a small Interlang policy;
  4. read the selected view with list or get; and
  5. include one valid but unselected record as a boundary check.

The first-interlace tutorial extends that local proof to two memory participants. The smaller quickstart-local companion focuses only on record construction and store/list/get in JavaScript and Rust.

Normal application code uses these shared operations:

store(target, records)
list(target, selector)
get(target, selector)
forEach(target, selector)
boundedInterlace(first, second, policy)
openInterlace(first, second, policy)

Bindings use language-appropriate spelling, but the semantics come from Lace-100. Bounded calls return a terminal plus ordered observations; list and get preserve partial captures. Open handles use next_event/nextEvent as their one-event primitive, while iteration and run_until_fixed_point/runUntilFixedPoint are thin consumers. Observer streams yield owned RecordSaved records plus FixedPoint and Closed. These operations are interlace-backed porcelain; they are not raw store insertion or iteration.

Step 2: move the same model to durable storage

Choose durability only after the record shape and selected view work in memory:

Host Durable choice
Native Rust Lace::filesystem(...)
Browser Lace.opfs(...); use openBestEffort(...) only when an explicit demo fallback is acceptable
Node Lace.filesystem(...) from the Node entry point
Python Lace.filesystem(...), within the runtime’s current support
Standalone service a laced DataDir

A store change should not change the application record family or turn policy into a database query language. Test close/reopen behavior and verify the same public selected view after reopening.

For a complete native durability companion, run:

cargo run --manifest-path examples/rust-tokio-tcp-fs/Cargo.toml

It uses two filesystem-backed Tokio Laces and TCP/ILTP. The storage and transport are more realistic, but the participant, record, policy, and porcelain model is the same as the memory tutorial.

Step 3: connect participants

Use direct in-process interlace while both participants are in one runtime. Use a connected peer handle when another process or host owns one participant. Connected addresses and transport behavior are defined by Lace-060 · ILTP.

Policy belongs to the operation, not the participant handle:

Participants Policy argument
Two local Laces Used for both operands
Local Lace and connected peer Local operand; the peer supplies the other

Current convenience connectors vary by runtime:

Runtime Connected client routes
Native Rust tcp:, unix:, ws:; quib: when lace-tokio/quib is enabled
Browser and Node JavaScript ws:, wss:
Python ws:, wss:

lace-tokio opens client handles with Lace::connect(...).await. For a server, the application binds and accepts its listener, adapts one established stream as AcceptedIltpTransport, constructs a one-use peer with LaceHandle::from_accepted_iltp(...), and spawns an ordinary interlace. Lace does not own listener admission or task supervision. The synchronous lace-single binding uses the same client constructor without .await: Lace::connect(...).

ILTP can be hosted over other standard byte carriers, including stdio, but a protocol binding does not imply that every runtime supplies a high-level connector for it.

Choose operation lifetime deliberately:

Both use the same policy and checked-record semantics. A fixed point means neither side requested another record in the settled round; it does not mean the stores are identical.

Add connected operation in this order:

  1. run the policy between two local memory participants;
  2. use durable participants if the application requires them;
  3. replace one direct participant with a connected handle;
  4. handle typed completion, cancellation, timeout, and transport failures; and
  5. add open operation only if the application needs continuing convergence.

The browser/daemon shape is demonstrated by shared-todo-list. It deliberately uses ordinary static hosting beside a WebSocket laced endpoint.

Step 4: operate laced only when a service is needed

Use laced when a persistent waypoint or background participant is actually part of the application. A minimal deployment has one data directory, one explicit listener or sync job, and ordinary Interlang/Datalog policy.

Before running it beyond a local smoke test:

  1. read laced(1) and laced.conf(5);
  2. validate the configuration with laced check-config;
  3. keep static files and application command APIs outside laced;
  4. choose transport proof behavior explicitly; and
  5. test one bounded interlace before service-manager hardening.

Assistant-driven operational work can use the repository’s setup-laced skill, but the manpages and numbered specs remain authoritative.

Keep runtime and policy changes independent

A useful review question is: did this change where records live and how peers meet, or did it change which checked records the policy selects for convergence?

Continue with Interlang policy authoring for the latter two. For larger implemented application shapes, browse incubator/, but treat those as experiments rather than one architecture to copy wholesale.