stitchkit 0.112.0 → 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,13 @@ 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
+
18
25
  ## [0.112.0] - 2026-10-24
19
26
 
20
27
  ### ⚠️ Breaking changes
@@ -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,
@@ -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
@@ -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-full.txt CHANGED
@@ -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
@@ -22288,6 +22344,16 @@ projection, budgeting and structured compaction; `isAssistantHistoryEvidence` ap
22288
22344
  compatibility default omits it. Approval message schemas are
22289
22345
  `AgentToolApprovalRequestPartSchema` and `AgentToolApprovalResponsePartSchema`.
22290
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
+
22291
22357
  Model exports are `AgentLanguageModelProvider`, `AgentModelCapability`,
22292
22358
  `AgentModelCapabilitySchema`, `AgentModelDescriptor`,
22293
22359
  `AgentModelDescriptorSchema`, `AgentModelRegistry`, `AgentModelRegistryConfig`,
package/llms.txt CHANGED
@@ -25,7 +25,7 @@ Load the slice for each entrypoint your code imports — `import … from 'stitc
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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stitchkit",
3
- "version": "0.112.0",
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",