@theokit/sdk 4.2.1 → 4.2.2
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 +97 -86
- package/dist/a2a/index.cjs.map +1 -1
- package/dist/a2a/index.js.map +1 -1
- package/dist/a2a/subagent.d.cts +2 -2
- package/dist/a2a/subagent.d.ts +2 -2
- package/dist/{cron-Y_aM4m4-.d.cts → cron-DxBlp3i4.d.ts} +2 -2
- package/dist/{cron-pjzn3jxf.d.ts → cron-afuxJHol.d.cts} +2 -2
- package/dist/cron.cjs.map +1 -1
- package/dist/cron.d.cts +2 -2
- package/dist/cron.d.ts +2 -2
- package/dist/cron.js.map +1 -1
- package/dist/{errors-B4QB3-Wu.d.ts → errors-DzCWdfkp.d.ts} +1 -1
- package/dist/{errors-B03TBTc_.d.cts → errors-VY7_0ki7.d.cts} +1 -1
- package/dist/errors.d.cts +2 -2
- package/dist/eval.cjs.map +1 -1
- package/dist/eval.js.map +1 -1
- package/dist/filesystem/index.cjs.map +1 -1
- package/dist/filesystem/index.js.map +1 -1
- package/dist/filesystem/local-filesystem.d.cts +1 -1
- package/dist/filesystem/local-filesystem.d.ts +1 -1
- package/dist/filesystem/types.d.cts +1 -1
- package/dist/filesystem/types.d.ts +1 -1
- package/dist/index.cjs +1 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +10 -10
- package/dist/index.d.ts +10 -10
- package/dist/index.js +1 -9
- package/dist/index.js.map +1 -1
- package/dist/internal/agent-loop/doom-loop-tracker.d.ts +1 -1
- package/dist/internal/budget/usage-accumulator.d.ts +1 -1
- package/dist/internal/llm/model-identifier.d.ts +1 -1
- package/dist/internal/llm/ollama-native.d.ts +2 -2
- package/dist/internal/llm/openai.d.ts +1 -1
- package/dist/internal/llm/router.d.ts +3 -4
- package/dist/internal/memory/adapters/ollama-embedding.d.ts +1 -1
- package/dist/internal/memory/embedding-adapter.d.ts +3 -3
- package/dist/internal/persistence/index.cjs.map +1 -1
- package/dist/internal/persistence/index.js.map +1 -1
- package/dist/internal/providers/builtin/vertex.d.ts +1 -1
- package/dist/internal/resilience/circuit-breaker.d.ts +1 -1
- package/dist/internal/runtime/concurrency/abort-utils.d.ts +1 -1
- package/dist/internal/runtime/registry/run-registry.d.ts +0 -1
- package/dist/internal/runtime/skills/skill-frontmatter.d.ts +0 -1
- package/dist/internal/task/registry.d.ts +0 -3
- package/dist/internal/telemetry/span-names.d.ts +0 -1
- package/dist/internal/workflow/telemetry.d.ts +0 -2
- package/dist/models.cjs.map +1 -1
- package/dist/models.js.map +1 -1
- package/dist/permission-engine.d.ts +3 -3
- package/dist/persistence.cjs.map +1 -1
- package/dist/persistence.js.map +1 -1
- package/dist/project.cjs.map +1 -1
- package/dist/project.js.map +1 -1
- package/dist/{run-CqihtbtU.d.cts → run-CsIsK9Gx.d.cts} +7 -7
- package/dist/{run-CqihtbtU.d.ts → run-CsIsK9Gx.d.ts} +7 -7
- package/dist/skills.cjs.map +1 -1
- package/dist/skills.js.map +1 -1
- package/dist/subscription/index.cjs.map +1 -1
- package/dist/subscription/index.js.map +1 -1
- package/dist/subscription/internal/sse-encoder.d.cts +1 -1
- package/dist/subscription/internal/sse-encoder.d.ts +1 -1
- package/dist/subscription/internal/sse-parser.d.cts +1 -1
- package/dist/subscription/internal/sse-parser.d.ts +1 -1
- package/dist/task-store.cjs.map +1 -1
- package/dist/task-store.js.map +1 -1
- package/dist/types/agent.d.ts +1 -1
- package/dist/types/processors.d.ts +1 -1
- package/dist/types/run.d.ts +2 -2
- package/dist/types/usage.d.ts +1 -1
- package/dist/workflow.cjs +1 -9
- package/dist/workflow.cjs.map +1 -1
- package/dist/workflow.d.cts +1 -1
- package/dist/workflow.d.ts +1 -1
- package/dist/workflow.js +1 -9
- package/dist/workflow.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 4.2.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Internal cleanup + dead-code hardening (no public-API change; exported names byte-stable).
|
|
8
|
+
|
|
9
|
+
- Removed 21 dead internal symbols the `quality:dead` gate could not see (knip ignores `src/internal/**`), plus dead test-seam helpers, with cascade cleanups.
|
|
10
|
+
- Added a `quality:dead-internal` gate (`tools/check-internal-deadcode.mjs`) that fails the build on any orphaned `**/internal/**` export OR unused private top-level declaration — closing the internal dead-code blind-spot.
|
|
11
|
+
- Tightened `tsconfig.base.json` (`noUnusedLocals` + `noUnusedParameters` + `allowUnreachableCode: false`) and removed the write-only `_truncated` flag in `@theokit/sdk-tools` shell-exec (capping behavior unchanged).
|
|
12
|
+
- Repo/docs hygiene: trimmed `docs/` to 3 files (the code is the documentation), consolidated the knowledge base, and cleaned repository history.
|
|
13
|
+
|
|
3
14
|
## 4.2.1
|
|
4
15
|
|
|
5
16
|
### Patch Changes
|
|
@@ -202,9 +213,9 @@
|
|
|
202
213
|
|
|
203
214
|
- ea3cd14: **SE35 — schedule a workflow on the `Cron` primitive (`workflow` + `inputData`).**
|
|
204
215
|
|
|
205
|
-
A `Cron` job may now target a committed `Workflow` (SE27–30) instead of an agent
|
|
216
|
+
A `Cron` job may now target a committed `Workflow` (SE27–30) instead of an agent. `Cron.create({ cron, workflow, inputData })` runs `workflow.run(inputData)` on each fire, reusing the shipped in-process scheduler + Task-registry observability. Mutually exclusive with agent targets: exactly one of `agent` | `agentId` | `workflow`; `message` is required for agent targets and forbidden with a workflow (typed `ConfigurationError`s: `cron_ambiguous_target` / `cron_no_target` / `cron_workflow_message` / `cron_missing_message`). `Cron.run(jobId)` returns `Run | WorkflowRun`; the fire handler records the correct terminal status for either shape.
|
|
206
217
|
|
|
207
|
-
Per ADR 0014, the job holds the `Workflow` **instance** (not a `workflowId` + resolver registry) — the cron store is in-memory, so there is no serialization problem to solve and a registry would be YAGNI; workflow cron jobs are local-runtime only (an instance can't cross the cloud boundary). Fire lifecycle hooks (`prepare`/`onFinish`/`onError`/`onAbort`) are deferred with a named re-eval trigger. Back-compat: agent-target jobs are byte-identical.
|
|
218
|
+
Per ADR 0014, the job holds the `Workflow` **instance** (not a `workflowId` + resolver registry) — the cron store is in-memory, so there is no serialization problem to solve and a registry would be YAGNI; workflow cron jobs are local-runtime only (an instance can't cross the cloud boundary). Fire lifecycle hooks (`prepare`/`onFinish`/`onError`/`onAbort`) are deferred with a named re-eval trigger. Back-compat: agent-target jobs are byte-identical. (SDK Evolution roadmap SE35.)
|
|
208
219
|
|
|
209
220
|
## 2.29.0
|
|
210
221
|
|
|
@@ -219,16 +230,16 @@
|
|
|
219
230
|
- **Standing `goal` config** — `AgentOptions.goal` (`{ judgeModel?, maxRuns?, prompt? }`). Precedence (remembered in the record): per-objective `record.options` → standing `goal` config → built-in default (`maxRuns` 20). The judge is the activation switch: with no judge resolved, the standing objective is inert (no scoring, no budget consumed).
|
|
220
231
|
- **`runUntil(goal?, options?)`** — `goal` is now OPTIONAL. Existing callers pass `goal` (unchanged transient behavior). Omitting `goal` with `options.threadId` set reads the durable objective, resolves options by precedence, caps per-call `maxTurns` by the remaining durable budget, runs the loop, and writes `runsUsed`/`status` back — `maxTurns` exhaustion leaves the objective `active` so raising `maxRuns` later resumes. Omitting `goal` with no objective (or no `threadId`) yields a single `status_change: paused` and never throws.
|
|
221
232
|
|
|
222
|
-
Reuses existing seams only (the shipped `runUntil` loop + `ConversationStorage`) — no new loop, no parallel runtime, no in-agentic-loop step (that is SE34). ADR 0012.
|
|
233
|
+
Reuses existing seams only (the shipped `runUntil` loop + `ConversationStorage`) — no new loop, no parallel runtime, no in-agentic-loop step (that is SE34). ADR 0012. (SDK Evolution roadmap SE33.)
|
|
223
234
|
|
|
224
|
-
- a8bed75: **SE34 — per-send `isTaskComplete` + `<current-objective>` projection (
|
|
235
|
+
- a8bed75: **SE34 — per-send `isTaskComplete` + `<current-objective>` projection (non-invasive half).**
|
|
225
236
|
|
|
226
237
|
Two opt-in `SendOptions`, both byte-identical to today when unused. The loop-touching in-agentic-loop goal step is DEFERRED with a named re-eval trigger (ADR 0013).
|
|
227
238
|
|
|
228
239
|
- **`completionCheck` (`isTaskComplete`)** — `send(msg, { completionCheck: { criteria, judgeModel?, apiKey? } })`. After the send reaches terminal `finished`, the shipped LLM-as-judge scores the final reply against `criteria`; the verdict surfaces on `RunResult.completionCheck` (`{ complete, reason, parseFailed }`) AND a typed `completion_check` run-event. This is the finer-grained, single-`send()` completion gate (contrast `runUntil`, which judges BETWEEN sends). Implemented as an outermost run wrapper — only judges a `finished` run with text, memoized (the judge fires exactly once), fail-safe (a judge parse failure yields `complete: false`, never a silent "done").
|
|
229
240
|
- **`objectiveThreadId` (`<current-objective>` projection)** — `send(msg, { objectiveThreadId })` reads the SE33 durable objective for that thread and, when it is `active`, prepends a `<current-objective>…</current-objective>` block to the assembled system prompt for that send, so the model always sees what it is working toward. Minimal + fail-soft (a storage read error never breaks the send) — not a general signal-provider framework.
|
|
230
241
|
|
|
231
|
-
Both reuse shipped seams (the run-wrapping seam + `judgeCallImpl` for the check; the SE33 objective store + system-prompt assembly for the projection). The agent tool-calling loop is UNTOUCHED.
|
|
242
|
+
Both reuse shipped seams (the run-wrapping seam + `judgeCallImpl` for the check; the SE33 objective store + system-prompt assembly for the projection). The agent tool-calling loop is UNTOUCHED. (SDK Evolution roadmap SE34.)
|
|
232
243
|
|
|
233
244
|
## 2.28.0
|
|
234
245
|
|
|
@@ -238,7 +249,7 @@
|
|
|
238
249
|
|
|
239
250
|
A pluggable filesystem _storage_ provider, the storage-side twin of `@theokit/sdk/sandbox`. `FilesystemBackend` is an abstract class with four methods (`readFile` / `writeFile` / `stat` / `list`), an `exists()` derived on the base, a boundary `basePath`, a `readOnly` flag, structured `stat().mtimeMs` (the read-before-write oracle for SE32), and typed errors (`FileNotFoundError` / `FilesystemSecurityError` / `FilesystemReadOnlyError` / `StaleFileError`). `LocalFilesystem` is the local-process implementation, boundary-enforced by reusing the core path-guard (traversal + symlink escape → `FilesystemSecurityError`). `FilesystemProvider` + `resolveFilesystem` support a per-request resolver `(ctx) => FilesystemBackend` for multi-tenant roots.
|
|
240
251
|
|
|
241
|
-
Unlike `SandboxBackend` (whose file ops shell out via `execute`, require command execution, and give no structured `stat`), a `FilesystemBackend` serves a filesystem-only workspace with no sandbox — see ADR 0011 for why file ops are NOT routed through `SandboxBackend`. `@theokit/sdk-tools`' `createWriteFileTool` now accepts an optional `filesystem` backend (writes route through it; omitted ⇒ identical local-`projectRoot` behavior). This is the backend seam, NOT a bundled `Workspace` and NOT a new toolset — bring-your-own-tools stands; `mounts`/FUSE, S3/GCS, and LSP remain out of core.
|
|
252
|
+
Unlike `SandboxBackend` (whose file ops shell out via `execute`, require command execution, and give no structured `stat`), a `FilesystemBackend` serves a filesystem-only workspace with no sandbox — see ADR 0011 for why file ops are NOT routed through `SandboxBackend`. `@theokit/sdk-tools`' `createWriteFileTool` now accepts an optional `filesystem` backend (writes route through it; omitted ⇒ identical local-`projectRoot` behavior). This is the backend seam, NOT a bundled `Workspace` and NOT a new toolset — bring-your-own-tools stands; `mounts`/FUSE, S3/GCS, and LSP remain out of core. (SDK Evolution roadmap SE31.)
|
|
242
253
|
|
|
243
254
|
## 2.27.0
|
|
244
255
|
|
|
@@ -248,25 +259,25 @@
|
|
|
248
259
|
|
|
249
260
|
`Workflow.create({ ..., inputSchema, outputSchema })` (from `@theokit/sdk/workflow`) now validates the workflow's overall input and final output, closing the SE19 debt (a Workflow carried no top-level schema — only per-step `FnStep` schemas). When `inputSchema` is set, `run(input)` validates `input` BEFORE step 1; a mismatch fails fast with `status: "failed"` and a typed `WorkflowInputError` in `run.error` (no step executes, no silent coerce). When `outputSchema` is set, the terminal `completed` output is validated before `WorkflowRun.output` is populated; a mismatch yields `status: "failed"` with a typed `WorkflowOutputError` (only the `completed` path is checked — suspended/failed runs skip output validation).
|
|
250
261
|
|
|
251
|
-
Both surface as `status: "failed"` (never a throw — consistent with the executor's non-throwing step-error contract). Back-compat: absent schemas ⇒ unchanged. New exports `WorkflowInputError` / `WorkflowOutputError`. `workflowAsTool` (SE19) keeps taking its own `inputSchema` to preserve its structural `{ run }` contract.
|
|
262
|
+
Both surface as `status: "failed"` (never a throw — consistent with the executor's non-throwing step-error contract). Back-compat: absent schemas ⇒ unchanged. New exports `WorkflowInputError` / `WorkflowOutputError`. `workflowAsTool` (SE19) keeps taking its own `inputSchema` to preserve its structural `{ run }` contract. (SDK Evolution roadmap SE27.)
|
|
252
263
|
|
|
253
264
|
- 09b89ea: **SE28 — `Workflow.stream()` (step-event stream during execution).**
|
|
254
265
|
|
|
255
266
|
`workflow.stream(input, opts?)` (from `@theokit/sdk/workflow`) runs the workflow and emits step-level events as they happen, instead of only the terminal result. It returns a `WorkflowStream` — an async iterator of `WorkflowEvent`s (`step_started` / `step_completed` (with `output`) / `step_failed` (with `error`) / `workflow_suspended` / `workflow_completed`) plus a `result` promise resolving to the SAME terminal `WorkflowRun` `run()` returns (the authoritative outcome — the stream ends when the run terminates).
|
|
256
267
|
|
|
257
|
-
Events fire in execution order for top-level steps (nested `parallel`/`branch`/`foreach` emit as their single wrapping step — coarse-grained by design). This is a STEP-event stream, distinct from the token-delta agent stream deferred in SE24. `run()` is unchanged + authoritative. New public types `WorkflowEvent` + `WorkflowStream`.
|
|
268
|
+
Events fire in execution order for top-level steps (nested `parallel`/`branch`/`foreach` emit as their single wrapping step — coarse-grained by design). This is a STEP-event stream, distinct from the token-delta agent stream deferred in SE24. `run()` is unchanged + authoritative. New public types `WorkflowEvent` + `WorkflowStream`. (SDK Evolution roadmap SE28.)
|
|
258
269
|
|
|
259
270
|
- f13d499: **SE29 — workflow shared state (`stateSchema` + `state` / `setState`).**
|
|
260
271
|
|
|
261
272
|
Workflow steps can now share values without threading them through every step's input/output. `Workflow.create({ stateSchema, initialState })` (from `@theokit/sdk/workflow`) seeds a shared state; every step's `StepContext` gains `state` (read the current value) and `setState(next)` (update it for subsequent steps). `setState` validates against `stateSchema` when set — a mismatch throws a typed `WorkflowStateError` that fails the step/run (Rule 8); an invalid `initialState` fails the run fast before step 1.
|
|
262
273
|
|
|
263
|
-
State is captured in the `WorkflowSnapshot` (bumped to `_schemaVersion: 2`) and restored on `Workflow.resume` — it survives a suspend→resume round-trip. A pre-SE29 (`_schemaVersion: 1`) snapshot has no state and resumes with `initialState`. Back-compat: no `stateSchema`/`initialState` ⇒ `state` is `undefined` and `setState` is unvalidated. New export `WorkflowStateError`.
|
|
274
|
+
State is captured in the `WorkflowSnapshot` (bumped to `_schemaVersion: 2`) and restored on `Workflow.resume` — it survives a suspend→resume round-trip. A pre-SE29 (`_schemaVersion: 1`) snapshot has no state and resumes with `initialState`. Back-compat: no `stateSchema`/`initialState` ⇒ `state` is `undefined` and `setState` is unvalidated. New export `WorkflowStateError`. (SDK Evolution roadmap SE29.)
|
|
264
275
|
|
|
265
276
|
- 8ce8441: **SE30 — workflows-as-steps (`workflowStep`) + `cloneWorkflow`.**
|
|
266
277
|
|
|
267
278
|
`workflowStep(child, { id? })` (from `@theokit/sdk/workflow`) uses a committed `Workflow` as a step inside another workflow: `.then(workflowStep(child))`. The child runs in its OWN executor (own runId, single-flight lock, and step-id space — so nested ids never collide with the parent's); its output becomes the step output. `cloneWorkflow(wf, { id })` returns a new independent `Workflow` with the same committed steps under a new name + a fresh workflowId (clones run independently, distinct observability identity).
|
|
268
279
|
|
|
269
|
-
A non-`completed` child fails the parent step with a typed `WorkflowNestedError`. **Nested suspend/resume is NOT supported in v1** (TheoKit's resume continues AFTER the suspended step, so a nested child would be skipped) — a nested `suspended` fails with a clear message pointing at a top-level suspend; re-running the child on resume (which would re-execute its side effects) is deliberately avoided. ADR 0010. New export `WorkflowNestedError`.
|
|
280
|
+
A non-`completed` child fails the parent step with a typed `WorkflowNestedError`. **Nested suspend/resume is NOT supported in v1** (TheoKit's resume continues AFTER the suspended step, so a nested child would be skipped) — a nested `suspended` fails with a clear message pointing at a top-level suspend; re-running the child on resume (which would re-execute its side effects) is deliberately avoided. ADR 0010. New export `WorkflowNestedError`. (SDK Evolution roadmap SE30.)
|
|
270
281
|
|
|
271
282
|
## 2.26.0
|
|
272
283
|
|
|
@@ -276,7 +287,7 @@
|
|
|
276
287
|
|
|
277
288
|
`AgentOptions.inputProcessors` run in order before the LLM (normalize / validate / block / rewrite the user message); `outputProcessors` run on the model's final text before it reaches the caller (redact / block). A `Processor` is `{ id; processInput?; processOutput?; onViolation? }`; each handler receives `ctx` with `abort(reason)` (block → the run stops with `RunResult.tripwire { reason, processorId }` + a `tripwire` run-event via `SendOptions.onRunEvent`) and `warn(message, detail?)` (non-blocking → fires `onViolation`, continues), and returns the (possibly rewritten) payload.
|
|
278
289
|
|
|
279
|
-
The core ships no `strategy` enum — block/rewrite/redact/warn reduce to `abort` / return-string / `warn` (the built-in SE25 processors expose a `strategy` option over these). An input block never reaches the model (a terminal tripwire run); an output block turns a finished run's result into a tripwire on `wait()`. Streaming output redaction is deferred (v1 processes the buffered `wait()` path). Cloud agents reject processors (function handlers don't serialize). Back-compat: no processors ⇒ unchanged. New public types `Processor` / `ProcessorViolation` / `InputProcessorContext` / `OutputProcessorContext` / `ProcessorControls` / `ProcessorTripwire` / `RunTripwireEvent` + `RunResult.tripwire`. ADR 0008.
|
|
290
|
+
The core ships no `strategy` enum — block/rewrite/redact/warn reduce to `abort` / return-string / `warn` (the built-in SE25 processors expose a `strategy` option over these). An input block never reaches the model (a terminal tripwire run); an output block turns a finished run's result into a tripwire on `wait()`. Streaming output redaction is deferred (v1 processes the buffered `wait()` path). Cloud agents reject processors (function handlers don't serialize). Back-compat: no processors ⇒ unchanged. New public types `Processor` / `ProcessorViolation` / `InputProcessorContext` / `OutputProcessorContext` / `ProcessorControls` / `ProcessorTripwire` / `RunTripwireEvent` + `RunResult.tripwire`. ADR 0008. (SDK Evolution roadmap SE24.)
|
|
280
291
|
|
|
281
292
|
- cc8efee: **SE25 — deterministic in-tree guardrail processors (`createUnicodeNormalizer`, `createTokenLimiter`).**
|
|
282
293
|
|
|
@@ -287,7 +298,7 @@
|
|
|
287
298
|
|
|
288
299
|
Both are OPT-IN (add to `inputProcessors`/`outputProcessors`); nothing auto-injects them; back-compat preserved.
|
|
289
300
|
|
|
290
|
-
|
|
301
|
+
**A batch-parts processor is intentionally DEFERRED**, not shipped: TheoKit's `run.stream()` emits full `SDKAssistantMessage`s, not token-granular deltas, so there is no SSE chunk stream to coalesce in the in-process runtime (batch-coalescing only saves HTTP network overhead). It becomes meaningful only alongside a future HTTP/SSE streaming transport (the same milestone as SE24's deferred streaming-output redaction). (SDK Evolution roadmap SE25.)
|
|
291
302
|
|
|
292
303
|
### Patch Changes
|
|
293
304
|
|
|
@@ -295,7 +306,7 @@
|
|
|
295
306
|
|
|
296
307
|
Records the decision (ADR 0009) to DELEGATE the LLM-classifier guardrail processors — moderation, PII, prompt-injection, language, prompt-scrubber — to specialist libraries / consumer code built ON the SE24 seam, rather than shipping concrete classifiers in `@theokit/sdk` core (mirrors the AUTH-DELEGATION lock: constant churn — provider/model deltas, taxonomies, thresholds, jailbreak patterns — vs a stable seam a single-maintainer core can own). No classifier is added to core.
|
|
297
308
|
|
|
298
|
-
Ships the paved path: `docs/concepts/guardrails.md` (how to build moderation / PII / injection processors on the seam + recommended external classifiers) and `examples/guardrails/` (a runnable moderation + PII-redaction example over a pluggable classifier). No public API change.
|
|
309
|
+
Ships the paved path: `docs/concepts/guardrails.md` (how to build moderation / PII / injection processors on the seam + recommended external classifiers) and `examples/guardrails/` (a runnable moderation + PII-redaction example over a pluggable classifier). No public API change. (SDK Evolution roadmap SE26.)
|
|
299
310
|
|
|
300
311
|
## 2.25.0
|
|
301
312
|
|
|
@@ -305,25 +316,25 @@
|
|
|
305
316
|
|
|
306
317
|
`agent.skills.list()` already returned skill metadata (name + description only); SE20 adds `agent.skills.get(name)` returning the skill INCLUDING its `instructions` (body) — read from the inline `createSkill` body, or from the filesystem SKILL.md (frontmatter stripped) for discovered skills. Returns `undefined` when no enabled skill matches (malformed skills stay excluded). New public type `SDKAgentSkillDetail`.
|
|
307
318
|
|
|
308
|
-
`list()` stays lean (the `<skills>` block only ever carries name + description); full bodies come only through `get`.
|
|
319
|
+
`list()` stays lean (the `<skills>` block only ever carries name + description); full bodies come only through `get`. Additive + backward-compatible. (SDK Evolution roadmap SE20.)
|
|
309
320
|
|
|
310
321
|
- 09865ee: **SE21 — `references` on `createSkill` (bundle supporting docs on an inline skill).**
|
|
311
322
|
|
|
312
|
-
`createSkill({ ..., references })` now accepts an optional `references` map (filename → content), mirroring a filesystem skill's `references/` directory. The docs travel on the inline skill object and surface to the app via `agent.skills.get(name)` (new `references` field on `SDKAgentSkillDetail`); they are NOT injected into the model prompt. Omitted when not provided (backward-compatible).
|
|
323
|
+
`createSkill({ ..., references })` now accepts an optional `references` map (filename → content), mirroring a filesystem skill's `references/` directory. The docs travel on the inline skill object and surface to the app via `agent.skills.get(name)` (new `references` field on `SDKAgentSkillDetail`); they are NOT injected into the model prompt. Omitted when not provided (backward-compatible).
|
|
313
324
|
|
|
314
|
-
Also closes a latent boundary leak surfaced by this change: `agent.skills.list()` now projects to the public shape (name + description only), so an inline skill's `instructions` / `references` / `source` never leak through `list()` — the body is reachable exclusively through `get()`, matching the documented `SystemPromptSkillRef` contract.
|
|
325
|
+
Also closes a latent boundary leak surfaced by this change: `agent.skills.list()` now projects to the public shape (name + description only), so an inline skill's `instructions` / `references` / `source` never leak through `list()` — the body is reachable exclusively through `get()`, matching the documented `SystemPromptSkillRef` contract. (SDK Evolution roadmap SE21.)
|
|
315
326
|
|
|
316
327
|
- abfcc5d: **SE22 — dynamic skills resolver (`skills: (ctx) => SkillsSettings`).**
|
|
317
328
|
|
|
318
329
|
`AgentOptions.skills` now accepts a resolver function in addition to the static `SkillsSettings` object. The resolver receives a per-send context (`agentId`, `cwd`, `model`, `userMessage`, `memory` — mirroring the systemPrompt resolver's context, minus the not-yet-resolved `skills`) and returns the `SkillsSettings` for that run. It is evaluated per `send()` before skill assembly, so a cached `getOrCreate` agent re-resolves each run — pick skills from runtime context (e.g. the user's role).
|
|
319
330
|
|
|
320
|
-
A static object behaves exactly as today. The agent-scoped `agent.skills` handle reflects the static/base config; the resolver drives the per-send `<skills>` block. The SDK imposes no timeout (wrap your own `Promise.race`); a throwing resolver fails the run — no silent fallback (Rule 8). Cloud agents reject a function resolver (it can't run on PaaS — resolve to a static object first), mirroring the systemPrompt-resolver cloud rule. New public types `SkillsResolver` + `SkillsResolverContext`.
|
|
331
|
+
A static object behaves exactly as today. The agent-scoped `agent.skills` handle reflects the static/base config; the resolver drives the per-send `<skills>` block. The SDK imposes no timeout (wrap your own `Promise.race`); a throwing resolver fails the run — no silent fallback (Rule 8). Cloud agents reject a function resolver (it can't run on PaaS — resolve to a static object first), mirroring the systemPrompt-resolver cloud rule. New public types `SkillsResolver` + `SkillsResolverContext`. (SDK Evolution roadmap SE22.)
|
|
321
332
|
|
|
322
333
|
- 0b9c0ac: **SE23 — `defineSkillReadTool` (opt-in model-facing lazy skill read).**
|
|
323
334
|
|
|
324
335
|
`defineSkillReadTool(skills)` returns a `skill_read` `CustomTool` the consumer explicitly adds to `AgentOptions.tools`. When the model calls it with a skill name, the handler returns that skill's `instructions` (+ SE21 `references`); an unknown-but-well-formed name returns a typed "not found" string listing the available skills — NOT a throw that kills the run (Rule 8). Malformed input (missing `name`) fails at the trust boundary via the input schema.
|
|
325
336
|
|
|
326
|
-
The SDK never auto-injects it — bring-your-own-tools stays intact (sibling of `defineSubAgent` / `workflowAsTool`). This is the LAZY read path that complements the eager `<skills>` block (name + description only): the block discloses which skills exist; `skill_read` loads a body on demand. The consumer controls exposure by choosing which skills to pass. See ADR 0007.
|
|
337
|
+
The SDK never auto-injects it — bring-your-own-tools stays intact (sibling of `defineSubAgent` / `workflowAsTool`). This is the LAZY read path that complements the eager `<skills>` block (name + description only): the block discloses which skills exist; `skill_read` loads a body on demand. The consumer controls exposure by choosing which skills to pass. See ADR 0007. The `skill_read` tool is opt-in, never auto-injected. (SDK Evolution roadmap SE23.)
|
|
327
338
|
|
|
328
339
|
## 2.24.0
|
|
329
340
|
|
|
@@ -333,25 +344,25 @@
|
|
|
333
344
|
|
|
334
345
|
`defineTool` (from `@theokit/sdk`) gains an optional `outputSchema` (a Zod schema). When set, the handler returns the STRUCTURED output inferred from it (`z.infer<outputSchema>`), the value is validated against the schema, and the tool result becomes its serialization — a string stays as-is, an object is JSON-stringified. A validation failure raises `ZodError` (converted to a `tool_result(isError)`), so a malformed tool output fails loudly instead of silently reaching the model.
|
|
335
346
|
|
|
336
|
-
Additive + fully backward-compatible: with no `outputSchema` the handler returns a plain `string` exactly as before (the handler return type is `string` when `outputSchema` is absent, `z.infer<outputSchema>` when present, via a conditional type).
|
|
347
|
+
Additive + fully backward-compatible: with no `outputSchema` the handler returns a plain `string` exactly as before (the handler return type is `string` when `outputSchema` is absent, `z.infer<outputSchema>` when present, via a conditional type). Pairs with SE17 (`toModelOutput`). (SDK Evolution roadmap SE16.)
|
|
337
348
|
|
|
338
349
|
- f621734: **SE17 — `toModelOutput` on `defineTool` (model-facing vs app-facing output split).**
|
|
339
350
|
|
|
340
351
|
`defineTool` (from `@theokit/sdk`) gains an optional `toModelOutput`. The handler returns the FULL result (validated by SE16's `outputSchema`); `toModelOutput(output)` maps it to the compact / multimodal representation the MODEL sees in the `tool_result` — so rich app-facing detail is not forced into model context. It returns a `string` OR SE7 `ToolResultContentBlock[]` (text + image). Absent ⇒ the tool result is the serialized handler output (SE16 / pre-SE17 behavior, unchanged).
|
|
341
352
|
|
|
342
|
-
|
|
353
|
+
Additive + backward-compatible. (SDK Evolution roadmap SE17.)
|
|
343
354
|
|
|
344
355
|
- 72435db: **SE18 — `SendOptions.activeTools` (per-send runtime tool subset).**
|
|
345
356
|
|
|
346
357
|
`agent.send(input, { activeTools })` restricts, per send, which of the agent's registered tools the model may actually call. A tool whose canonical name is not in the list is vetoed at dispatch (its handler never runs) — reusing the existing `withToolWhitelist` path that `Agent.fork`'s `allowedTools` uses, NOT `PermissionEngine`. Composes with `toolChoice`: `activeTools` narrows the set, `toolChoice` gates calling within it. Absent ⇒ the full toolset is available (unchanged).
|
|
347
358
|
|
|
348
|
-
The loop runs inside a `withToolWhitelist(new Set(activeTools))` scope when set. Additive + backward-compatible.
|
|
359
|
+
The loop runs inside a `withToolWhitelist(new Set(activeTools))` scope when set. Additive + backward-compatible. (SDK Evolution roadmap SE18.)
|
|
349
360
|
|
|
350
361
|
- f92f720: **SE19 — `workflowAsTool` (expose a Workflow as an agent tool).**
|
|
351
362
|
|
|
352
|
-
`workflowAsTool(workflow, { name, description, inputSchema })` (from `@theokit/sdk/workflow`) turns a `Workflow` into an agent `CustomTool`, completing the
|
|
363
|
+
`workflowAsTool(workflow, { name, description, inputSchema })` (from `@theokit/sdk/workflow`) turns a `Workflow` into an agent `CustomTool`, completing the "X as tools" trio (tools; agents-as-tools via `defineSubAgent`; workflows-as-tools). The handler validates the model's args against `spec.inputSchema`, runs the workflow, and returns its output (a string as-is, else JSON). A run that does not reach `status: "completed"` raises a typed `WorkflowToolError` (workflow step errors do NOT throw — they surface via `run.status === "failed"`).
|
|
353
364
|
|
|
354
|
-
Because a `Workflow` carries no top-level schema (`WorkflowOptions` is `name`/`persistence`/`workflowId`; schemas are per-step), the caller supplies the tool `inputSchema` in the spec (like `defineTool`). Accepts any `{ run }`-shaped workflow (structural), so it never imports the `Workflow` class. New exports: `workflowAsTool`, `WorkflowToolError`, `WorkflowAsToolSpec`. Additive.
|
|
365
|
+
Because a `Workflow` carries no top-level schema (`WorkflowOptions` is `name`/`persistence`/`workflowId`; schemas are per-step), the caller supplies the tool `inputSchema` in the spec (like `defineTool`). Accepts any `{ run }`-shaped workflow (structural), so it never imports the `Workflow` class. New exports: `workflowAsTool`, `WorkflowToolError`, `WorkflowAsToolSpec`. Additive. (SDK Evolution roadmap SE19.)
|
|
355
366
|
|
|
356
367
|
## 2.23.0
|
|
357
368
|
|
|
@@ -361,21 +372,21 @@
|
|
|
361
372
|
|
|
362
373
|
`DelegationStartDecision` (from `@theokit/sdk/a2a`) gains `modifiedMaxSteps?: number`. When an `onDelegationStart` hook returns it (and does not reject), `defineSubAgent` forwards it as `SendOptions.maxIterations` to the child `agent.send`, capping how many tool-loop rounds the subagent may run. Composes with SE10 (`signal`) and SE12 (`messageFilter` preamble) onto a single child `send`. Absent ⇒ the child uses its default iteration ceiling (unchanged).
|
|
363
374
|
|
|
364
|
-
Completes the SE11 `onDelegationStart` decision contract (the deferred `modifiedMaxSteps` — the `SendOptions.maxIterations` plumbing already existed). Additive + backward-compatible.
|
|
375
|
+
Completes the SE11 `onDelegationStart` decision contract (the deferred `modifiedMaxSteps` — the `SendOptions.maxIterations` plumbing already existed). Additive + backward-compatible. (SDK Evolution roadmap SE13.)
|
|
365
376
|
|
|
366
377
|
- b51dc6a: **SE14 — subagent result-context control (`SubAgentSpec.includeToolResults`).**
|
|
367
378
|
|
|
368
|
-
`defineSubAgent()` (from `@theokit/sdk/a2a`) gains an opt-in `includeToolResults`. When `true`, the child's completed tool-call results (name + result) are appended to the delegation payload returned to the supervisor, inside a delimited `<subagent-tool-results>` block; when absent/`false` the delegation returns the child's final text only — **text-only stays the default** (
|
|
379
|
+
`defineSubAgent()` (from `@theokit/sdk/a2a`) gains an opt-in `includeToolResults`. When `true`, the child's completed tool-call results (name + result) are appended to the delegation payload returned to the supervisor, inside a delimited `<subagent-tool-results>` block; when absent/`false` the delegation returns the child's final text only — **text-only stays the default** (the scoped-context posture).
|
|
369
380
|
|
|
370
381
|
Implemented as a `run.stream()` replay after `run.wait()` (a proven, safe idiom — the run buffers events and `stream()` replays them) collecting `tool_call` events with `status: "completed"`. **No `RunResult` change** — reads the existing public stream surface; tool _args_ are never surfaced (only completed results). Rationale + the `RunResult`-field alternative are recorded in ADR 0006.
|
|
371
382
|
|
|
372
|
-
Additive + backward-compatible (default `false` never touches the stream).
|
|
383
|
+
Additive + backward-compatible (default `false` never touches the stream). (SDK Evolution roadmap SE14.)
|
|
373
384
|
|
|
374
385
|
- 30e02d9: **SE15 — `iteration` count on the delegation-hook context (reject-after-N).**
|
|
375
386
|
|
|
376
|
-
`DelegationStartContext` and `DelegationCompleteContext` (from `@theokit/sdk/a2a`) gain `iteration: number` — a 1-based per-`defineSubAgent`-instance invocation counter, incremented before `onDelegationStart` runs (a rejected delegation still counts). This enables
|
|
387
|
+
`DelegationStartContext` and `DelegationCompleteContext` (from `@theokit/sdk/a2a`) gain `iteration: number` — a 1-based per-`defineSubAgent`-instance invocation counter, incremented before `onDelegationStart` runs (a rejected delegation still counts). This enables a reject-after-N-iterations pattern: `onDelegationStart: (ctx) => ctx.iteration > 8 ? { proceed: false, rejectionReason } : { proceed: true }`. `onDelegationComplete` sees the same iteration its `onDelegationStart` did.
|
|
377
388
|
|
|
378
|
-
Also fixes a delegation-hook DX regression: `onDelegationStart` / `onDelegationComplete` now accept a **side-effect-only (void-returning) callback** (e.g. `(ctx) => { log(ctx) }`) — the common case,
|
|
389
|
+
Also fixes a delegation-hook DX regression: `onDelegationStart` / `onDelegationComplete` now accept a **side-effect-only (void-returning) callback** (e.g. `(ctx) => { log(ctx) }`) — the common case, for `async ctx => { … }` hooks — via a shared `DelegationHookResult<T>` return type. Additive + backward-compatible. (SDK Evolution roadmap SE15.)
|
|
379
390
|
|
|
380
391
|
## 2.22.0
|
|
381
392
|
|
|
@@ -388,7 +399,7 @@
|
|
|
388
399
|
- Additive + backward-compatible: a handler invoked with no `ctx` (single-arg call sites) behaves exactly as before — no signal, no cancellation.
|
|
389
400
|
- The child agent is still disposed in `finally`, including on cancel.
|
|
390
401
|
|
|
391
|
-
|
|
402
|
+
The parent run's `AbortSignal` is forwarded to delegated subagents; aborting the parent cancels the in-flight subagent at its next step (SDK Evolution roadmap SE10).
|
|
392
403
|
|
|
393
404
|
- 8e3249d: **SE11 — delegation lifecycle hooks on `defineSubAgent` (`onDelegationStart` / `onDelegationComplete`).**
|
|
394
405
|
|
|
@@ -399,7 +410,7 @@
|
|
|
399
410
|
|
|
400
411
|
Additive + backward-compatible: specs without hooks behave exactly as before. New exported types: `DelegationStartContext`, `DelegationStartDecision`, `DelegationCompleteContext`, `DelegationCompleteDecision`.
|
|
401
412
|
|
|
402
|
-
|
|
413
|
+
Adds `onDelegationStart` / `onDelegationComplete` control points for delegation (SDK Evolution roadmap SE11).
|
|
403
414
|
|
|
404
415
|
- d2d0d16: **SE12 — opt-in parent-context forwarding for subagents (`messageFilter`).**
|
|
405
416
|
|
|
@@ -408,7 +419,7 @@
|
|
|
408
419
|
- New `ctx.messages` on the custom-tool handler `ToolContext`: a **read-only, text-only** projection of the current turn's transcript (`ToolContextMessage[]`), threaded by the agent loop the same way `ctx.signal` (#65) and `ctx.context` (M7) are. Non-text parts (tool calls / results) are dropped — a tool never sees raw wire parts or nested tool args.
|
|
409
420
|
- `messageFilter({ messages, input, name })` returns the subset to forward; `defineSubAgent` prepends it to the delegated input as a role-tagged context preamble. A filter returning `[]` forwards nothing. A filter that drops sensitive turns (e.g. anything `confidential`) provably keeps them out of the child context.
|
|
410
421
|
|
|
411
|
-
New exported types: `ToolContextMessage`, `MessageFilterArgs`. Additive + backward-compatible. Rationale + the transcript-exposure trade-off are recorded in ADR 0005.
|
|
422
|
+
New exported types: `ToolContextMessage`, `MessageFilterArgs`. Additive + backward-compatible. Rationale + the transcript-exposure trade-off are recorded in ADR 0005. (SDK Evolution roadmap SE12.)
|
|
412
423
|
|
|
413
424
|
## 2.21.0
|
|
414
425
|
|
|
@@ -421,15 +432,15 @@
|
|
|
421
432
|
- `default` — rules decide; unmatched ⇒ `ask` (fail-closed).
|
|
422
433
|
- `plan` — read-only: `allow` rules pass, everything else ⇒ `deny`.
|
|
423
434
|
- `acceptEdits` — auto-approve the UNMATCHED verdict but still honor an explicit `ask` rule (Codex `UnlessTrusted`).
|
|
424
|
-
- `bypass` — everything ⇒ `allow` EXCEPT an explicit `deny` rule (
|
|
435
|
+
- `bypass` — everything ⇒ `allow` EXCEPT an explicit `deny` rule (a skip-permissions posture; Codex `Never`).
|
|
425
436
|
|
|
426
|
-
**Invariant
|
|
437
|
+
**Invariant:** an explicit `deny` is immune to every auto-approve mode. `bypass`/`acceptEdits` never un-deny.
|
|
427
438
|
|
|
428
439
|
`createPermissionPlugin` gains `mode` + an enriched async **`canUseTool(toolName, input, ctx)`** gate (the Anthropic-parity shape) that resolves the `ask` verdict to allow/deny — fail-closed on absent/throwing gate. The old `onAsk(toolName)` is kept as a `@deprecated` back-compat fallback.
|
|
429
440
|
|
|
430
441
|
New exports: `PermissionMode`, `applyMode`, `PermissionGate`, `PermissionGateContext`, `PermissionGateDecision`. Additive + backward-compatible (`evaluate` mode defaults to `default`; `onAsk` still works). `updatedInput` (arg rewrite) is intentionally deferred — the `pre_tool_call` seam is veto-only today.
|
|
431
442
|
|
|
432
|
-
Grounded in a
|
|
443
|
+
Grounded in a survey of state-of-the-art agent permission models (SDK Evolution roadmap SE1).
|
|
433
444
|
|
|
434
445
|
- f9001bb: **SE2 — typed runtime event stream (opt-in `SendOptions.onRunEvent`).**
|
|
435
446
|
|
|
@@ -441,11 +452,11 @@
|
|
|
441
452
|
|
|
442
453
|
New exports: `RunEvent` (+ the 7 member types), `RunEventSink`, `emitRunEvent`. Additive + backward-compatible.
|
|
443
454
|
|
|
444
|
-
Grounded in the SDK Evolution roadmap SE2
|
|
455
|
+
Grounded in the SDK Evolution roadmap SE2.
|
|
445
456
|
|
|
446
457
|
- eec7d55: **SE3 — multi-agent provenance (`origin`).**
|
|
447
458
|
|
|
448
|
-
New public `MessageOrigin` discriminated union that stamps WHO triggered a turn in the multi-agent path (Squad / a2a / handoff / background-delegation) and is **forwarded onto the run result** — so consumers can attribute or route turns by their trigger. Metadata-only: zero change to routing or dispatch.
|
|
459
|
+
New public `MessageOrigin` discriminated union that stamps WHO triggered a turn in the multi-agent path (Squad / a2a / handoff / background-delegation) and is **forwarded onto the run result** — so consumers can attribute or route turns by their trigger. Metadata-only: zero change to routing or dispatch.
|
|
449
460
|
|
|
450
461
|
- `MessageOrigin` union: `{ kind: "human" }` | `{ kind: "peer"; from }` | `{ kind: "task-notification" }` | `{ kind: "coordinator"; from? }` | `{ kind: "auto-continuation" }`. Absence = a direct human turn.
|
|
451
462
|
- `SendOptions.origin?: MessageOrigin` — the caller stamps the provenance; `RunResult.origin?: MessageOrigin` — forwarded onto the result (both fixture and real runtimes).
|
|
@@ -455,7 +466,7 @@
|
|
|
455
466
|
|
|
456
467
|
New exports: `MessageOrigin`, plus `origin` fields on `SendOptions` / `RunResult` / `A2AMessage` / `AgentStep` and the `agentStep(..., { origin })` option. Additive + backward-compatible.
|
|
457
468
|
|
|
458
|
-
Grounded in the SDK Evolution roadmap SE3
|
|
469
|
+
Grounded in the SDK Evolution roadmap SE3.
|
|
459
470
|
|
|
460
471
|
- 8606f5b: **SE4 — session-management surface (`createSessionManager`).**
|
|
461
472
|
|
|
@@ -469,7 +480,7 @@
|
|
|
469
480
|
|
|
470
481
|
New exports: `createSessionManager`, `SessionManager`, `SessionSummary`, `SessionListOptions`, `SessionCapabilityResult`, `SessionMeta`, `SessionMetaPatch`. Additive + backward-compatible.
|
|
471
482
|
|
|
472
|
-
Grounded in the SDK Evolution roadmap SE4
|
|
483
|
+
Grounded in the SDK Evolution roadmap SE4.
|
|
473
484
|
|
|
474
485
|
- ce9b375: **SE7 — structured/multimodal tool results + `ToolError`.**
|
|
475
486
|
|
|
@@ -486,17 +497,17 @@
|
|
|
486
497
|
|
|
487
498
|
- 3722208: **SE8 — model bare-string shorthand.**
|
|
488
499
|
|
|
489
|
-
Every public model-accepting surface — `AgentOptions.model`, `SendOptions.model`, `AgentBuilder.model()`, and `GenerateObjectOptions.model` / `structuringModel` / `StreamObjectOptions.model` — now accepts a bare-string model id (`model: "openai/gpt-4o-mini"`) in addition to the `{ id }` object
|
|
500
|
+
Every public model-accepting surface — `AgentOptions.model`, `SendOptions.model`, `AgentBuilder.model()`, and `GenerateObjectOptions.model` / `structuringModel` / `StreamObjectOptions.model` — now accepts a bare-string model id (`model: "openai/gpt-4o-mini"`) in addition to the `{ id }` object — the familiar `"provider/model"` shorthand. Additive + fully backward-compatible: the object form (and `{ id, params }` for tuning) is unchanged.
|
|
490
501
|
|
|
491
502
|
- A bare string is normalized to `{ id }` at ONE boundary seam (`normalizeModel`), so all downstream code keeps seeing a `ModelSelection`. The id still parses a `provider/` prefix for routing.
|
|
492
503
|
- Use the object form when you need `params` (reasoning/temperature tuning): `model: { id: "...", params: [...] }`.
|
|
493
504
|
- An empty / whitespace-only string throws a typed `ConfigurationError` (`code: "invalid_model_selection"`).
|
|
494
505
|
|
|
495
|
-
|
|
506
|
+
A bare-string model id is the common shorthand across agent SDKs. Grounded in ROADMAP SE8.
|
|
496
507
|
|
|
497
508
|
- d039cd6: **SE9 — integrated structured output on `agent.generate()`.**
|
|
498
509
|
|
|
499
|
-
New typed `agent.generate(input, { output: schema, ...sendOptions })` method: runs the agent's NORMAL tool loop (the user's tools run first) and then coerces the final answer into a Zod schema, returning a validated, **inferred-typed** object — in one call, instead of a separate `generateObject`. This
|
|
510
|
+
New typed `agent.generate(input, { output: schema, ...sendOptions })` method: runs the agent's NORMAL tool loop (the user's tools run first) and then coerces the final answer into a Zod schema, returning a validated, **inferred-typed** object — in one call, instead of a separate `generateObject`. This delivers structured output straight off the agent's own tool loop.
|
|
500
511
|
|
|
501
512
|
- `agent.generate<T>(input, { output: T, ...SendOptions }): Promise<GenerateRunResult<z.infer<T>>>` — `{ object, result, raw, usage }`. `object` carries the inferred type; `result` is the underlying tool-loop `RunResult` (status/usage/model).
|
|
502
513
|
- **Sugar over `Agent.generateObject` (ADR D33), not a fork:** phase 1 is the user's own `agent.send()` run; phase 2 reuses `generateObjectImpl` (the synthetic forced-`output`-tool + Zod validation + retries) over the run's final answer.
|
|
@@ -504,7 +515,7 @@
|
|
|
504
515
|
- **Typed failure:** a run that errors before an answer surfaces a typed `GenerateObjectError` (no structuring over a failed run); a persistent parse-failure is governed by `errorStrategy`.
|
|
505
516
|
- Available on both local and cloud agents.
|
|
506
517
|
|
|
507
|
-
New exports: `GenerateOptions`, `GenerateRunResult` (+ the `SDKAgent.generate` method). Additive + backward-compatible.
|
|
518
|
+
New exports: `GenerateOptions`, `GenerateRunResult` (+ the `SDKAgent.generate` method). Additive + backward-compatible. Grounded in ROADMAP SE9.
|
|
508
519
|
|
|
509
520
|
### Patch Changes
|
|
510
521
|
|
|
@@ -633,25 +644,25 @@
|
|
|
633
644
|
|
|
634
645
|
### Patch Changes
|
|
635
646
|
|
|
636
|
-
- 6336f81: Suppress the leaked-dialect tool-call from the visible stream (R7). When `extractToolCallsFromContent` is enabled and a model leaks a `<function=NAME>` tool call as assistant text, the OpenAI-compat streaming now HOLDS that text back at the stream boundary (a small suspicion-buffer FSM that reuses the request-scoped allowlist from R5) instead of emitting it as `text_delta` events — so the raw dialect no longer flashes by in the live stream or lands in the final assistant text. `finish()` still recovers the call (unchanged). Fail-open: a never-closing marker or un-suppressable input is flushed as visible text (never held forever). Flag-off streaming is byte-for-byte unchanged. Grounded in
|
|
647
|
+
- 6336f81: Suppress the leaked-dialect tool-call from the visible stream (R7). When `extractToolCallsFromContent` is enabled and a model leaks a `<function=NAME>` tool call as assistant text, the OpenAI-compat streaming now HOLDS that text back at the stream boundary (a small suspicion-buffer FSM that reuses the request-scoped allowlist from R5) instead of emitting it as `text_delta` events — so the raw dialect no longer flashes by in the live stream or lands in the final assistant text. `finish()` still recovers the call (unchanged). Fail-open: a never-closing marker or un-suppressable input is flushed as visible text (never held forever). Flag-off streaming is byte-for-byte unchanged. Grounded in a survey of state-of-the-art stream-normalizer FSMs.
|
|
637
648
|
|
|
638
649
|
## 2.15.1
|
|
639
650
|
|
|
640
651
|
### Patch Changes
|
|
641
652
|
|
|
642
|
-
- bec2077: Make the leaked-dialect recovery **request-scoped (R5)**. The opt-in `extractToolCallsFromContent` recovery previously promoted ANY `<function=NAME>` block leaked into assistant text on an enabled route, so a code assistant printing a literal `<function=example>` in a fenced code block could be wrongly turned into a tool call. Recovery now gates on an exact, case-sensitive allowlist derived automatically from the current request's declared tools (`request.tools`): the per-route flag stays the coarse enable, and the allowlist is the precise false-positive guard. A request with no tools recovers nothing; a gated-out block keeps its text visible (it is not silently deleted). No public API change — the allowlist is derived from the tools you already pass
|
|
653
|
+
- bec2077: Make the leaked-dialect recovery **request-scoped (R5)**. The opt-in `extractToolCallsFromContent` recovery previously promoted ANY `<function=NAME>` block leaked into assistant text on an enabled route, so a code assistant printing a literal `<function=example>` in a fenced code block could be wrongly turned into a tool call. Recovery now gates on an exact, case-sensitive allowlist derived automatically from the current request's declared tools (`request.tools`): the per-route flag stays the coarse enable, and the allowlist is the precise false-positive guard. A request with no tools recovers nothing; a gated-out block keeps its text visible (it is not silently deleted). No public API change — the allowlist is derived from the tools you already pass, following the standard tool-call-repair allowlist approach.
|
|
643
654
|
|
|
644
655
|
## 2.15.0
|
|
645
656
|
|
|
646
657
|
### Minor Changes
|
|
647
658
|
|
|
648
|
-
- d7057f2: Add a **doom-loop / no-progress guard** to the agent loop. The loop now detects when the model repeats IDENTICAL tool calls (same name + same canonical input) that make no progress — the qwen3-coder `read_file`/`not_found` failure mode where the model retries the same failing call and the run grinds to the iteration ceiling — and stops early with a typed `no_progress` terminal instead of hanging. A pure `DoomLoopTracker` (canonical key-sorted-JSON signature + a consecutive-identical counter) escalates from a one-time guidance nudge at a soft threshold to a hard stop; the hard stop surfaces on `RunResult.stoppedByDoomLoop` and, through the continuation driver, as `terminal: "no_progress"` (so the outer loop does not re-send). It complements — does not replace — the existing empty-round `no_progress` (a different failure mode: model stuck repeating vs model gone silent). On by default with generous thresholds (soft 3 / hard 5); tune or disable per send via `SendOptions.doomLoop` (`false` to disable, or `{ softThreshold, hardThreshold }` to tune). Dependency-free. Grounded in a
|
|
659
|
+
- d7057f2: Add a **doom-loop / no-progress guard** to the agent loop. The loop now detects when the model repeats IDENTICAL tool calls (same name + same canonical input) that make no progress — the qwen3-coder `read_file`/`not_found` failure mode where the model retries the same failing call and the run grinds to the iteration ceiling — and stops early with a typed `no_progress` terminal instead of hanging. A pure `DoomLoopTracker` (canonical key-sorted-JSON signature + a consecutive-identical counter) escalates from a one-time guidance nudge at a soft threshold to a hard stop; the hard stop surfaces on `RunResult.stoppedByDoomLoop` and, through the continuation driver, as `terminal: "no_progress"` (so the outer loop does not re-send). It complements — does not replace — the existing empty-round `no_progress` (a different failure mode: model stuck repeating vs model gone silent). On by default with generous thresholds (soft 3 / hard 5); tune or disable per send via `SendOptions.doomLoop` (`false` to disable, or `{ softThreshold, hardThreshold }` to tune). Dependency-free. Grounded in a survey of state-of-the-art loop-detection and doom-loop guards.
|
|
649
660
|
|
|
650
661
|
## 2.14.0
|
|
651
662
|
|
|
652
663
|
### Minor Changes
|
|
653
664
|
|
|
654
|
-
- 6ee4217: Add a public, isolated tool-input **sanitization** primitive on the new `@theokit/sdk/sanitize` subpath, plus a declarative `defineTool({ sanitize })` opt-in. Custom tools can now clean the raw arguments a model emits before they reach the tool schema: `sanitizeToolInput(input, options?)` trims whitespace by default and — opt-in — coerces string values toward their expected type (`"5"`→`5`, `"true"`→`true`, JSON-encoded strings→arrays/objects) and repairs malformed JSON (via `jsonrepair`). Coercion is guarded against silent corruption: numeric coercion round-trips and stays finite, so ID-like strings (`"12345678901234567890"`, `"007"`) and `NaN`/`Infinity` are left as strings; JSON repair only runs on JSON-looking values; a non-object input is returned untouched (the primitive is total — it never throws). When a Zod object schema is passed, coercion is schema-aware (a `z.string()` field keeps `"5"` a string). `defineTool({ sanitize: true })` trims the raw args before validation; `defineTool({ sanitize: { coerce: true } })` additionally coerces toward the tool's own schema — absent, `defineTool` behaviour is unchanged. Internally, the leaked-dialect recovery (`hermes-tool-extract`) now reuses the same primitive, so the public surface and the internal path never diverge. Grounded in a
|
|
665
|
+
- 6ee4217: Add a public, isolated tool-input **sanitization** primitive on the new `@theokit/sdk/sanitize` subpath, plus a declarative `defineTool({ sanitize })` opt-in. Custom tools can now clean the raw arguments a model emits before they reach the tool schema: `sanitizeToolInput(input, options?)` trims whitespace by default and — opt-in — coerces string values toward their expected type (`"5"`→`5`, `"true"`→`true`, JSON-encoded strings→arrays/objects) and repairs malformed JSON (via `jsonrepair`). Coercion is guarded against silent corruption: numeric coercion round-trips and stays finite, so ID-like strings (`"12345678901234567890"`, `"007"`) and `NaN`/`Infinity` are left as strings; JSON repair only runs on JSON-looking values; a non-object input is returned untouched (the primitive is total — it never throws). When a Zod object schema is passed, coercion is schema-aware (a `z.string()` field keeps `"5"` a string). `defineTool({ sanitize: true })` trims the raw args before validation; `defineTool({ sanitize: { coerce: true } })` additionally coerces toward the tool's own schema — absent, `defineTool` behaviour is unchanged. Internally, the leaked-dialect recovery (`hermes-tool-extract`) now reuses the same primitive, so the public surface and the internal path never diverge. Grounded in a survey of state-of-the-art agent runtimes.
|
|
655
666
|
|
|
656
667
|
## 2.13.1
|
|
657
668
|
|
|
@@ -927,7 +938,7 @@
|
|
|
927
938
|
|
|
928
939
|
- 461c020: `createSquad` sequential agent-team convenience + `Agent.batch` boundary validation — first real npm publish.
|
|
929
940
|
|
|
930
|
-
- **`createSquad(options)`** — composes `Workflow.create()` + `agentStep` into a sequential agent team (own identity
|
|
941
|
+
- **`createSquad(options)`** — composes `Workflow.create()` + `agentStep` into a sequential agent team (own identity, built on the SDK's own primitives). Throws `ConfigurationError` (`invalid_squad` for empty agents, `squad_process_unsupported` for hierarchical). Cross-validation Gap 1.
|
|
931
942
|
- **`Agent.batch`** now fail-fast validates `concurrency` + prompt items at the public boundary (`ConfigurationError` with `invalid_concurrency` / `invalid_batch_item`) before any side effect. Cross-validation Gap 3.
|
|
932
943
|
|
|
933
944
|
Note: these features were tagged as `v1.8.0` but that version's npm publish failed (CI build cycle, fixed in `turbo.json`); `1.8.0` / `1.8.1` on npm predate them. They are published to npm for the first time in `1.9.0`. The `[1.8.0]` CHANGELOG section is retained as the GitHub-released record and is not rewritten.
|
|
@@ -1124,7 +1135,7 @@
|
|
|
1124
1135
|
- `SubscriptionCtx`, `SubscriptionDescriptor<TInput, TOutput>`, `TrackedEnvelope<T>` (types)
|
|
1125
1136
|
- **3 typed error classes:** `SubscriptionError`, `SubscriptionInputError` (carries Zod `issues`), `SubscriptionDisconnectError` (carries `closeCode`/`closeReason`). All extend `TheokitAgentError`.
|
|
1126
1137
|
- **`ws@>=8.0.0` + `@types/ws@>=8.0.0` optional peer deps** — Node WS adapter loads `ws` via dynamic `import()` with actionable error when missing (D426). SSE-only consumers pay zero cost.
|
|
1127
|
-
- **W3C-spec SSE encoder + parser** — independent of D38
|
|
1138
|
+
- **W3C-spec SSE encoder + parser** — independent of the D38 Data Stream v1 wire format (which stays locked for `streamAssistant` LLM streaming). Both coexist (D429).
|
|
1128
1139
|
- **Server integration primitives** — `scanSubscriptions({appDir, outFile})` emits `.theo/subscriptions.json` mirroring G6 routes scanner; `mountSubscriptions({manifest, appDir})` returns `{handleSseRequest, handleWsUpgrade}` ready to wire into `http.Server`. theokit-side Vite plugin + dev-server wiring is a cross-repo follow-up (D430).
|
|
1129
1140
|
|
|
1130
1141
|
### ADRs absorbed
|
|
@@ -1135,7 +1146,7 @@
|
|
|
1135
1146
|
- **D426** — `ws` Node canonical (optional peer); CF Workers / Bun / Deno deferred to v1.8.x as separate packages
|
|
1136
1147
|
- **D427** — `defineSubscription` AsyncGenerator + Zod input/output
|
|
1137
1148
|
- **D428** — `subscribe` lives at `@theokit/sdk/subscription` sub-path only (NOT promoted to `Theokit.subscribe` due to pre-existing `agent.ts ↔ fork-agent.ts` rollup-dts cycle; same isolation pattern as `path-safety`)
|
|
1138
|
-
- **D429** — W3C SSE wire format (independent of D38
|
|
1149
|
+
- **D429** — W3C SSE wire format (independent of the D38 Data Stream)
|
|
1139
1150
|
- **D430** — Server auto-route via `theokit.subscriptions` scanner (cross-repo follow-up for theokit-side wiring)
|
|
1140
1151
|
|
|
1141
1152
|
### Security threats addressed
|
|
@@ -1160,7 +1171,7 @@
|
|
|
1160
1171
|
|
|
1161
1172
|
### Notes
|
|
1162
1173
|
|
|
1163
|
-
- v1.7.0 is **additive** — no breaking changes. Existing `streamAssistant` (
|
|
1174
|
+
- v1.7.0 is **additive** — no breaking changes. Existing `streamAssistant` (Data Stream wire format, D38) untouched.
|
|
1164
1175
|
- Tests: **45 GREEN + 1 honest-SKIP** under `tests/subscription/` + `tests/integration/subscription-resume.test.ts` (real `ws.WebSocketServer` + `http.Server` real SSE roundtrip + lastEventId resume) + `tests/integration/subscription-real-llm.test.ts` (env-gated `OPENROUTER_API_KEY` — verified GREEN against real OpenRouter `openai/gpt-4o-mini` per `real-llm-validation.md`).
|
|
1165
1176
|
- Build: `dist/subscription/index.{js,cjs,d.ts,d.cts}` emitted; JS+CJS via tsup, DTS via tsc + `tsconfig.tools-dts.json` (mirrors `tools/` + `path-safety` pattern to avoid pre-existing `types/agent.ts ↔ fork-agent.ts` rollup-dts cycle).
|
|
1166
1177
|
|
|
@@ -1424,7 +1435,7 @@ FAIL before the fix).
|
|
|
1424
1435
|
- OpenAI / OpenRouter SSE accumulator parses 5 token buckets:
|
|
1425
1436
|
`prompt_tokens_details.cached_tokens` → `cacheReadTokens`,
|
|
1426
1437
|
`completion_tokens_details.reasoning_tokens` → `reasoningTokens`, plus the
|
|
1427
|
-
|
|
1438
|
+
top-level `cache_read_input_tokens` /
|
|
1428
1439
|
`cache_creation_input_tokens` fallback for Anthropic-on-OpenRouter.
|
|
1429
1440
|
- `stream_options: { include_usage: true }` is now sent on every
|
|
1430
1441
|
Chat Completions request so the final usage chunk arrives reliably.
|
|
@@ -1483,7 +1494,7 @@ FAIL before the fix).
|
|
|
1483
1494
|
- **`ConversationStorageAdapter`** interface + `FileSystemConversationStorage` (default) + `InMemoryConversationStorage`. New `AgentOptions.conversationStorage` opt-in. Postgres + Redis recipes in `docs/recipes/`. Strict resume integrity check via `requiresCustomStorage` marker (D325).
|
|
1484
1495
|
- **`Agent.registry`** — LRU + idle-timeout GC for live `SDKAgent` instances. `configure / evict / evictAll / size / ids` + `onEvict` listener. Defaults: `maxAgents: 100`, `idleTimeoutMs: 30 min`. Eliminates OOM in 24/7 Node deploys.
|
|
1485
1496
|
- **`AgentRunErrorCode`** discriminated union (16 codes including `quota_exceeded`, `tool_runtime_error`, `aborted`, `invalid_model`, `safety_blocked`, `provider_unreachable`). Plus `AgentRunError.requestId` / `.conversationId` fields and `.retriable` / `.retryAfterMs` / `.providerError` getters. Anti-leak invariant: `providerError` never in `.message`.
|
|
1486
|
-
- **`SendOptions.signal`** propagates end-to-end to LLM `fetch({ signal })`. Tokens stop billing on caller cancel. `anySignal` ponyfill for
|
|
1497
|
+
- **`SendOptions.signal`** propagates end-to-end to LLM `fetch({ signal })`. Tokens stop billing on caller cancel. `anySignal` ponyfill for edge runtimes without native `AbortSignal.any`. `agent.dispose()` fires lifecycle abort. Aborted runs throw `AgentRunError({ code: "aborted" })`; no partial assistant message persists.
|
|
1487
1498
|
- **`AgentOptions.onToolStart` / `onToolEnd` / `onToolError`** — observation callbacks with `callId` pair correlation + `durationMs`. Hook errors swallowed (do not crash run).
|
|
1488
1499
|
- **`AgentOptions.onBeforeCreate` / `onBeforeSend`** — admission gates for multi-tenant quota. Errors propagate (NOT swallowed — these are blockers, not observers).
|
|
1489
1500
|
|
|
@@ -1511,7 +1522,7 @@ Closes Gap 6 of the TheoKit cross-repo handoff. Lets multi-tenant SaaS deploys e
|
|
|
1511
1522
|
|
|
1512
1523
|
Closes Gap 4 of the TheoKit cross-repo handoff. Cost tracking, audit log, per-tool retry/alerting without writing a plugin.
|
|
1513
1524
|
|
|
1514
|
-
- **`AgentOptions.onToolStart`**, **`onToolEnd`**, **`onToolError`** callbacks accepted in `AgentOptions` (top-level — no plugin needed; D315).
|
|
1525
|
+
- **`AgentOptions.onToolStart`**, **`onToolEnd`**, **`onToolError`** callbacks accepted in `AgentOptions` (top-level — no plugin needed; D315). Familiar `onChunk`/`onFinish`-style callback ergonomics.
|
|
1515
1526
|
- **`callId` propagated** through the start/end (or start/error) pair from the existing `generateCallId()` in dispatch (D316). Consumers correlate without managing their own counter.
|
|
1516
1527
|
- **`durationMs`** measured between start hook fire and end/error hook fire — handler latency.
|
|
1517
1528
|
- **Hook errors swallowed** via single `safeEmitToolHook` chokepoint (D317). Listener throws logged to stderr but never crash the run.
|
|
@@ -1526,7 +1537,7 @@ Closes Gap 5 of the TheoKit cross-repo handoff. Tokens stop billing the moment a
|
|
|
1526
1537
|
|
|
1527
1538
|
- **`SendOptions.signal`** (already typed) now flows from `LocalAgent.send` → `dispatchRun` → `real-local-run.buildLoopInputs` → `AgentLoopInputs.signal` → `streamLlmTurn` → LLM client `fetch({ signal })`. The infrastructure was already in place at every LLM client; only the orchestrator wiring was missing.
|
|
1528
1539
|
- **`LocalAgent.#lifecycleAbortController`**: every agent owns a private controller fired by `dispose()`. `send()` composes `[userSignal, lifecycleSignal]` via `anySignal` so eviction (`Agent.registry.evict`) cancels in-flight LLM calls promptly.
|
|
1529
|
-
- **`anySignal` ponyfill** (`internal/runtime/abort-utils.ts`) absorbs EC-5: native `AbortSignal.any` when available, ponyfill for runtimes
|
|
1540
|
+
- **`anySignal` ponyfill** (`internal/runtime/abort-utils.ts`) absorbs EC-5: native `AbortSignal.any` when available, ponyfill for edge runtimes that lag. Single-signal short-circuit, undefined entries filtered, abort `reason` propagated.
|
|
1530
1541
|
- **`AgentLoopInputs.signal`** new optional field; loop uses caller's signal when present, never-aborting placeholder otherwise (legacy behavior preserved when nothing wired).
|
|
1531
1542
|
- **Aborted runs surface as `AgentRunError({ code: "aborted", retriable: false })`** (D321 + T3.5 finalization). `err.cause` preserves the original `DOMException`.
|
|
1532
1543
|
- **Aborted runs do not persist partial assistant messages** (D320): the user message persists at entry; the abort path skips the assistant append, preserving conversation history invariant.
|
|
@@ -1576,7 +1587,7 @@ Closes Gap 2 of the TheoKit cross-repo handoff. Eliminates OOM in 24/7 Node depl
|
|
|
1576
1587
|
|
|
1577
1588
|
### Added (`ConversationStorageAdapter` — pluggable conversation persistence — Production-Readiness #1)
|
|
1578
1589
|
|
|
1579
|
-
Closes Gap 1 of the TheoKit cross-repo production-readiness handoff (`docs/handoffs/from-theokit/2026-05-25-production-readiness.md`). Unblocks serverless (
|
|
1590
|
+
Closes Gap 1 of the TheoKit cross-repo production-readiness handoff (`docs/handoffs/from-theokit/2026-05-25-production-readiness.md`). Unblocks serverless (Cloudflare Workers, AWS Lambda) and multi-host (K8s replicas, TheoCloud canary) deploys that cannot use the default `<cwd>/.theokit/agents/<id>/messages.jsonl` filesystem persistence.
|
|
1580
1591
|
|
|
1581
1592
|
- **`ConversationStorageAdapter`** interface exported from `@theokit/sdk`. 5 methods (`getMessages`, `appendMessage`, `deleteConversation`, optional `listConversationIds`, optional `compact`, optional `dispose`). Implementations return `Promise<>` uniformly for adapter polymorphism (ADR D306).
|
|
1582
1593
|
- **`FileSystemConversationStorage`** exported. Default when `AgentOptions.conversationStorage` is unset (zero migration — existing apps unaffected). Wraps the pre-D303 byte-identical behavior including redaction (D68) + compaction every 50 appends (D18). Path-traversal guard re-applied in `deleteConversation` (EC-1, ADR D304); ENOENT swallowed in `listConversationIds` for first-run deploys (EC-2).
|
|
@@ -1647,7 +1658,7 @@ and llama.cpp sibling profiles. 100% local, zero remote API keys required.**
|
|
|
1647
1658
|
into `OpenAIClient` via new optional `providerName` constructor option.
|
|
1648
1659
|
- **Provider inference from model.id prefix** (D186). `model: "ollama/llama3.2:3b"`
|
|
1649
1660
|
routes to the Ollama profile and sends `llama3.2:3b` as the model name to the
|
|
1650
|
-
LLM body. Aligned with OpenRouter / Hermes
|
|
1661
|
+
LLM body. Aligned with OpenRouter / Hermes provider-prefix routing patterns. Aliases
|
|
1651
1662
|
`llama-cpp`/`llama.cpp` → `llamacpp`, `lm-studio` → `lmstudio`.
|
|
1652
1663
|
- **CredentialPool no-op for `authType: "none"`** (D187). `apiKeys: { ollama: [...] }`
|
|
1653
1664
|
is silently ignored with one-shot stderr warn instead of building a meaningless
|
|
@@ -2343,10 +2354,10 @@ tool-call-failure-recovery.md` (Hermes v0.2 #444, v0.3 #1300,
|
|
|
2343
2354
|
- D63 — WAL primary, DELETE journal fallback on NFS/SMB; warn once per label
|
|
2344
2355
|
- D64 — FTS5 sanitizer 6-step + CJK auto-detection (trigram routing deferred to v1.4)
|
|
2345
2356
|
|
|
2346
|
-
### Added (v1.2 features
|
|
2357
|
+
### Added (v1.2 features)
|
|
2347
2358
|
|
|
2348
2359
|
- **`Agent.streamObject<T>({ schema, prompt, ... })`** — typed structured output WITH partial-object streaming via synthetic forced tool (ADR D39). Returns `AsyncIterator<StreamObjectEvent<T>>` emitting zero or more `{ type: "partial", partial: DeepPartial<T>, attempt }` events plus exactly one `{ type: "complete", object: z.infer<T>, ... }` at the end. Reuses 80% of `generateObject` infrastructure. EC-4 (cancellation cleanup), EC-5 (refine/transform fallback), EC-6 (parallel tool-use dedup) covered by tests.
|
|
2349
|
-
- **`@theokit/react` v1.2.0 — family of 3 hooks** (ADR D40): `useTheoChat` (multi-turn, existing) + `useTheoCompletion` (single-shot text gen
|
|
2360
|
+
- **`@theokit/react` v1.2.0 — family of 3 hooks** (ADR D40): `useTheoChat` (multi-turn, existing) + `useTheoCompletion` (single-shot text gen) + `useTheoAssistant<T>` (object-shaped streaming, wraps `Agent.streamObject`). Each hook has a matching server-side handler: `streamTheoChat`, `streamCompletion`, `streamAssistant`. Shared SSE parser in `internal/sse-parser.ts` handles all wire codes including new `o:`/`O:` for object streaming (ADR D45).
|
|
2350
2361
|
- **OAuth 2.1 PKCE for MCP HTTP servers** (ADR D41). `McpAuthConfig.oauth` opts into the flow. Two modes: `manual` (paste callback URL via stdin, SSH-friendly) and `localhost` (auto-spawned http.createServer on a free port). Token storage prefers OS keychain (`keytar`, optional peer dep) with `~/.theokit/mcp-tokens.json` (chmod 600) fallback. EC-2 (state CSRF validation), EC-9 (concurrent refresh serialization), EC-10 (default expires_in 3600s) covered.
|
|
2351
2362
|
- **Auto-instrumentation of telemetry vendors** (ADR D42). `tracer.ts` feature-detects `@langfuse/node` v3+, `@sentry/node`, and `posthog-node` via `createRequire`. When present + `telemetry.enabled: true`, registers OTel exporter automatically. Opt-out via `telemetry.autoDetect: false` OR `telemetry.disable: ["langfuse"]`. EC-12 (double-billing prevention) covered.
|
|
2352
2363
|
- **LanceDB backend for Memory.index** (ADR D43). `Memory.create({ index: { backend: "lance" } })` activates `@lancedb/lancedb` (optional peer dep). SQLite remains default. Lance scales to 100k+ facts. Filters use Lance's structured filter API — NO string interpolation, EC-1 MUST FIX. EC-8 (embedding dim mismatch) typed error.
|
|
@@ -2371,7 +2382,7 @@ tool-call-failure-recovery.md` (Hermes v0.2 #444, v0.3 #1300,
|
|
|
2371
2382
|
|
|
2372
2383
|
- **`Agent.generateObject<T>({ schema, prompt })`** — typed structured output via synthetic forced tool (ADR D33). Returns `{ object: z.infer<T>, raw, usage, finishReason }`. Retry-on-parse-fail with `maxRetries` (default 1). Transient agent disposed AND hard-deleted from registry across retries (EC-3 no leak). Same provider routing/fallback as `agent.send`.
|
|
2373
2384
|
- **`AgentOptions.telemetry`** — opt-in OpenTelemetry spans for `agent.send`, `llm.call`, `tool.call` (ADR D34). Privacy-by-default: NO content logged unless `includeContent: true`. `@opentelemetry/api` is OPTIONAL peer dep loaded via `createRequire`. All OTel calls wrapped in `safe()` so exporter errors NEVER propagate to `agent.send` (EC-1).
|
|
2374
|
-
- **`@theokit/react` v1.0.0** — new workspace package (ADR D32). `useTheoChat` React hook (HTTP fetch + SSE parser, AbortController on unmount, EC-6 5xx handling, EC-8 graceful close). `streamTheoChat` Next.js-compatible SSE handler (EC-2 pre-stream typed errors return HTTP 400/401). Wire format =
|
|
2385
|
+
- **`@theokit/react` v1.0.0** — new workspace package (ADR D32). `useTheoChat` React hook (HTTP fetch + SSE parser, AbortController on unmount, EC-6 5xx handling, EC-8 graceful close). `streamTheoChat` Next.js-compatible SSE handler (EC-2 pre-stream typed errors return HTTP 400/401). Wire format = Data Stream v1 (drop-in `useChat` migration; no `ai` package runtime dep). React peer dep `^18 || ^19`.
|
|
2375
2386
|
|
|
2376
2387
|
### Validations (v1.1 pillar audits)
|
|
2377
2388
|
|
|
@@ -2458,7 +2469,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
|
2458
2469
|
|
|
2459
2470
|
### Added (multimodal demo `examples/telegram-pro`)
|
|
2460
2471
|
|
|
2461
|
-
- **New `examples/telegram-pro/`** — ~600 LoC Telegram bot
|
|
2472
|
+
- **New `examples/telegram-pro/`** — ~600 LoC Telegram bot demonstrating the 5 highest-value Telegram integration patterns on top of `@theokit/sdk` 1.0.0:
|
|
2462
2473
|
- **Voice transcription** ([`src/transcribe.ts`](../../examples/telegram-pro/src/transcribe.ts)) — downloads the OGG/Opus from Telegram, POSTs multipart to Whisper. Provider order: `OPENAI_API_KEY` → `GROQ_API_KEY` → graceful "voice not configured" reply. Transcript is injected into the agent loop as `[voice transcript: ...]`.
|
|
2463
2474
|
- **Vision** ([`src/vision.ts`](../../examples/telegram-pro/src/vision.ts)) — photo and sticker descriptions via `google/gemini-2.0-flash-001` multimodal on OpenRouter. Disk-cached at `.theokit/cache/vision/<sha256>.txt` keyed by Telegram's `file_unique_id`, so repeated stickers (common in groups) skip the LLM roundtrip.
|
|
2464
2475
|
- **Inline buttons** ([`src/buttons.ts`](../../examples/telegram-pro/src/buttons.ts)) — agent emits `[BUTTONS: A | B | C]` at end of reply; example strips the marker, renders a grammy `InlineKeyboard`, and routes button taps back to the agent as `[user tapped button: A]` so conversation history stays consistent.
|
|
@@ -2587,15 +2598,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
|
2587
2598
|
- **`placeholderScript` renamed to `unusedFixtureScript`** in `real-local-run.ts` + `real-cloud-run.ts` with a clarifying comment — the FixtureScript shape is required by the base Run class but never consumed by the real-LLM path.
|
|
2588
2599
|
- **`index-schema.ts` comment** corrected — `meta` table description matches what the code actually persists (embedding identity), and the `embeddings` virtual table is now documented.
|
|
2589
2600
|
|
|
2590
|
-
### Changed (memory-system
|
|
2601
|
+
### Changed (memory-system parity, Increment D — Dogfood follow-ups)
|
|
2591
2602
|
|
|
2592
2603
|
- **`local-agent.ts` decomposed** — memory glue (lazy IndexManager + tools cache + Active Memory breaker + summary cache) extracted to `local-agent-memory.ts`. Brings `local-agent.ts` under the G8 400-LoC cap.
|
|
2593
2604
|
- **`legacyMemoryJsonPath` centralized in `memory/types.ts`** — removes the 9-line jscpd clone between `migration.ts` and `runtime/memory-store.ts`. Both now call the leaf-module helper.
|
|
2594
2605
|
|
|
2595
|
-
### Added (memory-system
|
|
2606
|
+
### Added (memory-system parity, Increment C — Dogfood examples + Memory namespace)
|
|
2596
2607
|
|
|
2597
2608
|
- **`Memory` public namespace** exported from `@theokit/sdk` — `Memory.runDreamingSweep({ cwd, embedding })` lets users trigger consolidation outside of `agent.send()` (e.g. from a cron job handler).
|
|
2598
|
-
- **`MemoryEmbeddingRuntime` public type** — `embedding` now accepts either a built-in provider id (`{ provider, model? }`) OR a BYO runtime (`{ runtime: MemoryEmbeddingRuntime }`). Enables self-hosted/local embedding models and self-contained demos without external API creds.
|
|
2609
|
+
- **`MemoryEmbeddingRuntime` public type** — `embedding` now accepts either a built-in provider id (`{ provider, model? }`) OR a BYO runtime (`{ runtime: MemoryEmbeddingRuntime }`). Enables self-hosted/local embedding models and self-contained demos without external API creds. Follows the `EmbeddingRuntime` shape from ADR D3.
|
|
2599
2610
|
- **4 new example apps** under `examples/`:
|
|
2600
2611
|
- **`memory-search`** — LLM uses `memory_search` to find facts in MEMORY.md.
|
|
2601
2612
|
- **`memory-get`** — LLM uses `memory_get` for bounded reads of `notes/*.md`.
|
|
@@ -2603,44 +2614,44 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
|
2603
2614
|
- **`memory-dreaming`** — `Memory.runDreamingSweep` consolidates duplicates + clusters + writes a dream-diary entry. Ships with a deterministic local-demo embedding fallback so the example runs without `OPENAI_API_KEY` / `MISTRAL_API_KEY`.
|
|
2604
2615
|
- **`examples/README.md` inventory** updated with all 4 new examples marked ✅ Full.
|
|
2605
2616
|
|
|
2606
|
-
### Added (memory-system
|
|
2617
|
+
### Added (memory-system parity, Increment B — Active Memory wire-up)
|
|
2607
2618
|
|
|
2608
2619
|
- **`memory.activeRecall.enabled`** runtime wire-up — when `true`, the SDK calls `runActiveMemory` before every `send()` and prepends the recall summary as a `<active-memory>` block to the LLM system prompt (priority 5 — above context/skills/memory).
|
|
2609
2620
|
- **Per-agent `CircuitBreaker` + `ActiveMemoryCache`** — instantiated lazily on first send with active recall enabled. Keyed by `agentId` so multiple agents in the same process don't share state.
|
|
2610
2621
|
- **Stub-server E2E proof** — captured Anthropic request body contains `<active-memory>` when enabled, and does NOT when disabled.
|
|
2611
2622
|
- **Active recall config surface** — `queryMode` (`"message"` / `"recent"` / `"full"`), `timeoutMs`, `maxSummaryChars`, `persistTranscripts` are all wired from `MemorySettings.activeRecall` through to `runActiveMemory`.
|
|
2612
2623
|
|
|
2613
|
-
### Added (memory-system
|
|
2624
|
+
### Added (memory-system parity, Increment A — Agent.create/send wire-up)
|
|
2614
2625
|
|
|
2615
2626
|
- **`MemorySettings.index`** public field — `{ tools?: boolean; backend?: "sqlite-vec" | "lancedb"; embedding?: { provider, model? } }`. When `memory.enabled === true` and `index.tools !== false`, the SDK lazily opens an `IndexManager` on first send + registers `memory_search` and `memory_get` with the LLM. Default backend is `sqlite-vec`; default embedding is none (FTS-only mode).
|
|
2616
2627
|
- **`MemorySettings.activeRecall`** public field — reserved for Phase 7 wire-up (next increment). Type surface live today; runtime hookup pending.
|
|
2617
2628
|
- **Stub-server E2E tests** prove memory tools appear in the captured Anthropic request body's `tools` array when memory is enabled, and are absent when disabled or opted-out via `index.tools: false`.
|
|
2618
2629
|
- **Lazy embedding adapter resolution** — when `index.embedding.provider` is set, the SDK looks the adapter up via `MEMORY_EMBEDDING_ADAPTERS` and instantiates it on first send. Adapter failures degrade gracefully to FTS-only mode with a stderr warning.
|
|
2619
2630
|
|
|
2620
|
-
### Added (memory-system
|
|
2631
|
+
### Added (memory-system parity, Phase 13)
|
|
2621
2632
|
|
|
2622
|
-
- **Cross-validation report** at `.claude/knowledge-base/reviews/cross-validation/memory-system-
|
|
2633
|
+
- **Cross-validation report** at `.claude/knowledge-base/reviews/cross-validation/memory-system-parity-xval-2026-05-16.md`. Verdict **APROVADO COM RESSALVAS**, zero BLOCKERs. All 10 ADRs cross-checked against shipped code; all 13 edge cases verified resolved or documented.
|
|
2623
2634
|
|
|
2624
|
-
### Added (memory-system
|
|
2635
|
+
### Added (memory-system parity, Phase 12)
|
|
2625
2636
|
|
|
2626
2637
|
- **Backend selector** — `IndexManager.open({ backend: "sqlite-vec" | "lancedb" })`. Default `"sqlite-vec"`. `"lancedb"` reserved for Phase 12.1; throws `ConfigurationError(code: "memory_backend_not_implemented")` today (same KISS pattern as the Phase 11 stub embedding adapters).
|
|
2627
2638
|
|
|
2628
|
-
### Added (memory-system
|
|
2639
|
+
### Added (memory-system parity, Phase 11)
|
|
2629
2640
|
|
|
2630
|
-
- **`MEMORY_EMBEDDING_ADAPTERS` catalog** exports all 7
|
|
2641
|
+
- **`MEMORY_EMBEDDING_ADAPTERS` catalog** exports all 7 provider ids: `openai`, `mistral`, `voyage`, `deepinfra`, `lmstudio`, `google`, `bedrock`. Switching is one config field.
|
|
2631
2642
|
- **Mistral adapter** fully implemented — `mistral-embed` (1024 dims) via shared OpenAI-compatible factory (`POST /v1/embeddings`). Honors `MISTRAL_API_KEY` + `MISTRAL_API_BASE_URL`.
|
|
2632
2643
|
- **`createOpenAiCompatibleRuntime` shared factory** — extracted from the OpenAI adapter so any provider exposing the `{ model, input }` → `{ data: [{ embedding }] }` REST shape can plug in with a one-file thin wrapper.
|
|
2633
2644
|
- **5 stub adapters** (Voyage, DeepInfra, LMStudio, Google, Bedrock) — metadata-only. `embed()` throws `ConfigurationError(code: "adapter_not_implemented")` so callers detect the gap without crashing the agent loop.
|
|
2634
2645
|
|
|
2635
|
-
### Added (memory-system
|
|
2646
|
+
### Added (memory-system parity, Phase 10)
|
|
2636
2647
|
|
|
2637
2648
|
- **Wiki supplements** — files under `.theokit/memory/wiki/*.md` are read-only auxiliary corpora discovered by `discoverWikiFiles`. Indexed alongside `MEMORY.md` + `notes/*.md` with `source: "wiki"` tag in the `files` table.
|
|
2638
|
-
- **Corpus filtering in search** — `IndexManager.search(query, { sources: ["wiki"] })` returns only wiki hits; default search returns memory + wiki together. `memory_search` tool already honors `corpus: "wiki" | "memory" | "all"` per the
|
|
2649
|
+
- **Corpus filtering in search** — `IndexManager.search(query, { sources: ["wiki"] })` returns only wiki hits; default search returns memory + wiki together. `memory_search` tool already honors `corpus: "wiki" | "memory" | "all"` per the tool schema from Phase 6.
|
|
2639
2650
|
- **Source coercion on conflict** — `upsertFile` accepts an explicit `source` arg so reclassifying a file (moving a note into the wiki dir, etc.) updates the tag on next sync via `ON CONFLICT DO UPDATE SET source = excluded.source`.
|
|
2640
2651
|
|
|
2641
|
-
### Added (memory-system
|
|
2652
|
+
### Added (memory-system parity, Phase 9)
|
|
2642
2653
|
|
|
2643
|
-
- **`runDreamingSweep`** — cron-driven memory consolidation (ADR D7). Three phases
|
|
2654
|
+
- **`runDreamingSweep`** — cron-driven memory consolidation (ADR D7). Three phases:
|
|
2644
2655
|
- **light** — drop near-duplicate facts via cosine similarity (default threshold 0.95).
|
|
2645
2656
|
- **REM** — single-link agglomerative clustering by cosine similarity (default threshold 0.75).
|
|
2646
2657
|
- **deep** — write a `notes/dreamed-<ts>.md` per sweep with consolidated clusters.
|
|
@@ -2648,53 +2659,53 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
|
2648
2659
|
- **All dreaming writes are atomic (EC-3)** — `replaceFileAtomic` for notes and diary; per-cwd mutex held for the whole sweep so concurrent `Remember:` appends can't race.
|
|
2649
2660
|
- **LLM narrative summarization deferred to Phase 9.1** — v1 ships deterministic clustering only. The interface is stable enough to plug an LLM-mediated `narrative.ts` later without changing the orchestrator.
|
|
2650
2661
|
|
|
2651
|
-
### Added (memory-system
|
|
2662
|
+
### Added (memory-system parity, Phase 8)
|
|
2652
2663
|
|
|
2653
2664
|
- **CircuitBreaker** for Active Memory — `{ maxTimeouts: 3, cooldownMs: 60000 }` defaults. After N consecutive timeouts, `shouldSkip(key)` returns `true` until cooldown elapses. `recordSuccess` resets the counter immediately. Per-key isolation (multiple agents in one process don't share state).
|
|
2654
2665
|
- **`ActiveMemoryCache`** — TTL-bounded LRU keyed by `sha256(userText + queryMode)`. Default TTL 15s, capacity 1000. Cache hits skip the IndexManager search entirely.
|
|
2655
2666
|
- **`runActiveMemory` integration** — accepts optional `breaker` + `cache` + `agentKey` + `runId` + `persistTranscripts` + `cwd`. Breaker is consulted on entry and updated by status; cache stores results on the way out; transcripts written under `.theokit/memory/transcripts/active-memory/<runId>.json` when enabled.
|
|
2656
2667
|
- **`persistActiveMemoryTranscript`** — JSON transcript persistence. Failures swallowed with stderr warning so transcript IO never crashes the agent run.
|
|
2657
2668
|
|
|
2658
|
-
### Added (memory-system
|
|
2669
|
+
### Added (memory-system parity, Phase 7)
|
|
2659
2670
|
|
|
2660
2671
|
- **`runActiveMemory`** — blocking pre-send recall (ADR D6). Default `mode: "search"` calls `IndexManager.search` deterministically; `mode: "subagent"` (LLM-mediated curation) is stubbed for Phase 7.1. Query modes: `"message"` (only the user text), `"recent"` (user text + last N user turns, default 2), `"full"` (entire conversation). Hard timeout via `Promise.race` (default 15000ms) — returns `status: "timeout"` instead of throwing.
|
|
2661
2672
|
- **Status discriminator** — `ActiveMemoryStatus` covers `"ok" | "timeout" | "skipped" | "no-recall" | "error"`. Caller-side dispatch is one switch statement.
|
|
2662
2673
|
- **`ActiveMemoryPromptProvider`** at priority 5 (before context/skills/memory) — contributes the `<active-memory>` block via `SystemPromptAssemblyContext.activeMemorySummary`. Summary is XML-escaped (D9). Block omitted when summary is empty.
|
|
2663
2674
|
- **Pipeline auto-registration** — `SystemPromptPipeline.default()` now wires 5 providers: ActiveMemory (5) → Context (10) → Skills (20) → Memory (30) → Base (100).
|
|
2664
2675
|
|
|
2665
|
-
### Added (memory-system
|
|
2676
|
+
### Added (memory-system parity, Phase 6)
|
|
2666
2677
|
|
|
2667
|
-
- **`memory_search` + `memory_get` tools** (ADR D5) with
|
|
2678
|
+
- **`memory_search` + `memory_get` tools** (ADR D5) with well-defined JSON schemas and descriptions. `memory_search` returns ranked hits with `{ path, startLine, endLine, score, snippet, citation, source }`; `memory_get` returns bounded excerpts with truncation info.
|
|
2668
2679
|
- **Path-traversal guard (EC-2)** — `memory_get` resolves the requested path against the memory root and throws `ConfigurationError(code: "memory_path_escapes_root")` if the resolved path escapes (e.g. `../../etc/passwd`).
|
|
2669
2680
|
- **Result-size cap (EC-10)** — `memory_search` truncates the response when concatenated snippets exceed `maxTotalChars` (default 16384). Low-rank hits are dropped first; `truncated: true` marker on the payload.
|
|
2670
2681
|
- **Agent-loop integration** — new `AgentLoopInputs.memoryTools?: MemoryToolSpec[]` field; `collectTools` appends memory tools alongside shell + MCP tools; `tool-dispatch` routes `origin === "memory"` calls through a dedicated handler that wraps JSON-encoded results.
|
|
2671
2682
|
|
|
2672
|
-
### Added (memory-system
|
|
2683
|
+
### Added (memory-system parity, Phase 5)
|
|
2673
2684
|
|
|
2674
2685
|
- **sqlite-vec vector index** under the existing SQLite DB (ADR D2). `vec0` virtual table stores per-chunk embeddings; `vectorSearch` runs KNN with `MATCH` syntax. `loadSqliteVecExtension` wraps the native load with a typed `sqlite_vec_unavailable` ConfigurationError (EC-8) instead of a raw native exception.
|
|
2675
2686
|
- **`meta` table tracks embedding identity** (`providerId` + `model` + `dimension`). On `IndexManager.open`, current adapter config is compared against stored meta — any mismatch drops the `embeddings` table and forces a full re-embed on next `sync()` (EC-1).
|
|
2676
2687
|
- **Hybrid scoring** (ADR D4): FTS top-K + vector top-K merged, scores combined via `vectorScore * vectorWeight + textScore * textWeight` (defaults `0.6` / `0.4`, configurable per-call). Vector-only hits surface alongside FTS hits via a chunk-id outer join. `MemorySearchHit.vectorScore` exposed when vector backend is active.
|
|
2677
2688
|
- **`IndexManager.open({ cwd, embedding? })`** — embedding-aware constructor. FTS-only still works when `embedding` is omitted; backend reported via `status().backend` as `"fts-only"` or `"hybrid"`.
|
|
2678
2689
|
|
|
2679
|
-
### Added (memory-system
|
|
2690
|
+
### Added (memory-system parity, Phase 4)
|
|
2680
2691
|
|
|
2681
|
-
- **`MemoryEmbeddingProviderAdapter` interface** (ADR D3)
|
|
2692
|
+
- **`MemoryEmbeddingProviderAdapter` interface** (ADR D3) defines the adapter contract: `id`, `defaultModel`, `transport`, `authProviderId`, `autoSelectPriority`, `create(options) → EmbeddingRuntime`. Adapters live under `internal/memory/adapters/`.
|
|
2682
2693
|
- **OpenAI embedding adapter** (`openai-embedding.ts`) — native fetch only, no `openai` SDK dep. Batches at 100 texts/call. Retries once on 429 + 5xx with linear backoff (EC-9). Empty inputs skipped. Honors `OPENAI_API_KEY` + `OPENAI_API_BASE_URL`. Default model `text-embedding-3-small` (1536 dims).
|
|
2683
2694
|
- **LRU embedding cache** keyed by `sha256(model+text)`. Max 5000 entries; oldest evicted first. Observable via `runtime.stats()` (`cacheHits` / `cacheMisses` / `httpCalls` / `retries`).
|
|
2684
2695
|
|
|
2685
|
-
### Added (memory-system
|
|
2696
|
+
### Added (memory-system parity, Phase 3)
|
|
2686
2697
|
|
|
2687
2698
|
- **SQLite + FTS5 index** at `.theokit/memory/.index/memory.sqlite` (ADR D2). Schema: `files`, `chunks`, `chunks_fts` (FTS5 virtual table), `meta`. Triggers keep FTS in sync with `chunks` on insert/delete. WAL mode, foreign keys on. Backed by `better-sqlite3` (optional peer dep) — `node:sqlite` fallback path documented for Node 22.5+.
|
|
2688
2699
|
- **`IndexManager.open / sync / search / status / close`** — full lifecycle. `sync()` walks `MEMORY.md` + `notes/*.md`, computes content hashes, skips unchanged files, deletes old chunks before reindexing changed ones. `search()` runs FTS5 BM25 ranking, returns `MemorySearchHit[]` with `path`, `startLine`, `endLine`, `score`, `textScore`, `snippet`, `source`, `citation` (path:startLine-endLine).
|
|
2689
2700
|
- **Corrupt-DB recovery (EC-7)** — when opening fails with "malformed" / "not a database" / "encrypted" errors, the file is renamed to `<path>.corrupt-<ts>` (plus `-wal` and `-shm` siblings) and the schema is rebuilt from scratch. Diagnostic line emitted to stderr.
|
|
2690
2701
|
|
|
2691
|
-
### Added (memory-system
|
|
2702
|
+
### Added (memory-system parity, Phase 2)
|
|
2692
2703
|
|
|
2693
2704
|
- **`chunkMarkdown`** splits markdown by heading boundaries + blank-line paragraph boundaries. Oversize paragraphs split on word-boundary nearest the cap (EC-6) — never mid-word. Each chunk carries `startLine` / `endLine` / `text` / `hash` (sha256) / optional `heading`.
|
|
2694
|
-
- **`readMemoryFileBounded`** — bounded read with `from` (1-indexed) + `lines` (default 200,
|
|
2695
|
-
- Public types `MemoryChunk`, `MemoryReadResult`, `MemoryFileEntry` in `internal/memory/types.ts`
|
|
2705
|
+
- **`readMemoryFileBounded`** — bounded read with `from` (1-indexed) + `lines` (default 200, matching the `DEFAULT_MEMORY_READ_LINES` constant). Returns `linesReturned`, `totalLines`, `remainingLines`, `truncated` (true when content remains past the slice). Foundation for Phase 6's `memory_get` tool.
|
|
2706
|
+
- Public types `MemoryChunk`, `MemoryReadResult`, `MemoryFileEntry` in `internal/memory/types.ts` capturing the engine-storage shapes.
|
|
2696
2707
|
|
|
2697
|
-
### Added (memory-system
|
|
2708
|
+
### Added (memory-system parity, Phase 1)
|
|
2698
2709
|
|
|
2699
2710
|
- **Markdown-first memory storage** (ADR D1) — facts now persist to `.theokit/memory/MEMORY.md` under a `## Facts` section, human-editable and git-friendly. The legacy JSON file (`.theokit/memory/<namespace>/<scope>-<userId>.json`) migrates one-shot on first read and is deleted afterward (ADR D8). Behavior is preserved: `readMemoryFacts` + `appendMemoryFact` keep their signatures.
|
|
2700
2711
|
- **`replaceFileAtomic` + per-cwd mutex** — every append writes to `<file>.tmp`, fsync, rename; concurrent appends within the same process serialize through a per-`cwd` mutex (edge-case review EC-4). Multi-process safety is out of scope for v1 (documented).
|