The Architecture Has to Earn the Trust

Part 1 described the core inversion: personal agents handle integrations, apps focus on presentation, and data lives on the user's device. Part 2 looked honestly at the barriers — the access friction, the two distinct trust problems, and the technical constraints that don't dissolve under enthusiasm. This article is about what you actually build when you take that pattern seriously.

The central point of Part 2 was that trust claims in this space need to be structural rather than rhetorical. Saying "your data stays on your device" only means something if the system is built in a way that makes the alternative architecturally foreclosed rather than merely unlikely or policy-prohibited. An app whose relay server happens not to log content is different from an app whose relay server receives only ciphertext and therefore cannot log content in any meaningful sense, regardless of what someone later decides. That difference is what this article is about.

What follows is a walkthrough of the architecture I'm working through in ScrolLess. It is a live exploration rather than a finished blueprint — the design is in place and the core pipeline is running, but the project is still being built out. I'm documenting it here because the architectural reasoning matters independently of how complete the implementation is.

Five Principles Before the Diagram

Architecture decisions that aren't grounded in explicit principles tend to drift: each individual choice seems reasonable, but the cumulative effect is an incoherent system. These five principles shape every decision in this design.

User-owned data by default. Feed items, read state, preferences, saved content — primary data lives on the user's device. The app reads from local storage. Nothing is held centrally as a condition of using the service. This is not a privacy feature bolted on top; it is the load-bearing assumption everything else is designed around.

Agents handle integrations. The app has no OAuth flows, no API clients, no rate-limit logic, and no knowledge of what Twitter or YouTube look like from the outside. That complexity belongs entirely to the agent layer, which runs independently and is controlled by the user. The app's relationship to the outside world is to receive structured data and render it, and nothing beyond that.

Apps compete on experience. When data ownership is no longer a source of lock-in, the only thing left to differentiate on is how well the app works. This is, I think, a healthier competitive arrangement — interface design becomes the meaningful differentiator rather than the size of your integration catalogue or the switching cost you've engineered into your data model.

Cloud is an optional enhancement. Multi-device sync, reliable background scheduling, and compute-heavy features can be cloud-backed. But the app is fully functional without them. Cloud extends capability; it does not gatekeep it. A user who runs everything locally should get the same core experience as a paying subscriber.

Clear trust boundaries. A user should be able to understand, at a meaningful level of detail, what runs on their device, what leaves it, and what any server in the chain can and cannot access. This is not a UX nicety — it is an architectural constraint. If the relay can read your content, the trust model is broken regardless of what the privacy page promises.

The Full Flow

With those principles in place, the architecture resolves into a clear sequence. The user schedules a sync task. A personal AI agent picks it up, loads its workflow from an MCP server, fetches from the configured sources, structures and deduplicates the results, encrypts the entire payload using the device's public key, and submits it. A relay server receives the ciphertext — seeing nothing it could read even if it tried — and pushes it to the device. The device decrypts, writes to IndexedDB, and the app renders from local storage without a network round-trip.

Full architecture flow from User scheduling a task, through Personal AI Agent which loads from MCP Server and fetches from Sources, then encrypts the payload, through a blind Relay Server, to Device Storage which decrypts on arrival, and finally to App UI reading from local storage.
The full delivery flow. Encryption happens at the agent; the relay sees only ciphertext; the app reads from local storage with no cloud round-trip required.

Two parts of this flow matter because they depart from how most apps are built. The encryption step happens at the agent, before the payload touches any server. This means the relay is blind not by policy but by construction — it routes ciphertext, and the key to decrypt it never passes through it. The device is the terminal point: content arrives, gets decrypted locally, and the app reads it from there. There is no cloud database query standing between the user and their feed.

Deterministic Workflows and MCP's Two Roles

One of the more discussed challenges with AI agents in production is reliability. An agent given a loose instruction like "fetch my YouTube subscriptions and add them to my feed" will behave inconsistently — sometimes working, sometimes hallucinating a field name, occasionally inventing an endpoint that doesn't exist. That is not acceptable for a background task handling real user data on a schedule, and the answer is not a smarter model but a more structured workflow.

Deterministic agent workflows impose structure on what the agent can do and in what order. The application exposes named prompts, typed tools, ordered steps, and explicit schemas, and the agent executes a guided process rather than improvising one. Reliability comes from the design of the workflow, not from trusting the model to reason its way to the right answer every time.

MCP (Model Context Protocol) — now stewarded by the Agentic AI Foundation and increasingly adopted as a standard for agent-tool interaction — plays two distinct roles in this architecture. They're easy to conflate, so the distinction matters.

MCP Server at centre. On the left, AI Agent asks for workflow instructions and MCP returns named steps, typed tools, and skill resources — labelled Role 1: Instruction Layer. On the right, Agent Output submits an encrypted validated tool call, MCP forwards it to the Relay and Device — labelled Role 2: Ingestion Interface.
MCP as instruction layer (teaching the agent how to work with the app) and as ingestion interface (the validated boundary where agent output enters the system).

The first role is as an instruction layer, where the MCP server is the place the agent learns how to populate the app correctly. It exposes named workflows — sync_feed, for example — alongside typed tool definitions, schema specifications, source-specific skill documents, and the user's current preferences. The agent doesn't need to understand how the app works internally, because the MCP server tells it exactly what to do and in what shape to deliver the result. As others exploring MCP's broader implications have noted, this separation between agent capability and application-specific knowledge is one of the protocol's most useful structural properties.

