diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,173 @@
 
 ## [Unreleased]
 
+## [baikai 0.8.0.0] - 2026-10-10
+
+### Added
+
+- Catalog: `anthropic_claude_haiku_5_5` (Claude Haiku 5.5, released
+  2026-10-07). It uses Messages with adaptive thinking, drops sampling options,
+  forwards forced tool choice, and has a 1,000,000-token context and
+  128,000-token output limit. Prompts over 100,000 tokens, counting cache reads
+  and writes, are priced at $0.50/$0.05/$0.625/$2.50 per million input, cache
+  read, cache write and output tokens instead of $0.10/$0.01/$0.125/$0.50, and
+  one-hour cache writes at $1 instead of $0.20. Live text and tool cases
+  passed on 2026-10-10, and a live request confirmed forced tool choice with
+  adaptive thinking.
+
+### Changed (breaking)
+
+- `Baikai.Model.InputPriceTier` gains a third field, `longCacheWriteCost ::
+  Maybe Rational`: the one-hour cache-write rate for requests that cross that
+  tier. `resolveRates` now takes the long rate from the selected tier rather
+  than overriding every tier with the policy-level rate, and validation rejects
+  a policy whose policy-level long rate leaves any tier without one. Tier JSON
+  without the key decodes as `Nothing`. __Breaking__: positional constructions
+  of `InputPriceTier` need the new argument.
+
+### Changed
+
+- Catalog: the 2026-10-10 model refresh lowers `anthropic_claude_sonnet_5_5`'s
+  cache-read rate from $0.20 to $0.10 per million tokens, following Anthropic's
+  2026-10-07 price cut, and corrects `anthropic_claude_sonnet_4_5`'s context
+  window from 1,000,000 to 200,000 tokens (its 1M beta ended on 2026-04-30).
+  Claude Haiku 5.5 was curated separately (see Added). No OpenAI model was
+  added.
+
+- Documentation: the 2026-10-02 model refresh found no new OpenAI or Anthropic
+  models; the catalog is unchanged. `docs/user/models-and-providers.md` notes
+  Anthropic's retirement of Claude Sonnet 4.5 on 2026-11-30 (deprecation of
+  `anthropic_claude_sonnet_4_5` is planned in plan 89 for a future release;
+  it remains in 0.8.0.0),
+  why the access-gated Claude Mythos models are not curated, and how GPT-6
+  Astra's new ultrafast service tier is costed.
+
+### Fixed
+
+- The shared batch subprocess scope now owns POSIX process groups, joins pipe
+  readers, and synchronously reaps direct children before acknowledging
+  cancellation. Executable-version probes use the same scope. Darwin/Linux
+  coverage excludes processes that deliberately escape the group and adopted
+  descendant reaping; Windows retains direct-child cleanup. The provider fixes
+  ship in `baikai-claude 0.7.1.1` and `baikai-openai 0.7.1.1`; see
+  [plan 90](docs/plans/90-terminate-owned-batch-cli-process-groups-before-acknowledging-cancellation.md)
+  and [BUG-1](docs/bug-reports/batch-cli-cancellation-leaves-child-alive.md).
+
+- `baikai`'s source distribution now ships `data/models/*.json` and the test
+  suite's fixtures under `test/fixtures/`. Without them, running the suite from
+  the Hackage tarball failed 27 of 794 tests on missing files: the fixtures
+  themselves, plus the catalog round-trip test, whose `baikai-gen-models` run
+  had no model data to read.
+
+## [baikai-claude 0.7.1.1] - 2026-10-10
+
+### Fixed
+
+- Batch `claude -p` calls stop owned POSIX process-group members and join pipe
+  readers before acknowledging cancellation, including active synthetic streams
+  and evidence probes. Preserves the original asynchronous exception. Resolves
+  [BUG-1](docs/bug-reports/batch-cli-cancellation-leaves-child-alive.md).
+
+### Changed
+
+- Requires `baikai ^>=0.8.0` for shared subprocess ownership and the new pricing
+  tier field. No provider API change. Haiku 5.5 request contracts are covered
+  by offline tests.
+
+## [baikai-openai 0.7.1.1] - 2026-10-10
+
+### Fixed
+
+- Batch `codex exec` calls stop owned POSIX process-group members and join pipe
+  readers before acknowledging cancellation, including active synthetic streams,
+  repeated cancellation, and evidence probes. The temporary schema survives
+  until subprocess cleanup finishes. Resolves
+  [BUG-1](docs/bug-reports/batch-cli-cancellation-leaves-child-alive.md).
+
+### Changed
+
+- Requires `baikai ^>=0.8.0` for shared subprocess ownership and the new pricing
+  tier field. No provider API change.
+
+## [baikai-trace-otel 0.4.0.2] - 2026-10-10
+
+### Changed
+
+- Requires `baikai ^>=0.8.0` for compatibility with the core pricing tier
+  change. No package API change.
+
+## [baikai-effectful 0.4.0.3] - 2026-10-10
+
+### Changed
+
+- Requires `baikai ^>=0.8.0` for compatibility with the core pricing tier
+  change. No package API change.
+
+## [baikai-kit 0.4.0.1] - 2026-10-10
+
+### Changed
+
+- Requires `baikai ^>=0.8.0` for compatibility with the core pricing tier
+  change. No package API change.
+
+## [baikai-agent 0.2.0.2] - 2026-10-10
+
+### Changed
+
+- Requires `baikai ^>=0.8.0` for compatibility with the core pricing tier
+  change. No package API change.
+- Raises the provider bounds to `baikai-claude ^>=0.7.1.1` and
+  `baikai-openai ^>=0.7.1.1`, so installing the tool selects the fixed batch
+  adapters. Binary regressions use the shared process fixture and cover both
+  cancellation and a 1MiB output flood.
+
+## [baikai-kit 0.4.0.0] - 2026-10-02
+
+### Added
+
+- Per-item `visibility` (`tool-only` by default, or `shared`) for manifest
+  versions 1 and 2, with `kit install --shared`, `--tool-only`, and
+  `--accept-shared-codex`. Shared Claude skills and agents use tracked links;
+  tool-only Codex skills use tracked disabled entries in `config.toml`.
+- `codexSessionArgs`, required in the `extraArgs` of a consumer's own Codex
+  launches to re-enable its tool-only skills. This is the one launcher step
+  beyond a dependency bump; the standard `kitConfig`/`kitCommandParser`/`runKit`
+  integration otherwise compiles unchanged. Codex custom agents cannot be
+  isolated and require explicit acceptance, or shared visibility.
+- Requested and effective visibility in status (table and JSON), the
+  `visibility-broken` condition, and a migration note for legacy shared Codex
+  skills. Update repairs visibility even for items skipped for local edits;
+  uninstall removes only owned links and config entries.
+
+### Changed (breaking)
+
+- `SkillEntry` and `AgentEntry` gain `visibility`; `SidecarMeta` gains
+  `visibility`, `visibilitySource`, `sharedLinks`, and `codexDisabledSkills`;
+  `newSidecarMeta` takes these values. `RemovalOutcome` gains `linksRemoved`
+  and `configEntriesRemoved`; `StatusRow` gains `requestedVisibility` and
+  `effectiveVisibility`; `KitConfig` gains `confirmSharedCodex` (default
+  `Nothing` in `kitConfig`).
+- `KitInstall` gains `InstallOptions`; `installItem` and `installFrom` take
+  it as their last argument. Use `defaultInstallOptions` to follow the
+  manifest. Explicit install flags persist across updates, while new
+  manifest-driven installs follow updated defaults. Legacy placement is kept.
+- JSON adds list `visibility`, status `requestedVisibility` and
+  `effectiveVisibility`, and the new condition value. `formatVersion` stays
+  1 because all additions preserve existing keys and their meaning.
+
+### Fixed
+
+- Codex installs refuse destinations without this tool's sidecar instead of
+  overwriting user or other-tool assets. Uninstall also preserves foreign
+  Codex assets. Shared Claude names are checked before any provider write.
+- Codex config edits preserve UTF-8 text, comments and permissions, refuse
+  symlinks and invalid/conflicting config, and verify the semantic change
+  before atomic rename. Reused user-owned disabled entries survive uninstall.
+  Shared visibility refuses a user-owned entry that would keep the skill hidden.
+- The source distribution ships the test suite's manifest fixtures and JSON
+  goldens, so the test suite passes when run from the Hackage tarball. Since
+  0.3.0.0 it had failed there, because those files were missing.
+
 ## [baikai 0.7.2.0] - 2026-09-30
 
 ### Added
