Skip to content

Commit 16c2a2d

Browse files
feat(connectors): add omp (oh-my-pi) memory connector (#1570)
* feat(connectors): add omp (oh-my-pi) memory connector Oh My Pi (omp, can1357/oh-my-pi) is a fork of Pi that preserves Pi's extension API — its context/message_end/turn_end/session_shutdown/ session_before_compact hooks are a superset of what the Remnic runtime extension uses — so the existing @remnic/plugin-pi runtime binds unchanged. Only the installer differs (omp keeps agent files under ~/.omp/agent), so this parameterizes plugin-pi by host. Path 2 (native extension): - connectors catalog entry + token prefix (remnic_op_) for "omp" - omp path resolution (resolveOmpAgentHome/resolveOmpExtensionRoot), honoring PI_CODING_AGENT_DIR and OMP_PROFILE/PI_PROFILE like omp's own getAgentDir() - REMNIC_OMP_CONFIG discovery for direct loads (REMNIC_PI_CONFIG wins) - host-parameterized publisher: shared HostMemoryExtensionPublisher base + Pi/Omp subclasses; all symlink/rollback/atomic-write guards preserved (Pi suite unchanged) - CLI: registerPublisher("omp", ...) via one shared lazy loader Path 1 (MCP registration) documented alongside Path 2 in docs/integration/omp.md; README, plugin-pi README, and pi.md updated. Tests: omp coverage for paths/config/publisher; plugin-pi 89/89. * fix(plugin-pi): match omp DirResolver for extension path resolution Cursor Bugbot flagged that resolveOmpAgentHome could land the extension where omp never scans. Verified against omp's DirResolver (packages/utils/src/dirs.ts) and corrected two real gaps: - Honor PI_CONFIG_DIR (omp's config dir name; default .omp), not a hardcoded ~/.omp. - Let an active OMP_PROFILE/PI_PROFILE take precedence over PI_CODING_AGENT_DIR — omp discards the agent-dir override while a profile is active (previously reversed). XDG (XDG_DATA_HOME, etc.) relocates only the data/state/cache categories, not the base agent dir extensions load from, so it is intentionally not consulted. Documented in docs/integration/omp.md. Tests: +3 omp path cases (PI_CONFIG_DIR, profile-wins-over-override, combined); plugin-pi 92/92. * fix(plugin-pi): correct Path 1 MCP transport + harden omp connector removal Addresses AI review feedback on #1570: - docs(omp): Path 1 used 'remnic access mcp-serve'/'http-serve', but the remnic binary has no 'access' command (those live in @remnic/core's plugin registerCli, not the standalone CLI). Rewrote Path 1 to the daemon's HTTP /mcp endpoint with a bearer token, matching the canonical connector-setup.md flow every other MCP client uses. - fix(publisher): omp connector removal resolved the extension dir from the remove-time env only, so a profile-scoped install (OMP_PROFILE=work) was orphaned when removed without that env. unpublish now sweeps the base agent dir, PI_CODING_AGENT_DIR, and every existing profiles/<name>/agent under the config root (symlinked profile dirs skipped). Pi behavior unchanged (single-candidate path). Added resolveOmpConfigRoot helper. Tests: +1 profile-removal case; plugin-pi 93/93. Type-checks clean.
1 parent c718770 commit 16c2a2d

15 files changed

Lines changed: 814 additions & 68 deletions

File tree

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ OpenClaw's built-in memory works for simple cases, but it doesn't scale. It lack
6666

6767
Remnic is an open-source memory and context layer for user-aware agents. It watches agent conversations, extracts durable knowledge, and injects the right context back when it is needed. Route extraction through the OpenClaw gateway model chain, OpenAI, or a **local LLM** (Ollama, LM Studio, etc.) -- your choice.
6868

