<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Sohan</title>
    <description>The latest articles on DEV Community by Sohan (@sohan26).</description>
    <link>https://dev.to/sohan26</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1223620%2Ff9d94477-0590-4c10-8046-f8522115bffa.jpeg</url>
      <title>DEV Community: Sohan</title>
      <link>https://dev.to/sohan26</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sohan26"/>
    <language>en</language>
    <item>
      <title>Build a Multi-Tenant RAG with Fine-Grain Authorization using Motia and SpiceDB</title>
      <dc:creator>Sohan</dc:creator>
      <pubDate>Fri, 21 Nov 2025 11:51:34 +0000</pubDate>
      <link>https://dev.to/sohan26/build-a-multi-tenant-rag-with-fine-grain-authorization-using-motia-and-spicedb-3do1</link>
      <guid>https://dev.to/sohan26/build-a-multi-tenant-rag-with-fine-grain-authorization-using-motia-and-spicedb-3do1</guid>
      <description>&lt;h3&gt;
  
  
  This post was inspired by Stardew Valley 😎
&lt;/h3&gt;

&lt;p&gt;If I was hard-pressed to pick my favourite computer game of all time, I'd go with &lt;strong&gt;Stardew Valley&lt;/strong&gt; (sorry, &lt;a href="https://en.wikipedia.org/wiki/Dangerous_Dave" rel="noopener noreferrer"&gt;Dangerous Dave&lt;/a&gt;). The stats from my Nintendo Profile is all the proof you need:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcjr5yh0ilhsucgye9hkn.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcjr5yh0ilhsucgye9hkn.jpg" alt="nintendo-stats" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Stardew Valley sits atop with 430 hours played and in second place is Mario Kart (not pictured) with ~45 hours played. That's a significant difference, and should indicate how much I adore this game.&lt;/p&gt;

&lt;p&gt;I've been talking about the importance of &lt;strong&gt;Fine-Grained Authorization&lt;/strong&gt; and &lt;strong&gt;RAG&lt;/strong&gt; recently, so when I sat down to build a sample usecase for a production-grade RAG with Fine-Grained Permissions, my immediate thought went to Stardew Valley.&lt;/p&gt;

&lt;p&gt;For those not familiar, Stardew Valley is a farm life simulation game where players manage a farm by clearing land, growing seasonal crops, and raising animals. So I thought I could build a logbook for a large farm that one could query using natural language processing. This usecase is ideal for RAG Pipelines (a technique that uses external data to improve the accuracy, relevancy, and usefulness of a LLM model’s output). &lt;/p&gt;

&lt;p&gt;I focused on building something that was as close to production-grade as possible (and perhaps strayed from the original intent of a single farm) where an organization (not Joja Corporation though!) can own farms and data from the farms. The farms contain harvest data, users can log and query data for the farms they're part of. This provides a sticky situation for the authorization model. How does a LLM know who has access to what data?&lt;/p&gt;

&lt;p&gt;Here's where &lt;strong&gt;SpiceDB&lt;/strong&gt; and &lt;strong&gt;ReBAC&lt;/strong&gt; was vital. By using metadata to indicate where the relevant embedings came from, the RAG system returned harvest data to the user only based on what data they had access to. In fact, &lt;a href="https://authzed.com/customers/openai" rel="noopener noreferrer"&gt;OpenAI uses SpiceDB&lt;/a&gt; for their fine-grained authorization in ChatGPT Connectors.&lt;/p&gt;

&lt;p&gt;While I know my way around SpiceDB and authorization, I needed help to build out the other components for a production-grade harvest logbook. So I reached out to my friend &lt;a href="https://www.linkedin.com/in/rohit-ghumare/" rel="noopener noreferrer"&gt;Rohit Ghumare&lt;/a&gt; from Motia for his expertise. &lt;a href="https://motia.dev/" rel="noopener noreferrer"&gt;Motia.dev&lt;/a&gt; is a backend framework that unifies APIs, background jobs, workflows, and AI Agents into a single core primitive with built-in observability and state management&lt;/p&gt;

&lt;p&gt;Here's a photo of Rohit and myself at Kubecon Europe in 2025&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F204wl1zdxa0wdweqtzq6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F204wl1zdxa0wdweqtzq6.png" alt="sohan and rohit" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What follows below is a tutorial-style post on building a Retrieval Augmented Generation system with fine-grained authorization using the Motia framework and SpiceDB. We'll use &lt;a href="https://www.pinecone.io/" rel="noopener noreferrer"&gt;Pinecone&lt;/a&gt; as our vector database, and OpenAI as our LLM.&lt;/p&gt;

&lt;h2&gt;
  
  
  What You'll Build
&lt;/h2&gt;

&lt;p&gt;In this tutorial, you'll create a complete RAG system with authorization that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Stores harvest data and automatically generates embeddings for semantic search&lt;/li&gt;
&lt;li&gt;Splits text into optimized chunks with overlap for better retrieval accuracy&lt;/li&gt;
&lt;li&gt;Implements fine-grained authorization using SpiceDB's relationship-based access control&lt;/li&gt;
&lt;li&gt;Queries harvest history using natural language with AI-powered responses&lt;/li&gt;
&lt;li&gt;Returns contextually relevant answers with source citations from vector search&lt;/li&gt;
&lt;li&gt;Supports multi-tenant access where users only see data they have permission to access&lt;/li&gt;
&lt;li&gt;Logs all queries and responses for audit trails in CSV or Google Sheets&lt;/li&gt;
&lt;li&gt;Runs as an event-driven workflow orchestrated through Motia's framework&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By the end of the tutorial, you'll have a complete system that combines semantic search with multi-tenant authorization.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before starting the tutorial, ensure you have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://platform.openai.com/api-keys" rel="noopener noreferrer"&gt;OpenAI API key&lt;/a&gt; for embeddings and chat&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://app.pinecone.io/" rel="noopener noreferrer"&gt;Pinecone account&lt;/a&gt; with an index created (1536 dimensions, cosine metric)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.docker.com/get-docker/" rel="noopener noreferrer"&gt;Docker&lt;/a&gt; installed for running SpiceDB locally&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Create Your Motia Project
&lt;/h3&gt;

&lt;p&gt;Create a new Motia project using the CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx motia@latest create
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The installer will prompt you:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Template:&lt;/strong&gt; Select &lt;code&gt;Base (TypeScript)&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Project name:&lt;/strong&gt; Enter &lt;code&gt;harvest-logbook-rag&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Proceed?&lt;/strong&gt; Type &lt;code&gt;Yes&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Navigate into your project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;harvest-logbook-rag
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your initial project structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;harvest-logbook-rag/
├── src/
│   └── services/
│       └── pet-store/
├── steps/
│   └── petstore/
├── .env
└── package.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The default template includes a pet store example. We'll replace this with our harvest logbook system. For more on Motia basics, see the &lt;a href="https://www.motia.dev/docs/getting-started/quick-start" rel="noopener noreferrer"&gt;Quick Start guide&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Install Dependencies
&lt;/h3&gt;

&lt;p&gt;Install the SpiceDB client for authorization:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @authzed/authzed-node
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the only additional package needed.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Setup Pinecone
&lt;/h3&gt;

&lt;p&gt;Pinecone will store the vector embeddings for semantic search.&lt;/p&gt;

&lt;h4&gt;
  
  
  Create a Pinecone Account
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;Go to &lt;a href="https://app.pinecone.io/" rel="noopener noreferrer"&gt;app.pinecone.io&lt;/a&gt; and sign up&lt;/li&gt;
&lt;li&gt;Create a new project&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  Create an Index
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;Click &lt;strong&gt;Create Index&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Configure:&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Name:&lt;/strong&gt; &lt;code&gt;harvest-logbook&lt;/code&gt; (or your preference)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dimensions:&lt;/strong&gt; &lt;code&gt;1536&lt;/code&gt; (for OpenAI embeddings)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Metric:&lt;/strong&gt; &lt;code&gt;cosine&lt;/code&gt;

&lt;ol&gt;
&lt;li&gt;Click &lt;strong&gt;Create Index&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Get Your Credentials
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;Go to &lt;strong&gt;API Keys&lt;/strong&gt; in the sidebar&lt;/li&gt;
&lt;li&gt;Copy your &lt;strong&gt;API Key&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Go back to your index&lt;/li&gt;
&lt;li&gt;Click the &lt;strong&gt;Connect&lt;/strong&gt; tab&lt;/li&gt;
&lt;li&gt;Copy the &lt;strong&gt;Host&lt;/strong&gt; (looks like: &lt;code&gt;your-index-abc123.svc.us-east-1.pinecone.io&lt;/code&gt;)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Save these for the next step.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Setup SpiceDB
&lt;/h3&gt;

&lt;p&gt;SpiceDB handles authorization and access control for the system.&lt;/p&gt;

&lt;h4&gt;
  
  
  Start SpiceDB with Docker
&lt;/h4&gt;

&lt;p&gt;Run this command to start SpiceDB locally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; spicedb &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; 50051:50051 &lt;span class="se"&gt;\&lt;/span&gt;
  authzed/spicedb serve &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--grpc-preshared-key&lt;/span&gt; &lt;span class="s2"&gt;"sometoken"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Verify SpiceDB is Running
&lt;/h4&gt;

&lt;p&gt;Check that the container is running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker ps | &lt;span class="nb"&gt;grep &lt;/span&gt;spicedb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see output similar to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;6316f6cb50b4   authzed/spicedb   "spicedb serve --grp…"   31 seconds ago   Up 31 seconds   0.0.0.0:50051-&amp;gt;50051/tcp   spicedb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;SpiceDB is now running on &lt;code&gt;localhost:50051&lt;/code&gt; and ready to handle authorization checks.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Configure Environment Variables
&lt;/h3&gt;

&lt;p&gt;Create a &lt;code&gt;.env&lt;/code&gt; file in the project root:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# OpenAI (Required for embeddings and chat)&lt;/span&gt;
&lt;span class="nv"&gt;OPENAI_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;sk-proj-xxxxxxxxxxxxx


&lt;span class="c"&gt;# Pinecone (Required for vector storage)&lt;/span&gt;
&lt;span class="nv"&gt;PINECONE_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;pcsk_xxxxxxxxxxxxx
&lt;span class="nv"&gt;PINECONE_INDEX_HOST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your-index-abc123.svc.us-east-1.pinecone.io


&lt;span class="c"&gt;# SpiceDB (Required for authorization)&lt;/span&gt;
&lt;span class="nv"&gt;SPICEDB_ENDPOINT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;localhost:50051
&lt;span class="nv"&gt;SPICEDB_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;sometoken


&lt;span class="c"&gt;# LLM Configuration (OpenAI is default)&lt;/span&gt;
&lt;span class="nv"&gt;USE_OPENAI_CHAT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;


&lt;span class="c"&gt;# Logging Configuration (CSV is default)&lt;/span&gt;
&lt;span class="nv"&gt;USE_CSV_LOGGER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace the placeholder values with your actual credentials from the previous steps.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Initialize SpiceDB Schema
&lt;/h3&gt;

&lt;p&gt;SpiceDB needs a schema that defines the authorization model for organizations, farms, and users.&lt;/p&gt;

&lt;h4&gt;
  
  
  Create the Schema File
&lt;/h4&gt;

&lt;p&gt;Create &lt;code&gt;src/services/harvest-logbook/spicedb.schema&lt;/code&gt; with the authorization model. &lt;a href="https://authzed.com/docs/spicedb/concepts/schema" rel="noopener noreferrer"&gt;A SpiceDB schema&lt;/a&gt; defines the types of objects found your application, how those objects can relate to one another, and the permissions that can be computed off of those relations. &lt;/p&gt;

&lt;p&gt;Here's a snippet of the schema that defines &lt;code&gt;user&lt;/code&gt;, &lt;code&gt;organization&lt;/code&gt; and &lt;code&gt;farm&lt;/code&gt; and the relations and permissions between them.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;definition user {}


definition organization {
    relation admin: user
    relation member: user

    permission view = admin + member
    permission edit = admin + member
    permission query = admin + member
    permission manage = admin
}


