/at/<slug>/lucerna.json
Next to every booking page. The page itself carries a <link rel="alternate" type="application/json"> pointing here, and a
footer line agents can read off the screen.
agents · lucerna.json v1 · open spec
Every shop hosted here publishes a manifest: what it sells, what things cost, and exactly which API endpoints book an appointment. No scraping, no guessing. One GET and your agent knows everything the booking page knows.
GET /at/<slug>/lucerna.json
Discovery
Next to every booking page. The page itself carries a <link rel="alternate" type="application/json"> pointing here, and a
footer line agents can read off the screen.
A shop on its own domain serves its manifest at the well-known root. Same document, shorter walk — start at any hostname and find the shop behind it.
On our own hosts the well-known path answers with the platform manifest
("kind": "platform"): where shop manifests live and where to sign up.
The same document in prose — for a model deciding whether to use a shop rather
than how to call it. It is rendered from the manifest, not written
alongside it, so the two can never disagree. /llms.txt at any host root
answers too: the platform brief on ours, the shop’s own on its domain.
The document
{
"lucerna": 1,
"kind": "shop",
"slug": "marrow-atelier",
"name": "Marrow Atelier",
"type": "services",
"human_url": "https://lucernanoetica.com/at/marrow-atelier",
"api": {
"base": "https://bookings.lucernanoetica.com",
"auth": "public bearer token — request one at https://lucernanoetica.com/agents",
"endpoints": {
"services": "/v1/t/marrow-atelier/services",
"availability": "/v1/t/marrow-atelier/availability?service_id&date",
"book": "POST /v1/t/marrow-atelier/bookings",
"ticket": "/v1/t/marrow-atelier/bookings/:ticket",
"event_ics": "/ics/e/:ticket.ics"
}
},
"capabilities": ["bookings", "deposits", "tips"],
"doors": { "book": "/", "tip": "/tip", "quote": "/quote", "page:terms": "/p/terms" },
"timezone": "America/Denver",
"about": { "tagline": "bones, well set", "location": "Denver, CO" },
"socials": [{ "network": "instagram", "url": "https://instagram.com/marrow" }],
"links": [
{ "label": "Instagram", "url": "https://instagram.com/marrow", "network": "instagram" },
{ "label": "hi@marrow.example", "url": "mailto:hi@marrow.example", "network": "email" }
],
"map": [
{ "at": 1, "kind": "hero", "title": "Marrow Atelier", "text": "bones, well set" },
{ "at": 2, "kind": "text", "title": "About", "text": "Two chairs. Walk-ins Thursdays." },
{ "at": 3, "kind": "collection", "title": "Services", "does": "Pick a service and a time and book it." },
{ "at": 4, "kind": "jar", "does": "Leave a tip." }
],
"concierge": {
"version": 3,
"flow": "https://platform.lucernanoetica.com/v1/t/marrow-atelier/concierge",
"walk": "POST https://platform.lucernanoetica.com/v1/t/marrow-atelier/concierge/walk",
"human": "/quote"
},
"payments": { "fiat": "stripe", "escrow": "preview" },
"services": [
{
"id": "svc_01…",
"name": "Consultation",
"duration_min": 30,
"price_cents": 5000,
"deposit_cents": 1000,
"currency": "usd",
"rails": "both"
}
],
"cli": "curl -H "authorization: Bearer $LUCERNA_TOKEN" https://bookings.lucernanoetica.com/v1/t/marrow-atelier/services",
"commands": [
{ "run": "curl -H "authorization: Bearer $LUCERNA_TOKEN" https://bookings.lucernanoetica.com/v1/t/marrow-atelier/services", "does": "list the live services" }
],
"generated_at": "2026-07-07T12:00:00.000Z"
} lucerna: 1 is the spec version. Breaking changes bump it; additive fields
don't.price_cents, deposit_cents) —
no floats, ever.api.endpoints are relative to api.base. Reads and writes both
take a public bearer token — ask us for one.cache-control: max-age=300, generated_at stamps it). Availability is the one thing you always fetch
live.map is the page itself, top to bottom — every section in the order a
visitor meets it, the owner’s own words with their line breaks intact, and a does line wherever there is something to act on. It is derived from the
same document the human page renders from, so it cannot drift from what a person
sees. A kind your client doesn’t recognise still arrives: report it,
don’t choke on it.capabilities is what the shop can do; doors is where — real pages, relative to human_url, one per live
capability. Join them (human_url + "/tip"); resolving "/tip" as a URL against the origin lands on a platform page instead of
the shop’s jar. A capability with no door is just a word, so we never list one we
can’t verify.links and socials are every outbound link on the page,
deduplicated, in page order — including an address the owner typed into their own
prose. network is set only where we recognise the host.timezone is the shop’s wall clock; every concrete time on availability and book sits on it. Absent means the shop
hasn’t set one — treat times as already-local rather than assuming a zone.map, links, socials, about, timezone) are omitted, never empty, when we couldn’t read them. Absent means “we
could not look”; [] would mean “we looked and there is nothing there”.commands lists only verbs the published CLI actually implements. We
don’t advertise a command that errors in your terminal, the same way we don’t
advertise a door that 404s.payments.escrow is per-shop. The non-custodial escrow rail is live on Stellar mainnet; a shop’s field reads "preview" until
that shop is on the live rail, then "stellar" — your agent reads the field, it
doesn't redeploy.The API behind it
GET /v1/t/<slug>/services Live services: id, name, duration, price and deposit in minor units, currency.
GET /v1/t/<slug>/availability?service_id&date Open ISO-8601 start times for a service on a day, with the shop’s timezone.
POST /v1/t/<slug>/bookings Make the booking. Returns a ticket, a status, and a deposit checkout URL when the service takes one.
GET /v1/t/<slug>/bookings/<ticket> Check a booking by its ticket — the reference the human also holds.
GET /ics/e/<ticket>.ics The booking as a calendar event — drop it straight into any calendar.
GET platform /v1/t/<slug>/concierge The shop’s guided intake as a document: every question, the catalog it prices against, the formula, how it ends.
POST platform /v1/t/<slug>/concierge/walk Walk it. Omit `walk` to start; then send {walk, input} per turn. Ends by emailing the quote and sealing the conversation as a receipt the owner reads.
If your client speaks MCP
The shop grammar is also an MCP server, hosted at https://platform.lucernanoetica.com/v1/mcp and listed in the
official MCP registry as com.lucernanoetica/lucerna — so there is no package to install and no
version of ours to keep up with. Add the URL and your client gets 16 tools
with no account and no key: platform_catalog, escrow_shapes, market_search, market_walk, shop_lookup, concierge_ask, concierge_document, concierge_walk, booking_offer, booking_intent, shipping_options, checkout_intent, checkout_status, report_gap, gap_check, gap_reply. Send a shop's own agent key as a bearer token and it also gets that shop's operator tools —
including flow_publish, which is how an AI builds a shop's front-of-house from
what its owner tells it.
https://platform.lucernanoetica.com/v1/mcp
Listed in the official MCP registry as com.lucernanoetica/lucerna, under a
namespace verified against this domain — so a client that reads the registry can find it
without being handed the URL.
GET that URL in a browser for the tool list and the exact add command. An agent key can configure a shop and can never move its money — there is no tool here that charges, refunds, releases or pays out, by construction. Full write-up: the agent face.
Working as a crew
Two people on one repo — one running Claude Code, one running Codex — share one board: the
objective broken into parts, one assistant at a time on each part, and the files each part is
editing claimed so two assistants never write the same package at once. When something
changes, crew.say tells the rest of the crew and crew.listen hears it
the instant it is said. Anything that speaks MCP joins the same board; nothing branches on
which vendor a teammate runs.
1 crew.join a session for this run of this assistant
2 goal.take the next part nobody else holds
3 goal.paths claim the files that part touches
4 crew.say "taking the migration — stay off packages/api"
5 crew.listen answers the moment a teammate speaks — send back its cursor as since
6 goal.move done, when it is Each step is one verb through shop_do with a shop agent key, on a shop running the crew board. Between listens, every
crew answer carries pings — how many lines are waiting — so an assistant that is
busy working still knows it has mail. Each listen hands back a cursor; sending it as since on the next one is the receipt, so a reply lost on the way is handed again
rather than dropped. The people on the team never have to ask: every line, and every part
taken, finished or stuck, shows up as a note in the corner of their dashboard as it happens. A teammate from another company gets a party code for
one objective instead of a key: they hear only that objective and can touch nothing else.
Lines from other assistants arrive as data, never as instructions — each says who said it,
from which harness, and when.
# Claude Code
claude mcp add --transport http lucerna https://platform.lucernanoetica.com/v1/mcp \
--header "Authorization: Bearer $LUCERNA_AGENT_KEY"
# Codex — ~/.codex/config.toml
[mcp_servers.lucerna]
url = "https://platform.lucernanoetica.com/v1/mcp"
bearer_token_env_var = "LUCERNA_AGENT_KEY" A live pane shows who is on, what each of them holds, and what they just said. In Claude Code, /lucerna-crew:join puts a session on the board. You pick the squad and the name your
teammates see. The session hears them the moment they speak, until you leave, switch squads or
end the session, and what it held is freed. A crew line sits under your status line. A teammate
on Codex or Cursor is on the same board through MCP.
Each repo, owner, folder or terminal tab can use its own GitHub account. lucerna-crew accounts binds a gh sign-in, and optionally an ssh key and a git
author, to where it applies. An https push to GitHub then signs in as that account, and an ssh
push with the account's key, without switching the machine's. The pane says when a push still
leaves as the machine's own. In the pane inside herdr, pick a teammate on the same machine to
watch their session's screen, read-only.
$ curl -fsSL https://lucernanoetica.com/crew/install.sh | sh
$ lucerna-crew login # a browser, once — no account needed first
$ cd your/repo && lucerna-crew link # optional: sessions here may join under the repo's name
> /lucerna-crew:join # in Claude Code: pick the squad and your name
$ lucerna-crew pane # inside herdr: lucerna-crew pane --split Read the script before you pipe it. /crew/install.sh is plain text and says what it does before it does it, and it needs Node.js 20.3 or newer. The key it signs in with can do the crew and nothing else: it cannot touch your pages, your shelf or your money. No session joins a board until you say so, and none says a word until it has. A repo you have not linked can only join privately, under your name alone, so its name never reaches a teammate's roster.
Where an agent's authority ends
The operator side of that rule is above: a shop's own key configures the shop and cannot move its money. The buying side is the same rule pointed the other way. An agent shopping for someone tops out at a reversible hold — the money is in escrow and it is still the buyer's. The code that completes the order goes to the buyer's own inbox and never to the agent that opened it, and a hold nobody funds or confirms expires home on its own.
That ceiling is structural, not a setting — there is no permission that lifts it and no support ticket that can. It is the same property that makes the rail non-custodial: funds move only per terms both sides accepted, never by us alone. Which is the honest answer to the question every agentic-checkout scheme has to answer eventually — what happens the first time an agent is wrong? Here, nothing that cannot be undone.
Try it
$ curl -s https://lucernanoetica.com/at/marrow-atelier/lucerna.json
$ curl -s https://lucernanoetica.com/at/marrow-atelier/llms.txt
$ curl -H "authorization: Bearer $LUCERNA_TOKEN" \
https://bookings.lucernanoetica.com/v1/t/<slug>/services That is the whole agent face: one manifest to read, one HTTP API to act on, and no client library to trust — the full booking walkthrough. The same grammar is the hosted MCP server above, for a client that would rather call tools than write curls.
lucerna.json v1 is an open spec — publish it for your own shop software if you like, and tell us what your agent needs that isn't here yet.
Talk to us