69-
Remnic helps agents understand the people they work with: preferences, projects, constraints, decisions, patterns, and definitions of good. It works natively with **[OpenClaw](https://github.com/openclaw/openclaw)**, **[Claude Code](https://docs.anthropic.com/en/docs/claude-code)**, **[Codex CLI](https://github.com/openai/codex)**, **[Pi Coding Agent](https://pi.dev)**, **[Hermes Agent](https://github.com/NousResearch/hermes-agent)**, and any **MCP-compatible client** (Replit, Cursor, etc.). When you tell any agent a preference, every agent can use the same governed memory store.
69+
Remnic helps agents understand the people they work with: preferences, projects, constraints, decisions, patterns, and definitions of good. It works natively with **[OpenClaw](https://github.com/openclaw/openclaw)**, **[Claude Code](https://docs.anthropic.com/en/docs/claude-code)**, **[Codex CLI](https://github.com/openai/codex)**, **[Pi Coding Agent](https://pi.dev)**, **[Oh My Pi (omp)](https://omp.sh)**, **[Hermes Agent](https://github.com/NousResearch/hermes-agent)**, and any **MCP-compatible client** (Replit, Cursor, etc.). When you tell any agent a preference, every agent can use the same governed memory store.
7070

7171
Local-first storage is a trust feature. All data can stay on your machine as plain markdown files: no cloud dependency, no subscription, and no third-party memory service required.
7272

@@ -189,6 +189,7 @@ Once the Remnic daemon is running, connect any supported agent:
189189
remnic connectors install claude-code # Claude Code (hooks + MCP)
190190
remnic connectors install codex-cli # Codex CLI (hooks + MCP + memory extension)
191191
remnic connectors install pi # Pi Coding Agent (extension + MCP + compaction)
192+
remnic connectors install omp # Oh My Pi / omp (extension + MCP + compaction)
192193
remnic connectors install replit # Replit (MCP only)
193194
pip install --upgrade remnic-hermes # Hermes Agent (Python MemoryProvider)
194195
remnic connectors install hermes # Writes Hermes config + token

docs/integration/omp.md

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
# Oh My Pi (omp) Integration
2+
3+
[Oh My Pi](https://omp.sh) (`omp`, [`can1357/oh-my-pi`](https://github.com/can1357/oh-my-pi))
4+
is a terminal coding agent forked from [Pi](https://pi.dev). Because omp
5+
preserves Pi's extension API — the same `context`, `turn_end`,
6+
`session_shutdown`, and `session_before_compact` hooks — the existing
7+
`@remnic/plugin-pi` runtime extension runs on omp unchanged. Only the
8+
*installer* differs (omp keeps its agent files under `~/.omp/agent` instead of
9+
`~/.pi/agent`), so Remnic ships a dedicated **`omp` connector** that writes into
10+
the right place.
11+
12+
There are two ways to give omp the **same** governed memory that OpenClaw,
13+
Claude Code, Codex, ChatGPT, and Pi already share. Both point omp at the *same*
14+
Remnic daemon + memory directory, so memory written by any agent is recalled by
15+
every agent.
16+
17+
> **One brain, many front-ends.** Shared memory only requires that every agent
18+
> talks to one Remnic instance (one daemon URL + one `memoryDir`). omp joins as
19+
> another front-end; it does not get a private store.
20+
21+
---
22+
23+
## Path 1 — Register Remnic as an MCP server (fast, no extension)
24+
25+
omp is a first-class MCP client. This is the quickest way to validate shared
26+
read/write and needs no Remnic extension files.
27+
28+
First start the Remnic server (it serves the MCP endpoint on port 4318) and mint
29+
a token — the same HTTP/MCP surface every other MCP client uses (see
30+
[connector-setup.md](./connector-setup.md)):
31+
32+
```bash
33+
remnic daemon start # serves http://localhost:4318/mcp
34+
remnic connectors install generic-mcp # mints the connector token ($REMNIC_AUTH_TOKEN)
35+
```
36+
37+
Then point omp at it via `~/.omp/agent/mcp.json` (user scope) or `.omp/mcp.json`
38+
(project scope) using the HTTP transport:
39+
40+
```json
41+
{
42+
"$schema": "https://raw.githubusercontent.com/can1357/oh-my-pi/main/packages/coding-agent/src/config/mcp-schema.json",
43+
"mcpServers": {
44+
"remnic": {
45+
"type": "http",
46+
"url": "http://localhost:4318/mcp",
47+
"headers": {
48+
"Authorization": "Bearer ${REMNIC_AUTH_TOKEN}",
49+
"X-Engram-Namespace": "my-project"
50+
}
51+
}
52+
}
53+
}
54+
```
55+
56+
This exposes Remnic's MCP tools (`remnic_recall`, `remnic_memory_store`,
57+
`remnic_briefing`, `remnic_observe`, …) to omp against the same store the other
58+
agents use. The optional `X-Engram-Namespace` header scopes memory to a
59+
project/team.
60+
61+
> **Transport note.** The `remnic` binary does not serve MCP over stdio — the
62+
> daemon's HTTP `/mcp` endpoint is the supported transport for every MCP client
63+
> (OpenClaw plugin mode uses `openclaw engram access http-serve --port 4318`).
64+
65+
**Trade-off:** MCP recall is *tool-gated* — the model must decide to call
66+
`remnic_recall`/`remnic_briefing`. There is no automatic system-prompt
67+
injection. Use an omp rule/skill to nudge a `remnic_briefing` call at session
68+
start, or use Path 2 for automatic recall.
69+
70+
---
71+
72+
## Path 2 — Install the native omp extension (recommended)
73+
74+
Start the Remnic daemon, then install the `omp` connector:
75+
76+
```bash
77+
remnic daemon start
78+
remnic connectors install omp
79+
```
80+
81+
The installer writes (mirroring the Pi connector, under omp's agent home):
82+
83+
- `~/.omp/agent/extensions/remnic/index.ts` — omp auto-discovery wrapper
84+
- `~/.omp/agent/extensions/remnic/remnic.config.json` — private daemon URL, namespace, and auth token (`0600`)
85+
- `~/.omp/agent/extensions/remnic/README.md` — local operator notes
86+
87+
To skip writing the extension and only create the connector/token:
88+
89+
```bash
90+
remnic connectors install omp --config installExtension=false
91+
```
92+
93+
To target a non-default daemon or namespace:
94+
95+
```bash
96+
remnic connectors install omp \
97+
--config remnicDaemonUrl=http://127.0.0.1:4318 \
98+
--config namespace=work
99+
```
100+
101+
### Install location
102+
103+
The connector writes into the same agent directory omp auto-discovers
104+
extensions from, resolved the way omp's own `DirResolver` does:
105+
106+
- The config dir name is `PI_CONFIG_DIR` (default `.omp`).
107+
- An active profile (`OMP_PROFILE`, falling back to `PI_PROFILE`) wins and
108+
targets `<configRoot>/profiles/<name>/agent`; omp discards `PI_CODING_AGENT_DIR`
109+
while a profile is active.
110+
- Otherwise `PI_CODING_AGENT_DIR` overrides the whole agent dir.
111+
- Otherwise the base is `~/.omp/agent`.
112+
113+
Install under the same profile/env you launch omp with (e.g.
114+
`OMP_PROFILE=work remnic connectors install omp`) so the extension lands where
115+
that profile scans. `remnic connectors remove omp` sweeps the base agent dir and
116+
every `profiles/<name>/agent` under the config root, so removal cleans up a
117+
profile-scoped install even if the profile env is not set at remove time. omp's
118+
XDG redirection (`XDG_DATA_HOME`, …) relocates *data* dirs (sessions/state/cache),
119+
**not** the extension dir, so it does not affect where the connector installs.
120+
121+
### Turn off omp's built-in memory
122+
123+
omp ships its own memory backends (`memory.backend: local` and
124+
`memory.backend: mnemopi`). Those are **separate, per-project stores** that are
125+
*not* shared with other agents. To make Remnic the single source of truth, leave
126+
omp's backend off in `~/.omp/agent/config.yml`:
127+
128+
```yaml
129+
memory:
130+
backend: off
131+
```
132+
133+
Running both means two disjoint memories; pick Remnic (shared) or Mnemopi
134+
(local-only), not both.
135+
136+
## What The Extension Does
137+
138+
- Uses the `context` hook to recall relevant Remnic context before an agent turn.
139+
- Uses `message_end`, `turn_end`, and `session_shutdown` hooks to observe user, assistant, and tool activity with `sourceFormat: "pi"`.
140+
- Uses `session_before_compact` to flush Remnic LCM for the active session before omp compacts context, then records the compaction token delta.
141+
- Registers Remnic MCP tools as omp tools when the Remnic daemon token is configured.
142+
- Persists lightweight dedupe state with omp `custom` entries so repeated turns are not re-observed.
143+
144+
All of these hooks exist in omp's extension event surface (it is a superset of
145+
Pi's), so the shared runtime extension binds without modification.
146+
147+
## omp Commands
148+
149+
The extension registers these slash commands:
150+
151+
- `/remnic-status` — check daemon health
152+
- `/remnic-recall <query>` — recall Remnic context for a query
153+
- `/remnic-remember <memory>` — explicitly store a memory
154+
- `/remnic-lcm-search <query>` — search archived context
155+
- `/remnic-why` — inspect the last recall explanation
156+
- `/remnic-compact` — request compaction
157+
158+
## Configuration
159+
160+
The extension loads configuration from:
161+
162+
1. `REMNIC_OMP_CONFIG` (or `REMNIC_PI_CONFIG`, which takes precedence if both are set)
163+
2. The explicit `configPath` written into the auto-discovery wrapper (`~/.omp/agent/extensions/remnic/remnic.config.json`)
164+
165+
Supported config keys:
166+
167+
| Key | Default | Description |
168+
|-----|---------|-------------|
169+
| `remnicDaemonUrl` | `http://127.0.0.1:4318` | Remnic HTTP/MCP daemon URL |
170+
| `authToken` | unset | Connector token generated by `remnic connectors install omp` |
171+
| `namespace` | unset | Remnic namespace for recall/observe/store requests |
172+
| `recallMode` | `auto` | Recall mode: `auto`, `minimal`, `full`, `graph_mode`, or `no_recall` |
173+
| `recallTopK` | `8` | Max recalled results |
174+
| `recallBudgetChars` | `12000` | Max recalled context injected into omp |
175+
| `recallEnabled` | `true` | Enable context-hook recall |
176+
| `observeEnabled` | `true` | Enable turn observation |
177+
| `observeSkipExtraction` | `false` | Archive observed messages without extraction |
178+
| `compactionEnabled` | `true` | Enable LCM flush/checkpoint coordination |
179+
| `mcpToolsEnabled` | `true` | Register Remnic MCP tools as omp tools |
180+
| `statusEnabled` | `true` | Set omp UI status from daemon health |
181+
| `requestTimeoutMs` | `60000` | HTTP/MCP request timeout for recall/observe/compaction/commands |
182+
| `startupRequestTimeoutMs` | `1000` | Shorter timeout for startup-sensitive probes so a slow or offline daemon can't stall omp boot |
183+
184+
Boolean-like strings such as `"false"`, `"0"`, `"no"`, and `"off"` are treated as false.
185+
186+
### Direct load (without the connector installer)
187+
188+
You can load the package directly, pointing it at a config via `REMNIC_OMP_CONFIG`:
189+
190+
```bash
191+
REMNIC_OMP_CONFIG=~/.omp/agent/extensions/remnic/remnic.config.json \
192+
omp -e npm:@remnic/plugin-pi
193+
```
194+
195+
## API Surface
196+
197+
omp uses the shared Remnic access layer, identical to Pi:
198+
199+
- `POST /engram/v1/recall`
200+
- `POST /engram/v1/observe`
201+
- `POST /engram/v1/lcm/search`
202+
- `POST /engram/v1/lcm/compaction/flush`
203+
- `POST /engram/v1/lcm/compaction/record`
204+
- `POST /mcp` for MCP tool discovery and calls
205+
206+
Both canonical `remnic.*` MCP tools and legacy `engram.*` aliases remain available through the daemon.
207+
208+
## See Also
209+
210+
- [Pi Coding Agent Integration](./pi.md) — the upstream Pi connector this shares its runtime with.
211+
- [Connector Setup Guide](./connector-setup.md) — MCP/HTTP config snippets for other agents.

docs/integration/pi.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
Remnic ships a native Pi Coding Agent extension through `@remnic/plugin-pi`.
44
It uses Pi's extension hooks instead of wrapping Pi with a parallel runtime.
55

6+
> Using the [Oh My Pi (omp)](https://omp.sh) fork? The same runtime extension
7+
> works there via the `omp` connector — see [omp.md](./omp.md).
8+
69
## Install
710

811
Start the Remnic daemon, then install the connector:

packages/plugin-pi/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ Remnic memory and context for [Pi Coding Agent](https://pi.dev).
44

55
This package is the first-class Remnic extension for Pi. It uses Pi's extension hooks directly, so Remnic can recall context before a model call, observe useful session events after the turn, expose Remnic MCP tools inside Pi, and coordinate Pi compaction with Remnic's long-context memory archive.
66

7+
> **Oh My Pi (omp).** The [omp](https://omp.sh) fork preserves Pi's extension API, so this package's runtime extension runs there too. Install it with `remnic connectors install omp` (writes to `~/.omp/agent/extensions/remnic/`) via the `OmpMemoryExtensionPublisher`. See [docs/integration/omp.md](../../docs/integration/omp.md).
8+
79
## What It Does
810

911
- Recalls relevant Remnic context in Pi's `context` hook before an agent turn.

packages/plugin-pi/src/config.test.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,28 @@ test("resolveConfigPath uses the Pi extension config location by default", () =>
1818
}
1919
});
2020

21+
test("resolveConfigPath honors REMNIC_OMP_CONFIG for omp direct loads", () => {
22+
assert.equal(
23+
resolveConfigPath({
24+
env: { HOME: "/home/alice", REMNIC_OMP_CONFIG: "/x/omp/remnic.config.json" },
25+
}),
26+
"/x/omp/remnic.config.json",
27+
);
28+
});
29+
30+
test("resolveConfigPath prefers REMNIC_PI_CONFIG over REMNIC_OMP_CONFIG", () => {
31+
assert.equal(
32+
resolveConfigPath({
33+
env: {
34+
HOME: "/home/alice",
35+
REMNIC_PI_CONFIG: "/pi/remnic.config.json",
36+
REMNIC_OMP_CONFIG: "/omp/remnic.config.json",
37+
},
38+
}),
39+
"/pi/remnic.config.json",
40+
);
41+
});
42+
2143
test("resolveConfigPath honors Pi agent directory overrides", () => {
2244
const root = fs.mkdtempSync(path.join(os.tmpdir(), "remnic-pi-config-roots-"));
2345
try {

packages/plugin-pi/src/config.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,13 @@ function trimTrailingSlashes(value: string): string {
146146

147147
export function resolveConfigPath(options: LoadConfigOptions = {}): string {
148148
const env = options.env ?? process.env;
149-
return expandTildePath(options.configPath || env.REMNIC_PI_CONFIG || defaultConfigPath(env));
149+
// REMNIC_PI_CONFIG keeps precedence for upstream Pi; REMNIC_OMP_CONFIG lets an
150+
// omp (oh-my-pi) direct load (`omp -e npm:@remnic/plugin-pi`) point the shared
151+
// runtime module at its own config without an explicit configPath. Connector
152+
// installs always pass an explicit configPath, so this only affects direct loads.
153+
return expandTildePath(
154+
options.configPath || env.REMNIC_PI_CONFIG || env.REMNIC_OMP_CONFIG || defaultConfigPath(env),
155+
);
150156
}
151157

152158
export function loadConfig(options: LoadConfigOptions = {}): RemnicPiConfig {
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import assert from "node:assert/strict";
2+
import path from "node:path";
3+
import test from "node:test";
4+
5+
import { resolveOmpAgentHome, resolveOmpExtensionRoot } from "./paths.js";
6+
7+
test("resolveOmpAgentHome defaults to ~/.omp/agent", () => {
8+
assert.equal(
9+
resolveOmpAgentHome({ HOME: "/home/alice" }),
10+
path.join("/home/alice", ".omp", "agent"),
11+
);
12+
});
13+
14+
test("resolveOmpAgentHome honors PI_CODING_AGENT_DIR override", () => {
15+
assert.equal(
16+
resolveOmpAgentHome({
17+
HOME: "/home/alice",
18+
PI_CODING_AGENT_DIR: "/custom/omp-agent",
19+
}),
20+
"/custom/omp-agent",
21+
);
22+
});
23+
24+
test("resolveOmpAgentHome honors PI_CONFIG_DIR for the config dir name", () => {
25+
assert.equal(
26+
resolveOmpAgentHome({ HOME: "/home/alice", PI_CONFIG_DIR: ".myomp" }),
27+
path.join("/home/alice", ".myomp", "agent"),
28+
);
29+
});
30+
31+
test("resolveOmpAgentHome lets an active profile take precedence over PI_CODING_AGENT_DIR", () => {
32+
// Matches omp's DirResolver: when a profile is active, the agent-dir
33+
// override is discarded, so the extension must land in the profile dir.
34+
assert.equal(
35+
resolveOmpAgentHome({
36+
HOME: "/home/alice",
37+
OMP_PROFILE: "work",
38+
PI_CODING_AGENT_DIR: "/custom/omp-agent",
39+
}),
40+
path.join("/home/alice", ".omp", "profiles", "work", "agent"),
41+
);
42+
});
43+
44+
test("resolveOmpAgentHome combines PI_CONFIG_DIR with a named profile", () => {
45+
assert.equal(
46+
resolveOmpAgentHome({ HOME: "/home/alice", PI_CONFIG_DIR: ".myomp", PI_PROFILE: "scratch" }),
47+
path.join("/home/alice", ".myomp", "profiles", "scratch", "agent"),
48+
);
49+
});
50+
51+
test("resolveOmpAgentHome resolves a named profile under ~/.omp/profiles", () => {
52+
assert.equal(
53+
resolveOmpAgentHome({ HOME: "/home/alice", OMP_PROFILE: "work" }),
54+
path.join("/home/alice", ".omp", "profiles", "work", "agent"),
55+
);
56+
});
57+
58+
test("resolveOmpAgentHome falls back to PI_PROFILE for the profile name", () => {
59+
assert.equal(
60+
resolveOmpAgentHome({ HOME: "/home/alice", PI_PROFILE: "scratch" }),
61+
path.join("/home/alice", ".omp", "profiles", "scratch", "agent"),
62+
);
63+
});
64+
65+
test("resolveOmpAgentHome treats the default profile as the base agent dir", () => {
66+
assert.equal(
67+
resolveOmpAgentHome({ HOME: "/home/alice", OMP_PROFILE: "default" }),
68+
path.join("/home/alice", ".omp", "agent"),
69+
);
70+
});
71+
72+
test("resolveOmpExtensionRoot appends extensions/remnic to the agent home", () => {
73+
assert.equal(
74+
resolveOmpExtensionRoot({ HOME: "/home/alice" }),
75+
path.join("/home/alice", ".omp", "agent", "extensions", "remnic"),
76+
);
77+
});

0 commit comments

Comments
 (0)