definition farm {
    relation organization: organization
    relation owner: user
    relation editor: user
    relation viewer: user

    permission view = viewer + editor + owner + organization-&amp;gt;view
    permission edit = editor + owner + organization-&amp;gt;edit
    permission query = viewer + editor + owner + organization-&amp;gt;query
    permission manage = owner + organization-&amp;gt;admin
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://github.com/MotiaDev/motia-examples/blob/main/examples/harvest-logbook-rag/src/services/harvest-logbook/spicedb.schema" rel="noopener noreferrer"&gt;View the complete schema on GitHub&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The schema establishes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Organizations with admins and members&lt;/li&gt;
&lt;li&gt;Farms with owners, editors, and viewers
&lt;/li&gt;
&lt;li&gt;Harvest entries linked to farms&lt;/li&gt;
&lt;li&gt;Permission inheritance (org members can access farms in their org)&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Create Setup Scripts
&lt;/h4&gt;

&lt;p&gt;Create a &lt;code&gt;scripts/&lt;/code&gt; folder and add three files:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;scripts/setup-spicedb-schema.ts&lt;/code&gt;&lt;/strong&gt; - Reads the schema file and writes it to SpiceDB&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/MotiaDev/motia-examples/blob/main/examples/harvest-logbook-rag/scripts/setup-spicedb-schema.ts" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;scripts/verify-spicedb-schema.ts&lt;/code&gt;&lt;/strong&gt; - Verifies the schema was written correctly&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/MotiaDev/motia-examples/blob/main/examples/harvest-logbook-rag/scripts/verify-spicedb-schema.ts" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;scripts/create-sample-permissions.ts&lt;/code&gt;&lt;/strong&gt; - Creates sample users and permissions for testing&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/Taofiqq/motia-examples/blob/main/examples/harvest-logbook-rag/scripts/create-sample-permissions.ts" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Install Script Runner
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-D&lt;/span&gt; tsx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Add Scripts to package.json
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"spicedb:setup"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tsx scripts/setup-spicedb-schema.ts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"spicedb:verify"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tsx scripts/verify-spicedb-schema.ts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"spicedb:sample"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tsx scripts/create-sample-permissions.ts"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Run the Setup
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Write schema to SpiceDB&lt;/span&gt;
npm run spicedb:setup
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see output confirming the schema was written successfully:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqxxqk0pwdg9nk4tlr3kw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqxxqk0pwdg9nk4tlr3kw.png" alt="image" width="800" height="309"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Verify it was written correctly&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm run spicedb:verify
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This displays the complete authorization schema showing all definitions and permissions:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc7cixl0gbell7pk0lnzv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc7cixl0gbell7pk0lnzv.png" alt="image" width="800" height="920"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The output shows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;farm&lt;/strong&gt; definition with owner/editor/viewer roles&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;harvest_entry&lt;/strong&gt; definition linked to farms&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;organization&lt;/strong&gt; definition with admin/member roles&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;query_session&lt;/strong&gt; definition for RAG queries&lt;/li&gt;
&lt;li&gt;Permission rules for each resource type&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Create sample user (user_alice as owner of farm_1):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm run spicedb:sample
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1rplo5z9qtzunxz9lxdp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1rplo5z9qtzunxz9lxdp.png" alt="image" width="800" height="613"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This creates &lt;code&gt;user_alice&lt;/code&gt; as owner of &lt;code&gt;farm_1&lt;/code&gt;, ready for testing.&lt;/p&gt;

&lt;p&gt;Your authorization system is now ready.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Start Development Server
&lt;/h3&gt;

&lt;p&gt;Start the Motia development server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The server starts at &lt;code&gt;http://localhost:3000&lt;/code&gt;. Open this URL in your browser to see the Motia Workbench.&lt;/p&gt;

&lt;p&gt;You'll see the default pet store example. We'll replace this with our harvest logbook system in the next sections.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvach47jk4h3mq239gq03.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvach47jk4h3mq239gq03.png" alt="image" width="800" height="501"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Your development environment is now ready. All services are connected:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Motia running on &lt;code&gt;localhost:3000&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Pinecone index created and connected&lt;/li&gt;
&lt;li&gt;SpiceDB running with schema loaded&lt;/li&gt;
&lt;li&gt;Sample permissions created (&lt;code&gt;user_alice&lt;/code&gt; owns &lt;code&gt;farm_1&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Exploring the Project
&lt;/h2&gt;

&lt;p&gt;Before we start building, let's understand the architecture we're creating.&lt;/p&gt;

&lt;h3&gt;
  
  
  System Architecture
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────────────────────────────────────────────┐
│  POST /harvest_logbook                                      │
│  (Store harvest data + optional query)                      │
└─────────┬───────────────────────────────────────────────────┘
          │
          ├─→ Authorization Middleware (SpiceDB)
          │   - Check user has 'edit' permission on farm
          │
          ├─→ ReceiveHarvestData Step (API)
          │   - Validate input
          │   - Emit events
          │
          ├─→ ProcessEmbeddings Step (Event)
          │   - Split text into chunks (400 chars, 40 overlap)
          │   - Generate embeddings (OpenAI)
          │   - Store vectors (Pinecone)
          │
          └─→ QueryAgent Step (Event) [if query provided]
              - Retrieve similar content (Pinecone)
              - Generate response (OpenAI/HuggingFace)
              - Emit logging event
              │
              └─→ LogToSheets Step (Event)
                  - Log query &amp;amp; response (CSV/Sheets)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The RAG Pipeline
&lt;/h3&gt;

&lt;p&gt;Our system processes harvest data through these stages:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;API Entry&lt;/strong&gt; - Receive harvest data via REST endpoint&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Text Chunking&lt;/strong&gt; - Split content into overlapping chunks (400 chars, 40 overlap)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Embedding Generation&lt;/strong&gt; - Convert chunks to vectors using OpenAI&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vector Storage&lt;/strong&gt; - Store embeddings in Pinecone for semantic search&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Query Processing&lt;/strong&gt; - Search vectors and generate AI responses&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Audit Logging&lt;/strong&gt; - Log all queries and responses&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Event-Driven Architecture
&lt;/h3&gt;

&lt;p&gt;The system uses Motia's event-driven model:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;API Steps&lt;/strong&gt; handle HTTP requests&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Event Steps&lt;/strong&gt; process background tasks&lt;/li&gt;
&lt;li&gt;Steps communicate by emitting and subscribing to events&lt;/li&gt;
&lt;li&gt;Each step is independent and can be tested separately&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Authorization Layer
&lt;/h3&gt;

&lt;p&gt;Every API request passes through SpiceDB authorization:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Users have relationships with resources (owner, editor, viewer)&lt;/li&gt;
&lt;li&gt;Permissions are checked before processing requests&lt;/li&gt;
&lt;li&gt;Multi-tenant by design (users only access their farms)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What We'll Build
&lt;/h3&gt;

&lt;p&gt;We'll create five main steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;ReceiveHarvestData&lt;/strong&gt; - API endpoint to store harvest entries&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ProcessEmbeddings&lt;/strong&gt; - Event handler for generating and storing embeddings&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;QueryAgent&lt;/strong&gt; - Event handler for AI-powered queries&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;QueryOnly&lt;/strong&gt; - Separate API endpoint for querying without storing data&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LogToSheets&lt;/strong&gt; - Event handler for audit logging&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each component is a single file in the &lt;code&gt;steps/&lt;/code&gt; directory. Motia automatically discovers and connects them based on the events they emit and subscribe to.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Create the Harvest Entry API
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What We're Building
&lt;/h3&gt;

&lt;p&gt;In this step, we'll create an API endpoint that receives harvest log data and triggers the processing pipeline. This is the entry point that starts the entire RAG workflow.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why This Step Matters
&lt;/h3&gt;

&lt;p&gt;Every workflow needs an entry point. In Motia, API steps serve as the gateway between external requests and your event-driven system. By using Motia's &lt;code&gt;api&lt;/code&gt; step type, you get automatic HTTP routing, request validation, and event emission, all without writing boilerplate server code. When a farmer calls this endpoint with their harvest data, it validates the input, checks authorization, stores the entry, and emits events that trigger the embedding generation and optional query processing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create the Step File
&lt;/h3&gt;

&lt;p&gt;Create a new file at &lt;code&gt;steps/harvest-logbook/receive-harvest-data.step.ts&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The complete source code for all steps is available on GitHub. You can reference the working implementation at any time.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://github.com/MotiaDev/motia-examples/blob/main/examples/harvest-logbook-rag/steps/harvest-logbook/receive-harvest-data.step.ts" rel="noopener noreferrer"&gt;View the complete Step 1 code on GitHub →&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5tc0460csicfa0i30lud.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5tc0460csicfa0i30lud.png" alt="image" width="800" height="453"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now let's understand the key parts you'll be implementing:&lt;/p&gt;

&lt;h3&gt;
  
  
  Input Validation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bodySchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content cannot be empty&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;farmId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Farm ID is required for authorization&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Zod validates that requests include the harvest content and farm ID. The &lt;code&gt;query&lt;/code&gt; field is optional - if provided, the system will also answer a natural language question about the data after storing it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step Configuration
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ApiRouteConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;api&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ReceiveHarvestData&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/harvest_logbook&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;middleware&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;errorHandlerMiddleware&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;harvestEntryEditMiddleware&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;emits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;process-embeddings&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;query-agent&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="nx"&gt;bodySchema&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;type: 'api'&lt;/code&gt; makes this an HTTP endpoint&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;middleware&lt;/code&gt; runs authorization checks before the handler&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;emits&lt;/code&gt; declares this step triggers embedding processing and optional query events&lt;/li&gt;
&lt;li&gt;Motia handles all the routing automatically&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Authorization Check
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;middleware&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;errorHandlerMiddleware&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;harvestEntryEditMiddleware&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;harvestEntryEditMiddleware&lt;/code&gt; checks SpiceDB to ensure the user has &lt;code&gt;edit&lt;/code&gt; permission on the specified farm. If authorization fails, the request is rejected before reaching the handler. Authorization info is added to the request for use in the handler.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/MotiaDev/motia-examples/blob/main/examples/harvest-logbook-rag/middlewares/authz.middleware.ts" rel="noopener noreferrer"&gt;View authorization middleware →&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Handler Logic
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Handlers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ReceiveHarvestData&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;farmId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;bodySchema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;entryId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`harvest-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Store entry data in state&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;harvest-entries&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;entryId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;farmId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// Emit event to process embeddings&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;topic&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;process-embeddings&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;entryId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;metadata&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The handler generates a unique entry ID, stores the data in Motia's state management, and emits an event to trigger embedding processing. If a query was provided, it also emits a &lt;code&gt;query-agent&lt;/code&gt; event.&lt;/p&gt;

&lt;h3&gt;
  
  
  Event Emission
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;topic&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;process-embeddings&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;entryId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;farmId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;


&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;topic&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;query-agent&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;entryId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Events are how Motia steps communicate. The &lt;code&gt;process-embeddings&lt;/code&gt; event triggers the next step to chunk the text and generate embeddings. If a query was provided, the &lt;code&gt;query-agent&lt;/code&gt; event runs in parallel to answer the question using RAG.&lt;/p&gt;

&lt;p&gt;This keeps the API response fast as it returns immediately while processing happens in the background.&lt;/p&gt;

&lt;h3&gt;
  
  
  Test the Step
&lt;/h3&gt;

&lt;p&gt;Open the Motia Workbench and test this endpoint:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Click on the &lt;code&gt;harvest-logbook&lt;/code&gt; flow&lt;/li&gt;
&lt;li&gt;Find &lt;code&gt;POST /harvest_logbook&lt;/code&gt; in the sidebar&lt;/li&gt;
&lt;li&gt;Click on it to open the request panel&lt;/li&gt;
&lt;li&gt;Switch to the &lt;strong&gt;Headers&lt;/strong&gt; tab and add:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"x-user-id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user_alice"&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Switch to the &lt;strong&gt;Body&lt;/strong&gt; tab and add:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Harvested 500kg of tomatoes from field A. Weather was sunny."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"farmId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"farm_1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"metadata"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="nl"&gt;"field"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"A"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="nl"&gt;"crop"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tomatoes"&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Click &lt;strong&gt;Send&lt;/strong&gt; button.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You should see a success response with the entry ID. The Workbench will show the workflow executing in real-time, with events flowing to the next steps.&lt;/p&gt;



&lt;h2&gt;
  
  
  Step 2: Process Embeddings
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What We're Building
&lt;/h3&gt;

&lt;p&gt;This event handler takes the harvest data from Step 1, splits it into chunks, generates vector embeddings, and stores them in Pinecone for semantic search.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why This Step Matters
&lt;/h3&gt;

&lt;p&gt;RAG systems need to break down large text into smaller chunks for better retrieval accuracy. By chunking text with overlap and generating embeddings for each piece, we enable semantic search that finds relevant context even when queries don't match exact keywords.&lt;/p&gt;

&lt;p&gt;This step runs in the background after the API returns, keeping the user experience fast while handling the background work of embedding generation and vector storage.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create the Step File
&lt;/h3&gt;

&lt;p&gt;Create a new file at &lt;code&gt;steps/harvest-logbook/process-embeddings.step.ts&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/MotiaDev/motia-examples/blob/main/examples/harvest-logbook-rag/steps/harvest-logbook/process-embeddings.step.ts" rel="noopener noreferrer"&gt;View the complete Step 2 code on GitHub →&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now let's understand the key parts you'll be implementing:&lt;/p&gt;

&lt;h3&gt;
  
  
  Input Schema
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;inputSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;entryId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This step receives the entry ID, content, and metadata from the previous step's event emission.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step Configuration
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;EventConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;event&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ProcessEmbeddings&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;subscribes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;process-embeddings&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;emits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
  &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;inputSchema&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;type: 'event'&lt;/code&gt; makes this a background event handler&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;subscribes: ['process-embeddings']&lt;/code&gt; listens for events from Step 1&lt;/li&gt;
&lt;li&gt;No emits - this is the end of the embedding pipeline&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Text Chunking
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;vectorIds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;HarvestLogbookService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;storeEntry&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;entryId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The service handles text splitting (400 character chunks with 40 character overlap), embedding generation via OpenAI, and storage in Pinecone. This chunking strategy ensures semantic continuity across chunks.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/MotiaDev/motia-examples/blob/main/examples/harvest-logbook-rag/src/services/harvest-logbook/text-splitter.ts" rel="noopener noreferrer"&gt;View text splitter service →&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Embedding Generation
&lt;/h3&gt;

&lt;p&gt;The OpenAI service generates 1536-dimension embeddings for each text chunk using the &lt;code&gt;text-embedding-ada-002&lt;/code&gt; model.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/MotiaDev/motia-examples/blob/main/examples/harvest-logbook-rag/src/services/harvest-logbook/openai-service.ts" rel="noopener noreferrer"&gt;View OpenAI service →&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Vector Storage
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;harvest-vectors&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;entryId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;vectorIds&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;processedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;chunkCount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;vectorIds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After storing vectors in Pinecone, the step updates Motia's state with the vector IDs for tracking. Each chunk gets a unique ID like &lt;code&gt;harvest-123-chunk-0&lt;/code&gt;, &lt;code&gt;harvest-123-chunk-1&lt;/code&gt;, etc.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/MotiaDev/motia-examples/blob/main/examples/harvest-logbook-rag/src/services/harvest-logbook/pinecone-service.ts" rel="noopener noreferrer"&gt;View Pinecone service →&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The embeddings are now stored and ready for semantic search when users query the system.&lt;/p&gt;

&lt;h3&gt;
  
  
  Test the Step
&lt;/h3&gt;

&lt;p&gt;Step 2 runs automatically when Step 1 emits the &lt;code&gt;process-embeddings&lt;/code&gt; event. To test it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Send a request to the &lt;code&gt;POST /harvest_logbook&lt;/code&gt; endpoint (from Step 1)&lt;/li&gt;
&lt;li&gt;In the Workbench, watch the workflow visualization&lt;/li&gt;
&lt;li&gt;You'll see the &lt;code&gt;ProcessEmbeddings&lt;/code&gt; step activate automatically&lt;/li&gt;
&lt;li&gt;Check the &lt;strong&gt;Logs&lt;/strong&gt; tab at the bottom to see:&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;Text chunking progress&lt;/li&gt;
&lt;li&gt;Embedding generation&lt;/li&gt;
&lt;li&gt;Vector storage confirmation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The step completes when you see "Successfully stored embeddings" in the logs. The vectors are now in Pinecone and ready for semantic search.&lt;/p&gt;



&lt;h2&gt;
  
  
  Step 3: Query Agent
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What We're Building
&lt;/h3&gt;

&lt;p&gt;This event handler performs the RAG query, it searches Pinecone for relevant content, retrieves matching chunks, and uses an LLM to generate natural language responses based on the retrieved context.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why This Step Matters
&lt;/h3&gt;

&lt;p&gt;This is where retrieval-augmented generation happens. Instead of the LLM generating responses from its training data alone, it uses actual harvest data from Pinecone as context. This ensures accurate, source-backed answers specific to the user's farm data.&lt;/p&gt;

&lt;p&gt;The step supports both OpenAI and HuggingFace LLMs, giving you flexibility in choosing your AI provider based on cost and performance needs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create the Step File
&lt;/h3&gt;

&lt;p&gt;Create a new file at &lt;code&gt;steps/harvest-logbook/query-agent.step.ts&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/MotiaDev/motia-examples/blob/main/examples/harvest-logbook-rag/steps/harvest-logbook/query-agent.step.ts" rel="noopener noreferrer"&gt;View the complete Step 3 code on GitHub →&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now let's understand the key parts you'll be implementing:&lt;/p&gt;

&lt;h3&gt;
  
  
  Input Schema
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;inputSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;entryId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;conversationHistory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enum&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;assistant&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;system&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
    &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;})).&lt;/span&gt;&lt;span class="nf"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The step receives the query text and optional conversation history for multi-turn conversations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step Configuration
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;EventConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;event&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;QueryAgent&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;subscribes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;query-agent&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;emits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;log-to-sheets&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;inputSchema&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;subscribes: ['query-agent']&lt;/code&gt; listens for query events from Step 1&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;emits: ['log-to-sheets']&lt;/code&gt; triggers logging after generating response&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  RAG Query Process
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;agentResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;HarvestLogbookService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;queryWithAgent&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;conversationHistory&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The service orchestrates the RAG pipeline: embedding the query, searching Pinecone for similar vectors, extracting context from top matches, and generating a response using the LLM.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/MotiaDev/motia-examples/blob/main/examples/harvest-logbook-rag/src/services/harvest-logbook/index.ts" rel="noopener noreferrer"&gt;View RAG orchestration service →&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Vector Search
&lt;/h3&gt;

&lt;p&gt;The query is embedded using OpenAI and searched against Pinecone to find the top 5 most similar chunks. Each result includes a similarity score and the original text.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/MotiaDev/motia-examples/blob/main/examples/harvest-logbook-rag/src/services/harvest-logbook/pinecone-service.ts" rel="noopener noreferrer"&gt;View Pinecone query implementation →&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  LLM Response Generation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;agent-responses&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;entryId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;agentResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;sources&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;agentResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sources&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;agentResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timestamp&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The LLM generates a response using the retrieved context. The system supports both OpenAI (default) and HuggingFace, controlled by the &lt;code&gt;USE_OPENAI_CHAT&lt;/code&gt; environment variable. The response includes source citations showing which harvest entries informed the answer.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/MotiaDev/motia-examples/blob/main/examples/harvest-logbook-rag/src/services/harvest-logbook/openai-chat-service.ts" rel="noopener noreferrer"&gt;View OpenAI chat service →&lt;/a&gt;\&lt;br&gt;
&lt;a href="https://github.com/MotiaDev/motia-examples/blob/main/examples/harvest-logbook-rag/src/services/harvest-logbook/huggingface-service.ts" rel="noopener noreferrer"&gt;View HuggingFace service →&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Event Emission
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;topic&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;log-to-sheets&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;entryId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;agentResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;sources&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;agentResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sources&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;After generating the response, the step emits a logging event to create an audit trail of all queries and responses.&lt;/p&gt;
&lt;h3&gt;
  
  
  Test the Step
&lt;/h3&gt;

&lt;p&gt;Step 3 runs automatically when you include a &lt;code&gt;query&lt;/code&gt; field in the Step 1 request. To test it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Send a request to &lt;code&gt;POST /harvest_logbook&lt;/code&gt; with a query:
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Harvested 500kg of tomatoes from field A. Weather was sunny."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"farmId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"farm_1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"query"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"What crops did we harvest?"&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ol&gt;
&lt;li&gt;In the Workbench, watch the &lt;code&gt;QueryAgent&lt;/code&gt; step activate&lt;/li&gt;
&lt;li&gt;Check the &lt;strong&gt;Logs&lt;/strong&gt; tab to see:&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;Query embedding generation&lt;/li&gt;
&lt;li&gt;Vector search in Pinecone&lt;/li&gt;
&lt;li&gt;LLM response generation&lt;/li&gt;
&lt;li&gt;Source citations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The step completes when you see the AI-generated response in the logs. The query and response are automatically logged by Step 5.&lt;/p&gt;


&lt;h2&gt;
  
  
  Step 4: Query-Only Endpoint
&lt;/h2&gt;
&lt;h3&gt;
  
  
  What We're Building
&lt;/h3&gt;

&lt;p&gt;This API endpoint allows users to query their existing harvest data without storing new entries. It's a separate endpoint dedicated purely to RAG queries.&lt;/p&gt;
&lt;h3&gt;
  
  
  Why This Step Matters
&lt;/h3&gt;

&lt;p&gt;While Step 1 handles both storing and optionally querying data, users often need to just ask questions about their existing harvest logs. This dedicated endpoint keeps the API clean and focused - one endpoint for data entry, another for pure queries.&lt;/p&gt;

&lt;p&gt;This separation also makes it easier to apply different rate limits or permissions between data modification and read-only operations.&lt;/p&gt;
&lt;h3&gt;
  
  
  Create the Step File
&lt;/h3&gt;

&lt;p&gt;Create a new file at &lt;code&gt;steps/harvest-logbook/query-only.step.ts&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/MotiaDev/motia-examples/blob/main/examples/harvest-logbook-rag/steps/harvest-logbook/query-only.step.ts" rel="noopener noreferrer"&gt;View the complete Step 4 code on GitHub →&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now let's understand the key parts you'll be implementing:&lt;/p&gt;
&lt;h3&gt;
  
  
  Input Validation
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bodySchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Query cannot be empty&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;farmId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Farm ID is required for authorization&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;conversationHistory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enum&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;assistant&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;system&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
    &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;})).&lt;/span&gt;&lt;span class="nf"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The request requires a query and farm ID. Conversation history is optional for multi-turn conversations.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step Configuration
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ApiRouteConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;api&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;QueryHarvestLogbook&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/harvest_logbook/query&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;middleware&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;errorHandlerMiddleware&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;harvestQueryMiddleware&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;emits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;query-agent&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;path: '/harvest_logbook/query'&lt;/code&gt; creates a dedicated query endpoint&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;harvestQueryMiddleware&lt;/code&gt; checks for &lt;code&gt;query&lt;/code&gt; permission (not &lt;code&gt;edit&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;emits: ['query-agent']&lt;/code&gt; triggers the same RAG query handler as Step 3&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Authorization Middleware
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;middleware&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;errorHandlerMiddleware&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;harvestQueryMiddleware&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The &lt;code&gt;harvestQueryMiddleware&lt;/code&gt; checks SpiceDB for &lt;code&gt;query&lt;/code&gt; permission. This is less restrictive than &lt;code&gt;edit&lt;/code&gt; - viewers can query but cannot modify data.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/MotiaDev/motia-examples/blob/main/examples/harvest-logbook-rag/middlewares/authz.middleware.ts" rel="noopener noreferrer"&gt;View authorization middleware →&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Handler Logic
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Handlers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;QueryHarvestLogbook&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;logger&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;farmId&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;bodySchema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;queryId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`query-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;topic&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;query-agent&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;entryId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;queryId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;queryId&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The handler generates a unique query ID and emits the same &lt;code&gt;query-agent&lt;/code&gt; event used in Step 1. This reuses the RAG pipeline from Step 3 without duplicating code.&lt;/p&gt;

&lt;p&gt;The API returns immediately with the query ID. The actual processing happens in the background, and results are logged by Step 5.&lt;/p&gt;
&lt;h3&gt;
  
  
  Test the Step
&lt;/h3&gt;

&lt;p&gt;This is the dedicated query endpoint. Test it directly:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Click on &lt;code&gt;POST /harvest_logbook/query&lt;/code&gt; in the Workbench&lt;/li&gt;
&lt;li&gt;Add the header:
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"x-user-id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user_alice"&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ol&gt;
&lt;li&gt;Add the body:
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"query"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"What crops did we harvest?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"farmId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"farm_1"&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ol&gt;
&lt;li&gt;Click &lt;strong&gt;Send&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You'll see a &lt;code&gt;200 OK&lt;/code&gt; response with the query ID. In the &lt;strong&gt;Logs&lt;/strong&gt; tab, watch for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;QueryHarvestLogbook&lt;/code&gt; - Authorization and query received&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;QueryAgent&lt;/code&gt; - Querying AI agent&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;QueryAgent&lt;/code&gt; - Agent query completed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The query runs in the background and results are logged by Step 5. This endpoint is perfect for read-only query operations without storing new data.&lt;/p&gt;


&lt;h2&gt;
  
  
  Step 5: Log to Sheets
&lt;/h2&gt;
&lt;h3&gt;
  
  
  What We're Building
&lt;/h3&gt;

&lt;p&gt;This event handler creates an audit trail by logging every query and its AI-generated response. It supports both local CSV files (for development) and Google Sheets (for production).&lt;/p&gt;
&lt;h3&gt;
  
  
  Why This Step Matters
&lt;/h3&gt;

&lt;p&gt;Audit logs are essential for understanding how users interact with your system. They help with debugging, monitoring usage patterns, and maintaining compliance. By logging queries and responses, you can track what questions users ask, identify common patterns, and improve the system over time.&lt;/p&gt;

&lt;p&gt;The dual logging strategy (CSV/Google Sheets) gives you flexibility, use CSV locally for quick testing, then switch to Google Sheets for production without changing code.&lt;/p&gt;
&lt;h3&gt;
  
  
  Create the Step File
&lt;/h3&gt;

&lt;p&gt;Create a new file at &lt;code&gt;steps/harvest-logbook/log-to-sheets.step.ts&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/MotiaDev/motia-examples/blob/main/examples/harvest-logbook-rag/steps/harvest-logbook/log-to-sheets.step.ts" rel="noopener noreferrer"&gt;View the complete Step 5 code on GitHub →&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now let's understand the key parts you'll be implementing:&lt;/p&gt;
&lt;h3&gt;
  
  
  Input Schema
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;inputSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;entryId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;sources&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The step receives the query, AI response, and optional source citations from Step 3.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step Configuration
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;EventConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;event&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;LogToSheets&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;subscribes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;log-to-sheets&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;emits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
  &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;inputSchema&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;subscribes: ['log-to-sheets']&lt;/code&gt; listens for logging events from Step 3&lt;/li&gt;
&lt;li&gt;No emits - this is the end of the workflow&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Logging Service Selection
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;useCSV&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;USE_CSV_LOGGER&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;true&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GOOGLE_SHEETS_ID&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;


&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;HarvestLogbookService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;logToSheets&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sources&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The service automatically chooses between CSV and Google Sheets based on environment variables. This keeps the step code simple while supporting different deployment scenarios.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/MotiaDev/motia-examples/blob/main/examples/harvest-logbook-rag/src/services/harvest-logbook/csv-logger.ts" rel="noopener noreferrer"&gt;View CSV logger →&lt;/a&gt;\&lt;br&gt;
&lt;a href="https://github.com/MotiaDev/motia-examples/blob/main/examples/harvest-logbook-rag/src/services/harvest-logbook/sheets-service.ts" rel="noopener noreferrer"&gt;View Google Sheets service →&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Error Handling
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;HarvestLogbookService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;logToSheets&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sources&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Successfully logged to &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;destination&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Failed to log query response&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// Don't throw - logging failures shouldn't break the main flow&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The step catches logging errors without throwing. This ensures that even if logging fails, the main workflow completes successfully. Users get their query results even if the audit log has issues.&lt;/p&gt;
&lt;h3&gt;
  
  
  CSV Output Format
&lt;/h3&gt;

&lt;p&gt;The CSV logger saves entries to &lt;code&gt;logs/harvest_logbook.csv&lt;/code&gt; with these columns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Timestamp&lt;/li&gt;
&lt;li&gt;Query&lt;/li&gt;
&lt;li&gt;Response&lt;/li&gt;
&lt;li&gt;Sources (comma-separated)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each entry is automatically escaped to handle quotes and commas in the content.&lt;/p&gt;
&lt;h3&gt;
  
  
  Test the Step
&lt;/h3&gt;

&lt;p&gt;Step 5 runs automatically after Step 3 completes. To verify it's working:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Run a query using &lt;code&gt;POST /harvest_logbook/query&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Check the &lt;strong&gt;Logs&lt;/strong&gt; tab for &lt;code&gt;LogToSheets&lt;/code&gt; entries&lt;/li&gt;
&lt;li&gt;Verify the CSV file was created:
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   &lt;span class="nb"&gt;cat &lt;/span&gt;logs/harvest_logbook.csv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;




&lt;p&gt;You should see your query and response logged with a timestamp. Each subsequent query appends a new row to the CSV file.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4abz6syr70txk58ltv8w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4abz6syr70txk58ltv8w.png" alt="image" width="800" height="295"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Testing the System
&lt;/h2&gt;

&lt;p&gt;Now that all steps are built, let's test the complete workflow using the Motia Workbench.&lt;/p&gt;
&lt;h3&gt;
  
  
  Start the Server
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Open &lt;code&gt;http://localhost:3000&lt;/code&gt; in your browser to access the Workbench.&lt;/p&gt;
&lt;h3&gt;
  
  
  Test 1: Store Harvest Data
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Select the &lt;code&gt;harvest-logbook&lt;/code&gt; flow from the dropdown&lt;/li&gt;
&lt;li&gt;Find the &lt;code&gt;POST /harvest_logbook&lt;/code&gt; endpoint in the workflow&lt;/li&gt;
&lt;li&gt;Click on it to open the request panel&lt;/li&gt;
&lt;li&gt;Add the authorization header:
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"x-user-id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user_alice"&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ol&gt;
&lt;li&gt;Set the request body:
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Harvested 500kg of tomatoes from field A. Weather was sunny, no pest damage observed."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"farmId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"farm_1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"metadata"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="nl"&gt;"field"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"A"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="nl"&gt;"crop"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tomatoes"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="nl"&gt;"weight_kg"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ol&gt;
&lt;li&gt;Click &lt;strong&gt;Play&lt;/strong&gt; Button&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Watch the workflow execute in real-time. You'll see:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Authorization check passes (user_alice has edit permission)&lt;/li&gt;
&lt;li&gt;Text chunked into embeddings&lt;/li&gt;
&lt;li&gt;Vectors stored in Pinecone&lt;/li&gt;
&lt;li&gt;Success response returned&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Test 2: Query the Data
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Find the &lt;code&gt;POST /harvest_logbook/query&lt;/code&gt; endpoint&lt;/li&gt;
&lt;li&gt;Add the authorization header:
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"x-user-id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user_alice"&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ol&gt;
&lt;li&gt;Set the request body:
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"farmId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"farm_1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"query"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"What crops did we harvest recently?"&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ol&gt;
&lt;li&gt;Click &lt;strong&gt;Send&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Watch the RAG pipeline execute:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Query embedded via OpenAI&lt;/li&gt;
&lt;li&gt;Similar vectors retrieved from Pinecone&lt;/li&gt;
&lt;li&gt;AI generates response with context&lt;/li&gt;
&lt;li&gt;Query and response logged to CSV&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Test 3: Verify Authorization
&lt;/h3&gt;

&lt;p&gt;Try querying as a user without permission:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Use the same query endpoint&lt;/li&gt;
&lt;li&gt;Change the header:
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"x-user-id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user_unauthorized"&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ol&gt;
&lt;li&gt;Click &lt;strong&gt;Send&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You'll see a 403 Forbidden response - authorization works correctly.&lt;/p&gt;
&lt;h3&gt;
  
  
  View the Logs
&lt;/h3&gt;

&lt;p&gt;Check the audit trail:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat &lt;/span&gt;logs/harvest_logbook.csv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll see all queries and responses logged with timestamps.&lt;/p&gt;

&lt;p&gt;The Workbench also provides trace visualization showing exactly how data flows through each step, making debugging straightforward.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;You've built a complete RAG system with multi-tenant authorization using Motia's event-driven framework. You learned how to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Build event-driven workflows with Motia steps&lt;/li&gt;
&lt;li&gt;Implement RAG with text chunking, embeddings, and vector search&lt;/li&gt;
&lt;li&gt;Add fine-grained authorization using SpiceDB's relationship model&lt;/li&gt;
&lt;li&gt;Handle async operations with event emission&lt;/li&gt;
&lt;li&gt;Integrate multiple services (OpenAI, Pinecone, SpiceDB)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Your system now handles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Semantic search over harvest data with AI-powered embeddings&lt;/li&gt;
&lt;li&gt;Natural language querying with contextually relevant answers&lt;/li&gt;
&lt;li&gt;Multi-tenant access control with role-based permissions&lt;/li&gt;
&lt;li&gt;Event-driven processing for fast API responses&lt;/li&gt;
&lt;li&gt;Audit logging for compliance and debugging&lt;/li&gt;
&lt;li&gt;Flexible LLM options (OpenAI or HuggingFace)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your RAG system is ready to help farmers query their harvest data naturally while keeping data secure with proper authorization.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;This was a fun exercise in tackling a complex authorization problem and also building something production-grade. I also got to play out some of my Stardew Valley fancies IRL. Maybe it's time I actually move to a cozy farm and grow my own crops (so long as teh farm has a good Internet connection!)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2dv3ft9hkpfvrpi7mggr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2dv3ft9hkpfvrpi7mggr.png" alt="stardew farm" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The repository can be &lt;a href="https://github.com/MotiaDev/motia-examples/tree/main/examples/harvest-logbook-rag/src/services/harvest-logbook" rel="noopener noreferrer"&gt;found on the Motia GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Feel free to reach out to us on LinkedIn or jump into the &lt;a href="https://authzed.com/discord" rel="noopener noreferrer"&gt;SpiceDB Discord&lt;/a&gt; if you have any questions. Happy farming!&lt;/p&gt;

</description>
      <category>rag</category>
      <category>iam</category>
      <category>security</category>
      <category>ai</category>
    </item>
    <item>
      <title>Friends Don't Let Friends Write Custom Authorization Code</title>
      <dc:creator>Sohan</dc:creator>
      <pubDate>Thu, 17 Jul 2025 09:56:58 +0000</pubDate>
      <link>https://dev.to/sohan26/friends-dont-let-friends-write-custom-authorization-code-10fo</link>
      <guid>https://dev.to/sohan26/friends-dont-let-friends-write-custom-authorization-code-10fo</guid>
      <description>&lt;p&gt;Let’s get right to it:&lt;/p&gt;

&lt;p&gt;🗣️ Never write your own authorization code. &lt;strong&gt;Just don’t&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You’ve probably done it. We all have. That innocent-looking if statement checking user roles:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;func myApi() {
  roles := fetch_roles_for(request.user)
  if "admin" in roles || "editor" in roles {
      approve()
  }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It feels fine… until six months later, you’re drowning in permission bugs, scrambling to debug why Bob from Finance can edit your production database. Sound familiar? &lt;/p&gt;

&lt;p&gt;Let’s unpack why this happens—and why you should stop before writing another line of custom AuthZ code.&lt;/p&gt;

&lt;h2&gt;
  
  
  🧠 Code Is Debt, Especially AuthZ Code
&lt;/h2&gt;

&lt;p&gt;Every line of authorization code you write is debt:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt; It must be tested.&lt;/li&gt;
&lt;li&gt; It must be maintained.&lt;/li&gt;
&lt;li&gt; It must be reviewed and secured.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;AuthZ bugs are security bugs. One wrong &lt;code&gt;if&lt;/code&gt; statement, and your customer’s sensitive data leaks. Good luck explaining that breach to your CISO or customers. Just look at the number of recent data breaches that have occurred thanks to broken access control. &lt;/p&gt;

&lt;h2&gt;
  
  
  🔄 Hard to Evolve
&lt;/h2&gt;

&lt;p&gt;Here’s a scenario:&lt;br&gt;
Your simple role checks work until your CTO suddenly says,&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"We need fine-grained access control for every document by next quarter."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Now your tidy &lt;code&gt;if role == 'admin'&lt;/code&gt; logic needs to handle document-level permissions, delegation, and auditing. You’re staring at your old code thinking: "How did we get here?”&lt;/p&gt;

&lt;p&gt;Even worse if you’re a polyglot shop: you now need to replicate your homegrown logic across Go, Node.js, Python, and Java… or expose it as an RPC service and accidentally create a distributed dependency nightmare.&lt;/p&gt;

&lt;h2&gt;
  
  
  📉 Role-Based Agony (RBAC Explosion)
&lt;/h2&gt;

&lt;p&gt;Role-Based Access Control (RBAC) always starts simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Admin&lt;/li&gt;
&lt;li&gt;Editor&lt;/li&gt;
&lt;li&gt;Viewer&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then marketing wants a custom dashboard.&lt;br&gt;
Finance wants read-only access to sales data.&lt;br&gt;
Support needs limited write access to customer records.&lt;/p&gt;

&lt;p&gt;Before long you’ve got 50+ roles hardcoded across endpoints and environments. Want to change one? Enjoy your week-long deploy cycle and pray nothing breaks.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffqbubs87lyo5rpfqgwqq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffqbubs87lyo5rpfqgwqq.png" width="350" height="263"&gt;&lt;/a&gt;&lt;br&gt;Can I offer you a nice egg in this trying time?
  &lt;/p&gt;

&lt;h2&gt;
  
  
  🧍 Authorization Is a Human Problem, Too
&lt;/h2&gt;

&lt;p&gt;Here’s the thing:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Who decides access?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Well typically that is done on the Business side of things - HR, InfoSec, People Managers and the like. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Who implements it?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You, the engineer.&lt;/p&gt;

&lt;p&gt;When these two worlds drift apart, your authorization logic becomes a graveyard of outdated roles, guesswork, and subtle bugs nobody understands.&lt;/p&gt;

&lt;p&gt;Your codebase reflects &lt;strong&gt;services, modules, endpoints&lt;/strong&gt;. &lt;br&gt;
Your access model reflects &lt;strong&gt;teams, departments, projects&lt;/strong&gt;. Trying to map one to the other is a recipe for friction—and failure.&lt;/p&gt;

&lt;h2&gt;
  
  
  🚨 Distributed Mess
&lt;/h2&gt;

&lt;p&gt;In a distributed system, your goal is simple: make many computers behave like one computer. But when every service embeds its own ad-hoc AuthZ logic, subtle differences creep in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Marketing thinks they have access.&lt;/li&gt;
&lt;li&gt;Engineering says otherwise.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your access control looks like spaghetti… but distributed spaghetti.&lt;/p&gt;

&lt;h2&gt;
  
  
  🔬 You’re Not an Authorization Expert
&lt;/h2&gt;

&lt;p&gt;Let’s be blunt:&lt;br&gt;
You wouldn’t write your own database, would you? (Please say no.)&lt;/p&gt;

&lt;p&gt;Authorization research is as old as database research. Why reinvent the wheel on one of your application’s most critical paths?&lt;/p&gt;

&lt;h2&gt;
  
  
  ⚙️ AuthZ Must Perform at Scale
&lt;/h2&gt;

&lt;p&gt;Authorization isn’t just about correctness, it’s also about performance. After all permission checks are on the critical path. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fast checks at low latency.&lt;/li&gt;
&lt;li&gt;Always available.&lt;/li&gt;
&lt;li&gt;Resilient under load.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This isn’t something you hack together in a sprint. It’s infrastructure-grade engineering that involves thinking about caching, distributed consistency, failover, and observability.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Matters Now?
&lt;/h2&gt;

&lt;p&gt;Sure, maybe a few years ago you could “get away with it.” But today’s requirements demand:&lt;/p&gt;

&lt;p&gt;✅ Fine-grained permissions&lt;br&gt;
✅ Microservices architectures&lt;br&gt;
✅ Global scale&lt;br&gt;
✅ Collaborative apps with dynamic access policies&lt;/p&gt;

&lt;p&gt;That dials up the complexity all the way up to 11.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyr1qww3zdjogzzyvqf04.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyr1qww3zdjogzzyvqf04.gif" alt="goes to 11" width="500" height="270"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And the stakes are higher:&lt;br&gt;
OWASP’s current Top 10 security risks for web apps puts &lt;strong&gt;Broken Access Control&lt;/strong&gt; at #1 but it doesn't have to be. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvv22s12h8qzayh8o7dvb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvv22s12h8qzayh8o7dvb.png" width="800" height="220"&gt;&lt;/a&gt;&lt;br&gt;image source: owasp.org
  &lt;/p&gt;

&lt;p&gt;Don't believe me? Here's an example from the industry:&lt;/p&gt;

&lt;p&gt;In the 2010s, Broken Authentication was consistently in the Top 3 in OWASP's lists¹. So as an industry we: &lt;/p&gt;

&lt;p&gt;✅ Stopped writing our own authentication&lt;br&gt;
✅ Adopted off-the-shelf identity providers&lt;/p&gt;

&lt;p&gt;Result?&lt;br&gt;
The latest OWASP list puts 'Identification and Authentication Failures' all the way down at #7&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It’s time we do the same for authorization.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Better Way: Centralized Authorization
&lt;/h2&gt;

&lt;p&gt;The good news? You have options:&lt;/p&gt;

&lt;p&gt;OWASP themselves recommend adopting modern models like ABAC (Attribute-Based Access Control) or ReBAC (Relationship-Based Access Control) to fix broken access control. &lt;/p&gt;

&lt;p&gt;Using centralized, open source AuthZ systems (such as &lt;a href="https://github.com/authzed/spicedb/" rel="noopener noreferrer"&gt;SpiceDB&lt;/a&gt;, inspired by Google Zanzibar) offer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fine-grained checks&lt;/li&gt;
&lt;li&gt;Fast, low-latency queries&lt;/li&gt;
&lt;li&gt;Clear separation of business logic and access policy&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In short: let experts design, build, and maintain the thing that keeps your user data secure—so you can focus on building your product.&lt;/p&gt;

&lt;h2&gt;
  
  
  🔔 Final word
&lt;/h2&gt;

&lt;p&gt;Before I get flamed in the comments, here's a disclaimer: &lt;/p&gt;

&lt;p&gt;If you’re writing a weekend hobby app, maybe a simple &lt;code&gt;if role == admin&lt;/code&gt; is fine.&lt;br&gt;
If you’re scaling a business or product, you can’t afford to wing it anymore. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Remember&lt;/strong&gt;:&lt;br&gt;
“Just because you can write your own AuthZ doesn’t mean you should.”&lt;/p&gt;

&lt;p&gt;Got a custom AuthZ horror story? Share it in the comments 👉&lt;/p&gt;




&lt;p&gt;[1]Broken Authentication was #2 in 2017, #2 in 2013, #3 in 2010 &lt;br&gt;
(&lt;a href="https://medium.com/@dramkumar/history-of-all-owasp-top-10-over-the-years-9470c0adf43d" rel="noopener noreferrer"&gt;source&lt;/a&gt;) &lt;/p&gt;

&lt;p&gt;Cover Photo by &lt;a href="https://unsplash.com/@jdiegoph?utm_content=creditCopyText&amp;amp;utm_medium=referral&amp;amp;utm_source=unsplash" rel="noopener noreferrer"&gt;Diego PH&lt;/a&gt; on &lt;a href="https://unsplash.com/photos/person-holding-light-bulb-fIq0tET6llw?utm_content=creditCopyText&amp;amp;utm_medium=referral&amp;amp;utm_source=unsplash" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>iam</category>
      <category>learning</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Beware of the New Enemy Problem ⚠️</title>
      <dc:creator>Sohan</dc:creator>
      <pubDate>Thu, 06 Mar 2025 12:36:29 +0000</pubDate>
      <link>https://dev.to/sohan26/beware-of-the-new-enemy-problem-180m</link>
      <guid>https://dev.to/sohan26/beware-of-the-new-enemy-problem-180m</guid>
      <description>&lt;p&gt;Google Zanzibar is a globally distributed authorization system capable of processing "more than 10 million client queries per second," and powers all of Google's services including YouTube, Docs and Cloud IAM. Zanzibar was first described in a paper presented in 2019 and can be &lt;a href="https://zanzibar.tech/" rel="noopener noreferrer"&gt;read here.&lt;/a&gt;  &lt;/p&gt;

&lt;p&gt;In this paper, there's the first mention of &lt;strong&gt;The New Enemy Problem&lt;/strong&gt; - Described as: “[a failure] to respect the ordering between ACL updates or when we apply old ACLs to new content.”  &lt;/p&gt;

&lt;p&gt;Essentially, this is the name Google gave to two undesirable properties we wish to prevent in a distributed authorization system:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Neglecting an Access Control List (ACL) update order&lt;/li&gt;
&lt;li&gt;Misapplying old ACLs to new content&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In other words, the New Enemy Problem is a scenario where unauthorized access can occur when changes to permissions and the resources they protect are not updated together &lt;em&gt;consistently&lt;/em&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  A New Enemy Example
&lt;/h3&gt;

&lt;p&gt;Here's an example featuring Lex (a bad actor) and Kara&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F67y00jc1c62jjfb8hiy3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F67y00jc1c62jjfb8hiy3.png" alt="New Enemy Problem" width="800" height="642"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Essentially, Lex had permissions to view &lt;strong&gt;SeCrEt PlaNs&lt;/strong&gt; but his access was revoked. But due to a stale ACL check, he is now able to read this document. In this example, Lex is the New Enemy.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where New Enemy is Present
&lt;/h3&gt;

&lt;p&gt;There are different ways Authorization is implemented in the industry today. The most common pattern is for organizations to implement their own custom Authorization. Some of these approaches can be prone to not solving the New Enemy Problem. &lt;/p&gt;

&lt;p&gt;For example: &lt;a href="https://dev.to/authzed/dont-use-jwt-for-authorization-1io5"&gt;I'd earlier discussed&lt;/a&gt; why using JSON Web Tokens (JWTs) for Authorization isn't a good idea and one of the reasons is because of the New Enemy Problem. You could revoke someone's access on your server, but they'd still have access if they're holding a valid JWT from before. &lt;/p&gt;

&lt;h3&gt;
  
  
  How Zanzibar solves it
&lt;/h3&gt;

&lt;p&gt;In the Zanzibar paper there is a description to get an opaque token to a snapshot of the permissions as evaluated at a single point in time. This token is called a &lt;strong&gt;Zookie&lt;/strong&gt; (possibly a portmanteau of Zanzibar and cookie). By combining a token which represents the exact permissions used to protect a specific version of the content, and the content itself, we can make sure that the permissions we use to check access to that content in the future is &lt;strong&gt;at least as fresh&lt;/strong&gt; as the permissions when the content was created.&lt;/p&gt;

&lt;p&gt;Here's an example of how it works in &lt;a href="https://github.com/authzed/spicedb/" rel="noopener noreferrer"&gt;SpiceDB&lt;/a&gt; - a database inspired by Google Zanzibar. A zookie is passed when making a permissions check request, and guarantees that the policy and individual relationships used to compute the answer will be at least as fresh as the Zookie presented requires.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def write_content(user, content_id, new_content):
    is_allowed, zookie = authzed.content_change_check(content_id, user)
    if is_allowed:
        storage.write_content(contentd_id, new_content, zookie)
        return success
    return forbidden
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And when accessing the data, we use the following pseudocode:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def read_content(user, content_id):
    content, zookie = storage.get_content(content_id)
    is_allowed = authzed.check(content_id, user, zookie)
    if is_allowed:
        return content
    return forbidden
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a mechanism for enforcing that we will never give access to a version of the content to which the user has had their access revoked! New Enemy problem solved.&lt;/p&gt;

&lt;p&gt;Here's our previous example, but this time with Zookies. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8ecpixm0ixapw16996fk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8ecpixm0ixapw16996fk.png" alt="New Enemy solution" width="800" height="525"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  I did it all for the Zookie
&lt;/h3&gt;

&lt;p&gt;You may have picked up on the fact that there are probably some inconsistencies that can be introduced by using a version of the permissions that are at least as fresh, but not always the exact current permissions. So what are they?&lt;/p&gt;

&lt;p&gt;Let’s say at some point a user is granted access to a document in a way that doesn’t cause the document to store a new zookie. It may take some time for that access grant to propagate everywhere, and we may issue some false negative responses to check requests. This is an explicit choice to improve the performance of the system, while always guaranteeing that no false positives are ever issued.&lt;/p&gt;

&lt;p&gt;Permissions mutations also return a Zookie so if you can easily identify the content to which permission is being granted, you can optionally update the zookie on the content when the new permissions are granted. This will enforce a causal ordering between the permissions change and the next access request! This can alleviate the problem with false negatives, by trading off higher load to the datastore which stores the content. This will make sense for some use cases, but not for others.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;del&gt;New Enemies&lt;/del&gt; New Friends
&lt;/h3&gt;

&lt;p&gt;Open Worldwide Application Security Project (OWASP) publishes a "Top 10 Security Risks for Web Apps" list and currently &lt;strong&gt;Broken Access Control&lt;/strong&gt; sits at the top of their list. Authorization systems based on Google Zanzibar such as SpiceDB solve for the New Enemy Problem, and in turn improve Access Control mechanisms in your software. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxn75rmo0mohos6etlhzm.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxn75rmo0mohos6etlhzm.jpeg" alt="A burgler is reading an empty document" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;secretplans.doc can't be found&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let me know in the comments how you've solved for the New Enemy problem in your Authorization code.&lt;/p&gt;

</description>
      <category>security</category>
      <category>authorization</category>
      <category>distributedsystems</category>
      <category>spicedb</category>
    </item>
    <item>
      <title>Safeguarding Your Data When Using DeepSeek R1 In RAG Pipelines - Part II</title>
      <dc:creator>Sohan</dc:creator>
      <pubDate>Fri, 31 Jan 2025 20:04:15 +0000</pubDate>
      <link>https://dev.to/authzed/safeguarding-your-data-when-using-deepseek-r1-in-rag-pipelines-part-ii-2cli</link>
      <guid>https://dev.to/authzed/safeguarding-your-data-when-using-deepseek-r1-in-rag-pipelines-part-ii-2cli</guid>
      <description>&lt;p&gt;&lt;a href="https://dev.to/authzed/safeguarding-your-data-when-using-deepseek-r1-in-rag-pipelines-part-1-31d2"&gt;In Part I&lt;/a&gt; we learnt about why we should secure our RAG pipelines with Fine Grained Authorization, and also what are the methods to do so.&lt;/p&gt;

&lt;p&gt;Let's now get our hands dirty and write code to actually do so. &lt;/p&gt;

&lt;p&gt;We'll authorizing access to view blog articles and get information from it. We'll see what happens when a request is authorized and when it isn't. Here's our RAG pipeline with the software we're using. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2qxoxhtvlpopna9qg86t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2qxoxhtvlpopna9qg86t.png" alt="RAG software used" width="800" height="315"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Let's Talk Schema!
&lt;/h3&gt;

&lt;p&gt;Let's set up our permissions system. Once you've installed SpiceDB, create a schema about two objects: &lt;code&gt;users&lt;/code&gt; and &lt;code&gt;articles&lt;/code&gt;. The setup is simple - users can be "viewers" of articles, and if you're tagged as a viewer, you get the all-access pass to view that article.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;authzed.api.v1&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;WriteSchemaRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;

&lt;span class="c1"&gt;#change to bearer_token_credentials if you are using tls
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;grpcutil&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;insecure_bearer_token_credentials&lt;/span&gt;

&lt;span class="n"&gt;SCHEMA&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;definition user {}

definition article {
    relation viewer: user

    permission view = viewer
}&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;SPICEDB_ADDR&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;insecure_bearer_token_credentials&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;SPICEDB_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;

&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;await&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;WriteSchema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;WriteSchemaRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;schema&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;SCHEMA&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Write schema error: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Write a Relationship
&lt;/h3&gt;

&lt;p&gt;Alright, first things first - we're gonna tell SpiceDB that &lt;strong&gt;Tim&lt;/strong&gt; should be able to peek at &lt;code&gt;document 123&lt;/code&gt; and &lt;code&gt;document 456&lt;/code&gt;. Think of it like giving Tim a special pass to view these specific files.&lt;/p&gt;

&lt;p&gt;This is how we write a Relationship in SpiceDB. Once we've done this, SpiceDB will know exactly what Tim can and can't see.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;authzed.api.v1&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;ObjectReference&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Relationship&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;RelationshipUpdate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;SubjectReference&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;WriteRelationshipsRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;await &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;WriteRelationships&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nc"&gt;WriteRelationshipsRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;updates&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="nc"&gt;RelationshipUpdate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="n"&gt;operation&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;RelationshipUpdate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Operation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OPERATION_TOUCH&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;relationship&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;Relationship&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                        &lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;ObjectReference&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;object_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;article&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;object_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;123&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                        &lt;span class="n"&gt;relation&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;viewer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;SubjectReference&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                            &lt;span class="nb"&gt;object&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;ObjectReference&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                                &lt;span class="n"&gt;object_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                &lt;span class="n"&gt;object_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tim&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                            &lt;span class="p"&gt;)&lt;/span&gt;
                        &lt;span class="p"&gt;),&lt;/span&gt;
                    &lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="nc"&gt;RelationshipUpdate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="n"&gt;operation&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;RelationshipUpdate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Operation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OPERATION_TOUCH&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;relationship&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;Relationship&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                        &lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;ObjectReference&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;object_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;article&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;object_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;456&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                        &lt;span class="n"&gt;relation&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;viewer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;SubjectReference&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                            &lt;span class="nb"&gt;object&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;ObjectReference&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                                &lt;span class="n"&gt;object_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                &lt;span class="n"&gt;object_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tim&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                            &lt;span class="p"&gt;)&lt;/span&gt;
                        &lt;span class="p"&gt;),&lt;/span&gt;
                    &lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Write relationships error: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Writing to our Vector DB
