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
59 changes: 38 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,23 +12,22 @@ and the lessons learned across every project — automatically.
`UserPromptSubmit` hook.
- 💾 **Automatic capture** — conversations are stored incrementally (every N turns) and
at session end via the `Stop` hook.
- 🏷️ **Project + user scoping** — memories are tagged per-project and per-user so
context never leaks across repos.
- 🏷️ **Shared Agents scoping** — Codex and Claude Code use one collision-safe
repository container.
- 📦 **Custom container tags** — define custom memory containers (e.g., `work`, `personal`,
`code_style`). The AI automatically picks the right container based on your instructions
when saving, searching, or forgetting memories.
- 🏷️ **Project + user scoping** — automatic session memories are stored per-user,
while explicit project knowledge is tagged per-repo so context never leaks across repos.
- **Entity-aware extraction** - user and project containers get purpose-specific
extraction context so Supermemory stores durable preferences separately from
project/codebase facts.
- 🏷️ **Personal + project routing** — `sm_scope` metadata keeps automatic/personal
memories distinguishable from explicit project knowledge in the shared container.
- **Entity-aware extraction** - the shared container uses one coding-agent context
covering durable preferences and project/codebase facts.
- 🔒 **Privacy-aware** — anything wrapped in `<private>...</private>` is redacted
before being sent to Supermemory.
- ⚡ **Zero-config install** — one command sets up `~/.codex/config.toml` and
`~/.codex/hooks.json` for you.
- 🪶 **No runtime deps in hooks** — the hook scripts are pre-bundled with esbuild for
fast cold starts.
- 🔧 **Fallback skills** — explicit `/supermemory-search`, `/supermemory-save`,
- 🔧 **Fallback skills** — explicit `/supermemory-search`, `/supermemory-add`, `/supermemory-save`,
`/supermemory-forget`, `/supermemory-status`, and `/supermemory-logout` commands available when hooks
don't cover your use case.

Expand Down Expand Up @@ -74,6 +73,24 @@ The installer:
The hooks are tolerant: if Supermemory is unreachable, the API key is missing, or
anything else fails, they exit cleanly without breaking your Codex session.

### Shared Agents containers

Codex and Claude Code use one container for a repository:

- `repo_<project-name>__<remote-hash>` stores automatic capture and every explicit save.
- `sm_scope` metadata preserves optional personal/project filtering.

The hash comes from the normalized Git remote, so clones share memory while
same-named repositories do not collide. Repositories without a remote fall back to
a local path identity. Codex also reads the previous `user_project_*`,
`repo_<project-name>`, `codex_user_*`, `codex_project_*`, and
`claudecode_project_*` containers, so existing memories remain searchable without
duplicating or migrating them. Set `SUPERMEMORY_ISOLATE_WORKTREES=true` to use
the worktree path instead of the remote identity.

Explicit `projectContainerTag`/`repoContainerTag` overrides remain the canonical
write destination. Older user/personal overrides remain in the legacy read set.

## Configuration

### Environment variables
Expand All @@ -96,9 +113,9 @@ Drop this file in to override defaults:
| `maxMemories` | `number` | `5` | Max memories injected per prompt. |
| `maxProfileItems` | `number` | `5` | Max profile items considered. |
| `injectProfile` | `boolean` | `true` | Whether to fetch and inject the user profile. |
| `containerTagPrefix` | `string` | `"codex"` | Prefix for auto-generated container tags. |
| `userContainerTag` | `string` | auto | Override the user container tag. |
| `projectContainerTag` | `string` | auto (per-repo) | Override the project container tag. |
| `containerTagPrefix` | `string` | `"codex"` | Legacy prefix retained when reading containers created by older versions. |
| `userContainerTag` | `string` | auto | Legacy personal container retained for backward-compatible reads. |
| `projectContainerTag` | `string` | auto (per-repo) | Explicit unified project-container override, also honored by Claude Code. |
| `filterPrompt` | `string` | (sensible) | Filter prompt used by Supermemory's stateful filter. |
| `debug` | `boolean` | `false` | Enable debug logging. |
| `autoSaveEveryTurns` | `number` | `3` | Save memories every N turns (incremental capture). |
Expand All @@ -109,18 +126,17 @@ Drop this file in to override defaults:
| `customContainers` | `array` | `[]` | Custom containers with `tag` and `description` (see below). |
| `customContainerInstructions` | `string` | `""` | Free-text instructions for the AI on how to route memories to containers.

