Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions apps/memos-local-plugin/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,20 @@ daemon/ # bridge pid/port files
Upgrading or uninstalling the plugin **never** touches `data/`, `skills/`,
`logs/`, or `config.yaml`.

## Configuration Discovery

The bridge locates its runtime home (and `config.yaml`) using this priority:

1. **`--home=<path>` CLI flag** — passed to `bridge.cts` directly
2. **`MEMOS_HOME` env var** — overrides the runtime home directory
3. **`MEMOS_CONFIG_FILE` env var** — overrides only the config file path
4. **Built-in default** — `~/.<agent>/memos-plugin/` (e.g. `~/.hermes/memos-plugin/`)

When running inside Docker (where the daemon is started by the container's
entrypoint rather than the Python adapter), use `--home` or `MEMOS_HOME` to
ensure the bridge finds its config. See [**Docker Deployment**](./docs/DOCKER.md)
for the full guide.

## Quick start

```bash
Expand Down
16 changes: 16 additions & 0 deletions apps/memos-local-plugin/adapters/hermes/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,22 @@ communicates over its stdin/stdout pipes. The subprocess exits when the
provider's stdin closes (on `shutdown()`), yielding clean lifecycle
semantics.

### Docker / Manual Daemon Startup

When running inside Docker, the bridge may be started by the container's
entrypoint instead of being lazily spawned by the Python adapter. In this
case, pass `--home=<path>` to tell the bridge where its runtime home is:

```bash
node --experimental-strip-types bridge.cts \
--agent=hermes \
--daemon \
--home=/opt/data/home/.hermes/memos-plugin
```

Alternatively, set the `MEMOS_HOME` environment variable. See
[docs/DOCKER.md](../../docs/DOCKER.md) for the full Docker deployment guide.

## Why a subprocess instead of a long-lived daemon?

Earlier prototypes used a persistent HTTP daemon on a well-known port.
Expand Down
22 changes: 22 additions & 0 deletions apps/memos-local-plugin/bridge.cts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,22 @@
* node --experimental-strip-types bridge.cts (default: stdio)
* node --experimental-strip-types bridge.cts --daemon (stdio)
* node --experimental-strip-types bridge.cts --daemon --tcp=18911
* node --experimental-strip-types bridge.cts --agent=hermes --home=/opt/data/home/.hermes/memos-plugin
*
* The `.cts` extension is intentional: it lets the file be required
* from CommonJS environments that spawn Node with `require("...")`
* semantics. Internally we re-export the ESM implementation via
* `import()`.
*
* CLI flags:
* --daemon Run in daemon mode (keep process alive)
* --tcp=PORT Listen on TCP instead of stdio
* --agent=AGENT Agent type: openclaw | hermes (default: openclaw)
* --home=PATH Override runtime home (equivalent to MEMOS_HOME env)
*
* Environment variables (resolved by core/config/paths.ts):
* MEMOS_HOME Override runtime home directory
* MEMOS_CONFIG_FILE Override config.yaml path only
*/
// eslint-disable-next-line @typescript-eslint/no-require-imports
const path = require("node:path") as typeof import("node:path");
Expand All @@ -20,6 +31,8 @@ interface BridgeArgs {
daemon: boolean;
tcpPort?: number;
agent: "openclaw" | "hermes";
/** Override runtime home directory (equivalent to MEMOS_HOME env var). */
home?: string;
}

function parseArgs(argv: readonly string[]): BridgeArgs {
Expand All @@ -29,6 +42,7 @@ function parseArgs(argv: readonly string[]): BridgeArgs {
else if (raw.startsWith("--tcp=")) args.tcpPort = Number(raw.slice(6));
else if (raw === "--agent=hermes") args.agent = "hermes";
else if (raw === "--agent=openclaw") args.agent = "openclaw";
else if (raw.startsWith("--home=")) args.home = raw.slice(7);
}
return args;
}
Expand All @@ -51,6 +65,14 @@ async function main(): Promise<void> {
pathToEsmUrl(path.resolve(__dirname, "core/logger/index.ts"))
)) as typeof import("./core/logger/index.js");

// When --home is provided, set MEMOS_HOME so resolveHome() in
// core/config/paths.ts picks it up. This is the recommended way to
// configure the bridge in Docker where the daemon is started outside
// the Python adapter (which would normally pass extra_env).
if (args.home) {
process.env["MEMOS_HOME"] = path.resolve(args.home);
}

const { core, config, home } = await bootstrapMemoryCoreFull({
agent: args.agent,
pkgVersion: "2.0.0-alpha.1",
Expand Down
130 changes: 130 additions & 0 deletions apps/memos-local-plugin/docs/DOCKER.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
# Docker Deployment

This guide covers running MemOS Local Plugin inside a Docker container,
typically alongside an agent like Hermes Agent.

## The Config Discovery Problem

When MemOS runs in Docker, the bridge daemon may be started by the container's
`entrypoint.sh` rather than the Python adapter. In this scenario, the bridge
cannot find its `config.yaml` because:

1. The `MEMOS_HOME` / `MEMOS_CONFIG_FILE` environment variables are not set.
2. The default path (`~/.hermes/memos-plugin/`) may not exist or may not
contain the config file generated by `install.sh`.

**Symptoms:**

- Embedding provider falls back to `local` or is disabled.
- LLM-based features (summarization, reflection) do not work.
- Database is created in an unexpected (empty) directory.
- Viewer shows default branding instead of agent-specific title/logo.

## Solutions

### Option 1: `--home` CLI Flag (Recommended for Docker)

Pass `--home=<path>` when starting the bridge. This sets `MEMOS_HOME`
internally before the config is loaded.

```bash
# In your Docker entrypoint.sh:
node --experimental-strip-types bridge.cts \
--agent=hermes \
--daemon \
--home=/opt/data/home/.hermes/memos-plugin
```

The path should point to the runtime home directory containing `config.yaml`
and `data/`.

### Option 2: `MEMOS_HOME` Environment Variable

Set the environment variable before starting the bridge:

```bash
export MEMOS_HOME=/opt/data/home/.hermes/memos-plugin
node --experimental-strip-types bridge.cts --agent=hermes --daemon
```

Or in a Dockerfile / docker-compose.yml:

```yaml
services:
hermes:
image: nousresearch/hermes-agent
environment:
- MEMOS_HOME=/opt/data/home/.hermes/memos-plugin
```

### Option 3: `MEMOS_CONFIG_FILE` (Config-only Override)

If you only need to override the config file path (data/logs/skills still
derive from the same parent directory):

```bash
export MEMOS_CONFIG_FILE=/opt/data/home/.hermes/memos-plugin/config.yaml
```

## Config Resolution Priority

The bridge resolves its runtime home in this order (highest priority first):

1. `--home=<path>` CLI flag
2. `MEMOS_HOME` environment variable
3. `MEMOS_CONFIG_FILE` environment variable (config file only)
4. Built-in default: `~/.<agent>/memos-plugin/`

## Preparing the Config File

Before starting the bridge, ensure `config.yaml` exists in the runtime home
with the required provider configuration. You can use the template:

```bash
# Copy template and edit
cp templates/config.hermes.yaml /opt/data/home/.hermes/memos-plugin/config.yaml
chmod 600 /opt/data/home/.hermes/memos-plugin/config.yaml
$EDITOR /opt/data/home/.hermes/memos-plugin/config.yaml
```

Minimal config for a working deployment:

```yaml
version: 1

viewer:
port: 18799

embedding:
provider: openai_compatible
apiKey: "sk-your-api-key"
# endpoint: "https://api.siliconflow.cn/v1" # or your provider

llm:
provider: openai_compatible
apiKey: "sk-your-api-key"
model: "your-model-name"
```

## Verifying the Setup

After starting the bridge, check the logs for:

```
bridge: viewer → http://127.0.0.1:18799/
```

Then visit the viewer URL. If the config is loaded correctly, you should see:

- Your agent's title/logo in the header
- The embedding provider connected (check the Health page)
- No error messages about missing configuration

## Common Pitfalls

| Symptom | Cause | Fix |
|---------|-------|-----|
| Embedding: local (no vectors) | `config.yaml` not found | Set `--home` or `MEMOS_HOME` |
| Empty database on restart | Data stored in ephemeral path | Ensure `data/` is on a persistent volume |
| Default "MemOS" branding | Config not loaded before viewer start | Check the `--home` path is correct |
| Viewer port mismatch | Config says 18799 but bridge picks another | Port in use; check config.yaml `viewer.port` |