The second role is as an ingestion interface. Once the agent has gathered and structured content, it does not push raw results directly to any storage layer; instead, it submits a validated tool call to the MCP server, which acts as the structured boundary between agent output and the application's data layer. This enforces schema compliance and gives the system a single, auditable point where external data enters — a checkpoint rather than an open pipe. One protocol covering both roles, how the agent learns what to do and how the result safely enters the system, is what makes the pattern more than AI with guardrails applied afterwards.

Walking Through sync_feed

The most direct way to see this architecture working is to follow a single execution of sync_feed — the core scheduled workflow in ScrolLess, the one that runs on a timer and populates the user's feed. Here is what actually happens.

The agent begins by calling get_feed_state, which returns everything it needs before touching any external source: the timestamp of the last successful sync, the list of enabled sources with their priorities, the device's public key for the encryption step, and any skip rules the user has configured. A single call provides the complete context.

With that state in hand, the agent loads the relevant skill resources from the MCP server — skills/youtube.md, skills/x.md, and so on for each enabled source. Each skill file is a deterministic set of instructions written for that specific platform: where to navigate, which elements to extract, which to skip, and how to handle edge cases like deleted posts or private accounts. The agent does not improvise these steps; it follows a document that was written, tested, and tuned for that source.

It then works through each source, applying those instructions. New content since the last sync timestamp is collected, deduplicated against known url_hash values, and ranked according to the user's priority rules. The result is a flat list of items conforming to the app's schema — same fields, same types, regardless of origin platform. At this point, the content exists only in the agent's context.

Before anything leaves, the entire payload is encrypted using the device's public key (ECIES P-256 + AES-256-GCM, with the key returned in the first call). After this step, the agent has a ciphertext payload suitable for transmission, and no server in the delivery chain will be able to read it. The agent then calls write_feed_items with the encrypted payload, the ephemeral key needed for decryption on the device, and source metadata. The MCP server validates the structure of the tool call and forwards it to the relay, the relay pushes it to the device, and the device decrypts, deduplicates against existing IndexedDB records, and the app reflects the new items on next render.

If the device is offline when the agent runs, the relay accepts and queues the encrypted payload, then delivers it when the device reconnects. The sync window only advances after successful delivery acknowledgement from the device, so a queued-but-undelivered payload does not silently move the system past content the user has not received.

Where the Data Lives

The device

Feed items, read state, user preferences, saved content, and sync logs all live in IndexedDB. The app reads from there directly — no network request required for normal browsing, which gives it genuinely fast local queries and complete offline support. Deletion is straightforward: clearing the app's local data clears everything, because everything is there. URL deduplication runs client-side, checking url_hash values in IndexedDB before storing a newly delivered item. Data retention is also client-side, a periodic cleanup against the item's fetched_at timestamp and the user's configured retention window.

The relay

The relay exists for one reason: to bridge the gap between an agent that may run remotely or on a schedule and a device that may be offline at the time. It receives encrypted payloads and forwards them, stores nothing beyond what is operationally necessary to complete the delivery, and has no knowledge of what platforms the user follows, what the content looks like, or who the user is in any meaningful sense. The payload arrives already encrypted with the device's key and leaves the same way, so the architectural guarantee is not that the operator chooses not to read user data, but that the system makes it infeasible to do so.

The coordination backend

A minimal server handles what the relay can't: push notification subscriptions, device registration, source configuration metadata, MCP server discovery, and — for the optional paid tier — subscription entitlements and multi-device sync metadata. This backend never stores feed content. Its job is coordination, not custody. The distinction between free and paid tiers is handled in the auth middleware; route handlers don't need to know which they're serving.

The Optional Cloud Layer

For users who want more than a single device, an optional subscription tier extends the architecture without altering its character. Multi-device sync, reliable queuing for extended offline periods, background agent scheduling, and compute-heavy features like cross-source summarisation can all be cloud-backed. The constraint that keeps this honest is that these capabilities are strictly additive. A user who never opts in should get a fully functional app — agent running locally or on a machine they control, data on their device, relay handling delivery on a best-effort basis. The paid tier adds convenience and reliability. It does not change what the system can and cannot see.

ScrolLess as a Working Experiment

ScrolLess is the application of all of this — a unified social and news feed aggregator built around this architecture from the ground up. The agent scrapes the platforms the user follows (currently YouTube subscriptions and the X/Twitter following timeline), structures the results into a common content schema, encrypts the payload, and delivers it via the relay. The Preact PWA reads from IndexedDB and renders a chronological feed, and there are no direct connections between the app and any content platform. The feed belongs to the user; the app makes it useful to look at.

The project is in active development, with the core pipeline running across agent scraping, MCP-guided workflows, encrypted relay, and device-side decryption and storage. The architecture described in this series is structural from the first commit rather than something to be retrofitted when the system matures. What's still being built out is the full range of source skills, the preferences and source-management UI, and the optional sync tier. The shell is deployed and the pattern is in place.

The specific decisions that constrain ScrolLess — the blind relay, the device-first storage, the agent's independence from the app — are not incidental. They are the choices that make the trust claims mean something rather than read pleasantly, because a system where the relay operator genuinely cannot access user content is categorically different from one where they merely promise restraint. That difference is harder to achieve and easier to verify, which seemed worth building for.

Agents gather. Users own. Apps compete on experience.
That's a better arrangement for everyone — except the intermediaries.