mehendocs

ConceptsMachines

Machines#

What is a machine, and what may it do?

On this page

A device is an enrolled machine in one workspace. A computer can be enrolled in several workspaces; each enrolment is a separate device with its own key and scope.

Owners and keys#

A device is owned by the member whose machine it is, or by a team. It holds an ES256 (P-256) key:

BackendWhereKey storage
secure-enclavemacOS on Apple silicon, chosen by default; the Secure Enclave helper is embedded in the macOS binary and extracted under MEHEN_HOME/bin on first useA non-exportable Secure Enclave key. Only an enclave-wrapped blob sits in a 0600 file. Keys use "after first unlock" protection so background agents can sign while the screen is locked.
softwareLinux, CI, containers, or --backend softwareA P-256 key in a 0600 file in the workspace's directory on the machine. mehen refuses a key file that other users can read.

There is no bearer token. Every machine request carries a proof signed by this key (Keys, proofs and signing). A key belongs to one workspace at one vault: its scope file records both, a key made for another is never reused, and the same computer enrolled in two workspaces holds two keys in two slots.

Key and configuration files must be regular files, owned by this user, private and not symlinks. Mehen makes an owned state directory private if its permissions are too wide. The ledger's rules differ: an unsafe or symlinked ledger is not trusted as existing state, and is read as empty and replaced.

Enrolment#

  1. The machine asks. mehen enrol --workspace <slug> --name my-laptop (--team <team-slug> for a machine a team will own) reuses the key in the workspace's slot when it can vouch for it, otherwise it creates one; --fresh-key always creates one. A key is scoped to the vault and workspace it was made for: with another MEHEN_VAULT_URL it is refused with key_scope, never overwritten.
  2. The vault answers with a link. It returns an 8 character code, the link https://app.mehen.dev/w/<slug>/e/<code>, a lease that ends in 60 minutes and a hard cap 24 hours out. --connect <code>, with a connect code from the workspace's Connect an agent page, claims the attempt for that account: only that account can accept it, and it lands in that account's Needs you. The CLI computes the key's fingerprint itself and prints the ask: the link, the fingerprint, the four words and which group the page hides. There is one live link per key: a new attempt for the same key supersedes the older one.
  3. The person whose machine it is accepts it. They open the link, sign in, and see the machine's name, host, backend, when and from where it asked, the words and three of the four fingerprint groups, and the complete scope it will get. They type the hidden group and accept with a passkey that signs the workspace, the enrolment, the key's full thumbprint, their own account and membership, the whole scope including the private-credentials tick, and the start context. The server checks the typed group against the stored key before it honours the passkey. Nobody, owner or admin, can accept a machine on a member's behalf. A guest's machine then also needs admission by an insider who manages every project the scope names, a distinct action that never counts as acceptance; a team's machine is admitted by an owner or admin instead of accepted. Owners and admins are told that a machine was added, with a link to narrow or revoke it.
  4. The machine finishes. mehen enrol records the attempt, prints the ask and exits 0 (--wait waits in place). mehen wait enr_... (or vault_wait) from any process polls until the attempt is active. A live attempt is listed and can be accepted; when nothing renews it, it lapses (it cannot be accepted, "This link has lapsed") but stays revivable: mehen wait renews it in place, with the same id, code, link and fingerprint, when under five minutes remain, at most once a minute and never past the hard cap. An attempt is terminal when it is active, rejected or expired. Only a terminal expired calls for a new attempt with mehen enrol, which keeps the key. Five wrong typed groups reject an attempt across any number of renewals.

On active, mehen checks the answer before it writes anything: the returned workspace id agrees with what it asked for, the slug matches the requested workspace, the owner is of the requested kind (self or team), and the scope is complete. That does not by itself verify a particular human account or team; read the accepted owner that mehen wait prints. If activation does not match, mehen writes no new configuration, attempts to revoke the returned device and removes only files that belong to that attempt. Cleanup can fail; read the reported outcome, and keep any device id it names visible for a human to revoke. Two processes finishing the same attempt with the same key is one success, never a revoke; two different devices approved for one workspace on one machine is already_enrolled, which revokes and deletes nothing.

Scope and reach#

A device's scope is a ceiling under its owner's rights, never a grant of its own:

FieldMeaning
tagsItems the device may use: an item must carry one of these tags. Empty means every item its owner may use.
projectsProjects the device may use. Empty means every project its owner may use, and only such a member-owned device may create projects.
maxEnvironmentunprotected or protected: whether the device may ever use a protected environment or a protected credential, given that its owner has the protected right there.
modesWhich rungs of the exposure ladder this device may use at all. Never reveal.
privateCredentialsWhether the device may use its owner's private credentials and private overrides. Only a member-owned device; a team's is always false. Without it, the owner's private credentials are not_found to the machine.

Reach is what a device can actually do: for each project, its owner's effective access, with protected runs only when maxEnvironment is protected; a workspace credential only for a member's device (owner, admin or member, never a guest's or a team's); a project credential where the owner has use; a credential a protected environment references only inside that environment, with the owner's protected right; a private credential only for the owning member's ticked device, and never in a protected environment. Reach is re-evaluated on every request: a suspended or removed owner makes every call owner_inactive, an expired share or a lost project not_found or project_unshared, and a device never outlives its owner's standing.

Narrowing, widening and revoking#

