@elevasis/sdk 1.38.0 → 1.39.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.cjs CHANGED
@@ -45850,7 +45850,7 @@ function wrapAction(commandName, fn) {
45850
45850
  // package.json
45851
45851
  var package_default = {
45852
45852
  name: "@elevasis/sdk",
45853
- version: "1.38.0",
45853
+ version: "1.39.0",
45854
45854
  description: "SDK for building Elevasis organization resources",
45855
45855
  type: "module",
45856
45856
  bin: {
package/dist/index.d.ts CHANGED
@@ -11239,6 +11239,17 @@ type MessageEvent = {
11239
11239
  result?: unknown;
11240
11240
  error?: string;
11241
11241
  };
11242
+ /**
11243
+ * A message from an earlier turn of this session.
11244
+ *
11245
+ * Deliberately lean (no ids, timestamps, or event metadata): this crosses the
11246
+ * parent -> worker payload boundary and is replayed verbatim into the model's message
11247
+ * array, so it carries only what the model needs to read the conversation.
11248
+ */
11249
+ interface ConversationMessage {
11250
+ role: 'user' | 'assistant';
11251
+ content: string;
11252
+ }
11242
11253
  /**
11243
11254
  * Execution context for all resources
11244
11255
  * Unified callback replaces SessionTurnMessages (removed)
@@ -11246,6 +11257,15 @@ type MessageEvent = {
11246
11257
  interface ExecutionContext extends ExecutionMetadata {
11247
11258
  logger: IExecutionLogger;
11248
11259
  signal?: AbortSignal;
11260
+ /**
11261
+ * This session's earlier turns, oldest first, as actually said.
11262
+ *
11263
+ * A session agent is handed its own conversation: these are replayed into the model's
11264
+ * message array as real user/assistant turns. Absent for one-off (non-session)
11265
+ * executions. The session layer bounds this before it is sent -- see
11266
+ * `selectConversationHistory`.
11267
+ */
11268
+ conversationHistory?: ConversationMessage[];
11249
11269
  onMessageEvent?: (event: MessageEvent) => Promise<void>;
11250
11270
  /** Called per iteration to write heartbeat + check stall status. Non-fatal if it throws. */
11251
11271
  onHeartbeat?: () => Promise<void>;
@@ -2760,6 +2760,17 @@ type MessageEvent = {
2760
2760
  result?: unknown;
2761
2761
  error?: string;
2762
2762
  };
2763
+ /**
2764
+ * A message from an earlier turn of this session.
2765
+ *
2766
+ * Deliberately lean (no ids, timestamps, or event metadata): this crosses the
2767
+ * parent -> worker payload boundary and is replayed verbatim into the model's message
2768
+ * array, so it carries only what the model needs to read the conversation.
2769
+ */
2770
+ interface ConversationMessage {
2771
+ role: 'user' | 'assistant';
2772
+ content: string;
2773
+ }
2763
2774
  /**
2764
2775
  * Execution context for all resources
2765
2776
  * Unified callback replaces SessionTurnMessages (removed)
@@ -2767,6 +2778,15 @@ type MessageEvent = {
2767
2778
  interface ExecutionContext extends ExecutionMetadata {
2768
2779
  logger: IExecutionLogger;
2769
2780
  signal?: AbortSignal;
2781
+ /**
2782
+ * This session's earlier turns, oldest first, as actually said.
2783
+ *
2784
+ * A session agent is handed its own conversation: these are replayed into the model's
2785
+ * message array as real user/assistant turns. Absent for one-off (non-session)
2786
+ * executions. The session layer bounds this before it is sent -- see
2787
+ * `selectConversationHistory`.
2788
+ */
2789
+ conversationHistory?: ConversationMessage[];
2770
2790
  onMessageEvent?: (event: MessageEvent) => Promise<void>;
2771
2791
  /** Called per iteration to write heartbeat + check stall status. Non-fatal if it throws. */
2772
2792
  onHeartbeat?: () => Promise<void>;
@@ -10313,6 +10313,17 @@ type MessageEvent = {
10313
10313
  result?: unknown;
10314
10314
  error?: string;
10315
10315
  };
10316
+ /**
10317
+ * A message from an earlier turn of this session.
10318
+ *
10319
+ * Deliberately lean (no ids, timestamps, or event metadata): this crosses the
10320
+ * parent -> worker payload boundary and is replayed verbatim into the model's message
10321
+ * array, so it carries only what the model needs to read the conversation.
10322
+ */
10323
+ interface ConversationMessage {
10324
+ role: 'user' | 'assistant';
10325
+ content: string;
10326
+ }
10316
10327
  /**
10317
10328
  * Execution context for all resources
10318
10329
  * Unified callback replaces SessionTurnMessages (removed)
@@ -10320,6 +10331,15 @@ type MessageEvent = {
10320
10331
  interface ExecutionContext extends ExecutionMetadata {
10321
10332
  logger: IExecutionLogger;
10322
10333
  signal?: AbortSignal;
10334
+ /**
10335
+ * This session's earlier turns, oldest first, as actually said.
10336
+ *
10337
+ * A session agent is handed its own conversation: these are replayed into the model's
10338
+ * message array as real user/assistant turns. Absent for one-off (non-session)
10339
+ * executions. The session layer bounds this before it is sent -- see
10340
+ * `selectConversationHistory`.
10341
+ */
10342
+ conversationHistory?: ConversationMessage[];
10323
10343
  onMessageEvent?: (event: MessageEvent) => Promise<void>;
10324
10344
  /** Called per iteration to write heartbeat + check stall status. Non-fatal if it throws. */
10325
10345
  onHeartbeat?: () => Promise<void>;
@@ -10793,7 +10813,8 @@ type TypedAdapter<TMap extends ToolMethodMap$1> = {
10793
10813
  * humanCheckpoints?: [...], relationships?: {...} }
10794
10814
  *
10795
10815
  * Parent -> Worker: { type: 'execute', resourceId, executionId, input, organizationId?, organizationName?,
10796
- * sessionId?, sessionTurnNumber?, parentExecutionId?, executionDepth }
10816
+ * sessionId?, sessionTurnNumber?, sessionMemory?, conversationHistory?,
10817
+ * parentExecutionId?, executionDepth }
10797
10818
  * Worker -> Parent: { type: 'result', status, output?, memorySnapshot?, error?, logs, metrics: { durationMs } }
10798
10819
  *
10799
10820
  * Parent -> Worker: { type: 'abort' } (graceful abort before terminate)
@@ -4768,6 +4768,8 @@ function buildReasoningRequest(iterationContext) {
4768
4768
  iterationContext.iteration,
4769
4769
  iterationContext.executionContext.sessionTurnNumber
4770
4770
  ),
4771
+ // A session agent gets its own conversation. Non-session executions have none.
4772
+ conversationHistory: iterationContext.executionContext.conversationHistory ?? [],
4771
4773
  includeMessageAction: isSessionCapable,
4772
4774
  includeNavigateKnowledge: hasKnowledgeMap,
4773
4775
  includeMemoryOps
@@ -5214,12 +5216,16 @@ function validateTokenConfiguration(model, maxOutputTokens) {
5214
5216
  );
5215
5217
  }
5216
5218
  }