User tags are auto-derived from your `git config user.email`. Project tags are
derived from the Git common directory when available, so linked worktrees and
Conductor workspaces for the same repository share one project container by default.
Set `SUPERMEMORY_ISOLATE_WORKTREES=true` to keep each worktree isolated.
Project tags combine the sanitized repository name with a normalized Git-remote
hash. Linked worktrees and clones of the same remote therefore share one container;
same-named repositories with different remotes do not collide. Without a remote,
the Git common directory is used as the fallback identity.

### Entity context

Codex sends an `entityContext` whenever it saves memories. The user container is
guided toward durable user preferences and workflows; the project container is
guided toward repo architecture, conventions, setup, decisions, and implementation
lessons. Supermemory stores this context on the container tag and uses it to guide
memory extraction.
Codex sends one shared coding-agent `entityContext` whenever it saves memories. It
covers durable preferences, workflows, architecture, conventions, setup, decisions,
and implementation lessons without one save type overwriting another container-level
context.

### Signal extraction (optional)

Expand All @@ -139,11 +155,12 @@ npx codex-supermemory status # show current install status
## Skills (fallback commands)

These Codex skills are available as explicit commands when you need more control.
All memory skills support `--container <tag>` to target a specific custom container.
The search, save, and forget skills support `--container <tag>` to target a specific custom container.

