<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>The Tiles Blog</title>
    <link>https://localhost:3000/blog</link>
    <description>Open source privacy technology for personalized software experiences</description>
    <language>en-us</language>
    <lastBuildDate>Sun, 05 Apr 2026 14:43:38 GMT</lastBuildDate>
    <atom:link href="https://localhost:3000/api/rss" rel="self" type="application/rss+xml"/>
        <item>
      <title><![CDATA[Ship it up]]></title>
      <link>https://localhost:3000/blog/ship-it-up</link>
      <description><![CDATA[How Tiles installer is built]]></description>
      <content:encoded><![CDATA[<p>We ship Tiles as a tarball(tar.gz) and as a macos native installer (.pkg). Whether its tarball or pkg on a high-level these are the steps.</p>

<ul>
<li><p>Bundle the installer,</p><ul><li><p>Package the tiles binary and the python <a href="https://www.tiles.run/blog/move-along-python" target="_blank" rel="noopener noreferrer">venvstack</a> artifacts into a single file.</p></li></ul></li>
<li><p>Run the installer</p><ul><li><p>extract the Tiles binary and the Python artifacts to their appropriate directory.</p></li></ul></li>
<li><p>Run the postinstall scripts.</p></li>
</ul>

<p>Tarball was the only way one could install Tiles in the first few versions, but it had few shortcomings.</p>

<ul>
<li><p>Since its a gzip file, we can't codesign, notarize and staple. We have to do the mentioned steps to the binary only instead of the whole gzip.</p></li>
<li><p>Users have to use a curl script to install it, which is fine for developers but not a good UX for non-developers.</p></li>
<li><p>From a UX point-of-view not much customization is possible during the installation.</p></li>
</ul>

<p>So we wanted to try another approach for installing Tiles. Macos has native installers in form of <code>.dmg</code> and <code>.pkg</code>. We chose pkg because it allows us to run scripts, has good installer UX with customization ability simple html. The rest of the piece focuses on building a macos pkg installer for Tiles.</p>

<p>TODO: Add a pic of pkg installer</p>

<h2>Install file structure</h2>

<img src="https://localhost:3000/blog-ship-it-up-pkgroot-structure.jpg" alt="" style="width: 100%; height: auto; margin: 2rem 0;" />

<p>We have a folder with directories as the above image. Then we can instruct while building pkg to use this <code>pkgroot</code> folder as the "root" folder. So during installation, installer will just copy the folders as it is to the destination location (creating if directory doesn't exist).</p>

<p>So every time we bundle the app, we just build the new tiles binary and move it to <code>pkgroot/usr/local/bin</code> and rest of the updated artifacts under <code>pkgroot/usr/locals/share/tiles/</code></p>

<p>For more info check the <a href="https://github.com/tilesprivacy/tiles/blob/main/pkg/build.sh" target="_blank" rel="noopener noreferrer">script</a>.</p>

<h2>Code signing the tiles binary</h2>

<p>Code signing makes sure that,</p>

<ul>
<li><p>The installer can't be tampered</p></li>
<li><p>The installer is signed by a developer that Apple trusts.</p></li>
</ul>

<p>So for code signing the prerequisites are an Apple Developer account and two certificates namely <strong>DEVELOPER ID APPLICATION</strong> (for singing binary) and <strong>DEVELOPER ID INSTALLER</strong> (for signing the pkg installer itself). Remember the final installer pkg has binary (which will be individually signed) and other artificats as a single compressed file.</p>

<p>For more details on mentioned certificates and how to create/export them, this <a href="https://www.codevamping.com/2023/11/macos-pkg-installer/" target="_blank" rel="noopener noreferrer">article</a> explain them in depth.</p>

<pre><code># Signing the tiles binary
codesign --force \
  --sign "$DEVELOPER_ID_APPLICATION"\
  --options runtime \
  --timestamp \
  --strict \
  "${CLI_BIN_PATH}/tiles"
</code></pre>

<p>This how we sign the tiles binary where <code>$DEVELOPER_ID_APPLICATION</code> is an env variable for the name of the DEVELOPER ID APPLICATION certificate.</p>

<h2>Scripts</h2>

<p>We can have bash scripts that run before and after the installation. For that we need to add the scripts such as <strong>preinstall</strong> and <strong>postinstall</strong> under a directory which then we will point while building the pkg.</p>

<p>See how we do in Tiles <a href="https://github.com/tilesprivacy/tiles/tree/main/pkg/scripts" target="_blank" rel="noopener noreferrer">here</a>, we do it for cleaning up and running some internal scripts.</p>

<h2>Building the tiles package</h2>

<p>As of now we have a signed tiles binary and other install artifacts all under the <code>pkgroot</code> directory, now we can build a pkg out of it via</p>

<pre><code>pkgbuild --root pkgroot --scripts \
 pkg/scripts --identifier com.tilesprivacy.tiles --version "$VERSION" \
 pkg/tiles-unsigned.pkg
</code></pre>

<p>Here we use the scripts pkgroot directory as the value for <code>--root</code> and also use our the scripts folder mentioned in above section.</p>

<h2>Customizing the installer</h2>

<img src="https://localhost:3000/blog-ship-it-up-installer-custom-1.jpg" alt="" style="width: 100%; height: auto; margin: 2rem 0;" />

<p>When we open the pkg we created in the previous section, we can see it is bare minimum and filled with defaults. We can infact add intro page, conclusion page, logo etc via something called <a href="https://developer.apple.com/library/archive/documentation/DeveloperTools/Reference/DistributionDefinitionRef/Chapters/Introduction.html#//apple_ref/doc/uid/TP40005370-CH1-SW1" target="_blank" rel="noopener noreferrer" style="font-weight:700">distribution</a> which we express in XML files. In a distribution xml file we can add html files for intro, conclusion and other customizable components which are supported by the distribution standard. <a href="https://github.com/tilesprivacy/tiles/blob/main/pkg/distribution_network.xml" target="_blank" rel="noopener noreferrer">Here's </a> how the tiles's distribution xml looks like. The customizable xml components like <code>&lt;welcome/&gt;</code> <code>&lt;conclusion/&gt;</code> etc are called resources which we keep under its own directory and pass it when we build the final pkg with the distribution.</p>

<pre><code>productbuild \
  --distribution pkg/distribution_network.xml \
  --resources pkg/resources \
  --package-path pkg/  \
  pkg/tiles-dist-unsigned.pkg
</code></pre>

<p>So in <code>productbuild</code> we pass the pkg we created before. And thus we get a final installer with all the customization and the installer in it.</p>

<img src="https://localhost:3000/blog-ship-it-up-installer-custom-2.jpg" alt="" style="width: 100%; height: auto; margin: 2rem 0;" />

<img src="https://localhost:3000/blog-ship-it-up-installer-custom-3.jpg" alt="" style="width: 100%; height: auto; margin: 2rem 0;" />

<h2>Code signing the complete pkg installer</h2>

<pre><code>productsign \
  --sign "$DEVELOPER_ID_INSTALLER" \
  pkg/tiles-dist-unsigned.pkg \
  pkg/tiles.pkg
</code></pre>

<p>This is how we codesign a pkg. Remeber before we signed a binary and the commands for signing a binary and an installer are different.</p>

<p>The main difference is <code>productsign</code> instead of <code>codesign</code> and the certificate we use is <code>DEVELOPER ID INSTALLER</code> instead of <code style="white-space: pre-wrap">DEVELOPER  ID APPLICATION</code>.</p>

<h2>Notarizing the installer</h2>

<p>We notarize to make sure that the app is malware safe and this verification is done by Apple. For that we need to upload our installer to Apple, and once its verified by Apple it returns the status as valid or invalid.</p>

<p>Here's how we do this,</p>

<pre><code>xcrun notarytool submit pkg/tiles.pkg \
  --keychain-profile "tiles-notary-profile" \
  --wait
</code></pre>

<p>The <code>tiles-notary-profile</code> is the name of the credential we store in keychain, so that every time we need to pass all the details. Below is the command to store a credential for notarization</p>

<pre><code>xcrun notarytool store-credentials \
  "tiles-notary-profile" \
  --apple-id "john.doe@gmail.com" \
  --team-id "X********4" \
  --password "****-****-****-****"</code></pre>

<h2>Stapling the installer</h2>

<p>Stapling the installer basically attaches the notarizing proof to the installer itself, it will reduce the roundtrip notarizing check to Apple from the Gatekeeper security feature in macos.</p>

<pre><code>xcrun stapler staple pkg/tiles.pkg
</code></pre>

<h2>What's next</h2>

<p>We do have a fully offline installer where the installer includes a gpt-oss model, so that tiles work fully offline and can be used a portable installer which we can carry in an usb. But in newer Apple Silcion based M devices we need to download rosetta as apple stop shipping it by default. Rosetta is used to convert intel based instructions to Apple silicon.</p>

<p>More regarding this is added in this <a href="https://github.com/tilesprivacy/tiles/issues/105" target="_blank" rel="noopener noreferrer">issue</a>.</p>]]></content:encoded>
      <pubDate>Sun, 05 Apr 2026 00:00:00 GMT</pubDate>
      <guid isPermaLink="true">https://localhost:3000/blog/ship-it-up</guid>
    </item>
    <item>
      <title><![CDATA[Introducing Tiles Public Alpha]]></title>
      <link>https://localhost:3000/blog/introducing-tiles-public-alpha</link>
      <description><![CDATA[Announcing Tiles Public Alpha: our first release of a privacy-first AI assistant with local models, a CLI app for Apple Silicon, and a Tilekit SDK for developers.]]></description>
      <content:encoded><![CDATA[<img src="https://localhost:3000/kingston.webp" alt="Cover image for Introducing Tiles Public Alpha" style="width: 100%; height: auto; margin-bottom: 2rem;" />

<p>We're thrilled to launch Tiles Public Alpha, our first public release of a privacy-first AI assistant. Tiles brings together local-first models, personalized experiences, and verifiable privacy, all running on your device, with your data staying yours. We believe identity and memory are two sides of the same coin, and Tiles makes that coin yours: your user-agent.</p>

<p>Our first alpha is a CLI assistant app for Apple Silicon devices, and a Tilekit SDK that lets developers customize local models and agent experiences within Tiles. We aim to evolve Modelfile in collaboration with the community, establishing it as the standard for model customization.</p>

<h2>Philosophy</h2>

<p>The project is defined by four interdependent design choices<a href="https://localhost:3000/blog/introducing-tiles-public-alpha#ref-2">²</a>:</p>

<ol>
  <li>
    <strong>Device-anchored identity with keyless ops:</strong> Clients are provisioned through the device keychain and cannot access the registry by identity alone<a href="https://localhost:3000/blog/introducing-tiles-public-alpha#ref-3">³</a>. Keyless operations are only enabled after an identity is verified and linked to the device key, allowing third-party agent access under user-defined policies<a href="https://localhost:3000/blog/introducing-tiles-public-alpha#ref-4">⁴</a>.
  </li>
  <li>
    <strong>Immutable model builds:</strong> Every build is version-locked and reproducible, ensuring consistency and reliability across updates and platforms.
  </li>
  <li>
    <strong>Content-hashed model layers:</strong> Models are stored and referenced by cryptographic hashes of their layers, guaranteeing integrity and enabling efficient deduplication and sharing.
  </li>
  <li>
    <strong>Verifiable transparency and attestations:</strong> Every signing and build event is logged in an append-only transparency log, producing cryptographic attestations that can be independently verified. This ensures accountability, prevents hidden modifications, and provides an auditable history of model provenance across devices and registries.
  </li>
</ol>

<h2>Implementation</h2>

<img src="https://localhost:3000/tilescli.png" alt="Tiles CLI" style="width: 100%; height: auto; margin: 2rem 0;" />

<p>Tiles bundles a fine-tuned model to manage context and memories locally on-device with hyperlinked markdown files. Currently, we use <a href="https://huggingface.co/driaforall/mem-agent" target="_blank" rel="noopener noreferrer">mem-agent</a> model (from <a href="https://dria.co/" target="_blank" rel="noopener noreferrer">Dria</a>, based on <code>qwen3-4B-thinking-2507</code>), and are in the process of training our initial in-house memory models.</p>

<p>These models utilize a human-readable external memory stored as markdown, and learned policies (trained via reinforcement learning on synthetically generated data) to decide when to call Python functions that retrieve, update, or clarify memory, allowing the assistant to maintain and refine persistent knowledge across sessions.</p>

<h2>Looking forward</h2>

<p>We're building the next layer of private personalization: customizable memory, private sync, verifiable identity, and a more open model ecosystem.</p>

<ul>
  <li>
    <strong>Memory extensions:</strong> Add support for LoRA-based memory extensions so individuals and organizations can bring their own data and shape the assistant's behavior and tone on top of the base memory model.
  </li>
  <li>
    <strong>Sync:</strong> Build a reliable, peer-to-peer sync layer using <a href="https://www.iroh.computer/" target="_blank" rel="noopener noreferrer">Iroh</a> for private, device-to-device state sharing.
  </li>
  <li>
    <strong>Identity:</strong> Ship a portable identity system using <a href="https://atproto.com/specs/did" target="_blank" rel="noopener noreferrer">AT Protocol DIDs</a>, designed for device-anchored trust.
  </li>
  <li>
    <strong>MIR in Modelfile:</strong> Work with the <a href="https://darkshapes.org/" target="_blank" rel="noopener noreferrer">Darkshapes</a> team to support the <a href="https://huggingface.co/darkshapes/MIR_" target="_blank" rel="noopener noreferrer">MIR</a> (Machine Intelligence Resource) naming scheme in our Modelfile implementation.
  </li>
  <li>
    <strong>Registry:</strong> Continue supporting Hugging Face, while designing a decentralized registry for versioned, composable model layers using the open-source <a href="https://github.com/huggingface/xet-core" target="_blank" rel="noopener noreferrer">xet-core</a> client tech.
  </li>
  <li>
    <strong>Research roadmap:</strong> As part of our research on private software personalization infrastructure, we are investigating sparse memory finetuning, text diffusion models, Trusted Execution Environments (TEEs), and Per-Layer Embeddings (PLE) with offloading to flash storage.
  </li>
</ul>

<p>We are seeking design partners for training workloads that align with our goal of ensuring a verifiable privacy perimeter. If you're interested, please reach out to us at <a href="mailto:hello@tiles.run">hello@tiles.run</a>.</p>

<h2>References</h2>

<ol>
  <li id="ref-1">
    <a href="https://docs.ollama.com/modelfile" target="_blank" rel="noopener noreferrer">Ollama Modelfile</a> is the blueprint to create and share customized models using Ollama.
  </li>
  <li id="ref-2">
    <a href="https://newsletter.squishy.computer/p/decentralizability" target="_blank" rel="noopener noreferrer">Decentralizability</a> (Gordon Brander): Immutable data, universal IDs, user-controlled keys… and just using HTTP. I think this is probably minimum viable decentralizability.
  </li>
  <li id="ref-3">
    <a href="https://keybase.io/blog/keybase-new-key-model" target="_blank" rel="noopener noreferrer">Keybase's New Key Model</a>
  </li>
  <li id="ref-4">
    <a href="https://www.sigstore.dev/how-it-works" target="_blank" rel="noopener noreferrer">Sigstore: How It Works</a>
  </li>
</ol>]]></content:encoded>
      <pubDate>Fri, 02 Jan 2026 00:00:00 GMT</pubDate>
      <guid isPermaLink="true">https://localhost:3000/blog/introducing-tiles-public-alpha</guid>
    </item>
    <item>
      <title><![CDATA[Move Along, Python]]></title>
      <link>https://localhost:3000/blog/move-along-python</link>
      <description><![CDATA[Deterministic, portable Python runtimes for Tiles using layered venvstacks.]]></description>
      <content:encoded><![CDATA[<p>We have been working on <a href="https://www.tiles.run/" target="_blank" rel="noopener noreferrer">Tiles</a>, a private and secure AI assistant for everyday use.</p>

<h2>The Python Problem</h2>

<p>Right now, we have a polyglot architecture where the control pane and CLI are written in Rust, while local model inference runs through a Python server as a daemon. Ideally, when we ship Tiles, we should also ship the required artifacts needed to run Python on the user’s system.</p>

<p>Since Python servers cannot be compiled into a single standalone binary, the user’s system must have a Python runtime available. More importantly, it must be a deterministic Python runtime so that the server runs exactly on the version developers expect.</p>

<p>In earlier releases of Tiles (before 0.4.0), we packed the server files into the final release tarball. During installation, we extracted them to the user’s system, downloaded <code>uv</code> (a Python package manager), installed Python 3.13 if it was not already present, and then ran the server as a daemon.</p>

<p>This approach had several issues:</p>

<ul>
  <li>Downloading development-related tools such as <code>uv</code> onto the user’s system</li>
  <li>Relying on <code>uv</code> at install time to manage dependencies and run the server</li>
  <li>Increased chances of failures due to dependency or runtime non-determinism</li>
  <li>Requiring internet access to download all of the above tools</li>
  <li>Lack of a fully deterministic runtime across operating systems</li>
</ul>

<p>One of the long-term goals of Tiles is complete portability. The previous approach did not align with that vision.</p>

<h2>Portable Runtimes</h2>

<p>To address these issues, we decided to ship the runtime along with the release tarball. We are now using <a href="https://lmstudio.ai/blog/venvstacks" target="_blank" rel="noopener noreferrer">venvstacks</a> by LM Studio to achieve this.</p>

<p>Venvstacks allows us to build a layered Python environment with three layers:</p>

<ul>
  <li><strong>Runtimes</strong><br />
  Defines the exact Python runtime version we need.</li>
  <li><strong>Frameworks</strong><br />
  Specifies shared Python frameworks such as NumPy, MLX, and others.</li>
  <li><strong>Applications</strong><br />
  Defines the actual server application and its specific dependencies.</li>
</ul>

<p>Similar to Docker, each layer depends on the layer beneath it. A change in any layer requires rebuilding that layer and the ones above it.</p>

<p>All components within a layer share the layers beneath them. For example, every framework uses the same Python runtime defined in the <code>runtimes</code> layer. Likewise, if we have multiple servers in the <code>applications</code> layer and both depend on MLX, they will share the exact deterministic MLX dependency defined in <code>frameworks</code>, as well as the same Python runtime defined in <code>runtimes</code>.</p>

<p>We define everything inside a <code>venvstacks.toml</code> file. Here is the <a href="https://github.com/tilesprivacy/tiles/blob/main/server/stack/venvstacks.toml" target="_blank" rel="noopener noreferrer">venvstacks.toml</a> used in Tiles.</p>

<p>Because we pin dependency versions in the TOML file, we eliminate non-determinism.</p>

<p>Internally, venvstacks uses <code>uv</code> to manage dependencies. Once the TOML file is defined, we run:</p>

<pre><code>venvstacks lock venvstacks.toml</code></pre>

<p>This resolves dependencies and creates the necessary folders, lock files, and metadata for each layer.</p>

<p>Next:</p>

<pre><code>venvstacks build venvstacks.toml</code></pre>

<p>This builds the Python runtime and environments based on the lock files.</p>

<p>Finally:</p>

<pre><code>venvstacks publish venvstacks.toml</code></pre>

<p>This produces reproducible tarballs for each layer. These tarballs can be unpacked on external systems and run directly.</p>

<p>We bundle the venvstack runtime artifacts into the final installer using this <a href="https://github.com/tilesprivacy/tiles/blob/main/scripts/bundler.sh" target="_blank" rel="noopener noreferrer">bundler script</a>. During installation, this <a href="https://github.com/tilesprivacy/tiles/blob/main/scripts/install.sh" target="_blank" rel="noopener noreferrer">installer script</a> extracts the venvstack tarballs into a deterministic directory.</p>

<p>Our Rust CLI can then predictably start the Python server using:</p>

<pre><code>stack_export_prod/app-server/bin/python -m server.main
</code></pre>

<h2>What’s Next</h2>

<p>We tested version 0.4.0 on clean macOS virtual machines to verify portability, and the approach worked well.</p>

<p>For now, we are focusing only on macOS. When we expand support to other operating systems, we will revisit this setup and adapt it as needed.</p>

<p>Packaging the runtime and dependencies increases the size of the final installer. We are exploring ways to reduce that footprint.</p>

<p>We also observed that changes in lock files can produce redundant application tarballs when running the <code>publish</code> command. More details are tracked in this <a href="https://github.com/tilesprivacy/tiles/issues/84" target="_blank" rel="noopener noreferrer">issue</a>.</p>

<p>Overall, we are satisfied with this approach for now.</p>

<p>Till then, keep on tiling.</p>]]></content:encoded>
      <pubDate>Tue, 17 Feb 2026 00:00:00 GMT</pubDate>
      <guid isPermaLink="true">https://localhost:3000/blog/move-along-python</guid>
    </item>
  </channel>
</rss>