packages feed

shikumi-cache 0.2.0.0 → 0.2.0.1

raw patch · 9 files changed

+102/−89 lines, 9 filesdep ~baikaidep ~effectfuldep ~shikumi-cachePVP ok

version bump matches the API change (PVP)

Dependency ranges changed: baikai, effectful, shikumi-cache

API changes (from Hackage documentation)

Files

CHANGELOG.md view
@@ -2,6 +2,10 @@  ## Unreleased +## 0.2.0.1 — 2026-10-05++- Move the dependency on `mori://shinzui/baikai/packages/baikai` to `>=0.7.1.0 && <0.8` and widen the `effectful` bound to `>=2.6 && <2.8`, so both effectful 2.6 and 2.7 are supported (effectful 2.7 needs `baikai-effectful` 0.4.0.2, effectful 2.6 needs 0.4.0.1). Bounds only; no source changed.+ ## 0.2.0.0 — 2026-09-08  - Raise the internal `shikumi` bound to `^>=0.4.0.0` for the breaking core release.
shikumi-cache.cabal view
@@ -1,8 +1,8 @@-cabal-version:   3.4-name:            shikumi-cache-version:         0.2.0.0-synopsis:        Content-addressed response caching for shikumi (EP-6)-category:        AI+cabal-version: 3.4+name: shikumi-cache+version: 0.2.0.1+synopsis: Content-addressed response caching for shikumi (EP-6)+category: AI description:   The caching subsystem for shikumi: a content-addressed cache key (a BLAKE3   digest over a canonical request serialization — the contract EP-7's replay@@ -10,20 +10,26 @@   @cachedLLM@ memoizing interpreter that wraps EP-1's @LLM@ effect so identical   requests contact the provider once. -license:         BSD-3-Clause-author:          Nadeem Bitar-maintainer:      nadeem@gmail.com-build-type:      Simple+license: BSD-3-Clause+author: Nadeem Bitar+maintainer: nadeem@gmail.com+build-type: Simple extra-doc-files: CHANGELOG.md  common common-options   ghc-options:-    -Wall -Wcompat -Widentities -Wincomplete-uni-patterns-    -Wincomplete-record-updates -Wredundant-constraints-    -fhide-source-paths -Wmissing-export-lists -Wpartial-fields+    -Wall+    -Wcompat+    -Widentities+    -Wincomplete-uni-patterns+    -Wincomplete-record-updates+    -Wredundant-constraints+    -fhide-source-paths+    -Wmissing-export-lists+    -Wpartial-fields     -Wmissing-deriving-strategies -  default-language:   GHC2024+  default-language: GHC2024   default-extensions:     DeriveAnyClass     DuplicateRecordFields@@ -32,8 +38,8 @@     OverloadedStrings  library-  import:          common-options-  hs-source-dirs:  src+  import: common-options+  hs-source-dirs: src   exposed-modules:     Shikumi.Cache     Shikumi.Cache.Backend.Effort@@ -44,48 +50,51 @@     Shikumi.Cache.Types    build-depends:-    , aeson          >=2.2      && <2.3-    , baikai         >=0.7.0.0  && <0.8-    , base           >=4.20     && <5-    , blake3         >=0.3      && <0.4-    , bytestring     >=0.11     && <0.13-    , containers     >=0.6      && <0.9-    , direct-sqlite  >=2.3      && <2.4-    , effectful      >=2.5      && <2.7-    , generic-lens   >=2.2      && <2.4-    , lens           ^>=5.3-    , scientific     >=0.3      && <0.4-    , shikumi        ^>=0.4.0.0-    , stm            >=2.5      && <2.6-    , text           ^>=2.1-    , time           >=1.12     && <1.17-    , vector         >=0.13     && <0.14+    aeson >=2.2 && <2.3,+    baikai >=0.7.1.0 && <0.8,+    base >=4.20 && <5,+    blake3 >=0.3 && <0.4,+    bytestring >=0.11 && <0.13,+    containers >=0.6 && <0.9,+    direct-sqlite >=2.3 && <2.4,+    effectful >=2.6 && <2.8,+    generic-lens >=2.2 && <2.4,+    lens ^>=5.3,+    scientific >=0.3 && <0.4,+    shikumi ^>=0.4.0.0,+    stm >=2.5 && <2.6,+    text ^>=2.1,+    time >=1.12 && <1.17,+    vector >=0.13 && <0.14,  test-suite shikumi-cache-test-  import:         common-options-  type:           exitcode-stdio-1.0+  import: common-options+  type: exitcode-stdio-1.0   hs-source-dirs: test-  main-is:        Main.hs-  ghc-options:    -threaded -with-rtsopts=-N+  main-is: Main.hs+  ghc-options:+    -threaded+    -with-rtsopts=-N+   build-depends:-    , aeson-    , baikai         >=0.7.0.0  && <0.8-    , base-    , bytestring-    , containers-    , direct-sqlite-    , directory-    , effectful-    , filepath-    , generic-lens-    , lens-    , process-    , shikumi        ^>=0.4.0.0-    , shikumi-cache  ^>=0.2.0.0-    , stm-    , tasty-    , tasty-hunit-    , temporary-    , text-    , time-    , vector+    aeson,+    baikai >=0.7.1.0 && <0.8,+    base,+    bytestring,+    containers,+    direct-sqlite,+    directory,+    effectful,+    filepath,+    generic-lens,+    lens,+    process,+    shikumi ^>=0.4.0.0,+    shikumi-cache ^>=0.2.0.1,+    stm,+    tasty,+    tasty-hunit,+    temporary,+    text,+    time,+    vector,
src/Shikumi/Cache.hs view
@@ -64,7 +64,7 @@ -- | Policy knobs for 'cachedLLMWith', shared by every backend. -- -- 'entryTTL' is the maximum age of a usable entry, measured against--- 'CachedResponse.storedAt' at lookup time. 'Nothing' (the default) means+-- 'Shikumi.Cache.Types.storedAt' at lookup time. 'Nothing' (the default) means -- entries never expire, which is the uniform default across Memory, SQLite, -- Redis, and Postgres. Expiry is enforced here, at the policy layer, so it -- behaves identically no matter which backend interprets the 'Cache' effect; an@@ -97,7 +97,7 @@   Eff es a cachedLLM = cachedLLMWith defaultCacheConfig --- | A configured variant of 'cachedLLM'. See 'CacheConfig' for the shared TTL+-- | A configured variant of 'cachedLLM'. See t'CacheConfig' for the shared TTL -- policy. cachedLLMWith ::   (Cache :> es, LLM :> es, Time :> es, Error ShikumiError :> es) =>
src/Shikumi/Cache/Backend/Memory.hs view
@@ -1,6 +1,6 @@ -- | The in-memory cache backend (EP-6): a process-local @STM@ map from the hex -- cache key to a 'CachedResponse'. It stores the Haskell value directly (no--- serialization), so it needs no JSON instances for the baikai 'Response' graph.+-- serialization), so it needs no JSON instances for the baikai t'Baikai.Response.Response' graph. -- Build the store with 'newMemoryCache' and discharge the 'Cache' effect with -- 'runCacheMemory'. module Shikumi.Cache.Backend.Memory
src/Shikumi/Cache/Backend/SQLite.hs view
@@ -1,15 +1,15 @@ -- | The SQLite cache backend (EP-6) — a durable, embedded, single-file store. ----- Unlike the in-memory backend (which holds the Haskell 'CachedResponse'+-- Unlike the in-memory backend (which holds the Haskell t'CachedResponse' -- directly), SQLite persists the entry as JSON in a file, so a cache written by -- one process is read back by a later process: run a program, kill it, restart, -- and an identical request is served from disk with no provider call. The JSON -- round-trip for baikai's 'Baikai.Response.Response' graph comes from--- "Shikumi.Cache.ResponseJSON" (re-exported through 'CachedResponse''s--- 'Data.Aeson.ToJSON'/'Data.Aeson.FromJSON').+-- "Shikumi.Cache.ResponseJSON" (re-exported through t'CachedResponse'\'s+-- t'Data.Aeson.ToJSON'/t'Data.Aeson.FromJSON'). -- -- The store is a thin embedded SQLite database (no server) accessed via--- @direct-sqlite@. A single 'Database.SQLite3.Database' handle is guarded by an+-- @direct-sqlite@. A single t'Database.SQLite3.Database' handle is guarded by an -- 'MVar' so the 'Cache' effect's lookups and stores are serialized (SQLite's -- default threading mode does not allow concurrent use of one connection). WAL -- mode and a busy timeout make separate processes cooperate better. Lookup and@@ -43,13 +43,13 @@ import Shikumi.Cache (Cache (..), CacheKey (unCacheKey), CachedResponse (..)) import Shikumi.Cache.Backend.Effort (bestEffortIO) --- | A handle to an open SQLite-backed cache. The 'Database' is behind an 'MVar'+-- | A handle to an open SQLite-backed cache. The t'Database' is behind an 'MVar' -- so all access through the 'Cache' effect is serialized. newtype SQLiteCache = SQLiteCache {db :: MVar Database}  -- | The schema. The @key@ is the 64-hex 'CacheKey' (already version-namespaced -- via the @version@ field baked into the hash). @value@ is the UTF-8 JSON of--- 'CachedResponse'. @stored_at@ is ISO-8601 for inspection; policy-layer TTL+-- t'CachedResponse'. @stored_at@ is ISO-8601 for inspection; policy-layer TTL -- uses the same timestamp inside the JSON value. createTableSQL :: Text createTableSQL =
src/Shikumi/Cache/Key.hs view
@@ -1,7 +1,7 @@ {-# LANGUAGE DataKinds #-}  -- | The content-addressed cache key (EP-6) — the MasterPlan's integration point--- #7. A 'CacheKey' is a BLAKE3 256-bit digest (64 lowercase hex chars) over a+-- #7. A t'CacheKey' is a BLAKE3 256-bit digest (64 lowercase hex chars) over a -- /canonical/ JSON serialization of everything about a request that can change -- the model's answer: the model routing identity including base URL, model -- default headers, and compat shim; per-call headers; the rendered prompt with@@ -15,7 +15,7 @@ -- 'currentKeyVersion'; a bump invalidates all cache entries and makes previously -- recorded @shikumi-trace@ files unreplayable, with replay failing closed via -- @ReplayDivergence@. The replay engine in--- @docs/plans/7-hierarchical-tracing-observability-and-replay.md@ (EP-7) reuses+-- @docs\/plans\/7-hierarchical-tracing-observability-and-replay.md@ (EP-7) reuses -- 'cacheKey' verbatim, so both plans agree byte-for-byte; the golden test in the -- test suite pins the exact hex for a fixed request to catch any drift. module Shikumi.Cache.Key@@ -48,7 +48,7 @@ import Data.Text qualified as T  -- | The current cache key-namespace version. Baked into every hashed request--- (the @version@ field) and into 'Shikumi.Cache.Types.CachedResponse.keyVersion'.+-- (the @version@ field) and into 'Shikumi.Cache.Types.keyVersion'. -- Bumping it changes every key, making all prior entries unreachable — a clean -- invalidation with no row deletion. Because @shikumi-trace@ stores cache keys -- in trace files and recomputes them during replay, a version bump also makes@@ -113,7 +113,7 @@     toScientific = realToFrac  -- | Delete the payload-level @timestamp@ of every message in a serialized--- message vector. baikai's 'Baikai.Message.Message' encodes as+-- message vector. baikai's t'Baikai.Message.Message' encodes as -- @{"tag": ..., "contents": {..., "timestamp": ...}}@; the timestamp records -- when the message value was built, never what the provider sees, so two -- requests differing only in it must share a cache key. Only the
src/Shikumi/Cache/ResponseJSON.hs view
@@ -1,35 +1,35 @@ {-# OPTIONS_GHC -Wno-orphans #-} --- | A faithful JSON round-trip for baikai's 'Response' graph (EP-6).+-- | A faithful JSON round-trip for baikai's t'Response' graph (EP-6). ----- The persistent cache backends (SQLite/Redis/Postgres) store each cached--- 'Baikai.Response.Response' as JSON and read it back. baikai's 'Response' graph--- round-trips only /partially/ out of the box: 'Baikai.Model.Model',--- 'Baikai.Api.Api', 'Baikai.Content.AssistantContent', and--- 'Baikai.StopReason.StopReason' have both 'ToJSON' and 'FromJSON'; but+-- The persistent cache backends (SQLite\/Redis\/Postgres) store each cached+-- 'Baikai.Response.Response' as JSON and read it back. baikai's t'Response' graph+-- round-trips only /partially/ out of the box: t'Baikai.Model.Model',+-- t'Baikai.Api.Api', t'Baikai.Content.AssistantContent', and+-- t'Baikai.StopReason.StopReason' have both t'ToJSON' and t'FromJSON'; but -- 'Baikai.Usage.Usage', 'Baikai.Cost.Cost', and 'Baikai.Cost.CostBreakdown' are--- 'ToJSON'-only, and 'Baikai.Message.AssistantPayload' and 'Response' have no+-- t'ToJSON'-only, and 'Baikai.Message.AssistantPayload' and t'Response' have no -- aeson instances at all. ----- This module supplies the missing pieces as __orphan instances__ so a 'Response'+-- This module supplies the missing pieces as __orphan instances__ so a t'Response' -- can be encoded and decoded losslessly enough for caching. The instances mirror -- baikai's own encoders exactly: -----   * 'Usage' uses @camelTo2 '_'@ field labels (baikai's @usageOptions@), so the---     'FromJSON' reads the same snake_case keys baikai's 'ToJSON' writes.---   * 'Cost' / 'CostBreakdown' read the @usd@ / @*_usd@ 'Scientific' fields baikai+--   * t'Usage' uses @camelTo2 '_'@ field labels (baikai's @usageOptions@), so the+--     t'FromJSON' reads the same snake_case keys baikai's t'ToJSON' writes.+--   * t'Cost' \/ t'CostBreakdown' read the @usd@ \/ @*_usd@ 'Scientific' fields baikai --     writes (via @fromRationalRepetendUnlimited@) and lift them back to --     'Rational' with 'toRational'. This is lossy only for non-terminating --     repetends; it never affects the typed-output guarantee, which is decoded---     from the assistant __text__ ('AssistantContent', which round-trips+--     from the assistant __text__ ([AssistantContent]("Baikai.Content#t:AssistantContent"), which round-trips --     exactly) — cost is metadata.---   * 'AssistantPayload' uses @defaultOptions@ (matching baikai's+--   * t'AssistantPayload' uses @defaultOptions@ (matching baikai's --     @deriving anyclass ToJSON@).---   * 'Response' is written out by hand with the same keys @defaultOptions@+--   * t'Response' is written out by hand with the same keys @defaultOptions@ --     produced, minus @evidence@ — see below. ----- __'Response' does not cache its 'Baikai.Evidence.ModelCallEvidence'.__ Since--- baikai 0.5 a 'Response' may carry the evidence record for the call that+-- __t'Response' does not cache its t'Baikai.Evidence.ModelCallEvidence'.__ Since+-- baikai 0.5 a t'Response' may carry the evidence record for the call that -- produced it. That record describes /one/ crossing of the provider boundary; a -- cache hit is precisely the case where no such crossing happened, so replaying -- a stored record would attribute another call's evidence to this one — the@@ -67,7 +67,7 @@ import Data.Aeson.Types (Parser) import Data.Scientific (Scientific) --- | baikai's 'Usage' field-label scheme: @camelTo2 '_'@ (snake_case).+-- | baikai's t'Usage' field-label scheme: @camelTo2 '_'@ (snake_case). usageOptions :: Options usageOptions = defaultOptions {fieldLabelModifier = camelTo2 '_'} @@ -76,7 +76,7 @@ -- with a non-terminating decimal expansion, such as @1 % 3@, does not satisfy -- 'Eq' after an encode/decode round-trip because baikai's encoder drops the -- repetend index. Real USD pricing rates are decimal, so real provider costs--- terminate; do not rely on round-tripped 'CachedResponse' equality for+-- terminate; do not rely on round-tripped 'Shikumi.Cache.Types.CachedResponse' equality for -- synthetic non-decimal costs. ratField :: Object -> Key -> Parser Rational ratField o k = toRational <$> (o .: k :: Parser Scientific)
src/Shikumi/Cache/Types.hs view
@@ -2,7 +2,7 @@ -- operational metadata needed to age and version-guard an entry. -- -- The in-memory backend stores this Haskell value directly (no serialization).--- The persistent backends (SQLite/Redis/Postgres) store its JSON encoding; the+-- The persistent backends (SQLite\/Redis\/Postgres) store its JSON encoding; the -- @ToJSON@/@FromJSON@ for the baikai 'Response' graph that baikai itself does not -- ship in full come from "Shikumi.Cache.ResponseJSON" (imported here for their -- instances). Decoding an entry whose 'keyVersion' does not match the live
test/Main.hs view
@@ -2,7 +2,7 @@ -- golden-pinned), the in-memory backend round-trip, the SQLite backend -- round-trip and cross-process restart durability, the @cachedLLM@ memoizer (the -- headline one-provider-call behaviour) via a counting stub, and--- versioning/invalidation. The server-backed Redis/Postgres backends are tested+-- versioning\/invalidation. The server-backed Redis\/Postgres backends are tested -- in their own packages (@shikumi-cache-redis@/@shikumi-cache-postgres@). -- -- The SQLite restart test re-executes this binary as a subprocess in a "write"