A device is active or revoked. A revoked device's proofs are refused with device_revoked. A device can revoke itself (mehen revoke); its owner, an owner or an admin can revoke it from the browser with a passkey.

Narrowing a device's scope (every field a subset, maxEnvironment not raised, the private tick not turned on) is done by its owner, an owner or an admin with a passkey. Widening one is proposed and then accepted by the device's own owner with their passkey, as at enrolment; a team's device is widened by an owner or admin, and can never get the private tick. A change applies from the device's next request, live runs included. The audit records the scope before and after.

Revocation stops subsequent uses through Mehen. It cannot recall an upstream request already sent, undo what happened at the provider, erase a value already leased into a process, or revoke a credential at its provider; rotate an exposed credential at the provider (A machine or a key is compromised).

The Machines page shows each device as a card with its key's four words, key type, owner, last seen, status and what it may use. A member sees their own machines and their teams'; owners and admins see all.

A non-exportable key cannot be copied off the machine, but any process running as the same user can ask it to sign. The device key proves which machine is asking, not which process. It is not what keeps secrets from an agent: that is the vault never returning values, the bindings, the owner's rights capping the machine, and approvals that need a passkey.

Fingerprints and the hidden group#

A key's fingerprint is what a human compares between the terminal and the enrol page. The CLI, the server and the page derive it the same way:

mehen status prints the fingerprint recorded at enrolment and the Machines page shows the words, so a machine can be matched to a card at any time. The derivation is in Keys, proofs and signing.

Sessions#

A session is a label an agent puts on its requests: MEHEN_SESSION for mehen and mehen mcp, 1 to 120 letters, digits, ., :, _ or -. It might be a session id, a task id, a subagent run, or a label an app sets from its conversation. It is for the audit log and for the details of an intake, connect or approval page. It grants nothing, and because an agent chooses it, no page infers who asked from it: the "who asked" line is always the enrolled device's name ("An agent on my-laptop"), which its owner accepted at enrolment. Grants are per device and request digest, not per session.

Workspaces on a machine#

A machine identity's state lives under one root, MEHEN_HOME (default ~/.config/mehen): one slot per workspace, workspaces/<origin>/<tn_id>/ once active (its configuration, the device key and its scope file, and the ledger) and pending/<origin>/<slug>/ while an enrolment waits. Slots are keyed by origin and workspace id, so a rename costs nothing. They are discovered from disk, and a directory that is not a slot is named by mehen doctor and never deleted. Locks serialise enrolment, finishing and revocation per slot.

Harness registrations and skills remain per OS user, and installation and update state sits beside the executable, so separate homes do not create separate MCP profiles or separate updater installations.

Selection. Every command selects a workspace before it signs or sends anything:

  1. The ceiling MEHEN_WORKSPACES (comma-separated slugs, tn_ ids or <origin>/<slug or id>; unset or * for every slot, empty for none) is the set this process may use. It only narrows, and nothing inside a process widens it.
  2. A checkout's mehen.toml without workspace while several slots are allowed is workspace_unbound.
  3. Then --workspace <slug or id> or MEHEN_WORKSPACE; then the checkout's mehen.toml (workspace and vault); for mehen wait, the one slot that records the id; then the only active slot.

There is no default: several and nothing deciding is context_ambiguous for a command that needs one, while status, doctor, ls, pending and workspace list span the allowed set. These refusals are local, exit 2, and sent before anything is signed: workspace_not_allowed, workspace_unknown, workspace_mismatch, context_ambiguous, workspace_unbound, config_dir_retired. A workspace's name is data from the vault, never a selector; only the id and the canonical slug select. mehen status reports a renamed workspace's current slug and name without recording them; mehen workspace list --check and vault_status can each record them for the slot after a successful signed check. mehen workspace list --check refuses an answer for another workspace or origin, or a slot that changed while it asked, as workspace_mismatch and changes nothing; vault_status keeps the recorded names when the answer names another workspace.

The ceiling is an accident control, not an identity boundary: an environment variable can be edited. A separate MEHEN_HOME selects a separate local identity store, a separate machine to the vault with its own keys and slots. Commands using one home do not discover the other's slots. That is organisation under one OS user, not isolation from other processes running as the same user. Several workspaces on one machine and A second identity on the same machine are the how-to.

Managed hosts. An agent launcher that pins each agent to one workspace places a marker under the user's home directory as the system's user database records it, never as HOME says. Commands that open Mehen's state fail closed with key_insecure when that home cannot be resolved or its path is unsafe. There the pinned slot and MEHEN_MANAGED_CONTEXT, checked against the host's pin map, are the sole authority: no manifest or ceiling widens it, and a selector that disagrees refuses. Managed selections fail closed: an outdated managed-agent configuration is pin_outdated, and only the harness owner can repair it.

One MCP server. One mehen mcp server can serve several allowed workspaces, with a separate identity latch for each. Its startup workspace, the checkout's manifest, a managed pin and the harness ceiling may restrict that set, and a tool argument cannot widen it. A slot whose enrolment later changes device, workspace or origin, or vanishes, is refused for the life of the process with restart advice, while the other slots keep serving. mehen setup writes one mehen entry per harness, carrying the ceiling in MEHEN_WORKSPACES whenever it is restricted, so setup never makes a server wider than the shell it ran in.