ag-cortex 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agent/commands/test-browser.md +339 -0
- package/.agent/rules/00-constitution.md +46 -0
- package/.agent/rules/project-rules.md +49 -0
- package/.agent/skills/agent-browser/SKILL.md +223 -0
- package/.agent/skills/agent-native-architecture/SKILL.md +435 -0
- package/.agent/skills/agent-native-architecture/references/action-parity-discipline.md +409 -0
- package/.agent/skills/agent-native-architecture/references/agent-execution-patterns.md +467 -0
- package/.agent/skills/agent-native-architecture/references/agent-native-testing.md +582 -0
- package/.agent/skills/agent-native-architecture/references/architecture-patterns.md +478 -0
- package/.agent/skills/agent-native-architecture/references/dynamic-context-injection.md +338 -0
- package/.agent/skills/agent-native-architecture/references/files-universal-interface.md +301 -0
- package/.agent/skills/agent-native-architecture/references/from-primitives-to-domain-tools.md +359 -0
- package/.agent/skills/agent-native-architecture/references/mcp-tool-design.md +506 -0
- package/.agent/skills/agent-native-architecture/references/mobile-patterns.md +871 -0
- package/.agent/skills/agent-native-architecture/references/product-implications.md +443 -0
- package/.agent/skills/agent-native-architecture/references/refactoring-to-prompt-native.md +317 -0
- package/.agent/skills/agent-native-architecture/references/self-modification.md +269 -0
- package/.agent/skills/agent-native-architecture/references/shared-workspace-architecture.md +680 -0
- package/.agent/skills/agent-native-architecture/references/system-prompt-design.md +250 -0
- package/.agent/skills/agent-native-reviewer/SKILL.md +246 -0
- package/.agent/skills/andrew-kane-gem-writer/SKILL.md +184 -0
- package/.agent/skills/andrew-kane-gem-writer/references/database-adapters.md +231 -0
- package/.agent/skills/andrew-kane-gem-writer/references/module-organization.md +121 -0
- package/.agent/skills/andrew-kane-gem-writer/references/rails-integration.md +183 -0
- package/.agent/skills/andrew-kane-gem-writer/references/resources.md +119 -0
- package/.agent/skills/andrew-kane-gem-writer/references/testing-patterns.md +261 -0
- package/.agent/skills/ankane-readme-writer/SKILL.md +50 -0
- package/.agent/skills/architecture-strategist/SKILL.md +52 -0
- package/.agent/skills/best-practices-researcher/SKILL.md +100 -0
- package/.agent/skills/bug-reproduction-validator/SKILL.md +67 -0
- package/.agent/skills/code-simplicity-reviewer/SKILL.md +85 -0
- package/.agent/skills/coding-tutor/.claude-plugin/plugin.json +9 -0
- package/.agent/skills/coding-tutor/README.md +37 -0
- package/.agent/skills/coding-tutor/commands/quiz-me.md +1 -0
- package/.agent/skills/coding-tutor/commands/sync-tutorials.md +25 -0
- package/.agent/skills/coding-tutor/commands/teach-me.md +1 -0
- package/.agent/skills/coding-tutor/skills/coding-tutor/SKILL.md +214 -0
- package/.agent/skills/coding-tutor/skills/coding-tutor/scripts/create_tutorial.py +202 -0
- package/.agent/skills/coding-tutor/skills/coding-tutor/scripts/index_tutorials.py +203 -0
- package/.agent/skills/coding-tutor/skills/coding-tutor/scripts/quiz_priority.py +190 -0
- package/.agent/skills/coding-tutor/skills/coding-tutor/scripts/setup_tutorials.py +132 -0
- package/.agent/skills/compound-docs/SKILL.md +510 -0
- package/.agent/skills/compound-docs/assets/critical-pattern-template.md +34 -0
- package/.agent/skills/compound-docs/assets/resolution-template.md +93 -0
- package/.agent/skills/compound-docs/references/yaml-schema.md +65 -0
- package/.agent/skills/compound-docs/schema.yaml +176 -0
- package/.agent/skills/create-agent-skills/SKILL.md +299 -0
- package/.agent/skills/create-agent-skills/references/api-security.md +226 -0
- package/.agent/skills/create-agent-skills/references/be-clear-and-direct.md +531 -0
- package/.agent/skills/create-agent-skills/references/best-practices.md +404 -0
- package/.agent/skills/create-agent-skills/references/common-patterns.md +595 -0
- package/.agent/skills/create-agent-skills/references/core-principles.md +437 -0
- package/.agent/skills/create-agent-skills/references/executable-code.md +175 -0
- package/.agent/skills/create-agent-skills/references/iteration-and-testing.md +474 -0
- package/.agent/skills/create-agent-skills/references/official-spec.md +185 -0
- package/.agent/skills/create-agent-skills/references/recommended-structure.md +168 -0
- package/.agent/skills/create-agent-skills/references/skill-structure.md +372 -0
- package/.agent/skills/create-agent-skills/references/using-scripts.md +113 -0
- package/.agent/skills/create-agent-skills/references/using-templates.md +112 -0
- package/.agent/skills/create-agent-skills/references/workflows-and-validation.md +510 -0
- package/.agent/skills/create-agent-skills/templates/router-skill.md +73 -0
- package/.agent/skills/create-agent-skills/templates/simple-skill.md +33 -0
- package/.agent/skills/create-agent-skills/workflows/add-reference.md +96 -0
- package/.agent/skills/create-agent-skills/workflows/add-script.md +93 -0
- package/.agent/skills/create-agent-skills/workflows/add-template.md +74 -0
- package/.agent/skills/create-agent-skills/workflows/add-workflow.md +120 -0
- package/.agent/skills/create-agent-skills/workflows/audit-skill.md +138 -0
- package/.agent/skills/create-agent-skills/workflows/create-domain-expertise-skill.md +605 -0
- package/.agent/skills/create-agent-skills/workflows/create-new-skill.md +191 -0
- package/.agent/skills/create-agent-skills/workflows/get-guidance.md +121 -0
- package/.agent/skills/create-agent-skills/workflows/upgrade-to-router.md +161 -0
- package/.agent/skills/create-agent-skills/workflows/verify-skill.md +204 -0
- package/.agent/skills/data-integrity-guardian/SKILL.md +70 -0
- package/.agent/skills/data-migration-expert/SKILL.md +97 -0
- package/.agent/skills/deployment-verification-agent/SKILL.md +159 -0
- package/.agent/skills/design-implementation-reviewer/SKILL.md +85 -0
- package/.agent/skills/design-iterator/SKILL.md +197 -0
- package/.agent/skills/dhh-rails-reviewer/SKILL.md +45 -0
- package/.agent/skills/dhh-rails-style/SKILL.md +184 -0
- package/.agent/skills/dhh-rails-style/references/architecture.md +653 -0
- package/.agent/skills/dhh-rails-style/references/controllers.md +303 -0
- package/.agent/skills/dhh-rails-style/references/frontend.md +510 -0
- package/.agent/skills/dhh-rails-style/references/gems.md +266 -0
- package/.agent/skills/dhh-rails-style/references/models.md +359 -0
- package/.agent/skills/dhh-rails-style/references/testing.md +338 -0
- package/.agent/skills/dspy-ruby/SKILL.md +594 -0
- package/.agent/skills/dspy-ruby/assets/config-template.rb +359 -0
- package/.agent/skills/dspy-ruby/assets/module-template.rb +326 -0
- package/.agent/skills/dspy-ruby/assets/signature-template.rb +143 -0
- package/.agent/skills/dspy-ruby/references/core-concepts.md +265 -0
- package/.agent/skills/dspy-ruby/references/optimization.md +623 -0
- package/.agent/skills/dspy-ruby/references/providers.md +305 -0
- package/.agent/skills/every-style-editor/SKILL.md +134 -0
- package/.agent/skills/every-style-editor/references/EVERY_WRITE_STYLE.md +529 -0
- package/.agent/skills/figma-design-sync/SKILL.md +166 -0
- package/.agent/skills/file-todos/SKILL.md +251 -0
- package/.agent/skills/file-todos/assets/todo-template.md +155 -0
- package/.agent/skills/framework-docs-researcher/SKILL.md +83 -0
- package/.agent/skills/frontend-design/SKILL.md +42 -0
- package/.agent/skills/gemini-imagegen/SKILL.md +237 -0
- package/.agent/skills/gemini-imagegen/requirements.txt +2 -0
- package/.agent/skills/gemini-imagegen/scripts/compose_images.py +168 -0
- package/.agent/skills/gemini-imagegen/scripts/edit_image.py +157 -0
- package/.agent/skills/gemini-imagegen/scripts/gemini_images.py +265 -0
- package/.agent/skills/gemini-imagegen/scripts/generate_image.py +147 -0
- package/.agent/skills/gemini-imagegen/scripts/multi_turn_chat.py +215 -0
- package/.agent/skills/git-history-analyzer/SKILL.md +42 -0
- package/.agent/skills/git-worktree/SKILL.md +302 -0
- package/.agent/skills/git-worktree/scripts/worktree-manager.sh +345 -0
- package/.agent/skills/julik-frontend-races-reviewer/SKILL.md +222 -0
- package/.agent/skills/kieran-python-reviewer/SKILL.md +104 -0
- package/.agent/skills/kieran-rails-reviewer/SKILL.md +86 -0
- package/.agent/skills/kieran-typescript-reviewer/SKILL.md +95 -0
- package/.agent/skills/lint/SKILL.md +16 -0
- package/.agent/skills/pattern-recognition-specialist/SKILL.md +57 -0
- package/.agent/skills/performance-oracle/SKILL.md +110 -0
- package/.agent/skills/pr-comment-resolver/SKILL.md +69 -0
- package/.agent/skills/rclone/SKILL.md +150 -0
- package/.agent/skills/rclone/scripts/check_setup.sh +60 -0
- package/.agent/skills/repo-research-analyst/SKILL.md +113 -0
- package/.agent/skills/security-sentinel/SKILL.md +93 -0
- package/.agent/skills/skill-creator/SKILL.md +209 -0
- package/.agent/skills/skill-creator/scripts/init_skill.py +304 -0
- package/.agent/skills/skill-creator/scripts/package_skill.py +112 -0
- package/.agent/skills/skill-creator/scripts/quick_validate.py +72 -0
- package/.agent/skills/spec-flow-analyzer/SKILL.md +113 -0
- package/.agent/skills/test-agent/SKILL.md +4 -0
- package/.agent/workflows/agent-native-audit.md +277 -0
- package/.agent/workflows/ask-user-question.md +21 -0
- package/.agent/workflows/changelog.md +137 -0
- package/.agent/workflows/compound.md +202 -0
- package/.agent/workflows/create-agent-skill.md +8 -0
- package/.agent/workflows/deepen-plan-research.md +334 -0
- package/.agent/workflows/deepen-plan-synthesis.md +182 -0
- package/.agent/workflows/deepen-plan.md +79 -0
- package/.agent/workflows/feature-video.md +342 -0
- package/.agent/workflows/generate-command.md +162 -0
- package/.agent/workflows/heal-skill.md +142 -0
- package/.agent/workflows/lfg.md +20 -0
- package/.agent/workflows/plan-analysis.md +67 -0
- package/.agent/workflows/plan-next-steps.md +63 -0
- package/.agent/workflows/plan-review.md +33 -0
- package/.agent/workflows/plan-synthesis.md +106 -0
- package/.agent/workflows/plan.md +49 -0
- package/.agent/workflows/report-bug.md +150 -0
- package/.agent/workflows/reproduce-bug.md +99 -0
- package/.agent/workflows/resolve-parallel.md +34 -0
- package/.agent/workflows/resolve-pr-parallel.md +49 -0
- package/.agent/workflows/resolve-todo-parallel.md +35 -0
- package/.agent/workflows/review-analysis.md +145 -0
- package/.agent/workflows/review-synthesis.md +262 -0
- package/.agent/workflows/review.md +64 -0
- package/.agent/workflows/ship.md +90 -0
- package/.agent/workflows/test-command.md +3 -0
- package/.agent/workflows/triage.md +310 -0
- package/.agent/workflows/work.md +157 -0
- package/.agent/workflows/xcode-test.md +332 -0
- package/LICENSE +22 -0
- package/README.md +49 -0
- package/bin/ag-cortex.js +54 -0
- package/lib/core.js +165 -0
- package/package.json +31 -0
|
@@ -0,0 +1,871 @@
|
|
|
1
|
+
<overview>
|
|
2
|
+
Mobile is a first-class platform for agent-native apps. It has unique constraints and opportunities. This guide covers why mobile matters, iOS storage architecture, checkpoint/resume patterns, and cost-aware design.
|
|
3
|
+
</overview>
|
|
4
|
+
|
|
5
|
+
<why_mobile>
|
|
6
|
+
## Why Mobile Matters
|
|
7
|
+
|
|
8
|
+
Mobile devices offer unique advantages for agent-native apps:
|
|
9
|
+
|
|
10
|
+
### A File System
|
|
11
|
+
Agents can work with files naturally, using the same primitives that work everywhere else. The filesystem is the universal interface.
|
|
12
|
+
|
|
13
|
+
### Rich Context
|
|
14
|
+
A walled garden you get access to. Health data, location, photos, calendars—context that doesn't exist on desktop or web. This enables deeply personalized agent experiences.
|
|
15
|
+
|
|
16
|
+
### Local Apps
|
|
17
|
+
Everyone has their own copy of the app. This opens opportunities that aren't fully realized yet: apps that modify themselves, fork themselves, evolve per-user. App Store policies constrain some of this today, but the foundation is there.
|
|
18
|
+
|
|
19
|
+
### Cross-Device Sync
|
|
20
|
+
If you use the file system with iCloud, all devices share the same file system. The agent's work on one device appears on all devices—without you having to build a server.
|
|
21
|
+
|
|
22
|
+
### The Challenge
|
|
23
|
+
|
|
24
|
+
**Agents are long-running. Mobile apps are not.**
|
|
25
|
+
|
|
26
|
+
An agent might need 30 seconds, 5 minutes, or an hour to complete a task. But iOS will background your app after seconds of inactivity, and may kill it entirely to reclaim memory. The user might switch apps, take a call, or lock their phone mid-task.
|
|
27
|
+
|
|
28
|
+
This means mobile agent apps need:
|
|
29
|
+
- **Checkpointing** — Saving state so work isn't lost
|
|
30
|
+
- **Resuming** — Picking up where you left off after interruption
|
|
31
|
+
- **Background execution** — Using the limited time iOS gives you wisely
|
|
32
|
+
- **On-device vs. cloud decisions** — What runs locally vs. what needs a server
|
|
33
|
+
</why_mobile>
|
|
34
|
+
|
|
35
|
+
<ios_storage>
|
|
36
|
+
## iOS Storage Architecture
|
|
37
|
+
|
|
38
|
+
> **Needs validation:** This is an approach that works well, but better solutions may exist.
|
|
39
|
+
|
|
40
|
+
For agent-native iOS apps, use iCloud Drive's Documents folder for your shared workspace. This gives you **free, automatic multi-device sync** without building a sync layer or running a server.
|
|
41
|
+
|
|
42
|
+
### Why iCloud Documents?
|
|
43
|
+
|
|
44
|
+
| Approach | Cost | Complexity | Offline | Multi-Device |
|
|
45
|
+
|----------|------|------------|---------|--------------|
|
|
46
|
+
| Custom backend + sync | $$$ | High | Manual | Yes |
|
|
47
|
+
| CloudKit database | Free tier limits | Medium | Manual | Yes |
|
|
48
|
+
| **iCloud Documents** | Free (user's storage) | Low | Automatic | Automatic |
|
|
49
|
+
|
|
50
|
+
iCloud Documents:
|
|
51
|
+
- Uses user's existing iCloud storage (free 5GB, most users have more)
|
|
52
|
+
- Automatic sync across all user's devices
|
|
53
|
+
- Works offline, syncs when online
|
|
54
|
+
- Files visible in Files.app for transparency
|
|
55
|
+
- No server costs, no sync code to maintain
|
|
56
|
+
|
|
57
|
+
### Implementation: iCloud-First with Local Fallback
|
|
58
|
+
|
|
59
|
+
```swift
|
|
60
|
+
// Get the iCloud Documents container
|
|
61
|
+
func iCloudDocumentsURL() -> URL? {
|
|
62
|
+
FileManager.default.url(forUbiquityContainerIdentifier: nil)?
|
|
63
|
+
.appendingPathComponent("Documents")
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Your shared workspace lives in iCloud
|
|
67
|
+
class SharedWorkspace {
|
|
68
|
+
let rootURL: URL
|
|
69
|
+
|
|
70
|
+
init() {
|
|
71
|
+
// Use iCloud if available, fall back to local
|
|
72
|
+
if let iCloudURL = iCloudDocumentsURL() {
|
|
73
|
+
self.rootURL = iCloudURL
|
|
74
|
+
} else {
|
|
75
|
+
// Fallback to local Documents (user not signed into iCloud)
|
|
76
|
+
self.rootURL = FileManager.default.urls(
|
|
77
|
+
for: .documentDirectory,
|
|
78
|
+
in: .userDomainMask
|
|
79
|
+
).first!
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// All file operations go through this root
|
|
84
|
+
func researchPath(for bookId: String) -> URL {
|
|
85
|
+
rootURL.appendingPathComponent("Research/\(bookId)")
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
func journalPath() -> URL {
|
|
89
|
+
rootURL.appendingPathComponent("Journal")
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Directory Structure in iCloud
|
|
95
|
+
|
|
96
|
+
```
|
|
97
|
+
iCloud Drive/
|
|
98
|
+
└── YourApp/ # Your app's container
|
|
99
|
+
└── Documents/ # Visible in Files.app
|
|
100
|
+
├── Journal/
|
|
101
|
+
│ ├── user/
|
|
102
|
+
│ │ └── 2025-01-15.md # Syncs across devices
|
|
103
|
+
│ └── agent/
|
|
104
|
+
│ └── 2025-01-15.md # Agent observations sync too
|
|
105
|
+
├── Research/
|
|
106
|
+
│ └── {bookId}/
|
|
107
|
+
│ ├── full_text.txt
|
|
108
|
+
│ └── sources/
|
|
109
|
+
├── Chats/
|
|
110
|
+
│ └── {conversationId}.json
|
|
111
|
+
└── context.md # Agent's accumulated knowledge
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Handling iCloud File States
|
|
115
|
+
|
|
116
|
+
iCloud files may not be downloaded locally. Handle this:
|
|
117
|
+
|
|
118
|
+
```swift
|
|
119
|
+
func readFile(at url: URL) throws -> String {
|
|
120
|
+
// iCloud may create .icloud placeholder files
|
|
121
|
+
if url.pathExtension == "icloud" {
|
|
122
|
+
// Trigger download
|
|
123
|
+
try FileManager.default.startDownloadingUbiquitousItem(at: url)
|
|
124
|
+
throw FileNotYetAvailableError()
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return try String(contentsOf: url, encoding: .utf8)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// For writes, use coordinated file access
|
|
131
|
+
func writeFile(_ content: String, to url: URL) throws {
|
|
132
|
+
let coordinator = NSFileCoordinator()
|
|
133
|
+
var error: NSError?
|
|
134
|
+
|
|
135
|
+
coordinator.coordinate(
|
|
136
|
+
writingItemAt: url,
|
|
137
|
+
options: .forReplacing,
|
|
138
|
+
error: &error
|
|
139
|
+
) { newURL in
|
|
140
|
+
try? content.write(to: newURL, atomically: true, encoding: .utf8)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if let error = error { throw error }
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### What iCloud Enables
|
|
148
|
+
|
|
149
|
+
1. **User starts experiment on iPhone** → Agent creates config file
|
|
150
|
+
2. **User opens app on iPad** → Same experiment visible, no sync code needed
|
|
151
|
+
3. **Agent logs observation on iPhone** → Syncs to iPad automatically
|
|
152
|
+
4. **User edits journal on iPad** → iPhone sees the edit
|
|
153
|
+
|
|
154
|
+
### Entitlements Required
|
|
155
|
+
|
|
156
|
+
Add to your app's entitlements:
|
|
157
|
+
|
|
158
|
+
```xml
|
|
159
|
+
<key>com.apple.developer.icloud-container-identifiers</key>
|
|
160
|
+
<array>
|
|
161
|
+
<string>iCloud.com.yourcompany.yourapp</string>
|
|
162
|
+
</array>
|
|
163
|
+
<key>com.apple.developer.icloud-services</key>
|
|
164
|
+
<array>
|
|
165
|
+
<string>CloudDocuments</string>
|
|
166
|
+
</array>
|
|
167
|
+
<key>com.apple.developer.ubiquity-container-identifiers</key>
|
|
168
|
+
<array>
|
|
169
|
+
<string>iCloud.com.yourcompany.yourapp</string>
|
|
170
|
+
</array>
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### When NOT to Use iCloud Documents
|
|
174
|
+
|
|
175
|
+
- **Sensitive data** - Use Keychain or encrypted local storage instead
|
|
176
|
+
- **High-frequency writes** - iCloud sync has latency; use local + periodic sync
|
|
177
|
+
- **Large media files** - Consider CloudKit Assets or on-demand resources
|
|
178
|
+
- **Shared between users** - iCloud Documents is single-user; use CloudKit for sharing
|
|
179
|
+
</ios_storage>
|
|
180
|
+
|
|
181
|
+
<background_execution>
|
|
182
|
+
## Background Execution & Resumption
|
|
183
|
+
|
|
184
|
+
> **Needs validation:** These patterns work but better solutions may exist.
|
|
185
|
+
|
|
186
|
+
Mobile apps can be suspended or terminated at any time. Agents must handle this gracefully.
|
|
187
|
+
|
|
188
|
+
### The Challenge
|
|
189
|
+
|
|
190
|
+
```
|
|
191
|
+
User starts research agent
|
|
192
|
+
↓
|
|
193
|
+
Agent begins web search
|
|
194
|
+
↓
|
|
195
|
+
User switches to another app
|
|
196
|
+
↓
|
|
197
|
+
iOS suspends your app
|
|
198
|
+
↓
|
|
199
|
+
Agent is mid-execution... what happens?
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### Checkpoint/Resume Pattern
|
|
203
|
+
|
|
204
|
+
Save agent state before backgrounding, restore on foreground:
|
|
205
|
+
|
|
206
|
+
```swift
|
|
207
|
+
class AgentOrchestrator: ObservableObject {
|
|
208
|
+
@Published var activeSessions: [AgentSession] = []
|
|
209
|
+
|
|
210
|
+
// Called when app is about to background
|
|
211
|
+
func handleAppWillBackground() {
|
|
212
|
+
for session in activeSessions {
|
|
213
|
+
saveCheckpoint(session)
|
|
214
|
+
session.transition(to: .backgrounded)
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Called when app returns to foreground
|
|
219
|
+
func handleAppDidForeground() {
|
|
220
|
+
for session in activeSessions where session.state == .backgrounded {
|
|
221
|
+
if let checkpoint = loadCheckpoint(session.id) {
|
|
222
|
+
resumeFromCheckpoint(session, checkpoint)
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
private func saveCheckpoint(_ session: AgentSession) {
|
|
228
|
+
let checkpoint = AgentCheckpoint(
|
|
229
|
+
sessionId: session.id,
|
|
230
|
+
conversationHistory: session.messages,
|
|
231
|
+
pendingToolCalls: session.pendingToolCalls,
|
|
232
|
+
partialResults: session.partialResults,
|
|
233
|
+
timestamp: Date()
|
|
234
|
+
)
|
|
235
|
+
storage.save(checkpoint, for: session.id)
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
private func resumeFromCheckpoint(_ session: AgentSession, _ checkpoint: AgentCheckpoint) {
|
|
239
|
+
session.messages = checkpoint.conversationHistory
|
|
240
|
+
session.pendingToolCalls = checkpoint.pendingToolCalls
|
|
241
|
+
|
|
242
|
+
// Resume execution if there were pending tool calls
|
|
243
|
+
if !checkpoint.pendingToolCalls.isEmpty {
|
|
244
|
+
session.transition(to: .running)
|
|
245
|
+
Task { await executeNextTool(session) }
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### State Machine for Agent Lifecycle
|
|
252
|
+
|
|
253
|
+
```swift
|
|
254
|
+
enum AgentState {
|
|
255
|
+
case idle // Not running
|
|
256
|
+
case running // Actively executing
|
|
257
|
+
case waitingForUser // Paused, waiting for user input
|
|
258
|
+
case backgrounded // App backgrounded, state saved
|
|
259
|
+
case completed // Finished successfully
|
|
260
|
+
case failed(Error) // Finished with error
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
class AgentSession: ObservableObject {
|
|
264
|
+
@Published var state: AgentState = .idle
|
|
265
|
+
|
|
266
|
+
func transition(to newState: AgentState) {
|
|
267
|
+
let validTransitions: [AgentState: Set<AgentState>] = [
|
|
268
|
+
.idle: [.running],
|
|
269
|
+
.running: [.waitingForUser, .backgrounded, .completed, .failed],
|
|
270
|
+
.waitingForUser: [.running, .backgrounded],
|
|
271
|
+
.backgrounded: [.running, .completed],
|
|
272
|
+
]
|
|
273
|
+
|
|
274
|
+
guard validTransitions[state]?.contains(newState) == true else {
|
|
275
|
+
logger.warning("Invalid transition: \(state) → \(newState)")
|
|
276
|
+
return
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
state = newState
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
### Background Task Extension (iOS)
|
|
285
|
+
|
|
286
|
+
Request extra time when backgrounded during critical operations:
|
|
287
|
+
|
|
288
|
+
```swift
|
|
289
|
+
class AgentOrchestrator {
|
|
290
|
+
private var backgroundTask: UIBackgroundTaskIdentifier = .invalid
|
|
291
|
+
|
|
292
|
+
func handleAppWillBackground() {
|
|
293
|
+
// Request extra time for saving state
|
|
294
|
+
backgroundTask = UIApplication.shared.beginBackgroundTask { [weak self] in
|
|
295
|
+
self?.endBackgroundTask()
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Save all checkpoints
|
|
299
|
+
Task {
|
|
300
|
+
for session in activeSessions {
|
|
301
|
+
await saveCheckpoint(session)
|
|
302
|
+
}
|
|
303
|
+
endBackgroundTask()
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
private func endBackgroundTask() {
|
|
308
|
+
if backgroundTask != .invalid {
|
|
309
|
+
UIApplication.shared.endBackgroundTask(backgroundTask)
|
|
310
|
+
backgroundTask = .invalid
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
### User Communication
|
|
317
|
+
|
|
318
|
+
Let users know what's happening:
|
|
319
|
+
|
|
320
|
+
```swift
|
|
321
|
+
struct AgentStatusView: View {
|
|
322
|
+
@ObservedObject var session: AgentSession
|
|
323
|
+
|
|
324
|
+
var body: some View {
|
|
325
|
+
switch session.state {
|
|
326
|
+
case .backgrounded:
|
|
327
|
+
Label("Paused (app in background)", systemImage: "pause.circle")
|
|
328
|
+
.foregroundColor(.orange)
|
|
329
|
+
case .running:
|
|
330
|
+
Label("Working...", systemImage: "ellipsis.circle")
|
|
331
|
+
.foregroundColor(.blue)
|
|
332
|
+
case .waitingForUser:
|
|
333
|
+
Label("Waiting for your input", systemImage: "person.circle")
|
|
334
|
+
.foregroundColor(.green)
|
|
335
|
+
// ...
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
```
|
|
340
|
+
</background_execution>
|
|
341
|
+
|
|
342
|
+
<permissions>
|
|
343
|
+
## Permission Handling
|
|
344
|
+
|
|
345
|
+
Mobile agents may need access to system resources. Handle permission requests gracefully.
|
|
346
|
+
|
|
347
|
+
### Common Permissions
|
|
348
|
+
|
|
349
|
+
| Resource | iOS Permission | Use Case |
|
|
350
|
+
|----------|---------------|----------|
|
|
351
|
+
| Photo Library | PHPhotoLibrary | Profile generation from photos |
|
|
352
|
+
| Files | Document picker | Reading user documents |
|
|
353
|
+
| Camera | AVCaptureDevice | Scanning book covers |
|
|
354
|
+
| Location | CLLocationManager | Location-aware recommendations |
|
|
355
|
+
| Network | (automatic) | Web search, API calls |
|
|
356
|
+
|
|
357
|
+
### Permission-Aware Tools
|
|
358
|
+
|
|
359
|
+
Check permissions before executing:
|
|
360
|
+
|
|
361
|
+
```swift
|
|
362
|
+
struct PhotoTools {
|
|
363
|
+
static func readPhotos() -> AgentTool {
|
|
364
|
+
tool(
|
|
365
|
+
name: "read_photos",
|
|
366
|
+
description: "Read photos from the user's photo library",
|
|
367
|
+
parameters: [
|
|
368
|
+
"limit": .number("Maximum photos to read"),
|
|
369
|
+
"dateRange": .string("Date range filter").optional()
|
|
370
|
+
],
|
|
371
|
+
execute: { params, context in
|
|
372
|
+
// Check permission first
|
|
373
|
+
let status = await PHPhotoLibrary.requestAuthorization(for: .readWrite)
|
|
374
|
+
|
|
375
|
+
switch status {
|
|
376
|
+
case .authorized, .limited:
|
|
377
|
+
// Proceed with reading photos
|
|
378
|
+
let photos = await fetchPhotos(params)
|
|
379
|
+
return ToolResult(text: "Found \(photos.count) photos", images: photos)
|
|
380
|
+
|
|
381
|
+
case .denied, .restricted:
|
|
382
|
+
return ToolResult(
|
|
383
|
+
text: "Photo access needed. Please grant permission in Settings → Privacy → Photos.",
|
|
384
|
+
isError: true
|
|
385
|
+
)
|
|
386
|
+
|
|
387
|
+
case .notDetermined:
|
|
388
|
+
return ToolResult(
|
|
389
|
+
text: "Photo permission required. Please try again.",
|
|
390
|
+
isError: true
|
|
391
|
+
)
|
|
392
|
+
|
|
393
|
+
@unknown default:
|
|
394
|
+
return ToolResult(text: "Unknown permission status", isError: true)
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
)
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
### Graceful Degradation
|
|
403
|
+
|
|
404
|
+
When permissions aren't granted, offer alternatives:
|
|
405
|
+
|
|
406
|
+
```swift
|
|
407
|
+
func readPhotos() async -> ToolResult {
|
|
408
|
+
let status = PHPhotoLibrary.authorizationStatus(for: .readWrite)
|
|
409
|
+
|
|
410
|
+
switch status {
|
|
411
|
+
case .denied, .restricted:
|
|
412
|
+
// Suggest alternative
|
|
413
|
+
return ToolResult(
|
|
414
|
+
text: """
|
|
415
|
+
I don't have access to your photos. You can either:
|
|
416
|
+
1. Grant access in Settings → Privacy → Photos
|
|
417
|
+
2. Share specific photos directly in our chat
|
|
418
|
+
|
|
419
|
+
Would you like me to help with something else instead?
|
|
420
|
+
""",
|
|
421
|
+
isError: false // Not a hard error, just a limitation
|
|
422
|
+
)
|
|
423
|
+
// ...
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
### Permission Request Timing
|
|
429
|
+
|
|
430
|
+
Don't request permissions until needed:
|
|
431
|
+
|
|
432
|
+
```swift
|
|
433
|
+
// BAD: Request all permissions at launch
|
|
434
|
+
func applicationDidFinishLaunching() {
|
|
435
|
+
requestPhotoAccess()
|
|
436
|
+
requestCameraAccess()
|
|
437
|
+
requestLocationAccess()
|
|
438
|
+
// User is overwhelmed with permission dialogs
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// GOOD: Request when the feature is used
|
|
442
|
+
tool("analyze_book_cover", async ({ image }) => {
|
|
443
|
+
// Only request camera access when user tries to scan a cover
|
|
444
|
+
let status = await AVCaptureDevice.requestAccess(for: .video)
|
|
445
|
+
if status {
|
|
446
|
+
return await scanCover(image)
|
|
447
|
+
} else {
|
|
448
|
+
return ToolResult(text: "Camera access needed for book scanning")
|
|
449
|
+
}
|
|
450
|
+
})
|
|
451
|
+
```
|
|
452
|
+
</permissions>
|
|
453
|
+
|
|
454
|
+
<cost_awareness>
|
|
455
|
+
## Cost-Aware Design
|
|
456
|
+
|
|
457
|
+
Mobile users may be on cellular data or concerned about API costs. Design agents to be efficient.
|
|
458
|
+
|
|
459
|
+
### Model Tier Selection
|
|
460
|
+
|
|
461
|
+
Use the cheapest model that achieves the outcome:
|
|
462
|
+
|
|
463
|
+
```swift
|
|
464
|
+
enum ModelTier {
|
|
465
|
+
case fast // gemini-flash: ~$0.25/1M tokens
|
|
466
|
+
case balanced // gemini-pro: ~$3/1M tokens
|
|
467
|
+
case powerful // gemini-ultra: ~$15/1M tokens
|
|
468
|
+
|
|
469
|
+
var modelId: String {
|
|
470
|
+
switch self {
|
|
471
|
+
case .fast: return "gemini-flash-20240307"
|
|
472
|
+
case .balanced: return "gemini-pro-20240229"
|
|
473
|
+
case .powerful: return "gemini-ultra-20240229"
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// Match model to task complexity
|
|
479
|
+
let agentConfigs: [AgentType: ModelTier] = [
|
|
480
|
+
.quickLookup: .fast, // "What's in my library?"
|
|
481
|
+
.chatAssistant: .balanced, // General conversation
|
|
482
|
+
.researchAgent: .balanced, // Web search + synthesis
|
|
483
|
+
.profileGenerator: .powerful, // Complex photo analysis
|
|
484
|
+
.introductionWriter: .balanced,
|
|
485
|
+
]
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
### Token Budgets
|
|
489
|
+
|
|
490
|
+
Limit tokens per agent session:
|
|
491
|
+
|
|
492
|
+
```swift
|
|
493
|
+
struct AgentConfig {
|
|
494
|
+
let modelTier: ModelTier
|
|
495
|
+
let maxInputTokens: Int
|
|
496
|
+
let maxOutputTokens: Int
|
|
497
|
+
let maxTurns: Int
|
|
498
|
+
|
|
499
|
+
static let research = AgentConfig(
|
|
500
|
+
modelTier: .balanced,
|
|
501
|
+
maxInputTokens: 50_000,
|
|
502
|
+
maxOutputTokens: 4_000,
|
|
503
|
+
maxTurns: 20
|
|
504
|
+
)
|
|
505
|
+
|
|
506
|
+
static let quickChat = AgentConfig(
|
|
507
|
+
modelTier: .fast,
|
|
508
|
+
maxInputTokens: 10_000,
|
|
509
|
+
maxOutputTokens: 1_000,
|
|
510
|
+
maxTurns: 5
|
|
511
|
+
)
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
class AgentSession {
|
|
515
|
+
var totalTokensUsed: Int = 0
|
|
516
|
+
|
|
517
|
+
func checkBudget() -> Bool {
|
|
518
|
+
if totalTokensUsed > config.maxInputTokens {
|
|
519
|
+
transition(to: .failed(AgentError.budgetExceeded))
|
|
520
|
+
return false
|
|
521
|
+
}
|
|
522
|
+
return true
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
### Network-Aware Execution
|
|
528
|
+
|
|
529
|
+
Defer heavy operations to WiFi:
|
|
530
|
+
|
|
531
|
+
```swift
|
|
532
|
+
class NetworkMonitor: ObservableObject {
|
|
533
|
+
@Published var isOnWiFi: Bool = false
|
|
534
|
+
@Published var isExpensive: Bool = false // Cellular or hotspot
|
|
535
|
+
|
|
536
|
+
private let monitor = NWPathMonitor()
|
|
537
|
+
|
|
538
|
+
func startMonitoring() {
|
|
539
|
+
monitor.pathUpdateHandler = { [weak self] path in
|
|
540
|
+
DispatchQueue.main.async {
|
|
541
|
+
self?.isOnWiFi = path.usesInterfaceType(.wifi)
|
|
542
|
+
self?.isExpensive = path.isExpensive
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
monitor.start(queue: .global())
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
class AgentOrchestrator {
|
|
550
|
+
@ObservedObject var network = NetworkMonitor()
|
|
551
|
+
|
|
552
|
+
func startResearchAgent(for book: Book) async {
|
|
553
|
+
if network.isExpensive {
|
|
554
|
+
// Warn user or defer
|
|
555
|
+
let proceed = await showAlert(
|
|
556
|
+
"Research uses data",
|
|
557
|
+
message: "This will use approximately 1-2 MB of cellular data. Continue?"
|
|
558
|
+
)
|
|
559
|
+
if !proceed { return }
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
// Proceed with research
|
|
563
|
+
await runAgent(ResearchAgent.create(book: book))
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
### Batch API Calls
|
|
569
|
+
|
|
570
|
+
Combine multiple small requests:
|
|
571
|
+
|
|
572
|
+
```swift
|
|
573
|
+
// BAD: Many small API calls
|
|
574
|
+
for book in books {
|
|
575
|
+
await agent.chat("Summarize \(book.title)")
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// GOOD: Batch into one request
|
|
579
|
+
let bookList = books.map { $0.title }.joined(separator: ", ")
|
|
580
|
+
await agent.chat("Summarize each of these books briefly: \(bookList)")
|
|
581
|
+
```
|
|
582
|
+
|
|
583
|
+
### Caching
|
|
584
|
+
|
|
585
|
+
Cache expensive operations:
|
|
586
|
+
|
|
587
|
+
```swift
|
|
588
|
+
class ResearchCache {
|
|
589
|
+
private var cache: [String: CachedResearch] = [:]
|
|
590
|
+
|
|
591
|
+
func getCachedResearch(for bookId: String) -> CachedResearch? {
|
|
592
|
+
guard let cached = cache[bookId] else { return nil }
|
|
593
|
+
|
|
594
|
+
// Expire after 24 hours
|
|
595
|
+
if Date().timeIntervalSince(cached.timestamp) > 86400 {
|
|
596
|
+
cache.removeValue(forKey: bookId)
|
|
597
|
+
return nil
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
return cached
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
func cacheResearch(_ research: Research, for bookId: String) {
|
|
604
|
+
cache[bookId] = CachedResearch(
|
|
605
|
+
research: research,
|
|
606
|
+
timestamp: Date()
|
|
607
|
+
)
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
// In research tool
|
|
612
|
+
tool("web_search", async ({ query, bookId }) => {
|
|
613
|
+
// Check cache first
|
|
614
|
+
if let cached = cache.getCachedResearch(for: bookId) {
|
|
615
|
+
return ToolResult(text: cached.research.summary, cached: true)
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
// Otherwise, perform search
|
|
619
|
+
let results = await webSearch(query)
|
|
620
|
+
cache.cacheResearch(results, for: bookId)
|
|
621
|
+
return ToolResult(text: results.summary)
|
|
622
|
+
})
|
|
623
|
+
```
|
|
624
|
+
|
|
625
|
+
### Cost Visibility
|
|
626
|
+
|
|
627
|
+
Show users what they're spending:
|
|
628
|
+
|
|
629
|
+
```swift
|
|
630
|
+
struct AgentCostView: View {
|
|
631
|
+
@ObservedObject var session: AgentSession
|
|
632
|
+
|
|
633
|
+
var body: some View {
|
|
634
|
+
VStack(alignment: .leading) {
|
|
635
|
+
Text("Session Stats")
|
|
636
|
+
.font(.headline)
|
|
637
|
+
|
|
638
|
+
HStack {
|
|
639
|
+
Label("\(session.turnCount) turns", systemImage: "arrow.2.squarepath")
|
|
640
|
+
Spacer()
|
|
641
|
+
Label(formatTokens(session.totalTokensUsed), systemImage: "text.word.spacing")
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
if let estimatedCost = session.estimatedCost {
|
|
645
|
+
Text("Est. cost: \(estimatedCost, format: .currency(code: "USD"))")
|
|
646
|
+
.font(.caption)
|
|
647
|
+
.foregroundColor(.secondary)
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
```
|
|
653
|
+
</cost_awareness>
|
|
654
|
+
|
|
655
|
+
<offline_handling>
|
|
656
|
+
## Offline Graceful Degradation
|
|
657
|
+
|
|
658
|
+
Handle offline scenarios gracefully:
|
|
659
|
+
|
|
660
|
+
```swift
|
|
661
|
+
class ConnectivityAwareAgent {
|
|
662
|
+
@ObservedObject var network = NetworkMonitor()
|
|
663
|
+
|
|
664
|
+
func executeToolCall(_ toolCall: ToolCall) async -> ToolResult {
|
|
665
|
+
// Check if tool requires network
|
|
666
|
+
let requiresNetwork = ["web_search", "web_fetch", "call_api"]
|
|
667
|
+
.contains(toolCall.name)
|
|
668
|
+
|
|
669
|
+
if requiresNetwork && !network.isConnected {
|
|
670
|
+
return ToolResult(
|
|
671
|
+
text: """
|
|
672
|
+
I can't access the internet right now. Here's what I can do offline:
|
|
673
|
+
- Read your library and existing research
|
|
674
|
+
- Answer questions from cached data
|
|
675
|
+
- Write notes and drafts for later
|
|
676
|
+
|
|
677
|
+
Would you like me to try something that works offline?
|
|
678
|
+
""",
|
|
679
|
+
isError: false
|
|
680
|
+
)
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
return await executeOnline(toolCall)
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
```
|
|
687
|
+
|
|
688
|
+
### Offline-First Tools
|
|
689
|
+
|
|
690
|
+
Some tools should work entirely offline:
|
|
691
|
+
|
|
692
|
+
```swift
|
|
693
|
+
let offlineTools: Set<String> = [
|
|
694
|
+
"read_file",
|
|
695
|
+
"write_file",
|
|
696
|
+
"list_files",
|
|
697
|
+
"read_library", // Local database
|
|
698
|
+
"search_local", // Local search
|
|
699
|
+
]
|
|
700
|
+
|
|
701
|
+
let onlineTools: Set<String> = [
|
|
702
|
+
"web_search",
|
|
703
|
+
"web_fetch",
|
|
704
|
+
"publish_to_cloud",
|
|
705
|
+
]
|
|
706
|
+
|
|
707
|
+
let hybridTools: Set<String> = [
|
|
708
|
+
"publish_to_feed", // Works offline, syncs later
|
|
709
|
+
]
|
|
710
|
+
```
|
|
711
|
+
|
|
712
|
+
### Queued Actions
|
|
713
|
+
|
|
714
|
+
Queue actions that require connectivity:
|
|
715
|
+
|
|
716
|
+
```swift
|
|
717
|
+
class OfflineQueue: ObservableObject {
|
|
718
|
+
@Published var pendingActions: [QueuedAction] = []
|
|
719
|
+
|
|
720
|
+
func queue(_ action: QueuedAction) {
|
|
721
|
+
pendingActions.append(action)
|
|
722
|
+
persist()
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
func processWhenOnline() {
|
|
726
|
+
network.$isConnected
|
|
727
|
+
.filter { $0 }
|
|
728
|
+
.sink { [weak self] _ in
|
|
729
|
+
self?.processPendingActions()
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
private func processPendingActions() {
|
|
734
|
+
for action in pendingActions {
|
|
735
|
+
Task {
|
|
736
|
+
try await execute(action)
|
|
737
|
+
remove(action)
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
```
|
|
743
|
+
</offline_handling>
|
|
744
|
+
|
|
745
|
+
<battery_awareness>
|
|
746
|
+
## Battery-Aware Execution
|
|
747
|
+
|
|
748
|
+
Respect device battery state:
|
|
749
|
+
|
|
750
|
+
```swift
|
|
751
|
+
class BatteryMonitor: ObservableObject {
|
|
752
|
+
@Published var batteryLevel: Float = 1.0
|
|
753
|
+
@Published var isCharging: Bool = false
|
|
754
|
+
@Published var isLowPowerMode: Bool = false
|
|
755
|
+
|
|
756
|
+
var shouldDeferHeavyWork: Bool {
|
|
757
|
+
return batteryLevel < 0.2 && !isCharging
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
func startMonitoring() {
|
|
761
|
+
UIDevice.current.isBatteryMonitoringEnabled = true
|
|
762
|
+
|
|
763
|
+
NotificationCenter.default.addObserver(
|
|
764
|
+
forName: UIDevice.batteryLevelDidChangeNotification,
|
|
765
|
+
object: nil,
|
|
766
|
+
queue: .main
|
|
767
|
+
) { [weak self] _ in
|
|
768
|
+
self?.batteryLevel = UIDevice.current.batteryLevel
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
NotificationCenter.default.addObserver(
|
|
772
|
+
forName: NSNotification.Name.NSProcessInfoPowerStateDidChange,
|
|
773
|
+
object: nil,
|
|
774
|
+
queue: .main
|
|
775
|
+
) { [weak self] _ in
|
|
776
|
+
self?.isLowPowerMode = ProcessInfo.processInfo.isLowPowerModeEnabled
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
class AgentOrchestrator {
|
|
782
|
+
@ObservedObject var battery = BatteryMonitor()
|
|
783
|
+
|
|
784
|
+
func startAgent(_ config: AgentConfig) async {
|
|
785
|
+
if battery.shouldDeferHeavyWork && config.isHeavy {
|
|
786
|
+
let proceed = await showAlert(
|
|
787
|
+
"Low Battery",
|
|
788
|
+
message: "This task uses significant battery. Continue or defer until charging?"
|
|
789
|
+
)
|
|
790
|
+
if !proceed { return }
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
// Adjust model tier based on battery
|
|
794
|
+
let adjustedConfig = battery.isLowPowerMode
|
|
795
|
+
? config.withModelTier(.fast)
|
|
796
|
+
: config
|
|
797
|
+
|
|
798
|
+
await runAgent(adjustedConfig)
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
```
|
|
802
|
+
</battery_awareness>
|
|
803
|
+
|
|
804
|
+
<on_device_vs_cloud>
|
|
805
|
+
## On-Device vs. Cloud
|
|
806
|
+
|
|
807
|
+
Understanding what runs where in a mobile agent-native app:
|
|
808
|
+
|
|
809
|
+
| Component | On-Device | Cloud |
|
|
810
|
+
|-----------|-----------|-------|
|
|
811
|
+
| Orchestration | ✅ | |
|
|
812
|
+
| Tool execution | ✅ (file ops, photo access, HealthKit) | |
|
|
813
|
+
| LLM calls | | ✅ (Antigravity API) |
|
|
814
|
+
| Checkpoints | ✅ (local files) | Optional via iCloud |
|
|
815
|
+
| Long-running agents | Limited by iOS | Possible with server |
|
|
816
|
+
|
|
817
|
+
### Implications
|
|
818
|
+
|
|
819
|
+
**Network required for reasoning:**
|
|
820
|
+
- The app needs network connectivity for LLM calls
|
|
821
|
+
- Design tools to degrade gracefully when network is unavailable
|
|
822
|
+
- Consider offline caching for common queries
|
|
823
|
+
|
|
824
|
+
**Data stays local:**
|
|
825
|
+
- File operations happen on device
|
|
826
|
+
- Sensitive data never leaves the device unless explicitly synced
|
|
827
|
+
- Privacy is preserved by default
|
|
828
|
+
|
|
829
|
+
**Long-running agents:**
|
|
830
|
+
For truly long-running agents (hours), consider a server-side orchestrator that can run indefinitely, with the mobile app as a viewer and input mechanism.
|
|
831
|
+
</on_device_vs_cloud>
|
|
832
|
+
|
|
833
|
+
<checklist>
|
|
834
|
+
## Mobile Agent-Native Checklist
|
|
835
|
+
|
|
836
|
+
**iOS Storage:**
|
|
837
|
+
- [ ] iCloud Documents as primary storage (or conscious alternative)
|
|
838
|
+
- [ ] Local Documents fallback when iCloud unavailable
|
|
839
|
+
- [ ] Handle `.icloud` placeholder files (trigger download)
|
|
840
|
+
- [ ] Use NSFileCoordinator for conflict-safe writes
|
|
841
|
+
|
|
842
|
+
**Background Execution:**
|
|
843
|
+
- [ ] Checkpoint/resume implemented for all agent sessions
|
|
844
|
+
- [ ] State machine for agent lifecycle (idle, running, backgrounded, etc.)
|
|
845
|
+
- [ ] Background task extension for critical saves (30 second window)
|
|
846
|
+
- [ ] User-visible status for backgrounded agents
|
|
847
|
+
|
|
848
|
+
**Permissions:**
|
|
849
|
+
- [ ] Permissions requested only when needed, not at launch
|
|
850
|
+
- [ ] Graceful degradation when permissions denied
|
|
851
|
+
- [ ] Clear error messages with Settings deep links
|
|
852
|
+
- [ ] Alternative paths when permissions unavailable
|
|
853
|
+
|
|
854
|
+
**Cost Awareness:**
|
|
855
|
+
- [ ] Model tier matched to task complexity
|
|
856
|
+
- [ ] Token budgets per session
|
|
857
|
+
- [ ] Network-aware (defer heavy work to WiFi)
|
|
858
|
+
- [ ] Caching for expensive operations
|
|
859
|
+
- [ ] Cost visibility to users
|
|
860
|
+
|
|
861
|
+
**Offline Handling:**
|
|
862
|
+
- [ ] Offline-capable tools identified
|
|
863
|
+
- [ ] Graceful degradation for online-only features
|
|
864
|
+
- [ ] Action queue for sync when online
|
|
865
|
+
- [ ] Clear user communication about offline state
|
|
866
|
+
|
|
867
|
+
**Battery Awareness:**
|
|
868
|
+
- [ ] Battery monitoring for heavy operations
|
|
869
|
+
- [ ] Low power mode detection
|
|
870
|
+
- [ ] Defer or downgrade based on battery state
|
|
871
|
+
</checklist>
|