# @graphorin/core

## 0.15.1

## 0.15.0

## 0.14.0

## 0.13.13

## 0.13.12

## 0.13.11

## 0.13.10

## 0.13.9

## 0.13.8

## 0.13.7

## 0.13.6

## 0.13.5

### Patch Changes

- [#224](https://github.com/o-stepper/graphorin/pull/224) [`e80d6af`](https://github.com/o-stepper/graphorin/commit/e80d6af60cbad7352b2ebaf35bcef45b91989ce6) Thanks [@o-stepper](https://github.com/o-stepper)! - Every type referenced by a public API is now exported from a documented barrel - about 130 previously unreachable types (memory tool input/output shapes, executor and truncation hooks, audit listener signatures, protocol frame schemas, sandbox peer-module views, new core agent-event variants, and more) became importable, clearing all 115 TypeDoc referenced-but-not-included warnings. Three previously file-local names were renamed while being made public: the tools audit listener is `ToolAuditListener`, the memory-guard listener is `MemoryGuardAuditListener`, the secret-value listener is `SecretValueAuditListener`, and the built-in tool-search match row is `ToolSearchToolMatch` (the registry-level `ToolSearchMatch` is unchanged). None of these were importable before, so no consumer code breaks.

## 0.13.4

## 0.13.3

## 0.13.2

### Patch Changes

- [#213](https://github.com/o-stepper/graphorin/pull/213) [`bba9048`](https://github.com/o-stepper/graphorin/commit/bba9048043fe97ce623079700f0e93b4b7705425) Thanks [@o-stepper](https://github.com/o-stepper)! - A provider stream that finishes while a tool call is still streaming its argument JSON (typically `finishReason: 'length'` at the output-token ceiling) no longer completes the run with the call silently dropped - the runtime now fails the run with `error.code: 'incomplete-tool-call'` so a never-executed side effect can never read as success. The loop threads the provider `finishReason` through the step chain, emits a new terminal `tool.call.incomplete` event per cut call (`toolCallId`, `toolName`, `finishReason`, accumulated `argsPrefix`; additive `AgentEvent` variant, forwarded under the `'lifecycle'` sub-agent policy), records the truncated call's token usage on the failed state, and deliberately skips fallback/retry (a fallback provider would hit the same ceiling, and re-running a side-effecting step needs the caller's idempotency decision). A `'length'` finish with no pending tool call still completes (the text is simply truncated) and is now observable as the new optional `RunStep.finishReason` field.

## 0.13.1

### Patch Changes

- [#209](https://github.com/o-stepper/graphorin/pull/209) [`7991726`](https://github.com/o-stepper/graphorin/commit/7991726fbd06426bdfb1e9c1a5c1e4bd5466d0f4) Thanks [@o-stepper](https://github.com/o-stepper)! - Public TSDoc no longer carries internal audit/work-item ticket ids ("deep retest P1-3", "W-135", wave letters, finding families): roughly 1100 docblock sites across 28 packages were rewritten to describe the behaviour itself, with every technical statement preserved (decision-record references like ADR-x/DEC-x stay). Two runtime strings also dropped their ticket ids: the tools executor's approval-rewrite refusal message and the server's secret-resolution hint. A new `check-api-wording` gate scans the generated API reference and fails CI if ticket vocabulary ever leaks back in.

## 0.13.0

## 0.12.1

## 0.12.0

## 0.11.0

### Minor Changes

- [#193](https://github.com/o-stepper/graphorin/pull/193) [`47b6034`](https://github.com/o-stepper/graphorin/commit/47b60342dc8a345d1299a22a2fc4fe2aac50bb31) Thanks [@o-stepper](https://github.com/o-stepper)! - Ollama server timings surfaced in events and traces (external audit 2026-07-16, item 8). The core `ProviderEvent` `finish` variant gains an optional `providerMetadata` field mirroring `ProviderResponse.providerMetadata` for the streaming path. The Ollama adapter normalizes the server's nanosecond timing fields into the new `OllamaTimings` shape (`totalMs` / `loadMs` / `promptEvalMs` / `evalMs`) and reports it under `providerMetadata.ollama` on both the streamed `finish` event and the `generate()` response, so model load, prompt processing and generation are finally distinguishable - a cold call dominated by `loadMs` no longer looks like slow generation. `withTracing` stamps numeric vendor diagnostics from `providerMetadata` onto the provider span as `graphorin.provider.<vendor>.<key>` attributes (bounded, numbers only). `DEFAULT_OLLAMA_BASE_URL` is now exported from the package barrel.

## 0.10.2

## 0.10.1

### Patch Changes

- [#186](https://github.com/o-stepper/graphorin/pull/186) [`79ef389`](https://github.com/o-stepper/graphorin/commit/79ef3894c409c0a6b9d31fac9b6c888d4068d4e7) Thanks [@o-stepper](https://github.com/o-stepper)! - P3 documentation-drift sweep from the 2026-07 e2e campaign - docstring corrections only, no behavior changes: binary-json's `URL` corruption claim (a URL stringifies to its `href`, not `{}`; CORE-PRO-02); `isAgentFallbackEligible`'s bypass list now names the real `ProviderErrorKind` values (MODEL-FAL-02); `bySensitivity` / `stripSensitiveOutputs` document their actual weak redaction-token contract with an explicit warning instead of a nonexistent part-level sensitivity annotation (AGENT-FIL-01/02); `ProtocolGuardConfig` no longer advertises a nonexistent `Agent.protocolGuard` key (LATERAL-L-03); the token-counter serializer documents the real `[file:<mimeType>]` placeholder and the counter dispatch table the real per-family OpenAI encodings (PROVIDER-CT-02/03); `RegressionOptions` tolerances document their strictly-exceeds semantics (EVALS-REP-01); the reconnect backoff formula matches the implementation (`2^(attempt-1)`; ORPHAN-SU-02); the memory guard states its five tiers (CLI-05); the proactive cron-task docs speak about E1 deny-by-name in the present tense.

## 0.10.0

## 0.9.0

### Minor Changes

- [#170](https://github.com/o-stepper/graphorin/pull/170) [`24241a3`](https://github.com/o-stepper/graphorin/commit/24241a3cdb9c684338f02d4d66510c248eb47d7e) Thanks [@o-stepper](https://github.com/o-stepper)! - Durability tail (item 16, A3). `awaitExternal(name, { schema })` validates the resolved payload at the replay delivery point against a structural `PayloadSchemaLike` (zod v3/v4 compatible, no zod dependency in core): a failing payload restores the suspension instead of failing the thread - the invalid value is discarded (never persisted as satisfied), the thread stays suspended on the same awakeable, and the resolver receives a typed `awakeable-payload-invalid` error. New pure helpers `serializeAwakeableRef` / `parseAwakeableRef` round-trip the canonical `(workflowId, threadId, name)` awakeable address through one compact string for single-slot channel surfaces (messenger callback data); the parser returns `null` on malformed input. The server now warns loudly at `start()` when workflows are registered without a durable-timer driver - a `sleepFor` thread would otherwise sleep forever with zero signal (the wiring stays programmatic by design; the config file carries no domain adapters).

- [#171](https://github.com/o-stepper/graphorin/pull/171) [`08cf387`](https://github.com/o-stepper/graphorin/commit/08cf387a4dc5f4cc9b62462a384efe990309e041) Thanks [@o-stepper](https://github.com/o-stepper)! - Channel-inbound trust boundary (bot-adoption wave B, B1.5). New `'channel-inbound'` member of the core `ToolTrustClass` union, registered in the single `UNTRUSTED_TRUST_CLASSES` source so the taint engine and the Rule-of-Two untrusted-input leg agree by construction; `defaultInboundSanitization` maps it to `detect-and-strip-and-wrap`. The `TaintLedger` gains an optional `recordInboundMessage` entry (same widening + verbatim-span semantics as `recordOutput`) - a first-class input for message-borne untrusted content, which the Rule-of-Two deliberately does not derive from ordinary user messages. Agent side: `DataFlowGuardWithLedgers.recordInboundMessage` plus the new `AgentCallOptions.inboundTaint` seed, stamped in run init before the first step (after the AG-19 resume seed; widen-only), so a channel gateway arms the data-flow policy for every run it starts.

- [#171](https://github.com/o-stepper/graphorin/pull/171) [`08cf387`](https://github.com/o-stepper/graphorin/commit/08cf387a4dc5f4cc9b62462a384efe990309e041) Thanks [@o-stepper](https://github.com/o-stepper)! - New package `@graphorin/channels` - the messenger front door (bot-adoption wave B, item 1). The vendor-neutral adapter SPI (`ChannelAdapter`, `InboundChannelMessage`, the `ChannelIdentity` triple, `ChannelCapabilities`, `DeliveryPayload` with the optional `question` HITL placeholder, typed fire-and-forget `ChannelDeliveryError`); a deterministic identity router (ordered route table, first-match-wins, mandatory catch-all, stable per-peer `defaultSessionKey`; sessionKey is a routing selector, never an authz token); the access policy (`pairing` default with one-time TTL codes and a per-channel pending cap, `allowlist`, `open`, `disabled`) over the new `PairingStore` contract in `@graphorin/core/contracts` (sqlite implementation behind migration 034, exposed as `createSqliteStore(...).pairing`); the gateway runtime (bounded per-adapter queues with shed-on-overflow, access check before any routing or model spend, inbound sanitisation + ready-made `inboundTaint` seed, reply/proactive delivery through the shared outbound catalogue with channel-default `'strip'`); and `@graphorin/channels/testkit` (loopback adapter, in-memory pairing store, framework-agnostic adapter conformance suite). Core also gains the canonical `SttAdapter` contract whose transcripts pin `trustClass: 'channel-inbound'`. The server hosts the gateway structurally (`createServer({ channels })`, new `@graphorin/server/channels` subpath): started last / stopped first in the lifecycle, aggregated into `/v1/health`, and bridged so accepted inbound messages call `scheduler.recordActivity()`. No vendor adapters ship with the framework.

- [#171](https://github.com/o-stepper/graphorin/pull/171) [`08cf387`](https://github.com/o-stepper/graphorin/commit/08cf387a4dc5f4cc9b62462a384efe990309e041) Thanks [@o-stepper](https://github.com/o-stepper)! - Memory writes strictly after guardrails (bot-adoption wave B, B3 / item 15). The run loop's commit gates stamp a per-turn verdict sidecar - `RunState.verdicts`, a plain JSON-safe object keyed `'<step>:<offset>'` with `RunTurnVerdict { guardrail?, lateralLeak?, dataflowFlags? }` - covering input-guardrail block/rewrite, lateral-leak blocks and assistant-output dataflow findings; widen-only merge, serialized through `SerializedRunState` with a defensive rebuild, wiped by compaction for the turns its splice summarized away, and surfaced directly as `AgentResult.verdicts`. Verdicts persist next to the message: `SessionMessagePushOptions.verdict` threads through core `SessionMemoryStore.push` (additive third argument), the memory session tier, `Session.push` and the sqlite store (`verdict_json` column, migration 035; malformed rows degrade to no verdict), and `SessionMessageRecord.verdict` exposes it on the consolidator read path. `createMemory({ ingestGate })` then filters the extraction batch deterministically on BOTH consolidator paths before noise filtering - the canonical `verdictIngestGate` excludes blocked and lateral-leak-withheld turns while rewritten turns pass with their rewritten text; the idempotency cursor still advances through excluded messages (a blocked turn can never wedge consolidation) and a throwing gate fails closed. This gate is the required precondition for the auto-promotion and proactive act-grant features of later waves.

- [#176](https://github.com/o-stepper/graphorin/pull/176) [`7ac0470`](https://github.com/o-stepper/graphorin/commit/7ac0470bdfc579ee864c2ee54e119c94d24ad160) Thanks [@o-stepper](https://github.com/o-stepper)! - Pre-compaction flush, recall ledger, PromotionPolicy and auto-induction (wave-D D4, plan item 3). The context engine gains a `preCompactionHooks` seam (fired before the summarizer with the full buffer; side-effect only, failures fold into `hookFailures`) and the built-in `memoryFlushHook({ provider })` - one budgeted LLM call salvages durable facts from content about to be summarized away, passes candidates through the B3 ingest gate when configured, and writes them QUARANTINED; `SessionMemory.flushImportant` is deprecated in its favour. Migration 036 adds the recall ledger (`fact_recall_queries`: DISTINCT queries per fact by normalized-query hash, fed from the semantic search path, erased with its fact). The deterministic `PromotionPolicy` (`consolidator.promotion: { minSalience?, minRecalls?, minUniqueQueries?, minAgeMs?, allowedProvenance?, maxPerRun? }`) promotes quarantined facts whose recall evidence clears every threshold through the audited `validate` path (injection-flagged refused, pending W-019 supersedes completed; `PhaseOutcome.factsPromoted`). Fail-closed gates: `promotion` or `autoPromoteExtraction: true` without `ingestGate` now throws `IngestGateRequiredError`, and the W-083 guard forces autoPromote update/conflict decisions against QUARANTINED or USER-provenance targets onto the pending-supersede path. On the agent, `createAgent({ procedureInduction: { auto, minSteps?, minToolCalls?, minCostUsd? } })` distils COMPLETED runs above the thresholds via `memory.procedural.induceFromRun` (result stays quarantined; failures WARN once and never fail the run). SpanType gains `memory.consolidate.promotion`.

- [#176](https://github.com/o-stepper/graphorin/pull/176) [`7ac0470`](https://github.com/o-stepper/graphorin/commit/7ac0470bdfc579ee864c2ee54e119c94d24ad160) Thanks [@o-stepper](https://github.com/o-stepper)! - Profile projection (wave-D D2, plan item 6). `createMemory({ profile: { topics?, maxSlots?, maxChars?, scope? } })` adds a deep-phase pass that projects ACTIVE facts (never quarantined, never W-019 pending-supersede - sourced via the new `listActive({ excludePendingSupersede: true })`) into the reserved `profile` working block as deterministic topic / sub-topic / content slots with fact-id provenance; hallucinated provenance references and out-of-taxonomy topics are dropped. The block is registered `readOnly: true` (agent `block_*` tools refuse writes - the consolidator is the single writer) and written USER-scoped by default: session deletion deliberately does not erase it, and the erasure path is the new hard-delete surface `memory.working.purge(userScope, 'profile')` (`WorkingMemoryStore.purge?` optional-additive on the core contract; `forget()` remains a soft tombstone). `PhaseOutcome.profileProjectionUpdated` reports rewrites; configuring `profile` without an enabled consolidator WARNs once. SpanType gains `memory.consolidate.profile-projection`.

- [#176](https://github.com/o-stepper/graphorin/pull/176) [`7ac0470`](https://github.com/o-stepper/graphorin/commit/7ac0470bdfc579ee864c2ee54e119c94d24ad160) Thanks [@o-stepper](https://github.com/o-stepper)! - Memory tool profiles + curated blocks + the reviser preset (wave-D D3, plan item 5). `buildMemoryTools(deps, { profile: 'interactive' | 'reviser' | 'full' })` / `createMemory({ toolProfile })` split the tool surface single-writer-style: `'interactive'` constructs ONLY the read tools (a front-line agent cannot write memory by construction), `'reviser'` is the full read+write surface for the sleep-time curation agent, `'full'` (default) keeps the canonical stable order; unknown values throw. The learned-context pass generalises to a registered curated-block list - `consolidator.curatedBlocks[{ label, prompt?, maxChars? }]` gives each block its own resilient deep-phase rewrite (`learnedContext: true` stays byte-compatible sugar for the `learned_context` entry); labels are validated (unique, non-empty, never the reserved `profile`) via `CuratedBlocksMisconfiguredError`, and `PhaseOutcome.curatedBlocksUpdated` counts rewrites. `reviserConsolidatorPreset({ provider, defaultScope, curatedBlocks?, schedule?, onExceed?, ceilings? })` packages the reviser: standard tier, cheap provider on every phase, idle+cron cadence (cron reaches the deep phase), and a HARD budget posture - `onExceed: 'log'` is rejected. SpanType gains `memory.consolidate.curated-block`.

- [#177](https://github.com/o-stepper/graphorin/pull/177) [`9b389be`](https://github.com/o-stepper/graphorin/commit/9b389be2ac436f66d62b3ede9c64cd70808cfe9f) Thanks [@o-stepper](https://github.com/o-stepper)! - Four-value permissionDecision (wave-E E1, plan item 11). The tool-argument policy vocabulary widens to `allow | deny | ask | defer` with priority `deny > defer > ask > allow` (`'forbid'` stays accepted as the alias of `'deny'`): `evaluatePermissionDecision` is the four-value engine, `evaluateToolArgumentPolicy` its fail-closed binary projection, and `isToolDeniedByName` the advertise-time check over predicate-free deny rules. The executor gains the E1 `permissionHook` phase - one caller decision point evaluated on the validated input after schema validation and BEFORE approval; an allowed `updatedInput` rewrite is re-validated and substituted into both the validated input and the effective args (W-118), a throwing hook fails closed, and on `preApproved` resume replays a rewrite of the granted args is refused (tools-02). `ask`/`defer` verdicts ride the agent pre-screen's durable suspend (`ToolApproval.mode`, mirrored on `tool.approval.requested`); the bare executor fails them closed (`approval_denied`), and a granted resume satisfies them (`deny` still outranks the grant). Deny-by-name filters all three surfaces: the advertised per-step catalogue (post-promotions), `tool_search` results/promotion (`excludeTool`), and execution (executor mirror before validation + the run loop's inline handoff/sub-agent check). The defer parking half: `requestApproval(name, payload, { timeoutAt, timeoutDecision? })` stamps a durable deadline on the approval pause (enumerated by the existing timer daemon); a due `tick` resolves it with the timeout decision - `DEFAULT_APPROVAL_TIMEOUT_DECISION` (auto-deny) unless overridden. Sub-agent asks project through the W-001 composite key unchanged.

## 0.8.0

## 0.7.0

### Minor Changes

- [#154](https://github.com/o-stepper/graphorin/pull/154) [`fe98522`](https://github.com/o-stepper/graphorin/commit/fe98522ce2477c9a7dc09029f9dcfdb1f7c9aa04) Thanks [@o-stepper](https://github.com/o-stepper)! - W-100: `AnyTool` existential type; `createAgent({ tools })` accepts concretely-typed tools without casts.

  `Tool` is invariant in `TInput` (the `needsApproval`/`idempotencyKey` predicate properties are contravariant), so a typed `Tool<{q: string}, number, D>` was never assignable to `Tool<unknown, unknown, D>` - forcing `as unknown as Tool<...>` at every collection seam. `@graphorin/core` now exports `AnyTool<TDeps> = Tool<any, any, TDeps>` (existential input/output, following the `HandoffEntry` precedent), and both `AgentConfig.tools` and `PrepareStepOverrides.tools` in `@graphorin/agent` accept `ReadonlyArray<AnyTool<TDeps>>`. Widening only - existing code keeps compiling. `ToolRegistry.register` was already per-call generic and needed no change.

- [#154](https://github.com/o-stepper/graphorin/pull/154) [`fe98522`](https://github.com/o-stepper/graphorin/commit/fe98522ce2477c9a7dc09029f9dcfdb1f7c9aa04) Thanks [@o-stepper](https://github.com/o-stepper)! - W-005: HITL/workflow checkpoints are linked to their session and erased by the session hard-delete cascade.

  HITL suspends persist the FULL serialized conversation (`RunState`) into `workflow_checkpoints`; previously nothing connected those rows to a session, so `DELETE /v1/sessions/:id` left the entire transcript recoverable forever. Now: `CheckpointMetadata` gains an optional `sessionId` (additive); migration 029 adds a `session_id` column + index to `workflow_checkpoints` and backfills historical `namespace='agent'` rows from the state blob; the agent runtime stamps `sessionId` on all three suspend write sites (step suspend, resume write-ahead intent, post-dispatch journal); and `deleteSession`/`pruneSessions` collect thread ids from BOTH `session_workflow_runs` and the new column, erasing `workflow_checkpoints` + `workflow_pending_writes` before dropping the mapping. Deleting a session now removes its suspended-run snapshots - time-travel/forensics for a deleted session is intentionally gone (that is what hard-delete means).

- [#154](https://github.com/o-stepper/graphorin/pull/154) [`fe98522`](https://github.com/o-stepper/graphorin/commit/fe98522ce2477c9a7dc09029f9dcfdb1f7c9aa04) Thanks [@o-stepper](https://github.com/o-stepper)! - W-009: checkpoint GC primitives - `CheckpointStoreExt` with `pruneThreads` and `compactThread`.

  The engine writes a full state snapshot per execution step and nothing ever deleted them (`deleteThread` had zero production callers). `@graphorin/core` adds the additive `CheckpointStoreExt` contract: `pruneThreads({beforeEpochMs, onlyTerminal})` - a namespace-SCOPED retention sweep whose policy reads each pair's LATEST checkpoint (suspended threads with live HITL approvals/awakeables survive by default) - and `compactThread(threadId, namespace, keepLast)` for in-place history compaction (resume reads the latest tuple, so `keepLast >= 1` never breaks resumability). Implemented by `SqliteCheckpointStore` (per-pair transactions, never via the namespace-blind `deleteThread` - a reused threadId across workflows must not lose another workflow's suspended state) and by `InMemoryCheckpointStore`. `GraphorinSqliteStore.checkpoints` is now typed `CheckpointStoreExt`. `documentation/guide/workflow-engine.md` gains a "Retention and cleanup" section with the growth arithmetic and the deleteThread caveat.

- [#154](https://github.com/o-stepper/graphorin/pull/154) [`fe98522`](https://github.com/o-stepper/graphorin/commit/fe98522ce2477c9a7dc09029f9dcfdb1f7c9aa04) Thanks [@o-stepper](https://github.com/o-stepper)! - W-072: every export map's `import` condition becomes `default`, and the Node floor rises to `>=22.12.0`.

  CJS consumers previously hit a bewildering `ERR_PACKAGE_PATH_NOT_EXPORTED` instead of a clear ESM-only signal. With the `default` condition, plain `require('@graphorin/core')` works via Node's stable `require(esm)` - which shipped in 22.12, hence the engines bump across every workspace manifest (packages, examples, benchmarks, docs; enforced by the widened mvp-readiness sweep). No dual-instance hazard: there is no CJS build, `require()` returns the same ESM module instance. ESM consumers are unaffected (`default` serves both paths; `types` stays first). The pack gate now runs attw under the full `node16` profile (was `esm-only`) and adds a runtime `require(esm)` smoke against the packed tarballs. Installs on Node 22.0-22.11 with `engine-strict` will refuse - upgrade Node (see the migration guide).

- [#153](https://github.com/o-stepper/graphorin/pull/153) [`832f22e`](https://github.com/o-stepper/graphorin/commit/832f22e570b8c3175c1adeb4c150070cbd131534) Thanks [@o-stepper](https://github.com/o-stepper)! - Reachable retention lever for `memory_history` (W-066):

  - `@graphorin/core`: new `MemoryStoreExt` contract (`extends MemoryStore` with `pruneHistory(olderThanMs)`), mirroring the `SessionStoreExt` precedent - strictly additive, custom `MemoryStore` implementations keep compiling. The TSDoc pins the unit semantics: the argument is an AGE in milliseconds, never an epoch cutoff.
  - `@graphorin/store-sqlite`: `SqliteMemoryStore` declares `implements MemoryStoreExt` and `GraphorinSqliteStore.memory` is now typed `MemoryStoreExt`, so `pruneHistory` is reachable without casts.
  - `@graphorin/cli`: new `graphorin memory prune-history --older-than <duration|date>` command. `--older-than` is mandatory (destructive by design, no default), takes a duration (`30d`, `12h`) or a PAST ISO date (converted to `now - date`; future dates are refused - they would prune the whole table). Documented in the CLI guide and the memory-system guide: history grows by design, `purge()` already scrubs sensitive text, pruning is storage-cost hygiene.

- [#154](https://github.com/o-stepper/graphorin/pull/154) [`fe98522`](https://github.com/o-stepper/graphorin/commit/fe98522ce2477c9a7dc09029f9dcfdb1f7c9aa04) Thanks [@o-stepper](https://github.com/o-stepper)! - W-047: `RunContext.state` is now typed as the new `ReadonlyRunState` projection.

  Tools and hooks observe the run; they do not mutate its bookkeeping - assignments to `status`/`finishedAt` and `push`/`splice` on `steps`/`messages`/`pendingApprovals` through `ctx.state` are now compile errors. `ReadonlyRunState` is a hand-written structural mirror of `RunState` (keyof parity pinned by type tests); `RunState` remains assignable to it, so runtime call sites needed no changes. This is a compile-time contract only (no runtime freeze). BREAKING at the type level for tools that wrote to `ctx.runContext.state` - that was never supported. Companion cleanup in `@graphorin/agent`: `finishRunBase`/`finalize` take `MutableRunState & RunState`, removing the last `as unknown as RunState` cast in the runtime.

- [#154](https://github.com/o-stepper/graphorin/pull/154) [`fe98522`](https://github.com/o-stepper/graphorin/commit/fe98522ce2477c9a7dc09029f9dcfdb1f7c9aa04) Thanks [@o-stepper](https://github.com/o-stepper)! - W-024: thinking-block signatures now actually round-trip - the whole retention pipeline was dead because nothing captured them.

  `ProviderEvent` gains a `{type: 'reasoning-end', meta?: ReasoningContentMeta}` terminator (per-block, matching both AI SDK generations). The vercel adapter maps v4 `reasoning-signature`/`redacted-reasoning` chunks and v7 `reasoning-end` (`providerMetadata.anthropic.signature`/`.redactedData`) onto it; `reasoning-start` stays a no-op. The agent runtime flushes buffered deltas into per-block `ReasoningContent` parts carrying the meta (redacted blocks become meta-only parts), and the step assembles those parts instead of one meta-less collapse - adapters without block structure keep the collapsed fallback. Downstream, the already-shipped chain finally engages: `applyReasoningPolicy('pass-through-claude')` retains the signed parts and `toAssistantPart` emits `providerOptions.anthropic.signature`, so multi-step tool use with Anthropic extended thinking replays each block byte-equal (pinned end-to-end: the step-2 request carries both signatures of a two-block step-1). Known scope limit: the one-shot `generate()` path still returns no reasoning (`ProviderResponse` has no field for it). MIGRATION: external exhaustive switches over `ProviderEvent` need a case for `'reasoning-end'`; transcripts may now carry several reasoning parts per step instead of one.

- [#154](https://github.com/o-stepper/graphorin/pull/154) [`fe98522`](https://github.com/o-stepper/graphorin/commit/fe98522ce2477c9a7dc09029f9dcfdb1f7c9aa04) Thanks [@o-stepper](https://github.com/o-stepper)! - W-126: `SpanType` opens up via a namespaced escape: `SpanType = KnownSpanType | CustomSpanType` where `CustomSpanType` is any `` `x.${string}` `` (convention `x.<vendor>.<operation>`). Custom tracers can start spans for operations the framework has no literal for (rerankers, eval steps, ...) without a core release, while typos of known literals stay compile errors (they do not start with `x.`). MIGRATION: external exhaustive switches over `SpanType` need a default branch for the custom domain; span-type analytics must tolerate unknown strings (the policy events already follow). The previous closed union remains exported as `KnownSpanType`.

- [#158](https://github.com/o-stepper/graphorin/pull/158) [`32bbd03`](https://github.com/o-stepper/graphorin/commit/32bbd03b588136a355e4b5ad6ac5e19b36b4d8ab) Thanks [@o-stepper](https://github.com/o-stepper)! - Durable HITL composes across the sub-agent boundary (W-001). A handoff target or `toTool` child that suspends on an approval-gated tool now PARKS on the parent (`RunState.pendingSubRuns`, new core type `PendingSubRun`) instead of surfacing a terminal `execution_failed`/thrown error: the child's pending approvals mirror onto the parent's `pendingApprovals` with the new `ToolApproval.subRunToolCallId` routing field, and the parent suspends once per step. Operators echo (`toolCallId`, `subRunToolCallId`) back in `ApprovalDecision` - decisions match on the composite key, so colliding child-local ids never cross-apply; nested parks route recursively via a `/`-separated path. On grant the child's side effect executes exactly once, its shaped output becomes the parent's tool message, and its usage folds into the parent. `toTool` tools are executed INLINE by the tool-call walk (marked with the well-known `SUBAGENT_TOOL` symbol) rather than through the executor; foreign harnesses mounting them outside the graphorin loop keep the plain throw. `serializeRunState`/`deserializeRunState` recurse into parked children (each nested snapshot is version-stamped and secret-redacted; the core wire codec projects them recursively). Consumers relying on the old terminal-error behavior for suspending children will observe a suspension instead; failed/aborted children still surface as tool errors.

- [#158](https://github.com/o-stepper/graphorin/pull/158) [`32bbd03`](https://github.com/o-stepper/graphorin/commit/32bbd03b588136a355e4b5ad6ac5e19b36b4d8ab) Thanks [@o-stepper](https://github.com/o-stepper)! - Durable timers now fire without user polling code (W-032). The engine stamps `CheckpointMetadata.wakeAt` (earliest due frontier timer) on suspended checkpoints; `CheckpointStore` gains the optional `listSuspended(namespace, { dueBefore, limit })` enumeration (implemented by the SQLite adapter - migration 032 adds `workflow_checkpoints.wake_at` with a partial index - and by `InMemoryCheckpointStore`); the new `createTimerDriver({ workflows: [{ workflow, checkpointStore }] })` polls due threads and calls `workflow.tick`, re-arming at `min(pollIntervalMs, earliest nextWakeAt)`, with per-thread error isolation and benign handling of cross-process `checkpoint-version-conflict` races. On the server, `createServer({ workflowTimers: { driver } })` binds a lifecycle daemon and reports `checks.workflowTimers` on `/v1/health`. A custom store without `listSuspended` fails fast with `TimerDriverStoreUnsupportedError`. Threads suspended before migration 032 carry no `wake_at` and stay invisible to the driver until one manual `tick` or resume re-persists them.

- [#158](https://github.com/o-stepper/graphorin/pull/158) [`32bbd03`](https://github.com/o-stepper/graphorin/commit/32bbd03b588136a355e4b5ad6ac5e19b36b4d8ab) Thanks [@o-stepper](https://github.com/o-stepper)! - Sub-agent transparency (W-036). New additive `AgentEvent` member `subagent.event` wraps a child's event (with the parent-side toolCallId and the child agent name) and forwards it into the parent stream per the `forwardEvents` policy on handoff entries and `AgentToToolOptions`: `'lifecycle'` (default) forwards tool execution/approval, guardrail, lateral-leak, compaction and error events - never text deltas; `'all'` forwards everything; `'none'` keeps the child a black box. Multi-agent runs now form ONE trace tree: `AgentCallOptions.parentSpan` (not persisted in RunState) parents the run span, and the runtime supplies it automatically for handoffs and `toTool` children from the live step span. The wire codec projects the wrapped event recursively. TypeScript consumers with exhaustive switches over `AgentEvent` must add the new case.

- [#160](https://github.com/o-stepper/graphorin/pull/160) [`4ee256e`](https://github.com/o-stepper/graphorin/commit/4ee256e30fe9190cef6c48dc6785464757707156) Thanks [@o-stepper](https://github.com/o-stepper)! - W-049: `tool.execute.start` / `tool.execute.end` / `tool.execute.error` events now carry an optional `toolName` (the agent runtime always fills it, on every emit path: batch dispatch, handoff, inline sub-agent, approval pre-screen and resumed dispatch). Direct stream subscribers can render the tool name from any lifecycle event without a stateful join back to `tool.call.start`. The union TSDoc now states the correlation policy explicitly: cross-run attribution belongs to the server envelope (`subject`), in-lifecycle correlation is by `toolCallId`, and `runId` is deliberately NOT retrofitted onto every variant. Additive and wire-compatible (the wire projection spreads unknown fields through); old consumers keep working.

- [#160](https://github.com/o-stepper/graphorin/pull/160) [`4ee256e`](https://github.com/o-stepper/graphorin/commit/4ee256e30fe9190cef6c48dc6785464757707156) Thanks [@o-stepper](https://github.com/o-stepper)! - W-094: span EVENT attributes can finally carry a sensitivity tier. `AISpan.addEvent` gains an optional third parameter (`{ sensitivity, sensitivityByAttribute }`, additive - existing implementations keep compiling), the tracer records it onto `SpanRecordEvent.sensitivityByAttribute`, and the validation exporter honours it instead of passing an empty map (which dropped EVERY event attribute under the default `'public'` floor). Out of the box, `recordException` now exports a `exception` event with a non-empty `exception.type` (the class name - safe and load-bearing for error dashboards) while `exception.message`/`exception.stacktrace` stay `'internal'`; `emitGenAIMessageEvents` marks role / `gen_ai.system` / message name / tool-call id `'public'` and keeps content `'internal'`. Untagged event attributes keep the default-deny behaviour, and `onViolation` now distinguishes event drops from span-attribute drops via `origin: 'event:<name>'`.

- [#158](https://github.com/o-stepper/graphorin/pull/158) [`32bbd03`](https://github.com/o-stepper/graphorin/commit/32bbd03b588136a355e4b5ad6ac5e19b36b4d8ab) Thanks [@o-stepper](https://github.com/o-stepper)! - The ToolReturn envelope gets a symbol brand (W-115). New core exports: `TOOL_RETURN_BRAND` (`Symbol.for`, duplicate-copy safe), the `toolReturn()` factory, and the ONE shared guard `isToolReturnEnvelope` consumed by both the executor's unwrap and the registry's example-normalizer (the duplicated sniff is gone). The structural fallback for unbranded objects is deliberately narrow - own keys within `{output, contentParts, taint}` - so a tool legitimately returning `{output, exitCode, stderr}` now reaches the model whole instead of being silently stripped to `.output`; canonical unbranded literals keep unwrapping and increment `tool.result.envelope.unbranded-toolreturn.total` toward the sniff's future deprecation. First-party producers (MCP adaptCallResult, memory recall tools, toTool taint envelopes) now brand via `toolReturn()`. Downstream consumers relying on extra fields being dropped will now see them; plain data of exactly `{output: X}` remains ambiguous by contract - brand it or rename the field.

- [#158](https://github.com/o-stepper/graphorin/pull/158) [`32bbd03`](https://github.com/o-stepper/graphorin/commit/32bbd03b588136a355e4b5ad6ac5e19b36b4d8ab) Thanks [@o-stepper](https://github.com/o-stepper)! - Positional pause replay now detects divergence (W-120). Every satisfied resume value is journaled together with the identity of the pause it answered (`PendingPauseRecord.satisfiedMeta`: durable-primitive kind + awakeable/approval name), and `pause()` verifies the identity at each cursor during replay - a node whose pause ORDER depends on time/state/model output fails loudly with the new typed `pause-replay-divergence` WorkflowError (naming the node and the expected vs actual pause) instead of silently delivering a value to the wrong wait. Conservative by design: two plain `pause()` calls carry no identity and are never flagged (false positives impossible), and checkpoints written before the field existed replay their old values unchecked. Consumers with exhaustive switches over `WorkflowErrorCode` must add the new member; workflows that previously "worked by accident" with crossed values will now fail - that is the finding.

- [#164](https://github.com/o-stepper/graphorin/pull/164) [`764239b`](https://github.com/o-stepper/graphorin/commit/764239b97e0e0202442e91272583f13adeb12d00) Thanks [@o-stepper](https://github.com/o-stepper)! - Store mutation paths gain scope-guarded variants, symmetric with the read-side isolation (W-154): every read binds `scope_user_id`, but `forget`, `setStatus` (facts/episodes/rules/insights), `archive`, `archiveFact`, `purge`, and `markAccessed` operated on the bare id - code holding a leaked or cross-user id could quarantine, archive, or hard-purge another user's memory. All mutators now accept an optional trailing `scope?: SessionScope` (additive; existing adapter implementations stay structurally compatible): when supplied, a non-owned row is a deterministic silent no-op - a scoped `purge` of a foreign id writes nothing at all, not even the PURGE audit row. The `@graphorin/memory` tiers pass their scope through on `validate`/`forget`/`purge`/`archive` and the recall `markAccessed` path; the consolidator and erasure cascades deliberately keep calling unscoped.

- [#154](https://github.com/o-stepper/graphorin/pull/154) [`fe98522`](https://github.com/o-stepper/graphorin/commit/fe98522ce2477c9a7dc09029f9dcfdb1f7c9aa04) Thanks [@o-stepper](https://github.com/o-stepper)! - W-046: JSON-safe `WireAgentEvent` projection for all binary-bearing event variants, applied on the server WS path.

  `@graphorin/core` gains `WireFileGeneratedEvent`, `WireToolExecutePartialEvent`, `WireAgentEndEvent` (whose `result.state` is the `WireRunState` projection) plus the `WireAgentEvent` union and pure `toWireAgentEvent`/`fromWireAgentEvent` codecs. The `AgentEvent` TSDoc now documents the real two-layer wire contract (envelope `{eventId, subject, type, payload}` with `payload = WireAgentEvent`) instead of the false claim that `@graphorin/protocol` re-exports the union; protocol stays zod-only with a doc pointer. The server's `backgroundStreamAgent` projects every streamed event before emitting, so `file.generated`, binary `tool.execute.partial` chunks and a multimodal `agent.end` state arrive at WS clients decodable instead of as numeric-key mush. An exhaustive `Record<AgentEvent['type'], ...>` fixture gate in core forces a wire decision for every future event variant.

- [#154](https://github.com/o-stepper/graphorin/pull/154) [`fe98522`](https://github.com/o-stepper/graphorin/commit/fe98522ce2477c9a7dc09029f9dcfdb1f7c9aa04) Thanks [@o-stepper](https://github.com/o-stepper)! - W-004: JSON-safe binary codec for message content and a `WireRunState` projection; run-state schema 1.2.

  `@graphorin/core` gains `EncodedBytes`/`EncodedUrl` envelopes, `WireMessage`/`WireMessageContent`/`WireRunState` wire twins and pure `toJsonSafeMessage`/`fromJsonSafeMessage`, `toJsonSafeContentParts`/`fromJsonSafeContentParts`, `toJsonSafeRunState`/`fromJsonSafeRunState` codecs (plus `bytesToBase64`/`base64ToBytes`, Buffer-free). `serializeRunState` in `@graphorin/agent` now projects binary payloads (`Uint8Array | URL` in `messages` and tool-outcome `contentParts`) through the codec before its detach stringify, so a run with an image checkpointed at `awaiting_approval` and resumed no longer hands the provider corrupted bytes. `RUN_STATE_SCHEMA_VERSION` is now `graphorin-run-state/1.2`; 1.0/1.1 payloads remain readable and their stringify-corrupted numeric-key byte objects are repaired best-effort on rehydration. `SerializedRunState.messages`/`.steps` are now typed as `WireMessage[]`/`WireRunStep[]` - the on-disk truth.

- [#154](https://github.com/o-stepper/graphorin/pull/154) [`fe98522`](https://github.com/o-stepper/graphorin/commit/fe98522ce2477c9a7dc09029f9dcfdb1f7c9aa04) Thanks [@o-stepper](https://github.com/o-stepper)! - W-013: the declared `zod ^3.23 || ^4` peer range now actually typechecks under zod@4.

  Two classes of breakage fixed: (a) `ZodLikeError.issues[].path` is now `ReadonlyArray<PropertyKey>` - zod 4 bases `$ZodIssue.path` on `PropertyKey`, and the shim must be a superset of both peer majors or the canonical `tool({ inputSchema: z.object({...}) })` failed to compile for every zod@4 consumer even with `skipLibCheck` (type-level breaking for downstream code assigning path elements to `string | number`; `validateOrThrow` maps elements through `String` first since `join` throws on symbols); (b) the published d.ts of `@graphorin/memory` (fact/block/recall/runbook tools) and `@graphorin/tools` (read_result, tool_search, code-mode meta-tools) baked concrete v3 `z.ZodObject<...>` generics via `z.infer<typeof schema>` aliases - replaced with explicit exported interfaces whose schema parity is pinned by in-source compile-time gates. The pack gate gains a `dts-no-concrete-zod-generics` leg and CI no longer allow-fails the zod4 leg - both zod majors are enforced at `skipLibCheck: false` from here on.

### Patch Changes

- [#154](https://github.com/o-stepper/graphorin/pull/154) [`fe98522`](https://github.com/o-stepper/graphorin/commit/fe98522ce2477c9a7dc09029f9dcfdb1f7c9aa04) Thanks [@o-stepper](https://github.com/o-stepper)! - W-029/W-060: schema-driven session-content erasure via the exported `SESSION_SCOPED_PURGES` registry + a completeness gate test.

  `deleteSession`/`pruneSessions` previously purged only `session_messages` and `episodes`; consolidator-distilled facts (with their FTS/vec rows and entity links), insights, rules, working blocks, spans, consolidator state/runs and `memory_history` values all survived a hard-delete and stayed findable through semantic search. The purge is now a loop over a declarative registry (`SESSION_SCOPED_PURGES`, each entry naming the session column, FTS shadow, vec0 sidecar family, FK-referencing tables and memory-history scrub policy), with `SESSION_TABLE_EXEMPTIONS` documenting the tables the cascade handles directly. A schema-introspection gate test diffs the registry against every live table carrying `scope_session_id`/`session_id` - adding a new session-scoped table without an erasure decision fails the suite. Only rows scoped to the deleted session are removed; user-level rows (`scope_session_id IS NULL`) are untouched. BEHAVIOR CHANGE: session-scoped facts and insights no longer survive session deletion.

- [#160](https://github.com/o-stepper/graphorin/pull/160) [`4ee256e`](https://github.com/o-stepper/graphorin/commit/4ee256e30fe9190cef6c48dc6785464757707156) Thanks [@o-stepper](https://github.com/o-stepper)! - W-045: the `Cost.amount` units contract is now consistent across the ecosystem - and it is WHOLE currency units (for USD: dollars, fractional values expected), never "smallest fractional unit" / cents as the core TSDoc previously claimed. The canonical producer `calculateCost` (@graphorin/pricing), `CostTracker` snapshots (@graphorin/observability) and the memory consolidator's `costUsd` all already operated in dollars; a consumer that followed the old doc and divided by 100 was off by 100x. Docs-only for the code paths, with a numeric pin test (1M input tokens at $5/Mtok = exactly `5`) freezing the convention. If you implemented a minor-units conversion against the old wording, remove it.

- [#160](https://github.com/o-stepper/graphorin/pull/160) [`4ee256e`](https://github.com/o-stepper/graphorin/commit/4ee256e30fe9190cef6c48dc6785464757707156) Thanks [@o-stepper](https://github.com/o-stepper)! - W-048: the MemoryStore baseline-vs-full-adapter story is now explicit and gated. `GraphMemoryStoreExt` and `ProceduralMemoryStoreExt` are exported from the root of `@graphorin/memory` (they were package-internal); a type test pins `MemoryStore extends MemoryStoreAdapter`, so any future REQUIRED member on an `*MemoryStoreExt` breaks CI instead of silently breaking third-party core-only adapters; and the TSDoc on core `MemoryStore`, `Insight` and `GraphEntity` (plus the persistence guide) now states where the full-parity surfaces live and that graceful degradation is the contract.

- [#164](https://github.com/o-stepper/graphorin/pull/164) [`764239b`](https://github.com/o-stepper/graphorin/commit/764239b97e0e0202442e91272583f13adeb12d00) Thanks [@o-stepper](https://github.com/o-stepper)! - Core API stability is now enforced, not just claimed (W-127): the public surface is committed as an api-extractor report (`packages/core/etc/core.api.md`) and the new `check-api-report` CI gate fails any PR that changes the surface without updating it. Point fixes ride along: `ProviderResponse.toolCalls` reuses the canonical `ToolCall` type (the inline shape was structurally identical - not a breaking change), `Tool.defer_loading` documents its deliberate snake_case (one-to-one with the Anthropic wire flag), `SessionMemoryStore.search` documents that the positional `query` is authoritative over `opts.query` (pinned by a store-sqlite test), and the README's stale "loose @experimental corners" line now states the truth: the whole core surface ships `@stable`; `@experimental` lives in mcp/tools/security/skills.

- [#164](https://github.com/o-stepper/graphorin/pull/164) [`764239b`](https://github.com/o-stepper/graphorin/commit/764239b97e0e0202442e91272583f13adeb12d00) Thanks [@o-stepper](https://github.com/o-stepper)! - TSDoc `{@link}` hygiene sweep (W-130): all 55 broken links found by TypeDoc's now-enabled `validation.invalidLink` are fixed - two resolved to their real targets (`GraphorinMCPError` was misnamed `MCPError`), the rest (cross-package, `import()`-form, unexported-constant, and DOM-type references that have never rendered as hrefs) converted to plain inline code. The docs build now fails on any new broken `{@link}` via a scoped gate.

- [#164](https://github.com/o-stepper/graphorin/pull/164) [`764239b`](https://github.com/o-stepper/graphorin/commit/764239b97e0e0202442e91272583f13adeb12d00) Thanks [@o-stepper](https://github.com/o-stepper)! - Tarballs now ship `src/` so the published `dist/**/*.d.ts.map` files actually work (W-136): the maps referenced `../src/*.ts` that the `files` whitelist excluded, so go-to-definition fell back into `.d.ts` and the shipped maps were dead weight. The pack gate gains a `map-integrity` leg: every source referenced by a shipped map must resolve inside the tarball (or be embedded via `sourcesContent`), with an anti-vacuous guard - a package whose tsdown config emits declaration maps must contain a non-zero number of `.d.ts.map` files, so a cache-restored dist that silently dropped maps fails the gate instead of passing vacuously. `mvp-readiness` now requires `src` in every publishable `files` array.

- [#164](https://github.com/o-stepper/graphorin/pull/164) [`764239b`](https://github.com/o-stepper/graphorin/commit/764239b97e0e0202442e91272583f13adeb12d00) Thanks [@o-stepper](https://github.com/o-stepper)! - Every published package now declares its tree-shaking contract via `sideEffects` (W-137): 18 packages audited to a pure module scope get `false`, the CLI declares its bin entry (`["./dist/bin/*"]`), and `@graphorin/security` gets an explicit `true` - its secrets subsystem registers built-in resolvers and the SecretValue caller-context provider at import time, so marking it pure would let bundlers drop those registrations. `mvp-readiness` now fails any publishable manifest without a declared `sideEffects`, closing the drift for future packages.

## 0.6.1

### Patch Changes

- [#142](https://github.com/o-stepper/graphorin/pull/142) [`436d6ca`](https://github.com/o-stepper/graphorin/commit/436d6ca5ebbd16df094e915682d3915c279a8430) Thanks [@o-stepper](https://github.com/o-stepper)! - Version constants and version-bearing strings now derive from each package's manifest at build time (`VERSION = pkg.version`; writer ids, client/server info, OTLP framework attributes, build-info metrics interpolate it). No behavioral change at the current version: the rendered strings are byte-identical. A release bump no longer edits source; the new `check-version-consistency` gate fails any reintroduced hardcoded framework version.

## 0.6.0

### Minor Changes

- [#136](https://github.com/o-stepper/graphorin/pull/136) [`17a2d30`](https://github.com/o-stepper/graphorin/commit/17a2d30564154ca2ab87473335cdef43a5089c84) Thanks [@o-stepper](https://github.com/o-stepper)! - Prompt-cache economics end-to-end (audit 2026-07-04 Wave C, cluster C1).

  - `Usage` gains `cachedReadTokens` / `cacheWriteTokens` (subsets of `promptTokens`), mapped by the vercel adapter (v7 `inputTokenDetails`; reasoning split kept exclusive of `completionTokens`) and the OpenAI-compatible adapter (`prompt_tokens_details.cached_tokens`); the fields flow through step/run aggregation, `usageByModel`, run-state (de)serialization and `withCostTracking` (new `cachedReadPerMtok`/`cacheWritePerMtok` lookup rates, full-input-rate fallback).
  - New opt-in `ProviderRequest.cachePolicy` / `AgentConfig.cachePolicy` (`{ breakpoints: 'auto', ttl? }`): the vercel adapter anchors Anthropic `cache_control` on the first and last conversation messages so the stable prefix is written once and read at the discounted rate every later step.
  - `ModelPrice` gains `cacheWriteUsdPerToken`; `calculateCost` bills cache writes; the bundled pricing snapshot is regenerated (2026-07-04) with current Anthropic 4.x / OpenAI gpt-5 + gpt-4.1 + o3/o4-mini / Gemini 2.5 families (legacy ids retained), `lookupPrice` resolves dated ids via a date-suffix fallback, and a new snapshot-coverage release gate cross-checks the model-tier classifier against the snapshot with an explicit known-unpriced allowlist (post-cutoff models report null cost + WARN instead of invented numbers).
  - Cache-friendly catalogue: handoff tools serialize BEFORE the growing promoted section (byte-stable prefix under `tool_search` promotions), and new `toolPromotion: 'run-boundary'` freezes the advertised catalogue for a whole run while still persisting discoveries.

- [#136](https://github.com/o-stepper/graphorin/pull/136) [`17a2d30`](https://github.com/o-stepper/graphorin/commit/17a2d30564154ca2ab87473335cdef43a5089c84) Thanks [@o-stepper](https://github.com/o-stepper)! - Agent harness quality (audit 2026-07-04 Wave C, cluster C3).

  - Recoverable error envelope: `ToolError` gains `recoverable` + `recoveryHint` (retry_later / check_input / try_alternative / report_to_user), stamped from the kind at the executor's completion funnel and rendered to the model as a bracketed recovery line under the familiar `Error: <message>`.
  - Transparent bounded tool retry: `rate_limited` outcomes from pure/read-only tools (or tools with an `idempotencyKey`) silently re-execute with exponential backoff up to 3 total attempts (`ToolRateLimitError.retryAfterMs` wins); tune via executor `retry` / `AgentConfig.toolRetry`.
  - Verifier seam: `AgentConfig.verifiers` run deterministic checks on every terminal response, emit `verifier.result` events, feed failures back as a user message and continue up to `maxVerifierRounds` (default 1); throwing verifiers count as passed. Deliberately no evidence-free self-reflection step.
  - ACI: empty successful tool output renders as an explicit `(tool ran successfully with no output)` marker.
  - Deterministic replay: opt-in `recordProviderResponses` journals each step's raw model response onto `RunState.steps[].providerResponse`; new `createReplayProvider(state)` re-drives a run offline and fails loudly on divergence.

- [#136](https://github.com/o-stepper/graphorin/pull/136) [`17a2d30`](https://github.com/o-stepper/graphorin/commit/17a2d30564154ca2ab87473335cdef43a5089c84) Thanks [@o-stepper](https://github.com/o-stepper)! - Deterministic security adoptions (audit 2026-07-04 Wave C, cluster C6).

  - Derived-taint propagation: opt-in `dataFlowPolicy.derivedTaint: 'strict'` fires the paraphrase-robust `derived-untrusted-to-sink` flow for every model-driven sink call once untrusted content entered the run (CaMeL-style control-flow integrity); the agent also records each tainted step's assistant text as `llm-derived` spans so model-echoed phrasing trips the verbatim probe.
  - Taint into memory (cross-session MINJA leg): `ToolReturn` gains a widen-only `taint` override honoured through the executor record path; `fact_search` / `deep_recall` / `recall_episodes` attach it when any returned item is quarantined or foreign-provenance, re-arming the ledger at recall. `RunState.taintSummary` additionally carries one-way FNV-1a span-tile hashes (no plaintext), so a resumed run re-detects pre-suspend verbatim copies.
  - MCP pinning completed: `toTools({ pinStore })` records fingerprints on first use and REJECTS drift by default when a store is present (rug-pull defense; `onPinMismatch: 'warn'` downgrades); tool-description injection hits at registration are counted (`mcp.tool-description.injection-flagged.total`).
  - Signal-only heuristics + Unicode pre-pass: shared `normalizeForMatching` (NFKC + zero-width strip) applied in the guardrails injection catalogue and the memory quarantine heuristics; security.md repositions all pattern catalogues as best-effort signal, never a sole gate. `TaintLabel.sourceKind` widened to `string` for the new descriptive kinds.

- [#136](https://github.com/o-stepper/graphorin/pull/136) [`17a2d30`](https://github.com/o-stepper/graphorin/commit/17a2d30564154ca2ab87473335cdef43a5089c84) Thanks [@o-stepper](https://github.com/o-stepper)! - Trace-tree observability (audit 2026-07-04 Wave C, cluster C7; pairs periphery-04).

  The agent loop now emits the previously-declared `agent.run` span per run and `agent.step` spans per step (parented under the run); `tool.execute` parents under the current step via the new optional `RunContext.span`; a `withTracing`-wrapped provider parents under the step via the new `ProviderRequest.parentSpan` (a live handle like `signal`). Attributes align to the OTel GenAI semantic conventions (`gen_ai.operation.name`, `gen_ai.agent.id/name`, `gen_ai.tool.name`, `gen_ai.request.model`, `gen_ai.usage.input_tokens/output_tokens`), parent-based sampling finally has parents to follow, and observability.md documents the real tree plus the memory-tier-spans-not-yet-parented limitation.

- [#137](https://github.com/o-stepper/graphorin/pull/137) [`ce06b47`](https://github.com/o-stepper/graphorin/commit/ce06b472af9e30ac5d0792f7a8b6f42170a94627) Thanks [@o-stepper](https://github.com/o-stepper)! - Durable workflow orchestration (audit 2026-07-04 Wave D, cluster D1) - promotes the workflow engine to step-checkpoint durable execution and closes the confirmed `workflow-01..14` correctness floor.

  - Feature floor: atomic checkpoint compare-and-set (`CheckpointStore.put({ expectedLatestId })` + `CheckpointConflictError`, honoured by both bundled stores), planned-order channel writes, merge-failure + boundary-abort + max-steps terminal checkpointing, all-false-`__start__` dead-end, ephemeral-value observability on `workflow.channel.update`, satisfied-pause retention on sibling failure, `maxConcurrentTasks` bounded task pool, `Dispatch` cross-realm brand, and hygiene (dead `visitedNodes`, six->seven stream modes, `'async'` source removed, resume durability override).
  - New durable capabilities: per-node `timeoutMs` + `retry` (`nodeDefaults`), durable timers (`sleepUntil`/`sleepFor` + `workflow.tick`), durable promises (`awaitExternal` + `resolveAwakeable`), persisted approvals (`requestApproval` + `approve`), `WorkflowConfig.version` pinning (`workflow-version-mismatch`) + journal-divergence detection, and opt-in step journaling (`journalSteps`) with crash-recovery that replays completed tasks and re-runs only unfinished work.

- [#137](https://github.com/o-stepper/graphorin/pull/137) [`ce06b47`](https://github.com/o-stepper/graphorin/commit/ce06b472af9e30ac5d0792f7a8b6f42170a94627) Thanks [@o-stepper](https://github.com/o-stepper)! - Sub-agent isolation & orchestration primitives (audit 2026-07-04 Wave D, cluster D2).

  - Run-level `'read-only'` capability (single-writer constraint): `AgentConfig.capability` / `AgentCallOptions.capability` never advertise writer tools or handoffs and the executor deterministically blocks fabricated writer calls with the new `capability_blocked` `ToolErrorKind` (threaded through `executeBatch`/`executeOne`, HITL resume, and the code-mode bridge).
  - `toTool({ contextFold })` returns a compact distilled child-run outcome instead of raw output; `toTool({ propagateTaint })` (default on) carries the child's coarse taint flags across the fold as a widen-only `ToolReturn.taint` override (`sourceKind: 'sub-agent'`) that re-arms the parent's data-flow ledger.

- [#137](https://github.com/o-stepper/graphorin/pull/137) [`ce06b47`](https://github.com/o-stepper/graphorin/commit/ce06b472af9e30ac5d0792f7a8b6f42170a94627) Thanks [@o-stepper](https://github.com/o-stepper)! - Memory architecture evolution (audit 2026-07-04 Wave D, cluster D3) - opt-in; defaults byte-identical.

  - Learned-context digest block (Letta sleep-time): a deep-phase pass rewrites the reserved `learned_context` working block from recent evidence via one budgeted LLM call (`consolidator.learnedContext`, off at every tier).
  - Principal/owner dimension (`MemoryOwner` on facts/episodes/rules/insights, migration 026) stamped `'agent'` on synthesized writes, with a retrieval-time `owner` filter (`FactSearchOptions.owner`); default reads apply no filter.
  - Retrieval-frequency reinforcement: `facts.access_count` (migration 027) + opt-in `SalienceWeights.accessReinforcement` (default 0 = inert).
  - Runbook memory: `rules_fts` (migration 028) + `ProceduralMemory.search` returns whole validated procedures, with a gated `runbook_search` tool (`createMemory({ runbookSearch: true })`).

- [#137](https://github.com/o-stepper/graphorin/pull/137) [`ce06b47`](https://github.com/o-stepper/graphorin/commit/ce06b472af9e30ac5d0792f7a8b6f42170a94627) Thanks [@o-stepper](https://github.com/o-stepper)! - Security hardening (audit 2026-07-04 Wave D, cluster D4) - architectural, deterministic layers.

  - RFC-6962 Merkle transparency over the audit log (`@graphorin/security/audit`): tree head, inclusion + consistency proofs, and Ed25519-signed checkpoints, so anchoring a signed head out-of-band makes the trail tamper-resistant (a rewrite of a checkpointed prefix fails the consistency proof).
  - Operator trust root for skills: `verifySkillSignature`/`installSkillFrom{Npm,Git}` gain `trustRoot` - a valid signature from a key absent from the root returns `valid: false` reason `'untrusted-key'`.
  - Progent-style tool-argument policies + Rule-of-Two capability profiles (`@graphorin/security/policy`): `AgentConfig.toolPolicy` (forbid-before-allow, default-deny sensitive) + `ruleOfTwo` (drops a lethal-trifecta leg, forcing a read-only capability floor) enforced by a new `ExecutorOptions.argumentPolicy` hook (`capability_blocked`).
  - Code-mode sandbox blocklist parity (`bridged-source.ts`) blocks the process-escape modules (child_process/vm/worker_threads/cluster/inspector) via the ESM resolve hook + a CJS `Module._load` patch.

- [#137](https://github.com/o-stepper/graphorin/pull/137) [`ce06b47`](https://github.com/o-stepper/graphorin/commit/ce06b472af9e30ac5d0792f7a8b6f42170a94627) Thanks [@o-stepper](https://github.com/o-stepper)! - Retrieval SOTA graph wins (audit 2026-07-04 Wave D, cluster D5) - offline, opt-in; the billed/migration-heavy legs stay eval-gated.

  - PPR-lite graded expansion (HippoRAG-style damped spreading activation): `SqliteGraphStore.expandActivation` + `FactSearchOptions.expandHops: 0|1|2` + `graphScoring: 'ppr'` score neighbours by `damping^hopDepth` instead of a flat 1.
  - Graph + entity as first-class tunable fusion weights (`FusionWeights.graph` / `.entity`, were hardcoded neutral).
  - Exact entity-match retriever: `SqliteGraphStore.factsForEntityName` + `FactSearchOptions.entityMatch` add a precise "facts about <entity>" candidate leg.
  - `longmemeval` harness gains `--retrieval ppr` / `--retrieval entity`. Bitemporal event time, Matryoshka truncation, and cascade LLM reranking remain eval-gated.

- [#137](https://github.com/o-stepper/graphorin/pull/137) [`ce06b47`](https://github.com/o-stepper/graphorin/commit/ce06b472af9e30ac5d0792f7a8b6f42170a94627) Thanks [@o-stepper](https://github.com/o-stepper)! - Context engineering trial (audit 2026-07-04 Wave D, cluster D6) - opt-in; tool surface unchanged by default.

  - Structured plan tool (`update_plan`, TodoWrite-style full-replace checklist) journaled in the new `RunState.todos` (core `TodoItem`), surviving suspend/resume via the strict run-state (de)serializer.
  - Attention recitation: the current plan is rendered into a compact `<plan>` block appended to each step's request copy - request-only and cache-layout-aware (rides the last prompt-cache anchor, never touches the shared buffer or persisted state).
  - `AgentConfig.plan` opt-in. Progress files, resume-recap injection, and the degraded-mode ladder are documented as trial follow-ups.

### Patch Changes

- [#134](https://github.com/o-stepper/graphorin/pull/134) [`32f20c1`](https://github.com/o-stepper/graphorin/commit/32f20c110f184f8cef7eec85bf39f5f07c886cb6) Thanks [@o-stepper](https://github.com/o-stepper)! - Session hard-delete and the RP-6 retention sweep now purge the session's CONTENT (store-01): `session_messages` rows plus their FTS and per-embedder vector index entries, and episodes scoped to the session, all inside the existing cascade transaction. Previously no code path anywhere deleted `session_messages`, so a "deleted" conversation stayed permanently searchable via `memory.session.list/search` - a GDPR hazard for a privacy-positioned framework. The core `SessionStoreExt.deleteSession` contract is updated to document the content cascade.

- [#135](https://github.com/o-stepper/graphorin/pull/135) [`4f850d9`](https://github.com/o-stepper/graphorin/commit/4f850d9bc0a05d6256c59c5117b010336fcb41d3) Thanks [@o-stepper](https://github.com/o-stepper)! - Provider adapters now match their real SDK / wire contracts (audit 2026-07-04 Wave B, cluster B1).

  - vercel adapter: Graphorin tools convert to the AI SDK's name-keyed record with `jsonSchema()`-shaped input schemas; assistant `toolCalls` become `tool-call` content parts, `ToolMessage`s become `tool-result` messages, system-role messages hoist into the `system` option, and `toolChoice` maps onto the SDK spelling. Tool loops now run against the real `ai` peer (previously every tool conversation failed SDK validation). A real-SDK contract test suite (dev-only `ai` dependency) pins the shapes.
  - Anthropic token counter posts Anthropic wire-shaped bodies (system hoist, `tool_use` / `tool_result` blocks, turn merging) instead of raw Graphorin messages that 400'd on any agent transcript; degradation to the tiktoken fallback now WARNs once.
  - HTTP errors carry a canonical `errorKind` (shared `classifyHttpStatus` mapper: 429 rate-limit, 401/403 unauthorized, 5xx transient/capacity, context-length body sniff) plus captured `retry-after` / `x-ratelimit-*` headers; `withFallback` / `withRetry` consult them, so a 429 on the primary finally fails over and honours server-provided delays.
  - llamacpp-node: the system prompt is no longer injected twice, per-request contexts/sequences are disposed after every stream (KV-cache leak), and aborted streams report `finishReason: 'aborted'`.
  - OpenAI-compatible streaming sends `stream_options: { include_usage: true }` so vLLM / Together / OpenAI report real usage; `openAICompatibleAdapter` gained the `capabilities` / `timeoutMs` options its siblings had.
  - `withCostTracking` bills separately-reported reasoning tokens at the output rate; classifiers recognise Bedrock cross-region ids (`us.anthropic.claude-...`) and the AI SDK's dotted provider ids.

- [#135](https://github.com/o-stepper/graphorin/pull/135) [`4f850d9`](https://github.com/o-stepper/graphorin/commit/4f850d9bc0a05d6256c59c5117b010336fcb41d3) Thanks [@o-stepper](https://github.com/o-stepper)! - Retrieval and consolidation now honour the trust contracts they document (audit 2026-07-04 Wave B, cluster B3).

  - memory-retrieval-01: default fact reads (FTS / vector / graph) behave as `asOf = now`, so superseded and validity-expired facts no longer surface as current - exactly what the `fact_supersede` tool promises. New `includeSuperseded: true` escape hatch (core `MemorySearchOptions`, memory `FactSearchOptions`, graph expand options) restores the full history for inspector / audit paths; `fact_search` and `deep_recall` outputs expose `validTo` / `supersededBy`.
  - memory-retrieval-02: `deep_recall` passes `forceHard: true` (choosing the tool IS the hardness signal; the local heuristic gate rejected the tool's own documented examples and is English-only). Iterative results carry a new `graded` flag so ungraded single-shot passes stop claiming sufficiency as a verdict.
  - memory-retrieval-03: a tagged search widens the fusion pool the same way decay does - the record-level tags filter runs after the topK cut, so tagged searches no longer silently return fewer than topK hits.
  - memory-consolidation-01: the deep-phase dedup verdict now soft-forgets (replayable tombstone) instead of preferring the GDPR hard-delete `purge`, and a vanished conflicting fact skips the judge entirely (admit, no provider call) - a model verdict can no longer hard-delete the only surviving copy.
  - memory-consolidation-07: the standard phase gains an embedder-independent exact-text duplicate guard (FTS + string equality, quarantine-aware), so replaying a partially-committed slice (DLQ / cursor retry) without an embedder no longer duplicates already-committed facts.

## 0.5.0

First version published to the npm registry (with Sigstore build
provenance). The 0.2.0, 0.3.0, and 0.4.0 versions were internal lockstep
milestones and were never published. All `@graphorin/*` packages release
lockstep at the same version; the full release notes for 0.2.0-0.5.0 live
in the repository-level
[CHANGELOG](https://github.com/o-stepper/graphorin/blob/main/CHANGELOG.md).

## 0.1.0

### Minor Changes

- Initial release of the dependency-free root package for the Graphorin
  framework. Ships the public type system (`Message`, `AgentEvent`,
  `WorkflowEvent`, `RunContext`, `RunState`, `Usage`, `Sensitivity`,
  `MemoryKind`, `MemoryMetadata`, `Handoff`, `StopCondition`, …), every
  cross-package contract interface (`Provider`, `Tool`, `Logger`,
  `MemoryStore`, `CheckpointStore`, `SessionStore`, `TriggerStore`,
  `AuthTokenStore`, `EmbedderProvider`, `TokenCounter`, `Tracer`,
  `AISpan`, `RedactionValidator`, `SecretsStore`, `SecretValue`,
  `SecretRef`, `SecretResolver`, `Sandbox`, `EvalScorer`, …) and a small
  set of dependency-free utilities (`collect`, `mapStream`, `merge`,
  `withSignal`, `assertNever`, `md5`, `xxhash`, …).
- Workflow channel primitives under `@graphorin/core/channels`:
  `Directive`, `Dispatch`, `pause`, `LatestValue`, `Reducer`, `Stream`,
  `Barrier`, `Ephemeral`, `AnyValue`, `ListAggregate` - Graphorin's own
  vocabulary; names are part of the public API.
- Typed no-op defaults `NOOP_TRACER` and `NOOP_LOGGER` so downstream
  packages can carry a non-null observability surface without taking the
  observability dependency.
- `zod` is declared as a peer dependency (`^3.23 || ^4`); `@graphorin/core`
  has no other runtime dependencies and no internal dependencies on any
  other `@graphorin/*` package.
