diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,13 @@
 
 ## [Unreleased]
 
+## 0.11.0.0 — 2026-10-19
+
+### Added
+
+- Migration `0015-active-memory-recency.sql` adds an active-only memory-space/namespace/creation-time/ID index. It builds inside the migration transaction with a SHARE lock: reads continue while writers wait for commit.
+- A previous fourteen-entry Kioku manifest-prefix upgrade test proves only 0015 applies and existing Memory values remain unchanged. The composed plan has 62 entries (15 Kiroku, 32 Keiro, 15 Kioku); all shipped SQL and checksums are preserved and no ledger fixup is needed.
+
 ## 0.10.0.0 — 2026-10-19
 
 ### Changed
diff --git a/kioku-migrations.cabal b/kioku-migrations.cabal
--- a/kioku-migrations.cabal
+++ b/kioku-migrations.cabal
@@ -1,6 +1,6 @@
 cabal-version: 3.0
 name: kioku-migrations
-version: 0.10.0.0
+version: 0.11.0.0
 synopsis: Schema migrations for kioku
 description:
   Owns kioku schema evolution as a native pg-migrate component and composes
@@ -93,7 +93,7 @@
     directory >=1.3 && <1.4,
     ephemeral-pg >=0.3.1 && <0.4,
     keiro-test-support ^>=0.21.0.0,
-    kioku-migrations ^>=0.10.0.0,
+    kioku-migrations ^>=0.11.0.0,
     text >=2.0 && <2.2,
     unix >=2.8 && <2.9,
 
@@ -116,7 +116,7 @@
     containers >=0.6 && <0.8,
     hasql >=1.6,
     keiro-migrations ^>=0.21.0.0,
-    kioku-migrations ^>=0.10.0.0,
+    kioku-migrations ^>=0.11.0.0,
     kioku-migrations:test-support,
     kiroku-store-migrations ^>=0.7.1.0,
     pg-migrate ^>=1.2.0.0,
diff --git a/migrations/0015-active-memory-recency.sql b/migrations/0015-active-memory-recency.sql
new file mode 100644
--- /dev/null
+++ b/migrations/0015-active-memory-recency.sql
@@ -0,0 +1,7 @@
+-- Active recency ignores priority. The namespace page index is priority-first
+-- and therefore requires sorting the whole matching population for this query.
+-- This additive index builds in the migration transaction (not CONCURRENTLY):
+-- reads continue, but writers wait for the build/transaction to finish.
+CREATE INDEX IF NOT EXISTS memories_space_active_recency_idx
+  ON kioku.memories (memory_space_id, namespace, created_at DESC, memory_id ASC)
+  WHERE status = 'active';
diff --git a/migrations/manifest b/migrations/manifest
--- a/migrations/manifest
+++ b/migrations/manifest
@@ -12,3 +12,4 @@
 0012-relocate-projections-to-kioku-schema.sql
 0013-partition-aware-fts-index.sql
 0014-bounded-session-memory-pages.sql
+0015-active-memory-recency.sql
diff --git a/test/Main.hs b/test/Main.hs
--- a/test/Main.hs
+++ b/test/Main.hs
@@ -88,6 +88,7 @@
           testCase "public schema (long-lived dev databases)" (assertRegistryBump "public")
         ],
       testCase "the full migration chain applies to a fresh database" testFreshDatabase,
+      testCase "0.10.0.0 upgrades with one recency index and unchanged rows" testRecencyUpgrade,
       testCase "the released previous cohort preserves data and checkpoints through 0013–0015" testPreviousCohortUpgrade,
       testGroup
         "migration-session isolation"
@@ -462,7 +463,7 @@
   old <- either (assertFailure . show) pure (migrationPlan (oldKiroku :| [keiro, kioku]))
   current <- either (assertFailure . show) pure kiokuMigrationPlan
   installed <- runMigrationPlan defaultRunOptions settings old >>= either (assertFailure . show) pure
-  length (appliedNow installed) @?= 58
+  length (appliedNow installed) @?= 59
   withConnection connStr $ \conn -> do
     run conn (Session.script previousCohortSeed)
     -- Seed all seven application projections using the existing preservation fixture.
@@ -480,12 +481,12 @@
   verification <- verifyMigrationPlan defaultRunOptions settings current >>= either (assertFailure . show) pure
   let VerificationReport {issues, appliedMigrations, pendingMigrations, unknownMigrations} = verification
   issues @?= []
-  length appliedMigrations @?= 61
+  length appliedMigrations @?= 62
   pendingMigrations @?= []
   unknownMigrations @?= []
   repeated <- runMigrationPlan defaultRunOptions settings current >>= either (assertFailure . show) pure
   let MigrationReport {results} = repeated
-  length [() | MigrationResult {outcome = AlreadyApplied} <- toList results] @?= 61
+  length [() | MigrationResult {outcome = AlreadyApplied} <- toList results] @?= 62
   appliedNow repeated @?= []
   query connStr previousCohortSnapshot >>= (@?= before)
   query connStr previousCheckpointSnapshot >>= (@?= checkpointsBefore)
@@ -591,7 +592,7 @@
         runMigrationPlan defaultRunOptions (Settings.connectionString connStr) plan
           >>= either (assertFailure . show) pure
       let MigrationReport {results = repeatedResults} = repeated
-      length [() | MigrationResult {outcome = AlreadyApplied} <- toList repeatedResults] @?= 61
+      length [() | MigrationResult {outcome = AlreadyApplied} <- toList repeatedResults] @?= 62
       length [() | MigrationResult {outcome = AppliedNow} <- toList repeatedResults] @?= 0
       verification <-
         verifyMigrationPlan defaultRunOptions (Settings.connectionString connStr) plan
@@ -710,10 +711,10 @@
     plan <- either (fail . show) pure kiokuMigrationPlan
     let settings = Settings.connectionString connStr
     initial <- runMigrationPlan defaultRunOptions settings plan >>= either (assertFailure . show) pure
-    length (appliedNow initial) @?= 61
+    length (appliedNow initial) @?= 62
 
     baselineLedger <- query connStr fullLedgerSnapshot
-    fst baselineLedger @?= 61
+    fst baselineLedger @?= 62
     baselineSchema <- query connStr cohortSchemaSnapshotStatement
     fixup <- Text.IO.readFile ledgerFixupPath
 
@@ -739,7 +740,7 @@
 
     repeated <- runMigrationPlan defaultRunOptions settings plan >>= either (assertFailure . show) pure
     let MigrationReport {results = repeatedResults} = repeated
-    length [() | MigrationResult {outcome = AlreadyApplied} <- toList repeatedResults] @?= 61
+    length [() | MigrationResult {outcome = AlreadyApplied} <- toList repeatedResults] @?= 62
     length [() | MigrationResult {outcome = AppliedNow} <- toList repeatedResults] @?= 0
 
     withConnection connStr \conn -> run conn (Session.script fixup)
@@ -1210,7 +1211,7 @@
     adoption <- runMigrationPlan defaultRunOptions settings full >>= either (assertFailure . show) pure
     let MigrationReport {results = adoptionResults} = adoption
     length [() | MigrationResult {outcome = AlreadyApplied} <- toList adoptionResults] @?= 15
-    length [() | MigrationResult {outcome = AppliedNow} <- toList adoptionResults] @?= 46
+    length [() | MigrationResult {outcome = AppliedNow} <- toList adoptionResults] @?= 47
 
     -- Verified and skipped, never re-executed: the stored rows keep their checksums and their
     -- original application timestamps.
@@ -1223,7 +1224,7 @@
     verification <- verifyMigrationPlan defaultRunOptions settings full >>= either (assertFailure . show) pure
     let VerificationReport {issues = adoptionIssues, appliedMigrations, pendingMigrations, unknownMigrations} = verification
     adoptionIssues @?= []
-    length appliedMigrations @?= 61
+    length appliedMigrations @?= 62
     pendingMigrations @?= []
     unknownMigrations @?= []
 
@@ -1574,13 +1575,13 @@
     verification <- verifyMigrationPlan defaultRunOptions settings plan >>= either (assertFailure . show) pure
     let VerificationReport {issues = verificationIssues, appliedMigrations, pendingMigrations, unknownMigrations} = verification
     verificationIssues @?= []
-    length appliedMigrations @?= 61
+    length appliedMigrations @?= 62
     pendingMigrations @?= []
     unknownMigrations @?= []
 
     repeated <- runMigrationPlan defaultRunOptions settings plan >>= either (assertFailure . show) pure
     let MigrationReport {results = repeatedResults} = repeated
-    length [() | MigrationResult {outcome = AlreadyApplied} <- toList repeatedResults] @?= 61
+    length [() | MigrationResult {outcome = AlreadyApplied} <- toList repeatedResults] @?= 62
     length [() | MigrationResult {outcome = AppliedNow} <- toList repeatedResults] @?= 0
 
 testSchemaConvergence :: Assertion
@@ -1626,7 +1627,7 @@
     use connStr
 
 expectedKiokuSchemaChecksum :: Text
-expectedKiokuSchemaChecksum = "54fa912ca9f6332cb30ed604eaca9e4fdf25d38c09efc74bd1a5b5760273a112"
+expectedKiokuSchemaChecksum = "793da81763342806e2e29ea71b4fe17b5d620052dca23be355b8602bc61d6fbc"
 
 kiokuSchemaSnapshotStatement :: Statement () Text
 kiokuSchemaSnapshotStatement =
@@ -1770,7 +1771,8 @@
           migrationId "kioku" "0011-kioku-memory-space-partition",
           migrationId "kioku" "0012-relocate-projections-to-kioku-schema",
           migrationId "kioku" "0013-partition-aware-fts-index",
-          migrationId "kioku" "0014-bounded-session-memory-pages"
+          migrationId "kioku" "0014-bounded-session-memory-pages",
+          migrationId "kioku" "0015-active-memory-recency"
         ]
 
 expectRight :: (Show error) => Either error value -> value
@@ -1847,3 +1849,51 @@
 
 query :: Text -> Statement () a -> IO a
 query connStr statement = withConnection connStr \conn -> run conn (Session.statement () statement)
+
+-- The previous released manifest prefix is preserved byte-for-byte. Only the
+-- appended index runs when a data-bearing 0.10.0.0 database upgrades.
+testRecencyUpgrade :: Assertion
+testRecencyUpgrade = withBareDatabase $ \connStr -> do
+  let settings = Settings.connectionString connStr
+  entries <- checkMigrationManifest "migrations/manifest" >>= either (assertFailure . show) pure
+  let prefix = takeWhile ((/= "0015-active-memory-recency.sql") . fst) (toList entries)
+  length prefix @?= 14
+  oldKioku <- either (assertFailure . show) pure (migrationComponentFromEmbeddedSql "kioku" (Set.singleton "keiro") (case prefix of first : rest -> first :| rest; [] -> error "empty historical prefix"))
+  kiroku <- either (assertFailure . show) pure KirokuMigrations.kirokuMigrations
+  keiro <- either (assertFailure . show) pure KeiroMigrations.keiroMigrations
+  old <- either (assertFailure . show) pure (migrationPlan (kiroku :| [keiro, oldKioku]))
+  current <- either (assertFailure . show) pure kiokuMigrationPlan
+  initial <- runMigrationPlan defaultRunOptions settings old >>= either (assertFailure . show) pure
+  length (appliedNow initial) @?= 61
+  withConnection connStr $ \conn ->
+    run
+      conn
+      ( Session.script
+          """
+          INSERT INTO kioku.memories
+            (memory_space_id,memory_id,agent_id,namespace,memory_type,content,priority,confidence,tags,status,created_at,updated_at)
+          VALUES ('upgrade-space','upgrade-memory','upgrade','upgrade','fact','complete 記憶',50,'high','["tag","長い"]','active','2026-01-01','2026-01-01');
+          """
+      )
+  let snapshot = preparable "SELECT row_to_json(m)::text FROM kioku.memories m WHERE memory_id='upgrade-memory'" E.noParams (D.singleRow (D.column (D.nonNullable D.text)))
+  before <- query connStr snapshot
+  oldSchema <- query connStr kiokuSchemaSnapshotStatement
+  checksumHex (Text.Encoding.encodeUtf8 oldSchema) @?= "54fa912ca9f6332cb30ed604eaca9e4fdf25d38c09efc74bd1a5b5760273a112"
+  migrated <- runMigrationPlan defaultRunOptions settings current >>= either (assertFailure . show) pure
+  appliedNow migrated @?= [expectRight (migrationId "kioku" "0015-active-memory-recency")]
+  query connStr snapshot >>= (@?= before)
+  newSchema <- query connStr kiokuSchemaSnapshotStatement
+  let oldFacts = Text.lines oldSchema
+      newFacts = Text.lines newSchema
+      addedFacts = filter (`notElem` oldFacts) newFacts
+  length addedFacts @?= 1
+  assertBool "only the recency index is added" (all (Text.isPrefixOf "index|memories|memories_space_active_recency_idx|") addedFacts)
+  filter (`notElem` addedFacts) newFacts @?= oldFacts
+  checksumHex (Text.Encoding.encodeUtf8 newSchema) @?= expectedKiokuSchemaChecksum
+  indexExists <- query connStr (preparable "SELECT to_regclass('kioku.memories_space_active_recency_idx') IS NOT NULL" E.noParams (D.singleRow (D.column (D.nonNullable D.bool))))
+  indexExists @?= True
+  repeated <- runMigrationPlan defaultRunOptions settings current >>= either (assertFailure . show) pure
+  appliedNow repeated @?= []
+  query connStr snapshot >>= (@?= before)
+  VerificationReport {issues, pendingMigrations, unknownMigrations} <- verifyMigrationPlan defaultRunOptions settings current >>= either (assertFailure . show) pure
+  (issues, pendingMigrations, unknownMigrations) @?= ([], [], [])
