stitchkit 0.96.1 → 0.98.0

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.
Files changed (40) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/dist/agent-runtime/schedule-contract.d.ts +52 -0
  3. package/dist/agent-runtime/schedule-contract.d.ts.map +1 -0
  4. package/dist/agent-runtime/schedule-dispatch.d.ts +26 -0
  5. package/dist/agent-runtime/schedule-dispatch.d.ts.map +1 -0
  6. package/dist/agent-runtime/schedule-records.d.ts +8 -14
  7. package/dist/agent-runtime/schedule-records.d.ts.map +1 -1
  8. package/dist/agent-runtime/schedule-tools.d.ts +1 -1
  9. package/dist/agent-runtime/schedule-tools.d.ts.map +1 -1
  10. package/dist/agent-runtime/schedules.d.ts +7 -92
  11. package/dist/agent-runtime/schedules.d.ts.map +1 -1
  12. package/dist/agent-runtime/sqlite-schema.d.ts.map +1 -1
  13. package/dist/agent-runtime/store-migrations/v3-to-v4.d.ts +4 -0
  14. package/dist/agent-runtime/store-migrations/v3-to-v4.d.ts.map +1 -0
  15. package/dist/entrypoints/agent-runtime/sqlite/bun.js +1 -1
  16. package/dist/entrypoints/agent-runtime/sqlite/node.js +1 -1
  17. package/dist/entrypoints/agent-runtime.d.ts +2 -1
  18. package/dist/entrypoints/agent-runtime.d.ts.map +1 -1
  19. package/dist/entrypoints/agent-runtime.js +289 -214
  20. package/dist/entrypoints/cli.js +1 -1
  21. package/dist/entrypoints/tools.js +1 -1
  22. package/dist/{index-p9e0w8hs.js → index-1m2thnq0.js} +25 -1
  23. package/dist/{index-t9rhjr9f.js → index-f1nb70ae.js} +45 -9
  24. package/dist/tools/cli/config.d.ts +5 -1
  25. package/dist/tools/cli/config.d.ts.map +1 -1
  26. package/dist/tools/cli/io.d.ts.map +1 -1
  27. package/dist/tools/cli/stdin.d.ts +6 -0
  28. package/dist/tools/cli/stdin.d.ts.map +1 -0
  29. package/llms/agent-runtime.2.txt +224 -0
  30. package/llms/agent-runtime.3.txt +1 -1
  31. package/llms/agent-runtime.txt +18 -225
  32. package/llms/cli.txt +15 -0
  33. package/llms/stitchkit.txt +3 -3
  34. package/llms/{upgrading-0.37.0-0.56.0.txt → upgrading-0.37.0-0.58.0.txt} +82 -3
  35. package/llms/{upgrading-0.57.0-0.69.0.txt → upgrading-0.59.0-0.71.0.txt} +108 -82
  36. package/llms/{upgrading-0.70.0-0.96.0.txt → upgrading-0.72.0-0.98.0.txt} +64 -108
  37. package/llms/upgrading.txt +3 -3
  38. package/llms-full.txt +98 -5
  39. package/llms.txt +5 -5
  40. package/package.json +1 -1
@@ -19,7 +19,7 @@ description: Configure Stitchkit's optional durable history, stream loop, run co
19
19
  type: architecture
20
20
  status: active
21
21
  created: 2026-08-22
22
- updated: 2026-08-29
22
+ updated: 2026-09-26 16:00 +07:00
23
23
  ---
24
24
 
25
25
  # Agent application runtime
@@ -636,6 +636,23 @@ has invoked a tool is never automatically replayed.
636
636
  `createAgentScheduleService` persists `at`, `after` and `every` input. Repeating
637
637
  schedules require an explicit IANA time zone. Dispatch uses a stable
638
638
  `schedule:<id>:<occurrence>` idempotency key, and restart lateness is recorded.
