@elevasis/sdk 1.41.1 → 1.42.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
@@ -45834,7 +45834,7 @@ function wrapAction(commandName, fn) {
45834
45834
  // package.json
45835
45835
  var package_default = {
45836
45836
  name: "@elevasis/sdk",
45837
- version: "1.41.1",
45837
+ version: "1.42.0",
45838
45838
  description: "SDK for building Elevasis organization resources",
45839
45839
  type: "module",
45840
45840
  bin: {
@@ -4461,26 +4461,24 @@ function resolveSecurityLevel(config2) {
4461
4461
 
4462
4462
  // ../core/src/execution/engine/agent/reasoning/prompt-sections/base-actions.ts
4463
4463
  function buildBaseActionsPrompt(includeMessageAction, includeNavigateKnowledge) {
4464
- let actionCount = 2;
4465
- const actions = ["1. tool-call (call a tool)"];
4466
- if (includeMessageAction) {
4467
- actionCount++;
4468
- actions.push(`${actionCount}. message (send message to user)`);
4469
- }
4464
+ const actionNames = ["tool-call (call a tool)"];
4470
4465
  if (includeNavigateKnowledge) {
4471
- actionCount++;
4472
- actions.push(`${actionCount}. navigate-knowledge (load knowledge node)`);
4466
+ actionNames.push("navigate-knowledge (load knowledge node)");
4473
4467
  }
4474
- actions.push(`${actionCount + 1}. complete (finish task)`);
4475
- actionCount++;
4476
- const actionsList = actions.join("\n");
4468
+ actionNames.push("complete (finish task)");
4469
+ const actionsList = actionNames.map((name, index2) => `${index2 + 1}. ${name}`).join("\n");
4470
+ const actionCount = actionNames.length;
4477
4471
  return `# CORE AGENT INSTRUCTIONS
4478
4472
 
4479
- You are an AI agent. Your response is captured as structured output. Two fields are required on
4473
+ You are an AI agent. Your response is captured as structured output. ${includeMessageAction ? "Three fields are required" : "Two fields are required"} on
4480
4474
  every response:
4481
4475
 
4482
4476
  - **reasoning** -- your thought process, as plain prose.
4483
- - **nextActions** -- the actions to execute.
4477
+ - **nextActions** -- the actions to execute.${includeMessageAction ? `
4478
+ - **message** -- your reply to the user, as plain prose. This is the ONLY field the user sees.
4479
+ Whatever you decide to say, it goes here. Use an empty string only when this iteration just calls
4480
+ tools and you genuinely have nothing to tell the user yet -- an empty message ends the turn in
4481
+ silence.` : ""}
4484
4482
 
4485
4483
  **reasoning is prose, not JSON.** Do not write field names, braces, or a response object inside it,
4486
4484
  and never continue the response envelope in the reasoning text -- nextActions is a separate field
@@ -4491,14 +4489,16 @@ that you fill separately. A response carrying reasoning alone is discarded and r
4491
4489
  ${actionsList}
4492
4490
 
4493
4491
  **Formats:**
4494
- - tool-call: { "type": "tool-call", "id": "unique-id", "name": "tool_name", "input": {...} }${includeMessageAction ? `
4495
- - message: { "type": "message", "text": "Your message" }` : ""}${includeNavigateKnowledge ? `
4492
+ - tool-call: { "type": "tool-call", "id": "unique-id", "name": "tool_name", "input": {...} }${includeNavigateKnowledge ? `
4496
4493
  - navigate-knowledge: { "type": "navigate-knowledge", "id": "unique-id", "nodeId": "node-name" }` : ""}
4497
4494
  - complete: { "type": "complete" }
4498
-
4495
+ ${includeMessageAction ? `
4496
+ Talking to the user is NOT an action. There is no message action -- put your reply in the
4497
+ **message** field beside nextActions.
4498
+ ` : ""}
4499
4499
  ## Execution Flow
4500
4500
 
4501
- 1. You respond with reasoning + actions
4501
+ 1. You respond with reasoning + actions${includeMessageAction ? " + your message to the user" : ""}
4502
4502
  2. System executes actions (tool calls run **in parallel**)
4503
4503
  3. Tool results automatically appear in your next iteration
4504
4504
  4. You see results and decide: more work needed? Or complete?
@@ -4510,10 +4510,10 @@ ${actionsList}
4510
4510
  - Dependent operations need separate iterations (tool B needs tool A's result)
4511
4511
  - "complete" cannot mix with navigate-knowledge${includeNavigateKnowledge ? "" : " (when available)"}
4512
4512
  - "complete" can mix with tool-call when the tool is a fire-and-forget side effect and you do not need its result before ending${includeMessageAction ? `
4513
- - Always send at least one message before completing
4514
- - Send at most one message per iteration. Multiple messages in a session turn are collapsed into one visible assistant message.
4515
- - When you have your answer, send message + complete in the SAME iteration. Never send a message alone then complete in a later iteration.
4516
- - Never repeat or rephrase the same answer across iterations. One clear answer, then complete.` : ""}
4513
+ - Always fill message before completing -- it is the only field the user sees, and completing with it empty ends the turn in silence
4514
+ - message holds one reply. Write the whole reply in it; do not split a reply across iterations
4515
+ - When you have your answer, put it in message and include complete in the SAME iteration. Never reply on one iteration then complete on a later one
4516
+ - Never repeat or rephrase the same answer across iterations. One clear answer, then complete` : ""}
4517
4517
 
4518
4518
  **Use "complete" when:**
4519
4519
  - Task finished successfully
@@ -4527,27 +4527,27 @@ ${actionsList}
4527
4527
 
4528
4528
  ## Examples
4529
4529
 
4530
- Each example shows the two field values, not a JSON document to copy.
4530
+ Each example shows the field values, not a JSON document to copy.
4531
4531
 
4532
4532
  ### Example 1: Simple Task (No Tools)
4533
- - reasoning: Simple greeting, no tools needed.
4534
- - nextActions: [${includeMessageAction ? '{ "type": "message", "text": "Hi! How can I help?" }, ' : ""}{ "type": "complete" }]
4533
+ - reasoning: Simple greeting, no tools needed.${includeMessageAction ? "\n- message: Hi! How can I help?" : ""}
4534
+ - nextActions: [{ "type": "complete" }]
4535
4535
 
4536
4536
  ### Example 2: Tool Usage (Two Iterations)
4537
4537
 
4538
4538
  **Iteration 1 - Call tool (NO complete - waiting for results):**
4539
- - reasoning: User asked for time. Calling get_time tool.
4540
- - nextActions: [${includeMessageAction ? '{ "type": "message", "text": "Checking the time..." }, ' : ""}{ "type": "tool-call", "id": "t1", "name": "get_time", "input": { "timezone": "UTC" } }]
4539
+ - reasoning: User asked for time. Calling get_time tool.${includeMessageAction ? "\n- message: Checking the time..." : ""}
4540
+ - nextActions: [{ "type": "tool-call", "id": "t1", "name": "get_time", "input": { "timezone": "UTC" } }]
4541
4541
 
4542
4542
  **Iteration 2 - Tool result received, now complete:**
4543
- - reasoning: Got time result: 12:00 PM UTC. Task done.
4544
- - nextActions: [${includeMessageAction ? '{ "type": "message", "text": "The current time is 12:00 PM UTC." }, ' : ""}{ "type": "complete" }]
4543
+ - reasoning: Got time result: 12:00 PM UTC. Task done.${includeMessageAction ? "\n- message: The current time is 12:00 PM UTC." : ""}
4544
+ - nextActions: [{ "type": "complete" }]
4545
4545
 
4546
4546
  ### Example 3: Parallel Tool Calls (Independent Operations)
4547
4547
  When tools don't depend on each other, batch them for faster execution.
4548
4548
 
4549
- - reasoning: User wants time AND weather. Independent operations - calling both in parallel.
4550
- - nextActions: [${includeMessageAction ? '{ "type": "message", "text": "Getting time and weather..." }, ' : ""}{ "type": "tool-call", "id": "t1", "name": "get_time", "input": {} }, { "type": "tool-call", "id": "w1", "name": "get_weather", "input": { "city": "NYC" } }]
4549
+ - reasoning: User wants time AND weather. Independent operations - calling both in parallel.${includeMessageAction ? "\n- message: Getting time and weather..." : ""}
4550
+ - nextActions: [{ "type": "tool-call", "id": "t1", "name": "get_time", "input": {} }, { "type": "tool-call", "id": "w1", "name": "get_weather", "input": { "city": "NYC" } }]
4551
4551
 
4552
4552
  ### Example 4: Dependent Operations (Separate Iterations Required)
4553
4553
 
@@ -4557,8 +4557,8 @@ When tools don't depend on each other, batch them for faster execution.
4557
4557
  Problem: update_user needs userId from search_user result!
4558
4558
 
4559
4559
  **\u2705 CORRECT - Iteration 1 (get the dependency):**
4560
- - reasoning: Need to find user first before updating.
4561
- - nextActions: [${includeMessageAction ? '{ "type": "message", "text": "Looking up user..." }, ' : ""}{ "type": "tool-call", "id": "1", "name": "search_user", "input": { "email": "user@example.com" } }]
4560
+ - reasoning: Need to find user first before updating.${includeMessageAction ? "\n- message: Looking up user..." : ""}
4561
+ - nextActions: [{ "type": "tool-call", "id": "1", "name": "search_user", "input": { "email": "user@example.com" } }]
4562
4562
 
4563
4563
  **\u2705 CORRECT - Iteration 2 (use the result):**
4564
4564
  - reasoning: Found userId: user_123. Now can update.
@@ -4641,7 +4641,7 @@ function buildToolsPrompt(tools) {
4641
4641
  section += '{\n "type": "tool-call",\n "id": "unique-id",\n "name": "tool-name",\n "input": { /* tool input matching schema */ }\n}\n\n';
4642
4642
  section += "**IMPORTANT RULES:**\n";
4643
4643
  section += '1. "complete" CANNOT mix with navigate-knowledge actions in the same response\n';
4644
- section += '2. "complete" CAN mix with message - always pair your final message with complete in the same iteration\n';
4644
+ section += '2. The "message" field CAN be filled on the same response that completes - always pair your final message with complete in the same iteration\n';
4645
4645
  section += '3. "complete" CAN mix with fire-and-forget tool-call actions when you do not need their results\n';
4646
4646
  section += "4. To use tools and inspect their results, return ONLY tool-call actions, then wait for results in the next iteration\n";
4647
4647
  section += "5. After receiving tool results, you can either call more tools OR complete with final answer\n";
@@ -5204,6 +5204,7 @@ var MemoryOperationsSchema = z.object({
5204
5204
  });
5205
5205
  var AgentIterationOutputSchema = z.object({
5206
5206
  reasoning: z.string(),
5207
+ message: z.string().optional(),
5207
5208
  memoryOps: MemoryOperationsSchema.optional(),
5208
5209
  nextActions: z.array(AgentActionSchema)
5209
5210
  });
@@ -5252,6 +5253,17 @@ ${memory.framing}` : memory.framing },
5252
5253
  }
5253
5254
  return messages;
5254
5255
  }
5256
+ function withSynthesizedMessage(nextActions, message) {
5257
+ const text = message?.trim();
5258
+ if (!text) {
5259
+ return nextActions;
5260
+ }
5261
+ const alreadyPresent = nextActions.some((action) => action.type === "message" && action.text.trim() === text);
5262
+ if (alreadyPresent) {
5263
+ return nextActions;
5264
+ }
5265
+ return [{ type: "message", text }, ...nextActions];
5266
+ }
5255
5267
  async function callLLMForAgentIteration(adapter, request) {
5256
5268
  validateTokenConfiguration(request.model, request.constraints.maxOutputTokens);
5257
5269
  const messages = buildAgentMessages(
@@ -5289,7 +5301,7 @@ async function callLLMForAgentIteration(adapter, request) {
5289
5301
  return {
5290
5302
  reasoning: validated.reasoning,
5291
5303
  memoryOps: validated.memoryOps,
5292
- nextActions: validated.nextActions
5304
+ nextActions: withSynthesizedMessage(validated.nextActions, validated.message)
5293
5305
  };
5294
5306
  } catch (error) {
5295
5307
  flowLog("agent.iteration.validationFailed", {
@@ -5297,6 +5309,7 @@ async function callLLMForAgentIteration(adapter, request) {
5297
5309
  missingRequired: ["reasoning", "nextActions"].filter(
5298
5310
  (k2) => !(typeof response.output === "object" && response.output !== null && k2 in response.output)
5299
5311
  ),
5312
+ messagePresent: typeof response.output === "object" && response.output !== null && typeof response.output.message === "string",
5300
5313
  zodIssues: error instanceof ZodError ? error.issues.map((i) => ({ path: i.path.join("."), code: i.code })) : void 0
5301
5314
  });
5302
5315
  throw new AgentOutputValidationError("Agent iteration output validation failed", {
@@ -5374,17 +5387,6 @@ function buildIterationResponseSchema(tools, includeMessageAction, includeNaviga
5374
5387
  required: ["type"],
5375
5388
  additionalProperties: false
5376
5389
  });
5377
- if (includeMessageAction) {
5378
- actionSchemas.push({
5379
- type: "object",
5380
- properties: {
5381
- type: { type: "string", enum: ["message"] },
5382
- text: { type: "string" }
5383
- },
5384
- required: ["type", "text"],
5385
- additionalProperties: false
5386
- });
5387
- }
5388
5390
  if (includeNavigateKnowledge) {
5389
5391
  actionSchemas.push({
5390
5392
  type: "object",
@@ -5403,9 +5405,15 @@ function buildIterationResponseSchema(tools, includeMessageAction, includeNaviga
5403
5405
  items: {
5404
5406
  anyOf: actionSchemas
5405
5407
  }
5406
- },
5407
- reasoning: { type: "string", description: "Your reasoning process" }
5408
+ }
5408
5409
  };
5410
+ if (includeMessageAction) {
5411
+ properties.message = {
5412
+ type: "string",
5413
+ description: "Your reply to the user, as plain prose. This is the ONLY field the user sees. Use an empty string only when this iteration just calls tools and you have nothing to say yet."
5414
+ };
5415
+ }
5416
+ properties.reasoning = { type: "string", description: "Your reasoning process" };
5409
5417
  if (includeMemoryOps) {
5410
5418
  properties.memoryOps = {
5411
5419
  type: "object",
@@ -5436,7 +5444,7 @@ function buildIterationResponseSchema(tools, includeMessageAction, includeNaviga
5436
5444
  return {
5437
5445
  type: "object",
5438
5446
  properties,
5439
- required: ["nextActions", "reasoning"],
5447
+ required: includeMessageAction ? ["nextActions", "message", "reasoning"] : ["nextActions", "reasoning"],
5440
5448
  additionalProperties: false
5441
5449
  };
5442
5450
  }
@@ -2583,26 +2583,24 @@ function resolveSecurityLevel(config) {
2583
2583
 
2584
2584
  // ../core/src/execution/engine/agent/reasoning/prompt-sections/base-actions.ts
2585
2585
  function buildBaseActionsPrompt(includeMessageAction, includeNavigateKnowledge) {
2586
- let actionCount = 2;
2587
- const actions = ["1. tool-call (call a tool)"];
2588
- if (includeMessageAction) {
2589
- actionCount++;
2590
- actions.push(`${actionCount}. message (send message to user)`);
2591
- }
2586
+ const actionNames = ["tool-call (call a tool)"];
2592
2587
  if (includeNavigateKnowledge) {
2593
- actionCount++;
2594
- actions.push(`${actionCount}. navigate-knowledge (load knowledge node)`);
2588
+ actionNames.push("navigate-knowledge (load knowledge node)");
2595
2589
  }
2596
- actions.push(`${actionCount + 1}. complete (finish task)`);
2597
- actionCount++;
2598
- const actionsList = actions.join("\n");
2590
+ actionNames.push("complete (finish task)");
2591
+ const actionsList = actionNames.map((name, index) => `${index + 1}. ${name}`).join("\n");
2592
+ const actionCount = actionNames.length;
2599
2593
  return `# CORE AGENT INSTRUCTIONS
2600
2594
 
2601
- You are an AI agent. Your response is captured as structured output. Two fields are required on
2595
+ You are an AI agent. Your response is captured as structured output. ${includeMessageAction ? "Three fields are required" : "Two fields are required"} on
2602
2596
  every response:
2603
2597
 
2604
2598
  - **reasoning** -- your thought process, as plain prose.
2605
- - **nextActions** -- the actions to execute.
2599
+ - **nextActions** -- the actions to execute.${includeMessageAction ? `
2600
+ - **message** -- your reply to the user, as plain prose. This is the ONLY field the user sees.
2601
+ Whatever you decide to say, it goes here. Use an empty string only when this iteration just calls
2602
+ tools and you genuinely have nothing to tell the user yet -- an empty message ends the turn in
2603
+ silence.` : ""}
2606
2604
 
2607
2605
  **reasoning is prose, not JSON.** Do not write field names, braces, or a response object inside it,
2608
2606
  and never continue the response envelope in the reasoning text -- nextActions is a separate field
@@ -2613,14 +2611,16 @@ that you fill separately. A response carrying reasoning alone is discarded and r
2613
2611
  ${actionsList}
2614
2612
 
2615
2613
  **Formats:**
2616
- - tool-call: { "type": "tool-call", "id": "unique-id", "name": "tool_name", "input": {...} }${includeMessageAction ? `
2617
- - message: { "type": "message", "text": "Your message" }` : ""}${includeNavigateKnowledge ? `
2614
+ - tool-call: { "type": "tool-call", "id": "unique-id", "name": "tool_name", "input": {...} }${includeNavigateKnowledge ? `
2618
2615
  - navigate-knowledge: { "type": "navigate-knowledge", "id": "unique-id", "nodeId": "node-name" }` : ""}
2619
2616
  - complete: { "type": "complete" }
2620
-
2617
+ ${includeMessageAction ? `
2618
+ Talking to the user is NOT an action. There is no message action -- put your reply in the
2619
+ **message** field beside nextActions.
2620
+ ` : ""}
2621
2621
  ## Execution Flow
2622
2622
 
2623
- 1. You respond with reasoning + actions
2623
+ 1. You respond with reasoning + actions${includeMessageAction ? " + your message to the user" : ""}
2624
2624
  2. System executes actions (tool calls run **in parallel**)
2625
2625
  3. Tool results automatically appear in your next iteration
2626
2626
  4. You see results and decide: more work needed? Or complete?
@@ -2632,10 +2632,10 @@ ${actionsList}
2632
2632
  - Dependent operations need separate iterations (tool B needs tool A's result)
2633
2633
  - "complete" cannot mix with navigate-knowledge${includeNavigateKnowledge ? "" : " (when available)"}
2634
2634
  - "complete" can mix with tool-call when the tool is a fire-and-forget side effect and you do not need its result before ending${includeMessageAction ? `
2635
- - Always send at least one message before completing
2636
- - Send at most one message per iteration. Multiple messages in a session turn are collapsed into one visible assistant message.
2637
- - When you have your answer, send message + complete in the SAME iteration. Never send a message alone then complete in a later iteration.
2638
- - Never repeat or rephrase the same answer across iterations. One clear answer, then complete.` : ""}
2635
+ - Always fill message before completing -- it is the only field the user sees, and completing with it empty ends the turn in silence
2636
+ - message holds one reply. Write the whole reply in it; do not split a reply across iterations
2637
+ - When you have your answer, put it in message and include complete in the SAME iteration. Never reply on one iteration then complete on a later one
2638
+ - Never repeat or rephrase the same answer across iterations. One clear answer, then complete` : ""}
2639
2639
 
2640
2640
  **Use "complete" when:**
2641
2641
  - Task finished successfully
@@ -2649,27 +2649,27 @@ ${actionsList}
2649
2649
 
2650
2650
  ## Examples
2651
2651
 
2652
- Each example shows the two field values, not a JSON document to copy.
2652
+ Each example shows the field values, not a JSON document to copy.
2653
2653
 
2654
2654
  ### Example 1: Simple Task (No Tools)
2655
- - reasoning: Simple greeting, no tools needed.
2656
- - nextActions: [${includeMessageAction ? '{ "type": "message", "text": "Hi! How can I help?" }, ' : ""}{ "type": "complete" }]
2655
+ - reasoning: Simple greeting, no tools needed.${includeMessageAction ? "\n- message: Hi! How can I help?" : ""}
2656
+ - nextActions: [{ "type": "complete" }]
2657
2657
 
2658
2658
  ### Example 2: Tool Usage (Two Iterations)
2659
2659
 
2660
2660
  **Iteration 1 - Call tool (NO complete - waiting for results):**
2661
- - reasoning: User asked for time. Calling get_time tool.
2662
- - nextActions: [${includeMessageAction ? '{ "type": "message", "text": "Checking the time..." }, ' : ""}{ "type": "tool-call", "id": "t1", "name": "get_time", "input": { "timezone": "UTC" } }]
2661
+ - reasoning: User asked for time. Calling get_time tool.${includeMessageAction ? "\n- message: Checking the time..." : ""}
2662
+ - nextActions: [{ "type": "tool-call", "id": "t1", "name": "get_time", "input": { "timezone": "UTC" } }]
2663
2663
 
2664
2664
  **Iteration 2 - Tool result received, now complete:**
2665
- - reasoning: Got time result: 12:00 PM UTC. Task done.
2666
- - nextActions: [${includeMessageAction ? '{ "type": "message", "text": "The current time is 12:00 PM UTC." }, ' : ""}{ "type": "complete" }]
2665
+ - reasoning: Got time result: 12:00 PM UTC. Task done.${includeMessageAction ? "\n- message: The current time is 12:00 PM UTC." : ""}
2666
+ - nextActions: [{ "type": "complete" }]
2667
2667
 
2668
2668
  ### Example 3: Parallel Tool Calls (Independent Operations)
2669
2669
  When tools don't depend on each other, batch them for faster execution.
2670
2670
 
2671
- - reasoning: User wants time AND weather. Independent operations - calling both in parallel.
2672
- - nextActions: [${includeMessageAction ? '{ "type": "message", "text": "Getting time and weather..." }, ' : ""}{ "type": "tool-call", "id": "t1", "name": "get_time", "input": {} }, { "type": "tool-call", "id": "w1", "name": "get_weather", "input": { "city": "NYC" } }]
2671
+ - reasoning: User wants time AND weather. Independent operations - calling both in parallel.${includeMessageAction ? "\n- message: Getting time and weather..." : ""}
2672
+ - nextActions: [{ "type": "tool-call", "id": "t1", "name": "get_time", "input": {} }, { "type": "tool-call", "id": "w1", "name": "get_weather", "input": { "city": "NYC" } }]
2673
2673
 
2674
2674
  ### Example 4: Dependent Operations (Separate Iterations Required)
2675
2675
 
@@ -2679,8 +2679,8 @@ When tools don't depend on each other, batch them for faster execution.
2679
2679
  Problem: update_user needs userId from search_user result!
2680
2680
 
2681
2681
  **\u2705 CORRECT - Iteration 1 (get the dependency):**
2682
- - reasoning: Need to find user first before updating.
2683
- - nextActions: [${includeMessageAction ? '{ "type": "message", "text": "Looking up user..." }, ' : ""}{ "type": "tool-call", "id": "1", "name": "search_user", "input": { "email": "user@example.com" } }]
2682
+ - reasoning: Need to find user first before updating.${includeMessageAction ? "\n- message: Looking up user..." : ""}
2683
+ - nextActions: [{ "type": "tool-call", "id": "1", "name": "search_user", "input": { "email": "user@example.com" } }]
2684
2684
 
2685
2685
  **\u2705 CORRECT - Iteration 2 (use the result):**
2686
2686
  - reasoning: Found userId: user_123. Now can update.
@@ -2763,7 +2763,7 @@ function buildToolsPrompt(tools) {
2763
2763
  section += '{\n "type": "tool-call",\n "id": "unique-id",\n "name": "tool-name",\n "input": { /* tool input matching schema */ }\n}\n\n';
2764
2764
  section += "**IMPORTANT RULES:**\n";
2765
2765
  section += '1. "complete" CANNOT mix with navigate-knowledge actions in the same response\n';
2766
- section += '2. "complete" CAN mix with message - always pair your final message with complete in the same iteration\n';
2766
+ section += '2. The "message" field CAN be filled on the same response that completes - always pair your final message with complete in the same iteration\n';
2767
2767
  section += '3. "complete" CAN mix with fire-and-forget tool-call actions when you do not need their results\n';
2768
2768
  section += "4. To use tools and inspect their results, return ONLY tool-call actions, then wait for results in the next iteration\n";
2769
2769
  section += "5. After receiving tool results, you can either call more tools OR complete with final answer\n";
@@ -3296,6 +3296,7 @@ var MemoryOperationsSchema = z.object({
3296
3296
  });
3297
3297
  var AgentIterationOutputSchema = z.object({
3298
3298
  reasoning: z.string(),
3299
+ message: z.string().optional(),
3299
3300
  memoryOps: MemoryOperationsSchema.optional(),
3300
3301
  nextActions: z.array(AgentActionSchema)
3301
3302
  });
@@ -3344,6 +3345,17 @@ ${memory.framing}` : memory.framing },
3344
3345
  }
3345
3346
  return messages;
3346
3347
  }
3348
+ function withSynthesizedMessage(nextActions, message) {
3349
+ const text = message?.trim();
3350
+ if (!text) {
3351
+ return nextActions;
3352
+ }
3353
+ const alreadyPresent = nextActions.some((action) => action.type === "message" && action.text.trim() === text);
3354
+ if (alreadyPresent) {
3355
+ return nextActions;
3356
+ }
3357
+ return [{ type: "message", text }, ...nextActions];
3358
+ }
3347
3359
  async function callLLMForAgentIteration(adapter, request) {
3348
3360
  validateTokenConfiguration(request.model, request.constraints.maxOutputTokens);
3349
3361
  const messages = buildAgentMessages(
@@ -3381,7 +3393,7 @@ async function callLLMForAgentIteration(adapter, request) {
3381
3393
  return {
3382
3394
  reasoning: validated.reasoning,
3383
3395
  memoryOps: validated.memoryOps,
3384
- nextActions: validated.nextActions
3396
+ nextActions: withSynthesizedMessage(validated.nextActions, validated.message)
3385
3397
  };
3386
3398
  } catch (error) {
3387
3399
  flowLog("agent.iteration.validationFailed", {
@@ -3389,6 +3401,7 @@ async function callLLMForAgentIteration(adapter, request) {
3389
3401
  missingRequired: ["reasoning", "nextActions"].filter(
3390
3402
  (k) => !(typeof response.output === "object" && response.output !== null && k in response.output)
3391
3403
  ),
3404
+ messagePresent: typeof response.output === "object" && response.output !== null && typeof response.output.message === "string",
3392
3405
  zodIssues: error instanceof ZodError ? error.issues.map((i) => ({ path: i.path.join("."), code: i.code })) : void 0
3393
3406
  });
3394
3407
  throw new AgentOutputValidationError("Agent iteration output validation failed", {
@@ -3466,17 +3479,6 @@ function buildIterationResponseSchema(tools, includeMessageAction, includeNaviga
3466
3479
  required: ["type"],
3467
3480
  additionalProperties: false
3468
3481
  });
3469
- if (includeMessageAction) {
3470
- actionSchemas.push({
3471
- type: "object",
3472
- properties: {
3473
- type: { type: "string", enum: ["message"] },
3474
- text: { type: "string" }
3475
- },
3476
- required: ["type", "text"],
3477
- additionalProperties: false
3478
- });
3479
- }
3480
3482
  if (includeNavigateKnowledge) {
3481
3483
  actionSchemas.push({
3482
3484
  type: "object",
@@ -3495,9 +3497,15 @@ function buildIterationResponseSchema(tools, includeMessageAction, includeNaviga
3495
3497
  items: {
3496
3498
  anyOf: actionSchemas
3497
3499
  }
3498
- },
3499
- reasoning: { type: "string", description: "Your reasoning process" }
3500
+ }
3500
3501
  };
3502
+ if (includeMessageAction) {
3503
+ properties.message = {
3504
+ type: "string",
3505
+ description: "Your reply to the user, as plain prose. This is the ONLY field the user sees. Use an empty string only when this iteration just calls tools and you have nothing to say yet."
3506
+ };
3507
+ }
3508
+ properties.reasoning = { type: "string", description: "Your reasoning process" };
3501
3509
  if (includeMemoryOps) {
3502
3510
  properties.memoryOps = {
3503
3511
  type: "object",
@@ -3528,7 +3536,7 @@ function buildIterationResponseSchema(tools, includeMessageAction, includeNaviga
3528
3536
  return {
3529
3537
  type: "object",
3530
3538
  properties,
3531
- required: ["nextActions", "reasoning"],
3539
+ required: includeMessageAction ? ["nextActions", "message", "reasoning"] : ["nextActions", "reasoning"],
3532
3540
  additionalProperties: false
3533
3541
  };
3534
3542
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elevasis/sdk",
3
- "version": "1.41.1",
3
+ "version": "1.42.0",
4
4
  "description": "SDK for building Elevasis organization resources",
5
5
  "type": "module",
6
6
  "bin": {
@@ -59,8 +59,8 @@
59
59
  "typescript": "5.9.2",
60
60
  "zod": "^4.1.0",
61
61
  "@repo/core": "0.57.0",
62
- "@repo/typescript-config": "0.0.0",
63
- "@repo/eslint-config": "0.0.0"
62
+ "@repo/eslint-config": "0.0.0",
63
+ "@repo/typescript-config": "0.0.0"
64
64
  },
65
65
  "scripts": {
66
66
  "lint": "eslint src --max-warnings 0",
@@ -0,0 +1,84 @@
1
+ # The agent's reply to the user is now its own field
2
+
3
+ ## Why this note exists
4
+
5
+ **Your agents' user-facing text was being silently corrupted, and no layer could catch it.**
6
+
7
+ Measured across three 14-turn production sessions on 2026-07-28: 42 of 42 turns passed, and **20
8
+ corruption sites landed across 12 of the 42 replies — 28.6%**. The model emits a JSON escape
9
+ sequence for an em-dash and then completes it with the next letter of the sentence, so
10
+ `Hold on — red-line for me` becomes `Hold on \red-line for me`. That parses as valid JSON, satisfies
11
+ the response schema, and passes validation. There is no error state, no retry, and no provider-side
12
+ fix to wait for. The characters are simply gone from the stored transcript.
13
+
14
+ The cause is where the text sat in the schema, not the text itself. The reply used to be one variant
15
+ of an `anyOf` union nested inside the `nextActions` array. In the very same responses, the flat
16
+ top-level `reasoning` string carried **33 em-dashes with zero corruption** while the nested reply
17
+ text carried **59 em-dashes with 20 corruption sites** — and `reasoning` is the longer field.
18
+
19
+ The reply is now a flat top-level `message` property, declared beside `reasoning` rather than nested
20
+ inside the actions array. Re-measured over a fresh multi-turn session: **42 em-dashes, zero
21
+ corruption.**
22
+
23
+ **This supersedes the two-field description in `2026-07-27-agent-strict-output-and-turn-drift.md`.**
24
+ That note described the iteration response as `nextActions` then `reasoning`. It is now three
25
+ fields, in this order: `nextActions`, `message`, `reasoning`. The ordering rationale from that note
26
+ is unchanged and still holds — `reasoning` stays last so the actions and the reply are already on the
27
+ wire before any mid-response drift can start.
28
+
29
+ **`message` is required for session-capable agents.** This is not a detail you can ignore. When the
30
+ field shipped as optional, the model filled it **zero times in three turns** and those turns produced
31
+ no user-facing message at all. Under grammar-constrained sampling the model fills keys in declaration
32
+ order, so `nextActions` is chosen before the model has reasoned about the reply, `complete` is the
33
+ cheapest legal action, and a skippable `message` then gets skipped. Making it required costs nothing:
34
+ an empty or whitespace-only value is suppressed and produces no message bubble.
35
+
36
+ ## Applies to
37
+
38
+ - **Every `sessionCapable: true` agent.** `message` is the only field the user sees, so this is the
39
+ field that was being damaged.
40
+ - **Agents on Anthropic models**, where the enforced-schema path is active. On the unenforced path
41
+ the platform still accepts a message delivered the old way, as an entry in the actions list, so
42
+ nothing breaks mid-transition.
43
+ - **Anything that reads stored assistant messages** — a transcript, an export, a summarizer, a
44
+ downstream workflow. Already-stored text is not repaired by this change; see below.
45
+ - **No agent definition changes are required.** You do not edit your agents. The shape lives in the
46
+ runtime your bundle carries.
47
+
48
+ ## Required actions
49
+
50
+ 1. **Take the `@elevasis/sdk` baseline bump** this train propagates, then reinstall in `operations/`
51
+ so the new worker bundle is present.
52
+ 2. **Redeploy your operations bundle.** This is the step that actually closes the defect. The
53
+ response schema is emitted by the runtime inlined into your deployed bundle, so an existing
54
+ deployment keeps emitting the old nested shape — and keeps corrupting replies — until it is
55
+ redeployed:
56
+
57
+ ```bash
58
+ pnpm -C operations exec elevasis-sdk deploy --prod
59
+ ```
60
+
61
+ A platform-side deploy does not fix this for you, and neither does the reinstall on its own.
62
+
63
+ 3. **If you read or display stored assistant text, do not treat old records as clean.** Nothing is
64
+ rewritten retroactively. Transcripts written before your redeploy keep whatever corruption they
65
+ already have.
66
+
67
+ ## Verification
68
+
69
+ - Run a session and prompt for a reply likely to contain an em-dash — asking the agent to summarize
70
+ something in a couple of sentences is usually enough. Confirm the reply arrives, and read it: the
71
+ signature failure is a missing letter immediately after where punctuation belonged
72
+ (`Hold on \red-line`), not a visible error.
73
+ - **Confirm every turn produced an assistant message at all.** A turn that completes with no message
74
+ is the symptom of a bundle that has the flat field but not the required flag — that combination
75
+ only exists in an unreleased build, but it is the one failure worth ruling out explicitly.
76
+ - Check the observability rows for an execution: iteration calls should still record the enforced
77
+ path with no fallback reasons. This change does not push the schema off it.
78
+
79
+ ## Not handled by /git-sync
80
+
81
+ - **The redeploy.** `/git-sync` commits and pushes the propagated dependency baseline. The corruption
82
+ continues on your deployed agents until you run action 2 above.
83
+ - **Repairing existing transcripts.** No backfill is performed, and none is possible — the dropped
84
+ characters were never received.