diff --git a/baikai-claude.cabal b/baikai-claude.cabal
--- a/baikai-claude.cabal
+++ b/baikai-claude.cabal
@@ -1,6 +1,6 @@
 cabal-version: 3.4
 name: baikai-claude
-version: 0.7.1.0
+version: 0.7.1.1
 synopsis: Anthropic Claude providers for the baikai abstraction
 description:
   Anthropic backends for baikai: the Messages API over SSE, the claude -p batch
@@ -72,7 +72,7 @@
   autogen-modules: Paths_baikai_claude
   build-depends:
     aeson ^>=2.2,
-    baikai ^>=0.7.2,
+    baikai ^>=0.8.0,
     base >=4.20 && <5,
     base16-bytestring ^>=1.0,
     base64-bytestring ^>=1.2,
@@ -88,6 +88,7 @@
     http-client-tls >=0.3 && <0.5,
     http-types ^>=0.12,
     lens ^>=5.3,
+    process ^>=1.6,
     servant-client ^>=0.20,
     streamly >=0.11 && <0.13,
     streamly-core >=0.3 && <0.5,
@@ -101,7 +102,9 @@
   hs-source-dirs: test
   main-is: Main.hs
   other-modules:
+    CliCancellationSpec
     CliEvidenceSpec
+    CliProcessFixture
     Contract
     ErrorClassSpec
     EvidenceSpec