639
+ Resolve `void` after durable admission; return `{ status: 'retry', reason }` for a
640
+ transient refusal or `{ status: 'terminal', reason }` to stop the entire schedule in
641
+ `failed` state. Throws retry too. Backoff is 1, 2, 4, 8, 16, 32, then 60 seconds,
642
+ capped at 60 seconds and persisted across restart. `retryAt`, `attempts` and `lastError`
643
+ show retry diagnostics; success resets them. `nextAt` and `lateByMs` retain the original
644
+ occurrence time. Recurring schedules fire once after an idle stretch and advance to the
645
+ next future slot on their original cadence.
646
+
647
+ Dispatch receives an `AbortSignal`, has a 30-second deadline and holds a unique
648
+ 60-second lease. A late or cancelled attempt cannot settle or emit firing events.
649
+ Honor the signal and durably deduplicate the key: a crash after admission but before
650
+ settlement can replay delivery. This is at-least-once, not exactly-once.
651
+ Due selection is indexed, limited to 32 parallel deliveries per tick; one hung batch
652
+ can delay newly due work by at most its deadline. The service keeps one nearest-due
653
+ timer plus per-attempt deadline timers. Storage failures use a bounded recovery timer;
654
+ idle ticks do not write. SQLite v4 migration preserves due times and claims; stop older
655
+ scheduler processes before migration. See [ADR 0202](../decisions/0202-schedule-retries-preserve-occurrence-identity.md).
639
656
  `createAgentScheduleTools` exposes the exact `schedule_after`, `schedule_at`,
640
657
  `schedule_every`, `schedule_list` and `schedule_cancel` surface.
641
658
 
@@ -667,227 +684,3 @@ a successor input into the run already in flight.
667
684
  ```text
668
685
  input + queued run → running → execution settled → terminal CAS → successor
669
686
  ```
670
-
671
- ## What happens to a run when new input arrives
672
-
673
- `runs.inputPolicy` decides. It takes five values, or a function returning one:
674
-
675
- | policy | the run in flight | what it already produced |
676
- |--------|-------------------|--------------------------|
677
- | `queue` (default) | finishes first | kept |
678
- | `inject` | continues, and answers the new input too | kept, and built on |
679
- | `interrupt` | ends | kept, and marked as cut off |
680
- | `interrupt-next` | ends; this input runs before ordinary queued work | kept, and marked as cut off |
681
- | `supersede` | ends | discarded from the prompt, kept in the record |
682
-
683
- `interrupt-next` is the explicit priority path for an urgent input. If A is
684
- running, ordinary B is queued, and urgent C arrives, the coordinator aborts A,
685
- waits for A to settle and then runs C before B. B keeps its durable identity and
686
- eventually runs. Urgent inputs remain FIFO among themselves. They do not
687
- coalesce into an ordinary pending run: that would erase the priority boundary.
688
-
689
- `interrupt` and `supersede` differ in exactly one thing, and the question that
690
- picks between them is **not** "was the run interrupted" but **"did anyone see
691
- what it produced"**:
692
-
693
- - The user pressed **stop**. The partial answer was streamed to their screen and
694
- they read it. It belongs in the conversation — dropping it makes the history
695
- lie to the model about what the human has seen. That is `interrupt`.
696
- - A newer message **superseded** the run. Whether the partial reached anyone
697
- depends on the delivery surface: a token stream shows it as it is produced, a
698
- surface that sends nothing until the run is done never showed it at all. When
699
- it reached nobody, it is not part of the conversation. That is `supersede`.
700
-
701
- **Stitchkit cannot answer that question for you** — delivery belongs to the
702
- transport, and the runtime sees an abort, not a screen. Hence a declared policy
703
- (→ ADR 0108), and hence `inputPolicy` accepting a function, so one application
704
- can hold two surfaces with different rules without the core learning which is
705
- which:
706
-
707
- ```ts
708
- runs: {
709
- inputPolicy: (input) =>
710
- protocol.parseContext(input.context).surface === 'operator' ? 'queue' : 'supersede',
711
- }
712
- ```
713
-
714
- `input.context` is the **raw** context here — admission runs before the runtime
715
- parses it, so the callback narrows it itself with the protocol it already has.
716
-
717
- A superseded run ends with `terminalReason: 'superseded'`, run state
718
- `'superseded'` and an assistant message of status `'superseded'`. **The record
719
- is kept** — excluded from the projection, not deleted — so an operator can see
720
- what was thrown away, and run identity, admission receipts and the terminal CAS
721
- keep the row they depend on. Compaction leaves it alone for the same reason: a
722
- turn whose answer is never spoken is not a turn that may be summarised into one,
723
- because that would both feed the discarded text to the summariser and drop the
724
- record in `replacedMessageIds`.
725
-
726
- It is also outside the token budget. `selectAgentHistory` removes it with reason
727
- `'superseded'` and does not count it, so an abandoned fragment cannot push a
728
- real turn out of a context it never occupies.
729
-
730
- ### How an interrupted turn reaches the model
731
-
732
- An interrupted turn is projected, and says so:
733
-
734
- ```text
735
- { role: 'assistant', content: [
736
- { type: 'text', text: 'We are the team, where would you like' },
737
- { type: 'text', text: '[interrupted: this turn was cut off before it finished]' },
738
- ]}
739
- ```
740
-
741
- `history.interruptedAssistant` chooses the form, and the difference between the
742
- first two is structural rather than cosmetic. **An assistant turn in provider
743
- history is a commitment**: the model reads its own previous turn as something it
744
- said and stays consistent with it. A system line is context.
745
-
746
- | value | form | right when |
747
- |-------|------|-----------|
748
- | `assistant-marked` (default) | assistant turn plus a marker | the human read the text |
749
- | `system-note` | `[interrupted] partial response: …` as a system line | the fragment reached nobody |
750
- | `omit` | not projected at all | you want it gone from the request |
751
-
752
- There is deliberately no value that reproduces what the projection used to do,
753
- which was to send the partial as an ordinary assistant turn and drop its
754
- `control` marker on the way. That was the defect, not a behaviour to stay
755
- compatible with.
756
-
757
- `projectAgentHistoryDetailed` reports what reached the provider, including part
758
- types that no projected content stands for:
759
-
760
- ```ts
761
- const { decisions } = await projectAgentHistoryDetailed(snapshot.messages)
762
- // → { messageId: 'assistant-2', action: 'projected', reason: 'projected',
763
- // omittedParts: ['source', 'provider'] }
764
- // → { messageId: 'assistant-1', action: 'omitted', reason: 'superseded' }
765
- ```
766
-
767
- `runtime.stop(key, 'supersede')` is the same decision taken by hand: the
768
- process-local escape hatch chooses the reason, so a caller that knows the answer
769
- was never delivered can discard it without a newer input arriving.
770
-
771
- ### An input that joins a run in flight
772
-
773
- `inject` is right when the new input **refines** rather than redirects, and the
774
- steps already taken are still worth something: *"summarise this thread… actually,
775
- in bullet points"* should not throw away the reading the first message paid for.
776
-
777
- What happens:
778
-
779
- 1. The input is admitted exactly like any other — a committed user message and a
780
- **queued run**, durable before anything else happens.
781
- 2. At the running loop's next step boundary, that input joins its prompt. Only
782
- that input: an unrelated queued submission is never carried in.
783
- 3. When the run finishes, its terminal commit — **one transaction** — records
784
- the input as one the run answered and settles the queued successor with
785
- `terminalReason: 'absorbed'` and `absorbedIntoRunId` naming the run that
786
- answered it. Every ticket for that successor resolves to the same answer.
787
-
788
- Nothing durable happens between 1 and 3, and that is the design (→ ADR 0113).
789
- A run that crashes, is closed, or is interrupted after taking an input on
790
- commits no absorption at all, so what is left behind is an ordinary queued
791
- successor — the state every other policy already produces and recovery already
792
- handles. **There is no ordering in which an accepted input becomes
793
- unanswerable.** Only a run that *completes* may absorb; an interrupted one took
794
- the input into its prompt and then stopped, and does not get to say it answered
795
- it.
796
-
797
- An absorbed run has **no assistant message of its own** — it produced none, and
798
- writing an empty one would be a record claiming otherwise. Its answer is
799
- reachable through `absorbedIntoRunId`, and the store follows that pointer itself
800
- when a submission arrives on the absorbed run's idempotency key, so a retry
801
- after a restart returns the answer rather than an empty terminal record.
802
-
803
- It does publish one more `run-state` event, carrying `'superseded'`. It never
804
- enters the run executor, so that event is the only thing that tells a delivery
805
- surface following its `runId` that it is no longer queued.
806
-
807
- With `coalescePending`, one successor can carry several inputs, and an
808
- absorption covers a successor **whole or not at all** — a partial one would
809
- leave a terminal run with inputs nobody answered. Inputs that coalesce before
810
- the absorbing run's last step boundary join the same absorption. One that
811
- arrives after it cancels the absorption: the successor then answers all of its
812
- inputs itself, and the absorbing run has answered one of them too. A duplicate
813
- answer, never a missing one.
814
-
815
- (0.63.0 shipped a version of this that committed the absorption at the step
816
- boundary, before the answer existed, and it was withdrawn in 0.65.0. ADR 0113
817
- records what that ordering broke.)
818
-
819
- With `runs.coalescePending: true`, an active lane has at most one queued
820
- successor. Every later accepted input is atomically appended to that successor;
821
- its `AgentRun.inputMessageIds` records the whole batch and every input ticket
822
- resolves to the same terminal run. Coalescing never mutates the active run.
823
-
824
- `AbortSignal.aborted` is only a cooperation request. A successor does not begin
825
- while the predecessor still owns managed callbacks. A hung predecessor blocks
826
- the lane in the first version.
827
-
828
- Shutdown is two-phase, and both budgets carry the names they carry everywhere
829
- else in Stitchkit:
830
-
831
- ```ts
832
- const closed = await runtime.close({ gracePeriodMs: 30_000, forceTimeoutMs: 5_000 })
833
- if (!closed.settled) {
834
- console.warn(`exiting with ${closed.remaining} run(s) still in flight`)
835
- }
836
- ```
837
-
838
- `close` first rejects new process-local admissions and gives active runs
839
- `gracePeriodMs` to finish on their own. Only after that budget expires does it
840
- abort them with reason `shutdown`; `forceTimeoutMs` then bounds the settlement
841
- wait for a non-cooperative model or tool, measured from the abort — the two
842
- budgets add up rather than overlapping.
843
-
844
- **`close()` reports what it achieved rather than promising an outcome it cannot
845
- reach.** The result is `{ settled, timedOut, remaining }`: `settled` when every
846
- in-flight run finished, `timedOut` with a `remaining` count when the force
847
- budget expired first. There is no combination of budgets that is both bounded
848
- and guaranteed to leave nothing in flight — that is the trade the budgets exist
849
- to make, and the result is where you read which side of it you got:
850
-
851
- | budgets | behaviour | can return with a run in flight |
852
- |---------|-----------|---------------------------------|
853
- | neither | aborts immediately, waits for settlement | no — unbounded wait |
854
- | `gracePeriodMs` only | waits, aborts, then waits for settlement | no — unbounded wait |
855
- | `forceTimeoutMs` only | aborts immediately, waits at most that long | **yes** — `timedOut` |
856
- | both | waits, aborts, waits at most that long | **yes** — `timedOut` |
857
-
858
- A caller that wants the old shape still writes `await runtime.close(…)` and
859
- ignores the result. A caller deciding whether to exit the process reads it.
860
- Durably queued records rejected from the local queue remain recoverable through
861
- `scanRecoverable`; close never marks them terminal on its own.
862
-
863
- Use `await runtime.interrupt({ conversationId, runId })` when the interruption
864
- must be durable: it first commits `interrupt_requested`, then aborts the local
865
- coordinator signal. If provider completion races that revision change, the terminal path reloads
866
- the canonical snapshot. An already-terminal winner settles the ticket directly; a still-owned
867
- `interrupt_requested` run is committed as `interrupted`, and unrelated aggregate-head conflicts
868
- remain retriable while the run is active with the same owner and fencing token. A stale owner or
869
- fencing token remains a conflict. Only the execution that applies the terminal mutation publishes the
870
- **delivery** `terminal` event — a loser settles from canonical state without republishing the turn,
871
- and its `AgentRuntimeResult.metrics` is `undefined`. The **operator** `run-terminal` event is not
872
- gated that way: a losing execution still ran, and still spent whatever it spent, so it reports its
873
- own usage. The two channels answer to different readers — delivering a turn twice is a user's
874
- problem, and omitting a run's cost is an operator's.
875
- `runtime.stop(key)` is the process-local signal-only escape hatch.
876
-
877
- When an operator has positive evidence that another process owning one known
878
- run is gone, close that run through the runtime boundary rather than updating
879
- an adapter table or JSON payload directly:
880
-
881
- ```ts
882
- await runtime.abandon({
883
- conversationId,
884
- runId,
885
- expectedRevision: run.revision,
886
- staleOwner: true,
887
- })
888
- ```
889
-
890
- The revision protects against stale operator evidence. The mutation updates the
891
- canonical run and failed assistant atomically, removes the run from recovery
892
- indexes, and publishes the resulting `run-state`. A run that moved since it was
893
- read returns a conflict; `staleOwner: true` is mandatory evidence, not a default.
package/llms/cli.txt CHANGED
@@ -205,6 +205,21 @@ fields in declaration order, so `myapp generate "a fox"` is
205
205
  `--prompt "a fox"`. A piped value fills the first required unset field:
206
206
  `echo "a fox" | myapp generate`.
207
207
 
208
+ Automatic stdin routing skips TTYs and waits at most **250 ms for the first byte**.
209
+ An empty open pipe or EOF without data leaves the field unset, so normal schema
210
+ validation reports the missing field instead of waiting indefinitely. Once any
211
+ byte arrives, the reader collects the entire stream until EOF, including chunks
212
+ separated by long pauses; it decodes UTF-8 after collecting all bytes and trims
213
+ surrounding whitespace. Read errors or a close before EOF fail the invocation.
214
+ Explicit arguments and commands without required unset fields never read stdin.
215
+
216
+ A producer that takes longer than 250 ms to start must supply its value as an
217
+ argument (for example, `myapp generate --prompt "$(slow-producer)"`) or the
218
+ application must provide an explicit `CliConfig.stdin` reader that waits for it.
219
+ That hook is not wrapped in the availability timeout. `-` remains literal data,
220
+ not a special stdin marker. JSONL stream/batch commands own their input and wait
221
+ for lines as usual; MCP/serve and long-running handlers have no new deadline.
222
+
208
223
  A token is an option only when it is a long form (`--name`, `--name=value`,
209
224
  `--`) or `-` followed by a letter. Anything else that starts with `-` is data:
210
225
  `myapp note a "- item"` fills a positional, and after an option that expects a
