@springbrand/agent-runtime 0.2.0-alpha.13 → 0.2.0-alpha.15

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@springbrand/agent-runtime",
3
- "version": "0.2.0-alpha.13",
3
+ "version": "0.2.0-alpha.15",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "src",
@@ -15,7 +15,7 @@ import type { RuntimeDegradation } from "../../../kernel/degradation";
15
15
  import type { RuntimeMemoryProfile } from "../../../kernel/profile";
16
16
  import { AGENT_TYPES } from "../../../layers/orchestration/subagents/agent-types/registry";
17
17
  import type { RuntimeAgentConfigContext } from "../../../runtime-agent-context";
18
- import { createWorkspaceCodeExecutionPort } from "../../../pi/tool/core-host";
18
+ import { createWorkspaceCodeExecutionFactory } from "../../../pi/tool/core-host";
19
19
  import {
20
20
  createCloudflareSandboxAdapter,
21
21
  type SandboxAdmission,
@@ -127,7 +127,7 @@ export async function prepareWorkspace<Env extends Cloudflare.Env>(
127
127
  if (!workspace.value) return { degradations: workspace.degradations };
128
128
  return {
129
129
  workspace: workspace.value,
130
- codeExecution: createWorkspaceCodeExecutionPort({
130
+ codeExecution: createWorkspaceCodeExecutionFactory({
131
131
  ctx: context.ctx,
132
132
  loader: platform.loader,
133
133
  outbound: platform.outbound(),
@@ -1,5 +1,4 @@
1
1
  import type {
2
- RuntimeCodeExecutionPort,
3
2
  RuntimeMemoryPort,
4
3
  RuntimeSandboxPort,
5
4
  RuntimeSchedulePort,
@@ -9,7 +8,6 @@ import type {
9
8
  import type { RuntimeDegradation } from "../../../kernel/degradation";
10
9
  import type { RuntimeMemoryProfile } from "../../../kernel/profile";
11
10
  import type { WorkspaceRevisionRestorePort } from "../../../workspace-versioning";
12
- import { createCodeExecutionTool } from "../../../pi/tool/core";
13
11
  import { createScheduleTools } from "../../../pi/tool/schedule";
14
12
  import { createWorkspaceRevisionTools } from "../../../pi/tool/workspace-revision";
15
13
  import {
@@ -18,6 +16,7 @@ import {
18
16
  } from "../../../pi/tool/workspace-sandbox";
19
17
  import {
20
18
  mergeToolRegistries,
19
+ type RuntimeCodeExecutionFactory,
21
20
  type ToolAssemblyResult,
22
21
  type ToolRegistry,
23
22
  } from "../../../tool-registry";
@@ -36,7 +35,7 @@ export function assembleUniversalAgentTools(options: {
36
35
  hostTools?: ToolRegistry;
37
36
  workspace?: WorkspacePort;
38
37
  workspaceRevisions?: WorkspaceRevisionRestorePort;
39
- codeExecution?: RuntimeCodeExecutionPort;
38
+ codeExecution?: RuntimeCodeExecutionFactory;
40
39
  sandbox?: RuntimeSandboxPort;
41
40
  schedule?: RuntimeSchedulePort;
42
41
  subagents?: RuntimeSubagentPort;
@@ -52,9 +51,6 @@ export function assembleUniversalAgentTools(options: {
52
51
  options.workspaceRevisions
53
52
  ? createWorkspaceRevisionTools(options.workspaceRevisions)
54
53
  : {},
55
- options.codeExecution
56
- ? createCodeExecutionTool(options.codeExecution)
57
- : {},
58
54
  options.sandbox ? createSandboxTools(options.sandbox) : {},
59
55
  options.schedule ? createScheduleTools(options.schedule) : {},
60
56
  );
@@ -63,6 +59,7 @@ export function assembleUniversalAgentTools(options: {
63
59
  tools,
64
60
  bindings: {
65
61
  ...(options.workspace ? { workspace: options.workspace } : {}),
62
+ ...(options.codeExecution ? { codeExecution: options.codeExecution } : {}),
66
63
  ...(options.memory ? { memory: options.memory } : {}),
67
64
  ...(options.subagents ? { subagents: options.subagents } : {}),
68
65
  },
package/src/index.ts CHANGED
@@ -86,6 +86,7 @@ export {
86
86
  toolRegistryFromPiCandidates,
87
87
  } from "./tool-registry";
88
88
  export type { ModelOption } from "./lib/model-catalog";
89
+ export type { RuntimeCodeExecutionFactory } from "./tool-registry";
89
90
  export {
90
91
  assembleSubagentPrompt,
91
92
  assembleSystemPrompt,
@@ -134,11 +135,10 @@ export { skillPiToolCandidates } from "./pi/tool";
134
135
  export type { PiSkillBinding } from "./pi/tool";
135
136
  export {
136
137
  browserQuickActionPiToolCandidates,
137
- createCodeExecutionTool,
138
138
  codeExecutionPiToolCandidate,
139
139
  } from "./pi/tool";
140
140
  export {
141
- createWorkspaceCodeExecutionPort,
141
+ createWorkspaceCodeExecutionFactory,
142
142
  } from "./pi/tool";
143
143
  export {
144
144
  assemblePiExtensions,
@@ -117,7 +117,7 @@ export class SubmissionQueueFullError extends Error {
117
117
  readonly code = "queue_full";
118
118
 
119
119
  constructor() {
120
- super(`Pi submission queue is full (${MAX_PENDING_SUBMISSIONS})`);
120
+ super(`SpringBrand submission queue is full (${MAX_PENDING_SUBMISSIONS})`);
121
121
  }
122
122
  }
123
123
 
@@ -303,6 +303,16 @@ export class SubmissionLifecycle<
303
303
  return this.start(submissionId, true);
304
304
  }
305
305
 
306
+ /** 等当前切片退出后恢复同一条非终态 Submission,避免 planned wake 被实例内去重吞掉。 */
307
+ async recoverAfterCurrent(submissionId: string): Promise<TSubmission> {
308
+ const current = this.executions.get(submissionId);
309
+ if (current) {
310
+ const outcome = await current;
311
+ if (isTerminalSubmissionStatus(outcome.status)) return outcome;
312
+ }
313
+ return this.start(submissionId, true);
314
+ }
315
+
306
316
  recoverHead(): Promise<TSubmission | null> {
307
317
  const running = this.options.store.findRunning();
308
318
  if (running) return this.start(running.submissionId, true);
@@ -419,7 +429,7 @@ export class SubmissionLifecycle<
419
429
  }
420
430
  const submission = this.options.store.find(submissionId);
421
431
  if (!submission) {
422
- throw new Error(`Unknown Pi submission: ${submissionId}`);
432
+ throw new Error(`Unknown SpringBrand submission: ${submissionId}`);
423
433
  }
424
434
  shouldPump = isTerminalSubmissionStatus(submission.status);
425
435
  return submission;
@@ -450,10 +460,13 @@ export class SubmissionLifecycle<
450
460
  // TODO(待确认): 持久层若长期保持非终态且没有执行者,这里没有超时或外部唤醒上限。
451
461
  for (;;) {
452
462
  const execution = this.executions.get(submissionId);
453
- if (execution) return execution;
463
+ if (execution) {
464
+ await execution;
465
+ continue;
466
+ }
454
467
  const submission = this.options.store.find(submissionId);
455
468
  if (!submission) {
456
- throw new Error(`Unknown Pi submission: ${submissionId}`);
469
+ throw new Error(`Unknown SpringBrand submission: ${submissionId}`);
457
470
  }
458
471
  if (isTerminalSubmissionStatus(submission.status)) return submission;
459
472
  await new Promise((resolve) => setTimeout(resolve, 50));
@@ -491,7 +504,7 @@ export class SubmissionLifecycle<
491
504
  ): Promise<TSubmission> {
492
505
  const latest = this.options.store.find(submission.submissionId);
493
506
  if (!latest) {
494
- throw new Error(`Unknown Pi submission: ${submission.submissionId}`);
507
+ throw new Error(`Unknown SpringBrand submission: ${submission.submissionId}`);
495
508
  }
496
509
  if (isTerminalSubmissionStatus(latest.status)) return latest;
497
510
  return this.options.commitTerminal(latest, outcome, message);
@@ -1,3 +1,10 @@
1
+ import {
2
+ type ArtifactRef,
3
+ serializeOutput,
4
+ sha256Hex,
5
+ type SpillWorkspace,
6
+ } from "../../../lib/artifacts";
7
+
1
8
  /**
2
9
  * Durable history needs bounded values so one Tool response cannot make a
3
10
  * session impossible to restore.
@@ -11,7 +18,15 @@
11
18
  /** Maximum string leaf retained in durable tool output. */
12
19
  const STORAGE_LEAF_MAX_CHARS = 32 * 1024;
13
20
 
21
+ /** Serialized outputs above this threshold get a narrower model view. */
22
+ const MODEL_VIEW_THRESHOLD = 8 * 1024;
23
+
24
+ /** Maximum string leaf exposed in a structure-preserving model view. */
25
+ const MODEL_LEAF_MAX_CHARS = 500;
26
+
14
27
  const ELISION_RESERVE = 40;
28
+ const PREVIEW_CHARS = 600;
29
+ const DEFAULT_SPILL_DIR = "/scratch/tool-output";
15
30
 
16
31
  // 作用:把过长文本从中间截短,同时保留开头和结尾。
17
32
  // 调用:`truncateStringLeaves` 遇到字符串叶子时调用,传入该叶子的上限。
@@ -86,3 +101,87 @@ export function truncateStringLeaves(value: unknown, maxChars: number): unknown
86
101
  export function boundDurableToolOutput(value: unknown): unknown {
87
102
  return truncateStringLeaves(value, STORAGE_LEAF_MAX_CHARS);
88
103
  }
104
+
105
+ /** Selects whether a Tool result is externalized or kept structurally durable. */
106
+ export type BudgetPolicy = { kind: "spill" } | { kind: "structure" };
107
+
108
+ /**
109
+ * Writes one large payload to Workspace and returns its narrow durable reference.
110
+ *
111
+ * Small values and failed/unavailable Workspace writes return `null`; callers
112
+ * must then retain the existing bounded-structure fallback.
113
+ */
114
+ export async function spillDurableToolOutput(
115
+ value: unknown,
116
+ options: {
117
+ readonly workspace?: SpillWorkspace;
118
+ },
119
+ ): Promise<ArtifactRef | null> {
120
+ const { text, ext } = serializeOutput(value);
121
+ if (text.length <= MODEL_VIEW_THRESHOLD || !options.workspace) return null;
122
+ try {
123
+ const hash = await sha256Hex(text);
124
+ const path = `${DEFAULT_SPILL_DIR}/${hash.slice(0, 24)}.${ext}`;
125
+ await options.workspace.writeFile(
126
+ path,
127
+ text,
128
+ ext === "json" ? "application/json" : "text/plain",
129
+ );
130
+ return {
131
+ kind: "artifact_ref",
132
+ path,
133
+ bytes: new TextEncoder().encode(text).byteLength,
134
+ hash: hash.slice(0, 16),
135
+ preview: text.slice(0, PREVIEW_CHARS),
136
+ note:
137
+ "Output was large and has been saved to the Workspace file above. " +
138
+ "Use the read tool with that path to retrieve the full content when needed.",
139
+ };
140
+ } catch {
141
+ return null;
142
+ }
143
+ }
144
+
145
+ // Bounds leaves first, then removes oldest array entries until the view fits.
146
+ function structureView(output: unknown): unknown {
147
+ const capped = truncateStringLeaves(output, MODEL_LEAF_MAX_CHARS);
148
+ if (serializeOutput(capped).text.length <= MODEL_VIEW_THRESHOLD) return capped;
149
+ if (typeof capped !== "object" || capped === null) return capped;
150
+
151
+ const record = { ...(capped as Record<string, unknown>) };
152
+ const arrayKey = Object.entries(record)
153
+ .filter((entry): entry is [string, unknown[]] => Array.isArray(entry[1]))
154
+ .sort((left, right) => right[1].length - left[1].length)[0]?.[0];
155
+ if (!arrayKey) return capped;
156
+
157
+ const items = [...(record[arrayKey] as unknown[])];
158
+ let dropped = 0;
159
+ while (
160
+ items.length > 1 &&
161
+ serializeOutput({ ...record, [arrayKey]: items }).text.length >
162
+ MODEL_VIEW_THRESHOLD
163
+ ) {
164
+ items.shift();
165
+ dropped += 1;
166
+ }
167
+ if (dropped === 0) return capped;
168
+ record[arrayKey] = items;
169
+ record[`${arrayKey}Omitted`] = {
170
+ count: dropped,
171
+ note:
172
+ `${dropped} earlier entr(ies) omitted to fit the model context budget; ` +
173
+ "the most recent are kept. The durable record retains the bounded value.",
174
+ };
175
+ return record;
176
+ }
177
+
178
+ /** Creates a non-destructive, smaller Tool-result projection for the model. */
179
+ export function projectToolOutputForModel(output: unknown): unknown {
180
+ try {
181
+ return serializeOutput(output).text.length <= MODEL_VIEW_THRESHOLD
182
+ ? output
183
+ : structureView(output);
184
+ } catch {
185
+ return truncateStringLeaves(output, MODEL_LEAF_MAX_CHARS);
186
+ }
187
+ }
package/src/lib/prompt.ts CHANGED
@@ -80,9 +80,10 @@ export const FILES =
80
80
 
81
81
  // User-interaction actions are called only when their UI semantics are useful.
82
82
  export const INTERACTION =
83
- "Interaction: When the user must pick among a small set of options, call ask_user (2-6 options) " +
84
- "instead of writing 'please choose A/B/C' as text, then end your turn and wait. Don't use it for " +
85
- "open-ended questions or when a sensible default lets you proceed. " +
83
+ "Interaction: When progress requires user decisions, put all 1-4 necessary questions in one ask_user call " +
84
+ "instead of asking each question separately or writing 'please choose A/B/C' as text. Use 2-6 options for " +
85
+ "a bounded choice, or no options when a required free-text answer is necessary; continue the same turn when its result arrives. " +
86
+ "Don't use it when a sensible default lets you proceed. " +
86
87
  "After completing a substantive task (report, analysis, multi-step job): write your full answer " +
87
88
  "FIRST, then call suggest_followups (2-4 directions) as the very last action and end the turn — " +
88
89
  "write no text after it (the tool renders its own closing block). Never for small talk or while a " +
@@ -282,7 +282,7 @@ export class PiChunkEncoder {
282
282
  return [];
283
283
  default:
284
284
  throw new Error(
285
- `Unsupported Pi AgentEvent: ${String((event as { type?: unknown }).type)}`,
285
+ `Unsupported SpringBrand AgentEvent: ${String((event as { type?: unknown }).type)}`,
286
286
  );
287
287
  }
288
288
  }
@@ -449,7 +449,7 @@ export class PiChunkEncoder {
449
449
  if (message.stopReason === "error") {
450
450
  chunks.push({
451
451
  type: "error",
452
- errorText: message.errorMessage ?? "Pi turn failed",
452
+ errorText: message.errorMessage ?? "SpringBrand turn failed",
453
453
  });
454
454
  return chunks;
455
455
  }
@@ -321,7 +321,7 @@ class PreparedRuntime implements PreparedPiRuntime {
321
321
  read(owner: object): PreparedPiRuntimeState {
322
322
  if (owner !== this.owner) {
323
323
  throw new Error(
324
- "Prepared Pi Runtime belongs to another PiRuntimeAdapter",
324
+ "Prepared SpringBrand Runtime belongs to another runtime adapter",
325
325
  );
326
326
  }
327
327
  return this.state;
@@ -341,7 +341,7 @@ export function readPreparedPiRuntime(
341
341
  owner: object,
342
342
  ): PreparedPiRuntimeState {
343
343
  if (!(prepared instanceof PreparedRuntime)) {
344
- throw new Error("Prepared Pi Runtime handle is invalid");
344
+ throw new Error("Prepared SpringBrand Runtime handle is invalid");
345
345
  }
346
346
  return prepared.read(owner);
347
347
  }
@@ -46,10 +46,12 @@ import {
46
46
  withProviderRetry,
47
47
  } from "./models";
48
48
  import { EXECUTION_LEVELS } from "../../lib/execution-level";
49
+ import { projectToolOutputForModel } from "../../layers/context/budget/gate";
50
+ import type { SpillWorkspace } from "../../lib/artifacts";
49
51
 
50
52
  // #region Single-run Pi bridge
51
53
 
52
- const MAX_MODEL_TURNS = 30;
54
+ const MAX_MODEL_TURNS_PER_SLICE = 30;
53
55
 
54
56
  export class RetryableModelError extends Error {}
55
57
 
@@ -91,7 +93,7 @@ function classifyPiStopReason(stopReason: string | undefined): {
91
93
  return {
92
94
  outcome: "failed",
93
95
  turnStatus: "error",
94
- message: `Pi ended the turn with an unrecognized stop reason: ${
96
+ message: `SpringBrand ended the turn with an unrecognized stop reason: ${
95
97
  stopReason ?? "(none)"
96
98
  }`,
97
99
  };
@@ -134,6 +136,29 @@ interface PiTurnAdapterOptions {
134
136
  // PiCore 通过 AgentOptions.transformContext 调用它,Runtime 用它接入现有上下文处理。
135
137
  // 直接复用 Pi 的回调类型可避免这里维护另一套上下文转换约定。
136
138
  readonly transformContext: NonNullable<AgentOptions["transformContext"]>;
139
+ readonly workspace?: SpillWorkspace;
140
+ }
141
+
142
+ function projectToolResultsForModel(
143
+ messages: readonly AgentMessage[],
144
+ ): AgentMessage[] {
145
+ let changed = false;
146
+ const projected = messages.map((message) => {
147
+ if (message.role !== "toolResult") return message;
148
+ const payload = {
149
+ content: message.content,
150
+ details: message.details,
151
+ };
152
+ const next = projectToolOutputForModel(payload) as typeof payload;
153
+ if (next === payload) return message;
154
+ changed = true;
155
+ return {
156
+ ...message,
157
+ content: next.content,
158
+ details: next.details,
159
+ };
160
+ });
161
+ return changed ? projected : [...messages];
137
162
  }
138
163
 
139
164
  class PiTurnAdapter {
@@ -155,6 +180,7 @@ class PiTurnAdapter {
155
180
  private compile(candidates: readonly PiToolCandidate[]) {
156
181
  return compilePiTools(candidates, {
157
182
  governance: this.governance,
183
+ workspace: this.opts.workspace,
158
184
  settle: this.opts.settle,
159
185
  });
160
186
  }
@@ -201,7 +227,7 @@ class PiTurnAdapter {
201
227
  readonly signal?: AbortSignal;
202
228
  },
203
229
  listener: (event: AgentEvent) => void | Promise<void>,
204
- ): Promise<boolean> {
230
+ ): Promise<PiTurnRunResult> {
205
231
  const { systemPrompt, signal } = opts;
206
232
  signal?.throwIfAborted();
207
233
  const canonicalMessages = await this.opts.canonicalMessages();
@@ -221,11 +247,14 @@ class PiTurnAdapter {
221
247
  // 切换 provider 或消息跨 provider 回放时若不规范化,provider 会静默拒绝。
222
248
  transformContext: async (messages, signal) => {
223
249
  const ctx = await this.opts.transformContext(messages, signal);
224
- return transformMessages(ctx as Message[], this.opts.pi.model) as AgentMessage[];
250
+ return transformMessages(
251
+ projectToolResultsForModel(ctx) as Message[],
252
+ this.opts.pi.model,
253
+ ) as AgentMessage[];
225
254
  },
226
255
  afterToolCall: this.governance.afterToolCall,
227
256
  shouldStopAfterTurn: () => {
228
- reachedModelTurnLimit = ++modelTurns >= MAX_MODEL_TURNS;
257
+ reachedModelTurnLimit = ++modelTurns >= MAX_MODEL_TURNS_PER_SLICE;
229
258
  return signal?.aborted === true || reachedModelTurnLimit;
230
259
  },
231
260
  initialState: {
@@ -256,7 +285,9 @@ class PiTurnAdapter {
256
285
  pi.abort();
257
286
  }
258
287
  await running;
259
- return reachedModelTurnLimit;
288
+ return reachedModelTurnLimit
289
+ ? { kind: "yielded" }
290
+ : { kind: "terminal" };
260
291
  } finally {
261
292
  signal?.removeEventListener("abort", abort);
262
293
  unsubscribe();
@@ -447,6 +478,10 @@ export interface PiPreparedTurnRunOptions {
447
478
  readonly signal?: AbortSignal;
448
479
  }
449
480
 
481
+ export type PiTurnRunResult =
482
+ | { readonly kind: "terminal" }
483
+ | { readonly kind: "yielded" };
484
+
450
485
  // #endregion
451
486
 
452
487
  // #region Durable prepared-Turn execution
@@ -505,7 +540,7 @@ export class PreparedPiTurnAdapter {
505
540
  : candidate.requiredExecutionLevel;
506
541
  if (!EXECUTION_LEVELS.includes(requiredExecutionLevel)) {
507
542
  throw new Error(
508
- `Pi Tool "${candidate.tool.name}" resolved an invalid execution level`,
543
+ `SpringBrand Tool "${candidate.tool.name}" resolved an invalid execution level`,
509
544
  );
510
545
  }
511
546
  await state.snapshot.bindings.platform.gateTool?.({
@@ -652,6 +687,7 @@ export class PreparedPiTurnAdapter {
652
687
  this.interrupted ? undefined : options.durability.settleTool(call),
653
688
  onToolTelemetry: options.onToolTelemetry,
654
689
  transformContext: options.transformContext,
690
+ workspace: state.snapshot.bindings.workspace,
655
691
  });
656
692
  this.systemPrompt = descriptor.systemPrompt;
657
693
  this.options = options;
@@ -751,9 +787,9 @@ export class PreparedPiTurnAdapter {
751
787
  */
752
788
  async run(
753
789
  options: PiPreparedTurnRunOptions,
754
- ): Promise<void> {
790
+ ): Promise<PiTurnRunResult> {
755
791
  this.terminalIntent = undefined;
756
- const reachedModelTurnLimit = await this.turn.run(
792
+ const result = await this.turn.run(
757
793
  {
758
794
  systemPrompt: this.systemPrompt,
759
795
  tools: this.candidates,
@@ -761,15 +797,11 @@ export class PreparedPiTurnAdapter {
761
797
  },
762
798
  (event) => this.handleEvent(event),
763
799
  );
764
- if (reachedModelTurnLimit && !this.terminalIntent) {
765
- this.terminalIntent = {
766
- outcome: "failed",
767
- message: `Pi stopped after ${MAX_MODEL_TURNS} model turns`,
768
- };
769
- }
770
800
  if (this.terminalIntent) {
771
801
  await this.options.onTerminal(this.terminalIntent);
802
+ return { kind: "terminal" };
772
803
  }
804
+ return result;
773
805
  }
774
806
 
775
807
  // 提交完成的 Pi 消息、推导终态意图,并把事件投影给可恢复流。
@@ -259,6 +259,7 @@ export type {
259
259
  PiStoredToolSettlement,
260
260
  PiToolSettlement,
261
261
  PiToolInputRecord,
262
+ PiTurnRunResult,
262
263
  PreparedPiTurnAdapter,
263
264
  } from "./execution";
264
265
  export type {
@@ -498,7 +498,7 @@ function configuredModel(
498
498
  );
499
499
  if (!catalogModel) {
500
500
  throw new Error(
501
- `Model is not in Pi's built-in catalog for ${endpoint.protocol}: ${modelId}`,
501
+ `Model is not in SpringBrand's built-in catalog for ${endpoint.protocol}: ${modelId}`,
502
502
  );
503
503
  }
504
504
  const {
@@ -201,7 +201,7 @@ function milestoneMutation(
201
201
  // 缺少身份时立即失败,避免把无归属的记录混入另一次 Turn 的恢复日志。
202
202
  function recoveryIdentity(input: PiRecoveryInput) {
203
203
  if (!input.identity) {
204
- throw new Error("Pi recovery command is missing its Turn revision");
204
+ throw new Error("SpringBrand recovery command is missing its Turn revision");
205
205
  }
206
206
  return { version: 1 as const, ...input.identity };
207
207
  }
@@ -255,7 +255,7 @@ function interactionToolResult(
255
255
  timestamp: number,
256
256
  ): ToolResultMessage {
257
257
  if (!interaction) {
258
- throw new Error("Pi Tool interaction is missing for its response");
258
+ throw new Error("SpringBrand Tool interaction is missing for its response");
259
259
  }
260
260
  return {
261
261
  role: "toolResult",
@@ -417,7 +417,7 @@ export function decidePiRecovery(
417
417
  (milestone) => milestone.type === "approval",
418
418
  );
419
419
  if (!approval || approval.type !== "approval") {
420
- throw new Error("Pi approval decision milestone is missing");
420
+ throw new Error("SpringBrand approval decision milestone is missing");
421
421
  }
422
422
  mutations.push({
423
423
  kind: "decide-approval",
@@ -453,7 +453,7 @@ export function decidePiRecovery(
453
453
  JSON.stringify(existing.input) !== JSON.stringify(record.input)
454
454
  ) {
455
455
  throw new Error(
456
- `Conflicting Pi Tool input for ${record.toolCallId}`,
456
+ `Conflicting SpringBrand Tool input for ${record.toolCallId}`,
457
457
  );
458
458
  }
459
459
  } else {
@@ -482,7 +482,7 @@ export function decidePiRecovery(
482
482
  existing.inputJson !== approval.inputJson
483
483
  ) {
484
484
  throw new Error(
485
- `Conflicting Pi Tool approval: ${approval.executionId}`,
485
+ `Conflicting SpringBrand Tool approval: ${approval.executionId}`,
486
486
  );
487
487
  }
488
488
  } else {
@@ -530,7 +530,7 @@ export function decidePiRecovery(
530
530
  existing.inputJson !== interaction.inputJson
531
531
  ) {
532
532
  throw new Error(
533
- `Conflicting Pi Tool interaction: ${interaction.interactionId}`,
533
+ `Conflicting SpringBrand Tool interaction: ${interaction.interactionId}`,
534
534
  );
535
535
  }
536
536
  } else {
@@ -580,7 +580,7 @@ export function decidePiRecovery(
580
580
  (milestone) => milestone.type === "interaction",
581
581
  );
582
582
  if (!record || record.type !== "interaction") {
583
- throw new Error("Pi interaction settlement milestone is missing");
583
+ throw new Error("SpringBrand interaction settlement milestone is missing");
584
584
  }
585
585
  mutations.push({
586
586
  kind: "settle-interaction",
@@ -621,7 +621,7 @@ export function decidePiRecovery(
621
621
  if (existing) {
622
622
  if (JSON.stringify(existing) !== JSON.stringify(toolResult)) {
623
623
  throw new Error(
624
- `Conflicting Pi Tool result for ${command.toolCallId}`,
624
+ `Conflicting SpringBrand Tool result for ${command.toolCallId}`,
625
625
  );
626
626
  }
627
627
  } else {
@@ -676,7 +676,7 @@ export function decidePiRecovery(
676
676
  plan.continuationKey !== input.command.continuationKey
677
677
  ) {
678
678
  throw new Error(
679
- `Pi continuation is not ready: ${input.command.continuationKey}`,
679
+ `SpringBrand continuation is not ready: ${input.command.continuationKey}`,
680
680
  );
681
681
  }
682
682
  const committed = commitPiRecoveryContinuation(
@@ -686,7 +686,7 @@ export function decidePiRecovery(
686
686
  );
687
687
  if (!committed) {
688
688
  throw new Error(
689
- `Pi continuation could not be committed: ${plan.continuationKey}`,
689
+ `SpringBrand continuation could not be committed: ${plan.continuationKey}`,
690
690
  );
691
691
  }
692
692
  const mutation = milestoneMutation(
@@ -809,13 +809,13 @@ export class PiRuntimeRecoveryAdapter
809
809
  if (!durable) {
810
810
  return {
811
811
  kind: "fail",
812
- reason: "Recovered Pi submission is missing",
812
+ reason: "Recovered SpringBrand submission is missing",
813
813
  } as const;
814
814
  }
815
815
  if (durable.terminal) {
816
816
  return {
817
817
  kind: "ignore",
818
- reason: "Pi submission is already terminal",
818
+ reason: "SpringBrand submission is already terminal",
819
819
  } as const;
820
820
  }
821
821
  const decision = decidePiRecovery({
@@ -827,20 +827,20 @@ export class PiRuntimeRecoveryAdapter
827
827
  if (decision.effect.reason === "approval") {
828
828
  return {
829
829
  kind: "park",
830
- reason: "Pi Turn is waiting for Tool approval",
830
+ reason: "SpringBrand Turn is waiting for Tool approval",
831
831
  } as const;
832
832
  }
833
833
  if (decision.effect.reason === "interaction") {
834
834
  return {
835
835
  kind: "park",
836
- reason: "Pi Turn is waiting for a client Tool interaction response",
836
+ reason: "SpringBrand Turn is waiting for a client Tool interaction response",
837
837
  } as const;
838
838
  }
839
839
  if (decision.effect.reason === "uncertain-tool") {
840
840
  return {
841
841
  kind: "park",
842
842
  reason:
843
- "Pi Turn is parked on an uncertain non-idempotent Tool",
843
+ "SpringBrand Turn is parked on an uncertain non-idempotent Tool",
844
844
  incidentStatus: "failed",
845
845
  } as const;
846
846
  }
@@ -848,12 +848,12 @@ export class PiRuntimeRecoveryAdapter
848
848
  return {
849
849
  kind: "fail",
850
850
  reason:
851
- "Pi recovery is complete but the Submission is not terminal",
851
+ "SpringBrand recovery is complete but the Submission is not terminal",
852
852
  } as const;
853
853
  }
854
854
  return {
855
855
  kind: "park",
856
- reason: "Pi recovery is waiting for durable input",
856
+ reason: "SpringBrand recovery is waiting for durable input",
857
857
  } as const;
858
858
  }
859
859
  return {
@@ -526,7 +526,7 @@ export class PiRuntimeTranscript {
526
526
  */
527
527
  importSnapshot(snapshot: PiCanonicalTranscriptSnapshot): void {
528
528
  if (this.hasActiveTurn()) {
529
- throw new Error("Cannot import messages while a Pi Turn is active");
529
+ throw new Error("Cannot import messages while a SpringBrand Turn is active");
530
530
  }
531
531
  this.durability.transaction(() => {
532
532
  let currentSubmissionId: string | undefined;
@@ -551,7 +551,7 @@ export class PiRuntimeTranscript {
551
551
  : {}),
552
552
  });
553
553
  if (!appended) {
554
- throw new Error(`Pi Session message "${entry.id}" already exists`);
554
+ throw new Error(`SpringBrand Session message "${entry.id}" already exists`);
555
555
  }
556
556
  }
557
557
  });