Skip to content

PocketKernel: Apple on-device agent and local execution - #4

Open
NightVibes33 wants to merge 17 commits into
mainfrom
pocketkernel/apple-foundation-models
Open

PocketKernel: Apple on-device agent and local execution#4
NightVibes33 wants to merge 17 commits into
mainfrom
pocketkernel/apple-foundation-models

Conversation

@NightVibes33

@NightVibes33 NightVibes33 commented Aug 5, 2026

Copy link
Copy Markdown
Owner

What this changes

  • adds Apple Foundation Models as a first-class, no-key on-device provider
  • defaults new and existing installs to Apple On-Device routing
  • adds typed guided-generation plans for answers, shell commands, and file actions
  • requires explicit approval before any local execution
  • requires separate per-run approval for network access
  • blocks dangerous command and protected-file patterns
  • executes approved actions through Litter's existing embedded iSH terminal runtime
  • adds a real PocketKernel Chat screen with live availability, proposals, approvals, output, and errors
  • keeps optional OpenAI and OpenAI-compatible providers available for compatibility

Current integration branch

pocketkernel/apple-foundation-models

Validation

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:

  • Introduce Apple On-Device as a first-class local AI provider with default routing and capability metadata.
  • Add a PocketKernel Chat screen for interacting with the on-device agent, including approvals, execution output, and error handling.

Enhancements:

  • Implement guided-generation planning for answers, shell commands, and file/file-list actions with safety checks and risk explanations.
  • Add a local PocketKernel agent that orchestrates on-device planning and executes approved actions via the embedded iSH terminal runtime.
  • Update provider management to support Apple On-Device profiles, availability checks, routing mode selection, and safer handling of secrets and deletion.
  • Refine AI settings UI copy, layout, and accessibility to highlight Apple on-device as the default and clarify optional cloud/compatible providers.

@sourcery-ai

sourcery-ai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Reviewer's Guide

Adds 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 execution

sequenceDiagram
    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
Loading

File-Level Changes

Change Details Files
Introduce Apple On-Device provider as a first-class AI provider and default routing target, with migration and sorting logic.
  • Add appleOnDevice kind, capabilities, and a canonical Apple On-Device provider profile with fixed UUID, baseURL, and default model.
  • Ensure a default Apple provider is always present, normalize existing Apple profiles to the canonical one, and prevent deleting the Apple provider.
  • Add routing mode case for Apple On-Device, make it the default in GlobalModelSettings, and migrate existing installs to prefer the Apple provider.
  • Update provider store to treat appleOnDevice as secretless, special-case testing via AppleFoundationModelProvider, and sort providers with Apple/OpenAI precedence.
  • Adjust global model setting sanitization to fall back to Apple On-Device when preferred providers are missing or disabled.
apps/ios/Sources/Litter/Models/AIProviderStore.swift
apps/ios/Sources/Litter/Models/AIProviderModels.swift
Update AI provider settings UI to surface PocketKernel Chat, reflect Apple On-Device defaults, and improve provider details behavior.
  • Add an Apple Intelligence section linking to PocketKernelChatView, showing availability and guidance about on-device planning and approvals.
  • Change routing description copy to emphasize Apple On-Device as default and cloud routes as optional, and update runtime guidance text/casing.
  • Allow multi-line provider subtitles, show Apple availability for appleOnDevice providers, and use an apple.intelligence icon for that kind.
  • Update provider detail view subtitle and test button label to show availability/check wording for Apple providers and restrict deletion to OpenAI-compatible providers.
  • Bind routing mode changes to preferredProviderId so selecting Apple On-Device pins the Apple provider, and improve accessibility label for adding providers.
apps/ios/Sources/Litter/Views/AIProviderSettingsView.swift
Add PocketKernel Chat UI for on-device agent conversations with plan approvals, execution tracking, and error reporting.
  • Implement PocketKernelChatView with message transcript, status header showing Apple On-Device availability, and a composer that calls the local agent.
  • Render guided approvals as cards showing the proposed action, command/path/content, risk explanation, and toggles for per-run network approval.
  • Send requests by preparing a plan via the local agent with bounded conversation context and handle answer-only plans distinctly from execution plans.
  • Display execution output and errors as separate roles (assistant, execution, error), auto-scroll to new messages or approvals, and provide a reset action that clears state and resets the agent.
apps/ios/Sources/Litter/Views/PocketKernelChatView.swift
Introduce AppleFoundationModelProvider for on-device planning and answering using Apple’s system language model with guided generation and safety validation.
  • Define availability states mapping SystemLanguageModel availability to user-facing summaries and errors, and gate usage on availability checks.
  • Model PocketKernelAgentAction and PocketKernelAgentPlan with fields for command, paths, content, risk, network requirements, and approval flags, plus a convenience answer factory.
  • Use FoundationModels guided generation (@generable, @Guide) to produce structured AppleGeneratedAgentPlan, including action type, summaries, command/path/content, and risk/network flags.
  • Implement respond(to:) and plan(for:) that construct prompts with conversation context and working directory, generate a plan, and validate it into a PocketKernelAgentPlan.
  • Enforce safety by validating required fields per action, defaulting summaries, marking actions that need approval, and blocking dangerous shell patterns or protected-file writes with explanations.
apps/ios/Sources/Litter/Models/AppleFoundationModelProvider.swift
Add PocketKernelLocalAgent to orchestrate on-device planning and safe execution via the embedded iSH terminal runtime with explicit approvals.
  • Define PocketKernelExecutionResult and error types for pending-plan, approval, network-approval, blocking, terminal, timeout, and invalid-action failures.
  • Implement an @observable PocketKernelLocalAgent with phases (idle/planning/awaitingApproval/executing/completed/failed), tracking pending plans and last results.
  • Wire prepare(request:conversationContext:workingDirectory:) to AppleFoundationModelProvider.plan, updating phase and pendingPlan, and short-circuiting pure answer plans into completed results.
  • Implement approvePendingPlan(allowNetwork:) to enforce approvals, network consent, and blocking flags, then run shell-based actions through TerminalSessionController with a wrapped command and exit marker.
  • Provide helpers to construct safe shell commands for shell/read/write/list actions, wait for terminal readiness, parse exit status from output, handle timeouts, and manage cancellation/reset of active work and terminal sessions.
apps/ios/Sources/Litter/Models/PocketKernelLocalAgent.swift

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +150 to 157
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
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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)
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant