Five records
That's the whole lexicon.
Suppose people want to do the same creative thing at the same time — cover a song, build on the same API, write about a prompt. They don't want to do it together; they want to do their own version, on their own schedule, with a shared deadline. Then see what everyone made.
That's a jam. atjam is the bookkeeping.
There are five records in the lexicon. Once you've seen all five, you've seen the whole system.
The projectat.atjam.jam
A jam is the project. The thing with a name. EPTSS is a jam. Ludum Dare is a jam. It has a name, maybe a description, a few links to related places.
Notice what isn't here. No deadline. No list of rounds. No roster. A jam is the container, not the event.
We also don't store an organizer field. Whoever wrote the record is the organizer — that's already implicit in whose PDS the record lives on. Authority comes from location.
The eventat.atjam.round
A round is one event inside a jam. EPTSS Round 29. Ludum Dare 48. This is where the deadlines live.
A round has an assignment (the prompt), a list of milestones (when does signup open, when's the submission due), and a list of which kinds of records count as a submission. We'll come back to that last one — it's where things get interesting.
Now look at the milestones. They're dates with labels: signup-deadline, submission-deadline, things like that. What's not there is a state field. No "open" or "closed" enum.
We let the dates speak. If today is before the deadline, the round is open. After, it's closed. Dates are honest; enums lie.
One more field on the round before we move on: joinMode. It declares how the round handles signups — one of "open", "hosted", or "network". Three named patterns. We'll come back to what they mean when we get to the other side of the signup.
The intentat.atjam.signup
Someone says "I'm in." That's a signup. It points to a round and optionally carries a note ("I'm going to do this on acoustic guitar, send help").
We don't store the participant's DID inside the record. The record is on their PDS. If it's on their PDS, they wrote it. That's how atproto works.
The invitationat.atjam.invitation
Now, suppose the organizer doesn't want to let just anyone in. A private jam. A "send me a tape first" arrangement. A friend-of-a-friend round that grows by word of mouth. We need a way to gate participation.
Here's the trick. The signup is something the participant writes — on their own PDS. The invitation is something an inviter writes — on theirs. The signup points back at the invitation. Both records together mean: yes, you're in. Either side alone doesn't mean anything.
You might recognize this shape from rpg.actor's give-and-receive records. The pattern is the same: each party writes their own side of the transaction, on their own PDS. Neither party can fake the whole thing. The transaction is the intersection.
This is the pattern I'd call Two-Sided Accounting. Each party owns their half. The truth is what both records say.
That joinMode field on the round is what turns this on. Three named patterns, each a real kind of jam:
- "open" — no invitation needed. Anyone can sign up. Like an open mic.
- "hosted" — only the organizer can invite. Only invitees can sign up. Like a dinner party with a guest list.
- "network" — the organizer can invite, and so can current participants. Invitations chain. Like a friend-of-a-friend network where each person can bring a plus-one.
The three modes vary along one axis: who has invitation authority. Nobody (open). The organizer only (hosted). The organizer plus the people they've vouched for (network). One dimension, three real social forms.
The order matters: the invitation comes first. The inviter writes "I invite this DID." Later, the invitee writes a signup that points back at the invitation. The signup is the acceptance.
For "network", there's a second field on the round called networkGate. It says how much skin a participant needs to have in the game before they can invite others. "signup" — just being signed up is enough. "contributed" — you have to have actually submitted something. The organizer chooses per-round. The chain grows as fast or as slow as they want.
One more thing about invitation: silence isn't rejection. If you never get an invitation to a hosted or network round, it just means you're not in. Maybe the organizer hasn't gotten to it. Maybe they decided no. The lexicon doesn't try to record that distinction — that's a conversation, not a data point.
The workat.atjam.submission
Now here's the clever part. Once you see this, the whole system clicks.
A submission doesn't contain your work. It contains a pointer to your work.
Your song lives on a music app. Your blog post lives on a blogging app. Your video lives on a video app. atjam doesn't try to be any of those — it just says: "this person submitted that record over there to this round."
Why split it this way? Because storing the work and coordinating around the work are two different jobs. atjam picked the coordination job. The other apps are better at storage. The song app knows how to play your song. atjam doesn't need to.
This is the pattern I'd call The Borrowed Reference: a thin record that points at where the real thing lives. It's what makes the network composable. Other apps host. atjam organizes.
What's not in the lexicon
If you read the JSON, you'll notice things missing.
No participant list on the round. To find out who's in, you ask the network: which signups point at this round? You don't store a roster; you derive it.
No "accepted" or "rejected" status on submissions. Anyone can write a submission. Whether it shows up in a reader's UI is the reader's decision, not the data's. (Signups got a two-sided pattern because access control is a different question from "is this good work." Curation of submissions lives in the reader.)
No round state. Just milestones. The reader computes the state from the dates.
These absences aren't oversights. They're the design.
Where the source lives
The five JSON files: tangled.org/natespilman.at/atjam.at.
Also published on the network as com.atproto.lexicon.schema records on did:plc:b5bxdq5jyv3tvkyscu42xr7u, with DNS authority at _lexicon.atjam.at.
The takeaway
Five records. The jam is the project. The round is the event. The signup is the intent. The invitation is the other side of that intent when participation is gated. The submission points at the work. The work itself lives elsewhere — atjam is the bookkeeping that ties it together.