@rowan-agent/agent 0.9.3 → 0.9.5

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/index.js CHANGED
@@ -91,12 +91,12 @@ function buildPhaseDirectiveMessage(phase, output) {
91
91
  const parts = [];
92
92
  parts.push(`<phase_content name="${escapeXml(phase.name)}">`);
93
93
  parts.push(phase.content);
94
- if (output.results && output.results.length > 0) {
94
+ if (output.results && output.results.length > 0 || output.instruction) {
95
95
  parts.push(` <prev_phase_outputs>`);
96
96
  if (output.instruction) {
97
97
  parts.push(` <instruction>${escapeXml(output.instruction)}</instruction>`);
98
98
  }
99
- for (const r of output.results) {
99
+ for (const r of output.results ?? []) {
100
100
  parts.push(` <phase name="${escapeXml(r.name)}">`);
101
101
  if (r.output !== void 0) {
102
102
  parts.push(jsonToXml(r.output, 3));
@@ -172,18 +172,24 @@ function buildPhaseEntry(p) {
172
172
  function buildRouteDescription(availablePhases) {
173
173
  const phasesBlock = buildStructuredSection("phase", [
174
174
  ...availablePhases.map(buildPhaseEntry),
175
- { name: "stop", description: "Terminate the workflow and return final result to the user" }
175
+ {
176
+ name: "stop",
177
+ description: "Explicitly finish the current user request or task when no further user input is needed"
178
+ }
176
179
  ]);
177
180
  return [
178
- "Route execution to one or more phases concurrently, or stop execution.",
181
+ "Route is optional. Omit it when the current reply should wait for more user input and remain in the current phase.",
179
182
  "",
180
183
  "Rules:",
181
- "- `decision` lists phase executions; use phase 'stop' to terminate.",
184
+ "- Call route only to continue execution immediately in one or more phases, or to explicitly stop.",
185
+ "- `decision` lists phase executions; a single target equal to the current phase starts another iteration of that phase.",
186
+ "- `stop` means the current user request or task is complete and no further user input is needed; `stop` must be the only target.",
182
187
  "- Each target may include `phase`, `reason`, `payload`.",
183
188
  "- A phase may appear multiple times as independent execution instances.",
184
189
  "- `payload` MUST match the phase's `payload_schema`",
185
190
  "- `instruction` is optional shared guidance for all phases.",
186
191
  "- Executions are independent and concurrent; order is irrelevant.",
192
+ "- Do not call route in the same response as an ordinary tool; finish ordinary tools first.",
187
193
  "",
188
194
  "<available_phases>",
189
195
  phasesBlock,
@@ -202,12 +208,13 @@ function createRouteTool(availablePhases) {
202
208
  return {
203
209
  name: PhaseRouteTool,
204
210
  description: buildRouteDescription(availablePhases),
205
- promptSnippet: "Route to next phase on completion.",
211
+ promptSnippet: "Route is optional: omit it to remain in the current phase and wait for user input; use it for immediate phase execution or an explicit stop.",
206
212
  promptGuidelines: [
207
- "Call route immediately when the phase is complete."
213
+ "Do not call route together with ordinary tools.",
214
+ "Use route(stop) only when the current user request or task is complete and no further user input is needed."
208
215
  ],
209
216
  parameters: Type.Object({
210
- decision: Type.Array(DecisionTarget, { description: "Phase executions to start" }),
217
+ decision: Type.Array(DecisionTarget, { description: "Phase executions to start", minItems: 1 }),
211
218
  instruction: Type.Optional(Type.String({ description: "Overall instruction, passed as context" }))
212
219
  }),
213
220
  // No-op: this tool is intercepted by phases, never executed via tool execution
@@ -220,29 +227,35 @@ function createRouteTool(availablePhases) {
220
227
  };
221
228
  }
222
229
  function extractRouteCall(toolCalls) {
223
- const routeCall = toolCalls.find((t) => t.name === PhaseRouteTool);
224
- if (!routeCall) return void 0;
230
+ const routeCalls = toolCalls.filter((t) => t.name === PhaseRouteTool);
231
+ if (routeCalls.length !== 1) return void 0;
232
+ const routeCall = routeCalls[0];
225
233
  let args;
226
234
  if (typeof routeCall.args === "string") {
227
235
  try {
228
- args = JSON.parse(routeCall.args);
236
+ const parsed = JSON.parse(routeCall.args);
237
+ args = parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
229
238
  } catch {
230
- return void 0;
239
+ return { decision: [{ phase: "" }] };
231
240
  }
232
241
  } else {
233
- args = routeCall.args;
242
+ args = routeCall.args !== null && typeof routeCall.args === "object" ? routeCall.args : {};
234
243
  }
235
244
  const decisionRaw = args.decision;
236
245
  let decision = [];
237
246
  if (Array.isArray(decisionRaw)) {
238
247
  decision = decisionRaw.map((d) => {
239
- const obj = d;
248
+ const obj = d !== null && typeof d === "object" ? d : {};
240
249
  return {
241
- phase: typeof obj?.phase === "string" ? obj.phase : "stop",
250
+ // Empty phase is an invalid target sentinel. Never coerce malformed
251
+ // input to stop: only an explicit, valid stop target may terminate.
252
+ phase: typeof obj.phase === "string" ? obj.phase : "",
242
253
  reason: typeof obj?.reason === "string" ? obj.reason : void 0,
243
254
  payload: obj?.payload
244
255
  };
245
256
  });
257
+ } else if (decisionRaw !== void 0) {
258
+ decision = [{ phase: "" }];
246
259
  }
247
260
  return {
248
261
  decision,
@@ -765,6 +778,15 @@ function contentBlocksToMessageContent(blocks) {
765
778
  });
766
779
  }
767
780
 
781
+ // src/loop/types.ts
782
+ var DEFAULT_MAX_ATTEMPTS = 16;
783
+ function validateMaxAttempts(value) {
784
+ if (value !== void 0 && (!Number.isInteger(value) || value <= 0)) {
785
+ throw new TypeError("maxAttempts must be a positive integer.");
786
+ }
787
+ return value;
788
+ }
789
+
768
790
  // src/loop/errors.ts
769
791
  var EmptyResponseError = class extends Error {
770
792
  code = "empty_response";
@@ -882,7 +904,7 @@ async function collectStreamResult(input) {
882
904
  doneResponse = event.response;
883
905
  stopReason = event.response?.stopReason;
884
906
  const contentBlocks2 = lastPartial?.contentBlocks ?? [];
885
- const hasContent2 = contentBlocks2.length > 0 || (event.response?.content?.length ?? 0) > 0;
907
+ const hasContent2 = contentBlocks2.length > 0 || (event.response?.content?.length ?? 0) > 0 || (event.response?.toolCalls?.length ?? 0) > 0;
886
908
  const shouldStoreContentParts = contentBlocks2.some((block) => block.type !== "text");
887
909
  if (activeMessageId && hasContent2 && shouldStoreContentParts) {
888
910
  input.message.replaceContent(activeMessageId, contentBlocksToMessageContent(contentBlocks2));
@@ -905,7 +927,7 @@ async function collectStreamResult(input) {
905
927
  if (activeMessageId) {
906
928
  await input.message.end(activeMessageId);
907
929
  }
908
- const hasContent = (lastPartial?.contentBlocks?.length ?? 0) > 0 || (doneResponse?.content?.length ?? 0) > 0;
930
+ const hasContent = (lastPartial?.contentBlocks?.length ?? 0) > 0 || (doneResponse?.content?.length ?? 0) > 0 || (doneResponse?.toolCalls?.length ?? 0) > 0;
909
931
  if (!hasContent && stopReason !== "error" && stopReason !== "aborted") {
910
932
  throw new EmptyResponseError();
911
933
  }
@@ -1497,7 +1519,7 @@ function createDefaultPhase() {
1497
1519
  filePath: "",
1498
1520
  baseDir: "",
1499
1521
  skills: [],
1500
- content: "Execute tasks using current context.\nNo planning. No evaluation.\nRoute to next phase or stop when done."
1522
+ content: "Execute tasks using current context.\nNo planning. No evaluation.\nOmit route when more user input is needed. Use route for immediate phase execution, or route(stop) only when the current user request or task is complete and no further user input is needed."
1501
1523
  };
1502
1524
  }
1503
1525
 
@@ -1821,6 +1843,31 @@ function applyFirstDecision(route, output) {
1821
1843
  if (first.reason) output.routeReason = first.reason;
1822
1844
  if (first.payload !== void 0) output.payload = normalizePayload(first.payload);
1823
1845
  }
1846
+ function resolveRouteDecision(route, registry) {
1847
+ if (!route || route.decision.length === 0) return void 0;
1848
+ const requestedCount = route.decision.length;
1849
+ const hasStop = route.decision.some(({ phase }) => phase === "stop");
1850
+ if (hasStop) {
1851
+ if (requestedCount !== 1) return void 0;
1852
+ return { ...route, requestedCount };
1853
+ }
1854
+ const decision = route.decision.filter(({ phase }) => registry.phases.has(phase));
1855
+ if (decision.length === 0) return void 0;
1856
+ return { ...route, decision, requestedCount };
1857
+ }
1858
+ function resolveModelRouteDecision(toolCalls, registry) {
1859
+ return resolveRouteDecision(toolCalls ? extractRouteCall(toolCalls) : void 0, registry);
1860
+ }
1861
+ function resolveHookRouteDecision(output, registry) {
1862
+ if (output.route === void 0 || output.route === "continue") return void 0;
1863
+ return resolveRouteDecision({
1864
+ decision: [{
1865
+ phase: output.route,
1866
+ ...output.routeReason ? { reason: output.routeReason } : {},
1867
+ ...output.payload === void 0 ? {} : { payload: output.payload }
1868
+ }]
1869
+ }, registry);
1870
+ }
1824
1871
  function injectPhaseContent(phase, output, messages, mirror) {
1825
1872
  try {
1826
1873
  const phaseContent = phase.filePath ? readPhaseContent(phase) : phase.content ?? phase.description ?? "";
@@ -1952,12 +1999,13 @@ function createToolResultContent(result) {
1952
1999
  }
1953
2000
  ];
1954
2001
  }
1955
- function createRouteToolResultContent(toolCall) {
2002
+ function createRouteToolResultContent(toolCall, error) {
1956
2003
  return [
1957
2004
  {
1958
2005
  type: "tool_result",
1959
2006
  toolUseId: toolCall.id,
1960
- content: '{"ok": true}'
2007
+ content: error ? JSON.stringify({ ok: false, error }) : '{"ok": true}',
2008
+ ...error ? { isError: true } : {}
1961
2009
  }
1962
2010
  ];
1963
2011
  }
@@ -1967,7 +2015,6 @@ async function executePhaseWithModel(ctx) {
1967
2015
  );
1968
2016
  let output = {
1969
2017
  message: "",
1970
- route: "stop",
1971
2018
  phase: ctx.phase.name,
1972
2019
  toolCalls: []
1973
2020
  };
@@ -1979,15 +2026,24 @@ async function executePhaseWithModel(ctx) {
1979
2026
  const collected = await ctx.execution.invokeModel(roundContext);
1980
2027
  output = {
1981
2028
  message: collected.text,
1982
- route: "stop",
1983
2029
  phase: ctx.phase.name,
1984
- toolCalls: collected.toolCalls
2030
+ // A route decision sharing a model response with an ordinary Tool is
2031
+ // rejected for this round. Keep ordinary calls visible so they can run,
2032
+ // but do not let the route escape the error/re-decision boundary.
2033
+ toolCalls: collected.toolCalls.some((candidate) => candidate.name === PhaseRouteTool) && collected.toolCalls.some((candidate) => candidate.name !== PhaseRouteTool) ? collected.toolCalls.filter((candidate) => candidate.name !== PhaseRouteTool) : collected.toolCalls
1985
2034
  };
1986
2035
  for (const toolCall of collected.toolCalls) {
1987
2036
  if (toolCall.name !== PhaseRouteTool) continue;
1988
- const messageId = ctx.messageManager.start("tool", createRouteToolResultContent(toolCall), {
1989
- phase: ctx.phase.name
1990
- });
2037
+ const messageId = ctx.messageManager.start(
2038
+ "tool",
2039
+ createRouteToolResultContent(
2040
+ toolCall,
2041
+ collected.toolCalls.some((candidate) => candidate.name !== PhaseRouteTool) ? "Route must be called separately after ordinary tools finish; this route decision was ignored." : void 0
2042
+ ),
2043
+ {
2044
+ phase: ctx.phase.name
2045
+ }
2046
+ );
1991
2047
  await ctx.messageManager.end(messageId);
1992
2048
  }
1993
2049
  const executableToolCalls = collected.toolCalls.filter(
@@ -2019,6 +2075,7 @@ async function startPhaseLoop(config, state) {
2019
2075
  return runPhaseLoop(config, state, registry);
2020
2076
  }
2021
2077
  async function runPhaseLoop(config, state, registry) {
2078
+ const maxAttempts = validateMaxAttempts(config.maxAttempts) ?? DEFAULT_MAX_ATTEMPTS;
2022
2079
  const resumingSuspendedRun = state.status === "suspended" && Boolean(state.currentPhase);
2023
2080
  let currentPhaseId = resumingSuspendedRun ? state.currentPhase : registry.entryPhaseId;
2024
2081
  let isContinuing = resumingSuspendedRun ? state.continuation?.isContinuing ?? true : false;
@@ -2030,6 +2087,53 @@ async function runPhaseLoop(config, state, registry) {
2030
2087
  let previousPhaseInputMsgId;
2031
2088
  let previousResults = resumingSuspendedRun ? state.continuation?.previousResults?.map((result) => ({ ...result })) ?? [] : [];
2032
2089
  let pendingInstruction = resumingSuspendedRun ? state.continuation?.pendingInstruction : void 0;
2090
+ const awaitInput = async (prompt, resumeAsContinuing) => {
2091
+ if (!config.waitForInput) return void 0;
2092
+ const preAbort = LoopGuard.checkAbort(config.signal);
2093
+ if (preAbort.stopReason !== "none") {
2094
+ removePhaseMessage(config.context.messages, previousPhaseMsgId);
2095
+ removePhaseMessage(config.context.messages, previousPhaseInputMsgId);
2096
+ previousPhaseMsgId = void 0;
2097
+ previousPhaseInputMsgId = void 0;
2098
+ return {
2099
+ type: "completed",
2100
+ result: await completeRun(config, state, createOutcome.aborted())
2101
+ };
2102
+ }
2103
+ const requestedAt = createTimestamp();
2104
+ const inputRequest = {
2105
+ phase: currentPhaseId,
2106
+ prompt,
2107
+ requestedAt
2108
+ };
2109
+ state.currentPhase = currentPhaseId;
2110
+ state.status = "suspended";
2111
+ state.attempt = 0;
2112
+ state.continuation = {
2113
+ isContinuing: resumeAsContinuing,
2114
+ previousPayload,
2115
+ previousResults: previousResults.map((result) => ({ ...result })),
2116
+ pendingInstruction,
2117
+ previousPhaseMessageId: previousPhaseMsgId
2118
+ };
2119
+ const userMessages = await config.waitForInput(state, inputRequest);
2120
+ state.status = "running";
2121
+ const abortResult = LoopGuard.checkAbort(config.signal);
2122
+ if (abortResult.stopReason !== "none") {
2123
+ removePhaseMessage(config.context.messages, previousPhaseMsgId);
2124
+ removePhaseMessage(config.context.messages, previousPhaseInputMsgId);
2125
+ previousPhaseMsgId = void 0;
2126
+ previousPhaseInputMsgId = void 0;
2127
+ return {
2128
+ type: "completed",
2129
+ result: await completeRun(config, state, createOutcome.aborted())
2130
+ };
2131
+ }
2132
+ for (const message of userMessages) {
2133
+ config.context.messages.push(message);
2134
+ }
2135
+ return { type: "continued" };
2136
+ };
2033
2137
  while (currentPhaseId) {
2034
2138
  const availablePhases = [];
2035
2139
  for (const [, phase2] of registry.phases) {
@@ -2043,6 +2147,19 @@ async function runPhaseLoop(config, state, registry) {
2043
2147
  previousPhaseInputMsgId = void 0;
2044
2148
  return completeRun(config, state, createOutcome.aborted());
2045
2149
  }
2150
+ if (state.attempt >= maxAttempts) {
2151
+ const input = await awaitInput(
2152
+ `The autonomous Phase execution limit (${maxAttempts}) was reached. Provide input to continue.`,
2153
+ false
2154
+ );
2155
+ if (input?.type === "completed") return input.result;
2156
+ if (input?.type === "continued") {
2157
+ isContinuing = false;
2158
+ continue;
2159
+ }
2160
+ throw new Error("Phase execution reached maxAttempts but waitForInput is unavailable.");
2161
+ }
2162
+ state.attempt++;
2046
2163
  state.metrics.iterations++;
2047
2164
  if (needsCompaction(config.context.messages)) {
2048
2165
  const compacted = compactMessages(config.context.messages);
@@ -2143,13 +2260,9 @@ async function runPhaseLoop(config, state, registry) {
2143
2260
  }
2144
2261
  const runtime = { phase, config, state, execution, messageManager, registry, context: phaseContext };
2145
2262
  let output = await executePhase(runtime);
2146
- let routeDecision;
2147
- if (output.toolCalls && output.toolCalls.length > 0) {
2148
- routeDecision = extractRouteCall(output.toolCalls);
2149
- if (routeDecision) {
2150
- applyFirstDecision(routeDecision, output);
2151
- }
2152
- }
2263
+ let modelRouteDecision = resolveModelRouteDecision(output.toolCalls, registry);
2264
+ let routeDecision = modelRouteDecision;
2265
+ if (routeDecision) applyFirstDecision(routeDecision, output);
2153
2266
  if (config.afterPhase) {
2154
2267
  const extAfter = await config.afterPhase(currentPhaseId, output);
2155
2268
  if (extAfter.abort) {
@@ -2161,52 +2274,30 @@ async function runPhaseLoop(config, state, registry) {
2161
2274
  }
2162
2275
  if (extAfter.retry && (phase.run || phase.factory)) {
2163
2276
  output = await executePhase(runtime);
2164
- routeDecision = output.toolCalls ? extractRouteCall(output.toolCalls) : void 0;
2277
+ modelRouteDecision = resolveModelRouteDecision(output.toolCalls, registry);
2278
+ routeDecision = modelRouteDecision;
2165
2279
  if (routeDecision) applyFirstDecision(routeDecision, output);
2166
2280
  }
2167
2281
  if (extAfter.output) {
2282
+ const hookHasRoute = extAfter.output.route !== void 0;
2168
2283
  output = extAfter.output;
2284
+ if (hookHasRoute) {
2285
+ routeDecision = resolveHookRouteDecision(output, registry);
2286
+ } else if (phase.run || phase.factory) {
2287
+ output = { ...output, route: "stop" };
2288
+ routeDecision = void 0;
2289
+ } else {
2290
+ routeDecision = modelRouteDecision;
2291
+ if (routeDecision) applyFirstDecision(routeDecision, output);
2292
+ }
2169
2293
  }
2170
2294
  }
2171
- const routeToolAvailable = phaseContext.tools.some((tool) => tool.name === PhaseRouteTool);
2172
- const routeRequired = !phase.target && !phase.run && !phase.factory && routeToolAvailable && registry.phases.size > 1;
2173
- if (routeRequired && !routeDecision) {
2174
- if (config.waitForInput) {
2175
- const preAbort = LoopGuard.checkAbort(config.signal);
2176
- if (preAbort.stopReason !== "none") {
2177
- removePhaseMessage(config.context.messages, previousPhaseMsgId);
2178
- removePhaseMessage(config.context.messages, previousPhaseInputMsgId);
2179
- previousPhaseMsgId = void 0;
2180
- previousPhaseInputMsgId = void 0;
2181
- return completeRun(config, state, createOutcome.aborted());
2182
- }
2183
- const requestedAt = createTimestamp();
2184
- const inputRequest = {
2185
- phase: currentPhaseId,
2186
- prompt: output.message,
2187
- requestedAt
2188
- };
2189
- state.status = "suspended";
2190
- state.continuation = {
2191
- isContinuing,
2192
- previousPayload,
2193
- previousResults: previousResults.map((result) => ({ ...result })),
2194
- pendingInstruction,
2195
- previousPhaseMessageId: previousPhaseMsgId
2196
- };
2197
- const userMessages = await config.waitForInput(state, inputRequest);
2198
- state.status = "running";
2199
- const abortResult2 = LoopGuard.checkAbort(config.signal);
2200
- if (abortResult2.stopReason !== "none") {
2201
- removePhaseMessage(config.context.messages, previousPhaseMsgId);
2202
- removePhaseMessage(config.context.messages, previousPhaseInputMsgId);
2203
- previousPhaseMsgId = void 0;
2204
- previousPhaseInputMsgId = void 0;
2205
- return completeRun(config, state, createOutcome.aborted());
2206
- }
2207
- for (const message of userMessages) {
2208
- config.context.messages.push(message);
2209
- }
2295
+ if (phase.run || phase.factory) {
2296
+ if (output.route === void 0 && !routeDecision) output = { ...output, route: "stop" };
2297
+ } else if (!phase.target && output.route === void 0 && !routeDecision) {
2298
+ const input = await awaitInput(output.message, true);
2299
+ if (input?.type === "completed") return input.result;
2300
+ if (input?.type === "continued") {
2210
2301
  isContinuing = true;
2211
2302
  continue;
2212
2303
  }
@@ -2220,7 +2311,7 @@ async function runPhaseLoop(config, state, registry) {
2220
2311
  isContinuing = true;
2221
2312
  continue;
2222
2313
  }
2223
- if (routeDecision && routeDecision.decision.length > 1) {
2314
+ if (routeDecision && routeDecision.requestedCount > 1) {
2224
2315
  const contextSnapshot = snapshotMessages(config.context.messages);
2225
2316
  const parallelTasks = /* @__PURE__ */ new Map();
2226
2317
  const instanceIds = buildInstanceIds(routeDecision.decision.map((t) => t.phase));
@@ -2239,6 +2330,7 @@ async function runPhaseLoop(config, state, registry) {
2239
2330
  registry,
2240
2331
  pt,
2241
2332
  payload,
2333
+ routeDecision.instruction,
2242
2334
  context,
2243
2335
  availablePhases,
2244
2336
  instanceId,
@@ -2251,7 +2343,7 @@ async function runPhaseLoop(config, state, registry) {
2251
2343
  }
2252
2344
  const successfulResults = await waitForBackgroundTasks(parallelTasks);
2253
2345
  previousResults = successfulResults.map((r) => ({ name: r.instanceId, output: r.payload }));
2254
- pendingInstruction = routeDecision.instruction;
2346
+ pendingInstruction = void 0;
2255
2347
  const entryPhaseId = phase.target ?? registry.entryPhaseId;
2256
2348
  if (entryPhaseId === "stop") {
2257
2349
  removePhaseMessage(config.context.messages, previousPhaseMsgId);
@@ -2294,6 +2386,7 @@ async function runPhaseLoop(config, state, registry) {
2294
2386
  });
2295
2387
  previousPayload = output.payload;
2296
2388
  previousResults = output.payload !== void 0 ? [{ name: phase.name, output: output.payload }] : [];
2389
+ pendingInstruction = phase.target ? void 0 : routeDecision?.instruction;
2297
2390
  currentPhaseId = targetPhaseId;
2298
2391
  }
2299
2392
  throw new Error("Phase machine exited without a stop or abort transition.");
@@ -2548,10 +2641,10 @@ function createPhaseExecution(config, state, phase, messageManager, toolExecutio
2548
2641
  }
2549
2642
  };
2550
2643
  }
2551
- async function executeParallelPhase(config, state, registry, phase, payload, context, availablePhases, instanceId, groupId, index, count, sourcePhaseId) {
2644
+ async function executeParallelPhase(config, state, registry, phase, payload, instruction, context, availablePhases, instanceId, groupId, index, count, sourcePhaseId) {
2552
2645
  const messages = [...context];
2553
2646
  const allTools = buildToolsWithRouting(config, availablePhases);
2554
- const phaseTools = selectPhaseTools(allTools, phase.tools);
2647
+ const phaseTools = selectPhaseTools(allTools, phase.tools, false);
2555
2648
  const phaseSkills = mergeSkills(config.context.skills, phase.skills);
2556
2649
  const systemPrompt = config.context.systemPrompt;
2557
2650
  const phaseContext = {
@@ -2578,7 +2671,10 @@ async function executeParallelPhase(config, state, registry, phase, payload, con
2578
2671
  const messageManager = createMessageManager({ messages }, config.onMessage, config.onMessageDelta);
2579
2672
  const phaseMsgId = injectPhaseContent(
2580
2673
  phase,
2581
- { results: payload !== void 0 ? [{ name: sourcePhaseId, output: payload }] : [] },
2674
+ {
2675
+ instruction,
2676
+ results: payload !== void 0 ? [{ name: sourcePhaseId, output: payload }] : []
2677
+ },
2582
2678
  messages
2583
2679
  );
2584
2680
  const toolExecutionManager = createToolExecutionManager();
@@ -2590,8 +2686,8 @@ async function executeParallelPhase(config, state, registry, phase, payload, con
2590
2686
  const resultPayload = decision?.decision[0]?.payload !== void 0 ? normalizePayload(decision.decision[0].payload) : output.payload;
2591
2687
  return { instanceId, phaseId: phase.name, payload: resultPayload, content: output.message };
2592
2688
  }
2593
- function selectPhaseTools(candidates, names) {
2594
- const routing = candidates.filter(({ name }) => name === PhaseRouteTool);
2689
+ function selectPhaseTools(candidates, names, includeRouting = true) {
2690
+ const routing = includeRouting ? candidates.filter(({ name }) => name === PhaseRouteTool) : [];
2595
2691
  const ordinary = candidates.filter(({ name }) => name !== PhaseRouteTool);
2596
2692
  return [...selectNamedResources(ordinary, names, "Tool"), ...routing];
2597
2693
  }
@@ -2895,7 +2991,7 @@ function normalizeUserInput(input) {
2895
2991
  return normalized;
2896
2992
  }
2897
2993
  function isAssistantMessage(value) {
2898
- return isRecord3(value) && hasOnlyKeys(value, ["id", "agentId", "runId", "role", "content", "metadata", "sequenceWithinRun", "createdAt", "interrupted"]) && typeof value.id === "string" && typeof value.agentId === "string" && typeof value.runId === "string" && value.role === "assistant" && Number.isInteger(value.sequenceWithinRun) && value.sequenceWithinRun >= 0 && typeof value.createdAt === "string" && isAssistantContent(value.content) && (value.metadata === void 0 || isMetadata(value.metadata)) && (value.interrupted === void 0 || typeof value.interrupted === "boolean");
2994
+ return isRecord3(value) && hasOnlyKeys(value, ["id", "agentId", "runId", "messageRevision", "role", "content", "metadata", "sequenceWithinRun", "createdAt", "interrupted"]) && typeof value.id === "string" && typeof value.agentId === "string" && typeof value.runId === "string" && value.role === "assistant" && (value.messageRevision === void 0 || Number.isInteger(value.messageRevision) && value.messageRevision >= 0) && Number.isInteger(value.sequenceWithinRun) && value.sequenceWithinRun >= 0 && typeof value.createdAt === "string" && isAssistantContent(value.content) && (value.metadata === void 0 || isMetadata(value.metadata)) && (value.interrupted === void 0 || typeof value.interrupted === "boolean");
2899
2995
  }
2900
2996
  function isToolResult(value) {
2901
2997
  if (!isRecord3(value) || !isJsonValue(value.content) || typeof value.ok !== "boolean") return false;
@@ -3040,6 +3136,7 @@ function validateConfigResolution(agentId, value) {
3040
3136
  throw new Error(`Config Provider returned an invalid resolution for Agent ${agentId}.`);
3041
3137
  }
3042
3138
  function snapshotConfig(config) {
3139
+ validateMaxAttempts(config.maxAttempts);
3043
3140
  if (isAgentConfiguration(config)) return snapshotConfiguration(config);
3044
3141
  const definition = Object.freeze({
3045
3142
  ...config.definition,
@@ -3193,6 +3290,7 @@ var ConfigCommandService = class {
3193
3290
  const reserved = await this.store.reserveAgent({
3194
3291
  idempotencyKey: input.idempotencyKey,
3195
3292
  ...input.metadata === void 0 ? {} : { metadata: input.metadata },
3293
+ ...input.historySeed === void 0 ? {} : { historySeed: input.historySeed },
3196
3294
  configIdentity: input.config.identity
3197
3295
  });
3198
3296
  if (reserved.activatedAt && reserved.currentConfigToken) return reserved.id;
@@ -4999,6 +5097,7 @@ function adaptExtensionTool2(input) {
4999
5097
 
5000
5098
  // src/runtime/configuration-snapshot.ts
5001
5099
  function resolveConfigurationSnapshot(registry, input) {
5100
+ validateMaxAttempts(input.maxAttempts);
5002
5101
  const resolved = registry.resolveView(input.resourceView);
5003
5102
  const base = resolved.agents.find(({ name }) => name === input.definition.name);
5004
5103
  if (!base) {
@@ -5179,6 +5278,7 @@ var AgentRuntime = class _AgentRuntime {
5179
5278
  return this.commands.createAgent({
5180
5279
  config,
5181
5280
  ...options.metadata === void 0 ? {} : { metadata: options.metadata },
5281
+ ...options.historySeed === void 0 ? {} : { historySeed: options.historySeed },
5182
5282
  idempotencyKey: options.idempotencyKey ?? crypto.randomUUID()
5183
5283
  });
5184
5284
  }
@@ -5196,6 +5296,23 @@ var AgentRuntime = class _AgentRuntime {
5196
5296
  }
5197
5297
  await this.owned.deleteAgent(input);
5198
5298
  }
5299
+ async revise(agentId, input) {
5300
+ this.assertOpen();
5301
+ const result = await this.owned.reviseMessage({ agentId, ...input });
5302
+ for (const runId of result.invalidatedRunIds) {
5303
+ const execution = this.executions.get(runId);
5304
+ if (execution) {
5305
+ execution.controller.abort();
5306
+ this.transientEvents.clear(runId, execution.executionId);
5307
+ }
5308
+ }
5309
+ void this.pump();
5310
+ return result;
5311
+ }
5312
+ async compact(input = {}) {
5313
+ this.assertOpen();
5314
+ return this.owned.compact(input);
5315
+ }
5199
5316
  async start(agentId, input, options) {
5200
5317
  this.assertOpen();
5201
5318
  const agent = await this.requireAgent(agentId);
@@ -5249,6 +5366,10 @@ var AgentRuntime = class _AgentRuntime {
5249
5366
  this.assertOpen();
5250
5367
  return this.owned.snapshotRun(runId);
5251
5368
  }
5369
+ async history(agentId) {
5370
+ this.assertOpen();
5371
+ return this.owned.history(agentId);
5372
+ }
5252
5373
  async respond(runId, input) {
5253
5374
  this.assertOpen();
5254
5375
  const snapshot = await this.owned.snapshotRun(runId);
@@ -5762,6 +5883,11 @@ function normalizePhaseRegistry(registry) {
5762
5883
  if (registry?.phases.has(DEFAULT_PHASE_ID) && registry.phases.get(DEFAULT_PHASE_ID)?.filePath !== "") {
5763
5884
  throw new TypeError(`Configured Phase collides with Rowan built-in Phase "${DEFAULT_PHASE_ID}".`);
5764
5885
  }
5886
+ for (const [name] of registry?.phases ?? []) {
5887
+ if (name === "stop" || name === "continue") {
5888
+ throw new TypeError(`Configured Phase name "${name}" is reserved by Rowan routing controls.`);
5889
+ }
5890
+ }
5765
5891
  return {
5766
5892
  phases: new Map([
5767
5893
  [DEFAULT_PHASE_ID, createDefaultPhase()],
@@ -5793,6 +5919,7 @@ function deferred2() {
5793
5919
  }
5794
5920
 
5795
5921
  // src/runtime/durable-store.ts
5922
+ import { createHash } from "crypto";
5796
5923
  var InMemoryStore = class _InMemoryStore {
5797
5924
  incarnation;
5798
5925
  agents = /* @__PURE__ */ new Map();
@@ -5802,12 +5929,14 @@ var InMemoryStore = class _InMemoryStore {
5802
5929
  events = [];
5803
5930
  idempotency = /* @__PURE__ */ new Map();
5804
5931
  operationReceipts = /* @__PURE__ */ new Map();
5932
+ historySeeds = /* @__PURE__ */ new Map();
5805
5933
  consumerCheckpoints = /* @__PURE__ */ new Map();
5806
5934
  nextAgentSequence = /* @__PURE__ */ new Map();
5807
5935
  nextReadySequence = /* @__PURE__ */ new Map();
5808
5936
  owner;
5809
5937
  ownerEpoch = 0;
5810
5938
  eventSequence = 0;
5939
+ retentionFloor = 1;
5811
5940
  constructor(options = {}) {
5812
5941
  this.incarnation = options.incarnation ?? createId("store");
5813
5942
  }
@@ -5820,10 +5949,12 @@ var InMemoryStore = class _InMemoryStore {
5820
5949
  store.events.push(...clone(state.events));
5821
5950
  for (const [key, receipt] of state.idempotency) store.idempotency.set(key, clone(receipt));
5822
5951
  for (const [key, receipt] of state.operationReceipts) store.operationReceipts.set(key, clone(receipt));
5952
+ for (const [agentId, messages] of state.historySeeds ?? []) store.historySeeds.set(agentId, clone(messages));
5823
5953
  for (const [consumerId, cursor] of state.consumerCheckpoints ?? []) store.consumerCheckpoints.set(consumerId, cursor);
5824
5954
  for (const [agentId, sequence] of state.nextAgentSequence) store.nextAgentSequence.set(agentId, sequence);
5825
5955
  for (const [agentId, sequence] of state.nextReadySequence) store.nextReadySequence.set(agentId, sequence);
5826
5956
  store.eventSequence = state.eventSequence;
5957
+ store.retentionFloor = Math.max(1, state.retentionFloor ?? 1);
5827
5958
  store.ownerEpoch = Math.max(
5828
5959
  0,
5829
5960
  ...state.runs.map((run) => run.execution?.ownerEpoch ?? 0)
@@ -5840,7 +5971,9 @@ var InMemoryStore = class _InMemoryStore {
5840
5971
  events: this.events,
5841
5972
  idempotency: [...this.idempotency.entries()],
5842
5973
  operationReceipts: [...this.operationReceipts.entries()],
5974
+ historySeeds: [...this.historySeeds.entries()],
5843
5975
  consumerCheckpoints: [...this.consumerCheckpoints.entries()],
5976
+ retentionFloor: this.retentionFloor,
5844
5977
  nextAgentSequence: [...this.nextAgentSequence.entries()],
5845
5978
  nextReadySequence: [...this.nextReadySequence.entries()],
5846
5979
  eventSequence: this.eventSequence
@@ -5916,7 +6049,8 @@ var InMemoryStore = class _InMemoryStore {
5916
6049
  const scope = createIdempotencyScope("create_agent", input.idempotencyKey);
5917
6050
  const payload = canonicalJson({
5918
6051
  ...input.metadata === void 0 ? {} : { metadata: input.metadata },
5919
- ...input.configIdentity === void 0 ? {} : { configIdentity: input.configIdentity }
6052
+ ...input.configIdentity === void 0 ? {} : { configIdentity: input.configIdentity },
6053
+ ...input.historySeed === void 0 ? {} : { historySeed: input.historySeed }
5920
6054
  });
5921
6055
  const replay = this.replay(scope, payload);
5922
6056
  if (replay) return clone(replay);
@@ -5928,6 +6062,7 @@ var InMemoryStore = class _InMemoryStore {
5928
6062
  updatedAt: timestamp
5929
6063
  };
5930
6064
  this.agents.set(agent.id, agent);
6065
+ if (input.historySeed && input.historySeed.length > 0) this.historySeeds.set(agent.id, materializeHistorySeed(agent.id, input.historySeed));
5931
6066
  this.nextAgentSequence.set(agent.id, 0);
5932
6067
  this.nextReadySequence.set(agent.id, 0);
5933
6068
  this.writeReceipt(scope, payload, agent);
@@ -5975,6 +6110,7 @@ var InMemoryStore = class _InMemoryStore {
5975
6110
  this.agents.delete(agent.id);
5976
6111
  this.nextAgentSequence.delete(agent.id);
5977
6112
  this.nextReadySequence.delete(agent.id);
6113
+ this.historySeeds.delete(agent.id);
5978
6114
  for (const run of runs) this.runs.delete(run.id);
5979
6115
  for (const [messageId, message] of this.messages) {
5980
6116
  if (runIds.has(message.runId)) this.messages.delete(messageId);
@@ -5992,6 +6128,140 @@ var InMemoryStore = class _InMemoryStore {
5992
6128
  if (key.includes(String(agent.id)) || [...runIds].some((runId) => key.includes(String(runId)))) this.operationReceipts.delete(key);
5993
6129
  }
5994
6130
  }
6131
+ reviseMessage(lease, input) {
6132
+ this.assertOwner(lease);
6133
+ if (!Number.isInteger(input.expectedMessageRevision) || input.expectedMessageRevision < 0) {
6134
+ throw new TypeError("expectedMessageRevision must be a non-negative integer");
6135
+ }
6136
+ if (typeof input.operationId !== "string" || input.operationId.length === 0) throw new TypeError("operationId must be non-empty");
6137
+ const normalized = normalizeUserInput({ content: input.content });
6138
+ const operationKey = `revise_message:${input.agentId}:${input.operationId}`;
6139
+ const operationPayload = canonicalJson([
6140
+ input.messageId,
6141
+ input.expectedMessageRevision,
6142
+ normalized,
6143
+ input.effectDigestConfirmation ?? null
6144
+ ]);
6145
+ const replay = this.replayOperation(operationKey, operationPayload);
6146
+ if (replay) return clone(replay);
6147
+ const agent = this.requireAgent(input.agentId);
6148
+ const seed = this.historySeeds.get(agent.id) ?? [];
6149
+ const original = this.messages.get(input.messageId) ?? seed.find(({ id }) => id === input.messageId);
6150
+ if (!original || original.agentId !== agent.id || original.role !== "user") {
6151
+ throw new RuntimeError("message_revision_conflict", {
6152
+ agentId: agent.id,
6153
+ messageId: input.messageId,
6154
+ expected: input.expectedMessageRevision,
6155
+ actual: -1
6156
+ });
6157
+ }
6158
+ const actualRevision = original.messageRevision ?? 0;
6159
+ if (actualRevision !== input.expectedMessageRevision) {
6160
+ throw new RuntimeError("message_revision_conflict", {
6161
+ agentId: agent.id,
6162
+ messageId: input.messageId,
6163
+ expected: input.expectedMessageRevision,
6164
+ actual: actualRevision
6165
+ });
6166
+ }
6167
+ const targetRun = this.runs.get(original.runId);
6168
+ const isSeedMessage = !targetRun && seed.some(({ id }) => id === original.id);
6169
+ if (!targetRun && !isSeedMessage) {
6170
+ throw new RuntimeError("message_revision_conflict", {
6171
+ agentId: agent.id,
6172
+ messageId: input.messageId,
6173
+ expected: input.expectedMessageRevision,
6174
+ actual: actualRevision
6175
+ });
6176
+ }
6177
+ const targetAgentSequence = targetRun?.agentSequence ?? -1;
6178
+ const affectedRuns = [...this.runs.values()].filter((run) => run.agentId === agent.id && run.agentSequence >= targetAgentSequence).sort((left, right) => left.agentSequence - right.agentSequence);
6179
+ const affectedRunIds = affectedRuns.map((run) => run.id);
6180
+ const affectedRunSet = new Set(affectedRunIds);
6181
+ const affectedTools = [...this.toolCalls.values()].filter((toolCall) => affectedRunSet.has(toolCall.runId)).sort((left, right) => String(left.id).localeCompare(String(right.id)));
6182
+ const effectDigest = affectedTools.length > 0 ? digestToolEffects(affectedTools) : void 0;
6183
+ if (effectDigest && input.effectDigestConfirmation !== effectDigest) {
6184
+ throw new RuntimeError("tool_effect_confirmation_required", {
6185
+ agentId: agent.id,
6186
+ messageId: input.messageId,
6187
+ effectDigest,
6188
+ toolCallIds: affectedTools.map((toolCall) => toolCall.id)
6189
+ });
6190
+ }
6191
+ const timestamp = createTimestamp();
6192
+ const replacementRun = {
6193
+ id: createId("run"),
6194
+ agentId: agent.id,
6195
+ agentSequence: this.nextSequence(agent.id),
6196
+ readySequence: this.nextReady(agent.id),
6197
+ revision: 0,
6198
+ state: "queued",
6199
+ input: targetInput(normalized, original.metadata),
6200
+ initialMessageId: original.id,
6201
+ ...(targetRun?.pinnedConfigToken ?? this.agents.get(agent.id)?.currentConfigToken) === void 0 ? {} : { pinnedConfigToken: targetRun?.pinnedConfigToken ?? this.agents.get(agent.id)?.currentConfigToken },
6202
+ createdAt: timestamp,
6203
+ updatedAt: timestamp
6204
+ };
6205
+ for (const run of affectedRuns) {
6206
+ const previousState = run.state;
6207
+ run.invalidatedBy = { messageId: original.id, messageRevision: actualRevision + 1 };
6208
+ if (run.state === "running" || run.state === "queued" || run.state === "input_required") {
6209
+ if (run.state === "running") {
6210
+ const openToolCalls = [...this.toolCalls.values()].filter((toolCall) => toolCall.runId === run.id && toolCall.executionId === run.execution?.executionId && (toolCall.state === "pending" || toolCall.state === "running"));
6211
+ for (const toolCall of openToolCalls) this.interruptToolCall(run, toolCall, "Run superseded by Message revision.");
6212
+ } else {
6213
+ delete run.openInputRequest;
6214
+ delete run.checkpoint;
6215
+ }
6216
+ delete run.execution;
6217
+ run.state = "cancelled";
6218
+ run.cancellationReason = "Run superseded by Message revision.";
6219
+ run.revision += 1;
6220
+ run.updatedAt = timestamp;
6221
+ this.appendTransition(run, previousState, "cancelled", void 0, void 0, void 0, void 0, run.cancellationReason);
6222
+ } else {
6223
+ run.revision += 1;
6224
+ run.updatedAt = timestamp;
6225
+ }
6226
+ }
6227
+ for (const [messageId, message] of this.messages) {
6228
+ if (!affectedRunSet.has(message.runId)) continue;
6229
+ if (message.runId !== original.runId || message.sequenceWithinRun > original.sequenceWithinRun) this.messages.delete(messageId);
6230
+ }
6231
+ if (isSeedMessage) {
6232
+ const targetIndex = seed.findIndex(({ id }) => id === original.id);
6233
+ if (targetIndex >= 0) this.historySeeds.set(agent.id, seed.slice(0, targetIndex));
6234
+ }
6235
+ const revisedMessage = {
6236
+ ...clone(original),
6237
+ runId: replacementRun.id,
6238
+ content: userInputContent(normalized),
6239
+ messageRevision: actualRevision + 1,
6240
+ sequenceWithinRun: 0,
6241
+ createdAt: timestamp
6242
+ };
6243
+ this.messages.set(revisedMessage.id, revisedMessage);
6244
+ this.runs.set(replacementRun.id, replacementRun);
6245
+ this.appendTransition(replacementRun, null, "queued");
6246
+ this.events.push({
6247
+ ...this.baseEvent(replacementRun),
6248
+ kind: "message_revised",
6249
+ message: revisedMessage,
6250
+ previousRevision: actualRevision,
6251
+ invalidatedRunIds: affectedRunIds,
6252
+ targetRunId: original.runId,
6253
+ cutoffSequenceWithinRun: original.sequenceWithinRun
6254
+ });
6255
+ const result = {
6256
+ message: clone(revisedMessage),
6257
+ replacementRun: clone(replacementRun),
6258
+ invalidatedRunIds: affectedRunIds,
6259
+ affectedToolCallIds: affectedTools.map((toolCall) => toolCall.id),
6260
+ ...effectDigest === void 0 ? {} : { effectDigest }
6261
+ };
6262
+ this.writeOperationReceipt(operationKey, operationPayload, result);
6263
+ return clone(result);
6264
+ }
5995
6265
  createRun(lease, input) {
5996
6266
  this.assertOwner(lease);
5997
6267
  this.requireAgent(input.agentId);
@@ -6039,7 +6309,7 @@ var InMemoryStore = class _InMemoryStore {
6039
6309
  }
6040
6310
  if (!run.pinnedConfigToken && input.configToken) run.pinnedConfigToken = input.configToken;
6041
6311
  let message;
6042
- if (!run.checkpoint) {
6312
+ if (!run.checkpoint && !run.initialMessageId) {
6043
6313
  const userInput = normalizeUserInput(run.input);
6044
6314
  message = {
6045
6315
  id: input.messageId ?? createId("msg"),
@@ -6064,7 +6334,7 @@ var InMemoryStore = class _InMemoryStore {
6064
6334
  run.updatedAt = createTimestamp();
6065
6335
  if (message) this.appendMessage(run, message);
6066
6336
  this.appendTransition(run, "queued", "running");
6067
- const result = { run: clone(run), execution: clone(execution), history: this.history(run.agentId, run.agentSequence) };
6337
+ const result = { run: clone(run), execution: clone(execution), history: this.activeHistory(run.agentId, run.agentSequence) };
6068
6338
  this.writeOperationReceipt(operationKey, operationPayload, result);
6069
6339
  return result;
6070
6340
  }
@@ -6459,7 +6729,10 @@ var InMemoryStore = class _InMemoryStore {
6459
6729
  }
6460
6730
  listEvents(lease, input = {}) {
6461
6731
  this.assertOwner(lease);
6462
- const after = input.after ? this.parseCursor(input.after) : 0;
6732
+ const after = input.after ? this.parseCursor(input.after) : this.retentionFloor - 1;
6733
+ if (input.after && after < this.retentionFloor - 1) {
6734
+ throw new RuntimeError("invalid_cursor", { cursorType: "event", reason: "expired" });
6735
+ }
6463
6736
  if (after > this.eventSequence) {
6464
6737
  throw new RuntimeError("invalid_cursor", { cursorType: "event", reason: "beyond_waterline" });
6465
6738
  }
@@ -6476,11 +6749,72 @@ var InMemoryStore = class _InMemoryStore {
6476
6749
  advanceConsumerCheckpoint(lease, input) {
6477
6750
  this.assertOwner(lease);
6478
6751
  const next = this.parseCursor(input.cursor);
6752
+ if (next < this.retentionFloor - 1) {
6753
+ throw new RuntimeError("invalid_cursor", { cursorType: "event", reason: "expired" });
6754
+ }
6479
6755
  if (next > this.eventSequence) throw new RuntimeError("invalid_cursor", { cursorType: "event", reason: "beyond_waterline" });
6480
6756
  const previous = this.consumerCheckpoints.get(input.consumerId);
6481
6757
  if (previous !== void 0 && next <= this.parseCursor(previous)) return;
6482
6758
  this.consumerCheckpoints.set(input.consumerId, input.cursor);
6483
6759
  }
6760
+ compact(lease, input = {}) {
6761
+ this.assertOwner(lease);
6762
+ const retentionMs = input.retentionMs ?? 7 * 24 * 60 * 60 * 1e3;
6763
+ if (!Number.isFinite(retentionMs) || retentionMs < 0) {
6764
+ throw new TypeError("retentionMs must be a non-negative finite number");
6765
+ }
6766
+ const now = input.now === void 0 ? Date.now() : Date.parse(input.now);
6767
+ if (!Number.isFinite(now)) throw new TypeError("now must be an ISO timestamp");
6768
+ const cutoff = now - retentionMs;
6769
+ const checkpointSequences = [...this.consumerCheckpoints.values()].map((cursor) => this.parseCursor(cursor));
6770
+ const consumerFloor = checkpointSequences.length > 0 ? Math.min(...checkpointSequences) : this.eventSequence;
6771
+ const blockedRunIds = /* @__PURE__ */ new Set();
6772
+ for (const run of this.runs.values()) {
6773
+ if (run.state === "running") blockedRunIds.add(run.id);
6774
+ }
6775
+ for (const tool of this.toolCalls.values()) {
6776
+ if (tool.state === "pending" || tool.state === "running" || tool.state === "indeterminate") {
6777
+ blockedRunIds.add(tool.runId);
6778
+ }
6779
+ }
6780
+ let deleteThrough = this.retentionFloor - 1;
6781
+ for (const event of this.events) {
6782
+ const sequence = this.parseCursor(event.cursor);
6783
+ if (sequence < this.retentionFloor) continue;
6784
+ if (sequence > consumerFloor) break;
6785
+ if (Date.parse(event.createdAt) > cutoff || blockedRunIds.has(event.runId)) break;
6786
+ const eventRun = this.runs.get(event.runId);
6787
+ if (!eventRun || eventRun.invalidatedBy === void 0 || !["completed", "failed", "cancelled"].includes(eventRun.state) || Date.parse(eventRun.updatedAt) > cutoff) break;
6788
+ deleteThrough = sequence;
6789
+ }
6790
+ const deletedEvents = deleteThrough >= this.retentionFloor ? this.events.filter((event) => this.parseCursor(event.cursor) <= deleteThrough) : [];
6791
+ if (deleteThrough >= this.retentionFloor) {
6792
+ this.events.splice(0, this.events.length, ...this.events.filter((event) => this.parseCursor(event.cursor) > deleteThrough));
6793
+ this.retentionFloor = deleteThrough + 1;
6794
+ for (const [consumerId, cursor] of this.consumerCheckpoints) {
6795
+ if (this.parseCursor(cursor) < this.retentionFloor - 1) {
6796
+ this.consumerCheckpoints.delete(consumerId);
6797
+ }
6798
+ }
6799
+ }
6800
+ const activeMessageRunIds = new Set([...this.messages.values()].map((message) => message.runId));
6801
+ const deletableRuns = [...this.runs.values()].filter((run) => run.invalidatedBy !== void 0 && ["completed", "failed", "cancelled"].includes(run.state) && Date.parse(run.updatedAt) <= cutoff && !activeMessageRunIds.has(run.id) && !blockedRunIds.has(run.id) && this.events.filter((event) => event.runId === run.id).every((event) => this.parseCursor(event.cursor) <= consumerFloor && Date.parse(event.createdAt) <= cutoff));
6802
+ const deletedRunIds = deletableRuns.map((run) => run.id);
6803
+ const deletedRunSet = new Set(deletedRunIds);
6804
+ const deletedTools = [...this.toolCalls.values()].filter((tool) => deletedRunSet.has(tool.runId));
6805
+ for (const runId of deletedRunIds) this.runs.delete(runId);
6806
+ for (const [messageId, message] of this.messages) {
6807
+ if (deletedRunSet.has(message.runId)) this.messages.delete(messageId);
6808
+ }
6809
+ for (const tool of deletedTools) this.toolCalls.delete(tool.id);
6810
+ return {
6811
+ deletedRunIds,
6812
+ deletedToolCallIds: deletedTools.map((tool) => tool.id),
6813
+ deletedEventCount: deletedEvents.length,
6814
+ retentionFloor: `${this.incarnation}:${this.retentionFloor}`,
6815
+ ...deletedRunIds.length === 0 && deletedEvents.length === 0 ? { skipped: "no_eligible_events" } : {}
6816
+ };
6817
+ }
6484
6818
  replay(scope, payload) {
6485
6819
  const key = encodeIdempotencyScope(this.incarnation, scope);
6486
6820
  const receipt = this.idempotency.get(key);
@@ -6578,8 +6912,16 @@ var InMemoryStore = class _InMemoryStore {
6578
6912
  messagesForRun(runId) {
6579
6913
  return [...this.messages.values()].filter((message) => message.runId === runId).sort((a, b) => a.sequenceWithinRun - b.sequenceWithinRun);
6580
6914
  }
6581
- history(agentId, beforeSequence) {
6582
- return clone([...this.runs.values()].filter((run) => run.agentId === agentId && run.agentSequence <= beforeSequence).sort((a, b) => a.agentSequence - b.agentSequence).flatMap((run) => this.messagesForRun(run.id)));
6915
+ history(lease, agentId) {
6916
+ this.assertOwner(lease);
6917
+ this.requireAgent(agentId);
6918
+ return this.activeHistory(agentId, Number.MAX_SAFE_INTEGER);
6919
+ }
6920
+ activeHistory(agentId, beforeSequence) {
6921
+ return clone([
6922
+ ...this.historySeeds.get(agentId) ?? [],
6923
+ ...[...this.runs.values()].filter((run) => run.agentId === agentId && run.agentSequence <= beforeSequence).sort((a, b) => a.agentSequence - b.agentSequence).flatMap((run) => this.messagesForRun(run.id))
6924
+ ].map((message) => ({ ...message, messageRevision: message.messageRevision ?? 0 })));
6583
6925
  }
6584
6926
  appendMessage(run, message) {
6585
6927
  this.events.push(this.baseEvent(run, {
@@ -6650,6 +6992,12 @@ var MemoryOwnedStore = class {
6650
6992
  async deleteAgent(input) {
6651
6993
  this.store.deleteAgent(this.lease, input);
6652
6994
  }
6995
+ async reviseMessage(input) {
6996
+ return this.store.reviseMessage(this.lease, input);
6997
+ }
6998
+ async compact(input) {
6999
+ return this.store.compact(this.lease, input);
7000
+ }
6653
7001
  async createRun(input) {
6654
7002
  return this.store.createRun(this.lease, input);
6655
7003
  }
@@ -6686,6 +7034,9 @@ var MemoryOwnedStore = class {
6686
7034
  async snapshotRun(runId) {
6687
7035
  return this.store.snapshotRun(this.lease, runId);
6688
7036
  }
7037
+ async history(agentId) {
7038
+ return this.store.history(this.lease, agentId);
7039
+ }
6689
7040
  async listAgents() {
6690
7041
  return this.store.listAgents(this.lease);
6691
7042
  }
@@ -6733,6 +7084,41 @@ function userInputContent(input) {
6733
7084
  function userInputMetadata(input) {
6734
7085
  return typeof input === "string" ? void 0 : input.metadata;
6735
7086
  }
7087
+ function targetInput(input, metadata) {
7088
+ const content = typeof input === "string" ? input : input.content;
7089
+ return metadata === void 0 ? { content } : { content, metadata: clone(metadata) };
7090
+ }
7091
+ function materializeHistorySeed(agentId, source) {
7092
+ const seedRunId = createId("seed");
7093
+ const toolIds = /* @__PURE__ */ new Map();
7094
+ return source.map((message, sequenceWithinRun) => {
7095
+ const content = remapSeedContent(message.content, toolIds);
7096
+ return {
7097
+ ...clone(message),
7098
+ id: createId("msg"),
7099
+ agentId,
7100
+ runId: seedRunId,
7101
+ content,
7102
+ messageRevision: message.messageRevision ?? 0,
7103
+ sequenceWithinRun,
7104
+ createdAt: createTimestamp()
7105
+ };
7106
+ });
7107
+ }
7108
+ function remapSeedContent(content, toolIds) {
7109
+ if (typeof content === "string") return content;
7110
+ return content.map((part) => {
7111
+ if (part.type === "tool_use" || part.type === "tool_result") {
7112
+ const toolCallId = toolIds.get(part.toolCallId) ?? createId("tool");
7113
+ toolIds.set(part.toolCallId, toolCallId);
7114
+ return part.type === "tool_result" ? { ...part, toolCallId, result: clone(part.result) } : { ...part, toolCallId };
7115
+ }
7116
+ return clone(part);
7117
+ });
7118
+ }
7119
+ function digestToolEffects(toolCalls) {
7120
+ return createHash("sha256").update(canonicalJson(toolCalls)).digest("hex");
7121
+ }
6736
7122
  function assertToolValue(value, argument) {
6737
7123
  assertUtf8ByteLimit(canonicalJson(value), TOOL_VALUE_JSON_BYTES, argument);
6738
7124
  }
@@ -6740,8 +7126,9 @@ function assertToolValue(value, argument) {
6740
7126
  // src/runtime/sqlite-durable-store.ts
6741
7127
  import { Database } from "bun:sqlite";
6742
7128
  var SCHEMA_ID = "rowan.agent.runtime";
6743
- var SCHEMA_VERSION = "1";
7129
+ var SCHEMA_VERSION = "2";
6744
7130
  var SCHEMA_VALUE = `${SCHEMA_ID}:${SCHEMA_VERSION}`;
7131
+ var RETENTION_FLOOR_KEY = "retention_floor";
6745
7132
  var STATE_ROW = 1;
6746
7133
  var CURRENT_TABLES = /* @__PURE__ */ new Set([
6747
7134
  "runtime_meta",
@@ -6909,6 +7296,9 @@ var SqliteStore = class {
6909
7296
  async deleteAgent(lease, input) {
6910
7297
  return this.invoke(lease, (store, current) => store.deleteAgent(current, input));
6911
7298
  }
7299
+ async reviseMessage(lease, input) {
7300
+ return this.invoke(lease, (store, current) => store.reviseMessage(current, input));
7301
+ }
6912
7302
  async createRun(lease, input) {
6913
7303
  return this.invoke(lease, (store, current) => store.createRun(current, input));
6914
7304
  }
@@ -6945,6 +7335,9 @@ var SqliteStore = class {
6945
7335
  async snapshotRun(lease, runId) {
6946
7336
  return this.invoke(lease, (store, current) => store.snapshotRun(current, runId), false);
6947
7337
  }
7338
+ async history(lease, agentId) {
7339
+ return this.invoke(lease, (store, current) => store.history(current, agentId), false);
7340
+ }
6948
7341
  async listAgents(lease) {
6949
7342
  return this.invoke(lease, (store, current) => store.listAgents(current), false);
6950
7343
  }
@@ -6955,15 +7348,19 @@ var SqliteStore = class {
6955
7348
  this.assertOpen();
6956
7349
  return this.readTransaction(() => {
6957
7350
  this.requireMatchingOwner(lease, true);
6958
- const after = input.after ? parseEventCursor(lease, input.after) : 0;
7351
+ const retentionRow = this.database.query(
7352
+ "SELECT value FROM runtime_meta WHERE key = ?"
7353
+ ).get(RETENTION_FLOOR_KEY);
7354
+ const retentionFloor = Math.max(1, Number(retentionRow?.value ?? "1"));
7355
+ const after = input.after ? parseEventCursor(lease, input.after) : retentionFloor - 1;
7356
+ if (input.after && after < retentionFloor - 1) {
7357
+ throw new RuntimeError("invalid_cursor", { cursorType: "event", reason: "expired" });
7358
+ }
6959
7359
  if (input.after) {
6960
7360
  const latest = this.database.query(
6961
- "SELECT payload_json FROM run_events ORDER BY sequence DESC LIMIT 1"
7361
+ "SELECT max(sequence) AS sequence FROM run_events"
6962
7362
  ).get();
6963
- const waterline = latest ? parseEventCursor(
6964
- lease,
6965
- JSON.parse(latest.payload_json).cursor
6966
- ) : 0;
7363
+ const waterline = latest.sequence ?? 0;
6967
7364
  if (after > waterline) {
6968
7365
  throw new RuntimeError("invalid_cursor", { cursorType: "event", reason: "beyond_waterline" });
6969
7366
  }
@@ -6981,6 +7378,15 @@ var SqliteStore = class {
6981
7378
  async advanceConsumerCheckpoint(lease, input) {
6982
7379
  return this.invoke(lease, (store, current) => store.advanceConsumerCheckpoint(current, input));
6983
7380
  }
7381
+ async compact(lease, input = {}) {
7382
+ this.assertOpen();
7383
+ const result = this.invoke(lease, (store, current) => store.compact(current, input));
7384
+ if (result.deletedEventCount > 0 || result.deletedRunIds.length > 0) {
7385
+ this.database.run("PRAGMA wal_checkpoint(PASSIVE)");
7386
+ this.database.run("PRAGMA incremental_vacuum");
7387
+ }
7388
+ return result;
7389
+ }
6984
7390
  async renewOwner(lease, leaseMs) {
6985
7391
  this.assertOpen();
6986
7392
  assertOwnerInput(lease.ownerId, leaseMs);
@@ -7033,6 +7439,7 @@ var SqliteStore = class {
7033
7439
  if (sql) this.database.run(sql);
7034
7440
  }
7035
7441
  this.database.run("INSERT INTO runtime_meta (key, value) VALUES (?, ?)", ["schema_version", SCHEMA_VALUE]);
7442
+ this.database.run("INSERT INTO runtime_meta (key, value) VALUES (?, ?)", [RETENTION_FLOOR_KEY, "1"]);
7036
7443
  const state = new InMemoryStore();
7037
7444
  this.database.run("INSERT INTO runtime_owner (singleton, epoch) VALUES (?, ?)", [STATE_ROW, 0]);
7038
7445
  this.database.run("INSERT INTO runtime_state (singleton, state_json) VALUES (?, ?)", [STATE_ROW, JSON.stringify(state.exportState())]);
@@ -7081,6 +7488,10 @@ var SqliteStore = class {
7081
7488
  }
7082
7489
  persistState(state) {
7083
7490
  this.writeState(state);
7491
+ this.database.run(
7492
+ "INSERT INTO runtime_meta (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value",
7493
+ [RETENTION_FLOOR_KEY, String(Math.max(1, state.retentionFloor ?? 1))]
7494
+ );
7084
7495
  for (const table of ["consumer_checkpoints", "idempotency", "run_events", "messages", "input_requests", "tool_calls", "runs", "agents"]) {
7085
7496
  this.database.run(`DELETE FROM ${table}`);
7086
7497
  }
@@ -7199,6 +7610,12 @@ var SqliteOwnedStore = class {
7199
7610
  deleteAgent(input) {
7200
7611
  return this.store.deleteAgent(this.lease, input);
7201
7612
  }
7613
+ reviseMessage(input) {
7614
+ return this.store.reviseMessage(this.lease, input);
7615
+ }
7616
+ compact(input) {
7617
+ return this.store.compact(this.lease, input);
7618
+ }
7202
7619
  createRun(input) {
7203
7620
  return this.store.createRun(this.lease, input);
7204
7621
  }
@@ -7235,6 +7652,9 @@ var SqliteOwnedStore = class {
7235
7652
  snapshotRun(runId) {
7236
7653
  return this.store.snapshotRun(this.lease, runId);
7237
7654
  }
7655
+ history(agentId) {
7656
+ return this.store.history(this.lease, agentId);
7657
+ }
7238
7658
  listAgents() {
7239
7659
  return this.store.listAgents(this.lease);
7240
7660
  }