update .agents
Seto Elkahfi committed
Apr 2, 2026 at 17:02 UTC
a2d0f139e515a430d953e519aaa172750e102c0d
6 files changed
+508
.agents/AGENTS.md
new
+506
@@ -0,0 +1,506 @@
1
+# Onde Inference — AGENTS.md
2
+
3
+> **AI agent reference for the `onde` repository and the `onde-swift` Swift SDK.**
4
+> Keep this file accurate as the codebase evolves.
5
+
6
+---
7
+
8
+## What Is Onde?
9
+
10
+Onde is an **on-device LLM inference SDK** targeting Apple silicon (iOS, tvOS, macOS) plus Android, Windows, and Linux. It wraps [mistral.rs](https://github.com/EricLBuehler/mistral.rs) behind a clean, platform-aware API — automatic model selection, HuggingFace Hub downloads, cache management, and GPU acceleration via Metal.
11
+
12
+- **Website:** https://ondeinference.com
13
+- **Rust crate:** https://crates.io/crates/onde
14
+- **Swift package:** https://github.com/ondeinference/onde-swift
15
+- **In production:** [Splitfire AB apps on the Apple AppStore](https://apps.apple.com/se/developer/splitfire-ab/id1831430993)
16
+
17
+---
18
+
19
+## Repository Layout
20
+
21
+```
22
+onde/
23
+├── src/
24
+│ ├── lib.rs # Crate root — uniffi::setup_scaffolding!()
25
+│ ├── hf_cache.rs # HuggingFace Hub cache: list, download, delete, diagnose, repair
26
+│ └── inference/
27
+│ ├── mod.rs # Module exports + top-level re-exports
28
+│ ├── engine.rs # ChatEngine — Rust-native API (generics, mpsc)
29
+│ ├── ffi.rs # OndeChatEngine — UniFFI Object (FFI-safe, Arc-wrapped)
30
+│ ├── models.rs # Model ID constants + SupportedModelInfo metadata
31
+│ ├── token.rs # HF token resolution: build-time literal vs cache file
32
+│ └── types.rs # All shared types: ChatMessage, SamplingConfig, InferenceResult, etc.
33
+├── sdk/
34
+│ ├── Onde/ # Generated Swift package output (git-ignored; built by CI)
35
+│ ├── gem/ # Ruby native extension (Magnus)
36
+│ ├── kotlin/ # Kotlin/Android bindings
37
+│ └── python/ # Python bindings (maturin + uniffi)
38
+├── generated/ # UniFFI-generated headers and Swift glue (git-ignored)
39
+├── uniffi-bindgen/ # Standalone bindgen binary crate (pinned uniffi =0.31.0)
40
+├── .github/
41
+│ ├── workflows/
42
+│ │ └── build-swift-xcframework.yml # CI: builds + uploads OndeFramework.xcframework
43
+│ └── scripts/
44
+│ └── build-swift-xcframework.sh # Local/CI XCFramework assembly script
45
+├── .cargo/config.toml # Target-specific rustflags (fp16, linker overrides)
46
+├── Cargo.toml # Platform-conditional mistralrs deps
47
+├── build.rs # tvOS ___chkstk_darwin assembly stub
48
+├── tvos_chkstk.s # No-op arm64 stub for missing tvOS symbol
49
+├── uniffi.toml # UniFFI binding config
50
+└── docs/
51
+ ├── dev.md # Developer guide (build, architecture, platform table)
52
+ ├── swift-package.md # Swift API reference + XCFramework build steps
53
+ ├── distribution.md # Release process for all registries
54
+ └── ruby-gem.md # Ruby gem API reference
55
+```
56
+
57
+```
58
+onde-swift/ # Swift Package Manager wrapper repo
59
+├── Package.swift # Declares OndeFramework.xcframework binary target
60
+└── Sources/
61
+ └── Onde/
62
+ └── onde.swift # UniFFI-generated Swift glue (do NOT edit manually)
63
+```
64
+
65
+---
66
+
67
+## Architecture
68
+
69
+### Layer Diagram
70
+
71
+```
72
+┌──────────────────────────────────────────────────────────┐
73
+│ Swift (iOS / tvOS / macOS) Rust app / CLI / server │
74
+│ import Onde use onde::inference::* │
75
+└──────────┬───────────────────────────────┬───────────────┘
76
+ │ UniFFI FFI bindings │ Direct Rust API
77
+ ▼ ▼
78
+┌──────────────────────────┐ ┌─────────────────────────────┐
79
+│ OndeChatEngine │ │ ChatEngine │
80
+│ (ffi.rs — uniffi::Object│──│ (engine.rs — Rust-native) │
81
+│ FFI-safe, Arc<Self>) │ │ generics, mpsc channels │
82
+└──────────┬───────────────┘ └─────────────┬───────────────┘
83
+ │ │
84
+ └────────────────┬───────────────┘
85
+ ▼
86
+ ┌────────────────────────┐
87
+ │ mistralrs::Model │
88
+ │ GgufModelBuilder │
89
+ │ Metal / CUDA / CPU │
90
+ └────────────────────────┘
91
+```
92
+
93
+### Key Design Rules
94
+
95
+1. **`ChatEngine`** (`engine.rs`) owns all Rust-idiomatic logic: `impl Into<String>`, `tokio::sync::mpsc::Receiver`, etc. Never add UniFFI annotations here.
96
+2. **`OndeChatEngine`** (`ffi.rs`) is a thin `Arc`-wrapped UniFFI `Object` with concrete, FFI-safe signatures. It delegates every call to the inner `ChatEngine`.
97
+3. **`StreamChunkListener`** is a `#[uniffi::export(callback_interface)]` trait — it must be passed as a parameter to **free functions** (not Object methods) because UniFFI 0.31 does not support callback_interface on Object methods.
98
+4. **Free functions** in `ffi.rs` (e.g. `stream_chat_message`, `default_model_config`, `user_message`) are exported with `#[uniffi::export]` and generate top-level Swift functions.
99
+5. **`uniffi::setup_scaffolding!()`** lives in `lib.rs`. Never call `uniffi::generate_scaffolding!()` from `build.rs` for the proc-macro path.
100
+
101
+---
102
+
103
+## Platform Support
104
+
105
+| Platform | `target_os` | GPU Backend | Default Model | mistralrs features |
106
+| -------- | ----------- | ----------------- | --------------------- | -------------------|
107
+| macOS | `macos` | Metal | Qwen 2.5 3B (~1.93 GB) | `["metal"]` |
108
+| iOS | `ios` | Metal | Qwen 2.5 1.5B (~941 MB)| `["metal"]` |
109
+| tvOS | `tvos` | Metal | Qwen 2.5 1.5B (~941 MB)| `["metal"]` |
110
+| Android | `android` | CPU (candle) | Qwen 2.5 1.5B (~941 MB)| `[]` + hf-hub |
111
+| Windows | `windows` | CPU (CUDA in CI) | Qwen 2.5 3B (~1.93 GB) | `[]` |
112
+| Linux | `linux` | CPU (CUDA in CI) | Qwen 2.5 3B (~1.93 GB) | `[]` |
113
+
114
+- **iOS / tvOS memory constraint:** iOS gives apps ~2–3 GB; the 3B model (~1.93 GB) causes OOM on iPhone 16e (8 GB RAM). Always default to the 1.5B on iOS/tvOS/Android.
115
+- **tvOS tier-3 target:** requires `cargo +nightly -Z build-std`. Stable toolchain cannot build tvOS targets.
116
+- **Android `hf_hub`:** `dirs::home_dir()` panics in the Android sandbox. `hf-hub` is added as an explicit dep on Android so `HF_HOME` can be seeded programmatically via `hf_hub::api::tokio::ApiBuilder`.
117
+
118
+---
119
+
120
+## Supported Models
121
+
122
+All model constants live in `src/inference/models.rs`. When adding a new model:
123
+
124
+1. Add `pub const` entries for the HF repo ID, GGUF filename, and (on Android) `TOK_MODEL_ID`.
125
+2. Add the repo ID to `SUPPORTED_MODELS` so `list_local_hf_models` filters it.
126
+3. Add a `SupportedModelInfo` entry to `SUPPORTED_MODEL_INFO` with accurate `expected_size_bytes` (from HF API `siblings[].size`).
127
+4. Add a constructor to `GgufModelConfig` in `engine.rs` (see `qwen25_1_5b`, `qwen25_3b` for pattern).
128
+5. Export a free function in `ffi.rs` (see `qwen25_1_5b_config`, `qwen25_3b_config`).
129
+
130
+### Current Models
131
+
132
+| Model | Repo | File | Size | Platforms |
133
+|-------|------|------|------|-----------|
134
+| Qwen 2.5 1.5B (GGUF Q4_K_M) | `bartowski/Qwen2.5-1.5B-Instruct-GGUF` | `Qwen2.5-1.5B-Instruct-Q4_K_M.gguf` | ~941 MB | iOS, tvOS, Android (default) |
135
+| Qwen 2.5 3B (GGUF Q4_K_M) | `bartowski/Qwen2.5-3B-Instruct-GGUF` | `Qwen2.5-3B-Instruct-Q4_K_M.gguf` | ~1.93 GB | macOS, Windows, Linux (default) |
136
+| Qwen 2.5 Coder 1.5B (GGUF) | `bartowski/Qwen2.5-Coder-1.5B-Instruct-GGUF` | `Qwen2.5-Coder-1.5B-Instruct-Q4_K_M.gguf` | ~941 MB | All platforms |
137
+| Qwen 2.5 Coder 3B (GGUF) | `bartowski/Qwen2.5-Coder-3B-Instruct-GGUF` | `Qwen2.5-Coder-3B-Instruct-Q4_K_M.gguf` | ~1.93 GB | macOS |
138
+| Qwen 2.5 Coder 7B (ISQ) | `Qwen/Qwen2.5-Coder-7B-Instruct` | safetensors (ISQ in-situ) | ~8 GB | macOS (ISQ pipeline) |
139
+
140
+---
141
+
142
+## Key Types (Rust ↔ Swift)
143
+
144
+| Rust Type | Swift Type | Notes |
145
+|-----------|------------|-------|
146
+| `ChatRole` | `enum ChatRole` | `.system`, `.user`, `.assistant` |
147
+| `ChatMessage` | `struct ChatMessage` | `role: ChatRole`, `content: String` |
148
+| `SamplingConfig` | `struct SamplingConfig` | All fields `Optional` |
149
+| `GgufModelConfig` | `struct GgufModelConfig` | `modelId`, `files`, `tokModelId?`, `displayName`, `approxMemory` |
150
+| `IsqModelConfig` | `struct IsqModelConfig` | `modelId`, `isqBits: UInt8`, `displayName`, `approxMemory` |
151
+| `InferenceResult` | `struct InferenceResult` | `text`, `durationSecs`, `durationDisplay`, `finishReason` |
152
+| `StreamChunk` | `struct StreamChunk` | `delta`, `done`, `finishReason?` |
153
+| `EngineStatus` | `enum EngineStatus` | `.unloaded`, `.loading`, `.ready`, `.generating`, `.error` |
154
+| `EngineInfo` | `struct EngineInfo` | `status`, `modelName?`, `approxMemory?`, `historyLength: UInt64` |
155
+| `InferenceError` | `enum InferenceError: Error` | `noModelLoaded`, `alreadyLoaded`, `modelBuild`, `inference`, `cancelled`, `other` |
156
+| `OndeChatEngine` | `class OndeChatEngine` | Thread-safe; `Arc`-backed; constructed with `OndeChatEngine()` |
157
+| `StreamChunkListener` | `protocol StreamChunkListener` | Implement `onChunk(chunk:) -> Bool` |
158
+
159
+---
160
+
161
+## HuggingFace Token
162
+
163
+`src/inference/token.rs` resolves the HF token in priority order:
164
+
165
+1. **Build-time literal** (`HF_TOKEN` env var baked in via `option_env!`) — required for iOS/tvOS (no filesystem token possible).
166
+2. **On-disk cache** (`~/.cache/huggingface/token`) — works on macOS after `mistralrs login`.
167
+
168
+Set `HF_TOKEN` before building:
169
+
170
+```bash
171
+export HF_TOKEN=hf_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
172
+cargo build --release
173
+```
174
+
175
+On sandboxed platforms, your app's setup function must also configure `HF_HOME`, `HF_HUB_CACHE`, and `TMPDIR` to point inside the app container before any `OndeChatEngine` method is called. See `docs/swift-package.md` for the full `setupInferenceEnvironment()` Swift snippet.
176
+
177
+---
178
+
179
+## Building (Rust)
180
+
181
+### Prerequisites
182
+
183
+```bash
184
+# Stable toolchain (macOS, iOS, Android, Windows, Linux)
185
+rustup toolchain install stable
186
+
187
+# Nightly toolchain (tvOS tier-3 targets only)
188
+rustup toolchain install nightly
189
+rustup component add rust-src --toolchain nightly
190
+
191
+# Apple targets (stable)
192
+rustup target add aarch64-apple-ios aarch64-apple-ios-sim
193
+rustup target add aarch64-apple-darwin x86_64-apple-darwin
194
+```
195
+
196
+### Commands
197
+
198
+```bash
199
+# Verify compilation (macOS host)
200
+cargo check
201
+
202
+# Run all tests
203
+cargo test
204
+
205
+# Run inference module tests only
206
+cargo test inference::
207
+
208
+# Run FFI wrapper tests only
209
+cargo test inference::ffi::
210
+
211
+# Clippy lint
212
+cargo clippy
213
+
214
+# Format
215
+cargo fmt
216
+```
217
+
218
+### tvOS
219
+
220
+tvOS targets require nightly and `-Z build-std`:
221
+
222
+```bash
223
+cargo +nightly rustc -Z build-std \
224
+ --target aarch64-apple-tvos --release --lib --crate-type staticlib
225
+```
226
+
227
+---
228
+
229
+## Building the Swift XCFramework
230
+
231
+The XCFramework bundles staticlibs for iOS device, iOS simulator, tvOS device, tvOS simulator, and macOS into a single distributable that `Package.swift` references.
232
+
233
+### Quick build (local or CI)
234
+
235
+```bash
236
+.github/scripts/build-swift-xcframework.sh
237
+```
238
+
239
+Output lands in `dist/swift/`:
240
+- `OndeFramework.xcframework/` — the framework tree
241
+- `OndeFramework.xcframework.zip` — zipped for SPM remote binary
242
+- `OndeFramework.checksum.txt` — SHA-256 for `Package.swift`
243
+- `version.txt` — semver from `Cargo.toml`
244
+- `Package/Sources/Onde/onde.swift` — generated UniFFI Swift glue
245
+
246
+### Manual steps
247
+
248
+```bash
249
+# 1. Build the uniffi-bindgen binary (pinned to uniffi =0.31.0)
250
+cargo build --manifest-path uniffi-bindgen/Cargo.toml --release
251
+BINDGEN=uniffi-bindgen/target/release/uniffi-bindgen
252
+
253
+# 2. Compile staticlibs per target (use +1.92.0 or current stable)
254
+cargo +stable rustc --target aarch64-apple-ios --release --lib --crate-type staticlib
255
+cargo +stable rustc --target aarch64-apple-ios-sim --release --lib --crate-type staticlib
256
+cargo +stable rustc --target aarch64-apple-darwin --release --lib --crate-type staticlib
257
+cargo +nightly rustc -Z build-std --target aarch64-apple-tvos --release --lib --crate-type staticlib
258
+cargo +nightly rustc -Z build-std --target aarch64-apple-tvos-sim --release --lib --crate-type staticlib
259
+
260
+# 3. Generate Swift bindings from the iOS arm64 slice
261
+$BINDGEN generate target/aarch64-apple-ios/release/libonde.a \
262
+ --language swift \
263
+ --out-dir sdk/Onde/Sources/Onde \
264
+ --config uniffi.toml
265
+
266
+# 4. Assemble the XCFramework (see build-swift-xcframework.sh for full xcodebuild invocation)
267
+```
268
+
269
+### Updating `onde-swift` after a new release
270
+
271
+1. Run the build script and collect `OndeFramework.xcframework.zip` + checksum.
272
+2. Create a GitHub Release on the `onde` repo and attach the zip.
273
+3. In `onde-swift/Package.swift`, update the `.binaryTarget` to use the new `url:` and `checksum:`.
274
+4. Commit, tag (`0.x.y`), and push `onde-swift`.
275
+
276
+---
277
+
278
+## Swift SDK (`onde-swift`)
279
+
280
+### Package.swift structure
281
+
282
+```
283
+onde-swift/
284
+├── Package.swift # Declares OndeFramework binary target + Onde wrapper target
285
+└── Sources/Onde/
286
+ └── onde.swift # UniFFI-generated glue (NEVER edit manually)
287
+```
288
+
289
+`Package.swift` has two targets:
290
+
291
+- **`OndeFramework`** — `.binaryTarget` pointing at `OndeFramework.xcframework` (local path during dev, remote URL+checksum for distribution).
292
+- **`Onde`** — `.target` depending on `OndeFramework`, used as the public import name in Swift.
293
+
294
+### Swift API Quick Reference
295
+
296
+```swift
297
+import Onde
298
+
299
+// Create engine
300
+let engine = OndeChatEngine()
301
+
302
+// Load model (platform-aware default)
303
+let elapsed = try await engine.loadDefaultModel(
304
+ systemPrompt: "You are a helpful assistant.",
305
+ sampling: nil
306
+)
307
+
308
+// Multi-turn chat
309
+let result = try await engine.sendMessage(message: "Hello!")
310
+print(result.text)
311
+
312
+// Streaming (free function — callback_interface constraint in UniFFI 0.31)
313
+class Handler: StreamChunkListener {
314
+ func onChunk(chunk: StreamChunk) -> Bool {
315
+ print(chunk.delta, terminator: "")
316
+ return !chunk.done
317
+ }
318
+}
319
+try await streamChatMessage(engine: engine, message: "Tell me a story.", listener: Handler())
320
+
321
+// One-shot (does NOT modify conversation history)
322
+let enhanced = try await engine.generate(
323
+ messages: [userMessage(content: "Expand: a cat in space")],
324
+ sampling: deterministicSamplingConfig()
325
+)
326
+
327
+// Status
328
+let info = await engine.info() // EngineInfo
329
+
330
+// History management
331
+let history = await engine.history()
332
+let removed = await engine.clearHistory()
333
+await engine.pushHistory(message: userMessage(content: "..."))
334
+
335
+// Cleanup
336
+await engine.unloadModel()
337
+```
338
+
339
+### Free Functions
340
+
341
+| Function | Returns | Notes |
342
+|----------|---------|-------|
343
+| `defaultModelConfig()` | `GgufModelConfig` | Platform-aware (1.5B on iOS/tvOS, 3B on macOS) |
344
+| `qwen251_5bConfig()` | `GgufModelConfig` | Forces 1.5B regardless of platform |
345
+| `qwen253bConfig()` | `GgufModelConfig` | Forces 3B regardless of platform |
346
+| `defaultSamplingConfig()` | `SamplingConfig` | temp=0.7, top_p=0.95, max_tokens=512 |
347
+| `deterministicSamplingConfig()` | `SamplingConfig` | temp=0.0, greedy |
348
+| `mobileSamplingConfig()` | `SamplingConfig` | temp=0.7, max_tokens=128 |
349
+| `systemMessage(content:)` | `ChatMessage` | `.system` role |
350
+| `userMessage(content:)` | `ChatMessage` | `.user` role |
351
+| `assistantMessage(content:)` | `ChatMessage` | `.assistant` role |
352
+| `streamChatMessage(engine:message:listener:)` | `async throws` | Streaming via callback |
353
+
354
+---
355
+
356
+## UniFFI Conventions
357
+
358
+- **Version pin:** `uniffi = "=0.31.0"` everywhere — the `onde` crate, `uniffi-bindgen/`, and `[build-dependencies]` must all use the **same** version. Mixing versions causes bindgen panics.
359
+- **`uniffi::setup_scaffolding!()`** in `lib.rs` — proc-macro approach, no UDL file needed.
360
+- **Object methods** use `#[uniffi::export]` on the `impl OndeChatEngine` block.
361
+- **Callback interfaces** (`StreamChunkListener`) must be parameters of **free functions** only, not Object methods.
362
+- **`Arc<Self>`** is the return type for `#[uniffi::constructor]`. UniFFI automatically handles this.
363
+- **Async:** all async exported methods use `tokio` runtime (enabled via `uniffi = { features = ["tokio"] }`).
364
+- **`uniffi.toml`**: lives at the crate root. Adjust renaming or namespace settings there before regenerating.
365
+
366
+---
367
+
368
+## HuggingFace Cache (`hf_cache.rs`)
369
+
370
+The `hf_cache` module manages the on-device model cache, exposed via UniFFI to Swift / Kotlin as-needed.
371
+
372
+Key public functions:
373
+
374
+| Function | Description |
375
+|----------|-------------|
376
+| `list_local_hf_models()` | List downloaded models that Onde supports |
377
+| `list_supported_hf_models()` | All supported models with download status |
378
+| `download_model(model_id, progress_callback)` | Download a model with progress reporting |
379
+| `delete_local_hf_model(model_id)` | Remove a model from the local cache |
380
+| `diagnose_hf_cache()` | Inspect the cache for corruption |
381
+| `repair_hf_cache_symlinks()` | Fix broken symlinks in the HF cache layout |
382
+| `model_cache_path(model_id)` | Resolve the filesystem path for a model |
383
+| `clean_stale_lock_files()` | Remove leftover `.lock` files from interrupted downloads |
384
+
385
+`ModelDownloadProgress` carries `downloaded_bytes`, `total_bytes`, `progress` (0.0–1.0), and `done`.
386
+
387
+---
388
+
389
+## Sampling Presets
390
+
391
+| Preset | `temperature` | `top_p` | `max_tokens` | Use Case |
392
+|--------|--------------|---------|--------------|----------|
393
+| `SamplingConfig::default()` | 0.7 | 0.95 | 512 | General creative chat |
394
+| `SamplingConfig::deterministic()` | 0.0 | — | 512 | Reproducible / coding |
395
+| `SamplingConfig::mobile()` | 0.7 | 0.95 | 128 | Memory/latency constrained |
396
+| `SamplingConfig::coding()` | 0.0 | — | 512 | Code generation |
397
+| `SamplingConfig::coding_mobile()` | 0.0 | — | 128 | Code on mobile |
398
+
399
+---
400
+
401
+## Testing
402
+
403
+```bash
404
+# All tests (requires a macOS host with Metal)
405
+cargo test
406
+
407
+# Unit tests only (no model downloads)
408
+cargo test inference::
409
+cargo test inference::ffi::
410
+cargo test inference::types::
411
+
412
+# hf_cache module
413
+cargo test hf_cache::
414
+
415
+# Clippy (treat warnings as errors)
416
+cargo clippy -- -D warnings
417
+```
418
+
419
+Tests that require model downloads are integration tests and not run by default in CI. Unit tests verify:
420
+- Type constructors and `Display` implementations
421
+- `SamplingConfig` presets
422
+- `GgufModelConfig` constructors and `platform_default()` routing
423
+- `OndeChatEngine` lifecycle: `new()` starts unloaded, `send_message` without model returns `InferenceError::NoModelLoaded`, `clear_history` on empty returns 0, `unload_model` on empty returns `nil`.
424
+
425
+---
426
+
427
+## Known Issues
428
+
429
+### `___chkstk_darwin` linker error on tvOS
430
+
431
+`aws-lc-sys` (transitive via `reqwest → rustls → aws-lc-rs`) references `___chkstk_darwin`, a stack probing symbol that tvOS does not export. `build.rs` compiles `tvos_chkstk.s` (a no-op `ret` stub) via the `cc` crate to satisfy the linker. **Do not delete `tvos_chkstk.s`.**
432
+
433
+Affects: tvOS only. macOS, iOS, Android, Windows, Linux are unaffected.
434
+
435
+### Metal Toolchain missing (Xcode 26+)
436
+
437
+On Xcode 26+, the Metal compiler is a separate download. If you see empty `.metallib` files (92 bytes) or `"Error while loading function: fused_glu_float"` at runtime:
438
+
439
+```bash
440
+xcodebuild -downloadComponent MetalToolchain
441
+cargo clean -p mistralrs-quant
442
+cargo check
443
+```
444
+
445
+### Android `home_dir` sandbox panic
446
+
447
+`dirs::home_dir()` panics in the Android sandbox. The crate-level `Cargo.toml` adds `hf-hub` as an explicit Android dependency so `HF_HOME` can be seeded via `ApiBuilder` before any hub request. Never call `home::home_dir()` or `dirs::home_dir()` on Android.
448
+
449
+---
450
+
451
+## Code Conventions
452
+
453
+### Rust
454
+
455
+- **Error handling:** `anyhow` for application-level errors inside `engine.rs`; `thiserror` for the `InferenceError` enum (exported via UniFFI). Never `.unwrap()` or `.expect()` in non-test code.
456
+- **Async:** `tokio` runtime. All async functions in `OndeChatEngine` are `pub async fn`.
457
+- **Logging:** `log` crate macros (`log::debug!`, `log::info!`, `log::warn!`, `log::error!`). No `println!` in library code.
458
+- **Platform gating:** use `#[cfg(target_os = "...")]` blocks. Match `Cargo.toml`'s target-conditional dependency sections.
459
+- **Re-exports:** `mistralrs`, `hf_hub`, and `mistralrs_core` are re-exported from `lib.rs` for downstream Rust consumers. Keep these re-exports in sync with what's actually available per platform.
460
+- **No `mod.rs`:** use named files (`inference/engine.rs`) not `inference/mod.rs` — except that `inference/mod.rs` exists and is the intentional module root for the `inference` module.
461
+
462
+### Swift / SDK
463
+
464
+- **Never manually edit `onde-swift/Sources/Onde/onde.swift`** — it is generated by `uniffi-bindgen`. Regenerate by running the build script.
465
+- **`Package.swift` binary target path** must point to the correct `.xcframework` location: local path during development, remote URL+checksum for published releases.
466
+- **iOS/tvOS sandbox:** always call `setupInferenceEnvironment()` at app launch before any `OndeChatEngine` call.
467
+
468
+---
469
+
470
+## CI / Release Workflow
471
+
472
+### `build-swift-xcframework.yml`
473
+
474
+Triggered on `workflow_dispatch` or on published GitHub Releases. Runs on `macos-15`.
475
+
476
+Steps:
477
+1. Install stable Rust (`1.92.0`) with iOS/macOS targets.
478
+2. Install nightly Rust with `rust-src` component and tvOS targets.
479
+3. Run `build-swift-xcframework.sh`.
480
+4. Upload artifacts: zip, checksum, version, generated `onde.swift`.
481
+5. On release: attach zip and checksum to the GitHub Release.
482
+
483
+### Release Process (end-to-end)
484
+
485
+1. Bump version in `onde/Cargo.toml`.
486
+2. Tag and push `onde` repo → CI builds and attaches `OndeFramework.xcframework.zip`.
487
+3. In `onde-swift`, update `Package.swift` with new `url:` and `checksum:`.
488
+4. Commit, tag, push `onde-swift` → Swift Package Index indexes it automatically.
489
+5. `cargo publish` to crates.io.
490
+
491
+---
492
+
493
+## Distribution Registry Summary
494
+
495
+| Registry | Name | Import |
496
+|----------|------|--------|
497
+| crates.io | `onde` | `onde = "0.x"` |
498
+| Swift Package Index | `onde-swift` (org: `ondeinference`) | `import Onde` |
499
+| PyPI | `onde-inference` | `import onde_inference` |
500
+| RubyGems | `onde-inference` | `require 'onde'` |
501
+| Maven Central | `com.ondeinference:onde-inference` | Gradle `implementation(...)` |
502
+
503
+---
504
+
505
+*This file is for AI agent and human developer reference. Update it when the architecture, API surface, supported models, or toolchain requirements change.*
506
+```
.agents/skills/humanizer/SKILL.md
renamed
.agents/skills/sdk-swift-package/SKILL.md
renamed
.agents/skills/uniffi-rs/SKILL.md
renamed
AGENTS.md
new
+1
@@ -0,0 +1 @@
1
+.agents/AGENTS.md
\ No newline at end of file
CLAUDE.md
new
+1
@@ -0,0 +1 @@
1
+.agents/AGENTS.md
\ No newline at end of file