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,680 @@
|
|
|
1
|
+
<overview>
|
|
2
|
+
Agents and users should work in the same data space, not separate sandboxes. When the agent writes a file, the user can see it. When the user edits something, the agent can read the changes. This creates transparency, enables collaboration, and eliminates the need for sync layers.
|
|
3
|
+
|
|
4
|
+
**Core principle:** The agent operates in the same filesystem as the user, not a walled garden.
|
|
5
|
+
</overview>
|
|
6
|
+
|
|
7
|
+
<why_shared_workspace>
|
|
8
|
+
## Why Shared Workspace?
|
|
9
|
+
|
|
10
|
+
### The Sandbox Anti-Pattern
|
|
11
|
+
|
|
12
|
+
Many agent implementations isolate the agent:
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
┌─────────────────┐ ┌─────────────────┐
|
|
16
|
+
│ User Space │ │ Agent Space │
|
|
17
|
+
├─────────────────┤ ├─────────────────┤
|
|
18
|
+
│ Documents/ │ │ agent_output/ │
|
|
19
|
+
│ user_files/ │ ←→ │ temp_files/ │
|
|
20
|
+
│ settings.json │sync │ cache/ │
|
|
21
|
+
└─────────────────┘ └─────────────────┘
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Problems:
|
|
25
|
+
- Need a sync layer to move data between spaces
|
|
26
|
+
- User can't easily inspect agent work
|
|
27
|
+
- Agent can't build on user contributions
|
|
28
|
+
- Duplication of state
|
|
29
|
+
- Complexity in keeping spaces consistent
|
|
30
|
+
|
|
31
|
+
### The Shared Workspace Pattern
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
┌─────────────────────────────────────────┐
|
|
35
|
+
│ Shared Workspace │
|
|
36
|
+
├─────────────────────────────────────────┤
|
|
37
|
+
│ Documents/ │
|
|
38
|
+
│ ├── Research/ │
|
|
39
|
+
│ │ └── {bookId}/ ← Agent writes │
|
|
40
|
+
│ │ ├── full_text.txt │
|
|
41
|
+
│ │ ├── introduction.md ← User can edit │
|
|
42
|
+
│ │ └── sources/ │
|
|
43
|
+
│ ├── Chats/ ← Both read/write │
|
|
44
|
+
│ └── profile.md ← Agent generates, user refines │
|
|
45
|
+
└─────────────────────────────────────────┘
|
|
46
|
+
↑ ↑
|
|
47
|
+
User Agent
|
|
48
|
+
(UI) (Tools)
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Benefits:
|
|
52
|
+
- Users can inspect, edit, and extend agent work
|
|
53
|
+
- Agents can build on user contributions
|
|
54
|
+
- No synchronization layer needed
|
|
55
|
+
- Complete transparency
|
|
56
|
+
- Single source of truth
|
|
57
|
+
</why_shared_workspace>
|
|
58
|
+
|
|
59
|
+
<directory_structure>
|
|
60
|
+
## Designing Your Shared Workspace
|
|
61
|
+
|
|
62
|
+
### Structure by Domain
|
|
63
|
+
|
|
64
|
+
Organize by what the data represents, not who created it:
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
Documents/
|
|
68
|
+
├── Research/
|
|
69
|
+
│ └── {bookId}/
|
|
70
|
+
│ ├── full_text.txt # Agent downloads
|
|
71
|
+
│ ├── introduction.md # Agent generates, user can edit
|
|
72
|
+
│ ├── notes.md # User adds, agent can read
|
|
73
|
+
│ └── sources/
|
|
74
|
+
│ └── {source}.md # Agent gathers
|
|
75
|
+
├── Chats/
|
|
76
|
+
│ └── {conversationId}.json # Both read/write
|
|
77
|
+
├── Exports/
|
|
78
|
+
│ └── {date}/ # Agent generates for user
|
|
79
|
+
└── profile.md # Agent generates from photos
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Don't Structure by Actor
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
# BAD - Separates by who created it
|
|
86
|
+
Documents/
|
|
87
|
+
├── user_created/
|
|
88
|
+
│ └── notes.md
|
|
89
|
+
├── agent_created/
|
|
90
|
+
│ └── research.md
|
|
91
|
+
└── system/
|
|
92
|
+
└── config.json
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
This creates artificial boundaries and makes collaboration harder.
|
|
96
|
+
|
|
97
|
+
### Use Conventions for Metadata
|
|
98
|
+
|
|
99
|
+
If you need to track who created/modified something:
|
|
100
|
+
|
|
101
|
+
```markdown
|
|
102
|
+
<!-- introduction.md -->
|
|
103
|
+
---
|
|
104
|
+
created_by: agent
|
|
105
|
+
created_at: 2024-01-15
|
|
106
|
+
last_modified_by: user
|
|
107
|
+
last_modified_at: 2024-01-16
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
# Introduction to Moby Dick
|
|
111
|
+
|
|
112
|
+
This personalized introduction was generated by your reading assistant
|
|
113
|
+
and refined by you on January 16th.
|
|
114
|
+
```
|
|
115
|
+
</directory_structure>
|
|
116
|
+
|
|
117
|
+
<file_tools>
|
|
118
|
+
## File Tools for Shared Workspace
|
|
119
|
+
|
|
120
|
+
Give the agent the same file primitives the app uses:
|
|
121
|
+
|
|
122
|
+
```swift
|
|
123
|
+
// iOS/Swift implementation
|
|
124
|
+
struct FileTools {
|
|
125
|
+
static func readFile() -> AgentTool {
|
|
126
|
+
tool(
|
|
127
|
+
name: "read_file",
|
|
128
|
+
description: "Read a file from the user's documents",
|
|
129
|
+
parameters: ["path": .string("File path relative to Documents/")],
|
|
130
|
+
execute: { params in
|
|
131
|
+
let path = params["path"] as! String
|
|
132
|
+
let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
|
|
133
|
+
let fileURL = documentsURL.appendingPathComponent(path)
|
|
134
|
+
let content = try String(contentsOf: fileURL)
|
|
135
|
+
return ToolResult(text: content)
|
|
136
|
+
}
|
|
137
|
+
)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
static func writeFile() -> AgentTool {
|
|
141
|
+
tool(
|
|
142
|
+
name: "write_file",
|
|
143
|
+
description: "Write a file to the user's documents",
|
|
144
|
+
parameters: [
|
|
145
|
+
"path": .string("File path relative to Documents/"),
|
|
146
|
+
"content": .string("File content")
|
|
147
|
+
],
|
|
148
|
+
execute: { params in
|
|
149
|
+
let path = params["path"] as! String
|
|
150
|
+
let content = params["content"] as! String
|
|
151
|
+
let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
|
|
152
|
+
let fileURL = documentsURL.appendingPathComponent(path)
|
|
153
|
+
|
|
154
|
+
// Create parent directories if needed
|
|
155
|
+
try FileManager.default.createDirectory(
|
|
156
|
+
at: fileURL.deletingLastPathComponent(),
|
|
157
|
+
withIntermediateDirectories: true
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
try content.write(to: fileURL, atomically: true, encoding: .utf8)
|
|
161
|
+
return ToolResult(text: "Wrote \(path)")
|
|
162
|
+
}
|
|
163
|
+
)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
static func listFiles() -> AgentTool {
|
|
167
|
+
tool(
|
|
168
|
+
name: "list_files",
|
|
169
|
+
description: "List files in a directory",
|
|
170
|
+
parameters: ["path": .string("Directory path relative to Documents/")],
|
|
171
|
+
execute: { params in
|
|
172
|
+
let path = params["path"] as! String
|
|
173
|
+
let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
|
|
174
|
+
let dirURL = documentsURL.appendingPathComponent(path)
|
|
175
|
+
let contents = try FileManager.default.contentsOfDirectory(atPath: dirURL.path)
|
|
176
|
+
return ToolResult(text: contents.joined(separator: "\n"))
|
|
177
|
+
}
|
|
178
|
+
)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
static func searchText() -> AgentTool {
|
|
182
|
+
tool(
|
|
183
|
+
name: "search_text",
|
|
184
|
+
description: "Search for text across files",
|
|
185
|
+
parameters: [
|
|
186
|
+
"query": .string("Text to search for"),
|
|
187
|
+
"path": .string("Directory to search in").optional()
|
|
188
|
+
],
|
|
189
|
+
execute: { params in
|
|
190
|
+
// Implement text search across documents
|
|
191
|
+
// Return matching files and snippets
|
|
192
|
+
}
|
|
193
|
+
)
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### TypeScript/Node.js Implementation
|
|
199
|
+
|
|
200
|
+
```typescript
|
|
201
|
+
const fileTools = [
|
|
202
|
+
tool(
|
|
203
|
+
"read_file",
|
|
204
|
+
"Read a file from the workspace",
|
|
205
|
+
{ path: z.string().describe("File path") },
|
|
206
|
+
async ({ path }) => {
|
|
207
|
+
const content = await fs.readFile(path, 'utf-8');
|
|
208
|
+
return { text: content };
|
|
209
|
+
}
|
|
210
|
+
),
|
|
211
|
+
|
|
212
|
+
tool(
|
|
213
|
+
"write_file",
|
|
214
|
+
"Write a file to the workspace",
|
|
215
|
+
{
|
|
216
|
+
path: z.string().describe("File path"),
|
|
217
|
+
content: z.string().describe("File content")
|
|
218
|
+
},
|
|
219
|
+
async ({ path, content }) => {
|
|
220
|
+
await fs.mkdir(dirname(path), { recursive: true });
|
|
221
|
+
await fs.writeFile(path, content, 'utf-8');
|
|
222
|
+
return { text: `Wrote ${path}` };
|
|
223
|
+
}
|
|
224
|
+
),
|
|
225
|
+
|
|
226
|
+
tool(
|
|
227
|
+
"list_files",
|
|
228
|
+
"List files in a directory",
|
|
229
|
+
{ path: z.string().describe("Directory path") },
|
|
230
|
+
async ({ path }) => {
|
|
231
|
+
const files = await fs.readdir(path);
|
|
232
|
+
return { text: files.join('\n') };
|
|
233
|
+
}
|
|
234
|
+
),
|
|
235
|
+
|
|
236
|
+
tool(
|
|
237
|
+
"append_file",
|
|
238
|
+
"Append content to a file",
|
|
239
|
+
{
|
|
240
|
+
path: z.string().describe("File path"),
|
|
241
|
+
content: z.string().describe("Content to append")
|
|
242
|
+
},
|
|
243
|
+
async ({ path, content }) => {
|
|
244
|
+
await fs.appendFile(path, content, 'utf-8');
|
|
245
|
+
return { text: `Appended to ${path}` };
|
|
246
|
+
}
|
|
247
|
+
),
|
|
248
|
+
];
|
|
249
|
+
```
|
|
250
|
+
</file_tools>
|
|
251
|
+
|
|
252
|
+
<ui_integration>
|
|
253
|
+
## UI Integration with Shared Workspace
|
|
254
|
+
|
|
255
|
+
The UI should observe the same files the agent writes to:
|
|
256
|
+
|
|
257
|
+
### Pattern 1: File-Based Reactivity (iOS)
|
|
258
|
+
|
|
259
|
+
```swift
|
|
260
|
+
class ResearchViewModel: ObservableObject {
|
|
261
|
+
@Published var researchFiles: [ResearchFile] = []
|
|
262
|
+
|
|
263
|
+
private var watcher: DirectoryWatcher?
|
|
264
|
+
|
|
265
|
+
func startWatching(bookId: String) {
|
|
266
|
+
let researchPath = documentsURL
|
|
267
|
+
.appendingPathComponent("Research")
|
|
268
|
+
.appendingPathComponent(bookId)
|
|
269
|
+
|
|
270
|
+
watcher = DirectoryWatcher(url: researchPath) { [weak self] in
|
|
271
|
+
// Reload when agent writes new files
|
|
272
|
+
self?.loadResearchFiles(from: researchPath)
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
loadResearchFiles(from: researchPath)
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// SwiftUI automatically updates when files change
|
|
280
|
+
struct ResearchView: View {
|
|
281
|
+
@StateObject var viewModel = ResearchViewModel()
|
|
282
|
+
|
|
283
|
+
var body: some View {
|
|
284
|
+
List(viewModel.researchFiles) { file in
|
|
285
|
+
ResearchFileRow(file: file)
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
### Pattern 2: Shared Data Store
|
|
292
|
+
|
|
293
|
+
When file-watching isn't practical, use a shared data store:
|
|
294
|
+
|
|
295
|
+
```swift
|
|
296
|
+
// Shared service that both UI and agent tools use
|
|
297
|
+
class BookLibraryService: ObservableObject {
|
|
298
|
+
static let shared = BookLibraryService()
|
|
299
|
+
|
|
300
|
+
@Published var books: [Book] = []
|
|
301
|
+
@Published var analysisRecords: [AnalysisRecord] = []
|
|
302
|
+
|
|
303
|
+
func addAnalysisRecord(_ record: AnalysisRecord) {
|
|
304
|
+
analysisRecords.append(record)
|
|
305
|
+
// Persists to shared storage
|
|
306
|
+
saveToStorage()
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Agent tool writes through the same service
|
|
311
|
+
tool("publish_to_feed", async ({ bookId, content, headline }) => {
|
|
312
|
+
let record = AnalysisRecord(bookId: bookId, content: content, headline: headline)
|
|
313
|
+
BookLibraryService.shared.addAnalysisRecord(record)
|
|
314
|
+
return { text: "Published to feed" }
|
|
315
|
+
})
|
|
316
|
+
|
|
317
|
+
// UI observes the same service
|
|
318
|
+
struct FeedView: View {
|
|
319
|
+
@StateObject var library = BookLibraryService.shared
|
|
320
|
+
|
|
321
|
+
var body: some View {
|
|
322
|
+
List(library.analysisRecords) { record in
|
|
323
|
+
FeedItemRow(record: record)
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
### Pattern 3: Hybrid (Files + Index)
|
|
330
|
+
|
|
331
|
+
Use files for content, database for indexing:
|
|
332
|
+
|
|
333
|
+
```
|
|
334
|
+
Documents/
|
|
335
|
+
├── Research/
|
|
336
|
+
│ └── book_123/
|
|
337
|
+
│ └── introduction.md # Actual content (file)
|
|
338
|
+
|
|
339
|
+
Database:
|
|
340
|
+
├── research_index
|
|
341
|
+
│ └── { bookId: "book_123", path: "Research/book_123/introduction.md", ... }
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
```swift
|
|
345
|
+
// Agent writes file
|
|
346
|
+
await writeFile("Research/\(bookId)/introduction.md", content)
|
|
347
|
+
|
|
348
|
+
// And updates index
|
|
349
|
+
await database.insert("research_index", {
|
|
350
|
+
bookId: bookId,
|
|
351
|
+
path: "Research/\(bookId)/introduction.md",
|
|
352
|
+
title: extractTitle(content),
|
|
353
|
+
createdAt: Date()
|
|
354
|
+
})
|
|
355
|
+
|
|
356
|
+
// UI queries index, then reads files
|
|
357
|
+
let items = database.query("research_index", where: bookId == "book_123")
|
|
358
|
+
for item in items {
|
|
359
|
+
let content = readFile(item.path)
|
|
360
|
+
// Display...
|
|
361
|
+
}
|
|
362
|
+
```
|
|
363
|
+
</ui_integration>
|
|
364
|
+
|
|
365
|
+
<collaboration_patterns>
|
|
366
|
+
## Agent-User Collaboration Patterns
|
|
367
|
+
|
|
368
|
+
### Pattern: Agent Drafts, User Refines
|
|
369
|
+
|
|
370
|
+
```
|
|
371
|
+
1. Agent generates introduction.md
|
|
372
|
+
2. User opens in Files app or in-app editor
|
|
373
|
+
3. User makes refinements
|
|
374
|
+
4. Agent can see changes via read_file
|
|
375
|
+
5. Future agent work builds on user refinements
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
The agent's system prompt should acknowledge this:
|
|
379
|
+
|
|
380
|
+
```markdown
|
|
381
|
+
## Working with User Content
|
|
382
|
+
|
|
383
|
+
When you create content (introductions, research notes, etc.), the user may
|
|
384
|
+
edit it afterward. Always read existing files before modifying them—the user
|
|
385
|
+
may have made improvements you should preserve.
|
|
386
|
+
|
|
387
|
+
If a file exists and has been modified by the user (check the metadata or
|
|
388
|
+
compare to your last known version), ask before overwriting.
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
### Pattern: User Seeds, Agent Expands
|
|
392
|
+
|
|
393
|
+
```
|
|
394
|
+
1. User creates notes.md with initial thoughts
|
|
395
|
+
2. User asks: "Research more about this"
|
|
396
|
+
3. Agent reads notes.md to understand context
|
|
397
|
+
4. Agent adds to notes.md or creates related files
|
|
398
|
+
5. User continues building on agent additions
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
### Pattern: Append-Only Collaboration
|
|
402
|
+
|
|
403
|
+
For chat logs or activity streams:
|
|
404
|
+
|
|
405
|
+
```markdown
|
|
406
|
+
<!-- activity.md - Both append, neither overwrites -->
|
|
407
|
+
|
|
408
|
+
## 2024-01-15
|
|
409
|
+
|
|
410
|
+
**User:** Started reading "Moby Dick"
|
|
411
|
+
|
|
412
|
+
**Agent:** Downloaded full text and created research folder
|
|
413
|
+
|
|
414
|
+
**User:** Added highlight about whale symbolism
|
|
415
|
+
|
|
416
|
+
**Agent:** Found 3 academic sources on whale symbolism in Melville's work
|
|
417
|
+
```
|
|
418
|
+
</collaboration_patterns>
|
|
419
|
+
|
|
420
|
+
<security_considerations>
|
|
421
|
+
## Security in Shared Workspace
|
|
422
|
+
|
|
423
|
+
### Scope the Workspace
|
|
424
|
+
|
|
425
|
+
Don't give agents access to the entire filesystem:
|
|
426
|
+
|
|
427
|
+
```swift
|
|
428
|
+
// GOOD: Scoped to app's documents
|
|
429
|
+
let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
|
|
430
|
+
|
|
431
|
+
tool("read_file", { path }) {
|
|
432
|
+
// Path is relative to documents, can't escape
|
|
433
|
+
let fileURL = documentsURL.appendingPathComponent(path)
|
|
434
|
+
guard fileURL.path.hasPrefix(documentsURL.path) else {
|
|
435
|
+
throw ToolError("Invalid path")
|
|
436
|
+
}
|
|
437
|
+
return try String(contentsOf: fileURL)
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// BAD: Absolute paths allow escape
|
|
441
|
+
tool("read_file", { path }) {
|
|
442
|
+
return try String(contentsOf: URL(fileURLWithPath: path)) // Can read /etc/passwd!
|
|
443
|
+
}
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
### Protect Sensitive Files
|
|
447
|
+
|
|
448
|
+
```swift
|
|
449
|
+
let protectedPaths = [".env", "credentials.json", "secrets/"]
|
|
450
|
+
|
|
451
|
+
tool("read_file", { path }) {
|
|
452
|
+
if protectedPaths.any({ path.contains($0) }) {
|
|
453
|
+
throw ToolError("Cannot access protected file")
|
|
454
|
+
}
|
|
455
|
+
// ...
|
|
456
|
+
}
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
### Audit Agent Actions
|
|
460
|
+
|
|
461
|
+
Log what the agent reads/writes:
|
|
462
|
+
|
|
463
|
+
```swift
|
|
464
|
+
func logFileAccess(action: String, path: String, agentId: String) {
|
|
465
|
+
logger.info("[\(agentId)] \(action): \(path)")
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
tool("write_file", { path, content }) {
|
|
469
|
+
logFileAccess(action: "WRITE", path: path, agentId: context.agentId)
|
|
470
|
+
// ...
|
|
471
|
+
}
|
|
472
|
+
```
|
|
473
|
+
</security_considerations>
|
|
474
|
+
|
|
475
|
+
<examples>
|
|
476
|
+
## Real-World Example: Every Reader
|
|
477
|
+
|
|
478
|
+
The Every Reader app uses shared workspace for research:
|
|
479
|
+
|
|
480
|
+
```
|
|
481
|
+
Documents/
|
|
482
|
+
├── Research/
|
|
483
|
+
│ └── book_moby_dick/
|
|
484
|
+
│ ├── full_text.txt # Agent downloads from Gutenberg
|
|
485
|
+
│ ├── introduction.md # Agent generates, personalized
|
|
486
|
+
│ ├── sources/
|
|
487
|
+
│ │ ├── whale_symbolism.md # Agent researches
|
|
488
|
+
│ │ └── melville_bio.md # Agent researches
|
|
489
|
+
│ └── user_notes.md # User can add their own notes
|
|
490
|
+
├── Chats/
|
|
491
|
+
│ └── 2024-01-15.json # Chat history
|
|
492
|
+
└── profile.md # Agent generated from photos
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
**How it works:**
|
|
496
|
+
|
|
497
|
+
1. User adds "Moby Dick" to library
|
|
498
|
+
2. User starts research agent
|
|
499
|
+
3. Agent downloads full text to `Research/book_moby_dick/full_text.txt`
|
|
500
|
+
4. Agent researches and writes to `sources/`
|
|
501
|
+
5. Agent generates `introduction.md` based on user's reading profile
|
|
502
|
+
6. User can view all files in the app or Files.app
|
|
503
|
+
7. User can edit `introduction.md` to refine it
|
|
504
|
+
8. Chat agent can read all of this context when answering questions
|
|
505
|
+
</examples>
|
|
506
|
+
|
|
507
|
+
<icloud_sync>
|
|
508
|
+
## iCloud File Storage for Multi-Device Sync (iOS)
|
|
509
|
+
|
|
510
|
+
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.
|
|
511
|
+
|
|
512
|
+
### Why iCloud Documents?
|
|
513
|
+
|
|
514
|
+
| Approach | Cost | Complexity | Offline | Multi-Device |
|
|
515
|
+
|----------|------|------------|---------|--------------|
|
|
516
|
+
| Custom backend + sync | $$$ | High | Manual | Yes |
|
|
517
|
+
| CloudKit database | Free tier limits | Medium | Manual | Yes |
|
|
518
|
+
| **iCloud Documents** | Free (user's storage) | Low | Automatic | Automatic |
|
|
519
|
+
|
|
520
|
+
iCloud Documents:
|
|
521
|
+
- Uses user's existing iCloud storage (free 5GB, most users have more)
|
|
522
|
+
- Automatic sync across all user's devices
|
|
523
|
+
- Works offline, syncs when online
|
|
524
|
+
- Files visible in Files.app for transparency
|
|
525
|
+
- No server costs, no sync code to maintain
|
|
526
|
+
|
|
527
|
+
### Implementation Pattern
|
|
528
|
+
|
|
529
|
+
```swift
|
|
530
|
+
// Get the iCloud Documents container
|
|
531
|
+
func iCloudDocumentsURL() -> URL? {
|
|
532
|
+
FileManager.default.url(forUbiquityContainerIdentifier: nil)?
|
|
533
|
+
.appendingPathComponent("Documents")
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
// Your shared workspace lives in iCloud
|
|
537
|
+
class SharedWorkspace {
|
|
538
|
+
let rootURL: URL
|
|
539
|
+
|
|
540
|
+
init() {
|
|
541
|
+
// Use iCloud if available, fall back to local
|
|
542
|
+
if let iCloudURL = iCloudDocumentsURL() {
|
|
543
|
+
self.rootURL = iCloudURL
|
|
544
|
+
} else {
|
|
545
|
+
// Fallback to local Documents (user not signed into iCloud)
|
|
546
|
+
self.rootURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
// All file operations go through this root
|
|
551
|
+
func researchPath(for bookId: String) -> URL {
|
|
552
|
+
rootURL.appendingPathComponent("Research/\(bookId)")
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
func journalPath() -> URL {
|
|
556
|
+
rootURL.appendingPathComponent("Journal")
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
### Directory Structure in iCloud
|
|
562
|
+
|
|
563
|
+
```
|
|
564
|
+
iCloud Drive/
|
|
565
|
+
└── YourApp/ # Your app's container
|
|
566
|
+
└── Documents/ # Visible in Files.app
|
|
567
|
+
├── Journal/
|
|
568
|
+
│ ├── user/
|
|
569
|
+
│ │ └── 2025-01-15.md # Syncs across devices
|
|
570
|
+
│ └── agent/
|
|
571
|
+
│ └── 2025-01-15.md # Agent observations sync too
|
|
572
|
+
├── Experiments/
|
|
573
|
+
│ └── magnesium-sleep/
|
|
574
|
+
│ ├── config.json
|
|
575
|
+
│ └── log.json
|
|
576
|
+
└── Research/
|
|
577
|
+
└── {topic}/
|
|
578
|
+
└── sources.md
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
### Handling Sync Conflicts
|
|
582
|
+
|
|
583
|
+
iCloud handles conflicts automatically, but you should design for it:
|
|
584
|
+
|
|
585
|
+
```swift
|
|
586
|
+
// Check for conflicts when reading
|
|
587
|
+
func readJournalEntry(at url: URL) throws -> JournalEntry {
|
|
588
|
+
// iCloud may create .icloud placeholder files for not-yet-downloaded content
|
|
589
|
+
if url.pathExtension == "icloud" {
|
|
590
|
+
// Trigger download
|
|
591
|
+
try FileManager.default.startDownloadingUbiquitousItem(at: url)
|
|
592
|
+
throw FileNotYetAvailableError()
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
let data = try Data(contentsOf: url)
|
|
596
|
+
return try JSONDecoder().decode(JournalEntry.self, from: data)
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
// For writes, use coordinated file access
|
|
600
|
+
func writeJournalEntry(_ entry: JournalEntry, to url: URL) throws {
|
|
601
|
+
let coordinator = NSFileCoordinator()
|
|
602
|
+
var error: NSError?
|
|
603
|
+
|
|
604
|
+
coordinator.coordinate(writingItemAt: url, options: .forReplacing, error: &error) { newURL in
|
|
605
|
+
let data = try? JSONEncoder().encode(entry)
|
|
606
|
+
try? data?.write(to: newURL)
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
if let error = error {
|
|
610
|
+
throw error
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
```
|
|
614
|
+
|
|
615
|
+
### What This Enables
|
|
616
|
+
|
|
617
|
+
1. **User starts experiment on iPhone** → Agent creates `Experiments/sleep-tracking/config.json`
|
|
618
|
+
2. **User opens app on iPad** → Same experiment visible, no sync code needed
|
|
619
|
+
3. **Agent logs observation on iPhone** → Syncs to iPad automatically
|
|
620
|
+
4. **User edits journal on iPad** → iPhone sees the edit
|
|
621
|
+
|
|
622
|
+
### Entitlements Required
|
|
623
|
+
|
|
624
|
+
Add to your app's entitlements:
|
|
625
|
+
|
|
626
|
+
```xml
|
|
627
|
+
<key>com.apple.developer.icloud-container-identifiers</key>
|
|
628
|
+
<array>
|
|
629
|
+
<string>iCloud.com.yourcompany.yourapp</string>
|
|
630
|
+
</array>
|
|
631
|
+
<key>com.apple.developer.icloud-services</key>
|
|
632
|
+
<array>
|
|
633
|
+
<string>CloudDocuments</string>
|
|
634
|
+
</array>
|
|
635
|
+
<key>com.apple.developer.ubiquity-container-identifiers</key>
|
|
636
|
+
<array>
|
|
637
|
+
<string>iCloud.com.yourcompany.yourapp</string>
|
|
638
|
+
</array>
|
|
639
|
+
```
|
|
640
|
+
|
|
641
|
+
### When NOT to Use iCloud Documents
|
|
642
|
+
|
|
643
|
+
- **Sensitive data** - Use Keychain or encrypted local storage instead
|
|
644
|
+
- **High-frequency writes** - iCloud sync has latency; use local + periodic sync
|
|
645
|
+
- **Large media files** - Consider CloudKit Assets or on-demand resources
|
|
646
|
+
- **Shared between users** - iCloud Documents is single-user; use CloudKit for sharing
|
|
647
|
+
</icloud_sync>
|
|
648
|
+
|
|
649
|
+
<checklist>
|
|
650
|
+
## Shared Workspace Checklist
|
|
651
|
+
|
|
652
|
+
Architecture:
|
|
653
|
+
- [ ] Single shared directory for agent and user data
|
|
654
|
+
- [ ] Organized by domain, not by actor
|
|
655
|
+
- [ ] File tools scoped to workspace (no escape)
|
|
656
|
+
- [ ] Protected paths for sensitive files
|
|
657
|
+
|
|
658
|
+
Tools:
|
|
659
|
+
- [ ] `read_file` - Read any file in workspace
|
|
660
|
+
- [ ] `write_file` - Write any file in workspace
|
|
661
|
+
- [ ] `list_files` - Browse directory structure
|
|
662
|
+
- [ ] `search_text` - Find content across files (optional)
|
|
663
|
+
|
|
664
|
+
UI Integration:
|
|
665
|
+
- [ ] UI observes same files agent writes
|
|
666
|
+
- [ ] Changes reflect immediately (file watching or shared store)
|
|
667
|
+
- [ ] User can edit agent-created files
|
|
668
|
+
- [ ] Agent reads user modifications before overwriting
|
|
669
|
+
|
|
670
|
+
Collaboration:
|
|
671
|
+
- [ ] System prompt acknowledges user may edit files
|
|
672
|
+
- [ ] Agent checks for user modifications before overwriting
|
|
673
|
+
- [ ] Metadata tracks who created/modified (optional)
|
|
674
|
+
|
|
675
|
+
Multi-Device (iOS):
|
|
676
|
+
- [ ] Use iCloud Documents for shared workspace (free sync)
|
|
677
|
+
- [ ] Fallback to local Documents if iCloud unavailable
|
|
678
|
+
- [ ] Handle `.icloud` placeholder files (trigger download)
|
|
679
|
+
- [ ] Use NSFileCoordinator for conflict-safe writes
|
|
680
|
+
</checklist>
|