&lt;/h3&gt;

&lt;p&gt;Pinecone is a vector database where we store our embeddings. Let's set up our Pinecone serverless index - don't worry, it's not as complicated as it sounds!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;#from pinecone.grpc import PineconeGRPC as Pinecone
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pinecone&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ServerlessSpec&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pinecone&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Pinecone&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;

&lt;span class="n"&gt;pc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Pinecone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PINECONE_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="n"&gt;index_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;oscars&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="n"&gt;pc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;index_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;dimension&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;metric&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cosine&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;spec&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;ServerlessSpec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;cloud&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;aws&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;us-east-1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's where it gets fun - we're going to create a totally made-up fact: "&lt;strong&gt;Bill Gates won the 2025 Oscar for best football movie.&lt;/strong&gt;" (I know, wild right? 😄). We're using this made-up fact to show how RAG handles information that LLMs don't already know about.&lt;/p&gt;

&lt;p&gt;We'll also add a little tag (&lt;code&gt;article_id&lt;/code&gt;) to keep track of where this info came from. This is super important because it helps us link everything back to our permission system.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;langchain_pinecone&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;PineconeEmbeddings&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;langchain_pinecone&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;PineconeVectorStore&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;langchain.schema&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Document&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;

&lt;span class="c1"&gt;# Create a Document object that specifies our made up article and specifies the document_id as metadata.
&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Bill Gates won the 2025 Oscar for best football movie&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;metadata&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;article_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;123&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;document&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Document&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page_content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;metadata&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="c1"&gt;# Initialize a LangChain embedding object.
&lt;/span&gt;&lt;span class="n"&gt;model_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;multilingual-e5-large&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;embeddings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;PineconeEmbeddings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;model_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;pinecone_api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PINECONE_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;namespace_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;oscar&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="c1"&gt;# Upsert the embedding into your Pinecone index.
&lt;/span&gt;&lt;span class="n"&gt;docsearch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PineconeVectorStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_documents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;documents&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;document&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;index_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;index_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;embedding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;embeddings&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;namespace&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;namespace_name&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Checking Tim's VIP Permissions
&lt;/h3&gt;

