Memory identity reaches every source (MA-16)
A memory source decides whose memory a save, search or extraction touches with entityId(ctx). The run's userId, tags and metadata now reach ctx on every memory path. Before this release only runtime-js's (and the CF Durable Object's) auto-injection and realtime extraction carried them:
- the memory tools,
- completion extraction on every runtime,
- and CF Workflows' auto-injection and realtime extraction
all resolved a user-keyed source to its fallback, so saves landed under the wrong entity and searches came back empty.
This is a breaking change of @helix-agents/core and @helix-agents/memory (0.x, so a minor bump). @helix-agents/runtime-js, @helix-agents/runtime-cloudflare and @helix-agents/sdk take a minor.
Agent code
Nothing to change. A source like entityId: (ctx) => ctx.userId ?? ctx.sessionId now works on every path.
A Cloudflare-Workflow or Temporal extraction worker
CloudflareWorkflowMemoryExecutor / TemporalMemoryExecutor used to hand your createWorkflow / startWorkflow only { sessionId, agentType }. They now hand it the run's identity and each source's entityId, resolved at enqueue. Forward entityIds to memoryExtractionActivity(), which now requires it.
Before:
new CloudflareWorkflowMemoryExecutor({
createWorkflow: async ({ sessionId, agentType }) => {
await env.MEMORY_WORKFLOW.create({ params: { sessionId, agentType } });
},
});
// in the workflow / activity
await memoryExtractionActivity({ sessionId, agentType, messages, memoryConfig });After:
new CloudflareWorkflowMemoryExecutor({
// input: { sessionId, runId, agentType, userId?, tags?, metadata?, entityIds }
createWorkflow: async (input) => {
await env.MEMORY_WORKFLOW.create({ params: input });
},
});
// in the workflow / activity
await memoryExtractionActivity({
sessionId: params.sessionId,
agentType: params.agentType,
messages, // loaded from your state store, as before
memoryConfig,
entityIds: params.entityIds,
});TemporalMemoryExecutor's startWorkflow(input) gets the same input. Pass it as the workflow's argument and forward entityIds to the activity the same way.
The run's customState is never part of the input. An identity whose serialized size is over MEMORY_EXTRACTION_INPUT_MAX_BYTES (64 KiB) is refused at enqueue with a typed validation_error, which is logged; the runtimes log it as a failed non-blocking extraction.
Custom executors and bring-your-own-loop code
MemoryExtractionTask:sessionId/agentTypeare replaced byidentity: MemoryIdentity(sessionId, runId, agentType, userId?, tags?, metadata?) andentityIds: Record<sourceName, string>. A custom executor stores withmemoryManager.extractAndStoreUnder({ messages, entityIds }).MemoryManager.triggerAsyncExtraction: now takes{ context, messages }. Build the context with corememoryEntityContextOf(state).MemoryManager.buildTools: now takesmemoryIdentityOf(memoryEntityContextOf(state)), the identity withoutcustomState; the tools read state live.MemoryManager.storeExtractedMemories(extracted, entityIds): takes resolvedentityIds;resolveEntityIds(context)computes them.