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
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const L3WorldModelRuntimeNamespaceShape = {
workspaceId: OptionalNonEmptyStringSchema,
workspacePath: OptionalNonEmptyStringSchema,
sessionKey: OptionalNonEmptyStringSchema,
userId: OptionalNonEmptyStringSchema,
userId: OptionalNonEmptyStringSchema.nullable(),
tenantId: OptionalNonEmptyStringSchema
} as const;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -230,8 +230,10 @@ describe("codex skill target", () => {
it("installs a UserPromptSubmit hook that blocks resume commands with top L1 candidates", async () => {
const { rootDirectory, memmyConfigPath } = createFixture();
let requestBody: Record<string, unknown> | undefined;
let requestCount = 0;
let authorization = "";
const server = createServer(async (request: IncomingMessage, response: ServerResponse) => {
requestCount += 1;
authorization = String(request.headers.authorization || "");
const url = new URL(request.url || "/", "http://127.0.0.1");
if (request.method === "POST" && url.pathname === "/api/v1/memory/search") {
Expand Down Expand Up @@ -336,6 +338,11 @@ describe("codex skill target", () => {
};
expect(hooksAfter.hooks?.UserPromptSubmit).toBeUndefined();
expect(hooksAfter.hooks?.Stop).toBeUndefined();
const requestsBeforeRestart = requestCount;
const afterUninstall = await runNodeHook(hookScriptPath, JSON.stringify({hook_event_name:"Stop",session_id:"fresh-process"}));
expect(afterUninstall.status).not.toBe(0);
expect(afterUninstall.stderr).toContain("MODULE_NOT_FOUND");
expect(requestCount).toBe(requestsBeforeRestart);
expect(readFileSync(join(rootDirectory, "AGENTS.md"), "utf8")).toBe(existingTargetFile);
expect(existsSync(join(rootDirectory, "skills", "memmy-memory"))).toBe(false);
expect(existsSync(join(rootDirectory, "skills", "memmy-resume"))).toBe(false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1338,7 +1338,7 @@ def _load_runtime() -> Dict[str, str]:
return {
"baseUrl": base_url,
"token": token,
"userId": _clean_text(app.get("userId")) or _clean_text(memory.get("userId")) or _clean_text(plugin_config.get("userId")) or "local-user",
"userId": "local-user",
"workspaceHostId": _clean_text(plugin_config.get("workspaceHostId")),
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export async function readMemmyMemoryServiceConfig(configPath: string): Promise<
token: normalizeString(storage.token) ||
normalizeString(memmyMemory.token) ||
normalizeString(legacyStorage.token),
userId: normalizeString(app.userId) || normalizeString(memmyMemory.userId) || "local-user",
userId: "local-user",
workspaceHostId: deriveWorkspaceHostId(getOrCreateInstallationId())
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@ describe("memmy resume hook stop capture", () => {
workspaceHostId: "a".repeat(64),
namespace: {
source,
userId: "installed-owner",
userId: "local-user",
sessionKey: `${source}-memory-host-session`,
},
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ afterEach(() => {
});

describe("Memory lifecycle runtime", () => {
it("reads Memory connection and owner settings without a workspace scanning flag", async () => {
it("reads Memory connection with account-independent identity without a workspace scanning flag", async () => {
const fixture = createFixture();
const configUrl = pathToFileURL(join(fixture, "memmy-memory-config.json"));
const configPath = join(fixture, "config.yaml");
Expand All @@ -45,12 +45,12 @@ describe("Memory lifecycle runtime", () => {
await expect(readRuntimeConfig(configUrl, true)).resolves.toEqual({
endpoint: "http://127.0.0.1:18888",
token: "test-token",
userId: "installed-owner",
userId: "local-user",
workspaceHostId: "a".repeat(64),
});
});

it.each([undefined, "pending-session"])("submits the pinned owner with pending Session %s across account changes", async (sessionId) => {
it.each([undefined, "pending-session"])("submits device identity with pending Session %s across account changes", async (sessionId) => {
const fixture = createFixture();
const requests: Array<{ path: string; body: Record<string, unknown> }> = [];
const server = createServer(async (request, response) => {
Expand All @@ -77,7 +77,7 @@ describe("Memory lifecycle runtime", () => {
for (const request of requests) {
expect(request.path).toBe("/api/v1/source-turns/complete");
expect(request.body.namespace).toEqual({
source: "codex", profileId: "work", userId: "installed-owner", sessionKey: "native-conversation",
source: "codex", profileId: "work", userId: "local-user", sessionKey: "native-conversation",
});
expect(request.body).toMatchObject({
sourceTurn: { source: "codex", profileId: "work", conversationId: "native-conversation" },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,7 @@ export async function readRuntimeConfig(configUrl: URL, pinnedOwner = false): Pr
return {
endpoint: text(storage.endpoint) || text(memory.endpoint) || text(legacyStorage.endpoint) || text(snapshot.endpoint) || DEFAULT_ENDPOINT,
token: text(storage.token) || text(memory.token) || text(legacyStorage.token) || text(snapshot.token),
userId: pinnedOwner
? text(snapshot.userId) || "local-user"
: text(app.userId) || text(memory.userId) || text(snapshot.userId) || "local-user",
userId: "local-user",
workspaceHostId: text(snapshot.workspaceHostId),
};
}
Expand Down
4 changes: 2 additions & 2 deletions App/backend/src/infrastructure/memmy-config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -681,7 +681,7 @@ export async function writeAccountModelProjectionToMemmyConfig(
// inherit agent_chat or retain a stale BYOK memory endpoint.
roleRouting.summary = "fixed";
roleRouting.evolution = "fixed";
memory.userId = ownerAccountId;
delete memory.userId;
memory.roleRouting = roleRouting;
memory.summary = {
...withoutMemoryConnection(previousMemoryRecord(existingMemory.summary)),
Expand All @@ -702,7 +702,7 @@ export async function writeAccountModelProjectionToMemmyConfig(
mode: "cloud"
};
memoryConfigAffected = memoryConfigAffected
|| existingString(existingMemory.userId) !== ownerAccountId
|| existingMemory.userId !== undefined
|| !accountMemoryConnectionMatches(
existingMemory.summary,
ACCOUNT_MODELS.memory_summary,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,8 +143,8 @@ describe("account model projection current catalog", () => {
});
}
expect(saved.modelAssignments.byok).toEqual(beforeByok);
expect(saved.memmyMemory).not.toHaveProperty("userId");
expect(saved.memmyMemory).toMatchObject({
userId: "owner-a",
roleRouting: { summary: "fixed", evolution: "fixed" },
summary: {
provider: "openai_compatible",
Expand Down
5 changes: 1 addition & 4 deletions App/backend/src/services/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,10 +140,7 @@ export function createBackendServices(options: CreateBackendServicesOptions): Ba
const mode = options.appStateStore.repositories.bootstrap.getAppSettings().userMode;
return mode === "account" || mode === "byok" ? mode : null;
};
const resolveMemoryUserId = () => {
const session = accountSessionRepository.get();
return session.authenticated ? session.profile.userId : "local-user";
};
const resolveMemoryUserId = () => "local-user";
const memoryAddAnalytics = createMemoryDesktopAddAnalytics({
getUserId: resolveAnalyticsUserId,
getUserMode: resolveAnalyticsUserMode,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,21 +95,21 @@ describe("desktop task identity / issue 409", () => {
expect(await panel.tasks({ q: "no-matching-task" }, ctx)).toMatchObject({ total: 0, tasks: [] });
}

// Displaying local data must not migrate the original task ownership.
for (const [userId, total] of [["local-user", 21], ["account-user", 1], ["previous-account", 1]] as const) {
expect(f.service.panelTasks({ namespace: { userId } }).total).toBe(total);
// A legacy user namespace no longer partitions this device library.
for (const userId of ["local-user", "account-user", "previous-account"]) {
expect(f.service.panelTasks({ namespace: { userId } }).total).toBe(23);
}
});

it("preserves Memory service authentication and token-bound user scopes", async () => {
it("preserves authentication and operation permissions for the shared device library", async () => {
const f = await fixture();
const localId = await f.addTask("local-user", "local-scope-task");
const accountId = await f.addTask("account-user", "account-scope-task");
for (const token of ["scoped-panel-token", "cloud-panel-token"]) {
const panel = f.panel(token, () => "account-user");
const result = await panel.tasks({}, ctx);
expect(result.total).toBe(1);
expect(result.tasks.map(task => task.id)).toEqual([accountId]);
expect(result.total).toBe(2);
expect(result.tasks.map(task => task.id).sort()).toEqual([localId,accountId].sort());
if (token === "scoped-panel-token") {
await expect(panel.deleteTask(localId, ctx)).rejects.toMatchObject({ code: "forbidden" });
}
Expand Down
6 changes: 1 addition & 5 deletions App/memmy-agent/src/memmy-memory/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,11 @@ export function resolveMemmyMemoryConfig(config: Config | Record<string, any> |
const raw = (config as any)?.memmyMemory ?? {};
return {
enabled: Boolean(raw?.enabled ?? raw?.enable ?? true),
userId: stringOrUndefined(raw?.userId) ?? "local-user",
userId: "local-user",
retrievalLayers: memoryLayersOrUndefined(raw?.retrievalLayers),
};
}

function stringOrUndefined(value: any): string | undefined {
return typeof value === "string" && value.trim() ? value.trim() : undefined;
}

function memoryLayersOrUndefined(value: any): MemmyMemoryLayer[] | undefined {
if (!Array.isArray(value)) return undefined;
return [...new Set(value.filter((layer): layer is MemmyMemoryLayer => MEMORY_LAYERS.has(layer)))];
Expand Down
2 changes: 1 addition & 1 deletion App/memmy-agent/src/memmy-memory/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export type MemmyMemoryRuntimeNamespace = {
workspaceId?: string;
workspacePath?: string;
sessionKey?: string;
userId?: string;
userId?: string | null;
tenantId?: string;
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ describe("AgentLoop memmy memory integration", () => {
l3WorldModelProtocolVersion: 2,
workspaceUri: expect.stringMatching(/^file:\/\//u),
workspaceHostId: expect.stringMatching(/^[a-f0-9]{64}$/u),
namespace: { sessionKey: "websocket:memory-project", userId: "loop-user" },
namespace: { sessionKey: "websocket:memory-project", userId: "local-user" },
});
expect(JSON.stringify(opened.body)).not.toContain(project.id);
expect(requests.filter((request) => request.path === "/api/v1/sessions/open")).toHaveLength(1);
Expand Down
2 changes: 1 addition & 1 deletion App/memmy-agent/tests/memmy-memory/discovery.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ describe("memmy memory discovery", () => {
expect(defaultConfig.memmyMemory.enabled).toBe(true);
expect(resolveMemmyMemoryConfig(enabled).enabled).toBe(true);
expect(resolveMemmyMemoryConfig(defaultConfig).enabled).toBe(true);
expect(resolveMemmyMemoryConfig(enabled).userId).toBe("user_config_1");
expect(resolveMemmyMemoryConfig(enabled).userId).toBe("local-user");
expect(resolveMemmyMemoryConfig(enabled).retrievalLayers).toEqual(["L1", "L3"]);
expect(resolveMemmyMemoryConfig(disabled).enabled).toBe(false);
expect(resolveMemmyMemoryConfig(disabled).userId).toBe("local-user");
Expand Down
2 changes: 1 addition & 1 deletion Memory/src/agent-source/integration/hermes/target.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1338,7 +1338,7 @@ def _load_runtime() -> Dict[str, str]:
return {
"baseUrl": base_url,
"token": token,
"userId": _clean_text(app.get("userId")) or _clean_text(memory.get("userId")) or _clean_text(plugin_config.get("userId")) or "local-user",
"userId": "local-user",
"workspaceHostId": _clean_text(plugin_config.get("workspaceHostId")),
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export async function readMemmyMemoryServiceConfig(configPath: string): Promise<
token: normalizeString(storage.token) ||
normalizeString(memmyMemory.token) ||
normalizeString(legacyStorage.token),
userId: normalizeString(app.userId) || normalizeString(memmyMemory.userId) || "local-user",
userId: "local-user",
workspaceHostId: deriveWorkspaceHostId(getOrCreateInstallationId())
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,7 @@ export async function readRuntimeConfig(configUrl: URL, pinnedOwner = false): Pr
return {
endpoint: text(storage.endpoint) || text(memory.endpoint) || text(legacyStorage.endpoint) || text(snapshot.endpoint) || DEFAULT_ENDPOINT,
token: text(storage.token) || text(memory.token) || text(legacyStorage.token) || text(snapshot.token),
userId: pinnedOwner
? text(snapshot.userId) || "local-user"
: text(app.userId) || text(memory.userId) || text(snapshot.userId) || "local-user",
userId: "local-user",
workspaceHostId: text(snapshot.workspaceHostId),
};
}
Expand Down
6 changes: 3 additions & 3 deletions Memory/src/algorithm/plugin-algorithms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export interface TraceMemoryMeta {
rawTurnId?: string;
episodeId?: string;
sessionId?: string;
userId: string;
userId: string | null;
summary: string;
userText: string;
agentText: string;
Expand Down Expand Up @@ -4378,7 +4378,7 @@ export function retrievePluginMemories(input: {
traceVectorTags,
traceVectorTagsRequired,
seededChannelScores: input.channelScoresByMemory?.get(memory.id),
useSeededChannelScores: input.channelScoresByMemory !== undefined,
useSeededChannelScores: input.channelScoresByMemory?.has(memory.id) ?? false,
config
}
))
Expand Down Expand Up @@ -5202,7 +5202,7 @@ function hasTaggedTraceVectorMatch(
if (shouldExcludeTraceFromTurnContext(trace, options)) return false;
return vectorChannelsForMemory(memory, queryVec, options.config, {
seededChannelScores: options.channelScoresByMemory?.get(memory.id),
useSeededChannelScores: options.channelScoresByMemory !== undefined
useSeededChannelScores: options.channelScoresByMemory?.has(memory.id) ?? false
}).channels.some((channel) =>
channel.channel === "vec_summary" || channel.channel === "vec_action"
);
Expand Down
8 changes: 4 additions & 4 deletions Memory/src/algorithm/trace-direct-skill.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ export interface OutcomeThresholds {

export interface EpisodeSkillFeatures {
episodeId: string;
userId: string;
userId: string | null;
projectId?: string;
tools: string[];
artifacts: string[];
Expand All @@ -104,7 +104,7 @@ export interface EpisodeSkillFeatures {

export interface SkillClusterFeatures {
id: string;
userId: string;
userId: string | null;
projectId?: string;
tools: string[];
artifacts: string[];
Expand Down Expand Up @@ -282,7 +282,7 @@ export function firstUserQuery(turns: RawTurnLike[]): string {

export function extractEpisodeSkillFeatures(input: {
episodeId: string;
userId: string;
userId: string | null;
projectId?: string;
rTask?: number;
turns: RawTurnLike[];
Expand Down Expand Up @@ -421,7 +421,7 @@ export function decideClusterAssignment(
stage: "coarse" | "fine";
} | undefined;
for (const cluster of clusters) {
if (cluster.userId !== episode.userId) continue;

if (episode.projectId && cluster.projectId && episode.projectId !== cluster.projectId) continue;
const scored = scoreClusterJoin(episode, cluster, config);
if (scored.score === null) continue;
Expand Down
6 changes: 1 addition & 5 deletions Memory/src/cli/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,7 @@ export function loadCliMemoryConfig(configPath?: string): {
optionalString(process.env.MEMORY_SERVICE_TOKEN) ??
optionalString(storage.token) ??
optionalString(memmyMemory.token),
userId: optionalString(process.env.MEMMY_MEMORY_USER_ID) ??
optionalString(process.env.MEMMY_USER_ID) ??
optionalString(process.env.MEMORY_SERVICE_USER_ID) ??
optionalString(memmyMemory.userId) ??
optionalString(app.userId)
userId: undefined
},
path: selectedPath
};
Expand Down
2 changes: 1 addition & 1 deletion Memory/src/cli/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,7 @@ function setupMemmyMemoryConfig(
const memmyMemory: Record<string, unknown> = {
...existing,
version: 1,
userId: optionalString(existing.userId) ?? options.appUserId ?? "local-user",
userId: undefined,
roleRouting: {
...roleRouting,
// Account mode has platform-owned models for both memory roles. Never
Expand Down
4 changes: 2 additions & 2 deletions Memory/src/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -600,7 +600,7 @@ function parseConfigFile(path: string): Record<string, unknown> {
function configFromEnv(): Record<string, unknown> {
return compactRecord({
domain: process.env.MEMMY_MEMORY_DOMAIN ?? process.env.MEMMY_DOMAIN,
userId: process.env.MEMMY_MEMORY_USER_ID ?? process.env.MEMMY_USER_ID ?? process.env.MEMORY_SERVICE_USER_ID,
userId: undefined,
storage: compactRecord({
mode: process.env.MEMMY_MEMORY_MODE,
backend: process.env.MEMMY_MEMORY_BACKEND,
Expand Down Expand Up @@ -701,7 +701,7 @@ function normalizeConfig(input: Record<string, unknown>): MemmyConfig {
version: 1,
domain: memoryDomainName(input.domain, DEFAULT_MEMMY_CONFIG.domain),
roleRouting: normalizeRoleRouting(asRecord(input.roleRouting)),
userId: optionalString(input.userId),
userId: undefined,
...(language ? { language } : {}),
storage,
summary,
Expand Down
2 changes: 1 addition & 1 deletion Memory/src/contracts/memory-l3-world-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const L3WorldModelRuntimeNamespaceShape = {
workspaceId: OptionalNonEmptyStringSchema,
workspacePath: OptionalNonEmptyStringSchema,
sessionKey: OptionalNonEmptyStringSchema,
userId: OptionalNonEmptyStringSchema,
userId: OptionalNonEmptyStringSchema.nullable(),
tenantId: OptionalNonEmptyStringSchema
} as const;

Expand Down
1 change: 1 addition & 0 deletions Memory/src/server/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1565,6 +1565,7 @@ function strictEnvelopeWithPrincipal(
"source"
];
for (const field of fields) {
if (field === "userId" && (principal.kind === "local" || principal.kind === "anonymous")) continue;
const requested = requestNamespace?.[field];
const scoped = principalNamespace?.[field];
if (typeof requested === "string" && requested && typeof scoped === "string" && scoped && requested !== scoped) {
Expand Down
4 changes: 2 additions & 2 deletions Memory/src/service/embedding/embedding-job-processor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ export interface PreparedEmbeddingJob {

export interface EnqueueWorkerJobInput {
jobType: JobType;
userId: string;
userId: string | null;
sessionId?: string;
episodeId?: string;
targetMemoryId?: string;
Expand Down Expand Up @@ -758,7 +758,7 @@ export class EmbeddingJobProcessor {
: undefined;
if (
decision.userMemoryAction === "correct_existing" &&
(!correctionTarget || correctionTarget.userId !== sourceMemory.userId || correctionTarget.status !== "active")
(!correctionTarget || correctionTarget.status !== "active")
) return undefined;
const candidate = buildUserMemory({
id: `user_memory_${stableHash(`${sourceTurnId}:${content}`).slice(0, 20)}`,
Expand Down
4 changes: 2 additions & 2 deletions Memory/src/service/embedding/embedding-maintenance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export interface EmbeddingMaintenanceRunResult {
interface Dependencies {
repos: Repositories;
embedder: Embedder;
userId: string;
userId: string | null;
currentWork?(dimension: number): { retryQueue: { pending: number; active: number; stale: number; failed: number }; outdatedVectors: Set<string> };
enqueueMemory(memory: MemoryRow, text: string, at: string, vectorField?: EmbeddingRetryVectorField): string;
enqueueUserMemory(memory: UserMemoryRecord, at: string): string;
Expand All @@ -74,7 +74,7 @@ interface Dependencies {
export class EmbeddingMaintenance {
constructor(private readonly deps: Dependencies) {}

private get key(): string { return `embedding-maintenance:${this.deps.userId}`; }
private get key(): string { return `embedding-maintenance:${this.deps.userId ?? "device"}`; }

private entries(): EmbeddingMaintenanceEntry[] {
return [...this.deps.repos.vectors.maintenanceEntries(),
Expand Down
Loading
Loading