&lt;p&gt;Now comes the cool part! We'll ask SpiceDB what documents Tim can actually see. This is how you can check for permissions and look up resources in SpiceDB. Here we're using the &lt;code&gt;LookupResources&lt;/code&gt; API to get a list of articles that Tim has permission to view.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;authzed.api.v1&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;LookupResourcesRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ObjectReference&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;SubjectReference&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;subject&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SubjectReference&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nb"&gt;object&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;ObjectReference&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;object_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;object_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tim&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;lookupArticles&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;LookupResources&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nc"&gt;LookupResourcesRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;permission&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;view&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;resource_object_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;article&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;lookupArticles&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;authorized_articles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;authorized_articles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;resource_object_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Lookup error: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Article IDs that Tim is authorized to view:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;authorized_articles&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Article IDs that Tim is authorized to view:
['123', '456']
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With that sorted, we can chat with our DeepSeek R1 model, but only about stuff Tim's allowed to see. It's like having a really smart assistant who's also great at keeping secrets! &lt;/p&gt;

&lt;p&gt;Quick side notes: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We're using OpenRouter to access the DeepSeek R1 LLM&lt;/li&gt;
&lt;li&gt;We're sticking with OpenAI for the embeddings part because they're pretty much the gold standard for this kind of thing.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;langchain_community.chat_models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ChatOpenAI&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;langchain_openai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OpenAIEmbeddings&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;langchain_pinecone&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;PineconeVectorStore&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;langchain_core.output_parsers&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;StrOutputParser&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;langchain_core.prompts&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ChatPromptTemplate&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;langchain_core.runnables&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;RunnableParallel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;RunnablePassthrough&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;

