diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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.
diff --git a/agentic-io.cabal b/agentic-io.cabal
--- a/agentic-io.cabal
+++ b/agentic-io.cabal
@@ -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.
diff --git a/src/Agentic/IO/Store.hs b/src/Agentic/IO/Store.hs
--- a/src/Agentic/IO/Store.hs
+++ b/src/Agentic/IO/Store.hs
@@ -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
diff --git a/test/Spec.hs b/test/Spec.hs
--- a/test/Spec.hs
+++ b/test/Spec.hs
@@ -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"
