Skip to content

Commit e767d0b

Browse files
authored
Merge pull request #27 from gaelic-ghost/docs/milestone-5-decisions
api: settle semantic persistence API decisions
2 parents 82bff72 + ead4ca1 commit e767d0b

12 files changed

Lines changed: 48 additions & 24 deletions

README.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ import FetchCore
5858
import RAGCore
5959
import SwiftlyFetch
6060

61-
let library = try await SwiftlyFetchLibrary.default()
61+
let library = SwiftlyFetchLibrary.default()
6262

6363
try await library.addDocument(
6464
FetchDocumentRecord(
@@ -81,7 +81,7 @@ For lower-level semantic retrieval, import `RAGCore` and `RAGKit` directly:
8181
import RAGCore
8282
import RAGKit
8383

84-
let kb = try await KnowledgeBase.hashingDefault()
84+
let kb = KnowledgeBase.hashingDefault()
8585

8686
try await kb.addDocument(
8787
Document(
@@ -113,7 +113,7 @@ import FetchCore
113113
import RAGCore
114114
import SwiftlyFetch
115115

116-
let library = try await SwiftlyFetchLibrary.default()
116+
let library = SwiftlyFetchLibrary.default()
117117

118118
let mutation = try await library.addDocument(
119119
FetchDocumentRecord(
@@ -142,7 +142,7 @@ For semantic retrieval, use `KnowledgeBase` from `RAGKit`:
142142
import RAGCore
143143
import RAGKit
144144

145-
let localKB = try await KnowledgeBase.hashingDefault()
145+
let localKB = KnowledgeBase.hashingDefault()
146146
let appleKB = try await KnowledgeBase.naturalLanguageDefault(languageHint: "en")
147147
let semanticStore = FileManager.default
148148
.temporaryDirectory
@@ -205,9 +205,10 @@ Current defaults:
205205
- plain text uses paragraph chunking
206206
- markdown uses parser-backed heading-aware chunking
207207
- markdown link destinations stay out of chunk text by default, but `HeadingAwareMarkdownChunker(linkDestinationMetadataMode: .include)` can record raw destinations in chunk metadata when downstream indexing or fetch-oriented work needs them
208-
- `hashingDefault()` gives a deterministic local path for tests and examples
209-
- `naturalLanguageDefault()` uses the Apple Natural Language backend on supported platforms
208+
- `hashingDefault()` gives a synchronous deterministic local path for tests and examples
209+
- `naturalLanguageDefault()` uses the Apple Natural Language backend on supported platforms and remains throwing because backend setup can fail
210210
- `persistentHashingDefault(configuration:dimension:)` and `persistentNaturalLanguageDefault(configuration:languageHint:)` use the same retrieval defaults with a Core Data-backed semantic vector index
211+
- `SwiftlyFetchLibrary.default()` is synchronous and non-throwing because it uses deterministic in-memory defaults; persistent constructors remain async and throwing
211212
- metadata filtering supports explicit exclusions, ordered comparisons for `int`, `double`, and `date`, plus case-insensitive `startsWith` and `endsWith` string matching
212213
- markdown list items keep heading and immediate lead-in context in chunk text, and also carry structured chunk metadata for list kind, lead-in, ordinal, and heading path
213214
- markdown block quotes stay secondary by default, but are promoted into the primary retrieval stream when they make up more than one third of the document's chunkable block structure

ROADMAP.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -228,9 +228,9 @@ In Progress
228228
- [x] Plan the first `SwiftlyFetch` umbrella facade in maintainer docs.
229229
- [x] Add a narrow bridge from `FetchDocumentRecord` to `RAGCore.Document`.
230230
- [x] Add an umbrella ingestion surface only after the semantic persisted index is stable.
231-
- [ ] Decide whether the remaining Core Data vector-index XCTest coverage should migrate to Swift Testing now that the current XCTest path is stable.
232-
- [ ] Decide whether `KnowledgeBase.hashingDefault(dimension:)` should keep its current `async throws` shape for API consistency or become a synchronous non-throwing convenience in a future API polish pass.
233-
- [ ] Decide when to optimize `CoreDataVectorIndex.search` beyond the current pragmatic v1 in-memory ranking path, such as by using Core Data fetch batching, predicate pre-filtering, or a future ANN-backed query path.
231+
- [x] Keep the remaining Core Data vector-index coverage on XCTest because the framework-heavy Core Data lanes are stable there, while Swift Testing stays the default for ordinary package behavior.
232+
- [x] Make `KnowledgeBase.hashingDefault(dimension:)` and `SwiftlyFetchLibrary.default()` synchronous and non-throwing because their deterministic in-memory construction paths have no async or throwing setup work.
233+
- [x] Defer `CoreDataVectorIndex.search` optimization beyond the current pragmatic v1 in-memory ranking path until real corpus scale shows a need; likely future paths include Core Data fetch batching, predicate pre-filtering, or a future ANN-backed query path.
234234

235235
### Exit Criteria
236236

Sources/RAGKit/KnowledgeBase+NaturalLanguage.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import RAGCore
22

33
public extension KnowledgeBase {
4-
static func hashingDefault(dimension: Int = 64) async throws -> KnowledgeBase {
4+
static func hashingDefault(dimension: Int = 64) -> KnowledgeBase {
55
KnowledgeBase(
66
chunker: DefaultChunker(),
77
embedder: HashingEmbedder(dimension: dimension),

Sources/SwiftlyFetch/SwiftlyFetchLibrary.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ public actor SwiftlyFetchLibrary {
2222
self.documentMapper = documentMapper
2323
}
2424

25-
public static func `default`() async throws -> SwiftlyFetchLibrary {
26-
try await SwiftlyFetchLibrary(
25+
public static func `default`() -> SwiftlyFetchLibrary {
26+
SwiftlyFetchLibrary(
2727
fetchLibrary: .default(),
2828
knowledgeBase: .hashingDefault(),
2929
retryStore: InMemorySwiftlyFetchSemanticRetryStore()

Tests/FetchKitTests/CoreDataFetchDocumentStoreTests.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ import Foundation
33
import XCTest
44
@testable import FetchKit
55

6+
// Keep the Core Data-backed document-store coverage on XCTest. These tests exercise
7+
// framework-heavy persistence paths, and XCTest has been the stable runner for this
8+
// repository's Core Data lanes after Swift Testing exposed executor-assumption issues.
69
final class CoreDataFetchDocumentStoreTests: XCTestCase {
710
func testCoreDataFetchDocumentStoreRoundTripsRecord() async throws {
811
let store = try await CoreDataFetchDocumentStore()

Tests/RAGKitTests/CoreDataVectorIndexTests.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ import RAGCore
22
@testable import RAGKit
33
import XCTest
44

5+
// Keep the Core Data-backed semantic index coverage on XCTest. These tests exercise
6+
// framework-heavy persistence paths, and XCTest has been the stable runner for this
7+
// repository's Core Data lanes after Swift Testing exposed executor-assumption issues.
58
final class CoreDataVectorIndexTests: XCTestCase {
69
func testCoreDataVectorIndexPersistsChunksAcrossReopen() async throws {
710
let storeURL = temporaryStoreURL()

Tests/RAGKitTests/KnowledgeBaseTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -891,7 +891,7 @@ struct KnowledgeBaseRetrievalTests {
891891

892892
@Test("KnowledgeBase hashingDefault uses heading-aware markdown chunking by default")
893893
func knowledgeBaseHashingDefaultPrefersMarkdownAwareChunking() async throws {
894-
let knowledgeBase = try await KnowledgeBase.hashingDefault(dimension: 32)
894+
let knowledgeBase = KnowledgeBase.hashingDefault(dimension: 32)
895895

896896
try await knowledgeBase.addDocument(
897897
Document(

Tests/SwiftlyFetchTests/SwiftlyFetchLibraryTests.swift

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import Testing
1111
struct SwiftlyFetchLibraryTests {
1212
@Test("Default facade ingests one document into conventional and semantic search")
1313
func defaultFacadeIngestsOneDocumentIntoBothSearchModes() async throws {
14-
let library = try await SwiftlyFetchLibrary.default()
14+
let library = SwiftlyFetchLibrary.default()
1515
let record = FetchDocumentRecord(
1616
id: "doc-apple",
1717
title: "Apple Guide",
@@ -82,9 +82,9 @@ struct SwiftlyFetchLibraryTests {
8282
reason: "Test retry"
8383
)
8484
)
85-
let library = try SwiftlyFetchLibrary(
85+
let library = SwiftlyFetchLibrary(
8686
fetchLibrary: fetchLibrary,
87-
knowledgeBase: await KnowledgeBase.hashingDefault(),
87+
knowledgeBase: KnowledgeBase.hashingDefault(),
8888
retryStore: retryStore
8989
)
9090

@@ -132,9 +132,9 @@ struct SwiftlyFetchLibraryTests {
132132
nextRetryAt: Date().addingTimeInterval(-60)
133133
)
134134
)
135-
let library = try SwiftlyFetchLibrary(
135+
let library = SwiftlyFetchLibrary(
136136
fetchLibrary: fetchLibrary,
137-
knowledgeBase: await KnowledgeBase.hashingDefault(),
137+
knowledgeBase: KnowledgeBase.hashingDefault(),
138138
retryStore: retryStore
139139
)
140140

@@ -262,9 +262,9 @@ struct SwiftlyFetchLibraryTests {
262262
reason: "Test retry"
263263
)
264264
)
265-
let library = try SwiftlyFetchLibrary(
265+
let library = SwiftlyFetchLibrary(
266266
fetchLibrary: FetchKitLibrary(),
267-
knowledgeBase: await KnowledgeBase.hashingDefault(),
267+
knowledgeBase: KnowledgeBase.hashingDefault(),
268268
retryStore: retryStore
269269
)
270270

@@ -397,7 +397,7 @@ struct SwiftlyFetchLibraryTests {
397397
}
398398

399399
private func indexedFixtureLibrary() async throws -> SwiftlyFetchLibrary {
400-
let library = try await SwiftlyFetchLibrary.default()
400+
let library = SwiftlyFetchLibrary.default()
401401

402402
for record in GutenbergMiniCorpus.records + TinyStoriesMiniCorpus.records {
403403
try await library.addDocument(record)

docs/maintainers/hybrid-search-persistence-plan.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,20 @@ The first convenience constructors are:
112112

113113
These constructors keep the same chunker and embedder defaults as the in-memory defaults while swapping in the Core Data-backed vector index.
114114

115+
The in-memory deterministic hashing constructor is intentionally synchronous and non-throwing:
116+
117+
- `KnowledgeBase.hashingDefault(dimension:)`
118+
119+
That constructor only assembles `DefaultChunker`, `HashingEmbedder`, and `InMemoryVectorIndex`, so it should not ask callers to write `try await`. Keep `naturalLanguageDefault(...)` throwing because the Apple Natural Language embedder can fail during setup, and keep the persistent constructors async/throwing because Core Data-backed vector index setup awaits persistent-store loading and can fail.
120+
121+
For the same reason, the umbrella `SwiftlyFetchLibrary.default()` constructor is synchronous and non-throwing while it uses only in-memory deterministic dependencies. Keep `SwiftlyFetchLibrary.macOSPersistentLibrary(...)` async/throwing because it opens persistent Core Data stores and may retry pending index work.
122+
123+
## Validation And Search Optimization Decisions
124+
125+
Keep the Core Data-backed semantic vector-index tests on XCTest for now. Swift Testing remains the default for ordinary package behavior, but the Core Data persistence lanes are framework-heavy and XCTest has been the stable runner after earlier Swift Testing executor-assumption failures in this repository's Core Data-backed validation. Treat this as an intentional validation boundary, not forgotten migration work.
126+
127+
Keep `CoreDataVectorIndex.search` on the current pragmatic v1 path until real corpus scale shows that it is too slow or too memory-heavy. The current backend loads persisted chunks, decodes embeddings, applies metadata filters, and ranks in memory behind the `VectorIndex` protocol. That is simple and correct for the current package stage. Likely future optimization paths are Core Data fetch batching, predicate pre-filtering for metadata filters, or a separate ANN-backed vector backend if corpus size eventually earns that extra storage/query complexity.
128+
115129
## Follow-Up Design Work
116130

117131
The next architecture work should focus on shared corpus ingestion rather than another standalone index backend. The detailed umbrella plan lives in [swiftlyfetch-facade-plan.md](./swiftlyfetch-facade-plan.md).

docs/maintainers/retrieval-package-plan.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ Implemented today:
158158
- `KnowledgeBase`
159159
- `NaturalLanguageEmbedder`
160160
- `AppleContextualEmbeddingBackend`
161-
- convenience constructors for `hashingDefault()`, `naturalLanguageDefault()`, `persistentHashingDefault(configuration:dimension:)`, and `persistentNaturalLanguageDefault(configuration:languageHint:)`
161+
- convenience constructors for synchronous deterministic `hashingDefault()`, throwing `naturalLanguageDefault()`, persistent `persistentHashingDefault(configuration:dimension:)`, and persistent `persistentNaturalLanguageDefault(configuration:languageHint:)`
162162
- semantic index persistence now exists as a `RAGKit`-owned derived store through `CoreDataVectorIndex`, keeping semantic chunks and embeddings behind the existing `VectorIndex` protocol instead of pushing vector-storage concerns into `FetchKit`
163163
- persisted semantic index health now exists as a `RAGKit` concern through document-level status and fingerprints, while retry scheduling remains reserved for the future umbrella ingestion surface
164164
- markdown chunking now uses a parser-backed internal section model built on [swift-markdown](https://github.com/swiftlang/swift-markdown) instead of the earlier line-based heading scanner

0 commit comments

Comments
 (0)