Skip to content

Command line

pfmem

A single-file client for the memory API, written to be driven by an AI harness. It holds one key and calls the memory endpoints. It cannot mint, rotate or revoke a key, create a workspace, or configure a provider — those are administration, and they live behind the admin API on purpose.

Install

The installer fetches the published manifest, downloads the bundle, verifies it against the manifest's sha256, and installs a launcher. It compiles nothing and needs no root.

install.sh
curl -fsSL https://pfmem-api.packagefactory.dk/install.sh | sh
export PATH="$HOME/.pfmem/bin:$PATH"
pfmem init

It needs curl, Node 22 or newer, mktemp and sed, and says which one is missing rather than failing later with a syntax error.

Two environment variables move the installation: one relocates the whole thing, the other points it at a different deployment.

environment
PFMEM_HOME="$HOME/.local/pfmem"
PFMEM_ORIGIN="https://pfmem-api.packagefactory.dk"

The stdout contract

The primary caller is a model, so standard output is always exactly one JSON document. Everything written for a human — prompts, warnings, update notices — goes to standard error, where a parser never sees it.

stdout
{"ok":true,"request_id":"…","data":{…}}
{"ok":false,"error":{"code":"…","message":"…","request_id":"…"}}

Check the success flag first. On a failure, switch on the error code and never on the message; messages are written for people and change between releases.

Three invocations are exempt, because a human is the reader: the reference page prints markdown, help prints help text, and the version flag prints a bare version string. Nothing else puts anything but one JSON document on stdout.

One flag indents the JSON. It changes the whitespace and nothing else.

Exit codes

Scripts switch on these numbers, so they are part of the interface.

Exit codes and what they mean
CodeMeaning
0The call succeeded.
1Anything else.
2The arguments were wrong. Nothing was sent.
3The key was missing or is not accepted.
4The key lacks the verb, or the target is out of its scope.
5The request was well-formed but failed validation.
6A quota or rate limit was hit.
7The service could not serve the request. Retryable.
8The request never reached the service.
9The thing addressed does not exist.

The key, and where it lives

Give the key on standard input, or let it prompt when there is a terminal. Both keep the secret out of the process list, where ps shows it to every user on the machine, and out of your shell history. The flag that takes a key as an argument exists, and is the worst of the three.

pfmem init
cat key.txt | pfmem init
pfmem init --origin https://api.example.com

The read from standard input is bounded at 8 KB and ten seconds, so a harness that leaves the pipe open but idle gets a usage error naming its alternatives rather than hanging forever.

The key is read from the environment first, then from the config file, which is created readable only by its owner. It is never printed, never logged, and never echoed back by the diagnostic command.

A key read from that file is only ever sent to an https origin or to loopback. A stored credential rides along on every later invocation without anyone thinking about it, so an origin naming a plaintext host is refused rather than obeyed.

Overriding the origin for one call never repoints where the client fetches its own code. A per-invocation flag must not be able to nominate an installer.

What the client will not send

There is no flag for a workspace, an account, a filter or an embedding version, and a test asserts that none can be added. The key resolves to all of it server-side. Supplying any of them is precisely how a client would read someone else's memories.

Two scope flags exist, and they only ever narrow within the workspace the key already resolved to. Omitting them spans everything that key can already see; neither can widen anything.

--app-id · --project-id
pfmem search --memory-type user --user-id u_42 --query "retry policy" --app-id support-bot
pfmem delete --created-before 1735689600000 --project-id billing

Commands

The reference command prints all of this in full — every flag, every constraint, every response shape — generated from the same specification the argument parser uses, so it cannot describe a flag the client would reject. That is the page to give a model.

Commands and what each one does
CommandWhat it does
addIngest messages for extraction. Asynchronous; returns a task id.
searchRank memories by relevance to a natural-language question.
getList a subject's memories, filtered and paginated.
editCorrect one memory in place.
deleteDelete memories by id, by session, or by age.
flushForce a session's pending extraction to run now.
task · tasksRead one background task, or list them.
init · doctor · logs · docs · update · versionLocal. They send no API request, though two of them still reach the network.
skill install|status|updateInstall, check or update the skill an agent reads.

There is no command that stores raw text without extraction, and none that recalls a transcript. Reading before an edit is a listing, because an edit needs the memory's id and its version.

Global flags

These apply to every command.

Flags accepted by every command
FlagWhat it does
--prettyIndent the JSON written to stdout.
--verboseRecord request diagnostics in the log file.
--origin <url>Override the API origin for this invocation.
--help, -hPrint help for the command and exit.
--version, -vPrint the version and exit.

Logs

A JSONL file, readable only by its owner, recording each request's method, path, status, duration and request id, plus failures. It rotates at 1 MB and keeps one generation.

pfmem logs
pfmem logs --lines 50

Credentials are redacted on the way in: field names like authorization, api_key and token; any key string anywhere in a message, a URL or a nested value; and any bearer header.

Quote the request id from a failure. The service records the same id, which is what makes a problem traceable from your terminal to our logs.

Updates

At startup the client checks the release feed, cached for six hours. When a newer version exists it prompts only if both standard input and standard error are a terminal; otherwise it writes one line to standard error and carries on. A harness is never blocked, and an unreachable update service never fails a memory call.

pfmem update
pfmem update
pfmem update --check

An update downloads, verifies the sha256, and replaces the bundle atomically. A check reports what is published and installs nothing.

Update and skill downloads require https, and the installer pins the protocol across redirects. A checksum published beside an artifact only proves the bytes match what that same server sent; over plaintext an attacker on the path supplies both, so it is the transport that makes the checksum mean anything. A plaintext origin is refused before any request is made.

One environment variable disables the check entirely.

environment
PFMEM_NO_UPDATE_CHECK=1

The skill

The client ships a skill: one file teaching an agent when to reach for memory and how to call it. Install it and your assistant stops guessing at the interface.

pfmem skill
pfmem skill install
pfmem skill install --dir DIR
pfmem skill status
pfmem skill update

With no directory given, a project-level configuration found by walking up from the current directory wins over the user-level one — standing in a repository means that repository. When neither exists it reports both places it looked and asks for a directory rather than guessing.