Align skill with canonical Agent-Native Architecture document: ## Core Changes - Restructure SKILL.md with 5 named principles from canonical: - Parity: Agent can do whatever user can do - Granularity: Prefer atomic primitives - Composability: Features are prompts - Emergent Capability: Handle unanticipated requests - Improvement Over Time: Context accumulation - Add "The test" for each principle - Add "Why Now" section (Claude Code origin story) - Update terminology from "prompt-native" to "agent-native" - Add "The Ultimate Test" to success criteria ## New Reference Files - files-universal-interface.md: Why files, organization patterns, context.md pattern, conflict model - from-primitives-to-domain-tools.md: When to add domain tools, graduating to code - agent-execution-patterns.md: Completion signals, partial completion, context limits - product-implications.md: Progressive disclosure, latent demand discovery, approval matrix ## Updated Reference Files - mobile-patterns.md: Add iOS storage architecture (iCloud-first), "needs validation" callouts, on-device vs cloud section - architecture-patterns.md: Update overview to reference 5 principles and cross-link new files ## Anti-Patterns - Add missing anti-patterns: agent as router, build-then-add-agent, request/response thinking, defensive tool design, happy path in code 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude <noreply@anthropic.com>
12 KiB
<start_with_primitives>
Start with Pure Primitives
Begin every agent-native system with the most atomic tools possible:
read_file/write_file/list_filesbash(for everything else)- Basic storage (
store_item/get_item) - HTTP requests (
fetch_url)
Why start here:
- Proves the architecture - If it works with primitives, your prompts are doing their job
- Reveals actual needs - You'll discover what domain concepts matter
- Maximum flexibility - Agent can do anything, not just what you anticipated
- Forces good prompts - You can't lean on tool logic as a crutch
Example: Starting Primitive
// Start with just these
const tools = [
tool("read_file", { path: z.string() }, ...),
tool("write_file", { path: z.string(), content: z.string() }, ...),
tool("list_files", { path: z.string() }, ...),
tool("bash", { command: z.string() }, ...),
];
// Prompt handles the domain logic
const prompt = `
When processing feedback:
1. Read existing feedback from data/feedback.json
2. Add the new feedback with your assessment of importance (1-5)
3. Write the updated file
4. If importance >= 4, create a notification file in data/alerts/
`;
</start_with_primitives>
<when_to_add_domain_tools>
When to Add Domain Tools
As patterns emerge, you'll want to add domain-specific tools. This is good—but do it deliberately.
Vocabulary Anchoring
Add a domain tool when: The agent needs to understand domain concepts.
A create_note tool teaches the agent what "note" means in your system better than "write a file to the notes directory with this format."
// Without domain tool - agent must infer structure
await agent.chat("Create a note about the meeting");
// Agent: writes to... notes/? documents/? what format?
// With domain tool - vocabulary is anchored
tool("create_note", {
title: z.string(),
content: z.string(),
tags: z.array(z.string()).optional(),
}, async ({ title, content, tags }) => {
// Tool enforces structure, agent understands "note"
});
Guardrails
Add a domain tool when: Some operations need validation or constraints that shouldn't be left to agent judgment.
// publish_to_feed might enforce format requirements or content policies
tool("publish_to_feed", {
bookId: z.string(),
content: z.string(),
headline: z.string().max(100), // Enforce headline length
}, async ({ bookId, content, headline }) => {
// Validate content meets guidelines
if (containsProhibitedContent(content)) {
return { text: "Content doesn't meet guidelines", isError: true };
}
// Enforce proper structure
await feedService.publish({ bookId, content, headline, publishedAt: new Date() });
});
Efficiency
Add a domain tool when: Common operations would take many primitive calls.
// Primitive approach: multiple calls
await agent.chat("Get book details");
// Agent: read library.json, parse, find book, read full_text.txt, read introduction.md...
// Domain tool: one call for common operation
tool("get_book_with_content", { bookId: z.string() }, async ({ bookId }) => {
const book = await library.getBook(bookId);
const fullText = await readFile(`Research/${bookId}/full_text.txt`);
const intro = await readFile(`Research/${bookId}/introduction.md`);
return { text: JSON.stringify({ book, fullText, intro }) };
});
</when_to_add_domain_tools>
<the_rule>
The Rule for Domain Tools
Domain tools should represent one conceptual action from the user's perspective.
They can include mechanical validation, but judgment about what to do or whether to do it belongs in the prompt.
Wrong: Bundles Judgment
// WRONG - analyze_and_publish bundles judgment into the tool
tool("analyze_and_publish", async ({ input }) => {
const analysis = analyzeContent(input); // Tool decides how to analyze
const shouldPublish = analysis.score > 0.7; // Tool decides whether to publish
if (shouldPublish) {
await publish(analysis.summary); // Tool decides what to publish
}
});
Right: One Action, Agent Decides
// RIGHT - separate tools, agent decides
tool("analyze_content", { content: z.string() }, ...); // Returns analysis
tool("publish", { content: z.string() }, ...); // Publishes what agent provides
// Prompt: "Analyze the content. If it's high quality, publish a summary."
// Agent decides what "high quality" means and what summary to write.
The Test
Ask: "Who is making the decision here?"
- If the answer is "the tool code" → you've encoded judgment, refactor
- If the answer is "the agent based on the prompt" → good </the_rule>
<keep_primitives_available>
Keep Primitives Available
Domain tools are shortcuts, not gates.
Unless there's a specific reason to restrict access (security, data integrity), the agent should still be able to use underlying primitives for edge cases.
// Domain tool for common case
tool("create_note", { title, content }, ...);
// But primitives still available for edge cases
tool("read_file", { path }, ...);
tool("write_file", { path, content }, ...);
// Agent can use create_note normally, but for weird edge case:
// "Create a note in a non-standard location with custom metadata"
// → Agent uses write_file directly
When to Gate
Gating (making domain tool the only way) is appropriate for:
- Security: User authentication, payment processing
- Data integrity: Operations that must maintain invariants
- Audit requirements: Actions that must be logged in specific ways
The default is open. When you do gate something, make it a conscious decision with a clear reason. </keep_primitives_available>
<graduating_to_code>
Graduating to Code
Some operations will need to move from agent-orchestrated to optimized code for performance or reliability.
The Progression
Stage 1: Agent uses primitives in a loop
→ Flexible, proves the concept
→ Slow, potentially expensive
Stage 2: Add domain tools for common operations
→ Faster, still agent-orchestrated
→ Agent still decides when/whether to use
Stage 3: For hot paths, implement in optimized code
→ Fast, deterministic
→ Agent can still trigger, but execution is code
Example Progression
Stage 1: Pure primitives
Prompt: "When user asks for a summary, read all notes in /notes,
analyze them, and write a summary to /summaries/{date}.md"
Agent: Calls read_file 20 times, reasons about content, writes summary
Time: 30 seconds, 50k tokens
Stage 2: Domain tool
tool("get_all_notes", {}, async () => {
const notes = await readAllNotesFromDirectory();
return { text: JSON.stringify(notes) };
});
// Agent still decides how to summarize, but retrieval is faster
// Time: 10 seconds, 30k tokens
Stage 3: Optimized code
tool("generate_weekly_summary", {}, async () => {
// Entire operation in code for hot path
const notes = await getNotes({ since: oneWeekAgo });
const summary = await generateSummary(notes); // Could use cheaper model
await writeSummary(summary);
return { text: "Summary generated" };
});
// Agent just triggers it
// Time: 2 seconds, 5k tokens
The Caveat
Even when an operation graduates to code, the agent should be able to:
- Trigger the optimized operation itself
- Fall back to primitives for edge cases the optimized path doesn't handle
Graduation is about efficiency. Parity still holds. The agent doesn't lose capability when you optimize. </graduating_to_code>
<decision_framework>
Decision Framework
Should I Add a Domain Tool?
| Question | If Yes |
|---|---|
| Is the agent confused about what this concept means? | Add for vocabulary anchoring |
| Does this operation need validation the agent shouldn't decide? | Add with guardrails |
| Is this a common multi-step operation? | Add for efficiency |
| Would changing behavior require code changes? | Keep as prompt instead |
Should I Graduate to Code?
| Question | If Yes |
|---|---|
| Is this operation called very frequently? | Consider graduating |
| Does latency matter significantly? | Consider graduating |
| Are token costs problematic? | Consider graduating |
| Do you need deterministic behavior? | Graduate to code |
| Does the operation need complex state management? | Graduate to code |
Should I Gate Access?
| Question | If Yes |
|---|---|
| Is there a security requirement? | Gate appropriately |
| Must this operation maintain data integrity? | Gate appropriately |
| Is there an audit/compliance requirement? | Gate appropriately |
| Is it just "safer" with no specific risk? | Keep primitives available |
| </decision_framework> |
Feedback Processing Evolution
Stage 1: Primitives only
tools: [read_file, write_file, bash]
prompt: "Store feedback in data/feedback.json, notify if important"
// Agent figures out JSON structure, importance criteria, notification method
Stage 2: Domain tools for vocabulary
tools: [
store_feedback, // Anchors "feedback" concept with proper structure
send_notification, // Anchors "notify" with correct channels
read_file, // Still available for edge cases
write_file,
]
prompt: "Store feedback using store_feedback. Notify if importance >= 4."
// Agent still decides importance, but vocabulary is anchored
Stage 3: Graduated hot path
tools: [
process_feedback_batch, // Optimized for high-volume processing
store_feedback, // For individual items
send_notification,
read_file,
write_file,
]
// Batch processing is code, but agent can still use store_feedback for special cases
When NOT to Add Domain Tools
Don't add a domain tool just to make things "cleaner":
// Unnecessary - agent can compose primitives
tool("organize_files_by_date", ...) // Just use move_file + judgment
// Unnecessary - puts decision in wrong place
tool("decide_file_importance", ...) // This is prompt territory
Don't add a domain tool if behavior might change:
// Bad - locked into code
tool("generate_standard_report", ...) // What if report format evolves?
// Better - keep in prompt
prompt: "Generate a report covering X, Y, Z. Format for readability."
// Can adjust format by editing prompt
Starting Out
- Begin with pure primitives (read, write, list, bash)
- Write behavior in prompts, not tool logic
- Let patterns emerge from actual usage
Adding Domain Tools
- Clear reason: vocabulary anchoring, guardrails, or efficiency
- Tool represents one conceptual action
- Judgment stays in prompts, not tool code
- Primitives remain available alongside domain tools
Graduating to Code
- Hot path identified (frequent, latency-sensitive, or expensive)
- Optimized version doesn't remove agent capability
- Fallback to primitives for edge cases still works
Gating Decisions
- Specific reason for each gate (security, integrity, audit)
- Default is open access
- Gates are conscious decisions, not defaults