&lt;span class="c1"&gt;# Custom wrapper for OpenRouter
&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ChatOpenRouter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ChatOpenAI&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;openai_api_base&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;openai_api_key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;model_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                 &lt;span class="n"&gt;model_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                 &lt;span class="n"&gt;openai_api_base&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://openrouter.ai/api/v1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                 &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;openai_api_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;OPENROUTER_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
        &lt;span class="nf"&gt;super&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;openai_api_base&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;openai_api_base&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                         &lt;span class="n"&gt;openai_api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;openai_api_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                         &lt;span class="n"&gt;model_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;model_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Define the ask function
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;ask&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;

    &lt;span class="c1"&gt;# Initialize a LangChain object for DeepSeek via OpenRouter.
&lt;/span&gt;    &lt;span class="n"&gt;llm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ChatOpenRouter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="n"&gt;model_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;deepseek/deepseek-r1-distill-llama-70b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;max_tokens&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;max_retries&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Initialize a LangChain object for a Pinecone index with OpenAI embeddings model.
&lt;/span&gt;    &lt;span class="n"&gt;knowledge&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PineconeVectorStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_existing_index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;index_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;index_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;namespace&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;namespace_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;embedding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;OpenAIEmbeddings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;openai_api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;OPENAI_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;dimensions&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text-embedding-3-large&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Initialize a retriever with a filter that restricts the search to authorized documents.
&lt;/span&gt;    &lt;span class="n"&gt;retriever&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;knowledge&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;as_retriever&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;search_kwargs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;filter&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;article_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;$in&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;authorized_articles&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Initialize a string prompt template for context and question.
&lt;/span&gt;    &lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ChatPromptTemplate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_template&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Answer the question below using the context:&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s"&gt;Context: {context}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Question: {question}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Answer:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Combine retrieval and prompt to pass through DeepSeek LLM via OpenRouter
&lt;/span&gt;    &lt;span class="n"&gt;retrieval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;RunnableParallel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;context&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;retriever&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;question&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;RunnablePassthrough&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;chain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;retrieval&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;llm&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;StrOutputParser&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# Example question
&lt;/span&gt;    &lt;span class="n"&gt;question&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Who won the 2025 Oscar for best football movie?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Prompt: &lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;chain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="c1"&gt;# Invoke the ask function
&lt;/span&gt;&lt;span class="nf"&gt;ask&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Prompt: 

Who won the 2025 Oscar for best football movie?
Bill Gates won the 2025 Oscar for best football movie.

Answer: Bill Gates
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There you go! Our RAG pipeline got this information that LLM didn't already know about. &lt;/p&gt;

&lt;h3&gt;
  
  
  5. What Happens When Tim's Pass Expires?
&lt;/h3&gt;

&lt;p&gt;Let's shake things up and see what happens when Tim loses access to some docs.&lt;/p&gt;

&lt;p&gt;First step: we're gonna revoke Tim's viewing privileges fora document. This code snippet updates a relationship between Tim and &lt;code&gt;document 123&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; 
    &lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;WriteRelationships&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nc"&gt;WriteRelationshipsRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;updates&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="nc"&gt;RelationshipUpdate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="n"&gt;operation&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;RelationshipUpdate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Operation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OPERATION_DELETE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;relationship&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;Relationship&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                        &lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;ObjectReference&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;object_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;article&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;object_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;123&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                        &lt;span class="n"&gt;relation&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;viewer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;SubjectReference&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                            &lt;span class="nb"&gt;object&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;ObjectReference&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                                &lt;span class="n"&gt;object_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                &lt;span class="n"&gt;object_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tim&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                            &lt;span class="p"&gt;)&lt;/span&gt;
                        &lt;span class="p"&gt;),&lt;/span&gt;
                    &lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Write relationships error: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;Then&lt;/span&gt; &lt;span class="n"&gt;we&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ll double-check what Tim can still see.

#this function was defined above
try:
        resp = lookupArticles()

        authorized_articles = []

        async for response in resp:
                authorized_articles.append(response.resource_object_id)
except Exception as e:
    print(f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Lookup error: {type(e).__name__}: {e}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;)

print(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Documents that Tim can view:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;)
print(authorized_articles)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Documents that Tim can view:
['456']
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tim's lost access to &lt;code&gt;document_123&lt;/code&gt; which had the vital piece of info about the "2025 Oscar for Best Football Movie".&lt;/p&gt;

&lt;p&gt;Time to try our query again!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;#this function was defined above
&lt;/span&gt;&lt;span class="nf"&gt;ask&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Prompt: 

Who won the 2025 Oscar for best football movie?
The 2025 Oscars, which honored films released in 2024, did not include a category for "best football movie." The Academy Awards do not have a specific category dedicated to sports films or football-themed movies. Therefore, no award was given in that non-existent category. It's possible there might be confusion with another award ceremony that recognizes sports-related films. 

Answer: No one won an Oscar for best football movie in 2025 because the Academy Awards do not have such a category.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And... plot twist! The system won't spill the beans anymore because Tim's not authorized to see that document. It's like trying to read a book that's been checked out of the library. &lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;This was a step-by-step guide on how you can have fine grained authorization for your RAG pipelines. Do you have other ways of writing authorization logic for your LLMs and RAGs? Let me know in the comments! &lt;/p&gt;

&lt;p&gt;As for the image: Well this is what DALL-E thinks what "Bill Gates won the 2025 Oscar for best football movie" looks like! &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo32566erb8w5rsgwpim6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo32566erb8w5rsgwpim6.png" alt="Best football move" width="800" height="794"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As promised, here is a &lt;a href="https://github.com/authzed/workshops/blob/deepseek/secure-rag-pipelines/01-rag.ipynb" rel="noopener noreferrer"&gt;link to the working Jupyter Notebook&lt;/a&gt;. Have fun! &lt;/p&gt;

</description>
      <category>deepseek</category>
      <category>security</category>
      <category>authorization</category>
      <category>llm</category>
    </item>
    <item>
      <title>Safeguarding Your Data When Using DeepSeek R1 In RAG Pipelines - Part 1</title>
      <dc:creator>Sohan</dc:creator>
      <pubDate>Fri, 31 Jan 2025 19:38:58 +0000</pubDate>
      <link>https://dev.to/authzed/safeguarding-your-data-when-using-deepseek-r1-in-rag-pipelines-part-1-31d2</link>
      <guid>https://dev.to/authzed/safeguarding-your-data-when-using-deepseek-r1-in-rag-pipelines-part-1-31d2</guid>
      <description>&lt;p&gt;DeepSeek is the talk of the tech world right now, and rightfully so!&lt;/p&gt;

