Persistent, multimodal AI memory infrastructure for every model and agent — conversations, documents, spreadsheets, audio, video, images. Productivity intelligence for individuals and enterprises. You
Developer toolsRust · ShellGitHub @memorylake-aiOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
MemoryLake CLI
Translation pending
Taken from the repository README
Loading the full README. The saved excerpt is shown below.
Command-line interface for MemoryLake. Upload files,
store and search memories, connect databases, publish skills, talk to agents, and
manage the workspaces, projects, actors and teams they all belong to.
New to MemoryLake? The documentation overview
explains what the platform does and the concepts these commands operate on.
Working inside a coding agent? MemoryLake Harness
plugs the same memory into Claude Code, Codex, DeepSeek Harness, opencode,
QwenPaw, QwenWork and WorkBuddy. Its plugins fetch and log in this CLI for you,
so there is nothing to install here first.Read the full README ↗Command-line interface for MemoryLake. Upload files, store and search memories, connect databases, publish skills, talk to agents, and manage the workspaces, projects, actors and teams they all belong to.
New to MemoryLake? The documentation overview explains what the platform does and the concepts these commands operate on.
Working inside a coding agent? MemoryLake Harness plugs the same memory into Claude Code, Codex, DeepSeek Harness, opencode, QwenPaw, QwenWork and WorkBuddy. Its plugins fetch and log in this CLI for you, so there is nothing to install here first.
Every command prints the API response as pretty JSON, so anything here pipes into
jq.
macOS / Linux
curl -fsSL https://raw.githubusercontent.com/memorylake-ai/memorylake-cli/main/scripts/install.sh | sh
Windows (PowerShell)
irm https://raw.githubusercontent.com/memorylake-ai/memorylake-cli/main/scripts/install.ps1 | iex
Both verify the download against its published SHA-256 and refuse to install on a
mismatch. Re-running upgrades in place. On a first install they walk you through
logging in and picking a workspace; MEMORYLAKE_VERSION,
MEMORYLAKE_INSTALL_DIR, MEMORYLAKE_INSTALL_NAME and MEMORYLAKE_NO_SETUP
override the details — see the comments at the top of either script.
If Claude Code, Codex, Gemini CLI or opencode is set up on the machine, the
installers also give it the skills that teach it this CLI (see
Skills for coding agents), and refresh them on
every upgrade. Opt out with --no-skills or MEMORYLAKE_NO_SKILLS=1;
memorylake agent-skills uninstall removes them later.
Supply the credentials and nothing is asked — useful for a link handed out by a console, and for CI, where there is no one to prompt:
curl -fsSL https://raw.githubusercontent.com/memorylake-ai/memorylake-cli/main/scripts/install.sh \
| sh -s -- --api-key sk-… --workspace ws-… [--base-url URL]
# `irm | iex` cannot pass parameters, so Windows uses the environment
$env:MEMORYLAKE_API_KEY='sk-…'; $env:MEMORYLAKE_WORKSPACE='ws-…'
irm https://raw.githubusercontent.com/memorylake-ai/memorylake-cli/main/scripts/install.ps1 | iex
Every flag has an environment variable (MEMORYLAKE_API_KEY,
MEMORYLAKE_WORKSPACE, MEMORYLAKE_BASE_URL); a flag wins over its variable.
Supplied credentials replace whatever is already stored — that is the point
when a console hands out a key. The key is validated before anything is written,
so one that will not work leaves your existing configuration untouched.
A key on the command line is recorded in your shell's history. Prefer a short-lived key where that matters.
Prefer not to pipe a script into your shell? Download an archive from the
releases page, check
it against its .sha256, and put memorylake on your PATH.
Re-run the same install command. It replaces the binary in place and leaves your credentials and workspace alone — an install that is already logged in is not asked to set anything up again.
memorylake version # v20260818.1 — which release this is
A build that was not produced by the release workflow says so (0.1.0 (dev build)), so it cannot be mistaken for one.
memorylake auth login # pick an endpoint, then paste your API key
memorylake workspace use # pick a default workspace from a list
memorylake project create --name "Research" --custom-id research-1
memorylake lib upload ./report.pdf
memorylake project document import --project proj-… <item-id> --wait
memorylake search "what were the quarterly revenue figures"
workspace use remembers a workspace, so --workspace can be omitted everywhere
after it. Pass it explicitly to override for a single command.
For more than six lines, memorylake-usecases
has runnable end-to-end demos built on this CLI — sales calls, meetings, a
codebase, contract review, bookkeeping, agent memory and more — each printing
every memorylake command before it runs it, with a recording of the run.
Aliases: ws = workspace, proj = project, lib = library,
doc = document, db = project database, conv = conversation,
msg = message, dbconn = db-connection, ds = datasource,
key = api-key, invite = invitation.
memorylake auth login # interactive: endpoint, then API key
memorylake auth login --api-key sk-… [--base-url URL] [--profile NAME]
memorylake auth status # who am I, and where did it come from
memorylake auth switch <profile>
memorylake auth refresh
memorylake auth logout
Create an API key in the MemoryLake console first; see Authentication and API keys.
Interactive login offers the Global (app.memorylake.ai) and China
(app.memorylake.cn) endpoints, or a URL you type. They are separate
deployments: an account on one does not exist on the other. Choose one
non-interactively with --base-url.
memorylake ws list [--name FUZZY] [--page-size N] [--continuation-token TOKEN]
memorylake ws create --name "My Workspace" --custom-id my-ws-001
memorylake ws get <id> [--by-custom-id]
memorylake ws update <id> [--name NAME] [--description D] [--metadata '{"team":"core"}']
memorylake ws delete <id> --confirm "My Workspace"
memorylake ws use # pick a default from a list
memorylake ws use <id> # or name one
memorylake ws current # which one is in effect, and why
memorylake ws use --clear
update changes only the fields you pass. --metadata takes a JSON object of
strings and replaces the stored map; --metadata '{}' clears it.
delete is irreversible and takes everything in the workspace with it, so it
does not run on an id alone: --confirm must repeat the workspace's current
name exactly (ws get <id> shows it), or nothing is deleted. The team's
default workspace (custom_id _sys_default_workspace) is refused. A profile
that remembered the deleted workspace forgets it.
An actor is who a memory is attributed to. Actors exist account-wide and must be bound to a workspace to participate there.
memorylake actor create --custom-id user-001 --display-name "Alice Chen" \
[--description TEXT] [--tags vip,cn] \
[--metadata '{"tier":"premium"}']
memorylake actor list [--name FUZZY] [--tags vip,cn] [--page-size N]
memorylake actor me
memorylake actor get <id> [--by-custom-id]
memorylake actor update <id> [--display-name NAME] [--description D] \
[--tags vip,cn | --clear-tags] [--metadata JSON]
memorylake actor delete <id>
memorylake actor bind --actor <id> [--workspace <id>]
memorylake actor unbind --actor <id> [--workspace <id>]
memorylake actor list --workspace <id> # bindings, not actors
--custom-id is unique account-wide. On update, --metadata replaces the
stored value rather than merging into it.
actor me reports the actor your API key represents, so a script never has to
guess which of several human actors is yours:
memorylake actor me | jq -r .id
It is not necessarily bound to a workspace — the actor comes with the
account, while joining a workspace is a separate step. To know who can write in
one, use actor list --workspace <id>; to put this actor there, actor bind.
Tags are short labels for grouping and filtering: up to 20 per actor, each 1-64
characters, no commas. Matching is exact and case-sensitive — VIP and vip are
two different tags. Prefer them over --metadata for anything you want to filter
on, because metadata is not filterable.
memorylake actor list --tags vip # actors tagged vip
memorylake actor list --tags vip,cn # tagged BOTH vip and cn, not either
--tags on update replaces the whole list, like --metadata; --clear-tags
removes every tag. Leaving both out keeps the actor's tags as they are.
A project holds documents, conversations and the facts extracted from them.
memorylake proj list [--name FUZZY] [--page-size N] [--continuation-token TOKEN]
memorylake proj create --name "My Project" --custom-id my-proj-001 [--description D] \
[--opendata-ids research/academic,financial/markets]
memorylake proj get <id> [--by-custom-id]
memorylake proj update <id> [--name NAME] [--description D] \
[--opendata-ids IDS | --clear-opendata]
memorylake proj delete <id> # fails, deleting nothing, if the id names no project
memorylake proj stats <id> # document/database counts by processing status
--custom-id is unique within the workspace. --opendata-ids attaches public
opendata (see opendata list); on update it replaces the attached set,
--clear-opendata detaches them all, and leaving both out keeps them.
stats takes the project id only, not its custom_id.
The Library is MemoryLake's file system. MY_SPACE is the workspace root, and is
accepted anywhere an item id is.
memorylake lib list [<item-id>] [--page-size N] [--continuation-token TOKEN] [--xattr-keys k1,k2]
memorylake lib get <item-id>
memorylake lib mkdir "Reports" [--parent <item-id>] [--on-conflict rename|deny] [--xattrs JSON]
memorylake lib upload ./report.pdf [--parent <item-id>] [--name NAME] \
[--on-conflict rename|deny|overwrite|replace] [--xattrs JSON]
memorylake lib delete <item-id>
memorylake lib xattr set <item-id> --attrs '{"team":"core"}'
memorylake lib xattr delete <item-id> --keys team,owner
Extended attributes are string key/value tags on an item. xattr set merges
into what is there; --xattrs sets them when the item is created; list --xattr-keys limits the x_attrs shown to the keys you name. System keys such
as x_source cannot be changed, and the server reports success without
changing them.
upload streams the file in parts and retries transient failures; the file
appears only once it completes. --on-conflict decides what happens when the
name is taken — the default rename appends _N, so read the name in the
output rather than assuming the one you asked for. overwrite and replace
apply to files only.
Documents are Library files imported into a project and indexed for search.
memorylake proj doc import --project <id> <item-id>... \
[--recursive] [--max-files 500] [--wait] [--timeout 600]
memorylake proj doc list --project <id> [--name FUZZY] [--page-size N]
memorylake proj doc get --project <id> <doc-id>
memorylake proj doc download --project <id> <doc-id> [-o PATH] [--force]
memorylake proj doc delete --project <id> <doc-id>...
memorylake proj doc inspect --project <id> <doc-id>... # up to 100
memorylake proj doc reload --project <id> <doc-id>
Upload files with lib upload first. A folder id needs --recursive, and
--max-files caps how many files one command may import.
Importing is asynchronous: the command returns once the server accepts the batch,
and each document moves through pending / running to okay or error.
--wait polls until they settle. Giving up does not cancel anything — the import
carries on server-side.
success_count and each detail's result describe acceptance, not processing.
Under --wait the printed payload also gives each detail its final status
(plus error when it failed) and a processing tally of okay / error /
timed_out, and the command exits non-zero unless every document reached okay.
The API answers 200 even when individual files fail, so the CLI prints the full
payload and then exits non-zero if anything went wrong. Files already in the
project count as duplicates, not failures.
download writes the original file under the name the server reports, in the
current directory. -o takes a file path or a directory, and -o - streams to
stdout for piping. An existing file is never replaced without --force.
inspect returns what processing produced for each document, including
pre-signed links to the stored artifacts; anyone holding such a link can fetch
the file until it expires, so treat the output as sensitive. Documents in
error have nothing to inspect and are left out; the CLI names them on stderr.
reload re-queues a document that ended in error; any other status is
refused.
A project can use a relational database as memory. Three links make the chain:
a connection (db-connection, account-wide) holds how to reach a database
and its credentials; a datasource (datasource, in a workspace) picks one
schema of a connection and indexes it; a database memory (proj db) adds a
datasource of the project's workspace to the project. Create them in that order,
and delete them in the reverse one — a connection or datasource still in use
cannot be deleted.
memorylake dbconn test --host H [--port 5432] --username U --database D --password-env PGPASSWORD
memorylake dbconn create --name N --host H [--port P] --username U --database D \
(--password-env VAR | --password-stdin | --password-file PATH) [--description D] [--custom-id ID]
memorylake dbconn list [--name FUZZY] [--page-size N] [--continuation-token TOKEN]
memorylake dbconn get <id> [--by-custom-id]
memorylake dbconn update <id> [--name N] [--description D] [--host H] [--port P] \
[--username U] [--database D] [password source]
memorylake dbconn schemas <id>
memorylake dbconn delete <id>
memorylake ds create --connection <conn-id> --schema public --name N \
[--description D] [--custom-id ID] [--table-filter REGEX]
memorylake ds list [--connection <conn-id>] [--name FUZZY] [--page-size N]
memorylake ds get <id> [--by-custom-id]
memorylake ds update <id> [--name N] [--description D] [--table-filter REGEX]
memorylake ds build <id>
memorylake ds tables <id> [--name FUZZY]
memorylake ds columns <id> --table T
memorylake ds annotate <id> --table T [--column C] [--comment TEXT] [--embedding true|false]
memorylake ds annotate <id> (--edits '<json array>' | --edits-file edits.json)
memorylake ds delete <id>
memorylake proj db create --project <id> --datasource <ds-id> --name N \
[--instruction TEXT | --instruction-file PATH]
memorylake proj db list --project <id> [--page-size N]
memorylake proj db get --project <id> <db-id>
memorylake proj db update --project <id> <db-id> [--name N] \
[--instruction TEXT | --instruction-file PATH | --clear-instruction]
memorylake proj db reload --project <id> <db-id>
memorylake proj db generate-instruction --project <id> <db-id>
memorylake proj db delete --project <id> <db-id>
There is no --password flag, because a value there ends up in shell history.
Pass the password through an environment variable, standard input, or a file, or
type it at the prompt when running interactively. It is write-only: no command
prints it, and -vvv traces show REDACTED in its place. Changing a
connection's host, port, user or database means passing the password again.
Run dbconn test before create, with the same host, port, username, database
and password source: it saves nothing and names credential and reachability
problems, while a failed create may only say INTERNAL_ERROR. Should a
server error quote the password back, the CLI prints REDACTED in its place.
A datasource covers exactly one schema for now; dbconn schemas lists the
choices. Creating a datasource starts its first index build, and ds build
starts another. Both run in the background — ds get shows a non-empty
building_version until the build finishes. Annotations and --embedding
changes take effect on the next build.
generate-instruction drafts an instruction and prints it as
{"instruction": ...} without saving it. To keep it, capture it first and save
it only if that worked (jq -e fails when there is no draft):
draft=$(memorylake proj db generate-instruction --project P DB | jq -er .instruction) \
&& memorylake proj db update --project P DB --instruction "$draft"
--instruction-file drops one trailing newline and refuses empty or blank
content, so an empty pipe can never wipe a saved instruction. To remove the
instruction on purpose, use proj db update ... --clear-instruction.
A fact is one remembered statement, owned by exactly one actor, project, or agent.
<scope> below is one of --actor <id>, --project <id>, or --agent <id>.
memorylake fact add <scope> "fact text" ["another" ...]
memorylake fact get <scope> <fact-id>
memorylake fact update <scope> <fact-id> [--text TEXT] [--metadata '{"k":"v"}']
memorylake fact trace <scope> <fact-id>
memorylake fact delete <scope> <fact-id>...
memorylake fact list [--actors a,b] [--projects a,b] [--agents a,b] [--query TEXT]
[--page-size N] [--continuation-token TOK]
Facts are stored verbatim and are searchable immediately. fact update edits
one in place; its --metadata replaces the stored object whole ('{}' clears
it). fact trace shows every add, edit, and forget, newest first — forgotten
facts included. fact list needs at least one of --actors / --projects /
--agents (50 distinct owners at most) and tags each fact with its owner.
The server records contradictions between a scope's facts (and between a project's facts and its documents) as conflicts for review:
memorylake fact conflict list <scope> [--resolved BOOL] [--category m2m|m2d|self]
[--conflict-type logical|knowledge] [--stale BOOL] [--page-size N]
memorylake fact conflict get <scope> <conflict-id>
memorylake fact conflict resolve <scope> <conflict-id> --strategy dismiss
memorylake fact conflict resolve <scope> <conflict-id> --strategy keep_fact --keep-fact-id <fact-id>
memorylake fact conflict resolve <scope> <conflict-id> --strategy edit_fact --edit "<fact-id>=new text"
m2m conflicts take keep_fact or dismiss, m2d take trust_fact,
trust_document, or dismiss, and self take edit_fact or dismiss.
Each scope's fact instruction — Markdown saying what its memory is about and what it is not — steers what the server records there:
memorylake fact instruction get <scope>
memorylake fact instruction set <scope> (--text TEXT | --file instruction.md | --file -)
memorylake fact instruction clear <scope>
memorylake fact instruction draft <scope> [--guidance TEXT] [--language "Simplified Chinese"]
[--use-existing-facts] [--use-documents]
set replaces the instruction whole (2000 characters at most); clear restores
the built-in default. draft asks a language model for a candidate and takes a
few seconds; it saves nothing — review it, then set it. --language is an
English language name, not a locale tag like zh-CN.
A conversation is a log of messages that the server turns into memory in the background.
memorylake conv create --custom-id session-42 --project <id> --actors a1[,a2] \
[--name "Q3 Planning"] [--kind DIRECT|GROUP] [--metadata k=v ...]
memorylake conv list [--page-size N] [--continuation-token TOKEN]
memorylake conv get <id> [--by-custom-id]
memorylake conv cook-status <id> [--by-custom-id]
memorylake conv delete <id>
memorylake conv msg append <conv-id> --actor <id> --custom-id msg-42 \
(--text "hello" ... | --content-json '<blocks>' | --content-file blocks.json) \
[--parent <msg-id>] [--timestamp ISO8601] [--metadata k=v ...] [--wait [--timeout 600]]
memorylake conv msg list <conv-id> [--page-size N] [--continuation-token TOKEN]
memorylake conv msg get <conv-id> <msg-id>... # up to 100, in the order given
memorylake conv fact-actions <conv-id> (--project <id> | --actor <id>) \
[--message <msg-id>] [--by-custom-id] [--page-size N] [--continuation-token TOKEN]
memorylake conv consumed-messages <conv-id> --message <msg-id> [--by-custom-id]
Message content is a list of typed blocks. Each --text becomes one TEXT
block; use --content-json / --content-file for FILE, IMAGE, THINKING,
TOOL_USE and TOOL_RESULT.
Every message names the one it follows. Without --parent the command looks the
conversation's latest message up for you, which is why it needs a workspace then
— pass --parent <id> to skip the lookup.
Appends to one conversation are serialized, so two at once leave one caller with
a 409. Retrying is safe because --custom-id makes it idempotent — the same id
returns the message created the first time. After a 409, re-read msg list and
retry with --parent set to the current last message.
Memory lags messages: an appended message is stored immediately but is not
searchable until the server has processed it. cook-status reports when that is
done, and --wait polls for you. Facts drawn from a conversation are attributed
by the server to an actor or a project as it sees fit, so look under both
fact list --actors and fact list --projects.
fact-actions is the audit trail of that process: which facts a conversation
added, changed or removed in one project's or one actor's memory (an agent's
memory is its actor's). consumed-messages lists the messages the server read
when it processed a given one. For both, --by-custom-id applies to the
conversation only; message ids are always internal ids.
memorylake agent list [--name FUZZY] [--page-size N]
memorylake agent create --name "Support" --custom-id support-1 \
[--model M] [--system-prompt P] [--description D] [--config agent.json]
memorylake agent get <id> [--by-custom-id]
memorylake agent update <id> [--name NAME] [--description D] [--config identity.json]
memorylake agent delete <id>
memorylake agent fork <id> --custom-id support-2 [--name NAME] [--metadata '{"k":"v"}']
memorylake agent version create <id> [--model M] [--system-prompt P] \
[--config version.json] [--from-version latest|N]
memorylake agent version list <id>
memorylake agent version get <id> <version>
memorylake agent bind <id> --workspace <id>
memorylake agent unbind <id> --workspace <id>
memorylake agent bindings [--workspace <id>] [--name FUZZY]
Creating an agent also creates an actor identity for it, returned as actor_id.
An agent works only in the workspaces it is bound to.
fork copies an agent into a new one under a new custom_id; the name defaults
to the original's with (copy) appended. External agents cannot be forked.
Changes split in two. agent update changes identity — name, description,
metadata — in place. Anything about behaviour (model, system_prompt,
policies, capabilities, output, subagents, skills, …) creates a new
immutable version through agent version create; passing one of those to
update is rejected up front.
Structured fields come from a JSON file:
cat > agent.json <<'JSON'
{
"name": "Support",
"custom_id": "support-1",
"model": "claude-sonnet-4-20250514",
"policies": { "max_turns": 8, "deny_tools": ["shell"] }
}
JSON
memorylake agent create --config agent.json
memorylake agent create --config agent.json --model X # flags win over the file
Unknown top-level keys are forwarded to the API unchanged, so a newer server
field works without upgrading the CLI. --from-version latest|N starts from an
existing version and applies your overrides on top, replacing whole top-level
keys rather than deep-merging.
Every agent bound to a workspace answers over the
A2A protocol. The CLI speaks A2A v1.0 over
HTTP+JSON; --workspace defaults to the remembered one, as for agent bind.
MemoryLake's A2A integration guide
documents what these commands send: the base URL and protocol versions, the
metadata.memorylake extension (memory, identity, run configuration), the
REST and JSON-RPC transports, streaming, and errors.
memorylake agent card <agent-id> [--workspace <id>]
memorylake agent send <agent-id> --text "..." [--text "..."] \
[--context CTX] [--task TASK] [--stream | --no-wait] [--raw] \
[--actor ID] [--project ID] [--read-only-project ID]... [--skip-memory] \
[--metadata-json '{"overrides":{...}}'] [--workspace <id>]
memorylake agent send <agent-id> --message-json '[{"text":"..."},{"url":"...","mediaType":"image/png"}]'
memorylake agent send <agent-id> --message-file parts.json
memorylake agent task list <agent-id> [--context CTX] [--status STATE] \
[--page-size N] [--page-token TOK] [--after TIMESTAMP] [--history-length N] [--artifacts]
memorylake agent task get <agent-id> <task-id> [--history-length N]
memorylake agent task subscribe <agent-id> <task-id> [--raw]
memorylake agent task cancel <agent-id> <task-id>
memorylake agent task feedback <agent-id> <task-id> --rating up|down [--comment TEXT]
send waits for the answer and prints the reply text on stdout; the task id,
context id and final state go to stderr, so a script can pipe the reply and
still know how to continue:
$ memorylake agent send agent-… --project proj-… --text "Summarize yesterday's standup"
The team agreed to …
task run-… context 5fdb… state TASK_STATE_COMPLETED
Pass --context to keep talking in the same thread. A task that ends in
TASK_STATE_INPUT_REQUIRED needs more from you: reply with --task <id> --context <id>. --stream prints the reply as it is produced; --no-wait
returns the task as soon as it exists; --raw prints the protocol response as
JSON instead of the reply text.
After --no-wait, agent task subscribe is the way to get the reply: it prints
like --stream, and replays the whole stream of a task that already finished.
agent task get and agent task list report state only — the server does not
keep history or artifacts yet, so those come back empty even with
--history-length and --artifacts.
A task that ends FAILED, CANCELED or REJECTED exits non-zero. So does a
stream that closes before its task settles; the error names the
agent task subscribe command that picks it up again.
The --actor, --project, --read-only-project and --skip-memory flags set
MemoryLake's extension of the request (metadata.memorylake): whose message it
is, which projects the agent may read and write, and whether the exchange is
remembered at all. Anything else the extension accepts, such as overrides,
goes through --metadata-json. The server refuses a message without the
extension, continued conversations included, so every send needs at least one
of these — usually --project or --skip-memory; the CLI says so before
sending.
Feedback is a MemoryLake extension to A2A. task feedback records a rating on
the task; task get reads it back under metadata."task-feedback/v1".
memorylake skill list [--name FUZZY] [--page-size N] [--continuation-token T]
memorylake skill create --name equity-research --title "Equity Research" \
[--description D] (--package skill.zip | --package-uri URI)
memorylake skill get <id>
memorylake skill update <id> [--title T] [--description D]
memorylake skill delete <id>
memorylake skill upload skill.zip # prints {"s3_uri": ...}
memorylake skill version create <id> (--package skill.zip | --package-uri URI) [--changelog TEXT]
memorylake skill version list <id>
memorylake skill version get <id> <version>
A skill is a ZIP archive with a SKILL.md at its root or one directory down, at
most 10 MiB. --package uploads the archive and publishes it in one step; the
CLI takes a ready-made .zip rather than a directory, so zip it first
(cd my-skill && zip -r ../my-skill.zip .). skill upload only uploads and
prints the storage URI, which --package-uri accepts in place of --package.
If a --package publish fails after the upload, the error names that URI so
the retry can skip the upload. Skill names must be unique in the team.
Every published version goes through a security review: it starts pending
and settles on safe, blocked or error — skill get shows the latest
version's state as latest_security_status. Only a reviewed (safe) skill can
be referenced from an agent, as "skills": [{"skill_id": "...", "skill_version": N}]
in an agent create / agent version create config (omit skill_version to
follow the latest); anything else is refused with SKILL_NOT_USABLE. Publishing
a new version makes agents that follow the latest wait for its review, while
agents pinned to an older version are unaffected.
memorylake agent-skills list # bundled skills, and which agents were found
memorylake agent-skills install [--target claude|agents|all]... [--project] [--force]
memorylake agent-skills status [--target ...] [--project]
memorylake agent-skills uninstall [--target ...] [--project] [--force]
The CLI carries skills that teach coding agents how to drive it, and install
writes them where those agents look for skills:
| Target | Directory | Read by |
|---|---|---|
claude | ~/.claude/skills/ | Claude Code, opencode |
agents | ~/.agents/skills/ | Codex, Gemini CLI, opencode, and other agents that follow the Agent Skills standard |
Without --target, install picks the targets whose agent is set up on this
machine (~/.claude for the first; ~/.agents, ~/.codex, ~/.gemini or
~/.config/opencode for the second) and installs nothing when none is.
--project uses the current directory's .claude/skills/ and
.agents/skills/ instead, for skills committed with a repository.
The skills come from the binary itself, so they always describe the commands
of the version installed; re-run install after upgrading. Each installed
directory records what was written to it. A directory install did not write
is never touched, and one edited since it was written is kept until --force
says otherwise — for uninstall as well. Skills a newer release no longer
carries are removed on install.
These are not the skills memorylake skill publishes: those live in
MemoryLake, for MemoryLake agents to use.
memorylake boundary list [--workspace <id>] [--name FUZZY] [--page-size N] [--continuation-token T]
memorylake boundary create --name NAME [--workspace <id>] [--custom-id ID] \
[--projects ID,ID] [--human-actor ID] [--agent ID]
memorylake boundary get <id> [--by-custom-id]
memorylake boundary update <id> [--name NAME] \
[--projects ID,ID | --clear-projects] [--human-actor ID | --clear-human-actor] \
[--agent ID | --clear-agent]
memorylake boundary delete <id>
A boundary is a named, saved search scope inside a workspace: a set of projects
plus, optionally, one human actor and one agent whose memories are in scope.
Everything it names must belong to its workspace — projects must exist there,
and the actor and agent must be bound to it — and you need search permission on
each. --projects on update replaces the list rather than adding to it.
memorylake search "what were the quarterly revenue figures"
memorylake search "quarterly revenue" \
--projects proj-1,proj-2 --actors act-1 --types document,fact,database --top-k 10 --effort high
Returns one ranked list split into documents, facts and databases. Every
entry carries a rank that is unique across the three, so sorting by it
restores the overall order. The score on fact hits is exactly 1 / rank, so
it says nothing rank does not: a threshold such as score >= 0.3 keeps the top
three hits whatever they are, not the relevant ones. Filters take one comma-separated value each
(--projects a,b). Omitting --projects or --types searches all of them, but
omitting --actors searches only your own actor's memories. --top-k (1-1000,
default 10) caps the total across all types, not each type. --effort
(max, high, low) trades result quality for speed: max is the full
search, high skips query expansion from stored facts and uses a lighter final
ranking, low also skips model-based query understanding and narrows recall.
Lower levels may return fewer or different results; omit it for the server's
default. There is no pagination.
memorylake router models [--protocol openai|anthropic] # what you can call, with prices
memorylake router chat "Where do I live?" --model gpt-4o-mini --boundary bnd-xxxxxx
memorylake router responses "Where do I live?" --model gpt-4o-mini --boundary bnd-xxxxxx --stream
memorylake router messages "Where do I live?" --model claude-haiku-4-5 --boundary bnd-xxxxxx --text
memorylake router embed "text one" "text two" --model MODEL [--dimensions N]
memorylake router image "a small red circle" --model gpt-image-1 --size 1024x1024 --quality low [-n N]
MemoryRouter calls OpenAI- and Anthropic-compatible models with a Boundary's
memory added to the prompt. chat, responses and messages speak OpenAI Chat
Completions, OpenAI Responses and Anthropic Messages; --boundary names the
Boundary, and without it the model is called with memory off. Use the model
names router models lists: openai models with chat / responses,
anthropic models with messages.
--system, --max-tokens and --temperature set the common fields; anything
else goes in --body FILE (- for standard input) as a request in the
provider's own format, with the flags applied on top and the prompt appended to
its conversation. Output is the provider's JSON; --text prints only the reply
text, --stream prints it as it arrives, and --stream --raw prints each
event. The response's X-Trace-ID goes to standard error — quote it to support.
image generates pictures (OpenAI Images, no Boundary): --size, --quality,
--output-format, --background and -n set the common fields, and the reply's
data[].b64_json holds each base64-encoded picture.
Calls are billed to your MemoryLake balance.
memorylake opendata list # ids accepted by `proj create|update --opendata-ids`
The team your API key belongs to — its API keys, members, invitations and usage — is managed with the same key and endpoint as everything above. The team is fixed by the key: nothing here takes a team parameter, and each command is authorized by what the key's creator may do in the console.
memorylake team get
memorylake team rename --name "New Name" # owner only
memorylake team create --name Lab [--issue-api-key [--api-key-name NAME]]
memorylake team transfer-ownership --to <principal-id> --confirm <team-id> # owner only
memorylake team delete --confirm <team-id> # owner only, PERMANENT
memorylake key list [--name FUZZY] [--status enabled|disabled|expired] [--owner <principal-id>]
[--page-size N] [--continuation-token TOKEN]
memorylake key get <id>
memorylake key create --name ci [--member <principal-id>] [--expires-at UNIX_SECONDS]
[--allowed-ip IP_OR_CIDR]... [--group G] [--backup-group G]
memorylake key update <id> [--name N] [--status enabled|disabled] [--expires-at UNIX_SECONDS|0]
[--allowed-ip IP_OR_CIDR... | --no-ip-restriction] [--group G] [--backup-group G]
memorylake key rotate <id>
memorylake key revoke <id> # alias: key delete
memorylake role list # what --role below accepts
memorylake member list [--name FUZZY] [--role KEY] [--email ADDR]... [--type human|virtual] [--page-size N]
memorylake member get <principal-id>
memorylake member create --name "CI Bot" --role tenant_member # virtual member
memorylake member update <principal-id> [--role KEY] [--max-tokens N|0]
memorylake member set-role <principal-id> --role tenant_admin
memorylake member remove <principal-id> # alias: member delete
memorylake invite create --email [email protected] --role tenant_member
memorylake invite list [--status pending|accepted|rejected|expired|revoked] [--email ADDR]...
memorylake invite get <id>
memorylake invite revoke <id> # alias: invite delete
memorylake usage [--start-date YYYY-MM-DD] [--end-date YYYY-MM-DD] [--daily] [--group-by member,model]
memorylake usage summary # today, last 7 days, month to date
memorylake usage records [--start-time UNIX] [--end-time UNIX] [--model M]... [--kind model_call|platform_task]
[--member <principal-id>]... [--request-id ID] [--page-size N]
key create and key rotate print the full key exactly once — list and get
only ever return its prefix, and an idempotent replay omits it too, so capture
it from the first response. When a response carries no key, a note on standard
error says so and how to get a new one. To tell which key a profile runs with,
compare the last four characters auth status shows with key_suffix in
key list: that key cannot revoke or disable itself.
A virtual member is a login-less identity for automations: create one with
member create, then issue it a key with key create --member <principal-id>.
That key acts with the virtual member's role instead of yours, so a CI job can
hold exactly the permissions it needs.
Creates, renames, role changes, removals and revocations take
--idempotency-key VALUE. Retrying with the same value replays the first result
instead of repeating the write — no duplicate key, member, team, or invitation
email. (key update and team delete take none: the API documents none.)
team delete and team transfer-ownership cannot be undone. Both read the team
first and refuse unless --confirm repeats the id team get reports, so a
command copied into the wrong profile does not hit whichever team that profile's
key belongs to. Deleting the team stops every key of it at once — including the
one that ran the command. team create --issue-api-key prints the new team's
owner key exactly once; the key you ran it with stays in its own team.
usage reports for the whole team when you may view it, otherwise for you
alone; scope says which. A report or a usage records window spans at most 92
days, both ends counted; when both ends are given, that is checked before
anything is sent. used_tokens is what is charged against the balance.
What a member may do comes from four places, each with its own commands:
read, write or manage on one instance, set per
member.memorylake perm me # what your key may do here
memorylake perm catalog [--access-group] # resource types and actions rules can name
memorylake member explain <principal-id> # every permission and instance, with its source
memorylake role get <key>
memorylake role create --label Reviewers [--description D] [--base-role tenant_member]
memorylake role update <key> [--label L] [--description D|""]
memorylake role delete <key>
memorylake perm rule list [--subject-type role|member [--subject-id ID]] [--page-size N]
memorylake perm rule create (--role KEY | --member <principal-id>) --resource memorylake:skill
--action skill:read[,skill:modify] [--auto-grant-prerequisites] --reason TEXT
memorylake perm rule delete <rule-id> [--reason TEXT]
memorylake member scope get <principal-id> # also: role scope get <key>
memorylake member scope set <principal-id> (--scopes JSON | --scopes-file PATH|-) --reason TEXT [--if-match ETAG]
memorylake ag list | get <id> | explain <id> | delete <id>
memorylake ag create --name Ops [--base-role tenant_member | --base-group <id>]
[--permission memorylake:actor:actor:create]...
memorylake ag update <id> [--name N] [--description D|""]
memorylake ag member list <id>
memorylake ag member add <id> <principal-id>... # up to 200, all or nothing
memorylake ag member remove <id> <principal-id>
memorylake ag permissions set <id> (--permission P... | --none) --reason TEXT [--if-match ETAG]
memorylake ag scope get|set <id> ...
memorylake access show <type> <id> # who can reach the instance, and why
memorylake access levels <type> <id> --member <principal-id>
memorylake access set-level <type> <id> --member <principal-id> --level read|write|manage|none --reason TEXT
memorylake access grant <type> <id> (--member <principal-id> | --group <id>) --reason TEXT
memorylake access revoke <type> <id> (--member <principal-id> | --group <id>) [--reason TEXT]
<type> is one of workspace, project, actor, agent, boundary,
db-connection, db-datasource, skill, workspace-actor and
workspace-agent. The last two are an actor's or agent's binding in one
workspace: --workspace <id>, or else the workspace workspace use remembered,
as for actor bind. A project accepts --workspace as well, so it shows by name
in access scopes; that one is never filled in for you. Nothing else takes it.
scope set takes a JSON array of {resource_type, mode, instances} entries,
where mode is all or instances, and replaces the scope of only the types
it lists. It also accepts what scope get printed, edited: read-only fields
are dropped. Entries that list no instances must be removed first: leaving a
type out keeps it as it is, and for an access group mode all removes its
instances. Actions a scope get reports as shadowed_actions (the role no
longer grants them) are left out of what is written back, since the server
refuses them. scope set --help shows the whole read-edit-write loop.
Reads that a later write can be checked against (scope get, ag get) print
an ETag: "..." line on standard error, leaving standard output as plain JSON.
Pass that value as --if-match (quotes optional; the whole line works too) and
the write is refused with 412 if someone changed the same thing in between. Scope, rule, level and grant changes take a
--reason (at most 255 bytes), which is recorded in the audit log.
Reads of the team's credit, invoices, orders, cards and subscription. Buying credit, changing the plan and managing cards happen in the console; the two settings a key can change are the low-balance alert and the buyer details printed on an invoice.
memorylake billing balance # tokens, by source, and the billing mode
memorylake billing alert get
memorylake billing alert set (--percent-used 80,95 | --tokens-remaining N,...) [--recipient EMAIL]... [--disabled]
memorylake billing alert enable|disable # keeps the levels and recipients
memorylake billing alert reset # back to the (enabled) defaults
memorylake billing alert events [--page-size N]
memorylake billing auto-topup get|attempts
memorylake billing transactions [--start-time UNIX] [--end-time UNIX] [--page-size N]
memorylake billing invoice list [--status paid,open,...] | get <id>
memorylake billing invoice pdf <id> [-o PATH|DIR|-] [--force]
memorylake billing invoice details get <id>
memorylake billing invoice details set <id> --type individual|business --address A [--name N] [--company C]
[--tax-id T] [--contact-email E]
memorylake billing order list [--type onetime|auto_topup|subscription] [--status S] | get <id>
memorylake billing payment-methods
memorylake billing subscription get | preview-change --plan <id> # other plan ids: console only
memorylake audit list [--category management|system] [--action A]... [--actor|--member <principal-id>]...
[--target-type T] [--target-id ID] [--result succeeded|failed|partial]
[--start-time UNIX] [--end-time UNIX] [--page-size N]
Alert levels are all --percent-used (1–99 of the cycle's tokens) or all
--tokens-remaining (1–1,000,000,000), up to five. Recipients are added to the
team owner, who is always notified. details set replaces every field: ones
left out are cleared.
audit list covers the last 7 days unless --start-time/--end-time say
otherwise, and at most 92 days; times are Unix seconds, 1 or later. Pages carry no total — follow the
continuation token, with the same filters, until it is gone. Without permission
to read the whole team's log you see your own events only. The endpoint has its
own rate limit (20 requests a minute per key), so page with --page-size 100.
Connect a DingTalk, WeCom, Feishu or Slack bot to an agent, so people can talk to the agent from their chat app. Which channels are available depends on the deployment.
memorylake im list [--workspace ID] [--agent ID] [--page-size N]
memorylake im get <id>
memorylake im create --channel slack --name Help --agent <agent-id> --read-write-project <project-id>
[--workspace ID] [--read-only-project ID]... [--dataset ID]...
[--daily-message-limit N] [--card-template-id ID]
(--credentials-env VAR | --credentials-stdin | --credentials-file PATH) [--wait [--timeout SECS]]
memorylake im update <id> [--name N] [--agent ID] [--read-write-project ID]
[--read-only-project ID... | --no-read-only-projects] [--dataset ID... | --no-datasets]
[--daily-message-limit N] [--card-template-id ID|""] [--enable | --disable]
[--credentials-env VAR | --credentials-stdin | --credentials-file PATH] [--wait [--timeout SECS]]
memorylake im delete <id>
Credentials are a JSON object keyed by the platform's own field names:
| Channel | Fields |
|---|---|
dingtalk | appKey, appSecret |
feishu | appId, appSecret |
slack | botToken, appToken |
wecom | botId, secret |
export SLACK_BOT='{"botToken":"xoxb-…","appToken":"xapp-…"}'
memorylake im create --channel slack ... --credentials-env SLACK_BOT --wait
There is no --credentials flag: a value there would be saved in shell
history. Credentials are write-only — the API never returns them, and -v
traces redact them. To rotate a secret, update with just that field (e.g.
{"appSecret":"…"}); the bot's identifier cannot change, so connecting a
different bot means delete and create.
DingTalk, Feishu and Slack credentials are verified with the platform before
anything is saved, so a bad token fails at once and leaves nothing behind.
WeCom's are checked by connecting: status goes pending → connected or
error, with status_error_code saying why. --wait follows it (two minutes
unless --timeout says otherwise) and fails if it ends in error. On update,
only new credentials or re-enabling a disabled integration start a
reconnection; --wait without either is refused, and when the update started
none it just prints the integration. If following the status fails, the error
names the integration, which was saved all the same.
Anyone who can message the bot can add memories to the --read-write-project,
so point it at a project meant for that.
Credentials and settings live in ~/.memorylake/ (credentials.toml,
config.toml), or in MEMORYLAKE_CONFIG_DIR if that is set.
Profiles keep several accounts or endpoints side by side: --profile selects one
for a single command, auth switch changes the default.
| Setting | Resolution order |
|---|---|
| API key | profile in credentials.toml → MEMORYLAKE_API_KEY |
| Base URL | --base-url → profile → MEMORYLAKE_BASE_URL → app.memorylake.ai |
| Workspace | --workspace → ws use → MEMORYLAKE_WORKSPACE |
| Config location | MEMORYLAKE_CONFIG_DIR → ~/.memorylake |
Home for agent-skills | MEMORYLAKE_AGENT_SKILLS_HOME → your home directory |
auth status and ws current both report which source won. There is no built-in
default workspace: with none remembered and none passed, a command that needs one
fails and says how to supply it.
A custom base URL, from any of those sources, must start with http:// or
https:// and carry no ? or #; a trailing / is dropped. MemoryLake's own
deployments serve the API under /openapi/memorylake, so the URL normally ends
there.
--yes flag anywhere. Deleting a project takes its documents and conversations
with it; deleting a Library folder takes everything inside it.continuation_token; pass it back
to fetch the next page.agent send exits
non-zero when the task fails, is cancelled or rejected, or when a stream
closes before the task settles.. or .. is
refused locally, so a stray argument cannot address a parent resource.-v / -vv raise log verbosity, and RUST_LOG is honoured.auth login asks for.agent send and agent task, for calling MemoryLake
agents from any A2A client.~/.memorylake/credentials.toml), so a machine set up for a harness
is already logged in here, and the other way round.See CONTRIBUTING.md for building, testing and releasing.
MIT