# Changelog

Changes to the hosted vault first, newest at the top, then the release notes of the `mehen` command line.

## The platform

### 28 October 2026: workspaces

The vault moved to workspaces: one account belongs to any number of workspaces, each with its own members, teams, projects, credentials, machines and audit, at `https://app.mehen.dev/w/<slug>/`. The vault's data started fresh: machines enrolled before, credentials and projects did not carry over, and every machine enrols again in a named workspace with `mehen enrol --workspace <slug>`. The `mehen` release notes below say what changed for each version of the command line.

## mehen CLI release notes

The version is printed by `mehen version --json`, with the commit it was built from. Releases are served from `https://mehen.dev/dl/`; `mehen update` installs the latest one.

### 0.3.0

Many workspaces on one machine without bending anything, an enrolment that cannot deadlock, and a CLI that checks for signed releases and installs them itself. Installed release builds with a compiled trusted release key check for signed updates automatically; a build with no trusted key updates nothing.

**Getting there from 0.2.0.** Run `mehen update --json` once on each machine. A 0.2.0 `mehen` cannot check a release signature, so this one update is checked against the manifest's hashes over TLS only; every update after it is signed, and a release build with a compiled trusted key checks for them automatically from then on. Do not run the installer on a machine that already has `mehen`. The first non-observational 0.3.0 command that uses slots migrates `~/.config/mehen` in place (below); `mehen workspace list` is the local-only migration command; nothing needs re-enrolling, and credentials and projects are untouched.

#### Breaking changes

