* [2.17.0] Expand agent-native skill with mobile app learnings Major expansion of agent-native-architecture skill based on real-world learnings from building the Every Reader iOS app. New reference documents: - dynamic-context-injection.md: Runtime app state in system prompts - action-parity-discipline.md: Ensuring agents can do what users can - shared-workspace-architecture.md: Agents and users in same data space - agent-native-testing.md: Testing patterns for agent-native apps - mobile-patterns.md: Background execution, permissions, cost awareness Updated references: - architecture-patterns.md: Added Unified Agent Architecture, Agent-to-UI Communication, and Model Tier Selection patterns Enhanced agent-native-reviewer with comprehensive review process covering all new patterns, including mobile-specific verification. Key insight: "The agent should be able to do anything the user can do, through tools that mirror UI capabilities, with full context about the app state." 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * [2.18.0] Add Dynamic Capability Discovery and iCloud sync patterns New patterns in agent-native-architecture skill: - **Dynamic Capability Discovery** - For agent-native apps integrating with external APIs (HealthKit, HomeKit, GraphQL), use a discovery tool (list_*) plus a generic access tool instead of individual tools per endpoint. (Note: Static mapping is fine for constrained agents with limited scope.) - **CRUD Completeness** - Every entity needs create, read, update, AND delete. - **iCloud File Storage** - Use iCloud Documents for shared workspace to get free, automatic multi-device sync without building a sync layer. - **Architecture Review Checklist** - Pushes reviewer findings earlier into design phase. Covers tool design, action parity, UI integration, context. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
9.4 KiB
Core principle: The user's context IS the agent's context.
<why_context_matters>
Why Dynamic Context Injection?
A static system prompt tells the agent what it CAN do. Dynamic context tells it what it can do RIGHT NOW with the user's actual data.
The failure case:
User: "Write a little thing about Catherine the Great in my reading feed"
Agent: "What system are you referring to? I'm not sure what reading feed means."
The agent failed because it didn't know:
- What books exist in the user's library
- What the "reading feed" is
- What tools it has to publish there
The fix: Inject runtime context about app state into the system prompt. </why_context_matters>
## The Context Injection PatternBuild your system prompt dynamically, including current app state:
func buildSystemPrompt() -> String {
// Gather current state
let availableBooks = libraryService.books
let recentActivity = analysisService.recentRecords(limit: 10)
let userProfile = profileService.currentProfile
return """
# Your Identity
You are a reading assistant for \(userProfile.name)'s library.
## Available Books in User's Library
\(availableBooks.map { "- \"\($0.title)\" by \($0.author) (id: \($0.id))" }.joined(separator: "\n"))
## Recent Reading Activity
\(recentActivity.map { "- Analyzed \"\($0.bookTitle)\": \($0.excerptPreview)" }.joined(separator: "\n"))
## Your Capabilities
- **publish_to_feed**: Create insights that appear in the Feed tab
- **read_library**: View books, highlights, and analyses
- **web_search**: Search the internet for research
- **write_file**: Save research to Documents/Research/{bookId}/
When the user mentions "the feed" or "reading feed", they mean the Feed tab
where insights appear. Use `publish_to_feed` to create content there.
"""
}
<what_to_inject>
What Context to Inject
1. Available Resources
What data/files exist that the agent can access?
## Available in User's Library
Books:
- "Moby Dick" by Herman Melville (id: book_123)
- "1984" by George Orwell (id: book_456)
Research folders:
- Documents/Research/book_123/ (3 files)
- Documents/Research/book_456/ (1 file)
2. Current State
What has the user done recently? What's the current context?
## Recent Activity
- 2 hours ago: Highlighted passage in "1984" about surveillance
- Yesterday: Completed research on "Moby Dick" whale symbolism
- This week: Added 3 new books to library
3. Capabilities Mapping
What tool maps to what UI feature? Use the user's language.
## What You Can Do
| User Says | You Should Use | Result |
|-----------|----------------|--------|
| "my feed" / "reading feed" | `publish_to_feed` | Creates insight in Feed tab |
| "my library" / "my books" | `read_library` | Shows their book collection |
| "research this" | `web_search` + `write_file` | Saves to Research folder |
| "my profile" | `read_file("profile.md")` | Shows reading profile |
4. Domain Vocabulary
Explain app-specific terms the user might use.
## Vocabulary
- **Feed**: The Feed tab showing reading insights and analyses
- **Research folder**: Documents/Research/{bookId}/ where research is stored
- **Reading profile**: A markdown file describing user's reading preferences
- **Highlight**: A passage the user marked in a book
</what_to_inject>
<implementation_patterns>
Implementation Patterns
Pattern 1: Service-Based Injection (Swift/iOS)
class AgentContextBuilder {
let libraryService: BookLibraryService
let profileService: ReadingProfileService
let activityService: ActivityService
func buildContext() -> String {
let books = libraryService.books
let profile = profileService.currentProfile
let activity = activityService.recent(limit: 10)
return """
## Library (\(books.count) books)
\(formatBooks(books))
## Profile
\(profile.summary)
## Recent Activity
\(formatActivity(activity))
"""
}
private func formatBooks(_ books: [Book]) -> String {
books.map { "- \"\($0.title)\" (id: \($0.id))" }.joined(separator: "\n")
}
}
// Usage in agent initialization
let context = AgentContextBuilder(
libraryService: .shared,
profileService: .shared,
activityService: .shared
).buildContext()
let systemPrompt = basePrompt + "\n\n" + context
Pattern 2: Hook-Based Injection (TypeScript)
interface ContextProvider {
getContext(): Promise<string>;
}
class LibraryContextProvider implements ContextProvider {
async getContext(): Promise<string> {
const books = await db.books.list();
const recent = await db.activity.recent(10);
return `
## Library
${books.map(b => `- "${b.title}" (${b.id})`).join('\n')}
## Recent
${recent.map(r => `- ${r.description}`).join('\n')}
`.trim();
}
}
// Compose multiple providers
async function buildSystemPrompt(providers: ContextProvider[]): Promise<string> {
const contexts = await Promise.all(providers.map(p => p.getContext()));
return [BASE_PROMPT, ...contexts].join('\n\n');
}
Pattern 3: Template-Based Injection
# System Prompt Template (system-prompt.template.md)
You are a reading assistant.
## Available Books
{{#each books}}
- "{{title}}" by {{author}} (id: {{id}})
{{/each}}
## Capabilities
{{#each capabilities}}
- **{{name}}**: {{description}}
{{/each}}
## Recent Activity
{{#each recentActivity}}
- {{timestamp}}: {{description}}
{{/each}}
// Render at runtime
const prompt = Handlebars.compile(template)({
books: await libraryService.getBooks(),
capabilities: getCapabilities(),
recentActivity: await activityService.getRecent(10),
});
</implementation_patterns>
<context_freshness>
Context Freshness
Context should be injected at agent initialization, and optionally refreshed during long sessions.
At initialization:
// Always inject fresh context when starting an agent
func startChatAgent() async -> AgentSession {
let context = await buildCurrentContext() // Fresh context
return await AgentOrchestrator.shared.startAgent(
config: ChatAgent.config,
systemPrompt: basePrompt + context
)
}
During long sessions (optional):
// For long-running agents, provide a refresh tool
tool("refresh_context", "Get current app state") { _ in
let books = libraryService.books
let recent = activityService.recent(10)
return """
Current library: \(books.count) books
Recent: \(recent.map { $0.summary }.joined(separator: ", "))
"""
}
What NOT to do:
// DON'T: Use stale context from app launch
let cachedContext = appLaunchContext // Stale!
// Books may have been added, activity may have changed
</context_freshness>
## Real-World Example: Every ReaderThe Every Reader app injects context for its chat agent:
func getChatAgentSystemPrompt() -> String {
// Get current library state
let books = BookLibraryService.shared.books
let analyses = BookLibraryService.shared.analysisRecords.prefix(10)
let profile = ReadingProfileService.shared.getProfileForSystemPrompt()
let bookList = books.map { book in
"- \"\(book.title)\" by \(book.author) (id: \(book.id))"
}.joined(separator: "\n")
let recentList = analyses.map { record in
let title = books.first { $0.id == record.bookId }?.title ?? "Unknown"
return "- From \"\(title)\": \"\(record.excerptPreview)\""
}.joined(separator: "\n")
return """
# Reading Assistant
You help the user with their reading and book research.
## Available Books in User's Library
\(bookList.isEmpty ? "No books yet." : bookList)
## Recent Reading Journal (Latest Analyses)
\(recentList.isEmpty ? "No analyses yet." : recentList)
## Reading Profile
\(profile)
## Your Capabilities
- **Publish to Feed**: Create insights using `publish_to_feed` that appear in the Feed tab
- **Library Access**: View books and highlights using `read_library`
- **Research**: Search web and save to Documents/Research/{bookId}/
- **Profile**: Read/update the user's reading profile
When the user asks you to "write something for their feed" or "add to my reading feed",
use the `publish_to_feed` tool with the relevant book_id.
"""
}
Result: When user says "write a little thing about Catherine the Great in my reading feed", the agent:
- Sees "reading feed" → knows to use
publish_to_feed - Sees available books → finds the relevant book ID
- Creates appropriate content for the Feed tab
Before launching an agent:
- System prompt includes current resources (books, files, data)
- Recent activity is visible to the agent
- Capabilities are mapped to user vocabulary
- Domain-specific terms are explained
- Context is fresh (gathered at agent start, not cached)
When adding new features:
- New resources are included in context injection
- New capabilities are documented in system prompt
- User vocabulary for the feature is mapped