Nagare is a to-do app for iPhone, Mac, and agents. That third client is the interesting part. Most consumer apps add API access as an afterthought. Nagare ships with a Model Context Protocol server as a first-class interface alongside its native clients. The design choices required to make that work expose the plumbing decisions every consumer app will face when agents become routine users.
The Show HN post drew 13 points and 10 comments, signaling early adopter interest in agent-native product design. The discussion centers on how to expose state to both human and machine clients without creating conflicting mental models or security holes.
The Authentication Boundary Problem
When a human opens Nagare on their iPhone, they authenticate via iCloud. When an agent connects via MCP, it needs credentials that work in a headless context. The authentication boundary splits into three options:
- Shared session tokens: Agent reuses the same iCloud session as the human client. Simple, but ties agent access to active human sessions and exposes full account scope.
- API keys: Agent gets a scoped credential independent of iCloud sessions. Requires separate key management UI and revocation flow.
- Delegated auth: Agent authenticates through the same OAuth provider but with narrower scopes. Adds complexity but maintains consistent identity boundaries.
Most consumer apps choose option one because it requires no new infrastructure. The risk is that a compromised agent credential grants the same access as a stolen phone. Option two is cleaner for agent-specific permissions but doubles the authentication surface area. Option three is the enterprise pattern, rarely seen in personal productivity tools.
Nagare's MCP implementation likely uses shared session tokens because the app syncs through iCloud and the MCP server runs locally. The agent accesses the same data store as the native clients. This works when the agent runs on the user's machine but breaks down if the agent is a cloud service.
State Synchronization Primitives
Nagare merges calendar events and tasks into a single ordered list. Calendar events have fixed times. Tasks can be timed or untimed. Both are "commitments" in the data model. The MCP interface must expose this hybrid structure in a way agents can manipulate without breaking the human UI's assumptions.
The state sync question: does the MCP server expose a snapshot API or a streaming interface?
Snapshot model: Agent calls list_tasks() and gets the current state. To detect changes, it polls or waits for the human to trigger a refresh. Simple, stateless, but misses real-time updates.
Streaming model: Agent subscribes to task changes and receives deltas as they happen. Requires the MCP server to maintain WebSocket connections or long-polling state. More complex but enables reactive agent behavior.
Most MCP servers implement snapshot APIs because the protocol itself is request-response. Streaming requires custom extensions or a separate notification channel. For a to-do app, polling every few seconds is acceptable. For a trading bot or monitoring agent, missing a state change could be expensive.
Conflict resolution is the harder problem. If a human marks a task complete in the iOS app while an agent reschedules it via MCP, which change wins? iCloud's CloudKit provides last-write-wins semantics with conflict handlers, but the MCP layer must decide whether to expose those conflicts to the agent or silently merge them.
Schema Constraints and Validation Gaps
The iOS app enforces client-side validation: due dates must be in the future, task titles can't be empty, priority levels are an enum. The MCP interface exposes these constraints as JSON schemas, but agents can bypass validation if the server doesn't enforce the same rules.
Three validation boundaries:
| Boundary | Enforced By | Risk If Skipped |
|---|---|---|
| Client-side (iOS) | Swift code in UI layer | None, server validates |
| MCP schema | JSON Schema in tool definitions | Agent sends malformed requests |
| Server-side (iCloud) | CloudKit record validation | Corrupt data syncs to all clients |
If Nagare only validates in the iOS app, an agent could create a task with a past due date or an invalid priority. The iOS app would crash or display broken UI when it syncs. If validation lives only in the MCP schema, a malicious or buggy agent could craft raw CloudKit writes that bypass MCP entirely.
The correct pattern: validate at the server boundary (CloudKit record save) and expose those constraints in the MCP schema. The iOS app can add richer validation for better UX, but the server is the source of truth.
The Human-Agent Handoff Pattern
Nagare's design assumes humans and agents collaborate on the same task list. A human might add "study for exam" as an untimed task. An agent could:
- Query the calendar for conflicts
- Suggest a time slot before the dentist appointment
- Update the task with a proposed time
- Wait for human approval before committing
This handoff requires the agent to distinguish between "proposed" and "committed" states. The MCP interface could expose this as a status field (draft, proposed, confirmed) or rely on the agent to use task notes for suggestions.
Without explicit handoff primitives, agents default to one of two patterns:
- Append-only: Agent creates new tasks with suggestions but never modifies human-created tasks. Safe but clutters the list.
- Overwrite: Agent directly updates tasks. Fast but risks overwriting human edits made in parallel.
A better pattern is optimistic locking: the agent reads a task with a version token, proposes changes, and writes back only if the version hasn't changed. If the human edited the task in the meantime, the write fails and the agent retries with fresh data.
Code Shape: MCP Tool Definition
A minimal MCP tool for updating a task might look like this:
{
name: "update_task",
description: "Update an existing task's time or priority",
inputSchema: {
type: "object",
properties: {
task_id: { type: "string" },
scheduled_time: {
type: "string",
format: "date-time",
description: "ISO 8601 timestamp or null for untimed"
},
priority: {
type: "string",
enum: ["high", "normal", "low"]
},
version: {
type: "integer",
description: "Optimistic lock token from list_tasks"
}
},
required: ["task_id", "version"]
}
}
The version field enables optimistic locking. The scheduled_time accepts null to preserve Nagare's untimed task concept. The priority enum matches the iOS app's validation.
The server implementation checks the version before writing:
async function updateTask(
taskId: string,
updates: TaskUpdate,
version: number,
maxRetries: number = 3
) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
const current = await cloudKit.fetch(taskId);
if (current.version !== version) {
// Conflict detected: refetch and retry with exponential backoff
const backoffMs = Math.pow(2, attempt) * 100;
await new Promise(resolve => setTimeout(resolve, backoffMs));
version = current.version;
continue;
}
const updated = {
...current,
...updates,
version: version + 1,
modified_at: new Date()
};
try {
await cloudKit.save(updated);
return updated;
} catch (error) {
if (error.code === 'CONFLICT' && attempt < maxRetries - 1) {
continue;
}
throw error;
}
}
throw new Error("Task modified too frequently, exceeded retry limit");
}
This pattern prevents lost updates when human and agent edit concurrently. The retry loop with exponential backoff handles transient conflicts. After three attempts, the function throws to signal the agent should back off or alert the user.
Observability and Failure Modes
When an agent interacts with Nagare via MCP, failures fall into three categories:
Authentication failures: The agent's session token expired or was revoked. The MCP server returns 401, the agent re-authenticates. If using shared iCloud sessions, this means the human must sign in again on a native client.
Validation failures: The agent sent a malformed request (invalid date, missing required field). The MCP server returns 400 with schema errors. The agent should log the error and retry with corrected input, but many agents will silently fail or hallucinate a success.
Sync conflicts: The agent's write collided with a human edit. The server returns 409. The agent must re-fetch current state and decide whether to retry, merge, or abort. Without explicit conflict resolution logic, most agents will retry in a loop or give up.
Observability requires logging at three points:
- MCP request logs: What tool did the agent call, with what parameters?
- CloudKit write logs: Did the write succeed, fail validation, or conflict?
- Client sync logs: Did the iOS app successfully merge the agent's changes?
Nagare's iCloud sync logs would show whether agent writes succeeded at the CloudKit layer, but the MCP server logs are the only place to see the agent's intent before validation. Without end-to-end tracing, debugging a failed agent action requires correlating timestamps across three log streams. A production implementation should emit trace IDs that flow from MCP request through CloudKit write to client sync.
Example MCP response with trace ID:
{
"result": {
"task_id": "abc123",
"version": 42,
"status": "updated"
},
"trace_id": "mcp-req-7f3a9b2c",
"timestamp": "2026-10-10T14:23:11Z"
}
The trace ID appears in CloudKit write logs and iOS sync logs, enabling correlation across all three layers.
Technical Verdict
Nagare's shared-session, snapshot-polling, optimistic-locking pattern is production-ready for consumer apps with iCloud sync and discrete entity models, but unsuitable for cloud-native agents or sub-second consistency requirements.
Use Nagare's pattern when:
- Your sync backend already provides conflict resolution primitives. Nagare works because CloudKit handles last-write-wins conflicts at the record level with CKRecord versioning. Firebase Realtime Database's transaction API or Supabase's row-level locking provide similar guarantees.
- Your domain model maps to discrete entities that serialize cleanly to JSON without polymorphic types or circular references. Nagare's "commitment" abstraction (task or event) fits MCP's flat schema model. If your entities have complex inheritance hierarchies or bidirectional relationships, you'll spend more time mapping to MCP schemas than building features.
- Agent and human access patterns overlap. Both read and write the same entities with similar validation rules. Nagare's agents can create, update, and delete tasks just like the iOS app. If agents need bulk operations (import 10,000 transactions) or background processing (recalculate all portfolio values) that humans never trigger, you'll need separate API endpoints with different rate limits and timeout policies.
- Local-first architecture is acceptable. Nagare's MCP server runs on the user's machine and shares the iCloud sync layer. This breaks if agents need to run in the cloud without user credentials. A cloud-based financial advisor agent can't authenticate to your personal iCloud account.
Avoid this pattern when:
- You need sub-second consistency guarantees or Operational Transformation for real-time multi-agent collaboration. CloudKit's eventual consistency and last-write-wins semantics can't support collaborative text editing or distributed state machines. If two agents simultaneously update a portfolio allocation, one write will silently overwrite the other.
- Your data model includes rich media (images, video, large binary blobs) that don't serialize well in MCP tool calls. Agents would need separate upload flows with presigned URLs. Nagare's text-only tasks avoid this problem.
- Fine-grained permissions are critical. Shared iCloud sessions give agents full account access. If you need row-level security (agent can read all tasks but only update tasks it created) or time-limited scopes (agent access expires after 1 hour), you'll need OAuth delegation or API keys with custom authorization logic.
- Your app has complex multi-step workflows (approval chains, state machines with side effects) that don't decompose into independent MCP tools. Agents can't reason about transaction boundaries or rollback semantics. If updating a task triggers a webhook that charges a credit card, you need explicit two-phase commit or saga patterns, not optimistic locking.
The key insight: agent-native design isn't about adding an API. It's about exposing the same state model to human and machine clients with consistent validation, conflict resolution, and observability. Nagare's MCP integration works because the app's data model (ordered list of commitments) maps cleanly to MCP tools. Apps with more complex state machines will need richer protocols or accept that agents can only interact with a simplified projection of the full data model.
Source Links
- Introducing Nagare by Ilia Parunashvili, October 2026
- Hacker News Discussion
Top comments (0)