packages feed

agentic-io 0.2.0.2 → 0.2.0.3

raw patch · 4 files changed

+43/−30 lines, 4 filesPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

API changes (from Hackage documentation)

- Agentic.IO.Store: data StoreMiss
- Agentic.IO.Store: instance GHC.Internal.Exception.Type.Exception Agentic.IO.Store.StoreMiss
- Agentic.IO.Store: instance GHC.Internal.Show.Show Agentic.IO.Store.StoreMiss
+ Agentic.IO.Store: StoreUnreadable :: FilePath -> Text -> StoreError
+ Agentic.IO.Store: data StoreError
+ Agentic.IO.Store: instance GHC.Internal.Exception.Type.Exception Agentic.IO.Store.StoreError
+ Agentic.IO.Store: instance GHC.Internal.Show.Show Agentic.IO.Store.StoreError
- Agentic.IO.Store: StoreMiss :: FilePath -> Text -> StoreMiss
+ Agentic.IO.Store: StoreMiss :: FilePath -> Text -> StoreError

Files

CHANGELOG.md view
@@ -1,5 +1,13 @@ # Changelog for agentic-io +## 0.2.0.3 - 2026-10-06++* `StoreMiss` is a constructor of a new `StoreError`, whose other constructor,+  `StoreUnreadable`, is raised for a recording that can't be read back+  (previously the core's `MalformedAnswers`).+* Recordings key a request by its `input`, `inputSchema` and `outputSchema`, so+  recordings from earlier versions miss.+ ## 0.2.0.2 - 2026-10-01  * No changes; released alongside agentic 0.2.0.2.
agentic-io.cabal view
@@ -1,6 +1,6 @@ cabal-version:      3.0 name:               agentic-io-version:            0.2.0.2+version:            0.2.0.3 synopsis:           IO helpers for agentic runtimes: concurrency, recording and replay description:   Runs a flow's independent work concurrently, records model calls to a file and replays them, and loads provider keys from a .env file.
src/Agentic/IO/Store.hs view
@@ -13,7 +13,7 @@ module Agentic.IO.Store   ( Mode (..)   , withStore-  , StoreMiss (..)+  , StoreError (..)   ) where  import Agentic.Aeson (fromAeson)@@ -46,14 +46,19 @@     -- answer) when it can't.   deriving (Eq, Show) --- | A replayed run asked something the recording doesn't have.-data StoreMiss = StoreMiss FilePath Text+data StoreError+  = StoreMiss FilePath Text+    -- ^ A replayed run asked something the recording doesn't have.+  | StoreUnreadable FilePath Text+    -- ^ The recording has an answer that can't be read back.   deriving (Show) -instance Exception StoreMiss where-  displayException (StoreMiss file what) =-    "The recording " <> file <> " has no answer for " <> T.unpack what-      <> ". Something upstream changed; record again, or use ReplayOrRecord."+instance Exception StoreError where+  displayException = \case+    StoreMiss file what ->+      "The recording " <> file <> " has no answer for " <> T.unpack what+        <> ". Something upstream changed; record again, or use ReplayOrRecord."+    StoreUnreadable file what -> "The recording " <> file <> " has " <> T.unpack what <> " that can't be read; record again."  -- | Wrap a runtime's System One and System Two with a store in @file@. withStore :: Mode -> FilePath -> Runtime IO -> IO (Runtime IO)@@ -73,15 +78,15 @@             withMVar lock $ \_ -> append file key answer             pure answer       one request =-        lookupOr (judgeKey request) ("a judgement: " <> questionsText request) (encodeAnswers <$> askSystemOne (systemOne rt) request)-          >>= decodedAs (MalformedAnswers ("unreadable stored answers in " <> T.pack file)) decodeAnswers'+        lookupOr (judgeKey request) ("a judgement: " <> questionsText request) (encodeStoredAnswers <$> askSystemOne (systemOne rt) request)+          >>= decodedAs "answers to a judgement" decodeStoredAnswers       two conversation =-        lookupOr (turnKey conversation) ("a turn of: " <> instructionText (instruction conversation)) (encodeTurn <$> askSystemTwo (systemTwo rt) conversation)-          >>= decodedAs (MalformedAnswers ("an unreadable stored turn in " <> T.pack file)) decodeTurn+        lookupOr (turnKey conversation) ("a turn of: " <> instructionText (instruction conversation)) (encodeStoredTurn <$> askSystemTwo (systemTwo rt) conversation)+          >>= decodedAs "a turn" decodeStoredTurn   pure rt {systemOne = SystemOne one, systemTwo = SystemTwo two}   where-    decodedAs :: FlowError -> (Value -> Maybe a) -> Value -> IO a-    decodedAs err decode' = maybe (throwIO err) pure . decode'+    decodedAs :: Text -> (Value -> Maybe a) -> Value -> IO a+    decodedAs what decode' = maybe (throwIO (StoreUnreadable file what)) pure . decode'     questionsText r = T.intercalate "; " (map question (requestQuestions r))     question = \case       AskYesNo q -> q@@ -130,10 +135,10 @@     [ ("kind", String "turn")     , ("path", Array [String (noteName n) | n <- path c])     , ("instruction", String (instructionText (instruction c)))-    , ("state", state c)-    , ("stateSchema", jsonSchema (stateSchema c))+    , ("input", input c)+    , ("inputSchema", jsonSchema (inputSchema c))     , ("tools", Array [Object [("name", String (specName t)), ("description", String (specDescription t)), ("input", jsonSchema (specInput t))] | t <- tools c])-    , ("output", jsonSchema (output c))+    , ("outputSchema", jsonSchema (outputSchema c))     , ("history", Array (map exchange (history c)))     ]   where@@ -145,7 +150,7 @@       ToolFailed t -> Object [("failed", String t)]  judgeKey :: JudgeRequest -> Value-judgeKey r = Object [("kind", String "judgement"), ("state", requestState r), ("questions", Array (map spec (requestQuestions r)))]+judgeKey r = Object [("kind", String "judgement"), ("input", requestInput r), ("questions", Array (map spec (requestQuestions r)))]   where     spec = \case       AskYesNo q -> Object [("yesNo", String q)]@@ -156,15 +161,15 @@ -- --------------------------------------------------------------------------- -- Answers -encodeTurn :: Turn -> Value-encodeTurn (Turn (Raw r) a) = Object [("raw", r), ("action", act a)]+encodeStoredTurn :: Turn -> Value+encodeStoredTurn (Turn (Raw r) a) = Object [("raw", r), ("action", act a)]   where     act = \case       CallTools calls -> Object [("callTools", Array [Object [("id", String (callId c)), ("name", String (callName c)), ("input", callInput c)] | c <- calls])]       Respond v -> Object [("respond", v)] -decodeTurn :: Value -> Maybe Turn-decodeTurn = \case+decodeStoredTurn :: Value -> Maybe Turn+decodeStoredTurn = \case   Object kvs -> do     r <- lookupField "raw" kvs     a <- lookupField "action" kvs@@ -182,8 +187,8 @@       Just (String t) -> Just t       _ -> Nothing -encodeAnswers :: [Answer] -> Value-encodeAnswers = Array . map answer+encodeStoredAnswers :: [Answer] -> Value+encodeStoredAnswers = Array . map answer   where     answer = \case       YesNoAnswer p -> Object [("yesNo", prob p)]@@ -191,8 +196,8 @@       ScoreAnswer pos ps c -> Object [("score", Number pos), ("probabilities", Array [Array [Integer (toInteger i), prob p] | (i, p) <- ps]), ("confidence", prob c)]     prob = Integer . toInteger . basisPoints -decodeAnswers' :: Value -> Maybe [Answer]-decodeAnswers' = \case+decodeStoredAnswers :: Value -> Maybe [Answer]+decodeStoredAnswers = \case   Array xs -> traverse answer xs   _ -> Nothing   where@@ -216,7 +221,7 @@       Integer i -> Just (fromInteger i)       _ -> Nothing     prob = \case-      Integer bp -> Just (fromBasisPoints (fromInteger bp / 10000))+      Integer bp -> Just (fromBasisPoints (fromInteger bp))       _ -> Nothing     number = \case       Number d -> Just d
test/Spec.hs view
@@ -2,7 +2,7 @@  import Agentic import Agentic.IO-import Agentic.Scripted (alwaysYes, replyingWith, respond)+import Agentic.Scripted (fixedAnswers, replyingWith, respond) import Control.Exception (try) import Data.IORef import Data.Text (Text)@@ -31,7 +31,7 @@   turns <- newIORef 0   judgements <- newIORef 0   let SystemTwo two = replyingWith (const (respond joke))-      SystemOne one = alwaysYes 0.8+      SystemOne one = fixedAnswers 0.8   pure     ( runtime         { systemTwo = SystemTwo (\c -> modifyIORef turns (+ 1) >> two c)@@ -75,7 +75,7 @@     _ <- interpret recording flow "scarecrows"     replaying <- withStore Replay file offline     result <- try (interpret replaying flow "penguins")-    either (\(StoreMiss _ what) -> what) (const "no miss") result `shouldBe` "a turn of: a joke about this"+    either (\case StoreMiss _ what -> what; StoreUnreadable _ _ -> "unreadable") (const "no miss") result `shouldBe` "a turn of: a joke about this"    it "replays what it has and records what it doesn't" $ do     file <- fresh "agentic-store-both.jsonl"