diff --git a/README.md b/README.md
index 7ab7b0d..3613b6d 100644
--- a/README.md
+++ b/README.md
@@ -19,6 +19,9 @@ and the lessons learned across every project โ automatically.
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.
- ๐ **Privacy-aware** โ anything wrapped in `...` is redacted
before being sent to Supermemory.
- โก **Zero-config install** โ one command sets up `~/.codex/config.toml` and
@@ -108,6 +111,14 @@ 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.
+### 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.
+
### Signal extraction (optional)
When `signalExtraction` is enabled, only conversation turns containing signal keywords
diff --git a/src/services/capture.ts b/src/services/capture.ts
index 1d26d45..a97cb9d 100644
--- a/src/services/capture.ts
+++ b/src/services/capture.ts
@@ -4,7 +4,10 @@
* and saves to the user container.
*/
import { existsSync } from "node:fs";
-import { SupermemoryClient } from "./client.js";
+import {
+ SupermemoryClient,
+ USER_ENTITY_CONTEXT,
+} from "./client.js";
import { log } from "./logger.js";
import {
parseTranscript,
@@ -141,7 +144,10 @@ export async function captureEntries(
// knowledge is still saved via the supermemory-save skill.
// Use customId so all session turns go into the same document.
try {
- await client.addMemory(content, tags.user, metadata, { customId: sessionId });
+ await client.addMemory(content, tags.user, metadata, {
+ customId: sessionId,
+ entityContext: USER_ENTITY_CONTEXT,
+ });
const lastEntry = newEntries[newEntries.length - 1];
setLastCapturedIndex(sessionId, lastEntry.index);
diff --git a/src/services/client.ts b/src/services/client.ts
index c8f4eff..cae51f7 100644
--- a/src/services/client.ts
+++ b/src/services/client.ts
@@ -62,6 +62,33 @@ export interface ProfileWithSearchResult {
error?: string;
}
+export const USER_ENTITY_CONTEXT = `Developer coding session transcript for a persistent user profile.
+
+EXTRACT:
+- User preferences: preferred languages, frameworks, libraries, editors, workflows, and communication style
+- Stable habits: testing style, code review expectations, formatting preferences, privacy preferences
+- Repeated personal decisions: tools the user consistently chooses or avoids
+- Long-lived learnings: concepts the user learned or wants remembered across projects
+
+SKIP:
+- Project-specific architecture unless it reflects a durable user preference
+- One-off assistant suggestions the user did not accept
+- Low-level implementation details that only matter inside the current repository`;
+
+export const PROJECT_ENTITY_CONTEXT = `Project/codebase knowledge from Codex coding sessions.
+
+EXTRACT:
+- Architecture: repo structure, services, modules, data flow, and integration boundaries
+- Conventions: naming, component patterns, API patterns, testing practices, and style rules
+- Decisions: chosen approaches, tradeoffs, migrations, and rejected alternatives
+- Setup: commands, environment requirements, deployment notes, and debugging workflows
+- Implementation lessons: bugs fixed, root causes, and reusable project-specific context
+
+SKIP:
+- Generic user preferences that are not specific to this project
+- Verbatim assistant explanations unless they became an accepted project decision
+- Transient command output with no lasting project value`;
+
export class SupermemoryClient {
private client: Supermemory | null = null;
@@ -190,12 +217,13 @@ export class SupermemoryClient {
content: string,
containerTag: string,
metadata?: { type?: MemoryType; tool?: string; [key: string]: unknown },
- options?: { customId?: string }
+ options?: { customId?: string; entityContext?: string }
) {
log("addMemory: start", {
containerTag,
contentLength: content.length,
customId: options?.customId,
+ hasEntityContext: !!options?.entityContext,
});
try {
// Always stamp `sm_source` so mono's `document.source` column attributes
@@ -213,6 +241,7 @@ export class SupermemoryClient {
containerTag: string;
metadata?: Record;
customId?: string;
+ entityContext?: string;
} = {
content,
containerTag,
@@ -221,6 +250,9 @@ export class SupermemoryClient {
if (options?.customId) {
payload.customId = options.customId;
}
+ if (options?.entityContext) {
+ payload.entityContext = options.entityContext;
+ }
const result = await withTimeout(
this.getClient().memories.add(payload),
TIMEOUT_MS
diff --git a/src/skills/save-memory.ts b/src/skills/save-memory.ts
index a6c5294..35ca32d 100644
--- a/src/skills/save-memory.ts
+++ b/src/skills/save-memory.ts
@@ -1,5 +1,5 @@
-import { isConfigured, validateContainerTag } from "../config.js";
-import { SupermemoryClient } from "../services/client.js";
+import { CONFIG, isConfigured, validateContainerTag } from "../config.js";
+import { PROJECT_ENTITY_CONTEXT, SupermemoryClient } from "../services/client.js";
import { getProjectName, getProjectTag } from "../services/tags.js";
function parseArgs(args: string[]): { content: string; containerTag?: string } {
@@ -17,6 +17,25 @@ function parseArgs(args: string[]): { content: string; containerTag?: string } {
return { content: contentParts.join(" "), containerTag };
}
+function getEntityContext(containerTag: string | undefined): string {
+ if (!containerTag) return PROJECT_ENTITY_CONTEXT;
+
+ const customContainer = CONFIG.customContainers.find((c) => c.tag === containerTag);
+ if (!customContainer) return PROJECT_ENTITY_CONTEXT;
+
+ return `Custom Codex memory container.
+
+Purpose: ${customContainer.description}
+
+EXTRACT:
+- Memories that match this container's purpose
+- Stable facts, preferences, decisions, workflows, and implementation lessons relevant to this container
+
+SKIP:
+- Unrelated project or user context that belongs in another container
+- One-off assistant suggestions the user did not accept`;
+}
+
async function main(): Promise {
if (!isConfigured()) {
console.error(
@@ -46,7 +65,6 @@ async function main(): Promise {
const projectName = getProjectName(process.cwd());
const effectiveTag = containerTag || projectTag;
-
try {
const metadata = {
type: "project-knowledge" as const,
@@ -55,12 +73,14 @@ async function main(): Promise {
timestamp: new Date().toISOString(),
};
- const result = await client.addMemory(content, effectiveTag, metadata);
+ const result = await client.addMemory(content, effectiveTag, metadata, {
+ entityContext: getEntityContext(containerTag),
+ });
if (result.success) {
if (!containerTag) {
await client.updateContainerTagName(projectTag, `Codex ยท ${projectName}`);
- }
+ }
const tagLabel = containerTag ? `container '${containerTag}'` : `project '${effectiveTag}'`;
console.log(`Memory saved (id: ${result.id}) to ${tagLabel}`);
} else {
diff --git a/test/unit.mjs b/test/unit.mjs
index c80fcf7..4b8b8b9 100644
--- a/test/unit.mjs
+++ b/test/unit.mjs
@@ -231,6 +231,27 @@ describe("stripPrivateContent", () => {
// โโโ hooks.json format โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+describe("entity context wiring", () => {
+ test("client addMemory forwards entityContext into the API payload", () => {
+ const content = readFileSync(new URL("../src/services/client.ts", import.meta.url), "utf-8");
+ assert.ok(content.includes("USER_ENTITY_CONTEXT"));
+ assert.ok(content.includes("PROJECT_ENTITY_CONTEXT"));
+ assert.ok(content.includes("entityContext?: string"));
+ assert.ok(content.includes("payload.entityContext = options.entityContext"));
+ });
+
+ test("automatic capture writes user entity context", () => {
+ const content = readFileSync(new URL("../src/services/capture.ts", import.meta.url), "utf-8");
+ assert.ok(content.includes("entityContext: USER_ENTITY_CONTEXT"));
+ });
+
+ test("manual save writes project entity context", () => {
+ const content = readFileSync(new URL("../src/skills/save-memory.ts", import.meta.url), "utf-8");
+ assert.ok(content.includes("PROJECT_ENTITY_CONTEXT"));
+ assert.ok(content.includes("entityContext: getEntityContext(containerTag)"));
+ });
+});
+
describe("hooks.json format", () => {
test("wrapped hooks.json shape is valid JSON", () => {
const recallScript = "/home/user/.codex/supermemory/recall.js";