# Changelog — klieo-core

All notable changes to this crate are documented here.
Format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
This crate adheres to [Semantic Versioning](https://semver.org/).

## [Unreleased]

## [2.2.0] — 2026-06-18

### Added

- `Episode::RunOrigin { parent_anchor }` + `AgentContext::parent_anchor`
  (`with_parent_anchor` setter; children inherit) + `ToolCtx::parent_anchor`
  (`with_parent_anchor`). Cross-hop provenance (Phase B B5): when an
  authenticated external caller supplies a parent-chain anchor — by convention
  its own provenance chain-entry id — the run loop records a single `RunOrigin`
  episode at run start carrying the anchor **verbatim**, so klieo→klieo
  composition can be stitched across deployments. The anchor is a
  caller-asserted, **unverified** claim (co-recorded with `RunAttributed` so it
  is attributable to the authenticated principal); it is never appended to
  short-term / LLM-visible memory. `Episode` is `#[non_exhaustive]` so the new
  variant is additive (runlog/provenance-client project it as a no-op). Additive.
- `Episode::RunAttributed { tenant_label }` + `AgentContext::tenant_label`
  (`with_tenant_label` setter; children inherit). When set, the run loop records
  a single `RunAttributed` episode at run start carrying a **derived non-PII
  label** (callers stamp `principal_hash(..)`), so an externally-driven run is
  attributable in the audit trail. The label is never appended to short-term /
  LLM-visible memory; the raw caller identity is never recorded (stays in
  server-side tracing). `Episode` is `#[non_exhaustive]` so the new variant is
  additive (runlog/provenance-client project it as a no-op). Additive.
- `ToolCtx::caller_principal: Option<CallerPrincipal>` + `with_caller_principal`
  and `ToolCtx::parent_anchor: Option<ParentAnchor>` + `with_parent_anchor` —
  optional verified-caller tag + cross-hop provenance anchor a transport may
  stamp on the per-call context. Server-side metadata for authz/ticketing/
  attribution only; NOT recorded into short-term/episodic memory or the
  model-visible conversation. Carried in the new `CallerPrincipal` /
  `ParentAnchor` newtypes (both `pub`, `as_str()` accessor) — deliberately
  **not** `Serialize`, so neither can be folded into an `Episode` or other
  persisted structure by accident; the redaction/attribution contract is
  type-enforced, not doc-only. Additive (`ToolCtx` is `#[non_exhaustive]`;
  `ToolCtx::new` defaults both to `None`).
- PII-redacted audit-trail foundation (A3.1). All additive — minor bump:
  - `AuditRedactor` trait — injectable redaction port
    (`fn redact(&self, &serde_json::Value) -> serde_json::Value`). The
    concrete masking redactor lives in `klieo-ops`; `klieo-core` only
    defines the port and consumes it through `AgentContext`.
  - `opaque_digest(&serde_json::Value) -> serde_json::Value` — fail-closed
    total-opacity fallback returning `{"redacted":"sha256","digest":"<hex>"}`,
    a deterministic SHA-256 over the value's canonical JSON. Used when a
    redaction-flagged tool has no `AuditRedactor` wired so raw PII never
    reaches the durable audit trail.
  - `Tool::redacts_audit() -> bool` (default `false`) — tools handling
    claimant PII override to `true`.
  - `ToolInvoker::tool_redacts_audit(&self, name: &str) -> bool`
    (default `false`) — per-name mirror dispatch consults across the
    invoker boundary. Both are defaulted trait methods (no existing impl
    breaks).
  - `AgentContextBuilder::audit_redactor(Arc<dyn AuditRedactor>)` — installs
    the redactor. The backing field is non-public, so the
    `#[non_exhaustive]`-less `AgentContext` stays SemVer-minor (a new
    builder method is additive). When unset, dispatch falls back to
    `opaque_digest` for flagged tools.
  - Dispatch now redacts the recorded `Episode::ToolCall` args/result for
    flagged tools (short-term memory feeding the LLM stays raw).
- `AgentContext::with_audit_redactor(Arc<dyn AuditRedactor>)` consuming
  setter (A3.2) — symmetric with `with_llm`/`with_tools`, for callers
  that mint a context via `AgentContext::new` (e.g. the `klieo` facade)
  rather than the builder. Additive — minor.
- `Error::ModelSunset { model, since }` — returned by `App::build()` when an
  opt-in model registry marks the configured model sunset. Additive
  (`Error` is `#[non_exhaustive]`).
- `runtime::CaptureSink` trait + `RunOptions::capture_sink` field /
  `with_capture_sink` builder (ADR-048, S-06 slice 2a). When set, the run loop
  records each successful LLM call (request + assistant message + finish_reason)
  to the sink so a replayable capture can be built from a live run. `None`
  default — zero cost, no behaviour change. Additive (`RunOptions` is
  `#[non_exhaustive]` + builder).
- `ToolCall` now derives `PartialEq` (additive) so it can be compared in
  recorded-capture assertions and downstream value types.
- `KvStore::keys_paginated` + `KeyPage` + `page_from_sorted` (S-05b follow-up).
  All additive — minor bump. A key-ordered, cursor-based paginated enumeration:
  `keys_paginated(bucket, cursor, limit) -> KeyPage { keys, next }` resumes
  strictly after the prior page's last key, so pages stay stable across
  concurrent writes. The default fetches+sorts all keys per call (a convenience
  fallback that does NOT bound a large backend — those override; NATS override
  deferred). `page_from_sorted` is the shared slice helper for the default and
  overrides. `gc_checkpoints` now page-walks via `keys_paginated`, bounding the
  resident key list as well as the per-blob payload.
- Durable run suspend/resume for HITL gating (ADR-045, S-04b-1). All
  additive — minor bump:
  - `ReviewPolicy` trait + `NeverReview` default. The gate is wired through
    `RunOptions::review_policy` (consulted by `run_steps`); the per-agent
    `Agent::review_policy()` convenience hook is deferred to S-04b-2, where it
    gains a caller that bridges it into `RunOptions`.
  - `RunCheckpoint` + `ApprovalDecision` + `CHECKPOINT_BUCKET` in the new
    crate-root `checkpoint` module (pure data; keeps `error` and `runtime` free
    of a module cycle). `resume_from_checkpoint` (in `runtime`) re-enters the
    loop on an operator decision. Also re-exported from `klieo_core::runtime` and
    the crate root for path stability.
  - `RunOptions::review_policy` + `RunOptions::checkpoint_kv_bucket` (+
    `with_review_policy` / `with_checkpoint_bucket` builders).
  - `Error::Suspended` + `AgentEvent::Suspended` (both `#[non_exhaustive]`).
  - The review gate fires in the blocking `run_steps` path; streaming-path
    gating, `klieo-hitl-client`, and the HITL timeout scheduler are deferred
    to S-04b-2.
  - `ShortTermMemory::append_batch` default method (sequential fallback over
    `append`) — additive; the resume path restores history in one call so
    bulk-insert backends can override. Existing impls compile unchanged.
  - `ReviewPolicy::is_never` default method (S-04b-2a). `run_steps_streaming`
    now rejects a non-default review policy with `Config(InvalidValue)` before
    opening the provider stream — a checkpoint cannot capture a half-consumed
    stream, so gated runs must use `run_steps` (ADR-045). Additive: `NeverReview`
    overrides `is_never` to `true`; existing policies inherit the safe default.

### Fixed

- `opaque_digest` (fail-closed PII-redaction digest) now canonicalizes object
  keys explicitly (recursive sort) before hashing, so the digest is invariant
  to key order even when a downstream enables `serde_json`'s `preserve_order`
  feature. Previously it relied on `serde_json::Value` serializing keys in
  sorted `BTreeMap` order — which `preserve_order` flips to insertion-ordered
  `IndexMap`, yielding different digests for the same value with reordered keys.
- Resume-buffer sweeper: a failed directory-entry delete (stale-entry cleanup
  in `sweep_once`, and the post-eviction purge in `evict_bucket`) was silently
  discarded via `let _ = …`. Both now log a `warn` with the stream id + error so
  a persistent backend delete failure is visible rather than an invisible leaked
  directory entry.

## [0.6.0] — 2026-05-17

### Added

- Workspace shared-version bump 0.5.0 → 0.6.0. No klieo-core trait
  surface changes — tracks klieo-ops v0.6 breaking changes (B4 wire
  format, B6 verify_opaque, B7 sync-bridge timeout test).

## [0.5.0] — 2026-05-17

### Added

- Workspace shared-version bump 0.4.0 → 0.5.0. No klieo-core trait
  surface changes — tracks klieo-ops v0.5 hardening work.

## [0.4.0] — 2026-05-16

### Added

- Workspace shared-version bump from 0.3.0 to 0.4.0. No klieo-core
  trait surface changes — tracks klieo-ops v0.4 spec-fidelity work.

## [0.3.0] — 2026-05-16

### Added

- Workspace shared-version bump from 0.2.0 to 0.3.0. No klieo-core
  trait surface changes in this release — the bump tracks the
  klieo-ops Phase B feature additions which depend on klieo-core 0.2.

## [Unreleased]

### Added

- `Tool::is_effectful(&self) -> bool { false }` defaulted method. SemVer-minor (Rust guideline). See ADR-008.
- `Episode::Ops(serde_json::Value)` variant. SemVer-minor (`Episode` is `#[non_exhaustive]`). See ADR-008.
- `Error::Other { message, source }` variant — generic wrap for
  downstream errors that don't fit the typed `Llm`/`Tool`/`Memory`/
  `Bus`/`Config` variants. Carries an optional
  `Box<dyn std::error::Error + Send + Sync + 'static>` as `#[source]`
  so cause-chain traversal via `e.source()` is preserved. Classified
  non-retryable. SemVer-minor (`Error` is `#[non_exhaustive]`).
- `Job::builder(payload).dedup(k).max_attempts(n).build()` fluent
  builder on `klieo_core::bus::Job`. Existing `Job::new` and field
  assignment continue to compile. SemVer-minor.

### Changed — BREAKING

- `Episode::LlmCall` grew four `Option` fields: `provider`, `model`,
  `prompt_tokens`, `completion_tokens`. Every construction site must
  now supply all six fields (the legacy two plus the four new ones).
  Match-destructuring with `..` continues to work. Variant is **not**
  `#[non_exhaustive]` because Rust's `#[non_exhaustive]` on a struct
  variant blocks external struct-literal construction entirely;
  future field additions will ship deliberately in a minor release.
