Skip to content

Commit 717d543

Browse files
authored
Merge pull request anomalyco#250 from OkminLee/feat/skills/ios26-three-skills
LGTM — 3 iOS 26 skills. Pure documentation.
2 parents bbbb2d6 + 300b671 commit 717d543

4 files changed

Lines changed: 744 additions & 2 deletions

File tree

‎README.md‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ For manual install instructions see the README in the `rules/` folder.
143143
/plugin list everything-claude-code@everything-claude-code
144144
```
145145

146-
✨ **That's it!** You now have access to 13 agents, 44 skills, and 32 commands.
146+
✨ **That's it!** You now have access to 13 agents, 48 skills, and 32 commands.
147147

148148
---
149149

@@ -252,6 +252,10 @@ everything-claude-code/
252252
| |-- swift-actor-persistence/ # Thread-safe Swift data persistence with actors (NEW)
253253
| |-- swift-protocol-di-testing/ # Protocol-based DI for testable Swift code (NEW)
254254
| |-- search-first/ # Research-before-coding workflow (NEW)
255+
| |-- skill-stocktake/ # Audit skills and commands for quality (NEW)
256+
| |-- liquid-glass-design/ # iOS 26 Liquid Glass design system (NEW)
257+
| |-- foundation-models-on-device/ # Apple on-device LLM with FoundationModels (NEW)
258+
| |-- swift-concurrency-6-2/ # Swift 6.2 Approachable Concurrency (NEW)
255259
|
256260
|-- commands/ # Slash commands for quick execution
257261
| |-- tdd.md # /tdd - Test-driven development
@@ -811,7 +815,7 @@ The configuration is automatically detected from `.opencode/opencode.json`.
811815
|---------|-------------|----------|--------|
812816
| Agents | ✅ 13 agents | ✅ 12 agents | **Claude Code leads** |
813817
| Commands | ✅ 32 commands | ✅ 24 commands | **Claude Code leads** |
814-
| Skills | ✅ 44 skills | ✅ 16 skills | **Claude Code leads** |
818+
| Skills | ✅ 48 skills | ✅ 16 skills | **Claude Code leads** |
815819
| Hooks | ✅ 3 phases | ✅ 20+ events | **OpenCode has more!** |
816820
| Rules | ✅ 8 rules | ✅ 8 rules | **Full parity** |
817821
| MCP Servers | ✅ Full | ✅ Full | **Full parity** |
Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
---
2+
name: foundation-models-on-device
3+
description: Apple FoundationModels framework for on-device LLM — text generation, guided generation with @Generable, tool calling, and snapshot streaming in iOS 26+.
4+
---
5+
6+
# FoundationModels: On-Device LLM (iOS 26)
7+
8+
Patterns for integrating Apple's on-device language model into apps using the FoundationModels framework. Covers text generation, structured output with `@Generable`, custom tool calling, and snapshot streaming — all running on-device for privacy and offline support.
9+
10+
## When to Activate
11+
12+
- Building AI-powered features using Apple Intelligence on-device
13+
- Generating or summarizing text without cloud dependency
14+
- Extracting structured data from natural language input
15+
- Implementing custom tool calling for domain-specific AI actions
16+
- Streaming structured responses for real-time UI updates
17+
- Need privacy-preserving AI (no data leaves the device)
18+
19+
## Core Pattern — Availability Check
20+
21+
Always check model availability before creating a session:
22+
23+
```swift
24+
struct GenerativeView: View {
25+
private var model = SystemLanguageModel.default
26+
27+
var body: some View {
28+
switch model.availability {
29+
case .available:
30+
ContentView()
31+
case .unavailable(.deviceNotEligible):
32+
Text("Device not eligible for Apple Intelligence")
33+
case .unavailable(.appleIntelligenceNotEnabled):
34+
Text("Please enable Apple Intelligence in Settings")
35+
case .unavailable(.modelNotReady):
36+
Text("Model is downloading or not ready")
37+
case .unavailable(let other):
38+
Text("Model unavailable: \(other)")
39+
}
40+
}
41+
}
42+
```
43+
44+
## Core Pattern — Basic Session
45+
46+
```swift
47+
// Single-turn: create a new session each time
48+
let session = LanguageModelSession()
49+
let response = try await session.respond(to: "What's a good month to visit Paris?")
50+
print(response.content)
51+
52+
// Multi-turn: reuse session for conversation context
53+
let session = LanguageModelSession(instructions: """
54+
You are a cooking assistant.
55+
Provide recipe suggestions based on ingredients.
56+
Keep suggestions brief and practical.
57+
""")
58+
59+
let first = try await session.respond(to: "I have chicken and rice")
60+
let followUp = try await session.respond(to: "What about a vegetarian option?")
61+
```
62+
63+
Key points for instructions:
64+
- Define the model's role ("You are a mentor")
65+
- Specify what to do ("Help extract calendar events")
66+
- Set style preferences ("Respond as briefly as possible")
67+
- Add safety measures ("Respond with 'I can't help with that' for dangerous requests")
68+
69+
## Core Pattern — Guided Generation with @Generable
70+
71+
Generate structured Swift types instead of raw strings:
72+
73+
### 1. Define a Generable Type
74+
75+
```swift
76+
@Generable(description: "Basic profile information about a cat")
77+
struct CatProfile {
78+
var name: String
79+
80+
@Guide(description: "The age of the cat", .range(0...20))
81+
var age: Int
82+
83+
@Guide(description: "A one sentence profile about the cat's personality")
84+
var profile: String
85+
}
86+
```
87+
88+
### 2. Request Structured Output
89+
90+
```swift
91+
let response = try await session.respond(
92+
to: "Generate a cute rescue cat",
93+
generating: CatProfile.self
94+
)
95+
96+
// Access structured fields directly
97+
print("Name: \(response.content.name)")
98+
print("Age: \(response.content.age)")
99+
print("Profile: \(response.content.profile)")
100+
```
101+
102+
### Supported @Guide Constraints
103+
104+
- `.range(0...20)` — numeric range
105+
- `.count(3)` — array element count
106+
- `description:` — semantic guidance for generation
107+
108+
## Core Pattern — Tool Calling
109+
110+
Let the model invoke custom code for domain-specific tasks:
111+
112+
### 1. Define a Tool
113+
114+
```swift
115+
struct RecipeSearchTool: Tool {
116+
let name = "recipe_search"
117+
let description = "Search for recipes matching a given term and return a list of results."
118+
119+
@Generable
120+
struct Arguments {
121+
var searchTerm: String
122+
var numberOfResults: Int
123+
}
124+
125+
func call(arguments: Arguments) async throws -> ToolOutput {
126+
let recipes = await searchRecipes(
127+
term: arguments.searchTerm,
128+
limit: arguments.numberOfResults
129+
)
130+
return .string(recipes.map { "- \($0.name): \($0.description)" }.joined(separator: "\n"))
131+
}
132+
}
133+
```
134+
135+
### 2. Create Session with Tools
136+
137+
```swift
138+
let session = LanguageModelSession(tools: [RecipeSearchTool()])
139+
let response = try await session.respond(to: "Find me some pasta recipes")
140+
```
141+
142+
### 3. Handle Tool Errors
143+
144+
```swift
145+
do {
146+
let answer = try await session.respond(to: "Find a recipe for tomato soup.")
147+
} catch let error as LanguageModelSession.ToolCallError {
148+
print(error.tool.name)
149+
if case .databaseIsEmpty = error.underlyingError as? RecipeSearchToolError {
150+
// Handle specific tool error
151+
}
152+
}
153+
```
154+
155+
## Core Pattern — Snapshot Streaming
156+
157+
Stream structured responses for real-time UI with `PartiallyGenerated` types:
158+
159+
```swift
160+
@Generable
161+
struct TripIdeas {
162+
@Guide(description: "Ideas for upcoming trips")
163+
var ideas: [String]
164+
}
165+
166+
let stream = session.streamResponse(
167+
to: "What are some exciting trip ideas?",
168+
generating: TripIdeas.self
169+
)
170+
171+
for try await partial in stream {
172+
// partial: TripIdeas.PartiallyGenerated (all properties Optional)
173+
print(partial)
174+
}
175+
```
176+
177+
### SwiftUI Integration
178+
179+
```swift
180+
@State private var partialResult: TripIdeas.PartiallyGenerated?
181+
@State private var errorMessage: String?
182+
183+
var body: some View {
184+
List {
185+
ForEach(partialResult?.ideas ?? [], id: \.self) { idea in
186+
Text(idea)
187+
}
188+
}
189+
.overlay {
190+
if let errorMessage { Text(errorMessage).foregroundStyle(.red) }
191+
}
192+
.task {
193+
do {
194+
let stream = session.streamResponse(to: prompt, generating: TripIdeas.self)
195+
for try await partial in stream {
196+
partialResult = partial
197+
}
198+
} catch {
199+
errorMessage = error.localizedDescription
200+
}
201+
}
202+
}
203+
```
204+
205+
## Key Design Decisions
206+
207+
| Decision | Rationale |
208+
|----------|-----------|
209+
| On-device execution | Privacy — no data leaves the device; works offline |
210+
| 4,096 token limit | On-device model constraint; chunk large data across sessions |
211+
| Snapshot streaming (not deltas) | Structured output friendly; each snapshot is a complete partial state |
212+
| `@Generable` macro | Compile-time safety for structured generation; auto-generates `PartiallyGenerated` type |
213+
| Single request per session | `isResponding` prevents concurrent requests; create multiple sessions if needed |
214+
| `response.content` (not `.output`) | Correct API — always access results via `.content` property |
215+
216+
## Best Practices
217+
218+
- **Always check `model.availability`** before creating a session — handle all unavailability cases
219+
- **Use `instructions`** to guide model behavior — they take priority over prompts
220+
- **Check `isResponding`** before sending a new request — sessions handle one request at a time
221+
- **Access `response.content`** for results — not `.output`
222+
- **Break large inputs into chunks** — 4,096 token limit applies to instructions + prompt + output combined
223+
- **Use `@Generable`** for structured output — stronger guarantees than parsing raw strings
224+
- **Use `GenerationOptions(temperature:)`** to tune creativity (higher = more creative)
225+
- **Monitor with Instruments** — use Xcode Instruments to profile request performance
226+
227+
## Anti-Patterns to Avoid
228+
229+
- Creating sessions without checking `model.availability` first
230+
- Sending inputs exceeding the 4,096 token context window
231+
- Attempting concurrent requests on a single session
232+
- Using `.output` instead of `.content` to access response data
233+
- Parsing raw string responses when `@Generable` structured output would work
234+
- Building complex multi-step logic in a single prompt — break into multiple focused prompts
235+
- Assuming the model is always available — device eligibility and settings vary
236+
237+
## When to Use
238+
239+
- On-device text generation for privacy-sensitive apps
240+
- Structured data extraction from user input (forms, natural language commands)
241+
- AI-assisted features that must work offline
242+
- Streaming UI that progressively shows generated content
243+
- Domain-specific AI actions via tool calling (search, compute, lookup)

0 commit comments

Comments
 (0)