- **One state directory, one slot per workspace.** Workspace identity state lives under `MEHEN_HOME` (default `~/.config/mehen`); harness registrations and skill ownership remain per OS user, and update state is beside the executable: `workspaces/<vault>/<tn_id>/` for each active workspace and `pending/<vault>/<slug>/` while an enrolment waits. The 0.2.0 layout (the first enrolment in `~/.config/mehen` itself, the next ones in `contexts/<slug>`) is migrated by a slot-using command such as `mehen workspace list`, under a journal (`migration.json`) that resumes after a kill; device state is retained, while obsolete update-check files and the completed journal are removed. A directory that is not a slot (a hand-made `revoked-*`, say) is named by `mehen doctor` and left alone. A managed host is never migrated automatically: its owner provisions the pinned slot first.
- **`MEHEN_CONFIG_DIR` is retired to an alias.** For 0.3.0 it is read as `MEHEN_HOME` when it names the root, and refused as `config_dir_retired` (exit 2), before anything is read or sent, when it names a slot, a pending slot, an old context directory, or disagrees with `MEHEN_HOME`; the refusal names the `MEHEN_HOME` to use. The alias goes in the next release: move launchers and MCP entries to `MEHEN_HOME`. On a managed host the pinned slot directory is still passed as before.
- **`mehen enrol` prints the ask and exits 0.** It no longer waits in the foreground or needs to be backgrounded: relay the link, then `mehen wait enr_...` finishes the enrolment, from any shell. `--wait` keeps the old behaviour. Running `enrol` again resumes the same attempt with the same key; on an active workspace it answers `active` and changes nothing.
- **Links stay alive in place.** An attempt's link lives 60 minutes and `mehen wait` renews it while it waits (same id, address and fingerprint), up to 24 hours; there is no "issue a fresh link up to three times" any more. Only a terminal `expired` means a new attempt (`mehen enrol` again). A 0.2.0 attempt migrated as pending is never renewed and expires at its old time.
- **No default workspace.** `mehen workspace default` is gone. A command that needs one workspace takes `--workspace`, `MEHEN_WORKSPACE` or the checkout's `mehen.toml`, else the only workspace there is; several and nothing deciding is `context_ambiguous`. `status`, `doctor`, `ls`, `pending` and `workspace list` cover every workspace.
- **`mehen status --json` lists every workspace**: `{ home, allowed, workspaces, selected, next, cli }`, each entry `active` (with a `check` from the vault that never deletes anything), `pending`, `corrupt` or `absent`. `mehen status --workspace <slug> --json` keeps a scalar one-workspace result but renames `context` to `slot`. `mehen doctor --json` is one report for the machine, each vault and each workspace, with `cli`, `migration` and `strays`. `vault_status` and `vault_doctor` return the same shapes; `vault_status` has no `enrolled` field any more.
- **Status is observational.** `mehen status` and `mehen whoami` neither migrate the root nor persist display-field changes; `mehen workspace list --check` records a renamed workspace's new slug and name (only those, and only for the slot's own workspace and origin). On a legacy or resumable root they refuse with the next command to run; `mehen workspace list` performs the local migration, then status can be retried. `mehen doctor` reports the journal without migrating it.
- **Listings span workspaces.** `mehen ls` and `vault_list` add `workspace` to each item, and CLI tables add a workspace column when results span more than one workspace. A slug is unique only within its workspace: pass `--workspace` or the MCP `workspace` argument on subsequent single-item operations. Aggregate pending lists also span the admitted workspaces; a singular selector limits the applicable commands.
- **One MCP server for every workspace.** `mehen mcp` serves every workspace the entry's `MEHEN_WORKSPACES` allows, including one enrolled while it runs; every tool except `vault_templates` takes `workspace` (slug or id), needed only when more than one could serve. Each workspace is latched to the identity it first served; one whose enrolment changes under the server is refused until the agent restarts, while the others keep serving. `mehen setup --workspace` and the `mehen-<slug>` servers are gone: the next `mehen setup` removes the old `mehen-<slug>` entries it wrote and reports them under `removed`.
- **`mehen update --json`** answers `{ current, latest, available, updated, path, result, skills? }` with `result` one of `updated`, `current`, `locked`, `held` or `rolled_back`, and `skills` the per-skill report instead of `"refreshed"`.
- **The installer is for a fresh machine only.** On a machine with a verified `mehen` it prints `Already installed`, downloads nothing and changes nothing unless `--force` (a human's decision, for example after losing the only release key). Its last line is now `mehen-install: {"v":1,"result":...,"version":...,"path":...,"onPath":...,"env":...,"rc":{...}}`. It writes one guarded PATH line for zsh and bash and gives advice for other shells, and never overwrites a file it does not own.
- **A wider refusal when the user's home cannot be resolved.** The managed-host marker is now read under the home directory the system's user database records for this uid (`getent passwd`, then `/etc/passwd`, on Linux; `dscacheutil` on macOS), never under `HOME`. When that lookup fails or answers something unsafe (a relative path, a symlinked or group- or world-writable component, a home not owned by this uid), state-opening commands fail closed with `key_insecure` (exit 6) instead of guessing (pure help, guide and version output does not open the managed marker), naming the uid and the fix. Only a definite "no such user" on a Linux host whose user database is the plain files is treated as unmanaged. `HOME` and `MEHEN_HOME` no longer move where the marker is read.

#### New

- **`MEHEN_WORKSPACES`**: a comma-separated list of slugs, `tn_` ids or `<vault>/<slug>` that this process may use. It only narrows; anything outside is `workspace_not_allowed` (exit 2), and nothing inside a process widens it. An allowed workspace not joined yet shows as `absent` with the enrolment to run.
- **`mehen setup`** writes one `mehen` entry per agent and carries the shell's `MEHEN_WORKSPACES` into it whenever it is restricted (and `MEHEN_HOME` when not the default), so a restricted shell never writes an unrestricted server. `--workspaces a,b` requests a narrower entry and refuses a request wider than the calling shell; an existing wider entry is skipped until `--reset-env` rebuilds its environment. `--mcp-file <path>` writes `mcpServers.mehen` into any compatible JSON MCP client's file; use `--agent vscode` or `--agent opencode` for those clients' different formats. `--print [--format json|toml]` prints the entry and writes nothing. `--reset-env` rebuilds an entry's `env`, for one that still carries `MEHEN_CONFIG_DIR`. A `mehen` entry made by hand, or edited by hand after setup, is `skipped` with its reason until `--force` takes it over, still under every env check. Each agent reports `mcp` as `added`, `updated`, `unchanged`, `skipped` or `failed`, and each skill as `added`, `updated`, `unchanged`, `removed`, `external` or `failed`; setup exits 1 on any `failed`. `--remove --agent X` keeps the skills while another entry remains.
- **`mehen enrol --connect <code>`**: a connect code from the workspace's Connect an agent page claims the attempt for that member, so it lands in their own Needs you and only they can accept it. A refused code is `forbidden` (exit 5).
- **`mehen workspace forget (<slug or id> | --slot <path>)`** retires one slot on this machine only, without the vault; the device stays on the Machines page until someone revokes it there.
- **Self-update.** Releases are signed with a Secure Enclave release key whose public half is compiled into the binary. Installed release builds with a compiled trusted release key check for signed updates automatically; a build with no trusted key updates nothing. With a key, `mehen` checks for a release about every six hours in the background after a command and installs only a signed, newer version, with one journalled rename next to the binary. `mehen update` does it now; `--version <v>` selects a named signed release, while an intentional downgrade needs `--force` or a verified `--rollback`. Those local overrides still verify the release signature. `MEHEN_NO_AUTO_UPDATE=1` or `autoUpdate: false` in `settings.json` under `MEHEN_HOME` turns it off; CI and source runs never update themselves. After an update the command line uses the new version at once, and a running `mehen mcp` says in a tool result (and in `cli.mcp` of `vault_status`) that the agent should restart to load it.
- **`cli_outdated`** (exit 1): the vault refuses a `mehen` below its minimum version (HTTP 426) everywhere but freeze and self-revoke. `mehen` updates itself before a command when it knows the minimum is above it, and a refusal in the middle of a command is shown once with the command to run again, never re-run. Every `/v1` answer carries `Mehen-Cli: minimum=<v>` (with `; recommended=<v>` when the vault sets one), and requests send `User-Agent: mehen/<version>`.
- **`mehen doctor`** gains `home`, `migration`, `on_path` and `update` rows, checks every workspace, and never migrates, writes or deletes anything.
- **The docs moved to https://docs.mehen.dev.** The setup guide lives at https://docs.mehen.dev/setup.md and the skill at https://docs.mehen.dev/skill.md; the old addresses on `mehen.dev` redirect. `mehen guide` and the skill name the new address.
- **`mehen version --json`** adds `build` (`release`, `dev` or null when running from source) and `helper` (`embedded` or `none`); `commit` is also null for a source run without a build stamp. On macOS the Secure Enclave helper is embedded in the binary and extracted under `MEHEN_HOME/bin` on first use; the separate `mehen-se` file a 0.2.0 install left beside the binary is removed by the first update that 0.3.0 itself installs, when it and its checksum file match.

#### Fixed

- Finishing the same enrolment from two processes (a backgrounded `enrol` and a `wait`) no longer revokes the device it just activated: the same key finishing twice is one success. Two different devices approved for one workspace on one machine is `already_enrolled`, which now revokes and deletes nothing and says how to keep one.
- `mehen revoke` removes a slot only when the vault revoked it or says it was revoked already, so "already enrolled, revoke first" and "revoked, enrol again" can no longer point at each other; `mehen enrol` on a revoked slot starts a fresh attempt.
- Locks per slot stop two enrolments committing a key and a config that do not match.
- An approval granted just before a link's local expiry is no longer stranded: the record is kept until the vault says the attempt is over.
- `mehen update` no longer reports skills as refreshed when some were left alone.

### 0.2.0

Workspaces. The vault now holds many workspaces, each with its own members, teams, projects, credentials, machines and audit, and every machine is enrolled in exactly one of them.

**Enrolments made with 0.1.0 stop working at the cutover.** The vault's data starts fresh: every machine enrolled by 0.1.0, every credential and every project is gone, and a 0.1.0 `mehen` cannot enrol against the new vault (its enrolment names no workspace). Update (`mehen update --json`, or `curl -fsSL https://mehen.dev/install | sh`), then enrol again with `mehen enrol --workspace <slug>`. The update keeps the old enrolment's files in `~/.config/mehen`, so the new enrolment lands in `~/.config/mehen/contexts/<slug>`: make it the default with `mehen workspace default <slug>` (which also decides what the plain `mehen` MCP server serves), verify with `mehen status --json`, and let the human delete the dead enrolment's `config.json`, `device-key*` and `ledger.json` from `~/.config/mehen`; `https://mehen.dev/setup.md` has the steps. This is forward only: a 0.1.0 `mehen` cannot be used against the new vault, so there is no downgrade. The human re-adds credentials through intake links and re-creates projects with `mehen init`.

- **Workspace enrolment.** `mehen enrol --workspace <slug>` is required; a bare `mehen enrol` is a usage error (exit 2) and sends nothing. The slug is on the workspace's Connect an agent page. The machine's owner accepts it with their passkey; `--team <team-slug>` enrols a machine a team owns, which an owner or admin admits. The enrolment is verified against what was asked for before anything is written: an activation in another workspace, with another owner kind or without a complete scope writes no config: the CLI attempts to revoke the new device, reports whether that succeeded and, when it did not, names the device for a human to revoke. One enrolment per workspace per machine: the first lives in `~/.config/mehen`, the next in `~/.config/mehen/contexts/<slug>`, or wherever `MEHEN_CONFIG_DIR` points. Keys are scoped to one workspace and never reused across workspaces.
- **Contexts and selection.** A context is picked before any signature: `MEHEN_CONFIG_DIR`, then `--workspace <slug or id>` (a global flag) or `MEHEN_WORKSPACE`, then the checkout's `mehen.toml` (`workspace` and `vault`), then the default. New command `mehen workspace list [--check] | show | default <slug or id> | bind`. New local refusals, all exit 2 and never from the vault: `workspace_unknown`, `workspace_mismatch` (also for a `MEHEN_VAULT_URL` or key scope naming another origin or workspace), `context_ambiguous`, `workspace_unbound`. `mehen status` and `vault_status` name the workspace (id, slug, name), the owner and the context directory; every status call refreshes the recorded slug and name from the vault, never the id. `mehen revoke` revokes only the selected context.
- **Managed-host pins.** A managed host's agent configuration must be current. An outdated one is refused as `pin_outdated` (exit 2): selection fails closed, nothing is inferred, and only the harness owner can repair it. On a managed host only the pinned `MEHEN_CONFIG_DIR` is accepted.
- **Private credentials and homes.** `mehen request` and `mehen connect` take `--home private|project|workspace` (`--project <prj_id>` for a project home, or the checkout's manifest project); `vault_request` and `vault_connect` take `home` and `project`. The default is `private`: only the human who fills it in can use it, through their own machines with the private-credentials tick. The human may change the home on the page.
- **Overrides in `env ls`.** A member may set a personal override for a variable in an unprotected environment on the project page (their own private credential, or a plain non-secret value). `mehen env ls` marks it `override (your <slug>)` or `override (your plain value)` in its text and `override: true` in `--json`; `mehen env check --json` carries the same marker, while its human output only reports whether every variable resolves; `mehen run` names overridden variables in its banner. No value is ever printed.
- **Project context for `fetch`.** `mehen fetch --project <prj_id> --env <name>` and `vault_fetch` `context: { project, environment }` name the project environment a credential is used for, which a protected credential requires, and so does a workspace credential that a team's or a guest's machine reaches through a shared project; a credential whose home is a project you may use, or your own private one, is reachable by name. A context is an extra requirement, not a default: the environment must reference a non-private credential, and a private one needs an authorised unprotected environment. It never changes the workspace.
- **Links under the workspace.** Every ask link is `https://<vault>/w/<slug>/...`, under the canonical slug of the workspace the machine is enrolled in. A link under another slug, an alias or another origin is refused and rebuilt from the id under the canonical slug, with a warning; a link cannot be trusted without a known slug. Asks carry `context: { origin, workspace: { id, slug, name } | null, device }`, the enrolment that raised them.
- **The identity gate.** A workspace may require a verified Google identity on its domain. The vault then refuses a human who has not signed in with Google recently with `identity_required` (exit 4): the human completes the sign-in in the workspace, and the agent repeats the step. Google only identifies; passkeys still authenticate and sign. Enrolled machines are not re-gated in this release; removing a member cuts their machines off at once. New codes `account_pending`, `owner_inactive` and `project_unshared` are exit 4 as well.
- **MCP.** One `mehen mcp [--workspace <slug or id>]` server binds one context for its lifetime and refuses to serve anything else; `vault_env` in a checkout of another workspace is `workspace_mismatch` ("start a second server"). `mehen setup --workspace <slug or id>` registers that workspace's server by its stable id, as `mehen` for the default context or `mehen-<slug>` for another, without touching other registrations; `mehen setup` with no flags writes exactly what 0.1.0 wrote. Tool names are unchanged. `mehen pending` and `vault_pending` show this machine's own asks and its own "machine added" notice only.
- **Policy proposals** (`mehen policy preview|propose`, `vault_policy`) keep their surface but are refused by the vault in this release (`forbidden`, "Policy widening is not available yet"); `show`, `cancel` and `stop` work.
- **Help and next steps** from the CLI itself name `--workspace <slug>` wherever they tell you to enrol (the vault's own `next` for an unknown or revoked machine still says `mehen enrol`; add the workspace), and use neutral example names.

### 0.1.0

First public release: `mehen.dev/install`, `mehen setup`, `mehen update`, the ask block, enrolment with a kept key, `mehen mcp`, `mehen run`, templates, `mehen check` and `mehen pending`.
