Skip to content

Publish a mem

A mem is the packaged unit of sharing: a whole typed model — entities, relationships, and the schema they conform to — sealed into one .mem archive. Publishing puts that archive on the memstead.io registry under your GitHub handle, where anyone can install it with one command. This guide walks the first publish end to end.

You need a workspace with a mem worth sharing (see Getting started) and a GitHub account. Nothing else — no registry signup; authentication is GitHub Device Flow, triggered automatically on first use.

The registry renders each mem with a one-line description embedded in the archive. Set it before publishing (an empty string clears it):

Terminal window
memstead mem set-description recipes "Worked example: a tiny cookbook mem"

Versions come from the mem config too — seeded as 0.1.0 at init. Both setters (mem set-description, mem set-version) echo the old and new values back as they persist them, and the dry run in the next step shows the version that would ship.

--dry-run assembles and resolves everything — mem, version, scope, archive size — but POSTs nothing and mutates nothing:

Terminal window
memstead publish --mem recipes --dry-run
# Dry run — would publish
- Mem: `recipes`
- Version: `0.1.0`
- Scope: derived from your GitHub login
- Archive: 3153 bytes
- Registry: https://memstead.io
Nothing was published and nothing was changed.

--mem <name> names which mem to export-and-publish; it works in every workspace shape. (A single-mem folder workspace can also run bare memstead publish, which wraps up the surrounding folder.) The scope is not chosen by you — the registry derives it from your GitHub login at upload time, so the dry run can only tell you it will.

Same command, without the flag:

Terminal window
memstead publish --mem recipes

On first use this starts GitHub Device Flow: the CLI prints a code, opens the verification page, and stores the resulting token in ~/.config/memstead/credentials — subsequent publishes are silent. On success:

# Published github:dasboe/recipes v0.1.0
- URL: https://memstead.io/v/github:dasboe/recipes

You can also log in ahead of time with memstead login, or run non-interactively by setting MEMSTEAD_TOKEN (a GitHub token) — CI has no TTY for the device flow.

That’s what you put in your README. Anyone pulls your mem into their own workspace with:

Terminal window
memstead install github:dasboe/recipes

The installed mem mounts read-only: its entities and schema structure are readable and linkable, but the engine treats non-first-party content as untrusted input — its schema’s instruction prose is withheld and every read surface tags the content’s origin.

Bump the version and publish in one step (--version persists the bump to the mem config, like npm version + npm publish):

Terminal window
memstead publish --mem recipes --version 0.2.0

The registry serves the highest published version as current. Publishing an older version succeeds — it’s retained and resolvable — but the output notes that current stays where it was.

To take a mem down: memstead unpublish github:<handle>/recipes (permitted to the original uploader). The same <scope>/<name> becomes immediately re-publishable.

The refusals a first publisher actually hits

Section titled “The refusals a first publisher actually hits”

Every refusal carries a typed code (add --json and branch on .code):

  • NOT_AUTHENTICATED — no token and no TTY for the device flow: not logged in and stdin is not a TTY — set MEMSTEAD_TOKEN or run `memstead login` first. Also the shape a 401 from the registry maps to (expired/revoked token): re-run memstead login.
  • WORKSPACE_NOT_INITIALISED — you ran publish outside any workspace: no workspace found from <cwd> or any ancestor (missing .memstead/workspace.toml). cd into the workspace, pass --workspace <path>, or supply a pre-built archive path.
  • INVALID_INPUT--version without --mem (the bump needs to know which mem to re-version), or --version / --redact-anchors combined with a pre-built archive path (its content is already baked in — assemble with --mem or the bare shape instead).
  • INVALID_VERSION--version that isn’t a semver.
  • REGISTRY_VALIDATION_FAILED — the registry rejected the archive’s content (a 400); the message carries the validation variant, the offending path inside the archive, and the detail.
  • ARCHIVE_TOO_LARGE — the archive exceeds the 2 MiB compressed publisher cap (the unit the validator enforces). Slim the mem or split it.
  • RATE_LIMITED — too many publishes in a window; the message says how many seconds to wait.
  • FORBIDDEN — you tried to publish into a scope that isn’t yours (--scope overrides are reserved for registry admins; normal publishes never need it).

A .mem archive carries a provenance record for every entity in .memstead/provenance.json: the latest authoring rationale where a mutation recorded a note, and an explicit no-rationale record otherwise. Those notes are written inside your workspace and can name what a public archive must not: internal plan paths, a legacy domain, an absolute user path, a credential. memstead export --format mem redacts every such span to [redacted:<class>] before the archive is written and never strips the record: the row keeps its timestamp, kind and actor, and the rest of the sentence stays readable. The classes are the same seven the engine’s leak scan enforces, held equal by a test, and the export report counts what it redacted per class (redactions in the JSON envelope). Entity bodies are not rewritten: a private string in a body is yours to fix, and the leak scan keeps flagging it.

Publishing over a private source: --redact-anchors

Section titled “Publishing over a private source: --redact-anchors”

A mem built from a source carries provenance anchors — durable records tying each entity to the artifacts it describes. They travel in the published archive by design: a consumer reads how strongly each entity claims fidelity to its source. But every anchor also names the source’s file paths or URLs. If your source is private, publish with redaction:

Terminal window
memstead publish --mem my-notes --redact-anchors

Every artifact reference in the packaged sidecar — the artifact field and each derived_from entry — becomes the fixed sentinel [redacted], while the trust metadata survives: provenance class, at_version, grain, hash, hash stability, and your source name. A consumer still sees the trust grade of every entity; they no longer see which files it came from. Reverse artifact lookup (memstead anchors --artifact <path>) on an installed redacted mem finds nothing — by design.

Two things to know:

  • Your workspace is untouched. Redaction happens on the staged copy at publish time; the local sidecar keeps its real references, and local anchors, verify, and drift behaviour are unchanged.
  • Redaction removes identity, not existence. The kept fields still disclose something: grain reveals the medium shape, at_version may carry a commit SHA or ETag, source is the name you chose for the source, and hash lets someone confirm guessed content against the prepared form. A redacted package is not free of source-related information — it just names no artifact.

Without the flag, published anchors ride untouched — that honest default is unchanged.

If you already have a .mem file — e.g. from memstead export --format mem -o my.mem — publish the bytes directly:

Terminal window
memstead publish my.mem

Content-shaping flags (--version, --redact-anchors) refuse on this shape — the bytes are already baked. Re-export, or use an assembling shape.

  • The publishing guide on memstead.io documents the registry side of these commands.
  • The Glossary defines mem, archive, and mount precisely.