stitchkit 0.111.3 → 0.112.1

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/CHANGELOG.md CHANGED
@@ -15,6 +15,22 @@ additive**; the first breaking change landed in 0.10.0. Grep the file for
15
15
 
16
16
  ## [Unreleased]
17
17
 
18
+ ## [0.112.1] - 2026-10-25
19
+
20
+ ### Fixed
21
+
22
+ - `stitchkit/agent-runtime` — an empty or control-only failed assistant now retains its non-success marker when explicitly included with `evidencePolicy.failedAssistant: 'assistant-marked'`. The default still omits failed evidence. The compaction guide explains the existing failed-prefix recovery policy, status-aware summarization, and why a closed tool exchange cannot settle an external effect. → ADR 0269
23
+ **Affects:** `stitchkit/agent-runtime` projectAgentHistory, projectAgentHistoryDetailed
24
+
25
+ ## [0.112.0] - 2026-10-24
26
+
27
+ ### ⚠️ Breaking changes
28
+
29
+ **Who must act:** scripts that combine named array options with bare positional arguments without an explicit `positionals` policy must name the scalar fields or declare their destinations. Flag-only calls and explicitly declared positional policies need no migration.
30
+
31
+ - `stitchkit/cli` — **array options require named arguments or an explicit positional policy.** Without a declared `positionals` policy, a named array option together with any bare positional is now a usage error before dispatch. Schema-order inference could mistake the array's remaining elements for unrelated optional fields and execute a different call. Before: `app resolve --references alpha.md beta.md gamma.md` could assign `beta.md` and `gamma.md` to other fields. After: `app resolve --references alpha.md --references beta.md --references gamma.md`, or `app resolve --references:='["alpha.md","beta.md","gamma.md"]'`. Name scalar fields with flags too, or declare their exact positional policy. Command help describes one element per option; only a declared trailing positional array is variadic. See [ADR 0268](docs/decisions/0268-named-cli-arrays-require-explicit-positional-destinations.md) and the [migration](docs/guide/upgrading.md).
32
+ **Affects:** `stitchkit/cli` createCli, parseCliArgs
33
+
18
34
  ## [0.111.3] - 2026-10-20
19
35
 
20
36
  ### Fixed
@@ -1 +1 @@
1
- {"version":3,"file":"history.d.ts","sourceRoot":"","sources":["../../src/agent-runtime/history.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,QAAQ,EAAE,KAAK,YAAY,EAAsB,MAAM,IAAI,CAAC;AAO1E,OAAO,KAAK,EAAE,YAAY,EAAE,gBAAgB,EAAyB,MAAM,WAAW,CAAC;AACvF,OAAO,EACL,KAAK,0BAA0B,EAEhC,MAAM,mBAAmB,CAAC;AAE3B,KAAK,QAAQ,GAAG,gBAAgB,CAAC,MAAM,CAAC,CAAC;AAEzC,MAAM,WAAW,6BAA6B;IAC5C,cAAc,CAAC,EAAE,0BAA0B,CAAC;IAC5C,WAAW,CAAC,CACV,IAAI,EAAE,OAAO,CAAC,gBAAgB,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,EACjD,OAAO,EAAE,YAAY,GACpB,QAAQ,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;IAChD;;;;;;OAMG;IACH,cAAc,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;IAC3C,gBAAgB,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,OAAO,CAAC;IAC9C,kBAAkB,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IACtC;;;;;;;;;;;;;;;;;;;;;;;OAuBG;IACH,oBAAoB,CAAC,EAAE,kBAAkB,GAAG,aAAa,GAAG,MAAM,CAAC;CACpE;AAED,MAAM,WAAW,8BAA8B;IAC7C,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,WAAW,GAAG,SAAS,CAAC;IAChC,MAAM,EACF,WAAW,GACX,iBAAiB,GACjB,OAAO,GACP,mBAAmB,GACnB,sBAAsB,GACtB,aAAa,GACb,YAAY,CAAC;IACjB;;;;;;;;;OASG;IACH,YAAY,CAAC,EAAE,SAAS,QAAQ,EAAE,CAAC;CACpC;AAED,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,EAAE,SAAS,YAAY,EAAE,CAAC;IAClC,SAAS,EAAE,SAAS,8BAA8B,EAAE,CAAC;IACrD;;;;;;;;;;;OAWG;IACH,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;CAC3B;AAyMD,wEAAwE;AACxE,wBAAsB,2BAA2B,CAC/C,QAAQ,EAAE,SAAS,YAAY,EAAE,EACjC,OAAO,GAAE,6BAAkC,GAC1C,OAAO,CAAC,4BAA4B,CAAC,CA4GvC;AAED,4EAA4E;AAC5E,wBAAsB,mBAAmB,CACvC,QAAQ,EAAE,SAAS,YAAY,EAAE,EACjC,OAAO,GAAE,6BAAkC,GAC1C,OAAO,CAAC,YAAY,EAAE,CAAC,CAEzB"}
1
+ {"version":3,"file":"history.d.ts","sourceRoot":"","sources":["../../src/agent-runtime/history.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,QAAQ,EAAE,KAAK,YAAY,EAAsB,MAAM,IAAI,CAAC;AAO1E,OAAO,KAAK,EAAE,YAAY,EAAE,gBAAgB,EAAyB,MAAM,WAAW,CAAC;AACvF,OAAO,EACL,KAAK,0BAA0B,EAEhC,MAAM,mBAAmB,CAAC;AAE3B,KAAK,QAAQ,GAAG,gBAAgB,CAAC,MAAM,CAAC,CAAC;AAEzC,MAAM,WAAW,6BAA6B;IAC5C,cAAc,CAAC,EAAE,0BAA0B,CAAC;IAC5C,WAAW,CAAC,CACV,IAAI,EAAE,OAAO,CAAC,gBAAgB,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,EACjD,OAAO,EAAE,YAAY,GACpB,QAAQ,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;IAChD;;;;;;OAMG;IACH,cAAc,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;IAC3C,gBAAgB,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,OAAO,CAAC;IAC9C,kBAAkB,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IACtC;;;;;;;;;;;;;;;;;;;;;;;OAuBG;IACH,oBAAoB,CAAC,EAAE,kBAAkB,GAAG,aAAa,GAAG,MAAM,CAAC;CACpE;AAED,MAAM,WAAW,8BAA8B;IAC7C,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,WAAW,GAAG,SAAS,CAAC;IAChC,MAAM,EACF,WAAW,GACX,iBAAiB,GACjB,OAAO,GACP,mBAAmB,GACnB,sBAAsB,GACtB,aAAa,GACb,YAAY,CAAC;IACjB;;;;;;;;;OASG;IACH,YAAY,CAAC,EAAE,SAAS,QAAQ,EAAE,CAAC;CACpC;AAED,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,EAAE,SAAS,YAAY,EAAE,CAAC;IAClC,SAAS,EAAE,SAAS,8BAA8B,EAAE,CAAC;IACrD;;;;;;;;;;;OAWG;IACH,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;CAC3B;AA2MD,wEAAwE;AACxE,wBAAsB,2BAA2B,CAC/C,QAAQ,EAAE,SAAS,YAAY,EAAE,EACjC,OAAO,GAAE,6BAAkC,GAC1C,OAAO,CAAC,4BAA4B,CAAC,CA4GvC;AAED,4EAA4E;AAC5E,wBAAsB,mBAAmB,CACvC,QAAQ,EAAE,SAAS,YAAY,EAAE,EACjC,OAAO,GAAE,6BAAkC,GAC1C,OAAO,CAAC,YAAY,EAAE,CAAC,CAEzB"}
@@ -5,7 +5,7 @@ import"../../index-8kzct1d6.js";
5
5
  import {
6
6
  composeAgentPrompt2,
7
7
  createAgentRuntime2
8
- } from "../../index-5as18gs2.js";
8
+ } from "../../index-a0vj9edp.js";
9
9
  import"../../index-qaet40b2.js";
10
10
  import"../../index-mnkk1ffz.js";
11
11
  import"../../index-fynr93w4.js";
@@ -123,7 +123,7 @@ import {
123
123
  agentGoalStateSlot2,
124
124
  agentTodoStateSlot2,
125
125
  createAgentRuntime2
126
- } from "../index-5as18gs2.js";
126
+ } from "../index-a0vj9edp.js";
127
127
  import {
128
128
  AgentConversationSummarySchema2,
129
129
  AgentConversationPageSchema2,
@@ -16,7 +16,7 @@ import {
16
16
  renderCliView2,
17
17
  pollUntilDone2,
18
18
  createCli2
19
- } from "../index-jprxv2an.js";
19
+ } from "../index-bd7spznx.js";
20
20
  import"../index-fynr93w4.js";
21
21
  import"../index-ct75c3at.js";
22
22
  import"../index-h7accrrv.js";
@@ -74,7 +74,7 @@ import {
74
74
  WaitTimeoutError,
75
75
  runWaitOperation,
76
76
  createCli2
77
- } from "../index-jprxv2an.js";
77
+ } from "../index-bd7spznx.js";
78
78
  import {
79
79
  ManagedFilePathSchema2,
80
80
  ManagedFileRefSchema2
@@ -490,7 +490,7 @@ function assistantMessages(message, marker) {
490
490
  rendered.add("tool-approval-response");
491
491
  }
492
492
  }
