PocketKernel: Apple on-device agent and local execution - #4
PocketKernel: Apple on-device agent and local execution#4NightVibes33 wants to merge 17 commits into
Conversation
Reviewer's GuideAdds Apple’s on-device Foundation Models as a first-class provider, makes Apple On-Device the default routing mode, and introduces a PocketKernel local agent and chat UI that plan and execute iSH terminal/file actions with explicit approvals, safety checks, and per-run network consent, while keeping OpenAI/compatible providers available. Sequence diagram for PocketKernel on-device planning and local executionsequenceDiagram
actor User
participant ChatView as PocketKernelChatView
participant Agent as PocketKernelLocalAgent
participant Model as AppleFoundationModelProvider
participant Term as TerminalSessionController
User->>ChatView: type in draft
ChatView->>Agent: prepare(request, conversationContext, workingDirectory)
Agent->>Model: plan(for, conversationContext, workingDirectory)
Model-->>Agent: PocketKernelAgentPlan
alt plan.action == answer
Agent-->>ChatView: PocketKernelAgentPlan(answer)
ChatView->>User: show assistant answer
else plan.requiresApproval
Agent-->>ChatView: PocketKernelAgentPlan(requiresApproval)
ChatView->>User: show approvalCard(plan)
User->>ChatView: tap Approve and run
ChatView->>Agent: approvePendingPlan(allowNetwork)
Agent->>Term: openLocalIsh(cwd)
Agent->>Term: sendLine(wrapped shell command)
Term-->>Agent: output + exitCode
Agent-->>ChatView: PocketKernelExecutionResult
ChatView->>User: show execution output
end
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey - I've found 1 issue, and left some high level feedback:
- PocketKernelLocalAgent is annotated with @observable but PocketKernelChatView stores it in @State; consider using the Observation integration (e.g. @StateObject / @ObservedObject or @bindable) instead so phase/pendingPlan changes actually drive SwiftUI view updates.
- The custom sortProviders comparator mixes special-case branches with a fallback name comparison in a way that can be non-transitive (e.g., .openAI vs .openAICompatible vs others); consider refactoring to a clear kind rank + name sort key to guarantee a stable, well-defined ordering.
- AppleFoundationModelProvider.shared.availability() is invoked multiple times per render in AIProviderSettingsView and PocketKernelChatView; caching it in a local let within the view body/section would avoid redundant checks and ensure consistent availability/summary values within a single render pass.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- PocketKernelLocalAgent is annotated with @Observable but PocketKernelChatView stores it in @State; consider using the Observation integration (e.g. @StateObject / @ObservedObject or @Bindable) instead so phase/pendingPlan changes actually drive SwiftUI view updates.
- The custom sortProviders comparator mixes special-case branches with a fallback name comparison in a way that can be non-transitive (e.g., .openAI vs .openAICompatible vs others); consider refactoring to a clear kind rank + name sort key to guarantee a stable, well-defined ordering.
- AppleFoundationModelProvider.shared.availability() is invoked multiple times per render in AIProviderSettingsView and PocketKernelChatView; caching it in a local let within the view body/section would avoid redundant checks and ensure consistent availability/summary values within a single render pass.
## Individual Comments
### Comment 1
<location path="apps/ios/Sources/Litter/Models/AIProviderStore.swift" line_range="150-157" />
<code_context>
+ defaults.set(true, forKey: appleDefaultMigrationKey)
+ }
+
+ private func sortProviders() {
+ providers.sort { lhs, rhs in
+ if lhs.kind == .appleOnDevice { return rhs.kind != .appleOnDevice }
+ if rhs.kind == .appleOnDevice { return false }
+ if lhs.kind == .openAI { return rhs.kind == .openAICompatible }
+ return lhs.displayName.localizedCaseInsensitiveCompare(rhs.displayName) == .orderedAscending
+ }
}
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Refine provider sort comparator to avoid non-obvious ordering and potential inconsistencies.
This comparator bakes several rules directly into the closure (Apple on-device, then OpenAI vs compatible, then name), which makes it hard to reason about and may break transitivity. For clearer and more stable ordering, consider computing an explicit sort key like `(priority, displayName)`, where `priority` is an integer per kind (e.g. Apple, OpenAI, OpenAI-compatible), and sort by that tuple. This also makes it safer to add new kinds later.
```suggestion
private func sortProviders() {
providers.sort { lhs, rhs in
let lhsKey = providerSortKey(lhs)
let rhsKey = providerSortKey(rhs)
if lhsKey.priority != rhsKey.priority {
return lhsKey.priority < rhsKey.priority
}
return lhsKey.displayName.localizedCaseInsensitiveCompare(rhsKey.displayName) == .orderedAscending
}
}
private func providerSortKey(_ provider: AIProviderProfile) -> (priority: Int, displayName: String) {
let priority: Int
switch provider.kind {
case .appleOnDevice:
priority = 0
case .openAI:
priority = 1
case .openAICompatible:
priority = 2
default:
priority = 3
}
return (priority, provider.displayName)
}
```
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| private func sortProviders() { | ||
| providers.sort { lhs, rhs in | ||
| if lhs.kind == .appleOnDevice { return rhs.kind != .appleOnDevice } | ||
| if rhs.kind == .appleOnDevice { return false } | ||
| if lhs.kind == .openAI { return rhs.kind == .openAICompatible } | ||
| return lhs.displayName.localizedCaseInsensitiveCompare(rhs.displayName) == .orderedAscending | ||
| } | ||
| } |
There was a problem hiding this comment.
suggestion (bug_risk): Refine provider sort comparator to avoid non-obvious ordering and potential inconsistencies.
This comparator bakes several rules directly into the closure (Apple on-device, then OpenAI vs compatible, then name), which makes it hard to reason about and may break transitivity. For clearer and more stable ordering, consider computing an explicit sort key like (priority, displayName), where priority is an integer per kind (e.g. Apple, OpenAI, OpenAI-compatible), and sort by that tuple. This also makes it safer to add new kinds later.
| private func sortProviders() { | |
| providers.sort { lhs, rhs in | |
| if lhs.kind == .appleOnDevice { return rhs.kind != .appleOnDevice } | |
| if rhs.kind == .appleOnDevice { return false } | |
| if lhs.kind == .openAI { return rhs.kind == .openAICompatible } | |
| return lhs.displayName.localizedCaseInsensitiveCompare(rhs.displayName) == .orderedAscending | |
| } | |
| } | |
| private func sortProviders() { | |
| providers.sort { lhs, rhs in | |
| let lhsKey = providerSortKey(lhs) | |
| let rhsKey = providerSortKey(rhs) | |
| if lhsKey.priority != rhsKey.priority { | |
| return lhsKey.priority < rhsKey.priority | |
| } | |
| return lhsKey.displayName.localizedCaseInsensitiveCompare(rhsKey.displayName) == .orderedAscending | |
| } | |
| } | |
| private func providerSortKey(_ provider: AIProviderProfile) -> (priority: Int, displayName: String) { | |
| let priority: Int | |
| switch provider.kind { | |
| case .appleOnDevice: | |
| priority = 0 | |
| case .openAI: | |
| priority = 1 | |
| case .openAICompatible: | |
| priority = 2 | |
| default: | |
| priority = 3 | |
| } | |
| return (priority, provider.displayName) | |
| } |
What this changes
Current integration branch
pocketkernel/apple-foundation-modelsValidation
The PR is intended to run the repository's iOS checks on Xcode 26. The on-device model itself must be exercised on an Apple Intelligence-capable iPhone because CI cannot execute Apple Intelligence.
Summary by Sourcery
Default the iOS app’s AI routing to Apple’s on-device foundation model, introduce a PocketKernel local agent and chat experience for planning and safely executing local actions, and extend provider/settings models and UI to support the new on-device provider and safety guarantees.
New Features:
Enhancements: