Unattended desktop CLI and MCP
Unattended access is an explicit, local-only, read-only capability for selected live vaults. It includes ordinary and private memory/cards, original binary files, revision history and explicitly requested credential plaintext. Only enable it when you trust the receiving client with every asset and secret in those vaults. It isn't ordinary MCP authority or a permanent sudo session.
Enroll and select custody
Start the daemon in a separate terminal, using the same dedicated absolute home for every command. Initialize your library and create its vaults normally first. The paths below are examples; use your own private directory on each platform.
memories --home /absolute/private/home daemon
Choose a backend explicitly in your interactive terminal:
memories --home /absolute/private/home unattended enable --backend linux-secret-service
Enrollment warns that clients can read stored passwords, asks you to type
ENABLE, then prompts for your local password for unlock and again for sudo.
This consent/password setup is required once for enrollment, not on each
unattended read. Don't pass passwords through arguments or environment variables.
Later management still requires an ordinary password-unlocked, sudo-authorized
terminal.
| Backend | Custody and requirements |
|---|---|
windows-dpapi |
Current-user Windows DPAPI; protected files use owner-only permissions. No machine-wide scope or authentication UI. |
macos-keychain |
Nonsynchronizing macOS Keychain entry; locked or approval-needed access is denied rather than prompting. |
linux-secret-service |
Session-bus Secret Service with an already unlocked collection/item. It doesn't request Unlock or invoke Prompt. |
owner-file |
Explicit lower-protection opt-in on desktop: plaintext wrapping material in an owner-private file, not cryptographic protection against the OS account. |
For example, deliberately choosing the lower-protection option uses
unattended enable --backend owner-file. There is no automatic fallback when a
native backend is unavailable or needs interaction. Secret Service relies on
the local OS-account/D-Bus boundary; the client can't control arbitrary
provider-specific UI behavior. Backend selection is create-only. Don't edit its
metadata to switch backends. Custody binds the canonical store path and current
header identity, so copying it to another store isn't enrollment.
The password wrapper remains available for normal unlock and recovery. Enrollment adds a separate master-key wrapper whose wrapping key stays outside the database; it doesn't replace password authentication.
Mint a client file
In the same authorized terminal, mint a grant for the vaults the client needs:
memories --home /absolute/private/home unattended mint --vault VAULT_ID --client-file /absolute/private/home/client.json
# Repeat --vault VAULT_ID to include another vault in this grant.
The parent directory must already exist and be owner-private. Mint publishes the file atomically without overwriting an existing file and rejects unsafe ownership, permissions or links. Failed publication attempts to revoke the new grant and reports revocation failure. Output contains the grant ID and file path, never the bearer token. Keep the ID for revocation.
This client file is separate from ordinary MCP grant files. Protect it like a
password; don't print its contents, commit it, or put its token in command
arguments. CLI/MCP reads require explicit --client-file selection and the same
home/store. Scope is read_only_all_assets for the selected vaults, not a
per-asset or ordinary-content-only grant. Each read rechecks current local device
membership and vault scope.
Startup and reads
Restart the daemon after enrollment to use keys-only startup. Stop it without
issuing memories lock, then start it again with the same home. Successful startup
loads unattended keys but reports ordinary unlocked: false: ordinary reads,
writes, sudo, cloud work and RAG remain locked until password unlock. Backend
failure leaves unattended keys unloaded, with no password or ambient-auth fallback.
On Unix, forced termination can leave service.sock in the home directory. Remove
that stale socket only after confirming its daemon has stopped.
memories --home /absolute/private/home unattended list VAULT_ID --client-file /absolute/private/home/client.json
memories --home /absolute/private/home unattended history VAULT_ID ASSET_ID --client-file /absolute/private/home/client.json
memories --home /absolute/private/home unattended read VAULT_ID ASSET_ID --client-file /absolute/private/home/client.json
memories --home /absolute/private/home unattended credential-bytes VAULT_ID CREDENTIAL_ID --client-file /absolute/private/home/client.json
List returns asset metadata, including private, file and credential assets, but not their bodies; tombstones are omitted. History returns revision metadata, including head/deleted/quarantine flags, not credential bodies. A conflicted current asset requires an explicit matching revision. Historical and deleted revisions can be read explicitly, but quarantined revisions are denied. This route reads live-vault history, not recovery archives.
read returns memory/card Content JSON bytes or a file's original bytes. It
rejects credentials. credential-bytes accepts only credentials and returns the
stored body's UTF-8 bytes, including historical values when explicitly selected.
It doesn't add arbitrary binary credential storage. Protected card and wiki
secret references remain references; request their referenced credential
separately if needed.
Reassemble full bytes
Responses use data containing base64url without padding, with zero-based
offset, byte total, next (null when complete), actual revision and
encoding (content_json or raw_bytes). To retrieve the full content:
- Request offset 0 with
--limitbetween 1 and 65536 bytes (default 65536). - Decode each response's
dataseparately as base64url without padding. - Use
nextas the next--offsetand pin the first returned revision with--revision REVISION_IDon every subsequent request. - Continue until
nextis null, then concatenate decoded byte ranges in order. Only then decode UTF-8/JSON if applicable; a range can split a UTF-8 character.
For example, a subsequent range uses:
memories --home /absolute/private/home unattended read VAULT_ID ASSET_ID --client-file /absolute/private/home/client.json --revision REVISION_ID --offset NEXT_OFFSET --limit 65536
Use the same options with credential-bytes. Don't concatenate encoded strings
or treat the first range as the whole asset. List/history use --offset and
--limit between 1 and 100 items (default 100); their pages reflect current state,
not a snapshot, so changes between requests can alter ordering and totals.
Connect an AI client through MCP
Configure a separate stdio server command with the same home and client file:
memories --home /absolute/private/home mcp serve-unattended --client-file /absolute/private/home/client.json
It advertises only unattended_list, unattended_history, unattended_read and
unattended_credential_bytes. Tool arguments are vault, asset (except list),
optional revision (byte reads), offset and limit. Tool results contain the
same paged/ranged JSON in MCP text content; full downloads follow the byte-range
contract above. There is no asset mutation, execution, administration, sync,
backup dump, search/model execution or OTP-counter advancement tool. Reads save
signed delivery intents before returning output; audit/storage failures deny it.
Ordinary mcp serve --grant-file is unchanged and still excludes private content,
files and credential reads. Neither server inherits terminal sudo. Center-managed
stores reject unattended enrollment/startup/use. HTTP/WebSocket, relay RPC,
mobile FFI/Share and WebUI can't use unattended authority. Remote flags,
environment dial options and saved remote defaults are refused by these commands,
even though global remote options appear in executable help.
Credential base64 is encoding, not encryption. Unattended MCP intentionally gives the receiving client secret bytes. AI providers, transcripts, tool logs, caches or plugins can retain or transmit the encoded value or decoded plaintext. Read-only scope prevents mutations through this capability, not disclosure or use of a copied secret elsewhere. Use synthetic secrets for tests.
Lock, resume, revoke and disable
memories --home /absolute/private/home lock
Manual lock clears keys and durably inhibits unattended startup across restarts. Password unlock alone doesn't resume it. If durable writes fail, lock returns an error; don't assume inhibition persisted on an unwritable device. Idle/shutdown cleanup isn't manual inhibition.
Management requires password unlock and sudo in your local terminal:
memories --home /absolute/private/home unlock
memories --home /absolute/private/home sudo
memories --home /absolute/private/home unattended status
memories --home /absolute/private/home unattended resume
Status reports enabled/inhibited state and grant IDs, not tokens. After resume, restart the daemon without manual locking to return to keys-only state. To remove one client or disable all unattended access:
memories --home /absolute/private/home unattended revoke GRANT_ID
memories --home /absolute/private/home unattended disable
Revocation is durable and idempotent. Disable durably disables authority and revokes every grant before backend cleanup. Deleting a client file alone isn't revocation if someone copied it. Lock, revoke and disable can't recall plaintext already delivered; rotate exposed credentials with their provider.
Recovery and validation limits
Unattended wrapper/authority records aren't included in logical exported backups, device pairing or vault synchronization. Restoring a logical backup doesn't restore unattended enrollment. A whole raw local database copy still includes the auxiliary table, and filesystem backups can copy client files or custody material. Same-account processes, OS administrators, process memory, whole-device rollback and secure erasure remain outside the guarantees in SECURITY.md.
Implemented adapters cover Windows DPAPI, macOS Keychain, Linux Secret Service and owner-file custody. Linux end-to-end validation uses owner-file custody with the real daemon, private memory, an original binary file and a synthetic UTF-8 credential. It covers keys-only restart, CLI/stdin-stdout MCP, ordinary/cloud/RAG denial, manual inhibition across restart, resume, revocation and disable. Linux custody tests and an isolated D-Bus protocol fixture provide additional coverage, but the fixture isn't a live desktop keyring test.
Windows/macOS native roundtrips, native locked/approval-needed and no-UI behavior, and live desktop keyring verification remain release gates. Cross-target builds or tests of error translation don't establish those native behaviors. Independent review is pending; passing tests aren't a security audit. See the CLI implementation notes and core contract for focused validation commands.