493
- if (marker && (messages.length > 0 || content.length > 0)) {
493
+ if (marker === "failed" || marker && (messages.length > 0 || content.length > 0)) {
494
494
  append("assistant", {
495
495
  type: "text",
496
496
  text: marker === "interrupted" ? INTERRUPTION_NOTE : FAILURE_NOTE
@@ -1102,6 +1102,12 @@ function resolveCliView(flags, ascending) {
1102
1102
  // src/tools/cli/args.ts
1103
1103
  function buildToolArgs(tokens, fields, fieldFor, declaredPositionals, allowUnknown) {
1104
1104
  const { flags, jsonFields, literalBooleanFlags, positionals } = tokens;
1105
+ if (declaredPositionals === undefined && positionals.length > 0) {
1106
+ const arrayFlag = [...flags.keys()].find((key) => fieldFor(key)?.kind === "array");
1107
+ if (arrayFlag !== undefined) {
1108
+ throw new CliArgumentError2(`--${arrayFlag} cannot be combined with automatic positional arguments. Repeat --${arrayFlag} for each element or use --${arrayFlag}:=JSON; name other fields with flags, or declare a positional policy.`);
1109
+ }
1110
+ }
1105
1111
  const toolArgs = {};
1106
1112
  const fillable = declaredPositionals === undefined ? [...fields.entries()].filter(([, info]) => info.kind !== "boolean").map(([name]) => name) : [...declaredPositionals];
1107
1113
  const variadicTail = declaredPositionals !== undefined && fields.get(fillable[fillable.length - 1] ?? "")?.kind === "array" ? fillable[fillable.length - 1] : undefined;
@@ -1660,7 +1666,7 @@ function typeLabel(schema) {
1660
1666
  if (Array.isArray(schema.enum))
1661
1667
  return schema.enum.join("|");
1662
1668
  if (schema.type === "array")
1663
- return "value…";
1669
+ return "value";
1664
1670
  if (typeof schema.type === "string")
1665
1671
  return schema.type;
1666
1672
  return "value";
@@ -1822,6 +1828,13 @@ function renderCommandHelp(name, command, descriptor, applicationOptions = []) {
1822
1828
  if (flag !== undefined) {
1823
1829
  lines.push("", `A boolean flag is on alone or takes true/false: --${flag}, --${flag} false, --no-${flag}`);
1824
1830
  }
1831
+ const arrays = [...kinds].filter(([, info]) => info.kind === "array");
1832
+ for (const [field] of arrays) {
1833
+ lines.push("", `Repeat --${field} <value> for each element, or assign the whole array with --${field}:=JSON.`);
1834
+ }
1835
+ if (arrays.length > 0 && descriptor.positionals === undefined) {
1836
+ lines.push("Array options cannot be combined with automatic positional arguments; name other fields with flags.");
1837
+ }
1825
1838
  lines.push("", ...jsonValueHelp(fields), "");
1826
1839
  }
1827
1840
  const applicationLines = applicationOptionLines(applicationOptions);
@@ -1 +1 @@
1
- {"version":3,"file":"args.d.ts","sourceRoot":"","sources":["../../../src/tools/cli/args.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAcxB,OAAO,EAAE,KAAK,aAAa,EAAkB,MAAM,aAAa,CAAC;AAMjE,yEAAyE;AACzE,MAAM,WAAW,aAAa;IAC5B,sEAAsE;IACtE,IAAI,EAAE,OAAO,CAAC;IACd,iEAAiE;IACjE,IAAI,EAAE,OAAO,CAAC;IACd,8DAA8D;IAC9D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,wEAAwE;IACxE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,yDAAyD;IACzD,KAAK,EAAE,OAAO,CAAC;IACf,qEAAqE;IACrE,MAAM,EAAE,OAAO,CAAC;IAChB,qDAAqD;IACrD,IAAI,EAAE,OAAO,CAAC;IACd,0FAA0F;IAC1F,IAAI,CAAC,EAAE,aAAa,CAAC;CACtB;AAED,MAAM,WAAW,aAAa;IAC5B,mEAAmE;IACnE,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,+BAA+B;IAC/B,OAAO,EAAE,aAAa,CAAC;CACxB;AA6GD;;;GAGG;AACH,wBAAgB,YAAY,CAC1B,IAAI,EAAE,MAAM,EAAE,EACd,MAAM,EAAE,CAAC,CAAC,OAAO,GAAG,SAAS,EAC7B,MAAM,GAAE;IACN,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAChC,aAAa,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5C,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC5B,GACL,aAAa,CAsBf;AAED,iFAAiF;AACjF,MAAM,WAAW,qBAAqB;IACpC,0EAA0E;IAC1E,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,wEAAwE;IACxE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC;AASD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,MAAM,EAAE,CAAC,CAAC,SAAS,GAAG,SAAS,GAC9B,qBAAqB,CAyDvB"}
1
+ {"version":3,"file":"args.d.ts","sourceRoot":"","sources":["../../../src/tools/cli/args.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAcxB,OAAO,EAAE,KAAK,aAAa,EAAkB,MAAM,aAAa,CAAC;AAMjE,yEAAyE;AACzE,MAAM,WAAW,aAAa;IAC5B,sEAAsE;IACtE,IAAI,EAAE,OAAO,CAAC;IACd,iEAAiE;IACjE,IAAI,EAAE,OAAO,CAAC;IACd,8DAA8D;IAC9D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,wEAAwE;IACxE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,yDAAyD;IACzD,KAAK,EAAE,OAAO,CAAC;IACf,qEAAqE;IACrE,MAAM,EAAE,OAAO,CAAC;IAChB,qDAAqD;IACrD,IAAI,EAAE,OAAO,CAAC;IACd,0FAA0F;IAC1F,IAAI,CAAC,EAAE,aAAa,CAAC;CACtB;AAED,MAAM,WAAW,aAAa;IAC5B,mEAAmE;IACnE,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,+BAA+B;IAC/B,OAAO,EAAE,aAAa,CAAC;CACxB;AAwHD;;;GAGG;AACH,wBAAgB,YAAY,CAC1B,IAAI,EAAE,MAAM,EAAE,EACd,MAAM,EAAE,CAAC,CAAC,OAAO,GAAG,SAAS,EAC7B,MAAM,GAAE;IACN,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAChC,aAAa,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5C,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC5B,GACL,aAAa,CAsBf;AAED,iFAAiF;AACjF,MAAM,WAAW,qBAAqB;IACpC,0EAA0E;IAC1E,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,wEAAwE;IACxE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC;AASD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,MAAM,EAAE,CAAC,CAAC,SAAS,GAAG,SAAS,GAC9B,qBAAqB,CAyDvB"}
@@ -1 +1 @@
1
- {"version":3,"file":"help.d.ts","sourceRoot":"","sources":["../../../src/tools/cli/help.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,eAAe,EAAoB,MAAM,+BAA+B,CAAC;AAEvF,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,UAAU,CAAC;AAkIvD;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,sBAAsB,CAAC,EAC7C,MAAM,EAAE,MAAM,GACb,GAAG,CAAC,MAAM,EAAE,sBAAsB,CAAC,CASrC;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,sBAAsB,CAAC,CAAC;IAC9C,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,kBAAkB,EAAE,SAAS,eAAe,EAAE,CAAC;IAC/C,wEAAwE;IACxE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,mEAAmE;IACnE,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,GAAG,MAAM,CAsCT;AAED,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,sBAAsB,EAClC,kBAAkB,GAAE,SAAS,eAAe,EAAO,GAClD,MAAM,CA6DR"}
1
+ {"version":3,"file":"help.d.ts","sourceRoot":"","sources":["../../../src/tools/cli/help.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,eAAe,EAAoB,MAAM,+BAA+B,CAAC;AAEvF,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,UAAU,CAAC;AAkIvD;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,sBAAsB,CAAC,EAC7C,MAAM,EAAE,MAAM,GACb,GAAG,CAAC,MAAM,EAAE,sBAAsB,CAAC,CASrC;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,sBAAsB,CAAC,CAAC;IAC9C,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,kBAAkB,EAAE,SAAS,eAAe,EAAE,CAAC;IAC/C,wEAAwE;IACxE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,mEAAmE;IACnE,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,GAAG,MAAM,CAsCT;AAED,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,sBAAsB,EAClC,kBAAkB,GAAE,SAAS,eAAe,EAAO,GAClD,MAAM,CAyER"}
@@ -7,7 +7,7 @@
7
7
  Parts of this slice:
8
8
  - `llms/agent-runtime.txt`: Guide: Agent application runtime (docs/guide/agent-runtime.md)
9
9
  - `llms/agent-runtime.2.txt`: Guide: Agent application runtime (docs/guide/agent-runtime.md)
10
- - `llms/agent-runtime.3.txt`: API reference: `stitchkit/agent-runtime` (docs/api/reference.md) · API reference: `stitchkit-tui` (docs/api/reference.md)
10
+ - `llms/agent-runtime.3.txt`: Guide: Agent application runtime (docs/guide/agent-runtime.md) · API reference: `stitchkit/agent-runtime` (docs/api/reference.md) · API reference: `stitchkit-tui` (docs/api/reference.md)
11
11
 
12
12
  ==============================================================================
13
13
  # Guide: Agent application runtime (docs/guide/agent-runtime.md) (continued)
@@ -615,6 +615,62 @@ replace a leading summary on the next compaction.
615
615
  Set `maxAttempts` to allow bounded conflict recovery. Every retry reloads the snapshot, reselects the
616
616
  eligible range and recomputes the summary; the stale summary is never retried.
617
617
 
618
+ ### Recovering a prefix after a failed turn
619
+
620
+ The default evidence policy omits failed assistants. A failed turn therefore stops the
621
+ compactable prefix, including completed turns after it. A terminal failure is not an
622
+ unanswered tool call, but opting its partial evidence into a summary is an explicit host
623
+ decision. Use the existing `evidencePolicy: { failedAssistant: 'assistant-marked' }` on
624
+ `structuredCompaction` (or `selectCompactableHistory`) when that evidence is wanted.
625
+ The same policy belongs on the summarizer's history projection and on any corresponding
626
+ token-budget selection. Stored statuses remain unchanged.
627
+
628
+ The summarizer receives **raw** `eligibleMessages`, not provider messages and not an
629
+ automatic failure marker. Project them with the same policy before sending them to a
630
+ model, and preserve failed outcomes in the application's structured summary and prompt:
631
+
632
+ ```ts
633
+ const evidencePolicy: AgentHistoryEvidencePolicy = {
634
+ failedAssistant: 'assistant-marked',
635
+ }
636
+
637
+ // Inside structuredCompaction({ evidencePolicy, ... }).summarize:
638
+ const projected = await projectAgentHistoryDetailed(eligibleMessages, {
639
+ evidencePolicy,
640
+ incompleteToolTurn: 'error',
641
+ })
642
+ const failedMessageIds = eligibleMessages
643
+ .filter(message => message.role === 'assistant' && message.status === 'failed')
644
+ .map(message => message.id)
645
+ // Pass projected.messages, projected.system and failedMessageIds to the host's
646
+ // summarizer. Its schema/prompt must retain failures and uncertain effects as
647
+ // such, merge previousSummary, and never describe partial output as success.
648
+ ```
649
+
650
+ An opted-in failed assistant projects an explicit non-success marker even when it has
651
+ no text or only control parts. A dangling call, unresolved approval, invalid result
652
+ chronology or a user input with no assistant remains a barrier under either policy.
653
+ Raw whole-turn completeness does not guarantee that every record can be projected:
654
+ for example, a call and its result split across records may be rejected by the projector.
655
+ `incompleteToolTurn: 'error'` makes that rejection stop summarization; the original history
656
+ stays canonical. A host with such records must use a status-aware summarizer that explicitly
657
+ represents every relevant record, or retain the range. Do not silently summarize an omitted
658
+ exchange, filter the failed record out of the replacement range, rewrite its status or skip
659
+ over an incomplete turn. The replacement remains one contiguous prefix under snapshot CAS.
660
+
661
+ Provider chronology and external effect authority answer different questions (ADR 0269).
662
+ A matching `tool-result` with `outcome: 'error'` and an application code such as `UNKNOWN`
663
+ closes the provider exchange; it does **not** prove the external effect failed or succeeded.
664
+ Compaction changes the model-history projection, not the canonical event journal or the
665
+ retained terminal admission receipt. The `durability/effect` intent, accepted proof and
666
+ uncertain outcome remain available after compaction and archive restore. A reconstructed
667
+ `createLocalStepDurability` using the original `(conversationId, runId, effectName)` returns
668
+ the accepted proof, reconciles a bare intent, or returns `uncertain` without calling `run`
669
+ again. It does not deduplicate a new run or a newly named effect. Standalone effect callers
670
+ still own the exclusive execution lease; sharing a ledger is not an atomic claim.
671
+ An application-owned receipt outside that journal retains its own recovery rules and must
672
+ not be discarded because a history turn became compactable.
673
+
618
674
  ### Compacting without the store
619
675
 
620
676
  The *selection* inside compaction needs no store, no version and no runtime, so
@@ -772,105 +828,3 @@ first mutation**, so an adapter whose runtime rows reference an application-owne
772
828
  can provision those parents; the optional `cleanup(context)` removes them again, and runs once
773
829
  whether the scenario passed or failed. A factory that owns no fixture state ignores the argument and
774
830
  keeps working unchanged.
775
-
776
- ## Plain completions and shared invocation receipts
777
-
778
- Use `createModelInvocationLedger` from `stitchkit/agent-runtime` when a direct text
779
- completion and an agent must share causal provider evidence. It uses the existing
780
- runtime store, not a second database. The completion path sends exactly one user
781
- prompt, no framework instructions and no tools. It never starts an agent loop.
782
-
783
- ```ts
784
- import { createModelInvocationLedger } from 'stitchkit/agent-runtime';
785
-
786
- const invocations = createModelInvocationLedger({
787
- store, // the same AgentRuntimeStore object passed to createAgentRuntime
788
- models, // an existing defineModelRegistry result
789
- payloadKey, // 32 bytes from the host's secret store; retain separately from archives
790
- authorize: async ({ context, conversationId, action, trace }) => {
791
- const session = await authenticate(context);
792
- await authorizeConversation(session, conversationId, action, trace);
793
- return { subject: session.subject }; // verified identity, never a body field
794
- },
795
- });
796
-
797
- const completion = await invocations.complete({
798
- conversationId: 'evaluation-42',
799
- idempotencyKey: 'summary-case-3',
800
- trace: {
801
- operationId: 'summary-42', purpose: 'summarize', project: 'example',
802
- parent: { traceId: 'trace-42' }, experiment: 'evaluation', case: 'case-3',
803
- },
804
- prompt: 'Summarize these supplied facts.',
805
- models: ['primary', 'fallback'], // registry keys; repeat a key for an explicit retry
806
- settings: { maxOutputTokens: 500 },
807
- timeoutMs: 30_000,
808
- }, authenticatedContext);
809
-
810
- // Add to the existing createAgentRuntime configuration:
811
- const receiptConfig = {
812
- ledger: invocations,
813
- trace: ({ run }) => ({
814
- operationId: run.id, purpose: 'assistant-turn', project: 'example',
815
- }),
816
- };
817
- // createAgentRuntime({ ...existingConfig, invocations: receiptConfig });
818
-
819
- const page = await invocations.read({
820
- conversationId: 'evaluation-42', limit: 100,
821
- }, authenticatedContext);
822
- // Continue with fromSeq: page.nextSeq while it is defined.
823
- ```
824
-
825
- `complete` returns `invocationId`, `outcome` and, on success, `text`. `duplicate`
826
- returns the existing ID without provider execution or replaying plaintext. Changed
827
- input, trace or caller under the same key is a conflict. Model attempts are explicit
828
- and bounded to eight; SDK retries are disabled. Only provider failures trigger the
829
- next attempt. Abort, audit refusal and configuration errors do not. An admitted call
830
- whose process died stays claimed: inspect the receipt before choosing a new key.
831
-
832
- The agent retains its existing prompt, tools and loop. Both modes produce
833
- `invocation/started`, `provider/request`, `provider/response` and
834
- `invocation/finished` records through `read`. Each attempt has its own ID; response
835
- usage is counted once and keeps `unavailable` distinct from measured zero. Requested
836
- registry selection, sent adapter identity and effective provider identity are
837
- separate. Effective identity is null when not reported. A gateway's hidden retries
838
- are not visible as additional attempts.
839
-
840
- `currentModelInvocationAttempt()` returns the causal context inside a provider's
841
- `fetch` callback or streamed read. Use it to join a host transport audit to
842
- `operationId`, `invocationId`, `attemptId`, optional `runId` and `step`.
843
- `onAttempt` is an awaited admission hook; a rejected hook prevents the provider call.
844
- SDK payload hashes are not represented as hashes of HTTP wire bytes.
845
-
846
- Prompts and output are AES-256-GCM payload artifacts. `read` returns references and
847
- hashes; `readPayload({ conversationId, artifactId }, context)` separately authorizes
848
- `read-payload` and checks authentication tags and hashes. `payloadKey` must be 32
849
- bytes and is copied at construction. Keep it outside the event store/backups; retain
850
- the appropriate key when restoring an archive. `maxPayloadBytes` defaults to 4 MiB
851
- (range 1 KiB–64 MiB). Streaming output evidence keeps parts up to half of it; past
852
- that the evidence carries `truncated: { parts, sha256 }` over the whole stream, and
853
- the answer itself is never cut. A receipt that cannot be written never changes an
854
- outcome: `complete()` returns it with `receiptError` beside it, and a succeeded
855
- answer keeps its `text`. Request headers
856
- are excluded. Protect existing agent history independently: enabling receipts does
857
- not encrypt its ordinary conversation records.
858
-
859
- The built-in memory and SQLite stores implement `appendEventOnce`. Custom stores
860
- must implement its once-only check and append atomically, returning the original
861
- event on a duplicate key scoped by conversation and kind. A factory without this
862
- capability refuses receipt setup. The driver-backed implementation finds the keyed
863
- event by its identity through the driver's optional `events.find` — one index probe
864
- in SQLite — so admission costs the same on a long conversation as on a new one; a
865
- custom driver without `find` falls back to paging the log in one transaction.
866
- `store.findEventOnce({ conversationId, kind, key })` reads the same event back.
867
- Invocation payload artifacts are keyed by their artifact id the same way.
868
-
869
- ### Migration from direct SDK completions
870
-
871
- **Who must act:** applications opting into shared completion/agent receipts.
872
- Replace the direct `generateText` call with `invocations.complete`, configure host
873
- authorization and a retained payload key, and pass the same ledger in the agent's
874
- `invocations` option. Use the returned invocation ID and the paginated `read` method
875
- for receipt queries; do not import runtime internals. Available since 0.100.0;
876
- existing runtime configurations need no migration.
@@ -6,7 +6,113 @@
6
6
  Parts of this slice:
7
7
  - `llms/agent-runtime.txt`: Guide: Agent application runtime (docs/guide/agent-runtime.md)
8
8
  - `llms/agent-runtime.2.txt`: Guide: Agent application runtime (docs/guide/agent-runtime.md)
9
- - `llms/agent-runtime.3.txt`: API reference: `stitchkit/agent-runtime` (docs/api/reference.md) · API reference: `stitchkit-tui` (docs/api/reference.md)
9
+ - `llms/agent-runtime.3.txt`: Guide: Agent application runtime (docs/guide/agent-runtime.md) · API reference: `stitchkit/agent-runtime` (docs/api/reference.md) · API reference: `stitchkit-tui` (docs/api/reference.md)
10
+
11
+ ==============================================================================
12
+ # Guide: Agent application runtime (docs/guide/agent-runtime.md) (continued)
13
+ ==============================================================================
14
+
15
+ ## Plain completions and shared invocation receipts
16
+
17
+ Use `createModelInvocationLedger` from `stitchkit/agent-runtime` when a direct text
18
+ completion and an agent must share causal provider evidence. It uses the existing
19
+ runtime store, not a second database. The completion path sends exactly one user
20
+ prompt, no framework instructions and no tools. It never starts an agent loop.
21
+
22
+ ```ts
23
+ import { createModelInvocationLedger } from 'stitchkit/agent-runtime';
24
+
25
+ const invocations = createModelInvocationLedger({
26
+ store, // the same AgentRuntimeStore object passed to createAgentRuntime
27
+ models, // an existing defineModelRegistry result
28
+ payloadKey, // 32 bytes from the host's secret store; retain separately from archives
29
+ authorize: async ({ context, conversationId, action, trace }) => {
30
+ const session = await authenticate(context);
31
+ await authorizeConversation(session, conversationId, action, trace);
32
+ return { subject: session.subject }; // verified identity, never a body field
33
+ },
34
+ });
35
+
36
+ const completion = await invocations.complete({
37
+ conversationId: 'evaluation-42',
38
+ idempotencyKey: 'summary-case-3',
39
+ trace: {
40
+ operationId: 'summary-42', purpose: 'summarize', project: 'example',
41
+ parent: { traceId: 'trace-42' }, experiment: 'evaluation', case: 'case-3',
42
+ },
43
+ prompt: 'Summarize these supplied facts.',
44
+ models: ['primary', 'fallback'], // registry keys; repeat a key for an explicit retry
45
+ settings: { maxOutputTokens: 500 },
46
+ timeoutMs: 30_000,
47
+ }, authenticatedContext);
48
+
49
+ // Add to the existing createAgentRuntime configuration:
50
+ const receiptConfig = {
51
+ ledger: invocations,
52
+ trace: ({ run }) => ({
53
+ operationId: run.id, purpose: 'assistant-turn', project: 'example',
54
+ }),
55
+ };
56
+ // createAgentRuntime({ ...existingConfig, invocations: receiptConfig });
57
+
58
+ const page = await invocations.read({
59
+ conversationId: 'evaluation-42', limit: 100,
60
+ }, authenticatedContext);
61
+ // Continue with fromSeq: page.nextSeq while it is defined.
62
+ ```
63
+
64
+ `complete` returns `invocationId`, `outcome` and, on success, `text`. `duplicate`
65
+ returns the existing ID without provider execution or replaying plaintext. Changed
66
+ input, trace or caller under the same key is a conflict. Model attempts are explicit
67
+ and bounded to eight; SDK retries are disabled. Only provider failures trigger the
68
+ next attempt. Abort, audit refusal and configuration errors do not. An admitted call
69
+ whose process died stays claimed: inspect the receipt before choosing a new key.
70
+
71
+ The agent retains its existing prompt, tools and loop. Both modes produce
72
+ `invocation/started`, `provider/request`, `provider/response` and
73
+ `invocation/finished` records through `read`. Each attempt has its own ID; response
74
+ usage is counted once and keeps `unavailable` distinct from measured zero. Requested
75
+ registry selection, sent adapter identity and effective provider identity are
76
+ separate. Effective identity is null when not reported. A gateway's hidden retries
77
+ are not visible as additional attempts.
78
+
79
+ `currentModelInvocationAttempt()` returns the causal context inside a provider's
80
+ `fetch` callback or streamed read. Use it to join a host transport audit to
81
+ `operationId`, `invocationId`, `attemptId`, optional `runId` and `step`.
82
+ `onAttempt` is an awaited admission hook; a rejected hook prevents the provider call.
83
+ SDK payload hashes are not represented as hashes of HTTP wire bytes.
84
+
85
+ Prompts and output are AES-256-GCM payload artifacts. `read` returns references and
86
+ hashes; `readPayload({ conversationId, artifactId }, context)` separately authorizes
87
+ `read-payload` and checks authentication tags and hashes. `payloadKey` must be 32
88
+ bytes and is copied at construction. Keep it outside the event store/backups; retain
89
+ the appropriate key when restoring an archive. `maxPayloadBytes` defaults to 4 MiB
90
+ (range 1 KiB–64 MiB). Streaming output evidence keeps parts up to half of it; past
91
+ that the evidence carries `truncated: { parts, sha256 }` over the whole stream, and
92
+ the answer itself is never cut. A receipt that cannot be written never changes an
93
+ outcome: `complete()` returns it with `receiptError` beside it, and a succeeded
94
+ answer keeps its `text`. Request headers
95
+ are excluded. Protect existing agent history independently: enabling receipts does
96
+ not encrypt its ordinary conversation records.
97
+
98
+ The built-in memory and SQLite stores implement `appendEventOnce`. Custom stores
99
+ must implement its once-only check and append atomically, returning the original
100
+ event on a duplicate key scoped by conversation and kind. A factory without this
101
+ capability refuses receipt setup. The driver-backed implementation finds the keyed
102
+ event by its identity through the driver's optional `events.find` — one index probe
103
+ in SQLite — so admission costs the same on a long conversation as on a new one; a
104
+ custom driver without `find` falls back to paging the log in one transaction.
105
+ `store.findEventOnce({ conversationId, kind, key })` reads the same event back.
106
+ Invocation payload artifacts are keyed by their artifact id the same way.
107
+
108
+ ### Migration from direct SDK completions
109
+
110
+ **Who must act:** applications opting into shared completion/agent receipts.
111
+ Replace the direct `generateText` call with `invocations.complete`, configure host
112
+ authorization and a retained payload key, and pass the same ledger in the agent's
113
+ `invocations` option. Use the returned invocation ID and the paginated `read` method
114
+ for receipt queries; do not import runtime internals. Available since 0.100.0;
115
+ existing runtime configurations need no migration.
10
116
 
11
117
  ==============================================================================
12
118
  # API reference: `stitchkit/agent-runtime` (docs/api/reference.md)
@@ -244,6 +350,16 @@ projection, budgeting and structured compaction; `isAssistantHistoryEvidence` ap
244
350
  compatibility default omits it. Approval message schemas are
245
351
  `AgentToolApprovalRequestPartSchema` and `AgentToolApprovalResponsePartSchema`.
246
352
 
353
+ With `failedAssistant: 'assistant-marked'`, `selectCompactableHistory` and
354
+ `structuredCompaction` admit a failed prefix only when the entire turn's tool chronology
355
+ is complete. `eligibleMessages` stays raw: the summarizer must use the same policy in
356
+ `projectAgentHistoryDetailed` and preserve non-success/uncertain outcomes in its summary.
357
+ Even an empty opted-in failed assistant carries a non-success marker in that projection.
358
+ Use `incompleteToolTurn: 'error'` to refuse a range the projector cannot represent.
359
+ Compaction never deletes canonical effect events or the retained terminal receipt;
360
+ chronological completeness does not settle an external effect. See the
361
+ [failed-prefix recovery contract](../guide/agent-runtime.md#recovering-a-prefix-after-a-failed-turn).
362
+
247
363
  Model exports are `AgentLanguageModelProvider`, `AgentModelCapability`,
248
364
  `AgentModelCapabilitySchema`, `AgentModelDescriptor`,
249
365
  `AgentModelDescriptorSchema`, `AgentModelRegistry`, `AgentModelRegistryConfig`,
@@ -7,7 +7,7 @@
7
7
  Parts of this slice:
8
8
  - `llms/agent-runtime.txt`: Guide: Agent application runtime (docs/guide/agent-runtime.md)
9
9
  - `llms/agent-runtime.2.txt`: Guide: Agent application runtime (docs/guide/agent-runtime.md)
10
- - `llms/agent-runtime.3.txt`: API reference: `stitchkit/agent-runtime` (docs/api/reference.md) · API reference: `stitchkit-tui` (docs/api/reference.md)
10
+ - `llms/agent-runtime.3.txt`: Guide: Agent application runtime (docs/guide/agent-runtime.md) · API reference: `stitchkit/agent-runtime` (docs/api/reference.md) · API reference: `stitchkit-tui` (docs/api/reference.md)
11
11
 
12
12
  ==============================================================================
13
13
  # Guide: Agent application runtime (docs/guide/agent-runtime.md)
package/llms/cli.2.txt CHANGED
@@ -281,7 +281,7 @@ SDK nor the `ai` peer.
281
281
  |--------|------|---------|
282
282
  | `createCli` | function | build and run a CLI from contracts — [guide](../guide/cli.md) |
283
283
  | `defineCliCommand` | function | define one Zod-typed CLI-only executable command with optional validated-result presentation/exit policy |
284
- | `parseCliArgs` | function | argv → typed tool args against a schema; inline `--field:=JSON` assigns a whole JSON value, including null, while ordinary string flags remain lexical (advanced) |
284
+ | `parseCliArgs` | function | argv → typed tool args against a schema; inline `--field:=JSON` assigns a whole JSON value, including null, while ordinary string flags remain lexical; named array options together with bare positionals require an explicit `positionals` policy (advanced) |
285
285
  | `routeCliArgv` | function | select the command out of argv without duplicating the global-option grammar (advanced) |
286
286
  | `extractCliGlobalOptions` | function | lift the application's own global options out of argv before routing (advanced) |
287
287
  | `coerceJsonArgs` | function | the second half of `parseCliArgs` — parse array/object values a consumer sends itself, including discovered MCP object guards restored from JSON Schema, without the `stitchkit/tools` barrel |
package/llms/cli.txt CHANGED
@@ -247,6 +247,15 @@ string, the schema says what it should be:
247
247
  | `z.record(z.string(), z.unknown())` | `--data '{"code":"007"}'` or `--data.code=007` → `"007"` |
248
248
  | `.optional()` / `.default()` | not required |
249
249
 
250
+ An array option takes **one value per occurrence**, never a bare list after the flag.
251
+ Repeat it (`--tag a --tag b`) or assign the whole array (`--tag:='["a","b"]'`).
252
+ Without an explicit command `positionals` policy, combining any named array option
253
+ with bare positionals is refused before dispatch: schema-order inference could otherwise
254
+ assign the remaining array elements to unrelated fields. This includes short aliases,
255
+ dotted array leaves, whole-array JSON and tokens after `--`, regardless of token order.
256
+ Name the scalar fields too, or declare their exact positional destinations. An explicitly
257
+ declared trailing positional array is the variadic form described below.
258
+
250
259
  Use the inline `:=` form to assign a whole JSON value without scalar coercion:
251
260
 
252
261
  ```bash
@@ -522,7 +531,7 @@ Declared aliases render beside their canonical options, for example
522
531
 
523
532
  The argument table and `Application options:` show bounds from their JSON Schema,
524
533
  for example `<integer> [>=1, <=50]`, `<string> [length >=1, length <=12]`, and
525
- `<value…> [items >=2, items <=4]`. Numeric `>` and `<` are exclusive, while `>=`
534
+ `<value> [items >=2, items <=4]`. Numeric `>` and `<` are exclusive, while `>=`
526
535
  and `<=` include the endpoint; a zero limit is displayed too. Only limits the
527
536
  schema declares are shown: the safe-integer range every `z.int()` carries is the
528
537
  type, not a bound, so a bare integer prints `<integer>`, `.min(1)` prints `>=1` and
@@ -85,12 +85,12 @@ own, recorded as an ADR.
85
85
  | `stitchkit/geo` | server (Bun or Node) | evolving | managed GeoIP reader generations, last-known-good reload and the optional MaxMind adapter |
86
86
  | `stitchkit/oauth` | browser **and** server | evolving | provider-neutral Authorization Code + PKCE request and one-shot callback transaction mechanics |
87
87
  | `stitchkit/google` | server (Bun or Node) | evolving | optional Google code exchange and verified OIDC identity adapter |
88
- | `stitchkit/observability` | server | stable<br>_redefined in 2 of the 56 minors since 0.56.2, most recently 0.92.0_ | request/tool event projections — `createObservability`, trace context, sanitisation |
88
+ | `stitchkit/observability` | server | stable<br>_redefined in 2 of the 57 minors since 0.56.2, most recently 0.92.0_ | request/tool event projections — `createObservability`, trace context, sanitisation |
89
89
  | `stitchkit/testing` | tests on Bun or Node | stable | in-process generated clients over a real Fetch handler, the managed-resource conformance kit and manual time for schedules and retries |
90
90
  | `stitchkit/declaration` | browser + build and deployment tooling (Bun or Node) | evolving | `ProjectDeclarationSchema` — the one machine-readable statement a repository makes about itself |
91
91
  | `stitchkit/react` | browser + server rendering | stable | `createCursorQuery`, `createCacheBridge`, QueryClient and `ApiError` retry policy |
92
92
  | `stitchkit/react/keyboard` | browser | evolving | which part of a screen gets a key — ordered layers, one Escape per level, list zones with real focus; see [keyboard guide](keyboard.md) |
93
- | `stitchkit/agent-runtime` | server | evolving<br>_redefined in 23 of the 56 minors since 0.56.2, most recently 0.105.0_ | optional durable conversation/run loop, history, models, prompts, fencing and events |
93
+ | `stitchkit/agent-runtime` | server | evolving<br>_redefined in 23 of the 57 minors since 0.56.2, most recently 0.105.0_ | optional durable conversation/run loop, history, models, prompts, fencing and events |
94
94
  | `stitchkit/agent-runtime/sandbox` | server | evolving | optional Linux Bubblewrap sessions, durable workspaces and a host HTTP credential gateway; see [sandbox guide](sandbox.md) |
95
95
  | `stitchkit/agent-runtime/testing` | tests on Bun or Node | evolving | credential-free replay, scripted provider faults, deterministic race controls and the agent-store conformance kit |
96
96
  | `stitchkit/agent-runtime/harness` | server | evolving | resource-aware process-local facade over the canonical Agent runtime; supervision stays outside |
@@ -102,7 +102,7 @@ own, recorded as an ADR.
102
102
  | `stitchkit/agent-runtime/sqlite/bun` | server (Bun) | evolving | durable built-in SQLite store for the agent runtime |
103
103
  | `stitchkit/agent-runtime/sqlite/node` | server (Node ≥ 22.5) | evolving | durable built-in SQLite store for the agent runtime |
104
104
  | `stitchkit-tui` | terminal (Bun) | evolving | optional official OpenTUI host over a caller-composed headless runtime |
105
- | `stitchkit/application` | browser + server | evolving<br>_redefined in 14 of the 56 minors since 0.56.2, most recently 0.110.0_ | managed resource graph, readiness, admission, schedules, subtree restart and bounded shutdown |
105
+ | `stitchkit/application` | browser + server | evolving<br>_redefined in 14 of the 57 minors since 0.56.2, most recently 0.110.0_ | managed resource graph, readiness, admission, schedules, subtree restart and bounded shutdown |
106
106
  | `stitchkit/application/grammy` | server | evolving | a grammY bot as application resources — configuration, then polling with updates admitted by batch — and the webhook adapter |
107
107
  | `stitchkit/application/opentelemetry` | server | evolving | maps application snapshots onto an injected OpenTelemetry `Meter` |
108
108
  | `stitchkit/application/schemas` | browser + server | evolving | the application's snapshot, health and shutdown schemas alone, without the kernel |
@@ -1,14 +1,45 @@
1
- # Upgrading 0.101.0 → 0.111.0 — stitchkit docs slice
1
+ # Upgrading 0.103.0 → 0.112.0 — stitchkit docs slice
2
2
 
3
3
  > Generated from the `docs/` tree by `bun run gen:llms`. The index of all slices is
4
4
  > `llms.txt` in this package.
5
5
 
6
- Contains: Guide: Upgrading, 0.101.0 → 0.111.0 (docs/guide/upgrading.md)
6
+ Contains: Guide: Upgrading, 0.103.0 → 0.112.0 (docs/guide/upgrading.md)
7
7
 
8
8
  ==============================================================================
9
- # Guide: Upgrading, 0.101.0 → 0.111.0 (docs/guide/upgrading.md)
9
+ # Guide: Upgrading, 0.103.0 → 0.112.0 (docs/guide/upgrading.md)
10
10
  ==============================================================================
11
11
 
12
+ ## Released migration: 0.112.0
13
+
14
+ **Who must act:** scripts using an array option together with bare positional arguments without a declared `positionals` policy, including calls to `parseCliArgs`.
15
+
16
+ An array flag consumes one value. Automatic schema-order positionals cannot distinguish additional array elements from values intended for unrelated fields, so the combination is refused before any handler or stdin read. The rule is independent of token order, includes aliases, dotted array leaves and whole-array JSON, and `--` does not bypass it. See [ADR 0268](../decisions/0268-named-cli-arrays-require-explicit-positional-destinations.md).
17
+
18
+ ```sh
19
+ # before: the last two words could populate unrelated fields
20
+ app resolve --references alpha.md beta.md gamma.md
21
+ # after: repeat the option or assign the whole array
22
+ app resolve --references alpha.md --references beta.md --references gamma.md
23
+ app resolve --references:='["alpha.md","beta.md","gamma.md"]'
24
+
25
+ # before: even an intended scalar positional has an implicit destination
26
+ app resolve project-one --references alpha.md
27
+ # after: name it
28
+ app resolve --projectId project-one --references alpha.md
29
+ ```
30
+
31
+ Applications that intend positional syntax declare its destinations on the available command:
32
+
33
+ ```ts
34
+ createCli({ ...config, positionals: { resolve: ['projectId'] } })
35
+ // app resolve project-one --references alpha.md
36
+
37
+ createCli({ ...config, positionals: { resolve: ['references'] } })
38
+ // app resolve alpha.md beta.md gamma.md
39
+ ```
40
+
41
+ A declared trailing array accepts all remaining positional elements. Giving that same field both a flag and positional elements remains an error. An empty policy accepts flags only. Policies still target exact available command names: a partial dynamic catalogue needs no policy for an unavailable command to enforce this admission rule.
42
+
12
43
  ## Released migration: 0.111.0
13
44
 
14
45
  **Who must act:** applications whose raw routes call `parseBody` on JSON larger than 1 MiB, and applications whose OAuth clients send `/register` or `/token` bodies larger than 64 KiB.
@@ -874,63 +905,3 @@ For archive inspection, replace ad hoc line parsing with `readDiagnosticJournal`
874
905
  from the same filesystem leaf. Handle its `frame | anomaly` stream explicitly;
875
906
  missing files, permissions and unsafe paths still throw. See the
876
907
  [journal guide](application-kernel.md#bounded-local-diagnostic-journal).
877
-
878
- ## Released migration: 0.102.0
879
-
880
- ### `view_file` receives an array, not an array encoded as text
881
-
882
- **Who must act:** callers sending `paths` as a JSON-encoded array string, or
883
- matching the former media-extension refusal for that input. Managed MCP,
884
- Agent and CLI surfaces now reject it as `VALIDATION_ERROR` before IO; raw MCP
885
- reports an input-validation error. Correct single paths and arrays need no change.
886
-
887
- ```ts
888
- // Before
889
- { paths: '["image.png"]' }
890
- // After
891
- { paths: ['image.png'] }
892
- // One file may also be a string
893
- { paths: 'image.png' }
894
- ```
895
-
896
- The same rule applies to an array element containing another encoded array.
897
- Do not change generic string-union coercion: path strings remain strings.
898
- A legitimate filename such as `[preview].PNG` is still accepted. Local
899
- unsupported-extension errors now include the caller's path and extension;
900
- match the error code rather than the former generic message.
901
-
902
- ## Released migration: 0.101.0
903
-
904
- ### `TelegramInitDataUser` is `TelegramUser`
905
-
906
- **Who must act:** code that names the type `TelegramInitDataUser` from
907
- `stitchkit/telegram`. The record is the same; its name now says it is any
908
- Telegram user, since `parseTelegramUser(ctx.from)` returns it for a Bot API user
909
- too.
910
-
911
- ```ts
912
- // Before
913
- import type { TelegramInitDataUser } from 'stitchkit/telegram'
914
- function ensureAccount(user: TelegramInitDataUser) {}
915
- // After
916
- import type { TelegramUser } from 'stitchkit/telegram'
917
- function ensureAccount(user: TelegramUser) {}
918
- ```
919
-
920
- ### `TELEGRAM_BOT_TOKEN_PATTERN` matches the secret half only
921
-
922
- **Who must act:** code that uses `TELEGRAM_BOT_TOKEN_PATTERN` to find or cut a
923
- whole token. It now matches what follows `<bot id>:`, so a masking replace keeps
924
- the bot's id — `123456:[redacted]`. Masking needs nothing any more: `redact` and
925
- every logger on it mask bot tokens by shape; remove the pattern from
926
- `sensitiveUrlPatterns`, and use `redactTelegramBotToken(text)` for a string of
927
- your own.
928
-
929
- ```ts
930
- // Before
931
- createJsonLogger({ sensitiveUrlPatterns: [TELEGRAM_BOT_TOKEN_PATTERN] })
932
- text.replace(new RegExp(TELEGRAM_BOT_TOKEN_PATTERN.source, 'g'), '[redacted]')
933
- // After
934
- createJsonLogger({})
935
- redactTelegramBotToken(text)
936
- ```
@@ -1,14 +1,75 @@
1
- # Upgrading 0.61.0 → 0.73.0 — stitchkit docs slice
1
+ # Upgrading 0.61.0 → 0.74.0 — stitchkit docs slice
2
2
 
3
3
  > Generated from the `docs/` tree by `bun run gen:llms`. The index of all slices is
4
4
  > `llms.txt` in this package.
5
5
 
6
- Contains: Guide: Upgrading, 0.61.0 → 0.73.0 (docs/guide/upgrading.md)
6
+ Contains: Guide: Upgrading, 0.61.0 → 0.74.0 (docs/guide/upgrading.md)
7
7
 
8
8
  ==============================================================================
9
- # Guide: Upgrading, 0.61.0 → 0.73.0 (docs/guide/upgrading.md)
9
+ # Guide: Upgrading, 0.61.0 → 0.74.0 (docs/guide/upgrading.md)
10
10
  ==============================================================================
11
11
 
12
+ ## Released migration: 0.74.0
13
+
14
+ One change, and it only reaches you if you catch a refusal the **client** raised — one it made while
15
+ planning the request, before anything was sent. A server refusal is unchanged.
16
+
17
+ ### If you match on the text of a client-side refusal
18
+
19
+ A missing path param, a missing scoped prefix key, a non-flat field in a `GET` input, a missing or
20
+ invalid multipart file: each used to arrive as a plain `Error` carrying only a sentence. They now
21
+ arrive as an `ApiError` in the same shape a server validation failure uses.
22
+
23
+ ```ts
24
+ // before
25
+ catch (e) {
26
+ if (/Missing path param/.test(e.message)) …
27
+ }
28
+
29
+ // after
30
+ catch (e) {
31
+ if (e.code === 'VALIDATION_ERROR' && e.status === 0) … // refused here, nothing was sent
32
+ }
33
+ // e.details.issues[0].path === 'id'
34
+ ```
35
+
36
+ `status` is what separates the two worlds, and it is not new — `0` has always meant *this never
37
+ reached the server*, as `REQUEST_ABORTED` and `REQUEST_TIMEOUT` already used it:
38
+
39
+ | | `code` | `status` |
40
+ |---|---|---|
41
+ | the client refused your arguments | `VALIDATION_ERROR` | `0` |
42
+ | the server refused your arguments | `VALIDATION_ERROR` | `400` |
43
+
44
+ `details.issues` is the same `{ path, code, message }` array on both, so one rendering serves both —
45
+ and `zodIssues` / `ZodIssueSummary` are now importable from `stitchkit` itself, not only from
46
+ `stitchkit/server`.
47
+
48
+ ### If you are on `createHttpClient` and your call site relied on a synchronous throw
49
+
50
+ This is the sharp one, because it is invisible in a diff. Those refusals used to be thrown
51
+ **synchronously** on the Ky-backed client — before the call returned a promise — so
52
+ `api.upload({}).catch(handler)` never reached the handler, while the same mistake on the bare-fetch
53
+ client rejected normally. They now reject on both.
54
+
55
+ ```ts
56
+ // before, on createHttpClient only
57
+ try {
58
+ api.upload({}) // threw here
59
+ } catch (e) { … }
60
+
61
+ // after, on both transports
62
+ await api.upload({}).catch(handler)
63
+ ```
64
+
65
+ A `try/catch` around an `await`ed call keeps working. A `try/catch` around an un-awaited call no
66
+ longer catches anything — which was already true on the other transport.
67
+
68
+ Not a migration, but worth knowing: a missing multipart file used to be reported as
69
+ `UNKNOWN_ERROR`, the code that means *this client cannot tell you what happened*, on the one refusal
70
+ where it is certain nothing was sent. → ADR 0148 carries the reasoning, including why argument
71
+ validation stays on the server.
72
+
12
73
  ## Released migration: 0.73.0
13
74
 
14
75
  Two breaking changes. Nothing you send to a server changed, and only one of these
@@ -1,14 +1,74 @@
1
- # Upgrading 0.74.0 → 0.100.0 — stitchkit docs slice
1
+ # Upgrading 0.75.0 → 0.102.0 — stitchkit docs slice
2
2
 
3
3
  > Generated from the `docs/` tree by `bun run gen:llms`. The index of all slices is
4
4
  > `llms.txt` in this package.
5
5
 
6
- Contains: Guide: Upgrading, 0.74.0 → 0.100.0 (docs/guide/upgrading.md)
6
+ Contains: Guide: Upgrading, 0.75.0 → 0.102.0 (docs/guide/upgrading.md)
7
7
 
8
8
  ==============================================================================
9
- # Guide: Upgrading, 0.74.0 → 0.100.0 (docs/guide/upgrading.md)
9
+ # Guide: Upgrading, 0.75.0 → 0.102.0 (docs/guide/upgrading.md)
10
10
  ==============================================================================
11
11
 
12
+ ## Released migration: 0.102.0
13
+
14
+ ### `view_file` receives an array, not an array encoded as text
15
+
16
+ **Who must act:** callers sending `paths` as a JSON-encoded array string, or
17
+ matching the former media-extension refusal for that input. Managed MCP,
18
+ Agent and CLI surfaces now reject it as `VALIDATION_ERROR` before IO; raw MCP
19
+ reports an input-validation error. Correct single paths and arrays need no change.
20
+
21
+ ```ts
22
+ // Before
23
+ { paths: '["image.png"]' }
24
+ // After
25
+ { paths: ['image.png'] }
26
+ // One file may also be a string
27
+ { paths: 'image.png' }
28
+ ```
29
+
30
+ The same rule applies to an array element containing another encoded array.
31
+ Do not change generic string-union coercion: path strings remain strings.
32
+ A legitimate filename such as `[preview].PNG` is still accepted. Local
33
+ unsupported-extension errors now include the caller's path and extension;
34
+ match the error code rather than the former generic message.
35
+
36
+ ## Released migration: 0.101.0
37
+
38
+ ### `TelegramInitDataUser` is `TelegramUser`
39
+
40
+ **Who must act:** code that names the type `TelegramInitDataUser` from
41
+ `stitchkit/telegram`. The record is the same; its name now says it is any
42
+ Telegram user, since `parseTelegramUser(ctx.from)` returns it for a Bot API user
43
+ too.
44
+
45
+ ```ts
46
+ // Before
47
+ import type { TelegramInitDataUser } from 'stitchkit/telegram'
48
+ function ensureAccount(user: TelegramInitDataUser) {}
49
+ // After
50
+ import type { TelegramUser } from 'stitchkit/telegram'
51
+ function ensureAccount(user: TelegramUser) {}
52
+ ```
53
+
54
+ ### `TELEGRAM_BOT_TOKEN_PATTERN` matches the secret half only
55
+
56
+ **Who must act:** code that uses `TELEGRAM_BOT_TOKEN_PATTERN` to find or cut a
57
+ whole token. It now matches what follows `<bot id>:`, so a masking replace keeps
58
+ the bot's id — `123456:[redacted]`. Masking needs nothing any more: `redact` and
59
+ every logger on it mask bot tokens by shape; remove the pattern from
60
+ `sensitiveUrlPatterns`, and use `redactTelegramBotToken(text)` for a string of
61
+ your own.
62
+
63
+ ```ts
64
+ // Before
65
+ createJsonLogger({ sensitiveUrlPatterns: [TELEGRAM_BOT_TOKEN_PATTERN] })
66
+ text.replace(new RegExp(TELEGRAM_BOT_TOKEN_PATTERN.source, 'g'), '[redacted]')
67
+ // After
68
+ createJsonLogger({})
69
+ redactTelegramBotToken(text)
70
+ ```
71
+
12
72
  ## Released migration: 0.100.0
13
73
 
14
74
  ### watch capacity and session/live/delivery adoption
@@ -1026,64 +1086,3 @@ schema.
1026
1086
  `stitchkit/live`, the watch hub, the keyspace and the trust fence are all
1027
1087
  additive. Adopt them when you want them; the guide is
1028
1088
  [Live data](./live.md).
1029
-
1030
- ## Released migration: 0.74.0
1031
-
1032
- One change, and it only reaches you if you catch a refusal the **client** raised — one it made while
1033
- planning the request, before anything was sent. A server refusal is unchanged.
1034
-
1035
- ### If you match on the text of a client-side refusal
1036
-
1037
- A missing path param, a missing scoped prefix key, a non-flat field in a `GET` input, a missing or
1038
- invalid multipart file: each used to arrive as a plain `Error` carrying only a sentence. They now
1039
- arrive as an `ApiError` in the same shape a server validation failure uses.
1040
-
1041
- ```ts
1042
- // before
1043
- catch (e) {
1044
- if (/Missing path param/.test(e.message)) …
1045
- }
1046
-
1047
- // after
1048
- catch (e) {
1049
- if (e.code === 'VALIDATION_ERROR' && e.status === 0) … // refused here, nothing was sent
1050
- }
1051
- // e.details.issues[0].path === 'id'
1052
- ```
1053
-
1054
- `status` is what separates the two worlds, and it is not new — `0` has always meant *this never
1055
- reached the server*, as `REQUEST_ABORTED` and `REQUEST_TIMEOUT` already used it:
1056
-
1057
- | | `code` | `status` |
1058
- |---|---|---|
1059
- | the client refused your arguments | `VALIDATION_ERROR` | `0` |
1060
- | the server refused your arguments | `VALIDATION_ERROR` | `400` |
1061
-
1062
- `details.issues` is the same `{ path, code, message }` array on both, so one rendering serves both —
1063
- and `zodIssues` / `ZodIssueSummary` are now importable from `stitchkit` itself, not only from
1064
- `stitchkit/server`.
1065
-
1066
- ### If you are on `createHttpClient` and your call site relied on a synchronous throw
1067
-
1068
- This is the sharp one, because it is invisible in a diff. Those refusals used to be thrown
1069
- **synchronously** on the Ky-backed client — before the call returned a promise — so
1070
- `api.upload({}).catch(handler)` never reached the handler, while the same mistake on the bare-fetch
1071
- client rejected normally. They now reject on both.
1072
-
1073
- ```ts
1074
- // before, on createHttpClient only
1075
- try {
1076
- api.upload({}) // threw here
1077
- } catch (e) { … }
1078
-
1079
- // after, on both transports
1080
- await api.upload({}).catch(handler)
1081
- ```
1082
-
1083
- A `try/catch` around an `await`ed call keeps working. A `try/catch` around an un-awaited call no
1084
- longer catches anything — which was already true on the other transport.
1085
-
1086
- Not a migration, but worth knowing: a missing multipart file used to be reported as
1087
- `UNKNOWN_ERROR`, the code that means *this client cannot tell you what happened*, on the one refusal
1088
- where it is certain nothing was sent. → ADR 0148 carries the reasoning, including why argument
1089
- validation stays on the server.
@@ -11,9 +11,9 @@ Contains: Guide: Upgrading, general (docs/guide/upgrading.md)
11
11
 
12
12
  Load this slice, then every range slice between your installed version and the target:
13
13
 
14
- - 0.101.0 → 0.111.0: `llms/upgrading-0.101.0-0.111.0.txt`
15
- - 0.74.0 → 0.100.0: `llms/upgrading-0.74.0-0.100.0.txt`
16
- - 0.61.0 → 0.73.0: `llms/upgrading-0.61.0-0.73.0.txt`
14
+ - 0.103.0 → 0.112.0: `llms/upgrading-0.103.0-0.112.0.txt`
15
+ - 0.75.0 → 0.102.0: `llms/upgrading-0.75.0-0.102.0.txt`
16
+ - 0.61.0 → 0.74.0: `llms/upgrading-0.61.0-0.74.0.txt`
17
17
  - 0.44.0 → 0.60.0: `llms/upgrading-0.44.0-0.60.0.txt`
18
18
  - 0.37.0 → 0.37.0: `llms/upgrading-0.37.0.txt`
19
19
 
package/llms-full.txt CHANGED
@@ -77,12 +77,12 @@ own, recorded as an ADR.
77
77
  | `stitchkit/geo` | server (Bun or Node) | evolving | managed GeoIP reader generations, last-known-good reload and the optional MaxMind adapter |
78
78
  | `stitchkit/oauth` | browser **and** server | evolving | provider-neutral Authorization Code + PKCE request and one-shot callback transaction mechanics |
79
79
  | `stitchkit/google` | server (Bun or Node) | evolving | optional Google code exchange and verified OIDC identity adapter |
80
- | `stitchkit/observability` | server | stable<br>_redefined in 2 of the 56 minors since 0.56.2, most recently 0.92.0_ | request/tool event projections — `createObservability`, trace context, sanitisation |
80
+ | `stitchkit/observability` | server | stable<br>_redefined in 2 of the 57 minors since 0.56.2, most recently 0.92.0_ | request/tool event projections — `createObservability`, trace context, sanitisation |
81
81
  | `stitchkit/testing` | tests on Bun or Node | stable | in-process generated clients over a real Fetch handler, the managed-resource conformance kit and manual time for schedules and retries |
82
82
  | `stitchkit/declaration` | browser + build and deployment tooling (Bun or Node) | evolving | `ProjectDeclarationSchema` — the one machine-readable statement a repository makes about itself |
83
83
  | `stitchkit/react` | browser + server rendering | stable | `createCursorQuery`, `createCacheBridge`, QueryClient and `ApiError` retry policy |
84
84
  | `stitchkit/react/keyboard` | browser | evolving | which part of a screen gets a key — ordered layers, one Escape per level, list zones with real focus; see [keyboard guide](keyboard.md) |
85
- | `stitchkit/agent-runtime` | server | evolving<br>_redefined in 23 of the 56 minors since 0.56.2, most recently 0.105.0_ | optional durable conversation/run loop, history, models, prompts, fencing and events |
85
+ | `stitchkit/agent-runtime` | server | evolving<br>_redefined in 23 of the 57 minors since 0.56.2, most recently 0.105.0_ | optional durable conversation/run loop, history, models, prompts, fencing and events |
86
86
  | `stitchkit/agent-runtime/sandbox` | server | evolving | optional Linux Bubblewrap sessions, durable workspaces and a host HTTP credential gateway; see [sandbox guide](sandbox.md) |
87
87
  | `stitchkit/agent-runtime/testing` | tests on Bun or Node | evolving | credential-free replay, scripted provider faults, deterministic race controls and the agent-store conformance kit |
88
88
  | `stitchkit/agent-runtime/harness` | server | evolving | resource-aware process-local facade over the canonical Agent runtime; supervision stays outside |
@@ -94,7 +94,7 @@ own, recorded as an ADR.
94
94
  | `stitchkit/agent-runtime/sqlite/bun` | server (Bun) | evolving | durable built-in SQLite store for the agent runtime |
95
95
  | `stitchkit/agent-runtime/sqlite/node` | server (Node ≥ 22.5) | evolving | durable built-in SQLite store for the agent runtime |
96
96
  | `stitchkit-tui` | terminal (Bun) | evolving | optional official OpenTUI host over a caller-composed headless runtime |
97
- | `stitchkit/application` | browser + server | evolving<br>_redefined in 14 of the 56 minors since 0.56.2, most recently 0.110.0_ | managed resource graph, readiness, admission, schedules, subtree restart and bounded shutdown |
97
+ | `stitchkit/application` | browser + server | evolving<br>_redefined in 14 of the 57 minors since 0.56.2, most recently 0.110.0_ | managed resource graph, readiness, admission, schedules, subtree restart and bounded shutdown |
98
98
  | `stitchkit/application/grammy` | server | evolving | a grammY bot as application resources — configuration, then polling with updates admitted by batch — and the webhook adapter |
99
99
  | `stitchkit/application/opentelemetry` | server | evolving | maps application snapshots onto an injected OpenTelemetry `Meter` |
100
100
  | `stitchkit/application/schemas` | browser + server | evolving | the application's snapshot, health and shutdown schemas alone, without the kernel |
@@ -6706,6 +6706,62 @@ replace a leading summary on the next compaction.
6706
6706
  Set `maxAttempts` to allow bounded conflict recovery. Every retry reloads the snapshot, reselects the
6707
6707
  eligible range and recomputes the summary; the stale summary is never retried.
6708
6708
 
6709
+ ### Recovering a prefix after a failed turn
6710
+
6711
+ The default evidence policy omits failed assistants. A failed turn therefore stops the
6712
+ compactable prefix, including completed turns after it. A terminal failure is not an
6713
+ unanswered tool call, but opting its partial evidence into a summary is an explicit host
6714
+ decision. Use the existing `evidencePolicy: { failedAssistant: 'assistant-marked' }` on
6715
+ `structuredCompaction` (or `selectCompactableHistory`) when that evidence is wanted.
6716
+ The same policy belongs on the summarizer's history projection and on any corresponding
6717
+ token-budget selection. Stored statuses remain unchanged.
6718
+
6719
+ The summarizer receives **raw** `eligibleMessages`, not provider messages and not an
6720
+ automatic failure marker. Project them with the same policy before sending them to a
6721
+ model, and preserve failed outcomes in the application's structured summary and prompt:
6722
+
6723
+ ```ts
6724
+ const evidencePolicy: AgentHistoryEvidencePolicy = {
6725
+ failedAssistant: 'assistant-marked',
6726
+ }
6727
+
6728
+ // Inside structuredCompaction({ evidencePolicy, ... }).summarize:
6729
+ const projected = await projectAgentHistoryDetailed(eligibleMessages, {
6730
+ evidencePolicy,
6731
+ incompleteToolTurn: 'error',
6732
+ })
6733
+ const failedMessageIds = eligibleMessages
6734
+ .filter(message => message.role === 'assistant' && message.status === 'failed')
6735
+ .map(message => message.id)
6736
+ // Pass projected.messages, projected.system and failedMessageIds to the host's
6737
+ // summarizer. Its schema/prompt must retain failures and uncertain effects as
6738
+ // such, merge previousSummary, and never describe partial output as success.
6739
+ ```
6740
+
6741
+ An opted-in failed assistant projects an explicit non-success marker even when it has
6742
+ no text or only control parts. A dangling call, unresolved approval, invalid result
6743
+ chronology or a user input with no assistant remains a barrier under either policy.
6744
+ Raw whole-turn completeness does not guarantee that every record can be projected:
6745
+ for example, a call and its result split across records may be rejected by the projector.
6746
+ `incompleteToolTurn: 'error'` makes that rejection stop summarization; the original history
6747
+ stays canonical. A host with such records must use a status-aware summarizer that explicitly
6748
+ represents every relevant record, or retain the range. Do not silently summarize an omitted
6749
+ exchange, filter the failed record out of the replacement range, rewrite its status or skip
6750
+ over an incomplete turn. The replacement remains one contiguous prefix under snapshot CAS.
6751
+
6752
+ Provider chronology and external effect authority answer different questions (ADR 0269).
6753
+ A matching `tool-result` with `outcome: 'error'` and an application code such as `UNKNOWN`
6754
+ closes the provider exchange; it does **not** prove the external effect failed or succeeded.
6755
+ Compaction changes the model-history projection, not the canonical event journal or the
6756
+ retained terminal admission receipt. The `durability/effect` intent, accepted proof and
6757
+ uncertain outcome remain available after compaction and archive restore. A reconstructed
6758
+ `createLocalStepDurability` using the original `(conversationId, runId, effectName)` returns
6759
+ the accepted proof, reconciles a bare intent, or returns `uncertain` without calling `run`
6760
+ again. It does not deduplicate a new run or a newly named effect. Standalone effect callers
6761
+ still own the exclusive execution lease; sharing a ledger is not an atomic claim.
6762
+ An application-owned receipt outside that journal retains its own recovery rules and must
6763
+ not be discarded because a history turn became compactable.
6764
+
6709
6765
  ### Compacting without the store
6710
6766
 
6711
6767
  The *selection* inside compaction needs no store, no version and no runtime, so
@@ -9941,6 +9997,15 @@ string, the schema says what it should be:
9941
9997
  | `z.record(z.string(), z.unknown())` | `--data '{"code":"007"}'` or `--data.code=007` → `"007"` |
9942
9998
  | `.optional()` / `.default()` | not required |
9943
9999
 
10000
+ An array option takes **one value per occurrence**, never a bare list after the flag.
10001
+ Repeat it (`--tag a --tag b`) or assign the whole array (`--tag:='["a","b"]'`).
10002
+ Without an explicit command `positionals` policy, combining any named array option
10003
+ with bare positionals is refused before dispatch: schema-order inference could otherwise
10004
+ assign the remaining array elements to unrelated fields. This includes short aliases,
10005
+ dotted array leaves, whole-array JSON and tokens after `--`, regardless of token order.
10006
+ Name the scalar fields too, or declare their exact positional destinations. An explicitly
10007
+ declared trailing positional array is the variadic form described below.
10008
+
9944
10009
  Use the inline `:=` form to assign a whole JSON value without scalar coercion:
9945
10010
 
9946
10011
  ```bash
@@ -10216,7 +10281,7 @@ Declared aliases render beside their canonical options, for example
10216
10281
 
10217
10282
  The argument table and `Application options:` show bounds from their JSON Schema,
10218
10283
  for example `<integer> [>=1, <=50]`, `<string> [length >=1, length <=12]`, and
10219
- `<value…> [items >=2, items <=4]`. Numeric `>` and `<` are exclusive, while `>=`
10284
+ `<value> [items >=2, items <=4]`. Numeric `>` and `<` are exclusive, while `>=`
10220
10285
  and `<=` include the endpoint; a zero limit is displayed too. Only limits the
10221
10286
  schema declares are shown: the safe-integer range every `z.int()` carries is the
10222
10287
  type, not a bound, so a bare integer prints `<integer>`, `.min(1)` prints `>=1` and
@@ -16193,6 +16258,37 @@ analytics policy belong to the application.
16193
16258
 
16194
16259
  # Upgrading stitchkit
16195
16260
 
16261
+ ## Released migration: 0.112.0
16262
+
16263
+ **Who must act:** scripts using an array option together with bare positional arguments without a declared `positionals` policy, including calls to `parseCliArgs`.
16264
+
16265
+ An array flag consumes one value. Automatic schema-order positionals cannot distinguish additional array elements from values intended for unrelated fields, so the combination is refused before any handler or stdin read. The rule is independent of token order, includes aliases, dotted array leaves and whole-array JSON, and `--` does not bypass it. See [ADR 0268](../decisions/0268-named-cli-arrays-require-explicit-positional-destinations.md).
16266
+
16267
+ ```sh
16268
+ # before: the last two words could populate unrelated fields
16269
+ app resolve --references alpha.md beta.md gamma.md
16270
+ # after: repeat the option or assign the whole array
16271
+ app resolve --references alpha.md --references beta.md --references gamma.md
16272
+ app resolve --references:='["alpha.md","beta.md","gamma.md"]'
16273
+
16274
+ # before: even an intended scalar positional has an implicit destination
16275
+ app resolve project-one --references alpha.md
16276
+ # after: name it
16277
+ app resolve --projectId project-one --references alpha.md
16278
+ ```
16279
+
16280
+ Applications that intend positional syntax declare its destinations on the available command:
16281
+
16282
+ ```ts
16283
+ createCli({ ...config, positionals: { resolve: ['projectId'] } })
16284
+ // app resolve project-one --references alpha.md
16285
+
16286
+ createCli({ ...config, positionals: { resolve: ['references'] } })
16287
+ // app resolve alpha.md beta.md gamma.md
16288
+ ```
16289
+
16290
+ A declared trailing array accepts all remaining positional elements. Giving that same field both a flag and positional elements remains an error. An empty policy accepts flags only. Policies still target exact available command names: a partial dynamic catalogue needs no policy for an unavailable command to enforce this admission rule.
16291
+
16196
16292
  ## Released migration: 0.111.0
16197
16293
 
16198
16294
  **Who must act:** applications whose raw routes call `parseBody` on JSON larger than 1 MiB, and applications whose OAuth clients send `/register` or `/token` bodies larger than 64 KiB.
@@ -22248,6 +22344,16 @@ projection, budgeting and structured compaction; `isAssistantHistoryEvidence` ap
22248
22344
  compatibility default omits it. Approval message schemas are
22249
22345
  `AgentToolApprovalRequestPartSchema` and `AgentToolApprovalResponsePartSchema`.
22250
22346
 
22347
+ With `failedAssistant: 'assistant-marked'`, `selectCompactableHistory` and
22348
+ `structuredCompaction` admit a failed prefix only when the entire turn's tool chronology
22349
+ is complete. `eligibleMessages` stays raw: the summarizer must use the same policy in
22350
+ `projectAgentHistoryDetailed` and preserve non-success/uncertain outcomes in its summary.
22351
+ Even an empty opted-in failed assistant carries a non-success marker in that projection.
22352
+ Use `incompleteToolTurn: 'error'` to refuse a range the projector cannot represent.
22353
+ Compaction never deletes canonical effect events or the retained terminal receipt;
22354
+ chronological completeness does not settle an external effect. See the
22355
+ [failed-prefix recovery contract](../guide/agent-runtime.md#recovering-a-prefix-after-a-failed-turn).
22356
+
22251
22357
  Model exports are `AgentLanguageModelProvider`, `AgentModelCapability`,
22252
22358
  `AgentModelCapabilitySchema`, `AgentModelDescriptor`,
22253
22359
  `AgentModelDescriptorSchema`, `AgentModelRegistry`, `AgentModelRegistryConfig`,
@@ -23410,7 +23516,7 @@ SDK nor the `ai` peer.
23410
23516
  |--------|------|---------|
23411
23517
  | `createCli` | function | build and run a CLI from contracts — [guide](../guide/cli.md) |
23412
23518
  | `defineCliCommand` | function | define one Zod-typed CLI-only executable command with optional validated-result presentation/exit policy |
23413
- | `parseCliArgs` | function | argv → typed tool args against a schema; inline `--field:=JSON` assigns a whole JSON value, including null, while ordinary string flags remain lexical (advanced) |
23519
+ | `parseCliArgs` | function | argv → typed tool args against a schema; inline `--field:=JSON` assigns a whole JSON value, including null, while ordinary string flags remain lexical; named array options together with bare positionals require an explicit `positionals` policy (advanced) |
23414
23520
  | `routeCliArgv` | function | select the command out of argv without duplicating the global-option grammar (advanced) |
23415
23521
  | `extractCliGlobalOptions` | function | lift the application's own global options out of argv before routing (advanced) |
23416
23522
  | `coerceJsonArgs` | function | the second half of `parseCliArgs` — parse array/object values a consumer sends itself, including discovered MCP object guards restored from JSON Schema, without the `stitchkit/tools` barrel |
package/llms.txt CHANGED
@@ -18,14 +18,14 @@ Load the slice for each entrypoint your code imports — `import … from 'stitc
18
18
  - **stitchkit/tools/contract**: [llms/tools-contract.txt](llms/tools-contract.txt) 0.2 KB
19
19
  - **stitchkit/tools/invoker**: [llms/tools-invoker.txt](llms/tools-invoker.txt) 0.2 KB
20
20
  - **stitchkit/tools/connections**: [llms/tools-connections.txt](llms/tools-connections.txt) 3.8 KB
21
- - **stitchkit/cli**: [llms/cli.txt](llms/cli.txt) 44.6 KB → [llms/cli.2.txt](llms/cli.2.txt) 21.3 KB
21
+ - **stitchkit/cli**: [llms/cli.txt](llms/cli.txt) 45.3 KB → [llms/cli.2.txt](llms/cli.2.txt) 21.4 KB
22
22
  - **stitchkit/cli/publish**: [llms/cli-publish.txt](llms/cli-publish.txt) 1.5 KB
23
23
  - **stitchkit/remote**: [llms/remote.txt](llms/remote.txt) 1.0 KB
24
24
  - **stitchkit/contract**: [llms/contract.txt](llms/contract.txt) 31.9 KB
25
25
  - **stitchkit/primitives**: [llms/primitives.txt](llms/primitives.txt) 11.7 KB
26
26
  - **stitchkit/server**: [llms/server.txt](llms/server.txt) 44.2 KB → [llms/server.2.txt](llms/server.2.txt) 20.5 KB → [llms/server.3.txt](llms/server.3.txt) 45.6 KB → [llms/server.4.txt](llms/server.4.txt) 38.3 KB → [llms/server.5.txt](llms/server.5.txt) 22.9 KB
27
27
  - **stitchkit/observability**: [llms/observability.txt](llms/observability.txt) 46.9 KB
28
- - **stitchkit/agent-runtime**: [llms/agent-runtime.txt](llms/agent-runtime.txt) 42.7 KB → [llms/agent-runtime.2.txt](llms/agent-runtime.2.txt) 46.7 KB → [llms/agent-runtime.3.txt](llms/agent-runtime.3.txt) 35.6 KB
28
+ - **stitchkit/agent-runtime**: [llms/agent-runtime.txt](llms/agent-runtime.txt) 42.7 KB → [llms/agent-runtime.2.txt](llms/agent-runtime.2.txt) 44.8 KB → [llms/agent-runtime.3.txt](llms/agent-runtime.3.txt) 42.1 KB
29
29
  - **stitchkit/agent-runtime/testing**: [llms/agent-runtime-testing.txt](llms/agent-runtime-testing.txt) 2.6 KB
30
30
  - **stitchkit/agent-runtime/harness**: [llms/agent-runtime-harness.txt](llms/agent-runtime-harness.txt) 11.0 KB
31
31
  - **stitchkit/agent-runtime/coding-tools**: [llms/agent-runtime-coding-tools.txt](llms/agent-runtime-coding-tools.txt) 5.0 KB
@@ -64,9 +64,9 @@ Load the slice for each entrypoint your code imports — `import … from 'stitc
64
64
 
65
65
  ## Upgrading slices
66
66
  - **Upgrading — how to move across versions**: [llms/upgrading.txt](llms/upgrading.txt) 12.7 KB
67
- - **Upgrading 0.101.0 → 0.111.0**: [llms/upgrading-0.101.0-0.111.0.txt](llms/upgrading-0.101.0-0.111.0.txt) 46.9 KB
68
- - **Upgrading 0.74.0 → 0.100.0**: [llms/upgrading-0.74.0-0.100.0.txt](llms/upgrading-0.74.0-0.100.0.txt) 45.1 KB
69
- - **Upgrading 0.61.0 → 0.73.0**: [llms/upgrading-0.61.0-0.73.0.txt](llms/upgrading-0.61.0-0.73.0.txt) 41.2 KB
67
+ - **Upgrading 0.103.0 → 0.112.0**: [llms/upgrading-0.103.0-0.112.0.txt](llms/upgrading-0.103.0-0.112.0.txt) 46.6 KB
68
+ - **Upgrading 0.75.0 → 0.102.0**: [llms/upgrading-0.75.0-0.102.0.txt](llms/upgrading-0.75.0-0.102.0.txt) 44.8 KB
69
+ - **Upgrading 0.61.0 → 0.74.0**: [llms/upgrading-0.61.0-0.74.0.txt](llms/upgrading-0.61.0-0.74.0.txt) 43.5 KB
70
70
  - **Upgrading 0.44.0 → 0.60.0**: [llms/upgrading-0.44.0-0.60.0.txt](llms/upgrading-0.44.0-0.60.0.txt) 43.9 KB
71
71
  - **Upgrading 0.37.0 → 0.37.0**: [llms/upgrading-0.37.0.txt](llms/upgrading-0.37.0.txt) 6.2 KB
72
72
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stitchkit",
3
- "version": "0.111.3",
3
+ "version": "0.112.1",
4
4
  "description": "Contract-first backend framework — one defineContract() into an HTTP API, MCP tools, AI-agent tools and a typed client. Bun and Node.",
5
5
  "keywords": [
6
6
  "bun",