@@ -122,7 +125,7 @@
 
   build-depends:
     aeson,
-    baikai ^>=0.7.2,
+    baikai ^>=0.8.0,
     baikai-claude,
     base >=4.20 && <5,
     bytestring,
@@ -136,6 +139,7 @@
     http-types,
     lens ^>=5.3,
     network,
+    process,
     servant-client,
     stm,
     streamly,
@@ -147,3 +151,6 @@
     time,
     tls,
     vector,
+
+  if !os(windows)
+    build-depends: unix ^>=2.8
diff --git a/src/Baikai/Provider/Claude/Cli.hs b/src/Baikai/Provider/Claude/Cli.hs
--- a/src/Baikai/Provider/Claude/Cli.hs
+++ b/src/Baikai/Provider/Claude/Cli.hs
@@ -57,6 +57,7 @@
 import Baikai.Model (Model)
 import Baikai.Options (Options)
 import Baikai.Provider.Cli.Internal qualified as Internal
+import Baikai.Provider.Cli.Process.Internal (withOwnedProcess, withOwnedWorker)
 import Baikai.Provider.Registry
   ( ApiProvider (..),
     apiProviderWith,
@@ -70,17 +71,8 @@
 import Baikai.Usage (Usage, zeroUsage)
 import Control.Exception (SomeException, displayException, fromException)
 import Control.Lens ((&), (.~), (^.))
-import Cradle
-  ( ExitCode (..),
-    StderrRaw (..),
-    StdoutRaw (..),
-    addArgs,
-    cmd,
-    run,
-    setNoStdin,
-    setWorkingDir,
-  )
 import Data.Aeson qualified as Aeson
+import Data.ByteString qualified as BS
 import Data.ByteString.Lazy qualified as LBS
 import Data.Generics.Labels ()
 import Data.Maybe (fromMaybe)
@@ -90,6 +82,8 @@
 import Data.Time.Clock (UTCTime, diffUTCTime, getCurrentTime)
 import Data.Vector qualified as Vector
 import GHC.Generics (Generic)
+import System.Exit (ExitCode (..))
+import System.Process qualified as P
 
 -- | Configuration for the @claude -p@ subprocess.
 data ClaudeCliConfig = ClaudeCliConfig
@@ -229,12 +223,23 @@
   let (exe, args) = claudeCliCommand cfg m ctx opts
   start <- getCurrentTime
   executed <-
-    Internal.trySync $
-      run $
-        cmd exe
-          & addArgs args
-          & setNoStdin
-          & Internal.maybeApply (cfg ^. #workingDir) setWorkingDir
+    Internal.trySync
+      $ withOwnedProcess
+        (P.proc exe args)
+          { P.std_in = P.NoStream,
+            P.std_out = P.CreatePipe,
+            P.std_err = P.CreatePipe,
+            P.cwd = cfg ^. #workingDir
+          }
+      $ \_ output errors ph -> case (output, errors) of
+        (Just out, Just err) ->
+          withOwnedWorker (BS.hGetContents out) $ \joinOut ->
+            withOwnedWorker (BS.hGetContents err) $ \joinErr -> do
+              stdoutBytes <- joinOut
+              stderrBytes <- joinErr
+              code <- P.waitForProcess ph
+              pure (code, stdoutBytes, stderrBytes)
+        _ -> ioError (userError "claude: capture handles missing")
   end <- getCurrentTime
   -- The argument vector is the envelope: for a subprocess it is what
   -- crossed the boundary, and there is nothing else to describe the
@@ -264,7 +269,7 @@
       schemaRequested = not (null (schemaArgs opts))
   case executed of
     Left ex -> failedWith Nothing (exceptionToError ex)
-    Right (exitCode, StdoutRaw out, StderrRaw err) -> case exitCode of
+    Right (exitCode, out, err) -> case exitCode of
       ExitFailure n ->
         let stderr = Internal.decodeUtf8Lenient err
             flagRejected
diff --git a/src/Baikai/Provider/Claude/Internal/Request.hs b/src/Baikai/Provider/Claude/Internal/Request.hs
--- a/src/Baikai/Provider/Claude/Internal/Request.hs
+++ b/src/Baikai/Provider/Claude/Internal/Request.hs
@@ -403,8 +403,8 @@
 -- | The effort word sent for each level on the adaptive style.
 --
 -- Every level sends a word, @high@ included. Anthropic's defaults differ
--- by model (Claude Opus 5.5 defaults to @medium@, other adaptive models
--- to @high@; see
+-- by model (Claude Opus 5.5 and Claude Haiku 5.5 default to @medium@,
+-- other adaptive models to @high@; see
 -- <https://platform.claude.com/docs/en/build-with-claude/effort>), so an
 -- omitted field does not mean @high@. Sending a model's default
 -- explicitly is documented as identical to omitting it.
diff --git a/test/CliCancellationSpec.hs b/test/CliCancellationSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/CliCancellationSpec.hs
@@ -0,0 +1,37 @@
+{-# LANGUAGE CPP #-}
+
+module CliCancellationSpec (tests) where
+
+import Baikai
+import Baikai.Provider.Claude.Cli qualified as Cli
+import CliProcessFixture
+import Control.Lens ((&), (.~), (^.))
+import Control.Monad (void)
+import Streamly.Data.Stream qualified as Stream
+import Test.Tasty (TestTree, testGroup)
+import Test.Tasty.HUnit (testCase)
+
+tests :: TestTree
+#ifndef mingw32_HOST_OS
+tests = testGroup "CliCancellationSpec: Claude owned batch cancellation"
+  [ testCase "simple descendant" $ run False False False,
+    testCase "SIGKILL-resistant descendant" $ run True False False,
+    testCase "repeated cancellation" $ run True True False,
+    testCase "active synthetic stream" $ run False False True,
+    testCase "evidence version-probe descendant" $ withFixture False False versionPrefix $ \fixture -> do
+      let provider = makeProvider fixture
+          opts = emptyOptions & #evidence .~ Just (evidenceRequest "cancellation-probe")
+      cancelAndJoin False ((provider ^. #complete) model emptyContext opts) fixture assertStopped
+  ]
+  where
+    run resistant repeated streaming = withFixture resistant False "" $ \fixture -> do
+      let provider = makeProvider fixture
+          action | streaming = void (Stream.toList ((provider ^. #stream) model emptyContext emptyOptions))
+                 | otherwise = void ((provider ^. #complete) model emptyContext emptyOptions)
+      cancelAndJoin repeated action fixture assertStopped
+    makeProvider fixture = Cli.claudeCliProvider (Cli.defaultClaudeCliConfig & #executable .~ executable fixture)
+    model = emptyModel & #api .~ AnthropicMessagesCli & #modelId .~ "fixture"
+    versionPrefix = "if [ \"$1\" != \"--version\" ]; then printf '%s\\n' '{\"type\":\"result\",\"result\":\"ok\",\"is_error\":false}'; exit 0; fi"
+#else
+tests = testGroup "CliCancellationSpec: POSIX fixtures require Darwin/Linux" []
+#endif
diff --git a/test/CliProcessFixture.hs b/test/CliProcessFixture.hs
new file mode 100644
--- /dev/null
+++ b/test/CliProcessFixture.hs
@@ -0,0 +1,158 @@
+{-# LANGUAGE CPP #-}
+
+-- | Real, offline subprocess fixtures shared by core and adapter regressions.
+-- Keep the copies in each package identical: each source distribution must
+-- contain its own test dependencies.
+module CliProcessFixture
+  ( Fixture (..),
+    withFixture,
+    awaitReady,
+    assertStopped,
+    cancelAndJoin,
+    processState,
+    writeExecutable,
+  )
+where
+
+import Control.Concurrent (forkFinally, forkIO, killThread, threadDelay)
+import Control.Concurrent.MVar (readMVar)
+import Control.Concurrent.MVar qualified
+import Control.Exception (SomeAsyncException, bracket, finally, fromException, try)
+import Control.Monad (forM_, unless, void)
+import Data.List (isInfixOf)
+import System.Directory (doesFileExist, getPermissions, setOwnerExecutable, setPermissions)
+import System.Exit (ExitCode (..))
+import System.FilePath ((</>))
+import System.IO.Temp (withSystemTempDirectory)
+import System.Process qualified as P
+import System.Timeout (timeout)
+import Test.Tasty.HUnit (assertBool, assertFailure)
+#ifndef mingw32_HOST_OS
+import System.Posix.Signals (sigKILL, signalProcess)
+#endif
+
+data Fixture = Fixture
+  { directory :: FilePath,
+    executable :: FilePath,
+    parentFile :: FilePath,
+    childFile :: FilePath,
+    readyFile :: FilePath
+  }
+
+-- The optional prefix can implement a successful normal invocation and hang
+-- only in --version. Every fixture sleeps 30s so success cannot be natural expiry.
+withFixture :: Bool -> Bool -> String -> (Fixture -> IO a) -> IO a
+withFixture resistant early prefix use = withSystemTempDirectory "baikai-cli-cancellation" $ \dir -> do
+  let sleeper = dir </> "sleeper"
+      parent = dir </> "parent"
+      child = dir </> "child"
+      ready = dir </> "ready"
+      body =
+        unlines $
+          ["#!/bin/sh", prefix]
+            <> ["trap '' INT TERM" | resistant]
+            <> [ "echo $$ > '" <> parent <> "'",
+                 "sh -c 'echo $$ > \"" <> child <> "\"; exec \"" <> sleeper <> "\" 30' &",
+                 "while [ ! -s '" <> child <> "' ]; do sleep 0.01; done",
+                 "echo ready > '" <> ready <> "'"
+               ]
+            <> [if early then "exit 0" else "wait"]
+  P.callProcess "/bin/ln" ["-s", "/bin/sleep", sleeper]
+  exe <- writeExecutable dir "vendor" body
+  let fixture = Fixture dir exe parent child ready
+  -- Capture process identities before use returns/fails; identity comparison in
+  -- emergency cleanup avoids signalling a PID reused after a fixture exits.
+  bracket (pure fixture) cleanupFixture use
+
+cleanupFixture :: Fixture -> IO ()
+cleanupFixture fixture = forM_ [childFile fixture, parentFile fixture] $ \file -> do
+  exists <- doesFileExist file
+  if not exists
+    then pure ()
+    else do
+      pid <- read <$> readFile file
+      state <- processState pid
+      -- Both the script and sleeper executable have this invocation's unique
+      -- directory in their command line, including after exec. A reused PID
+      -- belonging to a different invocation cannot match that identity.
+      (_, identity, _) <- P.readProcessWithExitCode "/bin/ps" ["-p", show pid, "-o", "lstart=,command="] ""
+      let ours = (directory fixture <> "/") `isInfixOf` identity
+      unless (null state || 'Z' `elem` state || not ours) $ do
+        (_, current, _) <- P.readProcessWithExitCode "/bin/ps" ["-p", show pid, "-o", "lstart=,command="] ""
+        if current /= identity
+          then pure ()
+          else do
+            killFixturePid pid
+
+killFixturePid :: Int -> IO ()
+#ifndef mingw32_HOST_OS
+killFixturePid pid = void (try (signalProcess sigKILL (fromIntegral pid)) :: IO (Either IOError ()))
+#else
+killFixturePid _ = pure ()
+#endif
+
+writeExecutable :: FilePath -> String -> String -> IO FilePath
+writeExecutable dir name body = do
+  let path = dir </> name
+  writeFile path body
+  perms <- getPermissions path
+  setPermissions path (setOwnerExecutable True perms)
+  pure path
+
+awaitReady :: Fixture -> IO (Int, Int)
+awaitReady fixture = do
+  ready <- timeout 2000000 poll
+  case ready of
+    Nothing -> assertFailure "fixture did not publish readiness within two seconds"
+    Just () -> (,) <$> (read <$> readFile (parentFile fixture)) <*> (read <$> readFile (childFile fixture))
+  where
+    poll = do
+      exists <- doesFileExist (readyFile fixture)
+      if exists then pure () else threadDelay 10000 >> poll
+
+processState :: Int -> IO String
+processState pid = do
+  (code, out, err) <- P.readProcessWithExitCode "/bin/ps" ["-p", show pid, "-o", "stat="] ""
+  case code of
+    ExitSuccess -> pure out
+    ExitFailure 1 | null out && null err -> pure ""
+    _ -> assertFailure ("ps observation failed: " <> show (code, out, err))
+
+assertStopped :: (Int, Int) -> IO ()
+assertStopped (parent, child) = do
+  parentState <- processState parent
+  assertBool ("direct child remains: " <> parentState) (null parentState)
+  childState <- processState child
+  assertBool ("descendant is running: " <> childState) (null childState || 'Z' `elem` childState)
+  -- Removal of an adopted zombie is diagnostic, distinct from termination.
+  unless (null childState) $ do
+    removed <- timeout 200000 (let poll = processState child >>= \s -> if null s then pure () else threadDelay 10000 >> poll in poll)
+    case removed of
+      Nothing -> putStrLn "fixture descendant stopped; adoptive parent has not yet reaped zombie"
+      Just () -> pure ()
+
+cancelAndJoin :: Bool -> IO a -> Fixture -> ((Int, Int) -> IO ()) -> IO ()
+cancelAndJoin repeated action fixture after = do
+  done <- Control.Concurrent.MVar.newEmptyMVar
+  tid <- forkFinally (void action) (Control.Concurrent.MVar.putMVar done)
+  let stop = void (forkIO (killThread tid))
+  ( do
+      pids <- awaitReady fixture
+      stop
+      if repeated then void (forkIO (threadDelay 40000 >> killThread tid)) else pure ()
+      -- Keep the original 200ms observation without using it as acknowledgement.
+      threadDelay 200000
+      diagnostic <- processState (snd pids)
+      putStrLn ("descendant state at 200ms: " <> show diagnostic)
+      terminal <- timeout 2800000 (readMVar done)
+      case terminal of
+        Just (Left e) | Just _ <- (fromException e :: Maybe SomeAsyncException) -> after pids
+        other -> assertFailure ("expected acknowledged async cancellation within 3s, got " <> show other)
+    )
+    `finally` do
+      stop
+      cleanupFixture fixture
+      joined <- timeout 3000000 (readMVar done)
+      case joined of
+        Nothing -> assertFailure "fixture worker failed to finish after emergency cleanup"
+        Just _ -> pure ()
diff --git a/test/FableContractsSpec.hs b/test/FableContractsSpec.hs
--- a/test/FableContractsSpec.hs
+++ b/test/FableContractsSpec.hs
@@ -3,7 +3,7 @@
 module FableContractsSpec (tests) where
 
 import Baikai hiding (messages, model)
-import Baikai.Models.Generated (anthropic_claude_fable_5_1, anthropic_claude_opus_5_5, anthropic_claude_sonnet_5_5)
+import Baikai.Models.Generated (anthropic_claude_fable_5_1, anthropic_claude_haiku_5_5, anthropic_claude_opus_5_5, anthropic_claude_sonnet_5_5)
 import Baikai.Provider.Claude.Internal.Request qualified as R
 import Baikai.Provider.Claude.Internal.Stream (SseDriver, claudeMessagesStreamWith)
 import Claude.V1.Messages qualified as C
@@ -53,6 +53,13 @@
           field "tools" raw @?= field "tools" (Aeson.toJSON firstReq)
           contentAt 1 replayed @?= V.fromList [signed "" "sig-one", toolItem "toolu_1"]
           field "tool_use_id" (contentAt 2 replayed V.! 0) @?= Just (String "toolu_1"),
+      testCase "Haiku 5.5 forwards a forced tool choice with adaptive thinking" $ do
+        (req, _) <- either (\e -> assertFailure (T.unpack e) >> fail "map") pure (R.mapRequest anthropic_claude_haiku_5_5 haikuContext haikuForcedOptions)
+        Aeson.toJSON req @?= haikuForcedBody,
+      testCase "Haiku 5.5 drops temperature and records why" $ do
+        (req, translation) <- either (\e -> assertFailure (T.unpack e) >> fail "map") pure (R.mapRequest anthropic_claude_haiku_5_5 context (options & #temperature .~ Just 0.2))
+        field "temperature" (Aeson.toJSON req) @?= Nothing
+        translation ^. #adjustments @?= [SamplingDroppedUnsupportedModel ["temperature"]],
       testCase "forced choices fail before the driver for complete and stream, including renamed models" $
         forM_ [model, model & #modelId .~ "renamed-generation"] $ \m ->
           forM_ [ToolChoiceRequired, ToolChoiceSpecific "lookup"] $ \choice -> do
@@ -139,6 +146,28 @@
 
 model :: Model
 model = anthropic_claude_fable_5_1
+
+-- | The one request plan 91 sends live to settle whether Haiku 5.5 accepts
+-- a forced tool choice alongside baikai's explicit adaptive thinking. The
+-- same JSON is saved as docs/validation/plan-91/forced-tool-choice-request.json.
+haikuContext :: Context
+haikuContext = systemUser "Answer by calling the lookup tool." "What is the capital of France?" & #tools .~ V.singleton (mkTool "lookup" "Look up a fact." (object ["type" .= ("object" :: Text), "properties" .= object ["query" .= object ["type" .= ("string" :: Text)]], "required" .= ["query" :: Text]]))
+
+haikuForcedOptions :: Options
+haikuForcedOptions = options & #toolChoice .~ Just ToolChoiceRequired & #thinking .~ Just ThinkingLow & #maxTokens .~ Just 1024
+
+haikuForcedBody :: Value
+haikuForcedBody =
+  object
+    [ "model" .= ("claude-haiku-5-5" :: Text),
+      "max_tokens" .= (1024 :: Int),
+      "system" .= ("Answer by calling the lookup tool." :: Text),
+      "messages" .= [object ["role" .= ("user" :: Text), "content" .= [object ["type" .= ("text" :: Text), "text" .= ("What is the capital of France?" :: Text)]]]],
+      "tools" .= [object ["name" .= ("lookup" :: Text), "description" .= ("Look up a fact." :: Text), "input_schema" .= object ["type" .= ("object" :: Text), "properties" .= object ["query" .= object ["type" .= ("string" :: Text)]], "required" .= ["query" :: Text]]]],
+      "tool_choice" .= object ["type" .= ("any" :: Text)],
+      "thinking" .= object ["type" .= ("adaptive" :: Text), "display" .= ("summarized" :: Text)],
+      "output_config" .= object ["effort" .= ("low" :: Text)]
+    ]
 
 options :: Options
 options = emptyOptions & #apiKey .~ Just (ApiKeyLiteral "offline-key")
diff --git a/test/Main.hs b/test/Main.hs
--- a/test/Main.hs
+++ b/test/Main.hs
@@ -19,6 +19,7 @@
 import Baikai.Provider.Claude.Interactive
 import Baikai.Provider.Claude.Internal.Request (describeThinkingFor, mapRequest)
 import Claude.V1.Messages qualified as Messages
+import CliCancellationSpec qualified
 import CliEvidenceSpec qualified
 import Contract (assertErrorContract)
 import Control.Exception (bracket)
@@ -76,6 +77,7 @@
         cliMissingBinaryTest,
         responseFormatMappingTest,
         optionsMappingTest,
+        CliCancellationSpec.tests,
         CliEvidenceSpec.tests,
         ErrorClassSpec.tests,
         EvidenceSpec.tests,
diff --git a/test/ThinkingSpec.hs b/test/ThinkingSpec.hs
--- a/test/ThinkingSpec.hs
+++ b/test/ThinkingSpec.hs
@@ -73,6 +73,7 @@
   [ ("claude-fable-5", anthropic_claude_fable_5, AnthropicThinkingAdaptive, False, True, False),
     ("claude-fable-5-1", anthropic_claude_fable_5_1, AnthropicThinkingAdaptive, False, False, False),
     ("claude-haiku-4-5", anthropic_claude_haiku_4_5, AnthropicThinkingBudget, True, True, False),
+    ("claude-haiku-5-5", anthropic_claude_haiku_5_5, AnthropicThinkingAdaptive, False, True, False),
     ("claude-opus-4-5", anthropic_claude_opus_4_5, AnthropicThinkingBudget, True, True, False),
     ("claude-opus-4-6", anthropic_claude_opus_4_6, AnthropicThinkingAdaptive, True, True, False),
     ("claude-opus-4-7", anthropic_claude_opus_4_7, AnthropicThinkingAdaptive, False, True, False),