&lt;p&gt;If you're implementing the DeepSeek Large Language Model (or any LLM for that matter) in your &lt;strong&gt;Retrieval-Augmented Generation&lt;/strong&gt; (RAG) Pipeline, you have to ensure that the LLM accesses only the data its authorized to. &lt;/p&gt;

&lt;p&gt;This guide will walk you through the nuts and bolts of securing your RAG pipelines with Fine Grained Authorization while also about making your queries secure and super efficient! There's also a notebook linked at the end if you want to look at some code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; This example uses DeepSeek R1 but works with any LLM. Using Authorization for RAG Pipelines is a best practice regardless of which LLM and Emebedding model you are using. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo32566erb8w5rsgwpim6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo32566erb8w5rsgwpim6.png" alt="Best football move" width="800" height="794"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;How is this image relevant? It's relevant to our RAG Pipeline and you'll find out how at the end of this guide.&lt;/em&gt; 🤭&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Software used in this guide:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;DeepSeek R1 LLM (through OpenRouter) &lt;/li&gt;
&lt;li&gt;OpenAI for Embeddings &lt;/li&gt;
&lt;li&gt;SpiceDB for permissions&lt;/li&gt;
&lt;li&gt;Pinecone as our Vector Database&lt;/li&gt;
&lt;li&gt;Langchain for language model integration&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Why is this important?
&lt;/h3&gt;

&lt;p&gt;Because we now need to think of &lt;strong&gt;Day2 AI Ops&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Enterprises are working extra hard to keep sensitive info (like personal details and company secrets) from leaking out. The go-to solution? Setting up some solid guardrails around RAG to keep data safe while making sure everything runs smoothly and efficiently.&lt;/p&gt;

&lt;p&gt;To get these guardrails just right, you need to set up some smart permission systems that can keep track of who can see what and which resources they can access. &lt;/p&gt;

&lt;h3&gt;
  
  
  How It Works
&lt;/h3&gt;

&lt;p&gt;Let me break down how a typical RAG pipeline works - it's pretty straightforward with two main parts:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fekj2niaubtpur4t4ezp8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fekj2niaubtpur4t4ezp8.png" alt="typical rag pipeline" width="800" height="306"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Ingestion&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Think of this as preparing your knowledge base. We grab all sorts of data, clean it up a bit, turn it into embeddings (vectors that represent real-world objects), and store them in a vector database. It's like organizing your digital library, where each book (or document) gets a special tag - like "&lt;em&gt;document123&lt;/em&gt;" - so we can keep track of where everything came from.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Query &amp;amp; Response&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here's where it gets fun! When someone asks the chatbot a question, it transforms their question into the same kind of embedding format and goes hunting through the vector database for relevant matches. It's like having a super-smart librarian who knows exactly where to look! Once it finds the answer, the chatbot feeds this information to the LLM, which crafts a nice, helpful response based on what it found.&lt;/p&gt;

&lt;p&gt;But here's the catch - and it's a big one - this setup is missing something crucial: authorization checks! 🚨&lt;/p&gt;

&lt;p&gt;For example, if someone who shouldn't have access to sensitive financial data asks "What was our Q4 revenue?", they might get an answer they're not supposed to see. Not ideal, right?&lt;/p&gt;

&lt;h3&gt;
  
  
  Authorization, ReBAC &amp;amp; SpiceDB
&lt;/h3&gt;

&lt;p&gt;In case you're new to the world of AuthZ, here's a quick primer: &lt;/p&gt;

&lt;p&gt;Authorization determines whether you have permission to access a resource. Traditional models like Role-Based Access Control (RBAC) work well for simple setups, but as systems grow more complex, defining permissions based on roles alone can get messy. That’s where &lt;strong&gt;Relationship-Based Access Control&lt;/strong&gt; (ReBAC) comes in. Instead of just assigning roles, ReBAC uses relationships—like “Alice is a manager of Project X” or “Bob is a friend of Charlie”—to determine access dynamically. This makes it ideal when it comes to securing your RAG pipelines. &lt;/p&gt;

&lt;p&gt;This guide uses &lt;a href="https://github.com/authzed/spicedb/" rel="noopener noreferrer"&gt;SpiceDB&lt;/a&gt;, a powerful, open-source database designed to handle ReBAC at scale. Inspired by Google’s Zanzibar (which powers Google's Authorization systems across Docs, YouTube and more), SpiceDB lets you define and enforce complex access rules efficiently. With it, you can model relationships between users and resources, then perform lightning-fast permission checks. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Three things about SpiceDB&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here's a quick TL;DR of how SpiceDB works:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Schema&lt;/strong&gt;: This defines the types of objects found, how those objects relate to one another, and the permissions that can be computed off of those relations. Developers can read and write a schema based on their use-case and then store &amp;amp; query data. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Relationships&lt;/strong&gt;: Relationships are what binds together a Subject and a Resource via a Relation. A functioning Permissions System that uses ReBAC is the combination of Schema and Relationships &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Checks &amp;amp; Lookups&lt;/strong&gt;: Now that we have a schema and relationships in the database, we can issue checks on whether a subject has a permission on a specific resource, or what resources a subject can access whether via a computed permission or relation membership.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Adding Authorization to your RAG Pipeline
&lt;/h3&gt;

&lt;p&gt;Now there are two approaches to adding AuthZ to your RAG Pipeline. &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Post-filter Authorization&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnn0cxdpgg14afnj1k9tp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnn0cxdpgg14afnj1k9tp.png" alt="Post-filter Authorization" width="800" height="350"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So here's the deal: each embedding can have meta data showing which document it came from (like &lt;code&gt;document123&lt;/code&gt;). We use this to check if you're actually allowed to see that content.&lt;/p&gt;

&lt;p&gt;The process? We can perform a check for each relevant embedding to see if the user has permissions to view the document that the embedding originated from. You can specify the contexts you require: Ex: “I need 5 pieces of additional context before I make the prompt to the LLM” or “exhaust all the embeddings returned”&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Pre Filter Authorization&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6k1enafha7nfh6qdwkzf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6k1enafha7nfh6qdwkzf.png" alt="Pre Filter Authorization" width="800" height="308"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here we make a query and embed it. But before diving in, we check with our permissions system to see what stuff we're actually allowed to peek at. It gives us back a list of all the documents we can access.&lt;/p&gt;

&lt;p&gt;Then we just use that list as our filter, grab all the relevant embeddings we're allowed to see, and boom - we're good to go! That's what we'll be playing with in this guide. &lt;/p&gt;

&lt;h2&gt;
  
  
  Step-by-step guide
&lt;/h2&gt;

&lt;p&gt;Where's the code you ask? Well that's in &lt;a href="https://dev.to/sohan26/safeguarding-your-data-when-using-deepseek-r1-in-rag-pipelines-part-ii-2cli"&gt;Part II of this guide&lt;/a&gt;. Now that you've understood the concepts, here's the step-by-step guide to securing your RAG Pipelines.&lt;/p&gt;

</description>
      <category>deepseek</category>
      <category>authorization</category>
      <category>security</category>
      <category>rag</category>
    </item>
    <item>
      <title>Don't use JWT for Authorization!</title>
      <dc:creator>Sohan</dc:creator>
      <pubDate>Tue, 14 Jan 2025 14:30:00 +0000</pubDate>
      <link>https://dev.to/authzed/dont-use-jwt-for-authorization-1io5</link>
      <guid>https://dev.to/authzed/dont-use-jwt-for-authorization-1io5</guid>
      <description>&lt;p&gt;What's with the shouty title? Well, I wanted to grab your attention and get straight to the point: &lt;/p&gt;

&lt;p&gt;🗣️🗣️ &lt;strong&gt;Don't use JWT for your backend authorization&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Look, there's a time and place for every piece of technology and the tricky part is determining if your use case actually is the time and place. Hopefully this post will walk you through why JWTs might not be your best friend, and the rare cases where they actually make sense.&lt;/p&gt;

&lt;h3&gt;
  
  
  🔄 Quick Crash Course: What's a JWT?
&lt;/h3&gt;

&lt;p&gt;So, JWT (pronounced "&lt;em&gt;jot&lt;/em&gt;") stands for JSON Web Token. It's part of this whole family of specs called JOSE (no way!) that deal with encrypting and signing JSON. JWT is the cool kid of the family - it's defined in RFC7519 and gets all the attention. Why? Because while its siblings (JWA, JWE, JWK, JWS) handle the nitty-gritty encryption stuff, JWT is the one carrying the actual payload.&lt;/p&gt;

&lt;p&gt;Think of a JWT as a JSON object wearing a fancy coat (some headers) and carrying an ID card (a signature) to prove it's legit. It's got these things called "claims" - like when it expires (&lt;code&gt;exp&lt;/code&gt;), who created it (&lt;code&gt;iss&lt;/code&gt;), who it's for (&lt;code&gt;aud&lt;/code&gt;), and so on. The most popular claim for authorization is called "scope", which, fun fact, isn't even from JOSE - it's borrowed from OAuth2. Most developers end up mixing and matching these pieces like a authorization puzzle until something works.&lt;/p&gt;

&lt;h3&gt;
  
  
  ⚔️ The New Enemy Problem: JWT's Achilles' Heel
&lt;/h3&gt;

&lt;p&gt;Here's the thing: JWTs have a major weakness - once they're out there, you can't take them back (except waiting for them to expire). It's like giving someone an all-access pass and not being able to revoke it if they go rogue. This becomes super awkward with web sessions - ever tried implementing a proper "logout" with JWTs? Good luck with that! You're basically crossing your fingers hoping users will play nice and throw away their old tokens.&lt;/p&gt;

&lt;p&gt;But wait, it gets worse for backend services. Imagine this: you revoke someone's access on your server, but they're still holding a valid JWT from before. They can keep accessing stuff they shouldn't - this is what the smart folks call the "New Enemy Problem" (first spotted in &lt;a href="https://zanzibar.tech/" rel="noopener noreferrer"&gt;Google's Zanzibar paper&lt;/a&gt;). It's like changing the locks but forgetting about all the spare keys you handed out. Centralized authorization systems fix this by having a central service (think of it as like a bouncer at a bar) checking everyone's credentials in real-time. The New Enemy problem is a really hard and interesting distributed systems problem (and perhaps a future post here)&lt;/p&gt;

&lt;p&gt;An example of the New Enemy problem: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Alice removes Bob from the ACL of a document;&lt;/li&gt;
&lt;li&gt;Alice then asks Charlie to add new contents to the document;&lt;/li&gt;
&lt;li&gt;Bob should not be able to see the new contents, but may do so if the ACL check is evaluated with a stale ACL from before Bob's removal&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  📏 JWT Scopes: Not as Fine-Grained as You'd Think
&lt;/h3&gt;

&lt;p&gt;While JWTs look good on paper, things get messy in practice. Remember that scope claim I mentioned? It's... kinda vague. The spec basically just says "here's what characters you can use" and calls it a day. You'll see examples such as '&lt;code&gt;email profile phone address&lt;/code&gt;' floating around, and developers often try to get fancy with stuff like '&lt;code&gt;profile:admin&lt;/code&gt;'. But here's the million-dollar question: what does that actually mean? The whole site? Just one user's profile? Even GitHub's REST API has been wrestling with this for ages!&lt;/p&gt;

&lt;p&gt;Modern apps need super specific permissions - we're talking granular stuff like '&lt;code&gt;issue/authzed/spicedb/52:author&lt;/code&gt;' instead of just '&lt;code&gt;issue:author&lt;/code&gt;'. When your users might need access to billions of things, you can't stuff all that into a token that's bouncing between services.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw4nk6v8af945rhi79hbw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw4nk6v8af945rhi79hbw.png" alt="Coarse and Fine Grained Authorization" width="800" height="773"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Centralized authorization is like having a smart assistant who keeps track of everything in one place. Need to check something? Just ask! For example: &lt;a href="https://github.com/authzed/spicedb/" rel="noopener noreferrer"&gt;SpiceDB&lt;/a&gt; does this using something called &lt;strong&gt;ReBAC&lt;/strong&gt; (Relationship-Based Access Control) - it's like a Swiss Army knife that can handle super detailed permissions while still playing nice with other permissions systems such as Role Based Access Control (RBAC),  Attribute Based Access Control (ABAC), and other fancy patterns. Google also uses ReBAC for authorization across their services such as YouTube, Docs, and more. &lt;/p&gt;

&lt;h3&gt;
  
  
  🔮 The Crystal Ball Problem with JWT Authorization
&lt;/h3&gt;

&lt;p&gt;Let's play pretend and say you're cool with using just a few JWT scopes. Even then, you've got a problem: how do you know what permissions you'll need? When your JWT gets created at the front door (like in an API gateway), it needs to predict what every downstream service might want. For anything beyond a super simple setup, that's like trying to predict next week's lottery numbers!&lt;/p&gt;

&lt;p&gt;Plus, if you send a token with too many permissions to the next service, you're basically giving attackers a bigger target to hit. This headache led to the creation of &lt;a href="https://en.wikipedia.org/wiki/Macaroons_(computer_science)" rel="noopener noreferrer"&gt;Macaroons&lt;/a&gt;. These tokens can actually be trimmed down before being passed along - cool idea, right? But in reality, they're so complicated that most folks who tried them ended up saying "thanks, but no thanks." &lt;br&gt;
&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/MZFv62qz8RU"&gt;
&lt;/iframe&gt;
 &lt;/p&gt;

&lt;p&gt;Centralized authorization systems take a different approach. They're like "Hey, we know we can't predict the future, so just ask us when you need something!" Sure, you have to make an extra call, but systems like SpiceDB are optimized to keep data in-memory - so latency looks similar to reaching out to any other cache like redis or memcache.&lt;/p&gt;

&lt;h3&gt;
  
  
  🤔 So... Are JWTs Ever the Right Choice?
&lt;/h3&gt;

&lt;p&gt;After all this JWT-bashing, you might think they're completely useless. But there is one scenario where they shine: one-time grants where access cannot be revoked. Though honestly, that's about as rare as finding a unicorn in your backyard!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj6vun4vmmik0bfy46imq.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj6vun4vmmik0bfy46imq.jpeg" alt="Unicorn in the backyard" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What system do you use for your authorization needs? Let me know in the comments below. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;UPDATE 1:&lt;/strong&gt;&lt;br&gt;
There's a nice discussion in the comments about either adding state to the JWT, or a system of using a denylist for token revocation. Both these approaches have their downsides and can be fraught with errors. Check the comments below for more info&lt;/p&gt;

</description>
      <category>security</category>
      <category>authorization</category>
      <category>webdev</category>
      <category>development</category>
    </item>
    <item>
      <title>How I'm Learning SpiceDB</title>
      <dc:creator>Sohan</dc:creator>
      <pubDate>Tue, 12 Nov 2024 16:30:00 +0000</pubDate>
      <link>https://dev.to/authzed/how-im-learning-spicedb-5dha</link>
      <guid>https://dev.to/authzed/how-im-learning-spicedb-5dha</guid>
      <description>&lt;p&gt;(Cover pic by &lt;a href="https://unsplash.com/@kellysikkema?utm_content=creditCopyText&amp;amp;utm_medium=referral&amp;amp;utm_source=unsplash" rel="noopener noreferrer"&gt;Kelly Sikkema&lt;/a&gt; on &lt;a href="https://unsplash.com/photos/yellow-flower-on-gray-surface-pXmyDPziB8w?utm_content=creditCopyText&amp;amp;utm_medium=referral&amp;amp;utm_source=unsplash" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;)&lt;/p&gt;

&lt;h2&gt;
  
  
  A Life Update
&lt;/h2&gt;

