---
title: "ADR 0014 — The product is called kmem, and the plumbing keeps the name signpost"
type: decision
tags: [signpost, adr, decision]
proposed_by: tok_bc09b340db294c709763789154d8113d
model: kmem-docs-publisher
extracted_at: "2026-10-08T22:39:50.890Z"
---
# ADR 0014 — The product is called kmem, and the plumbing keeps the name signpost
**Status** Accepted · **Date** 2026-08-26 · **Relates to** [ADR 0003](./0003-source-preserving-markdown.md)
(`sp://` is inside customer Markdown), [ADR 0013](./0013-multi-tenancy-supersedes-single-project.md)
(`SIGNPOST_ROOT_DOMAIN`), `brand/DECISIONS.md` D1 to D7, `DESIGN_IDENTITY.md` §11
## Decision
The product is called **kmem**, always in lower case, one word, in every position — the first word
of a sentence, a heading, a title, a button label and a `
` element included. The strings
`Kmem` and `KMEM` appear in no text a person reads.
`kmem` is short for knowledge memory. That gloss is stated once, in `PRODUCT.md` §1 and in the
documentation, and never expanded again. There is no acronym and no backronym.
The visual identity does not change. The mark, the favicon in all five files, the app icon, the
maskable icon, the monochrome icon, both Template PNGs, every waymark, the colour tokens and the
three typefaces are untouched. `brand/DECISIONS.md` D1 to D7 all survive intact. The mark is
already a `k` whose diagonals have been moved off the stem and squared, so the wordmark reads as
`k` twice — once as a glyph, once as a mark — and no shape had to be redrawn.
## The line that decides every case
> **Rename a name a person reads as a word. Never rename a name a machine resolves as an address.**
The second-order test, for the cases where the first is arguable: *is this string an argument to a
Cloudflare API call, or a byte inside a customer's file?* If it is either, it is plumbing. Plumbing
keeps the name `signpost` forever, and that is not an embarrassment. It is a deployment identifier,
and nobody is embarrassed by a database called `postgres`.
### Renamed, because a person reads it as a word
The wordmark and every social asset. The landing page's title, footer, navigation and band copy.
Every value in `apps/web/src/locales/en.json` that named the product. The matching copy tables in
`design-system/specs/`. The MCP server's `initialize` name, `SERVER_NAME`, because a person sees it
in their assistant's connection list. The CLI binary. `SIGNPOST_PROJECT_NAME`'s **value**. The
webmanifest's `name`, `short_name` and `description`. The `.mcp.json` key the Quick Connect join
script prints. Every refusal string and every tool description.
### Not renamed, and each is argued individually
| Thing | Why not |
| --- | --- |
| The Worker name `signpost` | A rename creates a **new** Worker. Durable Object namespaces are scoped to the script name, so `NoteDoc`, `IngestAgent`, `GitAgent` and `RATE_LIMITER` all lose their stored state. Every live editing session's document history is in a `NoteDoc`. This is data loss dressed as a rebrand |
| D1 `signpost` | D1 has no rename. A new database means a full export and import at the current schema, for no user benefit. Nothing reads the name; the binding is `DB` |
| R2 `signpost-notes` | R2 has no rename, and this bucket **is** the ground truth. Copying every object to satisfy a logo change is the worst trade available |
| Vectorize `signpost-chunks` | No rename. A new index is a full reindex, which is the one action `AGENTS.md` names as near the spend gate |
| The queues, and their dead-letter queues | Queue names are account-scoped. A rename orphans every in-flight message, and the index queue is what invariant I4 depends on |
| The `SIGNPOST_*` variable names, 35 of them | The prefix is a namespace, not a brand. `SIGNPOST_ROOT_DOMAIN` alone has 219 references. A mechanical rename has no user-visible effect and one real failure mode: a missed reference reads `undefined`, and `domain.ts` then throws a fix message naming a variable the operator never set |
| The `sp://` ref scheme | **This is inside customer Markdown.** ADR 0003 and invariant I5 say the R2 bucket plus a git clone is everything, readable in any editor. A scheme change is a rewrite pass over customer files, which is exactly the lossy round-trip ADR 0003 refuses |
| The `sp_` and `sp_inv` token prefixes | A live credential in customer chat histories. A prefix change forces two parsers for one concept |
| `` and `` | The worst one. These markers are already in customer `AGENTS.md` files and committed to customer git repositories. Change the string and every existing region becomes unfindable, and the next sync appends a **second** region beside the orphan. The product name *inside* the region is content and re-renders on the next pull; the markers are addresses and never do |
**Amended 2026-09-11 (I-25), for the managed markers only.** The row above states
the failure correctly and draws the wrong conclusion from it: an address can be
renamed if the old address is still read. `services/agents-md.ts` now emits
`` and `` and finds all four
spellings, so a file that already carries the old pair is located by it, syncs
by it, and never gains a second region. A projection still changes no byte
outside the region, so an existing file keeps its own markers until a person
edits the two comments. The `signpost:link` markers are untouched and the row
above still holds for them.
| `signpost://brief`, the MCP resource URI | A scheme is an address a machine resolves, the same category as `sp://` |
| The `.signpost/` directory and `.signpostignore` | Files in the customer's own repository |
| `signpost` as a reserved subdomain | It stays reserved forever, so that no organization can claim the address the product answered on before the rename. `kmem`, `www`, `app`, `api` and `docs` join it |
| The `sp-` CSS prefix, `SignpostNav.tsx`, `signpost-scene.ts`, `@signpost/*` package names, `Signpost*` TypeScript type names | Never rendered to a person as words. The component draws a signpost, which is what the thing on the page is |
## How this product refers to its own past
**A dated record is never revised, and a rename that rewrites one is a falsification.** So:
- `docs/ADR/**` and `docs/TAXONOMY.md` — kind `decision`. Never edited after the date on them.
They say "Signpost" and they keep saying it.
- `docs/studies/**`, `docs/launch/**`, `docs/gates/**` — kind `evidence`. Dated measurements and
verbatim transcripts of real sessions. Editing a transcript to change a product name is editing
evidence. They keep the old name too.
- Everything of kind `reference`, `state`, `index` and `convention` describes the system as it is
today, so it carries the current name.
This ADR is the one place that joins the two. A reader who lands on a document written before
2026-08-26 and finds "Signpost" is reading the name this product had until that date, and this
entry is what tells them so. `docs/CONVENTIONS.md` points here for exactly that reason.
## Alternatives rejected
**Renaming the plumbing as well, for consistency.** Consistency between a bucket name and a logo
buys a person nothing and costs a migration with downtime, a full reindex, and a rewrite pass over
stored customer Markdown. The table above prices each one.
**Adding a `km://` ref scheme.** `packages/core/src/ref.ts` already shows the only acceptable
pattern for a second scheme: `memory://` is *accepted* on parse and *never emitted*, so a Basic
Memory import keeps working with no rewrite. But one implementation of each concept is a rule here,
and two emitted schemes would be two. `sp://` is the address forever, and the marketing never
prints one. A ref is not a brand surface.
**Keeping mixed case, `Kmem`, at the head of a sentence.** A rule with an exception is where mixed
case leaks in, and mixed case is the thing that looks amateur. `iPhone` and `eBay` hold their lower
case at the head of a sentence; so does this. The wordmark is set in Overpass 700 lower case, and a
capital `K` in copy contradicts the logo on the same page.
**Editing the dated records so that the whole repository says one name.** That is the falsification
this ADR's previous section refuses.
## Consequences
- `apps/web/test/brand/name-casing.test.ts` is a source scan and refuses `Kmem`, `KMEM`, `Signpost`
and any domain in the copy a person reads. It allowlists exactly two strings and names each.
- Every locale value and its `design-system/specs` copy-table row must change in one commit.
`apps/web/test/{editor,onboarding,sharing}/locales.test.ts` compare them cell for cell and fail on
an equality rather than on a missing key.
- The CLI ships two `bin` entries for one release, `kmem` and `signpost`, so a dogfood install does
not break mid-week.
- `NAMED_PROGRAMS`/`BUILDS_URLS` in `routes/quick-connect/negotiate.ts` carries both user-agent
fragments. A user-agent lives in a binary somebody already installed.
- The domain does not move in this change. `kmem.app` is not on the Cloudflare account.
`SIGNPOST_ROOT_DOMAIN` still reads `sarani.app`, and it is still the one variable that decides the
tenant root, so the switch stays one variable, one DNS record and one route.
- No copy names a domain. The landing page reads `ROOT_DOMAIN` from
`apps/web/src/landing/paths.ts`, which is the static site's single copy of the server's value.
## Amendment, 2026-09-17 — the ref scheme is `kmem://`
The owner asked for this: "sp://products/ata-jewelfx we need to change the addressing scheme to
kmem:// everywhere we are not signpost anymore."
This amendment replaces the `sp://` row of the table above and the rejected alternative "Adding a
`km://` ref scheme". The 2026-09-11 amendment for the managed markers gives the rule: an address
can change if kmem still reads the old address.
**What changes.**
- kmem writes every ref as `kmem://{permalink}` or `kmem://{permalink}#{block}`. This includes API
fields, MCP and Quick Connect results, tool descriptions, the briefing, the Copy doc reference
action, the Ask prompt and each audit `target` that kmem writes from now on.
- `parseRef` reads `kmem://`, `sp://` and `memory://`, and gives the same ref for each. `formatRef`
writes only `kmem://`, so kmem still emits one scheme.
**What does not change.**
- kmem does not rewrite a person's Markdown. A note that says `sp://` keeps its bytes, its links
still open, and its relations still resolve. ADR 0003 and invariant I5 still hold.
- No migration rewrites a D1 row. An audit row written before this date keeps `sp://`. A migration
cannot close the gap: the old Worker writes `sp://` rows until the new one is live, and a reader
that knows one scheme treats a row in the other as a row that names no note. The audit filter
shows such a row to every member, which is a disclosure (invariant I7).
**How the two schemes stay safe.**
- `packages/core/src/ref.ts` is the only source file that spells `sp://`. Every reader of a stored
or pasted ref uses `refSchemeLength`, `withoutRefScheme`, `storedRefsFor` or `STORED_REF_SCHEMES`.
`apps/worker/test/invariants/one-ref-scheme.test.ts` is a scan that refuses a second spelling in
`apps/*/src` and `packages/*/src`.
- `auditVisibleTo` treats a target in either stored scheme as a note address.
`test/invariants/read-scope.test.ts` holds a legacy `sp://` row of a private doc, with and without
a block fragment, to the answer a `kmem://` row gets.
- `GET /api/audit` shows a legacy target as `kmem://`. The stored row does not change.
- The `sp_` token prefix and the `signpost://brief` resource URI are not refs, and this amendment
does not change them.