@elevasis/sdk 1.44.2 → 1.44.3

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
@@ -43807,7 +43807,7 @@ function buildIterationResponseSchemaUncached(tools, capabilities) {
43807
43807
  if (capabilities.message !== "off") {
43808
43808
  properties.message = {
43809
43809
  type: "string",
43810
- 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."
43810
+ 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. Write real line breaks and real quotation marks -- the two characters \\n are not a line break, and \\" is not a quotation mark. Serialization is handled for you.'
43811
43811
  };
43812
43812
  }
43813
43813
  properties.reasoning = { type: "string", description: "Your reasoning process" };
@@ -43827,7 +43827,15 @@ function buildIterationResponseSchemaUncached(tools, capabilities) {
43827
43827
  type: "object",
43828
43828
  properties: {
43829
43829
  key: { type: "string" },
43830
- value: { type: "string" }
43830
+ value: {
43831
+ type: "string",
43832
+ // `memoryOps` has NO framework prompt section -- a grep across `prompt-sections/`
43833
+ // returns zero memory references -- so this description is the only framework-owned
43834
+ // lever on how a memory value is written. It matters more here than in `message`:
43835
+ // a session-memory value is re-sent on every iteration of every subsequent turn and
43836
+ // never ages out, so one literal escape stored here is one the model re-reads forever.
43837
+ description: "Stored verbatim and replayed to you on every later turn. Write real line breaks and real quotation marks -- the two characters \\n are not a line break."
43838
+ }
43831
43839
  },
43832
43840
  required: ["key", "value"],
43833
43841
  additionalProperties: false
@@ -46265,7 +46273,7 @@ function wrapAction(commandName, fn) {
46265
46273
  // package.json
46266
46274
  var package_default = {
46267
46275
  name: "@elevasis/sdk",
46268
- version: "1.44.2",
46276
+ version: "1.44.3",
46269
46277
  description: "SDK for building Elevasis organization resources",
46270
46278
  type: "module",
46271
46279
  bin: {
package/dist/index.js CHANGED
@@ -4021,7 +4021,7 @@ function buildIterationResponseSchemaUncached(tools, capabilities) {
4021
4021
  if (capabilities.message !== "off") {
4022
4022
  properties.message = {
4023
4023
  type: "string",
4024
- 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."
4024
+ 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. Write real line breaks and real quotation marks -- the two characters \\n are not a line break, and \\" is not a quotation mark. Serialization is handled for you.'
4025
4025
  };
4026
4026
  }
4027
4027
  properties.reasoning = { type: "string", description: "Your reasoning process" };
@@ -4041,7 +4041,15 @@ function buildIterationResponseSchemaUncached(tools, capabilities) {
4041
4041
  type: "object",
4042
4042
  properties: {
4043
4043
  key: { type: "string" },
4044
- value: { type: "string" }
4044
+ value: {
4045
+ type: "string",
4046
+ // `memoryOps` has NO framework prompt section -- a grep across `prompt-sections/`
4047
+ // returns zero memory references -- so this description is the only framework-owned
4048
+ // lever on how a memory value is written. It matters more here than in `message`:
4049
+ // a session-memory value is re-sent on every iteration of every subsequent turn and
4050
+ // never ages out, so one literal escape stored here is one the model re-reads forever.
4051
+ description: "Stored verbatim and replayed to you on every later turn. Write real line breaks and real quotation marks -- the two characters \\n are not a line break."
4052
+ }
4045
4053
  },
4046
4054
  required: ["key", "value"],
4047
4055
  additionalProperties: false
@@ -4479,6 +4479,11 @@ every response:
4479
4479
  and never continue the response envelope in the reasoning text -- nextActions is a separate field
4480
4480
  that you fill separately. A response carrying reasoning alone is discarded and retried.
4481
4481
 
4482
+ **Prose fields take real characters, never escape sequences.** A line break in ${includeMessage ? "message or reasoning" : "reasoning"} is
4483
+ a real line break -- not the two characters \\n. A quotation mark is the character ", not \\". The
4484
+ response is serialized for you; typing the escape yourself puts those literal characters into the
4485
+ stored text and in front of the reader.
4486
+
4482
4487
  ## Rules
4483
4488
 
4484
4489
  - Batch independent tool calls in one iteration (faster execution)
@@ -5074,7 +5079,7 @@ function buildIterationResponseSchemaUncached(tools, capabilities) {
5074
5079
  if (capabilities.message !== "off") {
5075
5080
  properties.message = {
5076
5081
  type: "string",
5077
- 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."
5082
+ 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. Write real line breaks and real quotation marks -- the two characters \\n are not a line break, and \\" is not a quotation mark. Serialization is handled for you.'
5078
5083
  };
5079
5084
  }
5080
5085
  properties.reasoning = { type: "string", description: "Your reasoning process" };
@@ -5094,7 +5099,15 @@ function buildIterationResponseSchemaUncached(tools, capabilities) {
5094
5099
  type: "object",
5095
5100
  properties: {
5096
5101
  key: { type: "string" },
5097
- value: { type: "string" }
5102
+ value: {
5103
+ type: "string",
5104
+ // `memoryOps` has NO framework prompt section -- a grep across `prompt-sections/`
5105
+ // returns zero memory references -- so this description is the only framework-owned
5106
+ // lever on how a memory value is written. It matters more here than in `message`:
5107
+ // a session-memory value is re-sent on every iteration of every subsequent turn and
5108
+ // never ages out, so one literal escape stored here is one the model re-reads forever.
5109
+ description: "Stored verbatim and replayed to you on every later turn. Write real line breaks and real quotation marks -- the two characters \\n are not a line break."
5110
+ }
5098
5111
  },
5099
5112
  required: ["key", "value"],
5100
5113
  additionalProperties: false
@@ -5113,6 +5126,59 @@ function buildIterationResponseSchemaUncached(tools, capabilities) {
5113
5126
  };
5114
5127
  }
5115
5128
 
5129
+ // ../core/src/execution/engine/agent/reasoning/adapters/prose-escapes.ts
5130
+ var BACKSLASH = String.fromCharCode(92);
5131
+ function normalizeProseEscapes(text) {
5132
+ if (!text.includes(BACKSLASH)) return text;
5133
+ let out = "";
5134
+ let i = 0;
5135
+ while (i < text.length) {
5136
+ const char = text[i];
5137
+ if (char !== BACKSLASH || i + 1 >= text.length) {
5138
+ out += char;
5139
+ i += 1;
5140
+ continue;
5141
+ }
5142
+ switch (text[i + 1]) {
5143
+ case BACKSLASH:
5144
+ out += BACKSLASH + BACKSLASH;
5145
+ i += 2;
5146
+ break;
5147
+ case "n":
5148
+ out += "\n";
5149
+ i += 2;
5150
+ break;
5151
+ case "r":
5152
+ if (text[i + 2] === BACKSLASH && text[i + 3] === "n") {
5153
+ out += "\n";
5154
+ i += 4;
5155
+ } else {
5156
+ out += "\n";
5157
+ i += 2;
5158
+ }
5159
+ break;
5160
+ case "t":
5161
+ out += " ";
5162
+ i += 2;
5163
+ break;
5164
+ case '"':
5165
+ out += '"';
5166
+ i += 2;
5167
+ break;
5168
+ default:
5169
+ out += char;
5170
+ i += 1;
5171
+ }
5172
+ }
5173
+ return out;
5174
+ }
5175
+ function normalizeMemoryValue(value) {
5176
+ if (typeof value !== "string") return value;
5177
+ const trimmed = value.trim();
5178
+ if (trimmed.startsWith("{") || trimmed.startsWith("[")) return value;
5179
+ return normalizeProseEscapes(value);
5180
+ }
5181
+
5116
5182
  // ../core/src/execution/engine/agent/reasoning/adapters/agent-adapter-helpers.ts
5117
5183
  var MemoryKeyValuePairSchema = z.object({ key: z.string(), value: z.any() });
5118
5184
  var MemorySetSchema = z.union([z.record(z.string(), z.any()), z.array(MemoryKeyValuePairSchema)]).transform((value) => {
@@ -5124,13 +5190,33 @@ var MemoryOperationsSchema = z.object({
5124
5190
  // Accept any value type - framework will stringify
5125
5191
  delete: z.array(z.string()).optional()
5126
5192
  });
5127
- var AgentIterationOutputSchema = z.object({
5193
+ var AgentIterationOutputObjectSchema = z.object({
5128
5194
  reasoning: z.string(),
5129
5195
  message: z.string().optional(),
5130
5196
  memoryOps: MemoryOperationsSchema.optional(),
5131
5197
  nextActions: z.array(AgentActionSchema)
5132
5198
  });
5133
- var REQUIRED_ITERATION_KEYS = Object.entries(AgentIterationOutputSchema.shape).filter(([, fieldSchema]) => !fieldSchema.isOptional()).map(([key]) => key);
5199
+ function normalizeIterationProse(output) {
5200
+ const normalized = { ...output };
5201
+ normalized.reasoning = normalizeProseEscapes(output.reasoning);
5202
+ if (typeof output.message === "string") {
5203
+ normalized.message = normalizeProseEscapes(output.message);
5204
+ }
5205
+ if (output.memoryOps?.set) {
5206
+ normalized.memoryOps = {
5207
+ ...output.memoryOps,
5208
+ set: Object.fromEntries(
5209
+ Object.entries(output.memoryOps.set).map(([key, value]) => [key, normalizeMemoryValue(value)])
5210
+ )
5211
+ };
5212
+ }
5213
+ normalized.nextActions = output.nextActions.map(
5214
+ (action) => action.type === "message" ? { ...action, text: normalizeProseEscapes(action.text) } : action
5215
+ );
5216
+ return normalized;
5217
+ }
5218
+ var AgentIterationOutputSchema = AgentIterationOutputObjectSchema.transform(normalizeIterationProse);
5219
+ var REQUIRED_ITERATION_KEYS = Object.entries(AgentIterationOutputObjectSchema.shape).filter(([, fieldSchema]) => !fieldSchema.isOptional()).map(([key]) => key);
5134
5220
  function withSynthesizedMessage(nextActions, message) {
5135
5221
  const text = message?.trim();
5136
5222
  if (!text) {
@@ -2601,6 +2601,11 @@ every response:
2601
2601
  and never continue the response envelope in the reasoning text -- nextActions is a separate field
2602
2602
  that you fill separately. A response carrying reasoning alone is discarded and retried.
2603
2603
 
2604
+ **Prose fields take real characters, never escape sequences.** A line break in ${includeMessage ? "message or reasoning" : "reasoning"} is
2605
+ a real line break -- not the two characters \\n. A quotation mark is the character ", not \\". The
2606
+ response is serialized for you; typing the escape yourself puts those literal characters into the
2607
+ stored text and in front of the reader.
2608
+
2604
2609
  ## Rules
2605
2610
 
2606
2611
  - Batch independent tool calls in one iteration (faster execution)
@@ -3166,7 +3171,7 @@ function buildIterationResponseSchemaUncached(tools, capabilities) {
3166
3171
  if (capabilities.message !== "off") {
3167
3172
  properties.message = {
3168
3173
  type: "string",
3169
- 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."
3174
+ 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. Write real line breaks and real quotation marks -- the two characters \\n are not a line break, and \\" is not a quotation mark. Serialization is handled for you.'
3170
3175
  };
3171
3176
  }
3172
3177
  properties.reasoning = { type: "string", description: "Your reasoning process" };
@@ -3186,7 +3191,15 @@ function buildIterationResponseSchemaUncached(tools, capabilities) {
3186
3191
  type: "object",
3187
3192
  properties: {
3188
3193
  key: { type: "string" },
3189
- value: { type: "string" }
3194
+ value: {
3195
+ type: "string",
3196
+ // `memoryOps` has NO framework prompt section -- a grep across `prompt-sections/`
3197
+ // returns zero memory references -- so this description is the only framework-owned
3198
+ // lever on how a memory value is written. It matters more here than in `message`:
3199
+ // a session-memory value is re-sent on every iteration of every subsequent turn and
3200
+ // never ages out, so one literal escape stored here is one the model re-reads forever.
3201
+ description: "Stored verbatim and replayed to you on every later turn. Write real line breaks and real quotation marks -- the two characters \\n are not a line break."
3202
+ }
3190
3203
  },
3191
3204
  required: ["key", "value"],
3192
3205
  additionalProperties: false
@@ -3205,6 +3218,59 @@ function buildIterationResponseSchemaUncached(tools, capabilities) {
3205
3218
  };
3206
3219
  }
3207
3220
 
3221
+ // ../core/src/execution/engine/agent/reasoning/adapters/prose-escapes.ts
3222
+ var BACKSLASH = String.fromCharCode(92);
3223
+ function normalizeProseEscapes(text) {
3224
+ if (!text.includes(BACKSLASH)) return text;
3225
+ let out = "";
3226
+ let i = 0;
3227
+ while (i < text.length) {
3228
+ const char = text[i];
3229
+ if (char !== BACKSLASH || i + 1 >= text.length) {
3230
+ out += char;
3231
+ i += 1;
3232
+ continue;
3233
+ }
3234
+ switch (text[i + 1]) {
3235
+ case BACKSLASH:
3236
+ out += BACKSLASH + BACKSLASH;
3237
+ i += 2;
3238
+ break;
3239
+ case "n":
3240
+ out += "\n";
3241
+ i += 2;
3242
+ break;
3243
+ case "r":
3244
+ if (text[i + 2] === BACKSLASH && text[i + 3] === "n") {
3245
+ out += "\n";
3246
+ i += 4;
3247
+ } else {
3248
+ out += "\n";
3249
+ i += 2;
3250
+ }
3251
+ break;
3252
+ case "t":
3253
+ out += " ";
3254
+ i += 2;
3255
+ break;
3256
+ case '"':
3257
+ out += '"';
3258
+ i += 2;
3259
+ break;
3260
+ default:
3261
+ out += char;
3262
+ i += 1;
3263
+ }
3264
+ }
3265
+ return out;
3266
+ }
3267
+ function normalizeMemoryValue(value) {
3268
+ if (typeof value !== "string") return value;
3269
+ const trimmed = value.trim();
3270
+ if (trimmed.startsWith("{") || trimmed.startsWith("[")) return value;
3271
+ return normalizeProseEscapes(value);
3272
+ }
3273
+
3208
3274
  // ../core/src/execution/engine/agent/reasoning/adapters/agent-adapter-helpers.ts
3209
3275
  var MemoryKeyValuePairSchema = z.object({ key: z.string(), value: z.any() });
3210
3276
  var MemorySetSchema = z.union([z.record(z.string(), z.any()), z.array(MemoryKeyValuePairSchema)]).transform((value) => {
@@ -3216,13 +3282,33 @@ var MemoryOperationsSchema = z.object({
3216
3282
  // Accept any value type - framework will stringify
3217
3283
  delete: z.array(z.string()).optional()
3218
3284
  });
3219
- var AgentIterationOutputSchema = z.object({
3285
+ var AgentIterationOutputObjectSchema = z.object({
3220
3286
  reasoning: z.string(),
3221
3287
  message: z.string().optional(),
3222
3288
  memoryOps: MemoryOperationsSchema.optional(),
3223
3289
  nextActions: z.array(AgentActionSchema)
3224
3290
  });
3225
- var REQUIRED_ITERATION_KEYS = Object.entries(AgentIterationOutputSchema.shape).filter(([, fieldSchema]) => !fieldSchema.isOptional()).map(([key]) => key);
3291
+ function normalizeIterationProse(output) {
3292
+ const normalized = { ...output };
3293
+ normalized.reasoning = normalizeProseEscapes(output.reasoning);
3294
+ if (typeof output.message === "string") {
3295
+ normalized.message = normalizeProseEscapes(output.message);
3296
+ }
3297
+ if (output.memoryOps?.set) {
3298
+ normalized.memoryOps = {
3299
+ ...output.memoryOps,
3300
+ set: Object.fromEntries(
3301
+ Object.entries(output.memoryOps.set).map(([key, value]) => [key, normalizeMemoryValue(value)])
3302
+ )
3303
+ };
3304
+ }
3305
+ normalized.nextActions = output.nextActions.map(
3306
+ (action) => action.type === "message" ? { ...action, text: normalizeProseEscapes(action.text) } : action
3307
+ );
3308
+ return normalized;
3309
+ }
3310
+ var AgentIterationOutputSchema = AgentIterationOutputObjectSchema.transform(normalizeIterationProse);
3311
+ var REQUIRED_ITERATION_KEYS = Object.entries(AgentIterationOutputObjectSchema.shape).filter(([, fieldSchema]) => !fieldSchema.isOptional()).map(([key]) => key);
3226
3312
  function withSynthesizedMessage(nextActions, message) {
3227
3313
  const text = message?.trim();
3228
3314
  if (!text) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elevasis/sdk",
3
- "version": "1.44.2",
3
+ "version": "1.44.3",
4
4
  "description": "SDK for building Elevasis organization resources",
5
5
  "type": "module",
6
6
  "bin": {
@@ -0,0 +1,88 @@
1
+ # Agent prose is repaired before it is stored
2
+
3
+ ## Why this note exists
4
+
5
+ **Your agents have been writing the two characters `\n` into their replies where a line break
6
+ belongs, and `\"` where a quotation mark belongs.** The operator reads `\n\n` in the middle of a
7
+ sentence, and the stored transcript keeps those characters forever.
8
+
9
+ Measured on 2026-08-03 across four 14-turn production sessions on a live tenant agent — 56 assistant
10
+ replies:
11
+
12
+ | Surface | Rows | Rows affected | Literal `\n` | Literal `\"` |
13
+ | ----------- | ---- | ------------- | ------------ | ------------ |
14
+ | `message` | 56 | 28 | 111 | 146 |
15
+ | `reasoning` | 56 | 1 | 4 | 0 |
16
+
17
+ Half the replies were affected. This is a different defect from the em-dash corruption in
18
+ `2026-07-28-agent-reply-is-its-own-field.md`, and that fix still holds — em-dashes were clean across
19
+ all 56 replies here. This one is on the field that fix created.
20
+
21
+ **The model is choosing the wrong characters, not the wrong encoding.** Three replies carried one
22
+ paragraph break written as real newlines and another written as the two characters, inside the
23
+ _same string_. No serialization layer produces that — a layer that escapes, escapes everything. So
24
+ there is nothing to wait for on the provider side: the output is valid JSON, it satisfies the
25
+ response schema, and no parser can tell the difference.
26
+
27
+ **It also feeds back.** Assistant text is replayed verbatim into the next turn as trusted context,
28
+ so a damaged reply becomes evidence to the model of how this agent writes.
29
+
30
+ The runtime now repairs it deterministically at the single point where model output is validated —
31
+ upstream of persist, stream, render, and replay. `\n`, `\r\n`, `\t`, and `\"` are converted to the
32
+ characters they denote in `message`, in `reasoning`, and in session-memory values.
33
+
34
+ ## Applies to
35
+
36
+ - **Every agent, not only session-capable ones.** `reasoning` and `memoryOps` are normalized on all
37
+ agents; `message` on the ones that have it.
38
+ - **Session memory especially.** A stored memory value is re-sent on every iteration of every
39
+ later turn and never ages out, so one literal escape stored there is one the model re-reads for
40
+ the life of the session. This surface had never been measured before this train.
41
+ - **No agent definition changes are required.** You do not edit your agents. The repair lives in the
42
+ runtime your bundle carries.
43
+
44
+ ## Two behaviors worth knowing
45
+
46
+ 1. **The normalizer is not markdown-aware.** A fenced code block or inline span containing a literal
47
+ `\n` **is** converted. This was a deliberate trade: a reply whose line breaks are all literal has
48
+ no line structure for a fence to start on, so the replies most in need of repair are exactly the
49
+ ones a fence scan would fail to parse. If you run an agent that discusses escape sequences or
50
+ regexes in its user-facing prose, this is the case to check.
51
+ 2. **A doubled backslash is left alone**, and JSON-shaped memory values are skipped entirely — so a
52
+ stored JSON blob keeps its internal escaping and still parses.
53
+
54
+ ## Required actions
55
+
56
+ 1. **Take the `@elevasis/sdk` baseline bump** this train propagates, then reinstall in `operations/`
57
+ so the new worker bundle is present.
58
+ 2. **Redeploy your operations bundle.** This is the step that closes the defect. The agent loop is
59
+ inlined into your deployed bundle, so an existing deployment keeps emitting damaged prose until
60
+ it is redeployed:
61
+
62
+ ```bash
63
+ pnpm -C operations exec elevasis-sdk deploy --prod
64
+ ```
65
+
66
+ A platform-side deploy does not fix this for you, and neither does the reinstall on its own.
67
+
68
+ 3. **Do not treat existing transcripts as clean.** No backfill is performed. Replies and memory
69
+ snapshots written before your redeploy keep the literal escapes they already have. Memory
70
+ snapshots in particular are live agent state, and rewriting them mid-session was judged riskier
71
+ than the damage.
72
+
73
+ ## Verification
74
+
75
+ - Run a multi-turn session and ask for a reply with paragraph structure — a short summary in two or
76
+ three paragraphs is enough. Read the reply: the signature failure is a visible `\n\n` between
77
+ sentences, or a quotation mark rendered as `\"`.
78
+ - **Check a reply containing quoted speech.** Quote escapes were the larger surface (146 of 257
79
+ measured occurrences), and one measured reply carried six of them with no newline escapes at all —
80
+ so a reply can be damaged purely on quotes and look fine structurally.
81
+ - If your agent declares `memoryPreferences`, run enough turns for it to write session memory, then
82
+ confirm a stored value comes back with real line breaks rather than literal ones.
83
+
84
+ ## Not handled by /git-sync
85
+
86
+ - **The redeploy.** `/git-sync` commits and pushes the propagated dependency baseline. Your deployed
87
+ agents keep producing damaged prose until you run action 2 above.
88
+ - **Repairing existing transcripts or memory snapshots.** No backfill is performed, by decision.
@@ -328,6 +328,8 @@ const myAgent: AgentDefinition = {
328
328
 
329
329
  `contract.inputSchema` is required, same as a workflow. `contract.outputSchema` is what a **non-session** (single-shot) agent like the example above returns — there is no conversational reply to read a structured answer from otherwise. A `sessionCapable: true` agent typically omits `outputSchema` and speaks through its conversational `message` instead.
330
330
 
331
+ The framework normalizes literal escape sequences out of that conversational `message`, out of `reasoning`, and out of session-memory values before any of them are persisted, streamed, or replayed. A model sometimes writes the two characters backslash-n where a line break belongs; the result is valid JSON that satisfies the response schema, so nothing upstream can catch it, and the operator ends up reading those characters mid-sentence. Nothing is required of you — no agent definition field changes — but two consequences are worth knowing. An agent that legitimately wants to show an escape sequence in prose (explaining a regex, say) will have it converted, because the normalizer is not markdown-aware. And a memory value that is itself JSON is skipped entirely, so a stored JSON blob keeps its internal escaping intact.
332
+
331
333
  ---
332
334
 
333
335
  ## DeploymentSpec