&lt;p&gt;I recently joined &lt;a href="https://dev.to/"&gt;AuthZed&lt;/a&gt; as a Developer Advocate, and I want to document my learning journey for those going through a similar process. &lt;/p&gt;

&lt;p&gt;Here are the 4 steps that helped me ramp up my knowledge of SpiceDB. I hope you'll find these helpful on your own learning journey! &lt;/p&gt;

&lt;h2&gt;
  
  
  1. Start with the Basics
&lt;/h2&gt;

&lt;p&gt;It's always beneficial to have strong foundational knowledge. In the past, my eagerness to code got the better of me, and I dove headfirst into building something only to backtrack to actually understand how it works. This time, I didn't want to repeat that mistake, so I started with a refresher on &lt;a href="https://authzed.com/blog/authentication-vs-authorization" rel="noopener noreferrer"&gt;Authorization&lt;/a&gt;, and &lt;a href="https://authzed.com/blog/exploring-rebac" rel="noopener noreferrer"&gt;ABAC RBAC &amp;amp; ReBAC&lt;/a&gt;. If these acronyms are new to you, I'd suggest starting here.&lt;/p&gt;

&lt;p&gt;I then read &lt;a href="https://research.google/pubs/zanzibar-googles-consistent-global-authorization-system/" rel="noopener noreferrer"&gt;the Google Zanzibar&lt;/a&gt; paper that inspired SpiceDB, and re-read it - this time &lt;a href="https://zanzibar.tech/" rel="noopener noreferrer"&gt;with annotations&lt;/a&gt;. I have to admit - I find it hard to parse academic papers (who doesn't wish for a TikTok-style summary sometimes?) &lt;/p&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExbHN6d3FoeHZrOXBiejAxOXRvemdkMnRkcXoxbjYybGlla2ZyY3B5bSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/nDlMLL4IW1OqGtTJ6m/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExbHN6d3FoeHZrOXBiejAxOXRvemdkMnRkcXoxbjYybGlla2ZyY3B5bSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/nDlMLL4IW1OqGtTJ6m/giphy.gif" width="480" height="270"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That's where this presentation by Jake Moshenko came in really handy. His explanation brings to life all the concepts listed in the paper and reinforces understanding of how Zanzibar works.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/WTfZsRPDv9Q"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Although SpiceDB is inspired by Zanzibar, &lt;a href="https://authzed.com/docs/spicedb/concepts/zanzibar#differences-with-spicedb" rel="noopener noreferrer"&gt;there are some key differences.&lt;/a&gt; Here are some differences in a Q&amp;amp;A format that helped clarify the concepts. If the number of new concepts and terminologies seems overwhelming, that's okay! You don't have to understand all of it from the start, and hopefully, the rest of this article will help with your learning journey.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Get the Hang of Schema Design
&lt;/h2&gt;

&lt;p&gt;Schema design is central to SpiceDB and was a new concept for me. A schema essentially defines the types of objects in your system, how those objects relate to one another, and the permissions that can be computed from those relations. I started by watching this video on modeling the GitHub permissions system using Schema.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/x3-B9-ICj0w"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;For practice, I used real-life examples (such as Google Groups or a banking system) and sketched out the different users, objects, and relationships between them. Progressing from a basic user-document schema to a complex real-life example provides valuable practice in designing schemas for SpiceDB.&lt;/p&gt;

&lt;p&gt;You can experiment with modeling these in the &lt;a href="https://play.authzed.com/schema" rel="noopener noreferrer"&gt;SpiceDB playground&lt;/a&gt;. I encourage you to try it out.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F22gjzqmibm0l4j5bdg7r.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F22gjzqmibm0l4j5bdg7r.jpg" alt="An image of the Google Groups schema handwritten" width="800" height="1066"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5pujd8fcm29tq41323ls.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5pujd8fcm29tq41323ls.jpg" alt="An image of a handwritten Github scema " width="800" height="1066"&gt;&lt;/a&gt;&lt;br&gt;
(My niece calls Github as Gibbut so that's the name I refer to it now  😎)&lt;/p&gt;
&lt;h2&gt;
  
  
  3. Build Something Starting from a Point of Familiarity
&lt;/h2&gt;

&lt;p&gt;Having worked at companies like Amazon Web Services (AWS) and Fermyon, I have background knowledge in Cloud, Compute, and Serverless technologies. I looked through the documentation for familiar territory and found &lt;a href="https://authzed.com/docs/spicedb/ops/deploying-spicedb-on-eks" rel="noopener noreferrer"&gt;Deploying SpiceDB on Elastic Kubernetes Service.&lt;/a&gt; My experience with Amazon EKS helped me understand how SpiceDB integrates into that system.&lt;/p&gt;

&lt;p&gt;If you come from an application development background, you might prefer starting with one of &lt;a href="https://authzed.com/docs/spicedb/getting-started/client-libraries" rel="noopener noreferrer"&gt;our client libraries&lt;/a&gt; to build a simple app that communicates with a local SpiceDB instance. Our getting started guide &lt;a href="https://authzed.com/docs/spicedb/getting-started/protecting-a-blog" rel="noopener noreferrer"&gt;Protecting A Blog Application&lt;/a&gt; can be particularly helpful. For those with authorization experience, we offer guides on how &lt;a href="https://authzed.com/docs/spicedb/getting-started/coming-from/opa" rel="noopener noreferrer"&gt;SpiceDB compares with Open Policy Agent (OPA)&lt;/a&gt; or a &lt;a href="https://authzed.com/docs/spicedb/getting-started/coming-from/cancancan" rel="noopener noreferrer"&gt;comparison with Ruby on Rails CanCanCan&lt;/a&gt;. Both show different approaches but share some common ground.&lt;/p&gt;

&lt;p&gt;Good time to shout-out that SpiceDB is &lt;a href="https://github.com/authzed/spicedb/" rel="noopener noreferrer"&gt;completely open-source&lt;/a&gt;, and we welcome community contributions! Whether you'd like to suggest improvements, fix documentation typos, or contribute to the community, please feel free to do so. Check out our &lt;a href="https://github.com/authzed/spicedb/labels/good%20first%20issue" rel="noopener noreferrer"&gt;Good First Issues&lt;/a&gt; and join our &lt;a href="https://authzed.com/discord" rel="noopener noreferrer"&gt;Discord community&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExdXZxeGR3cWUyY2c0NGtsZWZzNnQ0ZnpvbWU3OXlubHcwNHkyY2RjeSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/EnvjuAOlZEEtbMLKl5/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExdXZxeGR3cWUyY2c0NGtsZWZzNnQ0ZnpvbWU3OXlubHcwNHkyY2RjeSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/EnvjuAOlZEEtbMLKl5/giphy.gif" width="480" height="360"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  4. Use AI Strategically
&lt;/h2&gt;

&lt;p&gt;While learning to deploy SpiceDB on Amazon EKS, I encountered some challenges (a natural part of learning) and consulted ChatGPT about these errors. Here's a debugging step that I received:&lt;/p&gt;

&lt;p&gt;(For context: &lt;strong&gt;zed&lt;/strong&gt; is the AuthZed CLI tool)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7u9vorylsz564y6tz6j7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7u9vorylsz564y6tz6j7.png" alt="A chatGPT snippet" width="800" height="409"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Pretty straightforward, right? &lt;/p&gt;

&lt;p&gt;Well, except that &lt;code&gt;config&lt;/code&gt; is &lt;u&gt;not&lt;/u&gt; a zed CLI command. LLMs can hallucinate and often do so with a lot of confidence. Watch out for inconsistencies like these that could trip you up when copying code from an LLM.&lt;/p&gt;

&lt;p&gt;This highlights an important distinction between "learning something" and "building something". Asking ChatGPT "How do I install SpiceDB on EKS" and then just spamming the copy-paste keys is not the best way to learn something. I can attest to this because it's exactly what I did at the start! Only partway through did I realize that I hadn't achieved what I set out to do and had to backtrack. On the other hand, asking an LLM about how I could start debugging certain errors gave me a good understanding of what's under the hood. Use these tools thoughtfully and purposefully.&lt;/p&gt;
&lt;h2&gt;
  
  
  One Final Thought
&lt;/h2&gt;

&lt;p&gt;I'm on a roll with the advice, so here's one more thing (yes, that's a &lt;a href="https://en.wikipedia.org/wiki/Stevenote" rel="noopener noreferrer"&gt;Stevenote&lt;/a&gt; reference). This has held me in good stead over the years when learning anything new: enjoy the process, the results will follow.&lt;/p&gt;

&lt;p&gt;Happy Learning!&lt;/p&gt;

&lt;p&gt;P.S. Here's a webinar I recorded for CNCF about Deploying SpiceDB in EKS. There's nothing quite like learning in public! 😎&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/KT1RqTBeA1c"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

</description>
      <category>authorization</category>
      <category>security</category>
      <category>iam</category>
      <category>permissions</category>
    </item>
    <item>
      <title>The Complete Guide to Serverless Apps II - Functions and Apps</title>
      <dc:creator>Sohan</dc:creator>
      <pubDate>Wed, 03 Jul 2024 15:00:00 +0000</pubDate>
      <link>https://dev.to/fermyon/the-complete-guide-to-serverless-apps-ii-functions-and-apps-4bd8</link>
      <guid>https://dev.to/fermyon/the-complete-guide-to-serverless-apps-ii-functions-and-apps-4bd8</guid>
      <description>&lt;p&gt;In &lt;a href="https://dev.to/fermyon/the-complete-guide-to-serverless-apps-i-introduction-1ga4"&gt;Part I&lt;/a&gt; we took a close look at the term “serverless” as it is used in cloud computing. We spoke about a serverless application - where you do not have to write a software server. Instead, you focus only on writing a request handler. Let’s now spend some time talking about this programming model; easily creating serverless functions and serverless applications.&lt;/p&gt;

&lt;p&gt;Your program is started when a request is received. The request object is passed into a function in your code. That function is expected to run to completion, possibly handing back a response. Once the function has been completed, the program exits.&lt;/p&gt;

&lt;p&gt;There are three characteristics of this sort of program:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It is short running, often running for only milliseconds.&lt;/li&gt;
&lt;li&gt;It is triggered by an event or a request.&lt;/li&gt;
&lt;li&gt;It is responsible merely for dealing with that request (often returning a response).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Hello World 👋
&lt;/h2&gt;

&lt;p&gt;For the sake of clarity, let’s look at a simple example of this kind of program. We will use the world’s most popular programming language, &lt;strong&gt;JavaScript&lt;/strong&gt;, for this example. But the pattern is similar across languages. Also, we will write an example of a serverless function that handles an HTTP request.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;encoder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TextEncoder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;// Declare a function that handles a request (in this case, an HTTP request)&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handleRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="c1"&gt;// Send back an object that describes a response (in this case, an HTTP response)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;encoder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;I'm a Serverless Function&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;buffer&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are three things to note about the example above:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;We do not set up a server of any sort (we don't even import any libraries).&lt;/li&gt;
&lt;li&gt;There is a function called &lt;code&gt;handleRequest()&lt;/code&gt; that takes a &lt;code&gt;request&lt;/code&gt; object. This function is called when an inbound HTTP request occurs.&lt;/li&gt;
&lt;li&gt;The function returns a response. In this case, it's an HTTP response with a &lt;code&gt;200&lt;/code&gt; response code (which means no error occurred) and the content that will be displayed in the web browser.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here is the same example in Python&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;IncomingHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IncomingHandler&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handle_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content-type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text/plain&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="nf"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;I&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;m a Serverless Function written in Python&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExdmp2dzUwdG5nNXd5Y2hrYnZrazdiY2o4eWZqZHVmMXF4Z21iMWJjcyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/SZOojjqpIrY9AHz1VQ/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExdmp2dzUwdG5nNXd5Y2hrYnZrazdiY2o4eWZqZHVmMXF4Z21iMWJjcyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/SZOojjqpIrY9AHz1VQ/giphy.gif" width="480" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We don't start a server, map ports, handle interrupts, declare SSL/TLS certificates, or anything like that. The serverless app platform does all that stuff on our behalf outside of our code. When a request comes in, this app is started, the &lt;code&gt;handleRequest&lt;/code&gt; function is called, and then the app exits. &lt;/p&gt;

&lt;p&gt;And how fast is this? Different Serverless platforms have different speeds. With &lt;a href="https://github.com/fermyon/spin" rel="noopener noreferrer"&gt;Spin&lt;/a&gt;, the handler can be started in under a millisecond. That is why there is no reason to run a server. If we can start this fast, it's much more efficient (and much cheaper) to &lt;strong&gt;not&lt;/strong&gt; be running idle servers.&lt;/p&gt;

&lt;p&gt;The above is an example of a serverless function. And when we package that up and send it off to a server, we have built a simple serverless app.&lt;/p&gt;

&lt;h2&gt;
  
  
  More Definitions 😅
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;"Wait, i'm confused! If this is a Serverless function, what are Functions as a Service? How does it differ from an Edge Function?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExb2o2M2k0YXZldDh4YTkzZ2c3djd6dnBhaDUzY3QzY3U1cGZmcnJqNyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/3oKIPDNOFwZ0zi8nrq/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExb2o2M2k0YXZldDh4YTkzZ2c3djd6dnBhaDUzY3QzY3U1cGZmcnJqNyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/3oKIPDNOFwZ0zi8nrq/giphy.gif" width="480" height="384"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These are valid questions so let's clarify the two:&lt;/p&gt;

&lt;h4&gt;
  
  
  Functions as a Service (FaaS)
&lt;/h4&gt;

&lt;p&gt;When AWS Lambda first hit the scene, cloud mavens were keen on collapsing all cloud service names into “as-a-Service”-isms. For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;core infrastructure services like compute and networking became “Infrastructure-as-a-Service (IaaS)”.&lt;/li&gt;
&lt;li&gt;serverless databases were called “DB-as-a-Service (DBaaS)” and so on. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In such an environment, it is no surprise that the first wave of serverless app platforms was given the unattractive monicker “Function-as-a-Service”.&lt;/p&gt;

&lt;p&gt;Personally, I prefer using "Serverless functions" in favour of FaaS and here's why: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The term FaaS is opaque. If you don’t know what it means, there are not many clues embedded in the term itself. As with all the “aaS”es, one finds oneself mentally listing words that start with F for clarification.&lt;/li&gt;
&lt;li&gt;The term itself refers to the service that runs. So what do you call an application that runs in a FaaS? A Function-as-a-Service Function? A Function-as-a-Service App? That just sounds confusing.&lt;/li&gt;
&lt;li&gt;Lastly, in English "FaaS" can be verbally hard to distinguish from "PaaS".&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In contrast, an app run inside of a PaaS is usually called a server or a microservice. Thus, most people in the field refer to apps that run in a FaaS as serverless apps or serverless functions.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The most famous PaaS, &lt;a href="https://www.heroku.com/" rel="noopener noreferrer"&gt;Heroku&lt;/a&gt;, does not refer to itself as a PaaS, and for the same reason, we don’t use FaaS. Much of their documentation uses the term “cloud application platform.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  Cloud Functions and Edge Functions
&lt;/h4&gt;

&lt;p&gt;The terms cloud functions and edge functions occasionally arise when talking about serverless applications. For example, &lt;a href="https://docs.netlify.com/functions/overview/" rel="noopener noreferrer"&gt;Netlify&lt;/a&gt; uses these terms in its documentation. The distinction between these "cloud" and "edge" &lt;a href="https://dev.todefinition-of-serverless-functions"&gt;serverless functions&lt;/a&gt; does not concern the functions themselves but rather where the specific function is being executed.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;A cloud function executes "in the cloud," which usually means at one of the main hyperscalers such as AWS, Azure, or Google Cloud.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;An edge function executes on the edge, a concept we will cover more later. In a nutshell, "edge" refers to the proximity between the end user and the function which they are calling. The term edge also refers to the proximity of the function being executed and the data being processed. The ultimate goal is to obtain the most efficient round-trip between the user, the function and any data being processed.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Providers like Vercel and Netlify must make this distinction because the APIs they provide for the functions that run in the cloud are different from the APIs they provide for the functions that they run in edge providers like CloudFlare. This is an implementation-specific API difference that bubbles up to the developer.&lt;/p&gt;

&lt;p&gt;Our view is that “edge functions” and “cloud functions” are varieties of serverless apps. Keep in mind, when we talk about cloud and edge computing later on that the term &lt;strong&gt;edge&lt;/strong&gt; is niche and only relates to a subset of service providers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion 😌
&lt;/h2&gt;

&lt;p&gt;Thanks for staying with us thus far! In this post, we saw what the code for a Serverless function looks like and what happens when it is triggered by an event. In the upcoming posts we'll deep-dive in the characteristics of a Serverless App. We'll look at execution time, CPU &amp;amp; Memory, Statelessness, and more. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExcGY4bXAzeGwzOTZlYjdnczY2cnR5bmFobmg4bzgwaTNnb3dudTJhaSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/Lgd5dyd6T0myHCsi2X/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExcGY4bXAzeGwzOTZlYjdnczY2cnR5bmFobmg4bzgwaTNnb3dudTJhaSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/Lgd5dyd6T0myHCsi2X/giphy.gif" width="500" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let us know if you have come across any other terminology around Serverless Apps, and we'll try and compare and contrast it for you. &lt;/p&gt;

</description>
      <category>serverless</category>
      <category>cloud</category>
      <category>webassembly</category>
      <category>cloudcomputing</category>
    </item>
    <item>
      <title>The Complete Guide to Serverless Apps I - Introduction</title>
      <dc:creator>Sohan</dc:creator>
      <pubDate>Wed, 26 Jun 2024 15:00:00 +0000</pubDate>
      <link>https://dev.to/fermyon/the-complete-guide-to-serverless-apps-i-introduction-1ga4</link>
      <guid>https://dev.to/fermyon/the-complete-guide-to-serverless-apps-i-introduction-1ga4</guid>
      <description>&lt;p&gt;'Serverless' as a term &lt;a href="https://martinfowler.com/articles/serverless.html#origin" rel="noopener noreferrer"&gt;first appeared&lt;/a&gt; around 2012 but it was only in 2014 when it piqued interest after the launch of AWS Lambda. The term spiked in usage in 2020 but went into decline soon after. Early in 2022, interest began to climb again. And right now, the term is as popular as it's ever been. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv77c9civgs2ouk5py94s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv77c9civgs2ouk5py94s.png" alt="serverless google trends" width="800" height="585"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;There are few reasons for this increase in popularity (which we will delve into shortly) but I thought I'd take this opportunity to draw on my experience working in the cloud, and write a primer for Serverless Applications. This is intended to be read like an e-book and will cover all the facets of Serverless applications.  &lt;/p&gt;

&lt;p&gt;Here's the Complete Developer’s Guide to Serverless Apps which will be serialized over the course of the next few weeks.&lt;/p&gt;

&lt;h3&gt;
  
  
  Table of Contents
&lt;/h3&gt;



&lt;ul&gt;
&lt;li&gt;
Defining Serverless 💫

&lt;ul&gt;
&lt;li&gt;Definition 1: Serverless as SaaS&lt;/li&gt;
&lt;li&gt;Definition 2: Serverless as Hosted Application&lt;/li&gt;
&lt;li&gt;Definition 3: Serverless as a Software Concept&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;What is a Serverless App 🤓&lt;/li&gt;

&lt;li&gt;Comparing Serverless Apps and PaaS 🗒️&lt;/li&gt;

&lt;li&gt;Conclusion 😊&lt;/li&gt;

&lt;/ul&gt;



&lt;h2&gt;
  
  
  Defining Serverless 💫
&lt;/h2&gt;

&lt;p&gt;A friend of mine once argued that there is no such thing as &lt;em&gt;serverless&lt;/em&gt; because, of &lt;em&gt;course&lt;/em&gt; there are servers underneath every one of the services mentioned above. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExOWVlcnl6MTV2OTdkM3hsNDZmNGxzNDR4dnVjYWhiaTh5cHl2NzlvMSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/eUrE2DuMKOE0g/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExOWVlcnl6MTV2OTdkM3hsNDZmNGxzNDR4dnVjYWhiaTh5cHl2NzlvMSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/eUrE2DuMKOE0g/giphy.gif" width="245" height="276"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While he is not wrong in his statement that the things described as &lt;em&gt;serverless&lt;/em&gt; do indeed have servers running somewhere, he missed the main point: &lt;em&gt;Serverless&lt;/em&gt; is a statement about what resources you must be concerned with and not so much about the actual presence of physical server hardware. &lt;/p&gt;

&lt;p&gt;Let’s dive into three definitions of Serverless, and you’ll soon see what I mean. We’ll start with the most generic definition and work toward the most specific.&lt;/p&gt;

&lt;h4&gt;
  
  
  Definition 1: Serverless as SaaS
&lt;/h4&gt;

&lt;p&gt;The most generic term “serverless” indicates that some offering is run using the Software-as-a-Service (SaaS) model. In SaaS, an entire application is owned, built, and operated by one organization, and other individuals or organizations create accounts to use that application, typically via the web.&lt;/p&gt;

&lt;p&gt;Some SaaS providers prefer to use the term “serverless” to describe the SaaS model, particularly when they want to emphasize that you, as the user, don’t have to do any management of the cloud resources required to run the SaaS.&lt;/p&gt;

&lt;p&gt;If this is server-&lt;em&gt;less&lt;/em&gt;, then what does “server” mean here? The &lt;em&gt;server&lt;/em&gt;, that this &lt;em&gt;serverless&lt;/em&gt; offering is hiding from you is what used to be called the &lt;em&gt;application server&lt;/em&gt; (back in the pre-cloud days). In other words, the machine or machines whose job it was to execute a specific piece of software.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExYjh1d3ZsZHRueTBoZjZha3dqN2d0ZDh0a24ydmV4dzN6N2E4ZGVsdyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/3o7524uhfdq7pmiDJe/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExYjh1d3ZsZHRueTBoZjZha3dqN2d0ZDh0a24ydmV4dzN6N2E4ZGVsdyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/3o7524uhfdq7pmiDJe/giphy.gif" width="480" height="414"&gt;&lt;/a&gt;&lt;br&gt;Err..not that Sass
 &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Definition 1: &lt;em&gt;”Serverless” merely means no management of cloud resources&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You can spot this usage of the term easily. For example, when &lt;em&gt;serverless&lt;/em&gt; is being used as a synonym for &lt;em&gt;SaaS&lt;/em&gt; it signals that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the offering is targeted toward non-engineers,&lt;/li&gt;
&lt;li&gt;there is no REST API, library, SDK, or CLI to use, and&lt;/li&gt;
&lt;li&gt;the web interface is the only way to interact with the application.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Definition 2: Serverless as Hosted Application
&lt;/h4&gt;

&lt;p&gt;Consider the following cloud services, each of which claims to be serverless:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Serverless database hosting.&lt;/li&gt;
&lt;li&gt;Serverless logging and monitoring.&lt;/li&gt;
&lt;li&gt;Serverless messaging framework.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What do these three have in common? A serverless database is one in which someone else manages the database software (starting and stopping, upgrading, patching security issues) and you simply use the database (creating tables, running queries, inserting data).  Similarly, “serverless logging and monitoring” suggests the same: Someone else manages a bunch of servers that do log processing and monitoring, and you merely use the APIs provided to attach to your application.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExbmg3bmw1dHhqdDl4Zjl0amNwem53ZTY4Zm5zMnlxZnh5MXE0c251ZCZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/nH6AAUMOMxYhvzcmZU/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExbmg3bmw1dHhqdDl4Zjl0amNwem53ZTY4Zm5zMnlxZnh5MXE0c251ZCZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/nH6AAUMOMxYhvzcmZU/giphy.gif" width="480" height="270"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This definition treats the word “server” as referring to the hardware or Virtual Machine (VM) instance. While you (the user) may be required to say what kind of Operating System (OS) or architecture you prefer their cloud service to run on. In this case, you perhaps also choose your memory and/or storage requirements but you are not responsible for the day-to-day operations relating to any of this infrastructure.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Definition 2: &lt;em&gt;”Serverless” means you (the user) do not have to manage the infrastructure that your cloud services runs on.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You can spot the meaning of “serverless” in this context when the offering:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;is engineering-oriented,&lt;/li&gt;
&lt;li&gt;has a REST API, libraries, SDKs, or CLI client,&lt;/li&gt;
&lt;li&gt;allows you to create instances of this offering for your usage (but you don’t have to manage the day-to-day operations like upgrading, patching, or monitoring), and&lt;/li&gt;
&lt;li&gt;typically does not allow you to gain access directly to the operating system (such as a shell prompt or system administrator account).&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Definition 3: Serverless as a Software Concept
&lt;/h4&gt;

&lt;p&gt;Consider the process of building a Ruby on Rails, Python Django, or Node.js Express application. One of the first things you must do is write the code that starts a server to listen on a particular port. Or, if you go a level lower than these common frameworks, you might even have to create a socket server, attach a thread pool, and map incoming requests to an HTTP (or other protocol) handler. When you are doing any of these things, you are writing a &lt;em&gt;software&lt;/em&gt; server.&lt;/p&gt;

&lt;p&gt;In the software world, a server is a long-running process that listens for incoming requests (usually on a network connection) and then handles those requests. A server typically handles hundreds to millions of individual requests over its lifetime, which may span hours, days, months, or even years before the server is restarted.&lt;/p&gt;

&lt;p&gt;Contrast this with a program where, instead of standing up an entire server, you merely write a function that starts up (receives a single request), handles that request, and then optionally returns a response before it shuts down. That is, each request executes the program from start to finish. Such a program may run for milliseconds, seconds, or perhaps several minutes. But rarely does it run longer.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExNnQxM3dpeGg4NngxaWo3dDB0NmQzZW85NW8xc3ZxaDFubnc3NGk4dyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/nn0Kcrd72zzaZb1WxW/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExNnQxM3dpeGg4NngxaWo3dDB0NmQzZW85NW8xc3ZxaDFubnc3NGk4dyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/nn0Kcrd72zzaZb1WxW/giphy.gif" width="500" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is the serverless app model. And this is our third usage of the term “serverless”.&lt;/p&gt;

&lt;p&gt;This model gained the name “serverless function” when Amazon released its Lambda offering.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Definition 3: &lt;em&gt;“Serverless” means you do not have to write the software server that will listen for requests, nor do you need to manage the hardware or operating system&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You can spot the meaning of the term “serverless” in this context when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;you, the developer, write request (event) handlers instead of software servers,&lt;/li&gt;
&lt;li&gt;a variety of SDKs, APIs, or tooling are provided to make it easier for you to write programs,&lt;/li&gt;
&lt;li&gt;you do not need to manage server hardware or virtual machines, and&lt;/li&gt;
&lt;li&gt;you also typically do not have administrator access or shell access to the environment executing your code&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What is a Serverless App 🤓
&lt;/h2&gt;

&lt;p&gt;A serverless app is a thing that runs inside of an environment that manages all of the protocol-level and process-level aspects of serving content. Additionally, that serverless environment provides a layer of secure isolation from other serverless apps. In the strongest cases, this allows multitenant hosting, where two different customers or users can run their apps on the same serverless app platform without fear that the other users or customers can tamper with the app.&lt;/p&gt;

&lt;p&gt;A serverless app is the piece of software you, as the developer, write and upload to a serverless application platform. And the code you write is started when a new request is received. Your code is expected to handle that request and perhaps return a response, at which point your code is shut down again (ready to be started again in the future).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;For the most part, it is acceptable to use the terms “serverless app” and “serverless function” interchangeably. In this guide, we tend to use &lt;em&gt;serverless app&lt;/em&gt; because it is more generic (and also shorter to type). Where it is necessary to distinguish between the two, we do so carefully.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Comparing Serverless Apps and PaaS 🗒️
&lt;/h2&gt;

&lt;p&gt;It is useful to contrast a serverless app with an app written for a Platform-as-a-Service (PaaS). Heroku and the open-source &lt;a href="https://www.cloudfoundry.org/" rel="noopener noreferrer"&gt;Cloud Foundry&lt;/a&gt; are examples of PaaS. In contrast, &lt;a href="https://fermyon.com/cloud" rel="noopener noreferrer"&gt;Fermyon Cloud&lt;/a&gt; is an example of a serverless app platform, and &lt;a href="https://fermyon.com/spin" rel="noopener noreferrer"&gt;Spin&lt;/a&gt; is a developer tool for building serverless apps.&lt;/p&gt;

&lt;p&gt;For starters, Serverless apps are more cost-effective than long-running servers in a PaaS environment.  A serverless app is simpler, faster, and more resource-efficient than a PaaS. But let's dive into the conceptual details to see why this is the case.&lt;/p&gt;

&lt;p&gt;In a PaaS, you (the developer) write an application in any language the PaaS supports. And then you deploy that application to the PaaS service, where it is hosted on your behalf. Developer self-service is a core feature of a PaaS. That means developers can deploy their applications without relying upon an operations (DevOps or platform engineering) team.&lt;/p&gt;

&lt;p&gt;Most serverless app platforms, including &lt;a href="https://fermyon.com/cloud" rel="noopener noreferrer"&gt;Fermyon Cloud&lt;/a&gt;, also provide the same kind of developer self-service, including a web dashboard, metrics and monitoring, and a host of tools to assist in developing and debugging.&lt;/p&gt;

&lt;p&gt;Where a PaaS differs from serverless apps is, again, in the programming model. A PaaS follows definition 2 of serverless where the hardware is managed, but the developer must still write a &lt;em&gt;software server&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Meanwhile, serverless apps follow the 3rd definition of serverless. Whereby a developer writes only the small program that handles a request and does not have to worry about writing the software server.&lt;/p&gt;

&lt;p&gt;If you are looking for a developer self-service platform to run long-running services that are always on, a PaaS is probably the solution you are after. If you are looking for a developer self-service platform where you can quickly write highly efficient apps that need to execute instantly and scale rapidly, you may prefer to take a look at serverless apps. &lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion 😊
&lt;/h2&gt;

&lt;p&gt;That was a comprehensive look at defining Serverless and Serverless apps. In the rest of the series we will look at the characteristics of a serveless function, the use cases and of course some code. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExazlsa212MWZvamt6Y2dwdHQzeXZscjk3bjc0MTVkbjQ3MmZvanJubCZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/DAzIIpUmSgFXbp1hFV/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExazlsa212MWZvamt6Y2dwdHQzeXZscjk3bjc0MTVkbjQ3MmZvanJubCZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/DAzIIpUmSgFXbp1hFV/giphy.gif" width="400" height="333"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let us know in the comments if you are using Serverless already, and what your usecases are! &lt;/p&gt;

</description>
      <category>cloud</category>
      <category>serverless</category>
      <category>webassembly</category>
      <category>cloudnative</category>
    </item>
  </channel>
</rss>
