A few notes about Lace
AI Use Disclosure
The ideas here are my own. I use AI tools in various parts of the writing process, including summarizing my notes and prompts, improving grammar, tightening wording, and checking whether an argument is clear. These posts are as much notes for myself as they are for anyone else who is interested.
2026-06-18 - terminology has become outdated; this post is left for posterity.
Well, have to keep blogging if I ever want to get into the habit. So today: a few notes about Lace.
Lace is me taking everything I learned from developing distributed systems over the years and trying to collapse it into one thing that is actually small enough to reason about.
The original problem is this annoying distributed systems shape where two sides need to figure out, at the same time:
- what I want to send
- what they want to send
- what they accept to receive
- what I accept to receive
And then every app grows its own little matrix of push, pull, list, get, write, permissions, source policy, target policy, auth rules, cache rules, and special cases. The configuration space explodes. You can make it work through blood sweat and tears, but its a full time job to understand the nuances of a configuration space well enough not to blow it up.
The thing Lace tries to do is merge that space into one question:
what is the set of accepted packets?
Not “am I pushing” or “are you pulling”. Just: these packets belong in this context. Then the exchange algorithm computes the difference between the two stores, moves the missing accepted packets, validates them, and repeats until it reaches the current fixed point.
That sounds almost too simple, but it changes the shape of the problem a lot.
Packets first
The durable unit is a packet. A packet is canonical bytes with a
stable hash. There are blobs for raw bytes, plex packets with
coordinates like Group, API, Key,
and TAI, and seal packets that carry a Verifier and Mark
over a plex.
So instead of trusting a database row because it came from a connection, Lace wants everything important to become inspectable packet facts after validation:
Have(P)means I actually have validated bytes for packetPField(P, Name, Index, Value)exposes packet headers and coordinatesVerifiercarries the verifier that checks a sealed packet’s Mark- packet links can point at support/evidence packets
The important bit is that offers from the other side are only claims. They can say “I have packet P and it has field Group=team”, but that does not become truth until the bytes arrive and validate locally.
Lacegrams
A lacegram is a little Datalog program over those facts. That is the language part: select facts, derive more facts, then say which packets are in the selected set.
The mergeable lacegram model currently has two views of the same human rule:
LocalSelect(P) :- Have(P), Field(P,'Group',GI,'team').
OfferSelect(P,S) :- Offered(P,S), Offer(P,S,'Group',GI,'team').
LocalSelect runs over trusted local packet facts.
OfferSelect runs over untrusted peer offer facts, just to
decide what might be worth requesting. Same idea, different evidence
worlds.
Then both sides contribute their active mergeable lacegrams. Lace deterministically merges them by intersection. If I say “team packets” and you say “packets marked by Bob’s verifier”, the shared selected set becomes “team packets marked by Bob’s verifier”.
After that, the merged program lowers into the exchange mechanics:
Serve(P) :- LocalSelect(P).
Want(P) :- OfferSelect(P,S).
Those names are internal machinery. The programmer-facing thing is still the selected packet set.
Interlace
Interlace is the exchange algorithm. Very high level:
- both sides announce their active mergeable lacegrams
- both sides merge them into the same selected-set program
- each side advertises packets it can serve from its validated local store
- each side evaluates which advertised packets it wants and is missing
- those packets are transferred
- received bytes are validated and become new local facts
- repeat, because the new facts may unlock more packets
That last step is the part I am most excited about. The algorithm naturally supports evidence closures.
Example: “I want to host everything for a group of people, but only if they have cryptographic proof they are members of this group.”
Another device can connect with the equivalent of:
I am a member of this group, here is my proof, and I am looking for everything marked by Bob’s verifier.
In Lace terms this can be modeled as packets plus rules. Membership is not a magical side channel. A membership packet is just another marked packet. The rules can select membership evidence first, and then use the validated membership facts to select more packets.
Roughly:
Member(V) :- Have(P), Field(P,'Group',GI,'team'), Field(P,'API',AI,'member'), Field(P,'Key',KI,V).
LocalSelect(P) :- Have(P), Field(P,'Group',GI,'team'), Field(P,'API',AI,'member').
OfferSelect(P,S) :- Offered(P,S), Offer(P,S,'Group',GI,'team'), Offer(P,S,'API',AI,'member').
LocalSelect(P) :- Have(P), Field(P,'Group',GI,'team'), Field(P,'API',AI,'chat'), Field(P,'Verifier',SI,V), Member(V).
OfferSelect(P,S) :- Offered(P,S), Offer(P,S,'Group',GI,'team'), Offer(P,S,'API',AI,'chat'), Offer(P,S,'Verifier',SI,V), Member(V).
So in round one you might fetch the membership proof packets. After
validating them, Member(V) becomes true locally. Then the
next evaluation can select the chat packets marked by those members’
verifiers. The system did not need a special “auth then sync” command.
It just iterated the selected packet set until no more currently
accepted packets were missing.
There is already a simpler test scenario for this shape: Bob
contributes Group = team, Alice contributes
Verifier = Bob, Bob starts with four packets, and only the
two packets that are both in team and marked by Bob’s
verifier transfer to Alice. That is small, but it shows the merge:
neither side had to implement the cross product manually.
Why this feels like cracking the code
The big deal, to me, is not that Datalog is fancy. The big deal is that the weird distributed negotiation space becomes a local validation problem plus a repeated set difference problem.
Each side evaluates the same exchange program, but with different local facts:
- my validated packets
- your advertised packet claims
- explicit runtime facts like peer identity, transport, time, etc.
Then a packet moves only when the sender currently serves it and the receiver currently wants it. After it moves, the receiver validates the bytes before believing anything about it. New validated packets can create new facts, which can select more packets, and the loop continues.
It is too early for me to claim it works as desired and can scale. But being able to define these requirements in the first place has really simplified my distributed reasoning.
Instead of building another protocol with separate push/pull/write/list auth paths, the question becomes: can I describe the accepted packet set? If yes, interlace can try to converge the local store toward that set.
There obviously needs to be a DSL on top to make this readable.
Nobody wants to hand-write a screen of
Field(P,'Group',GI,...) rules for every app. But the model
as a whole seems reasonably reasonable.
First written: 2026-06-10
Last edited: 2026-06-18