@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 +11 -3
- package/dist/index.js +10 -2
- package/dist/test-utils/index.js +90 -4
- package/dist/worker/index.js +90 -4
- package/package.json +1 -1
- package/reference/claude-config/sync-notes/2026-08-03-agent-prose-escape-normalization.md +88 -0
- package/reference/sdk/resources/index.mdx +2 -0
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:
|
|
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: {
|
|
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.
|
|
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:
|
|
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: {
|
|
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
|
package/dist/test-utils/index.js
CHANGED
|
@@ -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:
|
|
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: {
|
|
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
|
|
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
|
-
|
|
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) {
|
package/dist/worker/index.js
CHANGED
|
@@ -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:
|
|
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: {
|
|
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
|
|
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
|
-
|
|
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
|
@@ -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
|