5219
+ function buildAgentMessages(systemPrompt, memoryContext, conversationHistory = []) {
5220
+ return [
5221
+ { role: "system", content: systemPrompt },
5222
+ ...conversationHistory.map(({ role, content }) => ({ role, content })),
5223
+ { role: "user", content: memoryContext }
5224
+ ];
5225
+ }
5217
5226
  async function callLLMForAgentIteration(adapter, request) {
5218
5227
  validateTokenConfiguration(request.model, request.constraints.maxOutputTokens);
5219
- const messages = [
5220
- { role: "system", content: request.systemPrompt },
5221
- { role: "user", content: request.memoryContext }
5222
- ];
5228
+ const messages = buildAgentMessages(request.systemPrompt, request.memoryContext, request.conversationHistory);
5223
5229
  const response = await adapter.generate({
5224
5230
  messages,
5225
5231
  responseSchema: buildIterationResponseSchema(
@@ -5248,10 +5254,7 @@ async function callLLMForAgentIteration(adapter, request) {
5248
5254
  async function callLLMForAgentCompletion(adapter, request) {
5249
5255
  validateTokenConfiguration(request.model, request.constraints.maxOutputTokens);
5250
5256
  const response = await adapter.generate({
5251
- messages: [
5252
- { role: "system", content: request.systemPrompt },
5253
- { role: "user", content: request.memoryContext }
5254
- ],
5257
+ messages: buildAgentMessages(request.systemPrompt, request.memoryContext, request.conversationHistory),
5255
5258
  responseSchema: request.outputSchema,
5256
5259
  // Use output schema directly
5257
5260
  temperature: request.constraints.temperature || 0.3,
@@ -5385,6 +5388,7 @@ async function processReasoning(iterationContext) {
5385
5388
  const { reasoning, memoryOps, nextActions } = await callLLMForAgentIteration(adapter, {
5386
5389
  systemPrompt: request.systemPrompt,
5387
5390
  memoryContext: request.memoryContext,
5391
+ conversationHistory: request.conversationHistory,
5388
5392
  tools: request.tools,
5389
5393
  constraints: request.constraints,
5390
5394
  model: iterationContext.modelConfig.model,
@@ -6257,11 +6261,10 @@ var MemoryManager = class {
6257
6261
  */
6258
6262
  toContext(currentIteration, currentTurn) {
6259
6263
  const status = this.getStatus();
6260
- const currentContext = this.memory.history.filter(
6261
- (entry) => (!currentTurn || entry.turnNumber === currentTurn || entry.turnNumber === void 0) && entry.iterationNumber === currentIteration
6262
- ).reverse();
6264
+ const inTurnScope = (entry) => !currentTurn || entry.turnNumber === currentTurn || entry.turnNumber == null;
6265
+ const currentContext = this.memory.history.filter((entry) => inTurnScope(entry) && entry.iterationNumber === currentIteration).reverse();
6263
6266
  const earlierContext = this.memory.history.filter(
6264
- (entry) => (!currentTurn || entry.turnNumber === currentTurn || entry.turnNumber === void 0) && entry.iterationNumber !== null && entry.iterationNumber < currentIteration
6267
+ (entry) => inTurnScope(entry) && entry.iterationNumber !== null && entry.iterationNumber < currentIteration
6265
6268
  );
6266
6269
  const formatEntry = (entry) => {
6267
6270
  const label = `[${entry.type.toUpperCase()}]`;
@@ -6338,6 +6341,9 @@ function initializeKnowledgeMap(knowledgeMap) {
6338
6341
  nodes: Object.fromEntries(Object.entries(knowledgeMap.nodes).map(([id, node]) => [id, { ...node }]))
6339
6342
  };
6340
6343
  }
6344
+ function hasMemoryContent(memory) {
6345
+ return Object.keys(memory.sessionMemory).length > 0 || memory.history.length > 0;
6346
+ }
6341
6347
  var Agent = class {
6342
6348
  // Base properties from definition
6343
6349
  config;
@@ -6347,6 +6353,7 @@ var Agent = class {
6347
6353
  knowledgeMap;
6348
6354
  definition;
6349
6355
  adapterFactory;
6356
+ initialMemory;
6350
6357
  // Derived properties (computed from definition)
6351
6358
  shouldGenerateOutput;
6352
6359
  // Runtime state (initialized during execution)
@@ -6361,10 +6368,12 @@ var Agent = class {
6361
6368
  *
6362
6369
  * @param definition - Agent definition with config, contract, tools, and optional preloadMemory
6363
6370
  * @param adapterFactory - Factory for creating LLM adapters (decouples engine from provider SDKs)
6371
+ * @param options - Per-execution options (e.g. restored session memory)
6364
6372
  */
6365
- constructor(definition, adapterFactory) {
6373
+ constructor(definition, adapterFactory, options = {}) {
6366
6374
  this.definition = definition;
6367
6375
  this.adapterFactory = adapterFactory;
6376
+ this.initialMemory = options.initialMemory;
6368
6377
  this.config = definition.config;
6369
6378
  this.contract = definition.contract;
6370
6379
  this.modelConfig = definition.modelConfig;
@@ -6458,25 +6467,9 @@ var Agent = class {
6458
6467
  * @returns Initialized MemoryManager instance
6459
6468
  */
6460
6469
  async initializeMemoryManager(validatedInput, context) {
6461
- let memory;
6462
- if (this.definition.preloadMemory) {
6463
- const preloadStartTime = Date.now();
6464
- memory = await this.definition.preloadMemory(context);
6465
- const preloadEndTime = Date.now();
6466
- this.logger.action(
6467
- "memory-preload",
6468
- `Preloaded ${Object.keys(memory.sessionMemory).length} session memory entries`,
6469
- 0,
6470
- preloadStartTime,
6471
- preloadEndTime,
6472
- preloadEndTime - preloadStartTime
6473
- );
6470
+ const memory = await this.resolveInitialMemory(context);
6471
+ if (hasMemoryContent(memory)) {
6474
6472
  await this.reloadKnowledgeMapTools(memory, context);
6475
- } else {
6476
- memory = {
6477
- sessionMemory: {},
6478
- history: []
6479
- };
6480
6473
  }
6481
6474
  const inputStartTime = Date.now();
6482
6475
  memory.history.push({
@@ -6497,6 +6490,44 @@ var Agent = class {
6497
6490
  );
6498
6491
  return new MemoryManager(memory, this.config.constraints, this.logger);
6499
6492
  }
6493
+ /**
6494
+ * Resolve the memory this execution starts from.
6495
+ *
6496
+ * Precedence: caller-supplied `initialMemory` (session restore) > the definition's
6497
+ * `preloadMemory` author hook > empty. Returns a detached copy in the restore case so
6498
+ * the agent's mutations cannot corrupt the caller's snapshot.
6499
+ */
6500
+ async resolveInitialMemory(context) {
6501
+ if (this.initialMemory) {
6502
+ const restoreStartTime = Date.now();
6503
+ const memory = structuredClone(this.initialMemory);
6504
+ const restoreEndTime = Date.now();
6505
+ this.logger.action(
6506
+ "memory-restore",
6507
+ `Restored ${Object.keys(memory.sessionMemory).length} session memory entries`,
6508
+ 0,
6509
+ restoreStartTime,
6510
+ restoreEndTime,
6511
+ restoreEndTime - restoreStartTime
6512
+ );
6513
+ return memory;
6514
+ }
6515
+ if (this.definition.preloadMemory) {
6516
+ const preloadStartTime = Date.now();
6517
+ const memory = await this.definition.preloadMemory(context);
6518
+ const preloadEndTime = Date.now();
6519
+ this.logger.action(
6520
+ "memory-preload",
6521
+ `Preloaded ${Object.keys(memory.sessionMemory).length} session memory entries`,
6522
+ 0,
6523
+ preloadStartTime,
6524
+ preloadEndTime,
6525
+ preloadEndTime - preloadStartTime
6526
+ );
6527
+ return memory;
6528
+ }
6529
+ return { sessionMemory: {}, history: [] };
6530
+ }
6500
6531
  /**
6501
6532
  * Reload tools from knowledge map state (cross-turn persistence)
6502
6533
  *
@@ -6776,6 +6807,7 @@ var Agent = class {
6776
6807
  const structuredOutput = await callLLMForAgentCompletion(adapter, {
6777
6808
  systemPrompt,
6778
6809
  memoryContext: this.memoryManager.toContext(this.iterationNumber, this.executionContext?.sessionTurnNumber),
6810
+ conversationHistory: this.executionContext?.conversationHistory,
6779
6811
  outputSchema,
6780
6812
  constraints: {
6781
6813
  maxOutputTokens: this.modelConfig.maxOutputTokens,
@@ -10254,6 +10286,7 @@ function buildWorkerExecutionContext(params) {
10254
10286
  resourceId: params.resourceId,
10255
10287
  sessionId: params.sessionId,
10256
10288
  sessionTurnNumber: params.sessionTurnNumber,
10289
+ conversationHistory: params.conversationHistory,
10257
10290
  parentExecutionId: params.parentExecutionId,
10258
10291
  executionDepth: params.executionDepth,
10259
10292
  signal: params.signal,
@@ -10360,6 +10393,8 @@ function startWorker(org) {
10360
10393
  organizationName,
10361
10394
  sessionId,
10362
10395
  sessionTurnNumber,
10396
+ sessionMemory,
10397
+ conversationHistory,
10363
10398
  parentExecutionId,
10364
10399
  executionDepth
10365
10400
  } = msg;
@@ -10405,7 +10440,9 @@ function startWorker(org) {
10405
10440
  try {
10406
10441
  console.log(`[SDK-WORKER] Running agent '${resourceId}' (${agentDef.tools.length} tools)`);
10407
10442
  const adapterFactory = createPostMessageAdapterFactory();
10408
- const agentInstance = new Agent(agentDef, adapterFactory);
10443
+ const agentInstance = new Agent(agentDef, adapterFactory, {
10444
+ initialMemory: sessionMemory
10445
+ });
10409
10446
  const context = buildWorkerExecutionContext({
10410
10447
  executionId,
10411
10448
  organizationId: organizationId ?? "",
@@ -10413,6 +10450,7 @@ function startWorker(org) {
10413
10450
  resourceId,
10414
10451
  sessionId,
10415
10452
  sessionTurnNumber,
10453
+ conversationHistory,
10416
10454
  parentExecutionId,
10417
10455
  executionDepth: executionDepth ?? 0,
10418
10456
  signal: localAbortController.signal
@@ -11,7 +11,8 @@
11
11
  * humanCheckpoints?: [...], relationships?: {...} }
12
12
  *
13
13
  * Parent -> Worker: { type: 'execute', resourceId, executionId, input, organizationId?, organizationName?,
14
- * sessionId?, sessionTurnNumber?, parentExecutionId?, executionDepth }
14
+ * sessionId?, sessionTurnNumber?, sessionMemory?, conversationHistory?,
15
+ * parentExecutionId?, executionDepth }
15
16
  * Worker -> Parent: { type: 'result', status, output?, memorySnapshot?, error?, logs, metrics: { durationMs } }
16
17
  *
17
18
  * Parent -> Worker: { type: 'abort' } (graceful abort before terminate)
@@ -2890,6 +2890,8 @@ function buildReasoningRequest(iterationContext) {
2890
2890
  iterationContext.iteration,
2891
2891
  iterationContext.executionContext.sessionTurnNumber
2892
2892
  ),
2893
+ // A session agent gets its own conversation. Non-session executions have none.
2894
+ conversationHistory: iterationContext.executionContext.conversationHistory ?? [],
2893
2895
  includeMessageAction: isSessionCapable,
2894
2896
  includeNavigateKnowledge: hasKnowledgeMap,
2895
2897
  includeMemoryOps
@@ -3306,12 +3308,16 @@ function validateTokenConfiguration(model, maxOutputTokens) {
3306
3308
  );
3307
3309
  }
3308
3310
  }
3311
+ function buildAgentMessages(systemPrompt, memoryContext, conversationHistory = []) {
3312
+ return [
3313
+ { role: "system", content: systemPrompt },
3314
+ ...conversationHistory.map(({ role, content }) => ({ role, content })),
3315
+ { role: "user", content: memoryContext }
3316
+ ];
3317
+ }
3309
3318
  async function callLLMForAgentIteration(adapter, request) {
3310
3319
  validateTokenConfiguration(request.model, request.constraints.maxOutputTokens);
3311
- const messages = [
3312
- { role: "system", content: request.systemPrompt },
3313
- { role: "user", content: request.memoryContext }
3314
- ];
3320
+ const messages = buildAgentMessages(request.systemPrompt, request.memoryContext, request.conversationHistory);
3315
3321
  const response = await adapter.generate({
3316
3322
  messages,
3317
3323
  responseSchema: buildIterationResponseSchema(
@@ -3340,10 +3346,7 @@ async function callLLMForAgentIteration(adapter, request) {
3340
3346
  async function callLLMForAgentCompletion(adapter, request) {
3341
3347
  validateTokenConfiguration(request.model, request.constraints.maxOutputTokens);
3342
3348
  const response = await adapter.generate({
3343
- messages: [
3344
- { role: "system", content: request.systemPrompt },
3345
- { role: "user", content: request.memoryContext }
3346
- ],
3349
+ messages: buildAgentMessages(request.systemPrompt, request.memoryContext, request.conversationHistory),
3347
3350
  responseSchema: request.outputSchema,
3348
3351
  // Use output schema directly
3349
3352
  temperature: request.constraints.temperature || 0.3,
@@ -3477,6 +3480,7 @@ async function processReasoning(iterationContext) {
3477
3480
  const { reasoning, memoryOps, nextActions } = await callLLMForAgentIteration(adapter, {
3478
3481
  systemPrompt: request.systemPrompt,
3479
3482
  memoryContext: request.memoryContext,
3483
+ conversationHistory: request.conversationHistory,
3480
3484
  tools: request.tools,
3481
3485
  constraints: request.constraints,
3482
3486
  model: iterationContext.modelConfig.model,
@@ -4349,11 +4353,10 @@ var MemoryManager = class {
4349
4353
  */
4350
4354
  toContext(currentIteration, currentTurn) {
4351
4355
  const status = this.getStatus();
4352
- const currentContext = this.memory.history.filter(
4353
- (entry) => (!currentTurn || entry.turnNumber === currentTurn || entry.turnNumber === void 0) && entry.iterationNumber === currentIteration
4354
- ).reverse();
4356
+ const inTurnScope = (entry) => !currentTurn || entry.turnNumber === currentTurn || entry.turnNumber == null;
4357
+ const currentContext = this.memory.history.filter((entry) => inTurnScope(entry) && entry.iterationNumber === currentIteration).reverse();
4355
4358
  const earlierContext = this.memory.history.filter(
4356
- (entry) => (!currentTurn || entry.turnNumber === currentTurn || entry.turnNumber === void 0) && entry.iterationNumber !== null && entry.iterationNumber < currentIteration
4359
+ (entry) => inTurnScope(entry) && entry.iterationNumber !== null && entry.iterationNumber < currentIteration
4357
4360
  );
4358
4361
  const formatEntry = (entry) => {
4359
4362
  const label = `[${entry.type.toUpperCase()}]`;
@@ -4430,6 +4433,9 @@ function initializeKnowledgeMap(knowledgeMap) {
4430
4433
  nodes: Object.fromEntries(Object.entries(knowledgeMap.nodes).map(([id, node]) => [id, { ...node }]))
4431
4434
  };
4432
4435
  }
4436
+ function hasMemoryContent(memory) {
4437
+ return Object.keys(memory.sessionMemory).length > 0 || memory.history.length > 0;
4438
+ }
4433
4439
  var Agent = class {
4434
4440
  // Base properties from definition
4435
4441
  config;
@@ -4439,6 +4445,7 @@ var Agent = class {
4439
4445
  knowledgeMap;
4440
4446
  definition;
4441
4447
  adapterFactory;
4448
+ initialMemory;
4442
4449
  // Derived properties (computed from definition)
4443
4450
  shouldGenerateOutput;
4444
4451
  // Runtime state (initialized during execution)
@@ -4453,10 +4460,12 @@ var Agent = class {
4453
4460
  *
4454
4461
  * @param definition - Agent definition with config, contract, tools, and optional preloadMemory
4455
4462
  * @param adapterFactory - Factory for creating LLM adapters (decouples engine from provider SDKs)
4463
+ * @param options - Per-execution options (e.g. restored session memory)
4456
4464
  */
4457
- constructor(definition, adapterFactory) {
4465
+ constructor(definition, adapterFactory, options = {}) {
4458
4466
  this.definition = definition;
4459
4467
  this.adapterFactory = adapterFactory;
4468
+ this.initialMemory = options.initialMemory;
4460
4469
  this.config = definition.config;
4461
4470
  this.contract = definition.contract;
4462
4471
  this.modelConfig = definition.modelConfig;
@@ -4550,25 +4559,9 @@ var Agent = class {
4550
4559
  * @returns Initialized MemoryManager instance
4551
4560
  */
4552
4561
  async initializeMemoryManager(validatedInput, context) {
4553
- let memory;
4554
- if (this.definition.preloadMemory) {
4555
- const preloadStartTime = Date.now();
4556
- memory = await this.definition.preloadMemory(context);
4557
- const preloadEndTime = Date.now();
4558
- this.logger.action(
4559
- "memory-preload",
4560
- `Preloaded ${Object.keys(memory.sessionMemory).length} session memory entries`,
4561
- 0,
4562
- preloadStartTime,
4563
- preloadEndTime,
4564
- preloadEndTime - preloadStartTime
4565
- );
4562
+ const memory = await this.resolveInitialMemory(context);
4563
+ if (hasMemoryContent(memory)) {
4566
4564
  await this.reloadKnowledgeMapTools(memory, context);
4567
- } else {
4568
- memory = {
4569
- sessionMemory: {},
4570
- history: []
4571
- };
4572
4565
  }
4573
4566
  const inputStartTime = Date.now();
4574
4567
  memory.history.push({
@@ -4589,6 +4582,44 @@ var Agent = class {
4589
4582
  );
4590
4583
  return new MemoryManager(memory, this.config.constraints, this.logger);
4591
4584
  }
4585
+ /**
4586
+ * Resolve the memory this execution starts from.
4587
+ *
4588
+ * Precedence: caller-supplied `initialMemory` (session restore) > the definition's
4589
+ * `preloadMemory` author hook > empty. Returns a detached copy in the restore case so
4590
+ * the agent's mutations cannot corrupt the caller's snapshot.
4591
+ */
4592
+ async resolveInitialMemory(context) {
4593
+ if (this.initialMemory) {
4594
+ const restoreStartTime = Date.now();
4595
+ const memory = structuredClone(this.initialMemory);
4596
+ const restoreEndTime = Date.now();
4597
+ this.logger.action(
4598
+ "memory-restore",
4599
+ `Restored ${Object.keys(memory.sessionMemory).length} session memory entries`,
4600
+ 0,
4601
+ restoreStartTime,
4602
+ restoreEndTime,
4603
+ restoreEndTime - restoreStartTime
4604
+ );
4605
+ return memory;
4606
+ }
4607
+ if (this.definition.preloadMemory) {
4608
+ const preloadStartTime = Date.now();
4609
+ const memory = await this.definition.preloadMemory(context);
4610
+ const preloadEndTime = Date.now();
4611
+ this.logger.action(
4612
+ "memory-preload",
4613
+ `Preloaded ${Object.keys(memory.sessionMemory).length} session memory entries`,
4614
+ 0,
4615
+ preloadStartTime,
4616
+ preloadEndTime,
4617
+ preloadEndTime - preloadStartTime
4618
+ );
4619
+ return memory;
4620
+ }
4621
+ return { sessionMemory: {}, history: [] };
4622
+ }
4592
4623
  /**
4593
4624
  * Reload tools from knowledge map state (cross-turn persistence)
4594
4625
  *
@@ -4868,6 +4899,7 @@ var Agent = class {
4868
4899
  const structuredOutput = await callLLMForAgentCompletion(adapter, {
4869
4900
  systemPrompt,
4870
4901
  memoryContext: this.memoryManager.toContext(this.iterationNumber, this.executionContext?.sessionTurnNumber),
4902
+ conversationHistory: this.executionContext?.conversationHistory,
4871
4903
  outputSchema,
4872
4904
  constraints: {
4873
4905
  maxOutputTokens: this.modelConfig.maxOutputTokens,
@@ -6883,6 +6915,7 @@ function buildWorkerExecutionContext(params) {
6883
6915
  resourceId: params.resourceId,
6884
6916
  sessionId: params.sessionId,
6885
6917
  sessionTurnNumber: params.sessionTurnNumber,
6918
+ conversationHistory: params.conversationHistory,
6886
6919
  parentExecutionId: params.parentExecutionId,
6887
6920
  executionDepth: params.executionDepth,
6888
6921
  signal: params.signal,
@@ -6989,6 +7022,8 @@ function startWorker(org) {
6989
7022
  organizationName,
6990
7023
  sessionId,
6991
7024
  sessionTurnNumber,
7025
+ sessionMemory,
7026
+ conversationHistory,
6992
7027
  parentExecutionId,
6993
7028
  executionDepth
6994
7029
  } = msg;
@@ -7034,7 +7069,9 @@ function startWorker(org) {
7034
7069
  try {
7035
7070
  console.log(`[SDK-WORKER] Running agent '${resourceId}' (${agentDef.tools.length} tools)`);
7036
7071
  const adapterFactory = createPostMessageAdapterFactory();
7037
- const agentInstance = new Agent(agentDef, adapterFactory);
7072
+ const agentInstance = new Agent(agentDef, adapterFactory, {
7073
+ initialMemory: sessionMemory
7074
+ });
7038
7075
  const context = buildWorkerExecutionContext({
7039
7076
  executionId,
7040
7077
  organizationId: organizationId ?? "",
@@ -7042,6 +7079,7 @@ function startWorker(org) {
7042
7079
  resourceId,
7043
7080
  sessionId,
7044
7081
  sessionTurnNumber,
7082
+ conversationHistory,
7045
7083
  parentExecutionId,
7046
7084
  executionDepth: executionDepth ?? 0,
7047
7085
  signal: localAbortController.signal
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elevasis/sdk",
3
- "version": "1.38.0",
3
+ "version": "1.39.0",
4
4
  "description": "SDK for building Elevasis organization resources",
5
5
  "type": "module",
6
6
  "bin": {
@@ -58,9 +58,9 @@
58
58
  "tsup": "^8.0.0",
59
59
  "typescript": "5.9.2",
60
60
  "zod": "^4.1.0",
61
- "@repo/eslint-config": "0.0.0",
62
- "@repo/core": "0.54.0",
63
- "@repo/typescript-config": "0.0.0"
61
+ "@repo/core": "0.55.0",
62
+ "@repo/typescript-config": "0.0.0",
63
+ "@repo/eslint-config": "0.0.0"
64
64
  },
65
65
  "scripts": {
66
66
  "lint": "eslint src --max-warnings 0",
@@ -0,0 +1,49 @@
1
+ # Session-capable agents now remember earlier turns
2
+
3
+ ## Why this note exists
4
+
5
+ Any agent with `sessionCapable: true` was silently forgetting everything from earlier in the same
6
+ conversation. Two things were broken:
7
+
8
+ - **Its saved memory was never loaded back in.** The agent wrote memory at the end of a turn; the
9
+ next turn started from an empty memory instead of restoring it.
10
+ - **The earlier messages were never given to the model.** The conversation was stored for display
11
+ but the execution path never read it, so turn N could not see turns 1..N-1.
12
+
13
+ Both are now fixed, and both are **on by default** for session-capable agents. Turn N is handed the
14
+ agent's restored memory plus the earlier user/assistant messages (token-budgeted), so it can actually
15
+ continue the conversation. The fix is entirely platform-side — no agent definition changes are
16
+ required to get it.
17
+
18
+ ## Applies to
19
+
20
+ - **Every agent with `sessionCapable: true`.** The behavior arrives with the `@elevasis/sdk` and
21
+ `@elevasis/core` dependency baselines this train propagates.
22
+ - **Especially agents whose memory strategy was written around the old broken behavior.** For
23
+ example, a voice/interview agent told _"never store the full transcript in memory — the transcript
24
+ is the source of truth"_ only worked if the transcript was actually replayed to the model. It
25
+ wasn't, so those agents deliberately declined to remember the one thing they needed. That guidance
26
+ is now correct: the earlier messages are given to the model.
27
+
28
+ ## Required actions
29
+
30
+ 1. **Take the `@elevasis/core` and `@elevasis/sdk` baseline bumps** this train propagates, then
31
+ reinstall in `operations/` so the new worker bundle is present.
32
+ 2. **Redeploy your operations bundle.** The worker is baked into the deployed bundle at build time,
33
+ so an existing deployment stays amnesiac until it is redeployed. `pnpm -C operations exec
34
+ elevasis-sdk deploy --prod` (or your project's deploy command).
35
+ 3. **Revisit any `memoryPreferences` you wrote around the old behavior.** If you told an agent not to
36
+ store something because "the conversation is the source of truth," confirm that still matches what
37
+ you want now that the conversation is actually available to the model.
38
+
39
+ ## Verification
40
+
41
+ - Hard recall probe: plant an unguessable token in turn 1, then in turn 2 ask for it back verbatim
42
+ **without restating it**. A fixed agent returns it; a broken one says the session is empty.
43
+ - `turnInputTokens` grows turn over turn as the conversation accumulates, instead of staying flat at
44
+ system-prompt-plus-current-message size.
45
+
46
+ ## Not handled by /git-sync
47
+
48
+ - **The redeploy.** `/git-sync` commits and pushes the propagated dependency baseline, but it does
49
+ not redeploy your operations bundle. The fix does not go live until you redeploy (action 2 above).
@@ -0,0 +1,50 @@
1
+ # WorkOS single-org binding moves to the `.elevasis` marker
2
+
3
+ ## Why this note exists
4
+
5
+ **This is a correctness fix for a live data-exposure class of bug. Read it before your next deploy.**
6
+
7
+ Single-org apps used to bind to their WorkOS organization through `VITE_WORKOS_ORG_ID`, a build-time env var. That variable lived only in a gitignored `ui/.env`, so it was absent from any clean build — CI, a fresh clone, a new hosting project. When it was absent the org guard in `__root.tsx` silently **no-opped**, and the app inherited whatever organization the WorkOS session happened to be using. It failed **open**.
8
+
9
+ That is not hypothetical. `app.contemplativerecords.com` served a different tenant's data because its production build had no `VITE_WORKOS_ORG_ID` set.
10
+
11
+ The binding now lives in the project's committed `.elevasis` marker:
12
+
13
+ ```yaml
14
+ projectSlug: your-project
15
+ templateVersion: "1.0"
16
+ appMode: client-centric
17
+ workosOrgId: org_01ABCDEFGHIJKLMNOPQRSTUVWX
18
+ ```
19
+
20
+ The shared `elevasisVite()` plugin (from `@elevasis/ui/vite`, already wired into your `ui/vite.config.ts`) walks up from `ui/`, reads the marker, and injects the value as the build-time constant `__ELEVASIS_WORKOS_ORG_ID__`. The org guard, the `login.tsx` `signIn()` calls, and the dev-centric topbar switcher gate all read that constant.
21
+
22
+ **A WorkOS `org_` id is a public identifier, not a secret** — it appears in URLs. Committing it is correct. Treating it as a secret is what put it in a gitignored file and caused the failure.
23
+
24
+ `VITE_WORKOS_ORG_ID` is removed with **no fallback**. There is no transition period and no back-compat read. If you leave the env var set and do not seed the marker, your org guard stops binding.
25
+
26
+ ## Applies to
27
+
28
+ - **Every template-derived project.** The plugin change arrives with the `@elevasis/ui` dependency baseline this train propagates.
29
+ - **`client-centric` projects — action required.** `/external verify` now fails closed: a `client-centric` project whose `.elevasis` lacks a non-empty `org_`-prefixed `workosOrgId` fails the gate. This is deliberate. A loud failure is the point; the old silent no-op is what shipped the bug.
30
+ - **`dev-centric` projects — optional.** The field may be absent or empty. If present and non-empty it must still be `org_`-prefixed.
31
+
32
+ ## Required actions
33
+
34
+ 1. **Add `workosOrgId` to your `.elevasis`.** Sync will not do this for you — see "Not handled by /git-sync" below. Find your org id in Command Center, or in the `organizations` table as `workos_org_id`.
35
+ 2. **Take the `@elevasis/ui` baseline bump** this train propagates, then reinstall in `ui/` so the plugin that injects the constant is actually present.
36
+ 3. **Remove `VITE_WORKOS_ORG_ID` from every environment you set it in** — `ui/.env`, `ui/.env.local`, and your hosting provider's env settings (for Vercel: Project → Settings → Environment Variables). Leaving it set does nothing, but it will mislead the next person who reads it.
37
+ 4. **Redeploy.** The constant is injected at build time, so an existing deployment keeps its old behavior until it is rebuilt.
38
+
39
+ ## Verification
40
+
41
+ - `pnpm external:verify` passes, and your project's `marker` category reports `.elevasis workosOrgId is set for client-centric project`.
42
+ - `grep -rn "VITE_WORKOS_ORG_ID" ui/ .env* 2>/dev/null` returns nothing.
43
+ - In a built bundle, your org id is present: `grep -o "org_[A-Za-z0-9]*" ui/dist/assets/*.js | head`. If this returns nothing for a `client-centric` project, the marker was not read — check that `.elevasis` sits at your project root, one level above `ui/`.
44
+ - After deploying, log in and confirm the app lands in the correct organization.
45
+
46
+ ## Not handled by /git-sync
47
+
48
+ - **Your `.elevasis` marker is project-owned and `never-touch`.** The sync engine will never write it, which means it will never seed `workosOrgId` for you and never overwrite the value once you set it. Step 1 above is a manual, per-project edit. A `client-centric` project that skips it will fail `/external verify` until it is done.
49
+ - **A diverged `__root.tsx` will not auto-merge.** `__root.tsx` is merge-managed with `critical-manual-merge` severity. If your shell has diverged from the template, sync preserves your copy and the `useOrgGuard` swap from `import.meta.env.VITE_WORKOS_ORG_ID` to `__ELEVASIS_WORKOS_ORG_ID__` is a manual edit. When you make it, do **not** remove the structural contract substrings `ElevasisAuthenticatedShell`, `from '@elevasis/ui/app'`, or `SYSTEM_MANIFESTS` — dropping them makes `sync-apply` mis-escalate your project to `catch-up-required`.
50
+ - **A diverged `ui/vite.config.ts` needs reconciliation, not overwrite.** It is `replace-all` managed, so a blind sync would clobber local divergence. Confirm `...elevasisVite()` survives in your plugins array — without it the constant is never defined and the guard reads `undefined`.