@@ -77,11 +77,11 @@ 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 41 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 43 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, plus the managed-resource conformance kit |
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
- | `stitchkit/agent-runtime` | server | evolving<br>_redefined in 21 of the 41 minors since 0.56.2, most recently 0.94.0_ | optional durable conversation/run loop, history, models, prompts, fencing and events |
84
+ | `stitchkit/agent-runtime` | server | evolving<br>_redefined in 22 of the 43 minors since 0.56.2, most recently 0.97.0_ | optional durable conversation/run loop, history, models, prompts, fencing and events |
85
85
  | `stitchkit/agent-runtime/sandbox` | server | evolving | optional Linux Bubblewrap sessions, durable workspaces and a host HTTP credential gateway; see [sandbox guide](sandbox.md) |
86
86
  | `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 |
87
87
  | `stitchkit/agent-runtime/harness` | server | evolving | resource-aware process-local facade over the canonical Agent runtime; supervision stays outside |
@@ -91,7 +91,7 @@ own, recorded as an ADR.
91
91
  | `stitchkit/agent-runtime/sqlite/bun` | server (Bun) | evolving | durable built-in SQLite store for the agent runtime |
92
92
  | `stitchkit/agent-runtime/sqlite/node` | server (Node ≥ 22.5) | evolving | durable built-in SQLite store for the agent runtime |
93
93
  | `stitchkit-tui` | terminal (Bun) | evolving | optional official OpenTUI host over a caller-composed headless runtime |
94
- | `stitchkit/application` | browser + server | evolving<br>_redefined in 9 of the 41 minors since 0.56.2, most recently 0.96.0_ | managed resource graph, readiness, admission, schedules, subtree restart and bounded shutdown |
94
+ | `stitchkit/application` | browser + server | evolving<br>_redefined in 9 of the 43 minors since 0.56.2, most recently 0.96.0_ | managed resource graph, readiness, admission, schedules, subtree restart and bounded shutdown |
95
95
  | `stitchkit/application/grammy` | server | evolving | a grammY bot as application resources — configuration, then polling with updates admitted by batch — and the webhook adapter |
96
96
  | `stitchkit/application/opentelemetry` | server | evolving | maps application snapshots onto an injected OpenTelemetry `Meter` |
97
97
  | `stitchkit/application/schemas` | browser + server | evolving | the application's snapshot, health and shutdown schemas alone, without the kernel |
@@ -1,14 +1,93 @@
1
- # Upgrading 0.37.0 → 0.56.0 — stitchkit docs slice
1
+ # Upgrading 0.37.0 → 0.58.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.37.0 → 0.56.0 (docs/guide/upgrading.md)
6
+ Contains: Guide: Upgrading, 0.37.0 → 0.58.0 (docs/guide/upgrading.md)
7
7
 
8
8
  ==============================================================================
9
- # Guide: Upgrading, 0.37.0 → 0.56.0 (docs/guide/upgrading.md)
9
+ # Guide: Upgrading, 0.37.0 → 0.58.0 (docs/guide/upgrading.md)
10
10
  ==============================================================================
11
11
 
12
+ ## Released migration: 0.58.0
13
+
14
+ ### The default history projection rejects invalid chronology
15
+
16
+ `projectAgentHistory` no longer forwards records a provider contract cannot
17
+ accept. A completed assistant record before the first user message, and an
18
+ assistant record whose tool calls have no matching results, are omitted with an
19
+ inspectable decision instead of being sent upstream:
20
+
21
+ ```ts
22
+ // before — a leading assistant record was forwarded as-is
23
+ projectAgentHistory(messages)
24
+ // after — opt in explicitly, and only where the provider contract permits it
25
+ projectAgentHistory(messages, { leadingAssistant: 'allow' })
26
+ ```
27
+
28
+ Two consequences the changelog does not spell out. First, the projection can now
29
+ return **fewer** messages than the history holds, so any assertion or metric
30
+ that compared projected length against stored length will move; read the
31
+ detailed projection instead — it reports what was omitted and why, which is the
32
+ supported way to see the difference. Second, the omission is silent to the
33
+ provider but not to you: if a conversation suddenly loses its leading context,
34
+ the decision record is where that shows up, not the transcript.
35
+
36
+ ### Operator events redact `internalCause` by default
37
+
38
+ Raw provider and tool failures no longer travel in operator events unless the
39
+ sink asks for them:
40
+
41
+ ```ts
42
+ // before — internalCause was present
43
+ createAgentObservability({ write })
44
+ // after — an explicit operator-only opt-in
45
+ createAgentObservability({ write, includeInternalCause: true })
46
+ ```
47
+
48
+ The consequence to check before upgrading: any dashboard, alert or log
49
+ processor keyed on `internalCause` goes blind the moment you upgrade, and it
50
+ goes blind quietly — the field is absent, not empty. Set the flag on the
51
+ operator sink you own. Product delivery stays redacted regardless of the flag;
52
+ this option cannot widen what reaches a user.
53
+
54
+ ## Released migration: 0.57.0
55
+
56
+ ### Duplicate admission results carry the complete identity
57
+
58
+ Custom `AgentRuntimeStore` adapters must persist and return the input and
59
+ assistant identities associated with an idempotency key:
60
+
61
+ ```ts
62
+ // before
63
+ return { outcome: 'duplicate', runId, snapshot }
64
+
65
+ // after
66
+ return { outcome: 'duplicate', input, inputMessageId, runId, assistantMessageId, snapshot }
67
+ ```
68
+
69
+ Prefer replacing the custom aggregate reducer with `createAgentRuntimeStore()`;
70
+ its admission record and transaction driver implement this contract
71
+ automatically. Historical note: 0.59.0 reshaped this driver again, so an adapter
72
+ crossing both versions should read that section first and migrate once.
73
+
74
+ ### `AgentRuntimeEvent` adds a post-commit `admission` variant
75
+
76
+ Add it to any exhaustive publisher switch. Its `assistant` is either the pending
77
+ placeholder for a new assignment or the canonical persisted assistant for a
78
+ duplicate:
79
+
80
+ ```ts
81
+ case 'admission':
82
+ await persistProductProjection(event.input, event.run, event.assistant)
83
+ break
84
+ ```
85
+
86
+ The consequence for an exhaustive switch written without a `default` branch is a
87
+ compile error, which is the point. The consequence for a switch that *has* a
88
+ `default` is worse and silent: post-commit admissions fall into it and are
89
+ projected as an unknown event. Grep for publisher switches before upgrading.
90
+
12
91
  ## Released migration: 0.56.0
13
92
 
14
93
  ### Surface manifests are version 2
@@ -1,14 +1,119 @@
1
- # Upgrading 0.57.0 → 0.69.0 — stitchkit docs slice
1
+ # Upgrading 0.59.0 → 0.71.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.57.0 → 0.69.0 (docs/guide/upgrading.md)
6
+ Contains: Guide: Upgrading, 0.59.0 → 0.71.0 (docs/guide/upgrading.md)
7
7
 
8
8
  ==============================================================================
9
- # Guide: Upgrading, 0.57.0 → 0.69.0 (docs/guide/upgrading.md)
9
+ # Guide: Upgrading, 0.59.0 → 0.71.0 (docs/guide/upgrading.md)
10
10
  ==============================================================================
11
11
 
12
+ ## Released migration: 0.71.0
13
+
14
+ The Agent coding tools. Two of the three changes are visible to the compiler;
15
+ the third is the one to read carefully, because nothing will point at it.
16
+
17
+ ### If you implement `authorize`
18
+
19
+ The operation union gained `edit`, `list` and `glob` and lost `patch`. An
20
+ exhaustive matcher stops compiling and the compiler shows you every arm. **A
21
+ matcher with a default branch does not**, and that is the dangerous case:
22
+
23
+ ```ts
24
+ // before — and after this upgrade, silently wrong in both directions
25
+ if (request.operation === 'patch') return reviewPatch(request)
26
+ return true // now also authorizes edit, list and glob
27
+ // …or: return false // now also kills edit_file
28
+ ```
29
+
30
+ ```ts
31
+ // after
32
+ switch (request.operation) {
33
+ case 'edit': return reviewEdit(request) // the old `patch` payload, unchanged
34
+ case 'list':
35
+ case 'glob': return true // or your own policy
36
+ // …existing read / write / search / shell / artifact-read arms
37
+ }
38
+ ```
39
+
40
+ `write` gained `createsDirectories` — the workspace-relative directories the
41
+ call would create, outermost first, reported **before** anything is created. A
42
+ host that wants to refuse implicit directory creation now can.
43
+
44
+ ### If you call `apply_patch`
45
+
46
+ It is `edit_file`, and it is one call:
47
+
48
+ ```ts
49
+ // before
50
+ const read = await readFile({ path })
51
+ await applyPatch({ path, baseSha256: read.sha256, oldText, newText, dryRun: true })
52
+ await applyPatch({ path, baseSha256: read.sha256, oldText, newText, dryRun: false })
53
+
54
+ // after
55
+ await editFile({ path, oldText, newText })
56
+ ```
57
+
58
+ `expectedSha256` is optional and still refuses a stale base with `CONFLICT` when
59
+ you pass it; `edit_file` returns the resulting `sha256`, so a chain of edits
60
+ never needs to re-read. If you key an approval policy or a UI label on the string
61
+ `apply_patch`, update the key — nothing will fail loudly.
62
+
63
+ ### If you match on `INTERNAL_SERVER_ERROR` from a coding tool
64
+
65
+ Ordinary outcomes no longer arrive that way. A missing file is `NOT_FOUND`, an
66
+ existing file without `overwrite` is `CONFLICT`, an ambiguous snippet is
67
+ `CONFLICT` with the occurrence count, a path outside the root is `FORBIDDEN`.
68
+ Code that treated any coding-tool failure as an internal fault will now see
69
+ codes it did not before; code that showed the model an empty error now has a
70
+ sentence and a `hint` to show it. Host-level causes are unchanged and still
71
+ scrubbed.
72
+
73
+ ### If you write files into new directories
74
+
75
+ Nothing to change: `write_file` creates missing parents inside the root. Read
76
+ `createdDirectories` in the result if you want to notice a typo — a path that
77
+ was a failure before is now a successful write into a new tree.
78
+
79
+ ### If you want a step to know its context budget
80
+
81
+ Opt in by reading it; nothing is injected for you:
82
+
83
+ ```ts
84
+ loop: {
85
+ prepareStep: ({ contextUsage }) => {
86
+ const used = contextUsage?.usedTokens
87
+ if (used?.provenance === 'unavailable') return {} // no step has landed yet
88
+ const fraction = (used?.value ?? 0) / (contextUsage?.contextWindow ?? 1)
89
+ // …render it wherever you put it
90
+ return {}
91
+ },
92
+ }
93
+ ```
94
+
95
+ Put it at the **tail** of the conversation rather than in the system
96
+ instructions unless you have a reason: changing the system prompt on every step
97
+ invalidates the provider's prefix cache for the whole conversation, and on a long
98
+ run that is a multiple of the input cost.
99
+
100
+ ## Released migration: 0.70.0
101
+
102
+ ### Descriptor-backed Agent filesystem containment
103
+
104
+ Built-in coding file/search tools and `createAgentHarnessFileResources` now require a runtime
105
+ that can address an opened directory descriptor: Linux `/proc/self/fd`, or the packaged macOS
106
+ Node-API backend added in 0.70.1. This is what keeps a mutable parent rename or outside-symlink replacement from changing
107
+ the authorized target between validation and the actual effect.
108
+
109
+ No call-site change is needed on those platforms. The 0.70.0 notes incorrectly inferred macOS and
110
+ FreeBSD support from `/dev/fd`; macOS is restored by 0.70.1 and FreeBSD remains unsupported. On
111
+ Windows or another platform without that
112
+ boundary, move the built-in filesystem operations to a supported worker or replace them with
113
+ application-owned tools backed by an equivalent native handle API. They fail closed rather than
114
+ falling back to path spelling. `run_command` remains separately available under its explicit
115
+ executable alias and authorization policy; this change does not claim an executable sandbox.
116
+
12
117
  ## Released migration: 0.69.0
13
118
 
14
119
  ### Direct coding-tool operation names
@@ -1035,82 +1140,3 @@ createAgentRuntimeStore({
1035
1140
  `runAgentStoreConformance()` against the migrated adapter before switching production traffic.
1036
1141
  If an application implements `AgentRuntimeStore` directly, its duplicate result must also include
1037
1142
  the canonical `run` and the retained `assistant` for a terminal run.
1038
-
1039
- ## Released migration: 0.58.0
1040
-
1041
- ### The default history projection rejects invalid chronology
1042
-
1043
- `projectAgentHistory` no longer forwards records a provider contract cannot
1044
- accept. A completed assistant record before the first user message, and an
1045
- assistant record whose tool calls have no matching results, are omitted with an
1046
- inspectable decision instead of being sent upstream:
1047
-
1048
- ```ts
1049
- // before — a leading assistant record was forwarded as-is
1050
- projectAgentHistory(messages)
1051
- // after — opt in explicitly, and only where the provider contract permits it
1052
- projectAgentHistory(messages, { leadingAssistant: 'allow' })
1053
- ```
1054
-
1055
- Two consequences the changelog does not spell out. First, the projection can now
1056
- return **fewer** messages than the history holds, so any assertion or metric
1057
- that compared projected length against stored length will move; read the
1058
- detailed projection instead — it reports what was omitted and why, which is the
1059
- supported way to see the difference. Second, the omission is silent to the
1060
- provider but not to you: if a conversation suddenly loses its leading context,
1061
- the decision record is where that shows up, not the transcript.
1062
-
1063
- ### Operator events redact `internalCause` by default
1064
-
1065
- Raw provider and tool failures no longer travel in operator events unless the
1066
- sink asks for them:
1067
-
1068
- ```ts
1069
- // before — internalCause was present
1070
- createAgentObservability({ write })
1071
- // after — an explicit operator-only opt-in
1072
- createAgentObservability({ write, includeInternalCause: true })
1073
- ```
1074
-
1075
- The consequence to check before upgrading: any dashboard, alert or log
1076
- processor keyed on `internalCause` goes blind the moment you upgrade, and it
1077
- goes blind quietly — the field is absent, not empty. Set the flag on the
1078
- operator sink you own. Product delivery stays redacted regardless of the flag;
1079
- this option cannot widen what reaches a user.
1080
-
1081
- ## Released migration: 0.57.0
1082
-
1083
- ### Duplicate admission results carry the complete identity
1084
-
1085
- Custom `AgentRuntimeStore` adapters must persist and return the input and
1086
- assistant identities associated with an idempotency key:
1087
-
1088
- ```ts
1089
- // before
1090
- return { outcome: 'duplicate', runId, snapshot }
1091
-
1092
- // after
1093
- return { outcome: 'duplicate', input, inputMessageId, runId, assistantMessageId, snapshot }
1094
- ```
1095
-
1096
- Prefer replacing the custom aggregate reducer with `createAgentRuntimeStore()`;
1097
- its admission record and transaction driver implement this contract
1098
- automatically. Historical note: 0.59.0 reshaped this driver again, so an adapter
1099
- crossing both versions should read that section first and migrate once.
1100
-
1101
- ### `AgentRuntimeEvent` adds a post-commit `admission` variant
1102
-
1103
- Add it to any exhaustive publisher switch. Its `assistant` is either the pending
1104
- placeholder for a new assignment or the canonical persisted assistant for a
1105
- duplicate:
1106
-
1107
- ```ts
1108
- case 'admission':
1109
- await persistProductProjection(event.input, event.run, event.assistant)
1110
- break
1111
- ```
1112
-
1113
- The consequence for an exhaustive switch written without a `default` branch is a
1114
- compile error, which is the point. The consequence for a switch that *has* a
1115
- `default` is worse and silent: post-commit admissions fall into it and are
1116
- projected as an unknown event. Grep for publisher switches before upgrading.