Metadata-Version: 2.4
Name: memfleet
Version: 3.0.2
Summary: The memfleet cloud client — connect a terminal to a Strata fleet on memfleet.com without leaving it.
Project-URL: Homepage, https://memfleet.com
Project-URL: Strata engine (open source), https://github.com/oren198/Strata
Requires-Python: >=3.11
Requires-Dist: cryptography
Requires-Dist: httpx
Requires-Dist: keyring
Requires-Dist: platformdirs
Requires-Dist: strata-mem>=1.6.1
Requires-Dist: tomli-w
Provides-Extra: dev
Requires-Dist: pytest; extra == 'dev'
Requires-Dist: ruff; extra == 'dev'
Description-Content-Type: text/markdown

# memfleet

Agent teams forget. **`memfleet`** makes them remember — with judgment,
provenance, and an operator in control.

`memfleet` is the command-line client for **memfleet.com**, the hosted platform
for [Strata](https://github.com/oren198/Strata), the open-source memory engine.
Connected in two commands, without leaving the terminal:

```
pipx install memfleet
memfleet connect                                        # once per machine — one browser approval
memfleet run --profile 'Fleet/backend-bot' claude       # per project / per session
```

`connect` links the **machine** to your account. `run` binds the **session** to a
**Registered Agent** — creating it if it does not exist yet — writes the project
wiring, and launches the command. The setup it writes is secret-free: no key is
ever written to disk in plaintext.

A teammate who holds only an **enrollment code** and no account of their own
reaches the same with:

```
memfleet run --code XXXX --name mate --scope g_backend claude
```

## What the fleet gets

- **Governed memory, not a shared scratchpad.** Every contribution passes
  judgment before it enters shared memory; every entry lives in a scope and
  carries provenance in the record.
- **The operator is sovereign.** See exactly what any scope's agents see, and
  steer memory directly. No hidden state.
- **Agents connect over MCP or REST** — the same service layer either way. A
  bind wires up the hosted `/mcp` endpoint; `/agent/v1` is the same service
  under a different door.
- **The engine is open source.** Run [Strata](https://github.com/oren198/Strata)
  yourself, or let memfleet run the fleet for you.

> ### A note on the version history
> Releases of the `memfleet` distribution **at or below 1.6.1** were the Strata
> *engine* — that package has been renamed and now ships as
> [`strata-mem`](https://pypi.org/project/strata-mem/) (import package and CLI
> still `strata`). **`memfleet` 2.0.0 was the first release of the cloud
> client**, and **3.0.0** is the `connect` / `run` split described here
> (a clean break — see *2.x → 3.0.0* below). The major-version jump at 2.0.0 was
> deliberate: the repurposed package's "latest" must never resolve to an old
> engine release.

## Install

Requires **Python 3.11+** (stdlib `tomllib`, and the `strata-mem` dependency
is 3.11+).

```bash
pipx install memfleet
# older default interpreter? point pipx at a newer one:
pipx install --python python3.11 memfleet
```

Depends on [`strata-mem`](https://pypi.org/project/strata-mem/) (the Strata
engine's install machinery); pip resolves it automatically. A `No matching
distribution found for memfleet` error from pip almost always means the
installing interpreter is older than 3.11 — note that **pipx uses its own
interpreter, not your shell's**: an apt-installed pipx on Ubuntu 22.04 runs
the system 3.10 even inside an activated 3.11 venv. Use
`pipx install --python "$(which python)" memfleet`, or plain
`pip install memfleet` inside any 3.11+ venv.

## The two-command story

| How often | Command | What happens |
|---|---|---|
| Once per machine | `memfleet connect` | Device-flow browser approval; the **owner token** lands in the OS keychain. Re-running it is a no-op that reports the existing connection — it asks nothing about agents. |
| Per project / per session | `memfleet run --profile '<Workspace>/<agent>' claude` | Binds to the Registered Agent — **minting a key** for one that exists, **creating** one that does not — writes the secret-free project setup (MCP config, contribution Stop-hook, the scope's skill), saves the **agent profile** as the project default, and execs `claude` with the **agent key** in its environment. |

`memfleet run` is a generic launcher: everything after the flags is the command
it starts (`claude`, `bash`, anything). The agent key is injected into **that
process's environment only** — it never touches a file or your shell, and dies
with the process. It is what the committed `.mcp.json`'s
`${MEMFLEET_AGENT_KEY}` resolves at session start.

Once a project is bound, later sessions are just:

```
memfleet run claude          # the project default profile, no network at all
```

That fast path is the common one: a key already stored for the resolved profile
means `run` execs immediately, making no request.

### Connecting more agents

- **Another project** — `cd` there and `memfleet run --profile '<Workspace>/<agent>' claude`.
  No browser step: the machine is already connected.
- **A second agent in the same project** (say, a different scope) —
  `memfleet run --profile 'Fleet/reviewer' --scope g_review claude`. Every bind
  saves its own profile (`<workspace>/<agent>`); the newest becomes the project
  default.
- **Choose per session** — `memfleet run --profile "Fleet/other-agent" claude`.
  Two terminals on the same repo can run as different agents concurrently
  (**sessions bind, not machines** — nothing is exported globally).
- **A teammate without owner access** — `memfleet run --code XXXX --name mate
  --scope g_backend claude` with an enrollment code you mint from the Console
  (no owner login involved).

## Commands

| Command | What it does |
|---|---|
| `memfleet connect` | Device-flow login; the **owner token** goes to the OS keychain. Idempotent, and asks no agent question. |
| `memfleet run [--profile X] [--code XXXX] [--scope ID] [--name N] [--] COMMAND…` | Bind (or create) the Registered Agent, wire the project, exec COMMAND with the **agent key** injected into its environment. |
| `memfleet status` | Report the resolved server, whether this machine is connected, what this directory binds to, and whether the scope's skill is installed. Read-only. |
| `memfleet logout` | Forget the stored owner token. |
| `memfleet workspaces list \| create` | Manage workspaces over `/api/v1/manage/*`. |
| `memfleet scopes list \| create` | List / define scopes (validated engine-side). |
| `memfleet agents list \| create \| revoke` | Manage Registered Agents. |
| `memfleet codes list \| create \| revoke` | Manage enrollment codes. |
| `memfleet profiles list \| use` | List agent profiles; `use` writes the project default. |
| `memfleet env [--profile X]` | Print eval-able `export` lines (shell/CI bridge; prints the key to stdout by design). |
| `memfleet setup` | (Re)write the additive project setup on its own. |

`--workspace` on `scopes`, `agents` and `codes` takes a Workspace **id or its
name** — the same reference `run --profile '<Workspace>/<agent>'` resolves.
`memfleet workspaces list` prints both.

### `memfleet status`

Read-only, always exits 0 — a network failure is reported as a line, never a
traceback — and it never prints a secret:

```
$ memfleet status
server   https://memfleet.com  (default)
account  connected (2 workspaces)
binding  profile Fleet/backend-bot  (project default)
         key     keychain
         agent   backend-bot — workspace Fleet, scope g_backend, skill code-writer
skill    installed (.claude/skills/code-writer/SKILL.md)
```

Each line answers one question:

- **server** — the resolved URL and *which* of the four precedence rungs decided
  it (`--server flag` / `MEMFLEET_SERVER` / `project .memfleet/config.toml` /
  `default`);
- **account** — whether an owner token is stored for that server, verified live
  with one `/manage/workspaces` call (`connected (N workspaces)` /
  `token invalid — run memfleet connect` / `not connected — run memfleet connect`);
- **binding** — the resolved agent profile and its source (`--profile flag` /
  `MEMFLEET_PROFILE` / `project default` / `none`), where its key comes from
  (`keychain` / `encrypted file` / `env` / `none`), and — when a key resolves —
  the live `/agent/v1/me` identity;
- **skill** — whether the bound scope's skill body is installed in this
  directory, compared against the server's own copy
  (`installed` / `not installed — a bind via memfleet run installs it` /
  `scope has no skill body`).

## 2.x → 3.0.0

A clean break: `connect` no longer touches agents, so there are no compatibility
shims. The mapping is one-for-one.

| 2.x | 3.0.0 | Why |
|---|---|---|
| `memfleet login` | `memfleet connect` | `connect` absorbed it; the name is already truthful. `login` now errors with this pointer. |
| `memfleet connect` (guided: workspace → scope → name → agent) | `memfleet run --profile '<Workspace>/<agent>' [--scope ID] COMMAND` | `connect` conflated machine↔account with session↔agent, so a second terminal in a new directory re-asked questions already answered. |
| `memfleet connect --workspace W --scope S --name N` | `memfleet run --profile 'W/N' --scope S COMMAND` | The agent flags moved with the act they belong to. |
| `memfleet connect --code XXXX --name N --scope S` | `memfleet run --code XXXX --name N --scope S COMMAND` | The enrollment-code plane moved to `run` too — one command for a teammate with no account. |
| `memfleet connect` refused an existing agent name | `memfleet run --profile 'W/existing'` binds to it | An existing agent is the bind target now; that is the command's whole point. |
| `connect --skill / --template / --create-workspace / --define-scope / --scope-name / --stratum / --parent` | `memfleet workspaces create`, `memfleet scopes create`, `memfleet agents create` | `run` binds inside an existing Workspace and never creates one. Topology is an explicit act. |
| `connect --project / --diff` | `memfleet setup --project / --diff` | `run`'s wiring is non-interactive by construction; `setup` is still the preview/rewrite door. |
| (nothing) | `memfleet status` | There was no way to see which server, account or binding a directory resolved. |

## The binding model

Three credential planes, **never crossed**:

- the **owner token** (`sot_…`) — device-flow issued, manages the fleet;
- the **agent key** (`sak_…`) — what a session presents; one agent ⇄ one
  `(scope, skill)` binding, fixed at registration;
- the **enrollment code** (`sge_…`) — delegation to non-owners.

**Sessions bind, not machines.** Locally there are only named **agent
profiles** (`<workspace>/<agent>`); the binding lives server-side. One machine
may hold many profiles and each concurrent session resolves its own.

### Secrets never touch disk in plaintext

By default secrets live **only** in the OS keychain (via `keyring`) or the
environment — the client never writes an owner token or an agent key to a file
in plaintext:

- the **owner token** is keyed by server URL;
- each **agent key** is keyed by its profile name.

With a keychain present, a bind prints **no key at all**. Where no keychain
backend exists (headless, CI, containers), secrets come from the environment
instead:

- `MEMFLEET_AGENT_KEY` — the agent key for the active profile (the same name the
  written MCP config resolves at session start);
- `MEMFLEET_OWNER_TOKEN` — an owner token for the management plane.

In that mode a plaintext *write* cannot persist without putting a secret on
disk, which the client refuses to do; it prints the env var to set instead —
**or**, if you opt in, it stores the credentials in the encrypted file store
below. Either way, no secret is ever written to disk in plaintext.

#### Opt-in encrypted file store

On a keychain-less machine you may choose to store credentials in
`~/.memfleet/credentials.enc` instead of managing `export` lines by hand. It is
**opt-in only** — nothing is stored unless you accept the prompt (or pass
`--credential-store file`), and declining keeps the show-once export behavior
exactly as before. When enabled:

- credentials are sealed with **AES-256-GCM** under a key derived from a
  passphrase (`scrypt`), the file is `chmod 600`, and every write re-seals under
  a fresh nonce — no plaintext secret ever lands on disk;
- `connect`, `run`, `status`, and `env` resolve profiles transparently, prompting
  once per process for the passphrase to unlock the store (the derived key is
  cached for that process only, never written);
- `logout` clears the owner token from the store, and re-binding a profile
  overwrites its entry — no stale credentials accumulate.

A wrong passphrase fails with a clean message, never a traceback.

## What a bind writes (additive, idempotent, secret-free)

Setup is **strictly additive** and reuses the engine's install machinery
(`strata.install`) — an existing entry is never overwritten. A
second bind in the same directory changes nothing; `memfleet setup --diff`
previews without writing.

- an **MCP config** entry for the hosted `/mcp` endpoint, keyed `memfleet`
  (distinct from the local engine's `strata` entry, so a project may run both).
  It is **secret-free**: the agent key is resolved at session start from the
  profile env (`MEMFLEET_AGENT_KEY`) —

  ```json
  {
    "mcpServers": {
      "memfleet": {
        "type": "http",
        "url": "https://memfleet.com/mcp",
        "headers": { "Authorization": "Bearer ${MEMFLEET_AGENT_KEY}" }
      }
    }
  }
  ```

- the **`memfleet-contribution` skill** into `.claude/skills/`.

- the **bound scope's skill body**, when the owner authored one, as
  `.claude/skills/<skill name>/SKILL.md` (a skill-less scope installs under
  `<scope_id>-scope`). It is fetched from `GET /agent/v1/skill-body` **with the
  agent key**, so the owner and enrollment-code planes install it identically.

- the **contribution Stop-hook** — the script at
  `.claude/hooks/memfleet-stop-hook` plus its `hooks.Stop` wiring merged into
  `.claude/settings.json`. Both blocks are server-rendered and fetched from the
  owner-token management endpoint; dependency-free and secret-free, the hook
  reads the agent key from the environment and degrades to a no-op when none is
  present. The enrollment-code plane has no owner token, so it notes the skip in
  one line and everything else proceeds.

The MCP entry is written to `.mcp.json` at the project root (the file where
Claude Code expands `${VAR}` in HTTP headers); it is secret-free and safe to
commit — a committed config with no profile present simply fails to authenticate
the MCP server, and the session degrades silently.

## Machines without an OS keychain (headless / CI / containers)

`run` still completes: the setup is written as usual and the agent key is shown
ONCE as an `export` line at the end (no secret is ever written to disk in
plaintext). You then have two choices:

- **Show-once exports (default).** Set them in the session environment —
  `memfleet run` passes an ambient `MEMFLEET_AGENT_KEY` through — or re-run the
  bind for a fresh key. `memfleet profiles list` marks such profiles
  `(key: env)`, and `memfleet status` reports `key     env`.
- **Opt into the encrypted file store.** On the keychain-less path you are asked
  once: *"No OS keychain is available. Store credentials in an encrypted file
  (~/.memfleet/credentials.enc) protected by a passphrase? [y/N]"*. Answer `y`
  (or pass `--credential-store file` for non-interactive setups) to seal the
  credentials into an AES-GCM file `chmod 600`, protected by a passphrase-derived
  key. `connect`, `run`, `status`, and `env` then resolve profiles automatically
  (see *Opt-in encrypted file store* above). Declining keeps the show-once
  behavior byte-for-byte.

## Configuration precedence

- **server** — `--server` flag → `MEMFLEET_SERVER` env → project
  `.memfleet/config.toml` `server` → default `https://memfleet.com`. No URL
  appears in the normal path; `--server` is for test / self-hosted only.
  `memfleet status` names the rung that won.
- **agent profile** — `--profile` flag → `MEMFLEET_PROFILE` env → project
  `.memfleet/config.toml` `default_profile` → none. The env override lets
  concurrent same-repo sessions each resolve their own profile; `status` names
  this rung too.
- **agent key**, for the resolved profile — OS keychain → opt-in encrypted file
  store → ambient `MEMFLEET_AGENT_KEY`. A key from any of the three is the
  no-network fast path.

`.memfleet/config.toml` holds only non-secret project state (a server URL and a
default profile name). It is `.memfleet/`, not `.strata/`, so a project may run
a local `strata` engine and the cloud side by side.
