Skip to main content
Original: Dan Shipper (Every) · 18/02/2026

Summary

Architecture decision: Store full agent configuration, or store only agentType and recreate from a registry. Bool { Date().timeIntervalSince(timestamp)

Key Insights

“Architecture decision: Store full agent configuration, or store only agentType and recreate from a registry.” — Discussing the trade-offs in agent configuration storage for flexibility and robustness.
“Use a storage abstraction layer. Don’t use raw FileManager.” — Advising on the importance of abstraction in storage for flexibility across different storage backends.
“Dynamic capability discovery: // Two tools handle everything” — Introducing a method for agents to adaptively discover and interact with available data types.
“Common failure: You build create_note and read_notes but forget update_note and delete_note.” — Highlighting a common oversight in ensuring agents have full CRUD capabilities.

Topics


Full Article

Agent-nativeArchitectures

Author: Dan Shipper (Every)
Published: 2026-02-18
Source: https://every.to/guides/agent-native

Bool { Date().timeIntervalSince(timestamp) < maxAge }
Architecture decision: Store full agent configuration, or store only `agentType` and recreate from a registry. The latter is simpler but means configs can break old checkpoints.

The gap: If the system kills the app, recovery depends on checkpoint frequency. Checkpoint after each tool result for maximum robustness.

### Cloud file states

Files may exist in iCloud but not be downloaded locally. Ensure availability before reading.

await StorageService.shared .ensureDownloaded(folder: .research, filename: “full_text.txt”)
### Storage abstraction

Use a storage abstraction layer. Don't use raw FileManager. Abstract over iCloud vs. local so the rest of your code doesn't care.

let url = StorageService.shared .url(for: .researchBook(bookId: id))
### Background execution

Needs validation

Claude's contribution from building; Dan is still forming his opinion.

iOS gives you limited background time:

func prepareForBackground() { backgroundTaskId = UIApplication.shared .beginBackgroundTask(withName: “AgentProcessing”) { handleBackgroundTimeExpired() } } func handleBackgroundTimeExpired() { for session in sessions where session.status == .running { session.status = .backgrounded Task { await saveSession(session) } } } func handleForeground() { for session in sessions where session.status == .backgrounded { Task { await resumeSession(session) } } }
You get roughly 30 seconds. Use it to:

* • Complete the current tool call if possible
* • Checkpoint the session state
* • Transition gracefully to backgrounded state

**For truly long-running agents:** Consider a server-side orchestrator that can run for hours, with the mobile app as a viewer and input mechanism.

### On-device vs. cloud

The app needs network for reasoning but can access data offline. Design tools to degrade gracefully when network is unavailable.

## Advanced patterns

### Dynamic capability discovery

Needs validation

Claude's contribution from building; Dan is still forming his opinion. This is one approach we're excited about, but others may be better depending on your use case.

One alternative to building a tool for each endpoint in an external API: Build tools that let the agent discover what's available at runtime.

The problem with static mapping:

// You built 50 tools for 50 data types read_steps() read_heart_rate() read_sleep() // When a new metric is added… code change required // Agent can only access what you anticipated
Dynamic capability discovery:

// Two tools handle everything list_available_types() → returns [“steps”, “heart_rate”, “sleep”, …] read_data(type) → reads any discovered type // When a new metric is added… agent discovers it automatically // Agent can access things you didn’t anticipate
This is granularity taken to its logical conclusion. Your tools become so atomic that they work with types you didn't know existed when you built them.

#### When to use this:

* •
 External APIs where you want the agent to have full user-level access (HealthKit, HomeKit, GraphQL endpoints)
* •
 Systems that add new capabilities over time
* •
 When you want the agent to be able to do anything the API supports

#### When static mapping is fine:

* •
 Intentionally constrained agents with limited scope
* •
 When you need tight control over exactly what the agent can access
* •
 Simple APIs with stable, well-known endpoints

The pattern: one tool to discover what's available, one tool to interact with any discovered capability. Let the API validate inputs rather than duplicating validation in your enum definitions.

### CRUD completeness

For every entity in your system, verify the agent has full create, read, update, delete (CRUD) capability:

Create

Can the agent make new instances?

Read

Can the agent see what exists?

Update

Can the agent modify instances?

Delete

Can the agent remove instances?

The audit: List every entity in your system and verify all four operations are available to the agent.

**Common failure:** You build `create_note` and `read_notes` but forget `update_note` and `delete_note`. User asks the agent to "fix that typo in my meeting notes" and the agent can't help.

## Anti-patterns

### Common approaches that aren't fully agent-native

These aren't necessarily wrong—they may be appropriate for your use case. But they're worth recognizing as different from the architecture this document describes.

#### Agent as router

The agent figures out what the user wants, then calls the right function. The agent's intelligence is used to *route*, not to *act*. This can work, but you're using a fraction of what agents can do.

#### Build the app, then add agent

You build features the traditional way (as code), then expose them to an agent. The agent can only do what your features already do. You won't get emergent capability.

#### Request/response thinking

Agent gets input, does one thing, returns output. This misses the loop: Agent gets an outcome to achieve, operates until it's done, handles unexpected situations along the way.

#### Defensive tool design

You over-constrain tool inputs because you're used to defensive programming. Strict enums, validation at every layer. This is safe, but it prevents the agent from doing things you didn't anticipate.

#### Happy path in code, agent just executes

Traditional software handles edge cases in code—you write the logic for what happens when X goes wrong. Agent-native lets the agent handle edge cases with judgment. If your code handles all the edge cases, the agent is just a caller.

### Specific anti-patterns

#### Agent executes your workflow instead of pursuing outcomes

You wrote the logic, agent just calls it. Decisions live in code, not agent judgment.

Wrong - you wrote the workflow

def process_request(input): category = categorize(input) # your code decides priority = score_priority(input) # your code decides store(input, category, priority) if priority > 3: notify() # your code decides

Right - agent pursues outcome in a loop

tools: store_item, send_notification prompt: “Evaluate urgency 1-5, store with your assessment, notify if >= 4”
#### Workflow-shaped tools

`analyze_and_organize` bundles judgment into the tool. Break it into primitives and let the agent compose them.

#### Orphan UI actions

User can do something through the UI that the agent can't achieve. Fix: Maintain parity.

#### Context starvation

Agent doesn't know what exists. User says "organize my notes" and agent doesn't know there are notes.

Fix: Inject available resources and capabilities into system prompt.

#### Gates without reason

Domain tool is the only way to do something, and you didn't intend to restrict access.

Fix: Default to open. Keep primitives available unless there's a specific reason to gate.

#### Artificial capability limits

Restricting what the agent can do out of vague safety concerns rather than specific risks.

The agent should generally be able to do what users can do. Use approval flows for destructive actions rather than removing capabilities entirely.

#### Static mapping when dynamic would serve better

Building 50 tools for 50 API endpoints when a discover + access pattern would give more flexibility and future-proof the system.

#### Heuristic completion detection

Detecting agent completion through heuristics (consecutive iterations without tool calls, checking for expected output files) is fragile.

Fix: Require agents to explicitly signal completion through a completion tool.

## Success criteria

### Architecture

* The agent can achieve anything users can achieve through the UI (parity)
* Tools are atomic primitives; domain tools are shortcuts, not gates (granularity)
* New features can be added by writing new prompts (composability)
* The agent can accomplish tasks you didn't explicitly design for (emergent capability)
* Changing behavior means editing prompts, not refactoring code

### Implementation

* System prompt includes available resources and capabilities
* Agent and user work in the same data space
* Agent actions reflect immediately in the UI
* Every entity has full CRUD capability
* External APIs use dynamic capability discovery where appropriate
* Agents explicitly signal completion (no heuristic detection)

### Product

* Simple requests work immediately with no learning curve
* Power users can push the system in unexpected directions
* You're learning what users want by observing what they ask the agent to do
* Approval requirements match stakes and reversibility

### Mobile

* Checkpoint/resume handles app interruption
* iCloud-first storage with local fallback
* Background execution uses available time wisely

### The ultimate test

Describe an outcome to the agent that's within your application's domain but that you didn't build a specific feature for.

Can it figure out how to accomplish it, operating in a loop until it succeeds?

If yes—you've built something agent-native.

If no—your architecture is too constrained.

---

## Key Takeaways

### Notable Quotes

> Architecture decision: Store full agent configuration, or store only `agentType` and recreate from a registry.

*Context: Discussing the trade-offs in agent configuration storage for flexibility and robustness.*

> Use a storage abstraction layer. Don't use raw FileManager.

*Context: Advising on the importance of abstraction in storage for flexibility across different storage backends.*

> Dynamic capability discovery: // Two tools handle everything

*Context: Introducing a method for agents to adaptively discover and interact with available data types.*

> Common failure: You build `create_note` and `read_notes` but forget `update_note` and `delete_note`.

*Context: Highlighting a common oversight in ensuring agents have full CRUD capabilities.*

## Related Topics

- [[topics/storage-abstraction]]
- [[topics/crud-completeness]]
- [[topics/anti-patterns-in-agent-design]]
- [[topics/agent-native-architecture]]
- [[topics/dynamic-capability-discovery]]

---

## Related Articles

<CardGroup cols={1}>
  <Card
    title="How to Build Agent-native: Lessons From Four Apps"
    icon="newspaper"
    href="/kb/articles/how-to-build-agent-native-lessons-from-four-apps-3d5bc9df"
  >
    Dan Shipper (Every) · explanation · 81% similar
  </Card>
  <Card
    title="Building an Agent-Native Read Later App in 2 Hours"
    icon="newspaper"
    href="/kb/articles/building-an-agent-native-read-later-app-in-2-hours-42baf7f6"
  >
    Naveen Naidu · explanation · 80% similar
  </Card>
  <Card
    title="Teach Your AI to Think Like a Senior Engineer"
    icon="newspaper"
    href="/kb/articles/teach-your-ai-to-think-like-a-senior-engineer-150690ee"
  >
    Dan Shipper (Every) · tutorial · 75% similar
  </Card>
</CardGroup>

---

<Note>
Originally published at [https://every.to/guides/agent-native](https://every.to/guides/agent-native).
</Note>