| Skill | Usage | Description |
| ---------------------- | ----------------------------------------------------------- | ---------------------------------------- |
| `/supermemory-search` | `/supermemory-search [--container <tag>] <query>` | Search memories manually. |
| `/supermemory-add` | `/supermemory-add <content>` | Add a personal memory for this project. |
| `/supermemory-save` | `/supermemory-save [--container <tag>] <content>` | Save a specific memory explicitly. |
| `/supermemory-forget` | `/supermemory-forget [--container <tag>] <content>` | Remove a memory. |
| `/supermemory-profile` | `/supermemory-profile` | Show remembered profile facts. |
Expand Down
5 changes: 3 additions & 2 deletions build.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const executableEntries = [
in: `src/hooks/${n}.ts`,
out: `dist/hooks/${n}.js`,
})),
...["search-memory", "save-memory", "forget-memory", "profile-memory", "status", "login", "logout"].map((n) => ({
...["search-memory", "add-memory", "save-memory", "forget-memory", "profile-memory", "status", "login", "logout"].map((n) => ({
in: `src/skills/${n}.ts`,
out: `dist/skills/${n}.js`,
})),
Expand All @@ -27,6 +27,7 @@ rmSync("dist", { recursive: true, force: true });
const libraryEntries = [
{ in: "src/services/session.ts", out: "dist/services/session.js" },
{ in: "src/services/tags.ts", out: "dist/services/tags.js" },
{ in: "src/services/resultMerge.ts", out: "dist/services/resultMerge.js" },
];

await Promise.all(
Expand All @@ -50,7 +51,7 @@ await Promise.all(
);

// Copy SKILL.md files to dist
for (const skillName of ["supermemory-search", "supermemory-save", "supermemory-forget", "supermemory-profile", "supermemory-status", "supermemory-login", "supermemory-logout"]) {
for (const skillName of ["supermemory-search", "supermemory-add", "supermemory-save", "supermemory-forget", "supermemory-profile", "supermemory-status", "supermemory-login", "supermemory-logout"]) {
mkdirSync(`dist/skills/${skillName}`, { recursive: true });
copyFileSync(
`src/skills/${skillName}/SKILL.md`,
Expand Down
3 changes: 2 additions & 1 deletion src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ const SESSION_START_TIMEOUT_SECONDS = 60;
// Skill metadata — single source of truth for install/uninstall/status.
const SKILLS = [
{ name: "supermemory-search", script: "search-memory.js" },
{ name: "supermemory-add", script: "add-memory.js" },
{ name: "supermemory-save", script: "save-memory.js" },
{ name: "supermemory-forget", script: "forget-memory.js" },
{ name: "supermemory-status", script: "status.js" },
Expand Down Expand Up @@ -309,7 +310,7 @@ Installation complete!

You now have:
• Session-start profile recall (${getRecallModeSummary()})
• Explicit memory — supermemory-search, supermemory-save, supermemory-forget, supermemory-profile, supermemory-status, supermemory-login, and supermemory-logout skills
• Explicit memory — supermemory-search, supermemory-add, supermemory-save, supermemory-forget, supermemory-profile, supermemory-status, supermemory-login, and supermemory-logout skills

${hadExistingConfig
? "Existing install: legacy per-prompt recall/capture preserved in ~/.codex/supermemory.json.\nTo opt into new defaults, set autoRecallEveryPrompt=false and captureEveryNTurns=0.\n"
Expand Down
9 changes: 4 additions & 5 deletions src/hooks/recall.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,17 +123,16 @@ async function main() {
}

try {
const [profileResult, projectSearchResult] = await Promise.all([
client.getProfileWithSearch(tags.user, query),
client.searchMemories(query, tags.project),
]);
const profileResult = await client.getProfileWithSearchMany(
tags.allReads,
query,
);

const seen = getSeenFacts(sessionId);
const { text, newFacts } = formatCombinedContext(
profileResult,
CONFIG.maxMemories,
CONFIG.maxProfileItems,
projectSearchResult,
seen,
);

Expand Down
3 changes: 1 addition & 2 deletions src/hooks/session-start.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ async function main() {
log("session-start: begin", { sessionId, tags });

try {
const profileResult = await client.getProfile(tags.user);
const profileResult = await client.getProfileMany(tags.allReads);
const seen = getSeenFacts(sessionId);
const { text, newFacts } = formatCombinedContext(
{
Expand All @@ -99,7 +99,6 @@ async function main() {
},
0,
CONFIG.maxProfileItems,
undefined,
seen,
);

Expand Down
12 changes: 8 additions & 4 deletions src/services/capture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
} from "./transcript.js";
import { getLastCapturedIndex, setLastCapturedIndex } from "./tracker.js";
import { filterBySignals, groupEntriesIntoTurns } from "./signals.js";
import type { ResolvedTags } from "./tags.js";

export interface CaptureOptions {
/** Minimum number of new entries required before capturing. Default: 0 */
Expand Down Expand Up @@ -53,7 +54,7 @@ export async function captureEntries(
client: SupermemoryClient,
sessionId: string,
transcriptPath: string | null,
tags: { project: string; user: string },
tags: Pick<ResolvedTags, "project" | "user" | "projectName">,
options: CaptureOptions = {},
): Promise<void> {
const { requireMinEntries = 0, requireMinTurns = 0 } = options;
Expand Down Expand Up @@ -134,17 +135,20 @@ export async function captureEntries(

const metadata = {
type: "conversation" as const,
project: tags.projectName,
sm_project_id: tags.projectId,
sessionId,
entryCount: newEntries.length,
timestamp: new Date().toISOString(),
sm_scope: "personal",
sm_capture_mode: caller === "flush" ? "session_end" : "turn",
};

// Save automatic transcript capture to the user container. Explicit project
// knowledge is still saved via the supermemory-save skill.
// Automatic capture and explicit saves share one project container. Scope
// metadata preserves optional personal/project filtering.
// Use customId so all session turns go into the same document.
try {
await client.addMemory(content, tags.user, metadata, {
await client.addMemory(content, tags.canonical, metadata, {
customId: sessionId,
entityContext: USER_ENTITY_CONTEXT,
});
Expand Down
Loading