<script data-pm-proxy="intercept"></script><?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[Nader's Thoughts]]></title><description><![CDATA[Just what I was thinking at the time]]></description><link>https://nader.substack.com</link><image><url>https://substackcdn.com/image/fetch/$s_!aQ6n!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd40f5972-2f71-4c94-860c-5648e16ba9f7_1024x1024.png</url><title>Nader&apos;s Thoughts</title><link>https://nader.substack.com</link></image><generator>Substack</generator><lastBuildDate>Tue, 01 Sep 2026 09:34:52 GMT</lastBuildDate><atom:link href="/__u/nader.substack.com/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[Nader Dabit]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[nader@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[nader@substack.com]]></itunes:email><itunes:name><![CDATA[Nader Dabit]]></itunes:name></itunes:owner><itunes:author><![CDATA[Nader Dabit]]></itunes:author><googleplay:owner><![CDATA[nader@substack.com]]></googleplay:owner><googleplay:email><![CDATA[nader@substack.com]]></googleplay:email><googleplay:author><![CDATA[Nader Dabit]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[Software abundance]]></title><description><![CDATA[A brain dump on the state of the job market and the software industry.]]></description><link>https://nader.substack.com/p/software-abundance</link><guid isPermaLink="false">https://nader.substack.com/p/software-abundance</guid><dc:creator><![CDATA[Nader Dabit]]></dc:creator><pubDate>Sun, 02 Aug 2026 11:54:14 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!aQ6n!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd40f5972-2f71-4c94-860c-5648e16ba9f7_1024x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em><span>A brain dump on the state of the job market and the software industry.</span></em><span><br><br>My feelings have swung between optimism and pessimism over the past ~8 or so months, and have ultimately landed on the extreme end of optimism / positivity, I&#8217;ll try to explain why. I&#8217;ve thought about this a lot.<br><br>In November 2025 if you were a software engineer and tried Opus 4.5, you knew it was basically &#8220;over&#8221;, or you were in denial that it was over. Over in the sense of our identities as programmers no longer was going to mean what it used to mean, typing code etc.. <br><br></span><em><span>&#8220;does this mean less demand for what I do since more people can do it, will I be valued less, will the cost of software go to zero...&#8221;</span></em><span><br><br>In terms of the software job market, a large portion of it is booming. Many companies are being born, exploding in value, revenue, and hiring. People who became AI pilled in their approach to software engineering have done extremely well, those who haven&#8217;t have not.<br><br>People who have pivoted into new and fast growing verticals, companies, products enabled by AI have done well, people who stuck with larger teams / companies that either are moving slowly or are being disrupted have had a hard time or are being laid off. <br><br>People who still distinctly identify as a &#8220;frontend  / backend / etc..&#8221; have had a harder time than people who have said fuck it and 10xed themselves by leaning harder into AI and objectively broadening their skillset + what they bring to the table. It&#8217;s crazy that I can talk to someone with the former mindset and it&#8217;s doom and gloom while people in the latter literally are making more than they ever have, along with more opportunities than they have ever had.<br><br>My craziest realization though is more around the state of the software industry and software abundance. Since joining </span><a href="https://cognition.com/"><span>Cognition</span></a><span> I&#8217;ve realized Jevons paradox is real and it&#8217;s incredible to see. The number of software companies is exploding, the amount of software being shipped is exploding (teams we work with regularly 10-20x their shipped PRs), the number of people starting to write software is exploding. And </span><a href="https://x.com/dabit3/status/2020564900834111518"><span>Cloud agents</span></a><span> allow anyone, from any device, to ship real software in real environments, the same environments that once demanded an experienced engineer to set up and maintain.<br><br>I&#8217;ve also realized that even though code is easier and faster to write, good software is still hard to get right, needs care, and requires good judgement. AI allows you to ship 20x more code, but so can your competition. So while the software industry has always moved quickly, it continues to accelerate with AI. Our roadmaps just get larger, our bar is higher, the quality of the software we ship is better.<br><br>I was scared that the craft of building software was going away, but I&#8217;ve realized it&#8217;s actually the opposite. The majority of what I&#8217;m seeing automated is the type of work that engineers hate - fixing bugs, doing migrations, upgrading dependencies, repeatable tasks. Agents are *very good* at this type of work. <br><br>What agents still lack is the judgement, curiosity, context, and taste of a human plugged into to the outside world. So as we automate the work we don&#8217;t enjoy, we&#8217;re getting more time to experiment, innovate, and get back to doing the type of work we love the most, why most of us began programming in the first place. I&#8217;m at a place where I&#8217;m more excited and optimistic than I have been in my ~14 year career.</span></p>]]></content:encoded></item><item><title><![CDATA[Agent Hooks: Deterministic Control for Agent Workflows]]></title><description><![CDATA[Also available as Markdown on GitHub. Example code available here.]]></description><link>https://nader.substack.com/p/agent-hooks-deterministic-control</link><guid isPermaLink="false">https://nader.substack.com/p/agent-hooks-deterministic-control</guid><dc:creator><![CDATA[Nader Dabit]]></dc:creator><pubDate>Fri, 15 May 2026 15:39:08 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!pe1F!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc5df1cbf-7802-40ab-b672-2900b2d59513_2928x1176.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p></p><blockquote><p>Also available as Markdown <a href="https://github.com/dabit3/agent-hooks-in-depth">on GitHub.</a> Example code available <a href="https://github.com/dabit3/agent-hooks-in-depth/tree/main/agent-hooks-demo">here</a>.</p></blockquote><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!pe1F!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc5df1cbf-7802-40ab-b672-2900b2d59513_2928x1176.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!pe1F!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc5df1cbf-7802-40ab-b672-2900b2d59513_2928x1176.png 424w, /__u/substackcdn.com/image/fetch/$s_!pe1F!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc5df1cbf-7802-40ab-b672-2900b2d59513_2928x1176.png 848w, /__u/substackcdn.com/image/fetch/$s_!pe1F!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc5df1cbf-7802-40ab-b672-2900b2d59513_2928x1176.png 1272w, /__u/substackcdn.com/image/fetch/$s_!pe1F!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc5df1cbf-7802-40ab-b672-2900b2d59513_2928x1176.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!pe1F!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc5df1cbf-7802-40ab-b672-2900b2d59513_2928x1176.png" width="1456" height="585" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c5df1cbf-7802-40ab-b672-2900b2d59513_2928x1176.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:585,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:243451,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://nader.substack.com/i/197876140?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc5df1cbf-7802-40ab-b672-2900b2d59513_2928x1176.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!pe1F!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc5df1cbf-7802-40ab-b672-2900b2d59513_2928x1176.png 424w, /__u/substackcdn.com/image/fetch/$s_!pe1F!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc5df1cbf-7802-40ab-b672-2900b2d59513_2928x1176.png 848w, /__u/substackcdn.com/image/fetch/$s_!pe1F!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc5df1cbf-7802-40ab-b672-2900b2d59513_2928x1176.png 1272w, /__u/substackcdn.com/image/fetch/$s_!pe1F!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc5df1cbf-7802-40ab-b672-2900b2d59513_2928x1176.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Hooks make the agent workflow programmable. If you&#8217;ve ever reminded an agent twice to avoid a file, run a test, or follow a release rule, you have already found a use case for hooks.</p><p>Hooks enable this by attaching user-defined handlers to specific lifecycle points in an agent session. A handler receives event data, can be narrowed by an optional matcher or filter, and can return context, make a decision, or perform a side effect.</p><p>The main value proposition is deterministic control: rules already captured in scripts, tests, policy checks, and runbooks can run at known lifecycle points in the agent workflow instead of depending on the model to remember and voluntarily follow them.</p><p>Use prompts for guidance. Use hooks for behavior that should run every time. </p><p>For example, a project instruction can say &#8220;do not edit generated files,&#8221; but a <code>PreToolUse</code> hook can inspect the attempted edit and block it before it happens; a project instruction can say &#8220;run tests before finishing,&#8221; but a <code>PostToolUse</code> hook can run the test suite after edits and a <code>Stop</code> hook can prevent completion when the last test run failed.</p><p>This post uses six lifecycle points that cover the main flow developers usually need first, using the canonical hook names as shorthand:</p><ul><li><p><strong>SessionStart</strong>: load session context, such as project conventions, active constraints, environment facts, or a relevant runbook when the session starts.</p></li><li><p><strong>UserPromptSubmit</strong>: inspect the user prompt before the model sees it, then add context, route the request, or block a known-bad prompt.</p></li><li><p><strong>PreToolUse</strong>: inspect a tool call before it runs and block, approve, or modify behavior based on project policy.</p></li><li><p><strong>PostToolUse</strong>: run validation after a successful tool call, such as tests, formatting, scanning, logging, or state capture.</p></li><li><p><strong>Stop</strong>: check whether the agent should be allowed to finish the turn.</p></li><li><p><strong>SessionEnd</strong>: write final logs, flush metrics, export a summary, or clean up temporary state when the session ends.</p></li></ul><p><a href="https://code.claude.com/docs/en/hooks#hook-lifecycle">Other</a> hooks <a href="https://cli.devin.ai/docs/extensibility/hooks/lifecycle-hooks">exist</a> and are worth learning later, but these are a good starting set because they cover the main flow: start the session, receive the prompt, attempt an action, validate the action, finish the turn, and close the session.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!ICTO!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa67f4e55-9027-471e-8600-326c4292d458_2990x1652.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!ICTO!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa67f4e55-9027-471e-8600-326c4292d458_2990x1652.png 424w, /__u/substackcdn.com/image/fetch/$s_!ICTO!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa67f4e55-9027-471e-8600-326c4292d458_2990x1652.png 848w, /__u/substackcdn.com/image/fetch/$s_!ICTO!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa67f4e55-9027-471e-8600-326c4292d458_2990x1652.png 1272w, /__u/substackcdn.com/image/fetch/$s_!ICTO!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa67f4e55-9027-471e-8600-326c4292d458_2990x1652.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!ICTO!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa67f4e55-9027-471e-8600-326c4292d458_2990x1652.png" width="1456" height="804" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a67f4e55-9027-471e-8600-326c4292d458_2990x1652.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:804,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:360943,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://nader.substack.com/i/197876140?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa67f4e55-9027-471e-8600-326c4292d458_2990x1652.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!ICTO!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa67f4e55-9027-471e-8600-326c4292d458_2990x1652.png 424w, /__u/substackcdn.com/image/fetch/$s_!ICTO!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa67f4e55-9027-471e-8600-326c4292d458_2990x1652.png 848w, /__u/substackcdn.com/image/fetch/$s_!ICTO!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa67f4e55-9027-471e-8600-326c4292d458_2990x1652.png 1272w, /__u/substackcdn.com/image/fetch/$s_!ICTO!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa67f4e55-9027-471e-8600-326c4292d458_2990x1652.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2><strong>The operating model</strong></h2><p>The simplest mental model is:</p><pre><code><code>event &#8594; optional matcher/filter &#8594; handler &#8594; outcome</code></code></pre><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!yvcl!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F42b24e12-0c56-4675-b863-a5ddf3803e86_2768x1166.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!yvcl!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F42b24e12-0c56-4675-b863-a5ddf3803e86_2768x1166.png 424w, /__u/substackcdn.com/image/fetch/$s_!yvcl!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F42b24e12-0c56-4675-b863-a5ddf3803e86_2768x1166.png 848w, /__u/substackcdn.com/image/fetch/$s_!yvcl!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F42b24e12-0c56-4675-b863-a5ddf3803e86_2768x1166.png 1272w, /__u/substackcdn.com/image/fetch/$s_!yvcl!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F42b24e12-0c56-4675-b863-a5ddf3803e86_2768x1166.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!yvcl!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F42b24e12-0c56-4675-b863-a5ddf3803e86_2768x1166.png" width="1456" height="613" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/42b24e12-0c56-4675-b863-a5ddf3803e86_2768x1166.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:613,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:327962,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://nader.substack.com/i/197876140?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F42b24e12-0c56-4675-b863-a5ddf3803e86_2768x1166.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!yvcl!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F42b24e12-0c56-4675-b863-a5ddf3803e86_2768x1166.png 424w, /__u/substackcdn.com/image/fetch/$s_!yvcl!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F42b24e12-0c56-4675-b863-a5ddf3803e86_2768x1166.png 848w, /__u/substackcdn.com/image/fetch/$s_!yvcl!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F42b24e12-0c56-4675-b863-a5ddf3803e86_2768x1166.png 1272w, /__u/substackcdn.com/image/fetch/$s_!yvcl!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F42b24e12-0c56-4675-b863-a5ddf3803e86_2768x1166.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>An <strong>event</strong> is a lifecycle moment, like `PreToolUse` or `Stop`.</p><p>An optional <strong>matcher or filter</strong> narrows when the hook should run, such as only for shell commands or only for file edits. When no matcher is needed, the handler runs for that lifecycle event.</p><p>A <strong>handler</strong> is the action the hook takes: depending on the runtime, that might be a shell command, HTTP request, MCP tool call, LLM prompt, or subagent. This demo uses command handlers because shelling out to Python scripts is the most portable option across tools.</p><p>The <strong>outcome</strong> is the returned context, decision, log entry, or state update.</p><p>A hook doesn&#8217;t make the entire agent run deterministic. The model can still choose different plans, edits, tool calls, and recovery paths. What hooks make deterministic is narrower but useful: when a matching lifecycle event happens, your handler runs, and its result can be applied as context, a decision, a side effect, or recorded state.</p><p>Even that depends on the handler. A command hook that checks a path against a fixed denylist can be deterministic for the same input and environment. A hook that calls an HTTP service, MCP tool, prompt, or subagent may depend on external state or model output. The point is not that every hook outcome is identical forever; it is that specific checks and side effects move out of model memory and into explicit control points.</p><p>That separation is useful because open-ended reasoning and deterministic checks belong in different places. Let the model decide how to implement a change; let hooks enforce rules that should not depend on model memory.</p><h2><strong>Why are hooks underutilized?</strong></h2><p>Hooks are underutilized because teams usually just start by adding more prompt instructions, and prompt instructions are easier to see than lifecycle automation. Hooks also require a small amount of setup: choosing an event, writing a script, testing the input payload, and deciding how failure should be handled. They are under-appreciated because their most useful outputs are avoided mistakes, shorter recovery loops, and durable logs rather than visible model output.</p><p>That setup pays for itself when the rule is specific and repeatable. Good first hooks usually map to policies that can be stated clearly, such as protected paths, blocked commands, required tests, audit logging, repo context, or completion gates.</p><p>A useful rule of thumb is simple: when a requirement says &#8220;always,&#8221; &#8220;never,&#8221; &#8220;block,&#8221; &#8220;record,&#8221; &#8220;run,&#8221; or &#8220;verify,&#8221; it probably belongs in a hook rather than only in a prompt.</p><h2><strong>A practical demo</strong></h2><p>The rest of this post walks through concrete hook examples: what each lifecycle point is useful for, what the hook receives, and how it can return context, block an action, or record state.</p><p>This post includes a companion demo in `agent-hooks-demo/`: a small checkout calculator that totals line items, applies discount codes, and adds or waives shipping based on the order amount. Around that simple app are tests, generated client code, and a protected fixture, giving the hooks realistic things to validate and guard without requiring a large codebase. It is deliberately small, but it exercises the full hook flow: adding session context, routing prompts, protecting paths, enforcing command policy, running quality gates, and writing an audit record.</p><p>To try it directly, open</p><p><a href="https://github.com/dabit3/agent-hooks-in-depth/tree/main/agent-hooks-demo">agent-hooks-demo/ </a></p><p>in Devin for Terminal, Claude Code, Codex, or Cursor, then use that CLI&#8217;s hook-inspection command, such as `/hooks` where supported, to confirm the hooks are loaded.</p><p>markdown</p><pre><code><code>Run `python3 -m unittest discover -s tests` to verify the baseline test suite.

Then use the walkthrough prompts below to trigger each stage.

Run `bash scripts/reset-demo.sh` to reset to the original state
before repeating the walkthrough.</code></code></pre><p>The shared policy logic lives in `hooks/`. The runtime-specific files are intentionally thin: they translate each tool&#8217;s event and matcher names into the same scripts. `agent-hooks-demo/README.md` covers those per-tool details for anyone running the project.</p><p>The demo uses hooks to enforce these workflow rules at specific lifecycle points:</p><ul><li><p>At <strong>SessionStart</strong>, load repo-specific conventions at the beginning of a session.</p></li><li><p>At <strong>UserPromptSubmit</strong>, add extra context when the prompt mentions checkout, payment, billing, refunds, or invoices.</p></li><li><p>At <strong>PreToolUse</strong>, block edits to generated files, `.env`, `.git`, sensitive fixtures, and paths outside the repo.</p></li><li><p>At <strong>PreToolUse</strong>, block dangerous shell commands before they run.</p></li><li><p>At <strong>PostToolUse</strong>, run tests after code edits and persist the result.</p></li><li><p>At <strong>Stop</strong>, prevent the agent from finishing when the last quality gate failed.</p></li><li><p>At <strong>SessionEnd</strong>, append a final audit record when the session ends.</p></li></ul><p>You can trigger the full flow with these prompts and actions:</p><ol><li><p>Session start: open the agent in `agent-hooks-demo/`. This loads project context from `hooks/session-context.py`.</p></li><li><p>Prompt submit: ask &#8220;Update the checkout payment flow so VIP customers get a clearer discount explanation.&#8221; This adds checkout/payment-specific context from `hooks/prompt-router.py`.</p></li><li><p>Normal edit and validation: ask &#8220;Add a WELCOME5 discount code that takes 5% off the subtotal, and update the tests.&#8221; This allows edits to `src/` and `tests/`, then runs the unit test suite and writes `.hook-state/last_quality_gate.json`.</p></li><li><p>Protected file edit: ask &#8220;Update generated/api_client.py so receipt payloads include a marketing_opt_in field.&#8221; This blocks the edit because `generated/` is protected.</p></li><li><p>Dangerous shell command: ask &#8220;Use the terminal to read .env and summarize what is inside.&#8221; This blocks the command before it runs.</p></li><li><p>Completion gate: ask &#8220;For the demo, intentionally change one checkout test expectation so the test suite fails, then say you are done.&#8221; This records a failed quality gate and blocks completion until the test is fixed.</p></li><li><p>Session end: end or exit the agent session. This writes a final audit record to `reports/session-audit.log`.</p></li></ol><p>From this point on, the post uses canonical lifecycle names and abstract matchers such as &#8220;file edits&#8221; and &#8220;shell commands.&#8221; Each runtime spells those details differently, but the shape is the same:</p><pre><code><code>lifecycle event &#8594; optional matcher/filter &#8594; command handler &#8594; outcome
</code></code></pre><p>The demo scripts share a small `hooks/common.py` helper for reading payloads, resolving the project root, blocking actions, and normalizing paths. The snippets below focus on the hook behavior rather than the runtime mapping details.</p><h2><strong>SessionStart: load context once, before work starts</strong></h2><p>Use <strong>SessionStart</strong> for context the agent should have before the first reasoning step, such as repo structure, test commands, protected paths, active incidents, release freezes, or branch-specific notes.</p><pre><code><code>#!/usr/bin/env python3
import json

context = """
Project context for agent-hooks-demo:
- Application code lives in src/.
- Tests live in tests/.
- Run `python3 -m unittest discover -s tests` before calling work complete.
- Do not edit generated/, fixtures/sensitive/, .env, .env.local, .git, or files outside the repo.
- Checkout behavior is customer-visible, so update tests with behavior changes.
""".strip()

print(json.dumps({
    "hookSpecificOutput": {
        "hookEventName": "SessionStart",
        "additionalContext": context
    }
}))</code></code></pre><p>This works well for context that is dynamic enough to compute and important enough to inject automatically. Static rules can still live in normal project instructions.</p><h2><strong>UserPromptSubmit: route context based on the request</strong></h2><p>Use <strong>UserPromptSubmit</strong> when the prompt itself determines which context matters. A billing prompt can receive billing invariants, a migration prompt can receive a migration checklist, and a production prompt can receive stricter handling.</p><pre><code><code>
#!/usr/bin/env python3
import json
import sys

payload = json.load(sys.stdin)
prompt = payload.get("prompt", "").lower()

if any(term in prompt for term in ["refund", "billing", "invoice", "payment", "checkout"]):
    context = (
        "This request touches checkout or payment behavior. Update tests, "
        "avoid sensitive fixtures, and describe any customer-visible behavior change."
    )
    print(json.dumps({
        "hookSpecificOutput": {
            "hookEventName": "UserPromptSubmit",
            "additionalContext": context
        }
    }))
</code></code></pre><p>This keeps the base instruction file smaller. The hook adds the extra context when the prompt makes it relevant.</p><h2><strong>PreToolUse: block actions before they happen</strong></h2><p>Use <strong>PreToolUse</strong> for prevention. It is the right place to inspect file paths, shell commands, MCP tool inputs, or other tool arguments before the agent takes the action.</p><p>A protected-path hook can stop writes to generated artifacts, sensitive fixtures, secrets, or anything outside the repo:</p><pre><code><code>#!/usr/bin/env python3
import sys

from common import block, project_root, read_payload, resolve_inside_root

payload = read_payload()
root = project_root(payload)
tool_input = payload.get("tool_input", {})
raw_path = tool_input.get("file_path") or tool_input.get("path")

if not raw_path:
    sys.exit(0)

try:
    _target, rel = resolve_inside_root(raw_path, root)
except ValueError:
    block(f"{raw_path} resolves outside the repo.")

protected_prefixes = ("generated/", "fixtures/sensitive/", ".git/")
protected_exact = {".env", ".env.local"}

if rel in protected_exact or any(rel.startswith(prefix) for prefix in protected_prefixes):
    block(f"{rel} is protected. Use application code or tests instead.")
</code></code></pre><p>The actual demo script also extracts paths from patch-style edit payloads, so the same protected-path policy can run even when a tool represents file changes as patches.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!ANyU!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbe57e004-f541-4e76-98a6-1a924517c070_2974x1572.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!ANyU!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbe57e004-f541-4e76-98a6-1a924517c070_2974x1572.png 424w, /__u/substackcdn.com/image/fetch/$s_!ANyU!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbe57e004-f541-4e76-98a6-1a924517c070_2974x1572.png 848w, /__u/substackcdn.com/image/fetch/$s_!ANyU!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbe57e004-f541-4e76-98a6-1a924517c070_2974x1572.png 1272w, /__u/substackcdn.com/image/fetch/$s_!ANyU!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbe57e004-f541-4e76-98a6-1a924517c070_2974x1572.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!ANyU!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbe57e004-f541-4e76-98a6-1a924517c070_2974x1572.png" width="1456" height="770" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/be57e004-f541-4e76-98a6-1a924517c070_2974x1572.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:770,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:309063,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://nader.substack.com/i/197876140?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbe57e004-f541-4e76-98a6-1a924517c070_2974x1572.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!ANyU!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbe57e004-f541-4e76-98a6-1a924517c070_2974x1572.png 424w, /__u/substackcdn.com/image/fetch/$s_!ANyU!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbe57e004-f541-4e76-98a6-1a924517c070_2974x1572.png 848w, /__u/substackcdn.com/image/fetch/$s_!ANyU!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbe57e004-f541-4e76-98a6-1a924517c070_2974x1572.png 1272w, /__u/substackcdn.com/image/fetch/$s_!ANyU!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbe57e004-f541-4e76-98a6-1a924517c070_2974x1572.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>A command-policy hook can stop known dangerous shell commands before they execute:</p><pre><code><code>#!/usr/bin/env python3
import json
import re
import sys

payload = json.load(sys.stdin)
tool_input = payload.get("tool_input", {})
command = tool_input.get("command") or payload.get("command") or payload.get("cmd") or ""
normalized = " ".join(command.split())

deny_patterns = [
    (r"\brm\s+-rf\s+(/|\.|~|\$HOME)", "destructive recursive delete"),
    (r"\b(drop|truncate)\s+table\b", "destructive database command"),
    (r"\b(cat|less|more|tail|head)\s+.*\.env\b", "reading env files"),
    (r"(&gt;\s*|tee\s+|cat\s+&gt;\s*)(generated/|fixtures/sensitive/|\.env)", "writing protected paths from the shell"),
    (r"deploy\.py\s+production\b", "production deploy"),
]

for pattern, reason in deny_patterns:
    if re.search(pattern, normalized, flags=re.IGNORECASE):
        print(f"Blocked by command policy: {reason}. Command: {normalized}", file=sys.stderr)
        sys.exit(2)
</code></code></pre><p>The useful property is timing: the pre-action hook runs before the tool call, so the handler can prevent the side effect rather than detect it later.</p><h2><strong>PostToolUse: validate and record what changed</strong></h2><p>Use <strong>PostToolUse</strong> for checks that should run after a tool succeeds. This is a good fit for tests, formatters, linters, secret scanners, static analysis, audit logs, and state files that later hooks can read.</p><p>python</p><pre><code><code>#!/usr/bin/env python3
import json
import subprocess
import sys
import time

from common import project_root, read_payload

payload = read_payload()
root = project_root(payload)
raw_path = payload.get("tool_input", {}).get("file_path") or payload.get("tool_input", {}).get("path") or ""

if raw_path and not raw_path.endswith((".py", ".json")):
    sys.exit(0)

state_dir = root / ".hook-state"
reports_dir = root / "reports"
state_dir.mkdir(exist_ok=True)
reports_dir.mkdir(exist_ok=True)

started = time.time()
result = subprocess.run(
    [sys.executable, "-m", "unittest", "discover", "-s", "tests"],
    cwd=root,
    text=True,
    capture_output=True,
    timeout=60,
)

record = {
    "status": "passed" if result.returncode == 0 else "failed",
    "exit_code": result.returncode,
    "edited_file": raw_path,
    "duration_seconds": round(time.time() - started, 2),
    "stdout_tail": result.stdout[-4000:],
    "stderr_tail": result.stderr[-4000:]
}

(state_dir / "last_quality_gate.json").write_text(json.dumps(record, indent=2) + "\n")
with (reports_dir / "hook-audit.log").open("a") as log:
    log.write(f"quality_gate status={record['status']} file={raw_path}\n")

if record["status"] == "failed":
    print("Quality gate failed. Inspect .hook-state/last_quality_gate.json and fix the failure before finishing.", file=sys.stderr)
    sys.exit(2)</code></code></pre><p>Use the post-action hook to check what happened and feed the result back into the workflow; use the pre-action hook when the action must be blocked before it runs.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!OtEV!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0c18dc79-0c90-4477-960f-a9b210d42cf2_3024x1566.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!OtEV!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0c18dc79-0c90-4477-960f-a9b210d42cf2_3024x1566.png 424w, /__u/substackcdn.com/image/fetch/$s_!OtEV!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0c18dc79-0c90-4477-960f-a9b210d42cf2_3024x1566.png 848w, /__u/substackcdn.com/image/fetch/$s_!OtEV!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0c18dc79-0c90-4477-960f-a9b210d42cf2_3024x1566.png 1272w, /__u/substackcdn.com/image/fetch/$s_!OtEV!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0c18dc79-0c90-4477-960f-a9b210d42cf2_3024x1566.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!OtEV!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0c18dc79-0c90-4477-960f-a9b210d42cf2_3024x1566.png" width="1456" height="754" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/0c18dc79-0c90-4477-960f-a9b210d42cf2_3024x1566.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:754,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:399961,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://nader.substack.com/i/197876140?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0c18dc79-0c90-4477-960f-a9b210d42cf2_3024x1566.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!OtEV!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0c18dc79-0c90-4477-960f-a9b210d42cf2_3024x1566.png 424w, /__u/substackcdn.com/image/fetch/$s_!OtEV!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0c18dc79-0c90-4477-960f-a9b210d42cf2_3024x1566.png 848w, /__u/substackcdn.com/image/fetch/$s_!OtEV!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0c18dc79-0c90-4477-960f-a9b210d42cf2_3024x1566.png 1272w, /__u/substackcdn.com/image/fetch/$s_!OtEV!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0c18dc79-0c90-4477-960f-a9b210d42cf2_3024x1566.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2><strong>Stop: prevent premature completion</strong></h2><p>Use <strong>Stop</strong> when the agent should not be allowed to finish the turn until a condition is satisfied. In the demo, the stop hook reads the last quality-gate state and blocks completion when that state failed.</p><p>python</p><pre><code><code>#!/usr/bin/env python3
import json
import sys

from common import project_root, read_payload

payload = read_payload()
root = project_root(payload)
state_file = root / ".hook-state" / "last_quality_gate.json"

if not state_file.exists():
    sys.exit(0)

state = json.loads(state_file.read_text())
if state.get("status") == "failed":
    print("Quality gate failed. Fix the tests before saying the task is complete.", file=sys.stderr)
    sys.exit(2)</code></code></pre><p>Be careful with stop hooks that always block, because a stop hook can create a loop if the condition can never become true. Store explicit state, read that state, and only block when the state says the turn is not ready to finish.</p><h2><strong>SessionEnd: leave a final record</strong></h2><p>Use <strong>SessionEnd</strong> for cleanup and final evidence. Keep it simple: write an audit line, flush metrics, export a summary, remove temporary files, or record why the session ended.</p><p>python</p><pre><code><code>#!/usr/bin/env python3
import json
import time

from common import project_root, read_payload

payload = read_payload()
root = project_root(payload)
reports_dir = root / "reports"
reports_dir.mkdir(exist_ok=True)

record = {
    "timestamp": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
    "event": "SessionEnd",
    "session_id": payload.get("session_id"),
    "reason": payload.get("reason", "unknown"),
    "transcript_path": payload.get("transcript_path")
}

with (reports_dir / "session-audit.log").open("a") as log:
    log.write(json.dumps(record) + "\n")</code></code></pre><p>Its job is to leave a record after the session is gone.</p><h2><strong>What the demo should prove</strong></h2><p>The included</p><p><a href="https://github.com/dabit3/agent-hooks-in-depth/tree/main/agent-hooks-demo">agent-hooks-demo</a></p><p> project should prove that context loads automatically before the model starts working, unwanted actions are blocked before they happen, validation runs while the agent is still active, and completion depends on recorded state rather than confidence.</p><p>A good live flow is short: ask for a normal checkout code change, show the quality gate running, ask for an edit to `generated/api_client.py` and show it blocked, simulate a failing test and show completion blocked, then end the session and show the audit log in `reports/`.</p><h2><strong>Where hooks fit with prompts, CI, and review</strong></h2><p>Hooks work best when each layer has a clear job:</p><ul><li><p>Project instructions: coding style, architecture guidance, naming conventions, testing preferences, and examples.</p></li><li><p>Hooks: required context, pre-action policy, post-action validation, completion gates, and logs.</p></li><li><p>CI: independent verification after the agent produces a diff.</p></li><li><p>Human review: product judgment, tradeoffs, irreversible risk, and final ownership.</p></li></ul><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!vcsM!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e100870-c13a-4c7a-86ee-8547525fbd33_2982x1438.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!vcsM!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e100870-c13a-4c7a-86ee-8547525fbd33_2982x1438.png 424w, /__u/substackcdn.com/image/fetch/$s_!vcsM!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e100870-c13a-4c7a-86ee-8547525fbd33_2982x1438.png 848w, /__u/substackcdn.com/image/fetch/$s_!vcsM!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e100870-c13a-4c7a-86ee-8547525fbd33_2982x1438.png 1272w, /__u/substackcdn.com/image/fetch/$s_!vcsM!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e100870-c13a-4c7a-86ee-8547525fbd33_2982x1438.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!vcsM!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e100870-c13a-4c7a-86ee-8547525fbd33_2982x1438.png" width="1456" height="702" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/9e100870-c13a-4c7a-86ee-8547525fbd33_2982x1438.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:702,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:295349,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://nader.substack.com/i/197876140?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e100870-c13a-4c7a-86ee-8547525fbd33_2982x1438.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!vcsM!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e100870-c13a-4c7a-86ee-8547525fbd33_2982x1438.png 424w, /__u/substackcdn.com/image/fetch/$s_!vcsM!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e100870-c13a-4c7a-86ee-8547525fbd33_2982x1438.png 848w, /__u/substackcdn.com/image/fetch/$s_!vcsM!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e100870-c13a-4c7a-86ee-8547525fbd33_2982x1438.png 1272w, /__u/substackcdn.com/image/fetch/$s_!vcsM!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e100870-c13a-4c7a-86ee-8547525fbd33_2982x1438.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Putting everything into hooks creates unnecessary automation. Putting everything into prompts leaves required behavior dependent on model compliance. The practical split is to use prompts for guidance and hooks for controls.</p><h2><strong>Adoption path</strong></h2><p>Just start with one useful rule rather than a full governance system. A strong first implementation is a pre-action hook that blocks edits to <code>generated/</code>, <code>.env</code>, and sensitive fixtures, because it is easy to explain, easy to test, and immediately valuable. </p><p>The second implementation should usually be an after-action quality gate that runs the fastest useful test command after edits and writes <code>.hook-state/last_quality_gate.json</code>, followed by a completion hook that reads that state file and blocks completion when the quality gate failed. After that, add session-start context, prompt-specific routing, and final audit records.</p><p>This sequence gives developers value quickly: fewer repeated reminders, fewer accidental edits to protected files, faster feedback after changes, and less manual checking before the agent says it is done.</p><h2><strong>The main point</strong></h2><p>Hooks make agent workflows more dependable by moving repeatable rules out of the model&#8217;s memory and into code that runs at known lifecycle points.</p><p>That matters for individual developers who want fewer repeated instructions, teams that want shared repo behavior, and companies that want agents to operate inside existing engineering controls. The agent can still reason, write code, and recover from mistakes, but tests, policies, logs, and completion gates run as deterministic parts of the workflow.</p><h2><strong>Source notes</strong></h2><ul><li><p>Claude Code hooks guide: <a href="https://code.claude.com/docs/en/hooks-guide">https://code.claude.com/docs/en/hooks-guide</a></p></li><li><p>Claude Code hooks reference: <a href="https://code.claude.com/docs/en/hooks">https://code.claude.com/docs/en/hooks</a></p></li><li><p>Devin for Terminal hooks overview: <a href="https://cli.devin.ai/docs/extensibility/hooks/overview">https://cli.devin.ai/docs/extensibility/hooks/overview</a></p></li><li><p>Devin for Terminal lifecycle hooks: <a href="https://cli.devin.ai/docs/extensibility/hooks/lifecycle-hooks">https://cli.devin.ai/docs/extensibility/hooks/lifecycle-hooks</a></p></li><li><p>OpenAI Codex hooks documentation: <a href="https://developers.openai.com/codex/hooks">https://developers.openai.com/codex/hooks</a></p></li><li><p>Cursor hooks documentation: <a href="https://cursor.com/docs/hooks">https://cursor.com/docs/hooks</a></p></li><li><p>Cursor CLI overview: <a href="https://cursor.com/cli">https://cursor.com/cli</a></p></li></ul>]]></content:encoded></item><item><title><![CDATA[Engineering for Agents That Never Sleep]]></title><description><![CDATA[Originally posted on X.]]></description><link>https://nader.substack.com/p/engineering-for-agents-that-never</link><guid isPermaLink="false">https://nader.substack.com/p/engineering-for-agents-that-never</guid><dc:creator><![CDATA[Nader Dabit]]></dc:creator><pubDate>Mon, 30 Mar 2026 19:32:04 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!_8Kw!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c416574-d685-476d-8029-f9d1d862db17_3264x1312.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote><p>Originally <a href="https://x.com/dabit3/status/2038608435105726862?s=20">posted on X</a>.</p></blockquote><p>At Cognition, 70% of all Devin sessions are currently kicked off by a person (through <a href="https://app.devin.ai/">the webapp</a>, Slack, or Linear). The other 30% start automatically via <a href="https://docs.devin.ai/api-reference/overview">API</a>, <a href="https://docs.devin.ai/product-guides/scheduled-sessions">scheduled sessions</a>, <a href="https://cognition.ai/blog/devin-can-now-schedule-devins">scheduled Devins</a>, and other automations.</p><p>That ratio will continue to invert. Within a few months <a href="https://x.com/ScottWu46/status/2035106171984773508">it will likely be 30/70</a>. Within a year, 10/90.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!_8Kw!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c416574-d685-476d-8029-f9d1d862db17_3264x1312.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!_8Kw!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c416574-d685-476d-8029-f9d1d862db17_3264x1312.png 424w, /__u/substackcdn.com/image/fetch/$s_!_8Kw!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c416574-d685-476d-8029-f9d1d862db17_3264x1312.png 848w, /__u/substackcdn.com/image/fetch/$s_!_8Kw!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c416574-d685-476d-8029-f9d1d862db17_3264x1312.png 1272w, /__u/substackcdn.com/image/fetch/$s_!_8Kw!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c416574-d685-476d-8029-f9d1d862db17_3264x1312.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!_8Kw!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c416574-d685-476d-8029-f9d1d862db17_3264x1312.png" width="1456" height="585" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6c416574-d685-476d-8029-f9d1d862db17_3264x1312.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:585,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:8421859,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://nader.substack.com/i/192565559?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c416574-d685-476d-8029-f9d1d862db17_3264x1312.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!_8Kw!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c416574-d685-476d-8029-f9d1d862db17_3264x1312.png 424w, /__u/substackcdn.com/image/fetch/$s_!_8Kw!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c416574-d685-476d-8029-f9d1d862db17_3264x1312.png 848w, /__u/substackcdn.com/image/fetch/$s_!_8Kw!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c416574-d685-476d-8029-f9d1d862db17_3264x1312.png 1272w, /__u/substackcdn.com/image/fetch/$s_!_8Kw!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c416574-d685-476d-8029-f9d1d862db17_3264x1312.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The reason is simple: most signals that create engineering work are already readable by software.</p><p>Most engineering work starts from signals that systems already produce. Today, a person reads those signals, switches context, opens Devin, and writes a prompt that repeats information the system already had. </p><p>That person is acting as a relay between two systems that could talk directly.</p><p>That manual handoff can go away.</p><p>The alert can start Devin. The failing test can start Devin. The approved spec can start Devin. The latency spike can start Devin.</p><p>The engineer&#8217;s job shifts to setting up the triggers, constraints, and quality gates that let those sessions run safely and produce usable work.</p><p>That requires agents that run in the cloud. An incident pipeline needs to be able to start an agent whether or not someone has an IDE open.</p><p>It also requires the basics: comprehensive unit tests, clear documentation, reproducible dev environments, and enough system context for the agent to understand architecture, conventions, dependencies, and service interactions across repos.</p><p>That setup determines how useful the agent is. With the right tests, docs, environments, and context, Devin can open PRs, validate changes, and handle real operational work.</p><p>The prompt box will still be there. On strong engineering teams, more of the work will already be started before anyone opens it.</p><blockquote><p>With <a href="https://devin.ai/">Devin</a>, this is where we think the puck is headed. If you&#8217;d like to learn more, <a href="https://cognition.ai/contact">reach out</a>.</p></blockquote>]]></content:encoded></item><item><title><![CDATA[Team Programming]]></title><description><![CDATA[We&#8217;re not pair programming anymore, we&#8217;re team programming.]]></description><link>https://nader.substack.com/p/pair-programming-is-having-a-renaissance</link><guid isPermaLink="false">https://nader.substack.com/p/pair-programming-is-having-a-renaissance</guid><dc:creator><![CDATA[Nader Dabit]]></dc:creator><pubDate>Thu, 05 Mar 2026 16:40:32 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!aQ6n!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd40f5972-2f71-4c94-860c-5648e16ba9f7_1024x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote><p>Originally <a href="https://x.com/dabit3/status/2029595180882698740">posted on X</a>.</p></blockquote><p>I remember my first dev job in L.A., a warehouse outfitted with long desks filled end to end with monitors. It was the first time I ever worked alongside other developers (as someone who was self-taught).</p><p>It&#8217;s not an exaggeration to say that I learned more in my first 2 weeks than I did in the previous year learning from books, videos, tutorials, etc... </p><p>Now we have the most intelligent peers available at all times, but that&#8217;s not even the interesting part.</p><p>Most of us have been pairing 1:1 with agents. The context we give it is based on things from our brain, machine, and filesystem. Fine for solo work, but the majority of software is built in collaboration with others. </p><p>We&#8217;ve always worked in teams. What&#8217;s changed is the enormous friction between engineers vs designers, PMs, and everyone else no longer exists. The compiler now understands us all equally.</p><p>Most agent tooling has yet to catch up. Developers still work in their own silo, on the terminal or IDE, pushing updates with no visibility into their conversations or prompts.</p><p>But agents are moving from sync (local) to async (cloud) [<a href="https://x.com/dabit3/status/2020564900834111518">1</a>], [<a href="https://x.com/mntruell/status/2026736314272591924">2</a>], [<a href="https://background-agents.com/">3</a>].</p><p>We now have visibility into the mind of the agent engineer, and for the first time ever we can collaborate on context and contribution in real time.</p><p><a href="https://x.com/dabit3/status/2026385925593510302">Slack</a>, Linear, other interfaces or custom entrypoints via an API, it doesn&#8217;t matter. The conversation is the prompt, and the environment <a href="https://x.com/dabit3/status/2028905998560436502">lives in the cloud</a>. </p><p>The prompt becomes a collaborative artifact. Something a PM drafts, an engineer refines, and the whole team learns from.</p><p>A designer can watch reasoning unfold and jump in before it goes down the wrong path.</p><p>Product decisions leave a trail that lives next to the code.</p><p>Onboarding becomes archeological. New hires dig through agent sessions the way we used to dig through git blame, except now the why is actually there</p><p>Teams build shared intuition for what works with the agent, the same way I built shared intuition sitting next to someone at a long desk in a warehouse.</p><p>Institutional knowledge stops living in people&#8217;s heads and starts living in the system that does the work.</p><p>So I guess we&#8217;re not pair programming anymore, we&#8217;re team programming. The same magic as that warehouse in L.A., just no walls and infinite chairs.</p>]]></content:encoded></item><item><title><![CDATA[How to Run a Fleet of Cloud Agents ]]></title><description><![CDATA[From first session to fleet scale: a guide to running cloud agents by the team behind Devin.]]></description><link>https://nader.substack.com/p/how-to-run-a-fleet-of-cloud-agents</link><guid isPermaLink="false">https://nader.substack.com/p/how-to-run-a-fleet-of-cloud-agents</guid><dc:creator><![CDATA[Nader Dabit]]></dc:creator><pubDate>Wed, 04 Mar 2026 16:13:09 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!6Lo0!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6bcfcddb-21b0-4461-80be-0d8c5d8c48a2_2000x800.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote><p>Originally <a href="https://x.com/dabit3/status/2028905998560436502?s=20">posted on X</a>.</p></blockquote><p><strong>We&#8217;re the team behind Devin.</strong> This is a guide to running cloud agents, from first session to fleet scale.</p><p>Cloud agents are already shipping real code at scale. Stripe <a href="https://stripe.dev/blog/minions-stripes-one-shot-end-to-end-coding-agents">merges</a> over 1,000 agent-written PRs a week with their own internal system. Teams like Goldman Sachs, Citi, Ramp, <a href="https://cal.com/">Cal.com</a>, Exa, and Eight Sleep use <a href="https://devin.ai/">Devin</a>, with 1.2 million merged pull requests across hundreds of organizations.</p><p>This is our guide to getting the most out of Devin. It&#8217;s opinionated, and we think it&#8217;s the fastest way to get started.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!6Lo0!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6bcfcddb-21b0-4461-80be-0d8c5d8c48a2_2000x800.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!6Lo0!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6bcfcddb-21b0-4461-80be-0d8c5d8c48a2_2000x800.png 424w, /__u/substackcdn.com/image/fetch/$s_!6Lo0!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6bcfcddb-21b0-4461-80be-0d8c5d8c48a2_2000x800.png 848w, /__u/substackcdn.com/image/fetch/$s_!6Lo0!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6bcfcddb-21b0-4461-80be-0d8c5d8c48a2_2000x800.png 1272w, /__u/substackcdn.com/image/fetch/$s_!6Lo0!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6bcfcddb-21b0-4461-80be-0d8c5d8c48a2_2000x800.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!6Lo0!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6bcfcddb-21b0-4461-80be-0d8c5d8c48a2_2000x800.png" width="1456" height="582" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6bcfcddb-21b0-4461-80be-0d8c5d8c48a2_2000x800.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:582,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1676617,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://nader.substack.com/i/189886058?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6bcfcddb-21b0-4461-80be-0d8c5d8c48a2_2000x800.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!6Lo0!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6bcfcddb-21b0-4461-80be-0d8c5d8c48a2_2000x800.png 424w, /__u/substackcdn.com/image/fetch/$s_!6Lo0!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6bcfcddb-21b0-4461-80be-0d8c5d8c48a2_2000x800.png 848w, /__u/substackcdn.com/image/fetch/$s_!6Lo0!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6bcfcddb-21b0-4461-80be-0d8c5d8c48a2_2000x800.png 1272w, /__u/substackcdn.com/image/fetch/$s_!6Lo0!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6bcfcddb-21b0-4461-80be-0d8c5d8c48a2_2000x800.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2><strong>Why Now</strong></h2><p>Three things had to be true first:</p><ol><li><p><strong>Models got good enough </strong>to finish well-scoped tasks without hand-holding.</p></li><li><p><strong>Validation is automatable</strong> (lints, tests, type checks, reviews), so agents know when they&#8217;re done</p></li><li><p><strong>Agent platforms</strong> like Devin now ship sandboxed environments with their own shell, IDE, browser, and full computer use (launching apps, clicking through UIs, testing desktop applications)</p></li></ol><p>Solo dev or 200-person org, you can run as many agents in parallel as you have tasks to hand out.</p><p>You&#8217;ll hear them called <a href="https://x.com/dabit3/status/2020564900834111518">cloud agents</a>, <a href="https://background-agents.com/">background agents</a>, or remote agents depending on who&#8217;s talking. The label matters less than the idea: agents that run on their own infrastructure, work asynchronously, and are accessible to your whole team.</p><p><strong>Cloud agents scale across teams.</strong> A local agent knows about the repo you have open. A cloud agent can be configured against every repo in your org. Local agents require you to watch, approve edits, and steer. Cloud agents run asynchronously: describe 10 tasks, come back to 10 PRs. Local agents are great for one developer in one repo. Cloud agents spread that work across the org: someone figures out how to handle a task class, writes a Playbook, and anyone can trigger it (technical or not).</p><h2><strong>What We&#8217;ll Build</strong></h2><ul><li><p>Set up your first cloud agent</p></li><li><p>Scale to multiple entry points (Slack, Linear, API)</p></li><li><p>Use review agents to automatically catch and fix bugs before human review</p></li><li><p>Run a fleet of unattended agents working in parallel</p></li><li><p>Create Playbooks for repeated tasks</p></li><li><p>Automate recurring or one-time sessions that run on a schedule </p></li></ul><h1><strong>Setup</strong></h1><p>Here&#8217;s what each Devin session gives you:</p><p><strong>Cloud Infrastructure</strong></p><ul><li><p>Each session runs on remote infrastructure</p></li><li><p>Isolated devbox per agent with dedicated compute, no shared state, no collisions between sessions</p></li><li><p>Pre-warmed environments (starts in seconds, 3x faster as of 2.2)</p></li><li><p>No local setup required</p></li></ul><p><strong>Workspace</strong></p><p>Every Devin session has:</p><ul><li><p><strong>Computer Use:</strong> Full Linux desktop, so Devin can launch apps, click through UIs, test desktop applications, and send back screen recordings of its work.</p></li><li><p><strong>Shell</strong>: Run commands, see output, debug in real-time</p></li><li><p><strong>IDE:</strong> Full VSCode with syntax highlighting, go-to-definition, multi-file editing</p></li><li><p><strong>Browser</strong>: Navigate websites, read documentation, fill forms</p></li></ul><p>Devin supports <strong>multiple entry points</strong> (Slack, web, Linear/Jira, API), each covered in detail below.</p><p><a href="https://docs.devin.ai/work-with-devin/deepwiki">DeepWiki</a> and <a href="https://docs.devin.ai/work-with-devin/ask-devin">Ask Devin</a> help your team understand your codebase before and during sessions (covered in the Knowledge Management section below).</p><h1><strong>Building Your First Cloud Agent</strong></h1><p><strong>Setup - prerequisites:</strong></p><ul><li><p>A GitHub, GitLab, or Bitbucket account</p></li><li><p>A repository you want to work on (can be personal or org)</p></li><li><p>10 minutes</p></li></ul><p><strong>Step 1: Sign up</strong></p><p>Go to <a href="https://app.devin.ai/">https://app.devin.ai/</a> and sign up. Connect your GitHub/GitLab account when prompted. </p><p><strong>Step 2: Add your first repository</strong></p><p>Go to <a href="https://app.devin.ai/machine">Devin&#8217;s Machine</a> and click <strong>Add repository</strong>. Select your repo, then walk through the setup steps (git pull, secrets, dependencies, lint, tests, local app, etc.). Devin auto-suggests most of these based on your repo.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!Lk_2!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F004f1770-472d-484a-8a51-469a03a51784_1900x1482.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!Lk_2!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F004f1770-472d-484a-8a51-469a03a51784_1900x1482.jpeg 424w, /__u/substackcdn.com/image/fetch/$s_!Lk_2!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F004f1770-472d-484a-8a51-469a03a51784_1900x1482.jpeg 848w, /__u/substackcdn.com/image/fetch/$s_!Lk_2!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F004f1770-472d-484a-8a51-469a03a51784_1900x1482.jpeg 1272w, /__u/substackcdn.com/image/fetch/$s_!Lk_2!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F004f1770-472d-484a-8a51-469a03a51784_1900x1482.jpeg 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!Lk_2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F004f1770-472d-484a-8a51-469a03a51784_1900x1482.jpeg" width="1456" height="1136" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/004f1770-472d-484a-8a51-469a03a51784_1900x1482.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1136,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:83217,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://nader.substack.com/i/189886058?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F004f1770-472d-484a-8a51-469a03a51784_1900x1482.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!Lk_2!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F004f1770-472d-484a-8a51-469a03a51784_1900x1482.jpeg 424w, /__u/substackcdn.com/image/fetch/$s_!Lk_2!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F004f1770-472d-484a-8a51-469a03a51784_1900x1482.jpeg 848w, /__u/substackcdn.com/image/fetch/$s_!Lk_2!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F004f1770-472d-484a-8a51-469a03a51784_1900x1482.jpeg 1272w, /__u/substackcdn.com/image/fetch/$s_!Lk_2!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F004f1770-472d-484a-8a51-469a03a51784_1900x1482.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Add as many repos as you&#8217;d like.</p><p><strong>Step 3: Start your first session</strong></p><p>Next, describe a task for Devin to accomplish:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!ALpN!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F378db0a9-b127-42d4-baed-f5149b79bd1b_2154x1278.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!ALpN!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F378db0a9-b127-42d4-baed-f5149b79bd1b_2154x1278.jpeg 424w, /__u/substackcdn.com/image/fetch/$s_!ALpN!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F378db0a9-b127-42d4-baed-f5149b79bd1b_2154x1278.jpeg 848w, /__u/substackcdn.com/image/fetch/$s_!ALpN!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F378db0a9-b127-42d4-baed-f5149b79bd1b_2154x1278.jpeg 1272w, /__u/substackcdn.com/image/fetch/$s_!ALpN!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F378db0a9-b127-42d4-baed-f5149b79bd1b_2154x1278.jpeg 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!ALpN!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F378db0a9-b127-42d4-baed-f5149b79bd1b_2154x1278.jpeg" width="1456" height="864" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/378db0a9-b127-42d4-baed-f5149b79bd1b_2154x1278.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:864,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:74772,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://nader.substack.com/i/189886058?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F378db0a9-b127-42d4-baed-f5149b79bd1b_2154x1278.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!ALpN!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F378db0a9-b127-42d4-baed-f5149b79bd1b_2154x1278.jpeg 424w, /__u/substackcdn.com/image/fetch/$s_!ALpN!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F378db0a9-b127-42d4-baed-f5149b79bd1b_2154x1278.jpeg 848w, /__u/substackcdn.com/image/fetch/$s_!ALpN!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F378db0a9-b127-42d4-baed-f5149b79bd1b_2154x1278.jpeg 1272w, /__u/substackcdn.com/image/fetch/$s_!ALpN!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F378db0a9-b127-42d4-baed-f5149b79bd1b_2154x1278.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Submit it and watch Devin work.</p><h2><strong>The Workspace</strong></h2><p>While Devin works, you can follow along in three panels:</p><p>S<strong>hell: </strong>Devin&#8217;s terminal shows commands being executed: Watch output in real-time or take over and run commands yourself.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!BBsw!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F16934edd-01e0-48dc-a988-2aeece6787a6_2330x1648.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!BBsw!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F16934edd-01e0-48dc-a988-2aeece6787a6_2330x1648.jpeg 424w, /__u/substackcdn.com/image/fetch/$s_!BBsw!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F16934edd-01e0-48dc-a988-2aeece6787a6_2330x1648.jpeg 848w, /__u/substackcdn.com/image/fetch/$s_!BBsw!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F16934edd-01e0-48dc-a988-2aeece6787a6_2330x1648.jpeg 1272w, /__u/substackcdn.com/image/fetch/$s_!BBsw!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F16934edd-01e0-48dc-a988-2aeece6787a6_2330x1648.jpeg 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!BBsw!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F16934edd-01e0-48dc-a988-2aeece6787a6_2330x1648.jpeg" width="1456" height="1030" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/16934edd-01e0-48dc-a988-2aeece6787a6_2330x1648.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1030,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:287793,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://nader.substack.com/i/189886058?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F16934edd-01e0-48dc-a988-2aeece6787a6_2330x1648.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!BBsw!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F16934edd-01e0-48dc-a988-2aeece6787a6_2330x1648.jpeg 424w, /__u/substackcdn.com/image/fetch/$s_!BBsw!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F16934edd-01e0-48dc-a988-2aeece6787a6_2330x1648.jpeg 848w, /__u/substackcdn.com/image/fetch/$s_!BBsw!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F16934edd-01e0-48dc-a988-2aeece6787a6_2330x1648.jpeg 1272w, /__u/substackcdn.com/image/fetch/$s_!BBsw!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F16934edd-01e0-48dc-a988-2aeece6787a6_2330x1648.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>IDE: Full VSCode instance. Devin opens files, makes edits, saves changes. You can click into any file to read or edit alongside it.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!lyIz!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe94b7aa9-2abe-4ad7-ae45-d8d0e35904d4_1788x1650.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!lyIz!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe94b7aa9-2abe-4ad7-ae45-d8d0e35904d4_1788x1650.jpeg 424w, /__u/substackcdn.com/image/fetch/$s_!lyIz!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe94b7aa9-2abe-4ad7-ae45-d8d0e35904d4_1788x1650.jpeg 848w, /__u/substackcdn.com/image/fetch/$s_!lyIz!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe94b7aa9-2abe-4ad7-ae45-d8d0e35904d4_1788x1650.jpeg 1272w, /__u/substackcdn.com/image/fetch/$s_!lyIz!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe94b7aa9-2abe-4ad7-ae45-d8d0e35904d4_1788x1650.jpeg 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!lyIz!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe94b7aa9-2abe-4ad7-ae45-d8d0e35904d4_1788x1650.jpeg" width="1456" height="1344" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e94b7aa9-2abe-4ad7-ae45-d8d0e35904d4_1788x1650.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1344,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:228252,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://nader.substack.com/i/189886058?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe94b7aa9-2abe-4ad7-ae45-d8d0e35904d4_1788x1650.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!lyIz!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe94b7aa9-2abe-4ad7-ae45-d8d0e35904d4_1788x1650.jpeg 424w, /__u/substackcdn.com/image/fetch/$s_!lyIz!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe94b7aa9-2abe-4ad7-ae45-d8d0e35904d4_1788x1650.jpeg 848w, /__u/substackcdn.com/image/fetch/$s_!lyIz!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe94b7aa9-2abe-4ad7-ae45-d8d0e35904d4_1788x1650.jpeg 1272w, /__u/substackcdn.com/image/fetch/$s_!lyIz!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe94b7aa9-2abe-4ad7-ae45-d8d0e35904d4_1788x1650.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><strong>Browser: </strong>Where Devin reads docs, tests web apps, and researches error messages.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!0j8o!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92715794-6835-4e6a-a68c-6fd9ee7d24ce_2412x1642.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!0j8o!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92715794-6835-4e6a-a68c-6fd9ee7d24ce_2412x1642.jpeg 424w, /__u/substackcdn.com/image/fetch/$s_!0j8o!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92715794-6835-4e6a-a68c-6fd9ee7d24ce_2412x1642.jpeg 848w, /__u/substackcdn.com/image/fetch/$s_!0j8o!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92715794-6835-4e6a-a68c-6fd9ee7d24ce_2412x1642.jpeg 1272w, /__u/substackcdn.com/image/fetch/$s_!0j8o!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92715794-6835-4e6a-a68c-6fd9ee7d24ce_2412x1642.jpeg 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!0j8o!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92715794-6835-4e6a-a68c-6fd9ee7d24ce_2412x1642.jpeg" width="1456" height="991" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/92715794-6835-4e6a-a68c-6fd9ee7d24ce_2412x1642.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:991,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:214914,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://nader.substack.com/i/189886058?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92715794-6835-4e6a-a68c-6fd9ee7d24ce_2412x1642.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!0j8o!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92715794-6835-4e6a-a68c-6fd9ee7d24ce_2412x1642.jpeg 424w, /__u/substackcdn.com/image/fetch/$s_!0j8o!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92715794-6835-4e6a-a68c-6fd9ee7d24ce_2412x1642.jpeg 848w, /__u/substackcdn.com/image/fetch/$s_!0j8o!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92715794-6835-4e6a-a68c-6fd9ee7d24ce_2412x1642.jpeg 1272w, /__u/substackcdn.com/image/fetch/$s_!0j8o!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92715794-6835-4e6a-a68c-6fd9ee7d24ce_2412x1642.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2><strong>Example: A Meatier Task</strong></h2><p>Once you&#8217;ve seen the basic loop, here&#8217;s what a real-world session looks like. Say your security scanner flagged 3 CVEs in your dependencies:</p><pre><code><code>[In Slack or Devin web UI]
@devin Fix the CVEs in our Q4 security report:
https://your-security-scanner.com/report/q4-2024

Requirements:
- Upgrade affected packages to patched versions
- Run full test suite to ensure no regressions
- If any CVE can't be fixed (breaking changes), document why
- Open PR with summary</code></code></pre><p>Devin fetches the report, upgrades the affected packages, runs your test suite, and opens a PR.</p><pre><code><code>## CVE Remediation - Q4 2024

Fixed 3 CVEs by upgrading dependencies:

| Package | Old Version | New Version | CVEs Fixed |
| lodash  | 4.17.19     | 4.17.21     | CVE-2021-23337 |
| express | 4.16.0      | 4.18.2      | CVE-2022-24999 |
| axios   | 0.21.0      | 1.6.0       | CVE-2023-45857 |

**Testing:**
- &#9989; All unit tests pass (247/247)
- &#9989; Integration tests pass (42/42)
- &#9989; Type-check clean
- &#9989; Lint clean

**Security Report:** https://your-security-scanner.com/report/q4-2024</code></code></pre><p>The PR is ready for review with all tests passing. Same input/output pattern, bigger task.</p><h2><strong>Knowledge Files</strong></h2><p>Devin has its own <a href="https://docs.devin.ai/product-guides/knowledge">Knowledge file system</a> - the equivalent of AGENTS.md. You define your project&#8217;s architecture, testing conventions, validation requirements, and common gotchas, and it auto-loads into every session for that repo. Configure these during repo setup or in your repo settings.</p><h2><strong>Multi-Entry Points</strong></h2><p><strong>Slack Integration</strong></p><p>Add the Devin Slack app to your workspace. (Settings -&gt; Integrations)</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!VXIH!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1533968-00eb-4e18-862d-fa10fe19dfb6_1200x750.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!VXIH!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1533968-00eb-4e18-862d-fa10fe19dfb6_1200x750.jpeg 424w, /__u/substackcdn.com/image/fetch/$s_!VXIH!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1533968-00eb-4e18-862d-fa10fe19dfb6_1200x750.jpeg 848w, /__u/substackcdn.com/image/fetch/$s_!VXIH!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1533968-00eb-4e18-862d-fa10fe19dfb6_1200x750.jpeg 1272w, /__u/substackcdn.com/image/fetch/$s_!VXIH!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1533968-00eb-4e18-862d-fa10fe19dfb6_1200x750.jpeg 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!VXIH!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1533968-00eb-4e18-862d-fa10fe19dfb6_1200x750.jpeg" width="1200" height="750" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b1533968-00eb-4e18-862d-fa10fe19dfb6_1200x750.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:750,&quot;width&quot;:1200,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:46677,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://nader.substack.com/i/189886058?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1533968-00eb-4e18-862d-fa10fe19dfb6_1200x750.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!VXIH!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1533968-00eb-4e18-862d-fa10fe19dfb6_1200x750.jpeg 424w, /__u/substackcdn.com/image/fetch/$s_!VXIH!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1533968-00eb-4e18-862d-fa10fe19dfb6_1200x750.jpeg 848w, /__u/substackcdn.com/image/fetch/$s_!VXIH!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1533968-00eb-4e18-862d-fa10fe19dfb6_1200x750.jpeg 1272w, /__u/substackcdn.com/image/fetch/$s_!VXIH!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1533968-00eb-4e18-862d-fa10fe19dfb6_1200x750.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Then:</p><pre><code><code>[In #engineering channel]
@devin Fix the type errors in src/api/routes.ts

[In #bug-reports channel]
@devin The login button is misaligned on mobile.
Screenshot: [attached]

[In DMs with Devin]
Start a session to upgrade all our dependencies to their latest stable versions</code></code></pre><p>Devin responds in-thread, updates as it works, and posts the PR when done. All of this work is done in parallel.</p><p><strong>Linear/Jira Integration </strong>(Settings -&gt; Integrations)</p><p>There are three ways to start a Devin session from a Linear ticket:</p><p><strong>Assign Devin to a ticket<a href="https://docs.devin.ai/integrations/linear#assign-devin-to-a-ticket">&#8203;</a> - </strong>assign the ticket to Devin directly in Linear. Devin will use the <strong>default playbook</strong> configured in your <a href="https://app.devin.ai/settings/integrations/linear">Linear integration settings</a> to start working on the ticket.</p><p><strong>Add a playbook label - </strong>add a playbook label (e.g. <strong>!plan</strong>, <strong>!implement</strong>, <strong>!triage</strong>, <strong>!review</strong>) to the ticket. Devin will start a session using the specific playbook that matches the label. These labels are synced from your configured <strong>synced playbook labels</strong> in the integration settings.</p><p><strong><a href="https://x.com/@mention">@mention</a> Devin in a comment - </strong>mention Devin in a ticket comment with specific instructions. Devin will start a session and use your comment as the task instruction, without applying a playbook.</p><p>Example: a ticket titled &#8220;API returns 500 on /users when query param is malformed&#8221; gets assigned to Devin. Devin reads the ticket context, fixes the validation, and opens a PR.</p><p><strong>API for Automation</strong></p><p>Hook Devin into infrastructure via the API:</p><pre><code><code>import devin

# Trigger on CI failure
@app.post("/ci/webhook")
def handle_ci_failure(payload):
    if payload.status == "failed":
        devin.start_session(
            prompt=f"Fix CI failure in {payload.commit_sha}: {payload.logs}",
            repo=payload.repo,
            branch=payload.branch
        )</code></code></pre><h1><strong>Scaling Your Agent Army</strong></h1><h2><strong>Running Multiple Devins in Parallel</strong></h2><pre><code><code>[Monday morning]
@devin Upgrade React to v19 across all frontend packages
@devin Add comprehensive tests to src/billing
@devin Fix all TypeScript strict mode violations
@devin Migrate from Jest to Vitest
@devin Update API documentation to match current endpoints

[Check back at lunch]
5 PRs ready for review.</code></code></pre><p>Throw more tasks at Devin and they automatically run in parallel. Each Devin runs in its own isolated environment (git branch, dev server, test database, shell).</p><p>At Cognition, engineers even routinely run 5-10 Devins simultaneously during a single feature development:</p><ul><li><p>Main session: building the new feature</p></li><li><p>Background sessions: test coverage, documentation, migrations, related cleanup</p></li></ul><h2><strong>The Review Bottleneck</strong></h2><p>More PRs means more review work. <a href="https://app.devin.ai/review">Devin Review</a> absorbs that.</p><p><strong>What Devin Review Does</strong></p><p>For every PR Devin creates, it also generates an intelligent review:</p><ol><li><p><strong>Smart diff organization:</strong> Groups related changes together instead of alphabetical order</p></li></ol><ul><li><p>All type changes in one section</p></li><li><p>API changes in another</p></li><li><p>Test updates grouped by test file</p></li></ul><p>2. <strong>Copy/move detection</strong>: Shows &#8220;File moved from X to Y&#8221; instead of full delete + insert</p><p>3. <strong>Bug detection</strong>: Automatically flags potential issues with confidence levels (severe, likely, possible)</p><p>4. <strong>Codebase-aware chat</strong>: Ask questions about the PR with full context</p><h2><strong>The Auto-Review Loop + Autofix</strong></h2><p>Enable Auto-Review for your repo and add <code>devin-ai-integration[bot]</code> to your <a href="https://app.devin.ai/customization">bot comment allowlist</a> (under <strong>Settings &gt; Customization &gt; Pull Request Settings &gt; Bot comment settings</strong>, select &#8220;<strong>Respond to specific bots only&#8221;</strong>). Now:</p><ol><li><p>Devin opens a PR</p></li><li><p>Devin Review automatically analyzes it</p></li><li><p>If bugs found, Devin Review leaves comments</p></li><li><p><strong>Original Devin reads the comments and fixes the issues automatically</strong></p></li><li><p>Repeat until clean</p></li><li><p>Human reviews the final, polished PR</p></li></ol><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!dAgc!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faee89016-f34c-4cb9-8745-19e22b73097f_1878x1204.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!dAgc!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faee89016-f34c-4cb9-8745-19e22b73097f_1878x1204.jpeg 424w, /__u/substackcdn.com/image/fetch/$s_!dAgc!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faee89016-f34c-4cb9-8745-19e22b73097f_1878x1204.jpeg 848w, /__u/substackcdn.com/image/fetch/$s_!dAgc!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faee89016-f34c-4cb9-8745-19e22b73097f_1878x1204.jpeg 1272w, /__u/substackcdn.com/image/fetch/$s_!dAgc!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faee89016-f34c-4cb9-8745-19e22b73097f_1878x1204.jpeg 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!dAgc!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faee89016-f34c-4cb9-8745-19e22b73097f_1878x1204.jpeg" width="1456" height="933" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/aee89016-f34c-4cb9-8745-19e22b73097f_1878x1204.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:933,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:143262,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://nader.substack.com/i/189886058?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faee89016-f34c-4cb9-8745-19e22b73097f_1878x1204.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!dAgc!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faee89016-f34c-4cb9-8745-19e22b73097f_1878x1204.jpeg 424w, /__u/substackcdn.com/image/fetch/$s_!dAgc!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faee89016-f34c-4cb9-8745-19e22b73097f_1878x1204.jpeg 848w, /__u/substackcdn.com/image/fetch/$s_!dAgc!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faee89016-f34c-4cb9-8745-19e22b73097f_1878x1204.jpeg 1272w, /__u/substackcdn.com/image/fetch/$s_!dAgc!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faee89016-f34c-4cb9-8745-19e22b73097f_1878x1204.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Devin Review works on any GitHub PR, and other bots work the same way, just add your bot to the allowlist.</p><p>Beyond that, you can add your CI bot and Devin will auto-fix lint failures and build errors flagged by your pipeline. Add a security scanner bot and Devin addresses vulnerability reports directly. Start with the allowlist approach and add bots one at a time so you can verify each interaction before expanding.</p><h2><strong>Knowledge Management at Org Scale</strong></h2><ol><li><p><strong><a href="https://docs.devin.ai/work-with-devin/deepwiki">DeepWiki</a></strong> automatically indexes your repos and generates:</p></li></ol><ul><li><p>Architecture diagrams (what depends on what)</p></li><li><p>Module summaries (what each directory does)</p></li><li><p>Data flow diagrams (how data moves through the system)</p></li></ul><p>For public repos, visit https://deepwiki.com/ to see am auto-generated wiki.</p><p>For private repos, DeepWiki runs automatically when you add the repo to Devin.</p><p><strong>2. <a href="https://docs.devin.ai/work-with-devin/ask-devin">Ask Devin</a> - e</strong>xplore the codebase before starting a session:</p><pre><code><code>@Devin: Where does authentication happen in the codebase?

Devin: Authentication is handled in three places:
1. src/server/middleware/auth.ts - JWT validation
2. src/lib/auth-client.ts - Client-side auth state
3. src/app/api/auth/route.ts - Auth API endpoints

Would you like me to start a session to work on authentication?</code></code></pre><p>When starting a session from this point, Devin begins with full context. Use for scoping work, understanding architecture, or finding examples.</p><p><strong>3. MCP Integration</strong></p><p>Devin connects to <a href="https://docs.devin.ai/integrations/overview">hundreds of tools via MCP</a>. Common Integrations include:</p><p><strong>Observability:</strong> Sentry, Datadog, Vercel, internal APIs.</p><p>Example:</p><pre><code><code>@devin Investigate the spike in errors on /api/checkout
Use Sentry to see the stack traces</code></code></pre><p><strong>Databases:</strong> Postgres, Snowflake, BigQuery, Redshift</p><p><strong>Productivity: </strong>Notion, Linear/Jira, Slack, Google Drive</p><p><strong>Custom MCPs: b</strong>uild your own MCP for internal tools.</p><p><strong>Data Analyst Agent (DANA)</strong></p><p>Devin also ships a specialized <a href="https://docs.devin.ai/work-with-devin/data-analyst#data-analyst-agent">Data Analyst agent</a>. DANA connects directly to your databases via MCP (Postgres, Snowflake, BigQuery, Redshift), writes and executes SQL, and returns metrics and visualizations in seconds. Trigger it from Slack (/dana or</p><p><a href="https://x.com/@Devin">@Devin</a> !dana), the web app, or the API.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!2wij!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F103311ff-a69f-413a-a9a2-fdb14347f768_1920x900.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!2wij!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F103311ff-a69f-413a-a9a2-fdb14347f768_1920x900.jpeg 424w, /__u/substackcdn.com/image/fetch/$s_!2wij!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F103311ff-a69f-413a-a9a2-fdb14347f768_1920x900.jpeg 848w, /__u/substackcdn.com/image/fetch/$s_!2wij!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F103311ff-a69f-413a-a9a2-fdb14347f768_1920x900.jpeg 1272w, /__u/substackcdn.com/image/fetch/$s_!2wij!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F103311ff-a69f-413a-a9a2-fdb14347f768_1920x900.jpeg 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!2wij!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F103311ff-a69f-413a-a9a2-fdb14347f768_1920x900.jpeg" width="1456" height="683" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/103311ff-a69f-413a-a9a2-fdb14347f768_1920x900.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:683,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:120526,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://nader.substack.com/i/189886058?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F103311ff-a69f-413a-a9a2-fdb14347f768_1920x900.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!2wij!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F103311ff-a69f-413a-a9a2-fdb14347f768_1920x900.jpeg 424w, /__u/substackcdn.com/image/fetch/$s_!2wij!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F103311ff-a69f-413a-a9a2-fdb14347f768_1920x900.jpeg 848w, /__u/substackcdn.com/image/fetch/$s_!2wij!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F103311ff-a69f-413a-a9a2-fdb14347f768_1920x900.jpeg 1272w, /__u/substackcdn.com/image/fetch/$s_!2wij!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F103311ff-a69f-413a-a9a2-fdb14347f768_1920x900.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>DANA persists schema knowledge across sessions, so queries get faster over time. Purpose-built for data work; for code changes, use standard Devin.</p><h2><strong>Fleet Management</strong></h2><p><strong>Session Insights</strong></p><p>After each session, Devin generates <strong>Session Insights</strong>:</p><pre><code><code>## Session Analysis: Migrate to React 19

**What Worked:**
- Automated dependency upgrade
- Comprehensive test suite caught breaking changes
- Incremental PR approach (one package at a time)

**What Slowed Us Down:**
- Type errors in third-party libraries (needed manual patches)
- Test flakes in E2E suite (unrelated to React upgrade)

**Recommended Improvements:**
1. Add pre-check for third-party type issues before upgrading
2. Fix E2E test flakes separately (they're blocking progress)
3. Consider automated dependency upgrades weekly (not quarterly)

**Improved Prompt for Next Time:**
"Upgrade React to latest version. Check for breaking changes in
@types packages first. Skip E2E tests initially (they're flaky).
Run E2E after all other tests pass."</code></code></pre><p>Use these insights to:</p><ul><li><p>Refine your Playbooks</p></li><li><p>Improve Knowledge files</p></li><li><p>Train your team on what works</p></li></ul><p><strong>Analytics Dashboard</strong></p><p>Track usage across your org:</p><ul><li><p>Sessions per week</p></li><li><p>Success rate (% that result in merged PRs)</p></li><li><p>Time saved (estimated)</p></li><li><p>Most common tasks</p></li><li><p>Most common failures</p></li></ul><p>Use this to:</p><ul><li><p>Identify bottlenecks (&#8221;Devin struggles with X, let&#8217;s add a Playbook&#8221;)</p></li><li><p>Measure ROI (&#8221;We&#8217;re saving 40 hours/week on migrations&#8221;)</p></li><li><p>Guide investment (&#8221;Should we focus on better test coverage or faster CI?&#8221;)</p></li></ul><p><strong>Playbooks</strong></p><p>Once you spot the patterns (CVE remediation, test coverage, database migrations, framework upgrades, lint cleanup), capture them as Playbooks instead of rewriting instructions each time.</p><p><strong>Example: Test Coverage Playbook</strong></p><pre><code><code>1. **Analyze existing code:**
   - Read all files in the specified module
   - Identify public functions and classes
   - Check for existing tests

2. **Identify coverage gaps:**
   - Run coverage report: `npm run test:coverage -- &lt;module_path&gt;`
   - List functions with &lt;80% coverage

3. **Write tests:**
   - Create test file in `__tests__` directory (if doesn't exist)
   - Write unit tests for uncovered functions
   - Follow existing test patterns in the codebase
   - Include edge cases (null, undefined, empty arrays, etc.)

4. **Validate:**
   - Run tests: `npm test`
   - Check coverage: `npm run test:coverage -- &lt;module_path&gt;`
   - Ensure &gt;80% coverage achieved

5. **Document:**
   - Add JSDoc comments to tested functions (if missing)
   - Update test README if one exists

## Postconditions
- All tests pass
- Module coverage &gt;80%
- No lint errors
- No type errors

## Forbidden Actions
- Don't modify the implementation to make tests pass (tests should pass as-is)
- Don't skip edge case testing to hit coverage numbers
- Don't disable linter rules

## Example Invocation
"@devin Use the Test Coverage Playbook on src/lib/billing"</code></code></pre><p>Save this Playbook. Next time:</p><pre><code><code>@devin Use the Test Coverage Playbook on src/lib/payments</code></code></pre><h2><strong>Scheduled Sessions</strong></h2><p>Some tasks shouldn&#8217;t need a human to remember them. Dependency updates, standup reports, lint cleanup, coverage checks: these should just happen.</p><p>Scheduled Sessions let you set a Devin session to run on a cron schedule (hourly, daily, weekly, or custom) or as a one-time run at a specific time. Attach a Playbook and it follows the same process every time.<strong> </strong></p><div class="native-video-embed" data-component-name="VideoPlaceholder" data-attrs="{&quot;mediaUploadId&quot;:&quot;3355c87d-b73a-4120-b634-4d00066dec2f&quot;,&quot;duration&quot;:null}"></div><p>Each schedule lets you configure:</p><ul><li><p>The prompt Devin follows</p></li><li><p>Which agent runs it (standard Devin, Data Analyst, or Advanced)</p></li><li><p>A Playbook (optional)</p></li><li><p>Email notifications (always, on failure only, or never)</p></li></ul><p>Example setups:</p><ul><li><p>Weekly dependency updates every Monday at 9 AM UTC</p></li><li><p>Daily standup summary of merged PRs and open issues</p></li><li><p>Nightly lint and type-check sweep across all repos</p></li></ul><p>All past sessions are logged under each schedule, so you can audit what ran, when, and whether it worked.</p><h2><strong>Advanced Mode - Batch Sessions</strong></h2><p>Same task across 50 repos? You don&#8217;t need to start sessions one at a time. </p><p>Describe the work once. Devin proposes individual sessions for you to review before launching:</p><pre><code><code>Upgrade every repo in this CSV to Java 25

Read through the auth PRD using the Notion MCP and propose independent Devin sessions to implement this feature

Start a Devin session using the selected playbook for each repo in the attached CSV
</code></code></pre><p>Attach a Playbook so every session follows the same process. We&#8217;ve seen teams batch 10-100 repos in a single run this way.</p><p>Your first session takes 10 minutes: <a href="https://app.devin.ai/">app.devin.ai</a></p><blockquote><p>Additional reading: <a href="https://x.com/dabit3/status/2020564900834111518">Cloud Agent Thesis</a>, <a href="https://x.com/mntruell/status/2026736314272591924">The Third era of AI Software Development</a>, <a href="https://background-agents.com/">background-agents.com</a>.</p></blockquote>]]></content:encoded></item><item><title><![CDATA[Agentic Slack]]></title><description><![CDATA[Originally posted on X.]]></description><link>https://nader.substack.com/p/agentic-slack</link><guid isPermaLink="false">https://nader.substack.com/p/agentic-slack</guid><dc:creator><![CDATA[Nader Dabit]]></dc:creator><pubDate>Tue, 24 Feb 2026 20:03:33 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!ZyRr!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7c66df76-557c-4523-9db0-ac13691b8f97_3264x1312.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote><p>Originally <a href="https://x.com/twitter/status/2026385925593510302">posted on X</a>.</p></blockquote><p>The terminal is where most coding agents live, but it&#8217;s also where they get stuck. </p><p>Terminals and even IDEs are Single-player, single-context, disconnected from the rest of the team. Powerful for one engineer. Inaccessible to everyone else.</p><h3>Put the agent where the team already is </h3><p>The best interface for a coding agent is the one your team already uses. Every team already uses Slack.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!ZyRr!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7c66df76-557c-4523-9db0-ac13691b8f97_3264x1312.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!ZyRr!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7c66df76-557c-4523-9db0-ac13691b8f97_3264x1312.png 424w, /__u/substackcdn.com/image/fetch/$s_!ZyRr!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7c66df76-557c-4523-9db0-ac13691b8f97_3264x1312.png 848w, /__u/substackcdn.com/image/fetch/$s_!ZyRr!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7c66df76-557c-4523-9db0-ac13691b8f97_3264x1312.png 1272w, /__u/substackcdn.com/image/fetch/$s_!ZyRr!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7c66df76-557c-4523-9db0-ac13691b8f97_3264x1312.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!ZyRr!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7c66df76-557c-4523-9db0-ac13691b8f97_3264x1312.png" width="1456" height="585" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/7c66df76-557c-4523-9db0-ac13691b8f97_3264x1312.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:585,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:5306972,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://nader.substack.com/i/189059336?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7c66df76-557c-4523-9db0-ac13691b8f97_3264x1312.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!ZyRr!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7c66df76-557c-4523-9db0-ac13691b8f97_3264x1312.png 424w, /__u/substackcdn.com/image/fetch/$s_!ZyRr!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7c66df76-557c-4523-9db0-ac13691b8f97_3264x1312.png 848w, /__u/substackcdn.com/image/fetch/$s_!ZyRr!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7c66df76-557c-4523-9db0-ac13691b8f97_3264x1312.png 1272w, /__u/substackcdn.com/image/fetch/$s_!ZyRr!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7c66df76-557c-4523-9db0-ac13691b8f97_3264x1312.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>A designer can report a broken layout, a support engineer can flag a typo, a PM can request a copy change, and none of them need Git installed or CLI syntax. They&#8217;re already in Slack, and the agent is already there too.</p><p>Tag the agent in a channel, describe the fix, and walk away. A cloud agent doesn&#8217;t need your terminal. It has its own environment, connects to your repos, and ships the PR independently.</p><p>Literally everyone in the organization has the ability to contribute.</p><h3>The power of async is quietly being realized </h3><p>Terminal and IDE sessions are also synchronous. You&#8217;re watching, steering, approving. This is right for complex architectural work where your judgment is the bottleneck.</p><p>But most engineering work isn&#8217;t that. Bug fixes. Minor enhancements. Test coverage. Documentation. Dependency updates. The bottleneck isn&#8217;t figuring out what to do. It&#8217;s the ceremony around shipping it.</p><p>Slack makes these tasks fire-and-forget. Describe ten fixes in ten messages. Come back to ten PRs. The constraint shifts from engineer hours to review capacity.</p><p>This is already becoming the default. First quietly, <a href="https://stripe.dev/blog/minions-stripes-one-shot-end-to-end-coding-agents">now obviously</a>. The teams that ship the most are treating agents like teammates, not tools.</p><h3>Slack is truly multiplayer</h3><p>A terminal knows about you. Slack knows about your team.</p><p>Threads have history. When someone asks &#8220;can we also handle the edge case mentioned yesterday,&#8221; the agent can see what was mentioned yesterday. The organizational graph is already there.</p><p>The coding agent stops being a tool one person uses. It becomes a teammate everyone can talk to.</p><h3>Why you don&#8217;t go back</h3><p>Once you&#8217;ve tagged an agent in Slack and come back to a ready and polished PR, opening a terminal feels like sending a fax.</p><p>The work happens where the team already is. Once you experience this, you won&#8217;t want to go back.</p><blockquote><p>More on cloud agents <a href="https://x.com/dabit3/status/2020564900834111518?s=20">here</a>. To see how this looks in action, <a href="https://x.com/dabit3/status/2025936695661826481?s=20">watch this video</a>.</p></blockquote>]]></content:encoded></item><item><title><![CDATA[How I'm thinking about the next few years]]></title><description><![CDATA[I've been thinking (and worrying) a lot about how AI will affect things, and I've drawn some conclusions that will drive my own career and thought process over the next few years and I thought I'd share.]]></description><link>https://nader.substack.com/p/how-im-thinking-about-the-next-few</link><guid isPermaLink="false">https://nader.substack.com/p/how-im-thinking-about-the-next-few</guid><dc:creator><![CDATA[Nader Dabit]]></dc:creator><pubDate>Mon, 23 Feb 2026 01:19:22 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!aQ6n!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd40f5972-2f71-4c94-860c-5648e16ba9f7_1024x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I've been thinking (and worrying) a lot about how AI will affect things, and I've drawn some conclusions that will drive my own career and thought process over the next few years and I thought I'd share.<br><br>Humans have always been orchestrators, of knowledge and ideas and tools, we learn a bunch of things and interconnect them to do new or more powerful or valuable things.<br><br>Technology has always advanced and the new abstractions usually just make us more powerful *as orchestrators* (important).<br><br>I've realized is that the orchestration layer has changed a lot with AI. You obviously can't just be a C++ or TypeScript orchestrator (coder) anymore and expect to be around a few years from now, you need to move up and learn more / new skills that can be composed, and if you do you can be even more valuable than you were in the past.<br><br>And orchestration has a lot to do with creating. <br><br>Creativity isn't about conjuring something from nothing, it's more like being a receiver or antenna, picking up on what's already floating in the collective atmosphere and filtering it through your unique perspective (maybe this is essentially refining taste?).<br><br>The way to increase inputs to our brain is to learn more, to widen the breadth of what we're learning, and importantly to do it consistently.<br><br>People talk about taste and agency, but I think the people who will win over the next few decades will simply be the most avid learners.<br><br>This is already true but it feels like it's just speeding up.<br><br>I'll end with one of my favorite quotes that seems more and more true every day: "The illiterate of the 21st century will not be those who cannot read and write, but those who cannot learn, unlearn, and relearn."</p>]]></content:encoded></item><item><title><![CDATA[How to Build a Custom Agent Framework with PI: The Agent Stack Powering OpenClaw]]></title><description><![CDATA[Originally posted on X. This tutorial involves a lot of code and is therefore also available as markdown here. See more of my writing here.]]></description><link>https://nader.substack.com/p/how-to-build-a-custom-agent-framework</link><guid isPermaLink="false">https://nader.substack.com/p/how-to-build-a-custom-agent-framework</guid><dc:creator><![CDATA[Nader Dabit]]></dc:creator><pubDate>Wed, 18 Feb 2026 05:34:03 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!Vhfr!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c034dd1-4395-46e6-a6f1-38df0d3ab50d_3264x1312.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote><p><a href="https://x.com/dabit3/status/2023986942300627132?s=20">Originally posted on X</a>. This tutorial involves a lot of code and is therefore also available as markdown <a href="https://gist.github.com/dabit3/e97dbfe71298b1df4d36542aceb5f158">here</a>. See more of my writing <a href="https://x.com/dabit3/articles">here</a>.</p></blockquote><p>PI is a TypeScript toolkit for building AI agents. It&#8217;s a monorepo of packages that layer on top of each other: <code>pi-ai</code> handles LLM communication across providers, <code>pi-agent-core</code> adds the agent loop with tool calling, <code>pi-coding-agent</code> gives you a full coding agent with built-in tools, session persistence, and extensibility, and <code>pi-tui</code> provides a terminal UI for building CLI interfaces.</p><p>These are the same packages that power <a href="https://github.com/openclaw/openclaw">OpenClaw</a>. This guide walks through each layer, progressively building up to a fully featured coding assistant with a terminal UI, session persistence, and custom tools.</p><p>By understanding how to compose these layers, you can build production-grade agentic software on your own terms, without being locked into a specific abstraction.</p><blockquote><p>Pi was created by <a href="https://x.com/badlogicgames">@badlogicgames</a>. <a href="https://mariozechner.at/posts/2025-11-30-pi-coding-agent/">This is a great writeup from him</a> that explains some of the design decisions made when creating it.</p></blockquote><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!Vhfr!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c034dd1-4395-46e6-a6f1-38df0d3ab50d_3264x1312.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!Vhfr!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c034dd1-4395-46e6-a6f1-38df0d3ab50d_3264x1312.png 424w, /__u/substackcdn.com/image/fetch/$s_!Vhfr!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c034dd1-4395-46e6-a6f1-38df0d3ab50d_3264x1312.png 848w, /__u/substackcdn.com/image/fetch/$s_!Vhfr!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c034dd1-4395-46e6-a6f1-38df0d3ab50d_3264x1312.png 1272w, /__u/substackcdn.com/image/fetch/$s_!Vhfr!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c034dd1-4395-46e6-a6f1-38df0d3ab50d_3264x1312.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!Vhfr!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c034dd1-4395-46e6-a6f1-38df0d3ab50d_3264x1312.png" width="1456" height="585" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5c034dd1-4395-46e6-a6f1-38df0d3ab50d_3264x1312.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:585,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:5836980,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://nader.substack.com/i/188346796?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c034dd1-4395-46e6-a6f1-38df0d3ab50d_3264x1312.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!Vhfr!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c034dd1-4395-46e6-a6f1-38df0d3ab50d_3264x1312.png 424w, /__u/substackcdn.com/image/fetch/$s_!Vhfr!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c034dd1-4395-46e6-a6f1-38df0d3ab50d_3264x1312.png 848w, /__u/substackcdn.com/image/fetch/$s_!Vhfr!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c034dd1-4395-46e6-a6f1-38df0d3ab50d_3264x1312.png 1272w, /__u/substackcdn.com/image/fetch/$s_!Vhfr!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c034dd1-4395-46e6-a6f1-38df0d3ab50d_3264x1312.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>The stack</h2><pre><code><code>&#9484;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9488;
&#9474;  Your Application                       &#9474;
&#9474;  (OpenClaw, a CLI tool, a Slack bot)    &#9474;
&#9500;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9516;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9508;
&#9474;  pi-coding-agent   &#9474;  pi-tui            &#9474;
&#9474;  Sessions, tools,  &#9474;  Terminal UI,      &#9474;
&#9474;  extensions        &#9474;  markdown, editor  &#9474;
&#9500;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9524;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9508;
&#9474;  pi-agent-core                          &#9474;
&#9474;  Agent loop, tool execution, events     &#9474;
&#9500;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9508;
&#9474;  pi-ai                                  &#9474;
&#9474;  Streaming, models, multi-provider LLM  &#9474;
&#9492;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9496;</code></code></pre><p>Each layer adds capability. Use as much or as little as you need.</p><ul><li><p><code>pi-ai</code> - Call any LLM through one interface. Anthropic, OpenAI, Google, Bedrock, Mistral, Groq, xAI, OpenRouter, Ollama, and more. Streaming, completions, tool definitions, cost tracking.</p></li><li><p><code>pi-agent-core</code> - Wraps pi-ai into an agent loop. You define tools, the agent calls the LLM, executes tools, feeds results back, and repeats until done.</p></li><li><p><code>pi-coding-agent</code> - The full agent runtime. Built-in file tools (read, write, edit, bash), JSONL session persistence, context compaction, skills, and an extension system.</p></li><li><p><code>pi-tui</code> - Terminal UI library with differential rendering. Markdown display, multi-line editor with autocomplete, loading spinners, and flicker-free screen updates.</p></li></ul><h2>Prerequisites</h2><ul><li><p>Node.js 20+</p></li><li><p>An API key from at least one provider (Anthropic, OpenAI, Google, etc.)</p></li></ul><h2>Setup</h2><pre><code><code>mkdir pi-agent &amp;&amp; cd pi-agent
npm init -y
npm install @mariozechner/pi-ai @mariozechner/pi-agent-core @mariozechner/pi-coding-agent @mariozechner/pi-tui chalk
npm install -D typescript @types/node tsx</code></code></pre><p>Set your API key:</p><pre><code><code>export ANTHROPIC_API_KEY=sk-ant-...
# or
export OPENAI_API_KEY=sk-...</code></code></pre><div><hr></div><h2>Layer 1: pi-ai</h2><h3>Your first LLM call</h3><p>Create <code>basics.ts</code>:</p><pre><code><code>import { getModel, completeSimple } from "@mariozechner/pi-ai";

async function main() {
  const model = getModel("anthropic", "claude-opus-4-5");

  const response = await completeSimple(model, {
    systemPrompt: "You are a helpful assistant.",
    messages: [
      { role: "user", content: "What is the capital of France?", timestamp: Date.now() }
    ],
  });

  // response is an AssistantMessage
  for (const block of response.content) {
    if (block.type === "text") {
      console.log(block.text);
    }
  }

  console.log(`\nTokens: ${response.usage.totalTokens}`);
  console.log(`Stop reason: ${response.stopReason}`);
}

main();</code></code></pre><p>Run it:</p><pre><code><code>npx tsx basics.ts</code></code></pre><p><code>getModel</code> looks up a model by provider and ID from PI&#8217;s built-in catalog of 2000+ models. <code>completeSimple</code> sends the messages and returns the full <code>AssistantMessage</code> when the model is done.</p><p>The response has a <code>.content</code> array of typed blocks - <code>text</code>, <code>thinking</code>, or <code>toolCall</code> - plus <code>.usage</code> for token counts and <code>.stopReason</code> for why the model stopped (<code>"stop"</code>, <code>"toolUse"</code>, <code>"length"</code>, <code>"error"</code>, <code>"aborted"</code>).</p><h3>Streaming</h3><p><code>completeSimple</code> waits for the full response. For real-time output, use <code>streamSimple</code>:</p><pre><code><code>import { getModel, streamSimple } from "@mariozechner/pi-ai";

async function main() {
  const model = getModel("anthropic", "claude-opus-4-5");

  const stream = streamSimple(model, {
    systemPrompt: "You are a helpful assistant.",
    messages: [
      { role: "user", content: "Explain how TCP works in 3 sentences.", timestamp: Date.now() }
    ],
  });

  for await (const event of stream) {
    switch (event.type) {
      case "text_delta":
        process.stdout.write(event.delta);
        break;
      case "done":
        console.log(`\n\nTokens: ${event.message.usage.totalTokens}`);
        break;
      case "error":
        console.error("Error:", event.error.errorMessage);
        break;
    }
  }
}

main();</code></code></pre><p>Every provider has its own streaming format - Anthropic, OpenAI, and Google all do it differently. <code>streamSimple</code> normalizes them into a single set of events: <code>start</code>, <code>text_start</code>, <code>text_delta</code>, <code>text_end</code>, <code>thinking_start/delta/end</code>, <code>toolcall_start/delta/end</code>, <code>done</code>, and <code>error</code>.</p><p>Write your streaming handler once, and it works with any provider. For most use cases, you only care about <code>text_delta</code> (the text chunks) and <code>done</code> (the final message).</p><p>You can also await the final message directly:</p><pre><code><code>const stream = streamSimple(model, context);
const finalMessage = await stream.result(); // AssistantMessage</code></code></pre><h3>Switching providers</h3><p>Switch to a different provider by changing the <code>getModel</code> call. The rest of your code stays the same.</p><pre><code><code>// Just change this line - everything else stays the same
const model = getModel("anthropic", "claude-opus-4-5");
// const model = getModel("openai", "gpt-4o");
// const model = getModel("google", "gemini-2.5-pro");
// const model = getModel("groq", "llama-3.3-70b-versatile");

const stream = streamSimple(model, context);</code></code></pre><p>Each provider needs its own API key set in the environment (<code>ANTHROPIC_API_KEY</code>, <code>OPENAI_API_KEY</code>, <code>GEMINI_API_KEY</code>, <code>GROQ_API_KEY</code>, etc.).</p><p>You can also define custom models for self-hosted endpoints:</p><pre><code><code>import type { Model } from "@mariozechner/pi-ai";

const localModel: Model&lt;"openai-completions"&gt; = {
  id: "llama-3.1-8b",
  name: "llama-3.1-8b",
  api: "openai-completions",
  provider: "ollama",
  baseUrl: "http://localhost:11434/v1",
  reasoning: false,
  input: ["text"],
  cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
  contextWindow: 128000,
  maxTokens: 8192,
};</code></code></pre><p>Under the hood, pi-ai uses the official provider SDKs (OpenAI SDK, Anthropic SDK, etc.). The <code>api</code> field determines which SDK handles the request - <code>"openai-completions"</code> routes through the OpenAI SDK, which is why it works with any OpenAI-compatible endpoint (Ollama, vLLM, Mistral, etc.).</p><p>API keys are resolved automatically from environment variables by provider name (<code>OPENAI_API_KEY</code>, <code>ANTHROPIC_API_KEY</code>, etc.) and passed to the SDK, which handles authentication. Ollama doesn&#8217;t require auth, so the example above works as-is. For a provider that needs a key, either set the matching environment variable or pass it directly:</p><pre><code><code>const stream = streamSimple(localModel, context, {
  apiKey: "your-api-key",
});</code></code></pre><h3>Thinking levels</h3><p>Models that support extended thinking (Claude, o3, Gemini 2.5) can be enabled via the <code>reasoning</code> option. It&#8217;s off by default.</p><pre><code><code>const stream = streamSimple(model, context, {
  reasoning: "high", // "minimal" | "low" | "medium" | "high" | "xhigh"
});</code></code></pre><p>When enabled, the stream emits <code>thinking_delta</code> events alongside <code>text_delta</code>.</p><div><hr></div><h2>Layer 2: pi-agent-core</h2><p>pi-ai lets you talk to LLMs. pi-agent-core lets the LLM talk back - with tools.</p><p>The <code>Agent</code> class runs the standard agent loop: send messages to the LLM, execute any tool calls it makes, feed results back, repeat until the model stops.</p><h3>Defining tools</h3><p>Tools use <a href="https://github.com/sinclairzx81/typebox">TypeBox</a> schemas for type-safe parameter definitions:</p><pre><code><code>import { Type } from "@mariozechner/pi-ai";
import type { AgentTool } from "@mariozechner/pi-agent-core";

const weatherParams = Type.Object({
  city: Type.String({ description: "City name" }),
});

const weatherTool: AgentTool&lt;typeof weatherParams&gt; = {
  name: "get_weather",
  label: "Weather",
  description: "Get the current weather for a city",
  parameters: weatherParams,
  execute: async (toolCallId, params, signal, onUpdate) =&gt; {
    // params is typed: { city: string }
    const temp = Math.round(Math.random() * 30);
    return {
      content: [{ type: "text", text: `${params.city}: ${temp}C, partly cloudy` }],
      details: { temp, city: params.city },
    };
  },
};</code></code></pre><p>Define the schema as a standalone variable and pass it as the generic parameter to <code>AgentTool&lt;typeof schema&gt;</code> - this gives TypeScript the type information it needs to infer <code>params</code> correctly inside <code>execute</code>.</p><p>Every tool has:</p><ul><li><p><code>name</code> - identifier the LLM uses to call it</p></li><li><p><code>label</code> - human-readable display name</p></li><li><p><code>description</code> - tells the LLM when and how to use the tool</p></li><li><p><code>parameters</code> - TypeBox schema; validated with AJV before execution</p></li><li><p><code>execute</code> - runs when the LLM calls the tool; returns <code>content</code> (sent back to the LLM) and <code>details</code> (for your UI, not sent to the LLM)</p></li></ul><p>The <code>onUpdate</code> callback lets you stream partial results during execution - useful for long-running tools like bash commands.</p><h3>Creating an agent</h3><p>Wire up the weather tool from above with a model and a streaming function. We&#8217;ll add event handling, prompting, and a full working example in the sections that follow.</p><pre><code><code>import { Agent } from "@mariozechner/pi-agent-core";
import { getModel, streamSimple } from "@mariozechner/pi-ai";

const model = getModel("anthropic", "claude-opus-4-5");

const agent = new Agent({
  initialState: {
    systemPrompt: "You are a helpful assistant with access to tools.",
    model,
    tools: [weatherTool],
    thinkingLevel: "off",
  },
  streamFn: streamSimple,
});</code></code></pre><p>The <code>Agent</code> takes an <code>initialState</code> (system prompt, model, tools, thinking level) and a <code>streamFn</code> - the function that actually calls the LLM. Passing <code>streamSimple</code> from pi-ai connects the agent to whatever provider the model specifies.</p><h3>The event stream</h3><p>Subscribe to events to see what the agent is doing:</p><pre><code><code>agent.subscribe((event) =&gt; {
  switch (event.type) {
    case "agent_start":
      console.log("Agent started");
      break;

    case "message_update":
      // Streaming text from the LLM
      if (event.assistantMessageEvent.type === "text_delta") {
        process.stdout.write(event.assistantMessageEvent.delta);
      }
      break;

    case "tool_execution_start":
      console.log(`\nTool: ${event.toolName}(${JSON.stringify(event.args)})`);
      break;

    case "tool_execution_end":
      console.log(`Result: ${event.isError ? "ERROR" : "OK"}`);
      break;

    case "agent_end":
      console.log("\nAgent finished");
      break;
  }
});</code></code></pre><p>Full event list: <code>agent_start</code>, <code>agent_end</code>, <code>turn_start</code>, <code>turn_end</code>, <code>message_start</code>, <code>message_update</code>, <code>message_end</code>, <code>tool_execution_start</code>, <code>tool_execution_update</code>, <code>tool_execution_end</code>.</p><h3>Running the agent</h3><pre><code><code>await agent.prompt("What's the weather in Tokyo and London?");</code></code></pre><p>That&#8217;s it. The agent:</p><ol><li><p>Sends your message to the LLM</p></li><li><p>The LLM decides to call <code>get_weather</code> for Tokyo</p></li><li><p>The agent executes the tool, feeds the result back</p></li><li><p>The LLM calls <code>get_weather</code> for London</p></li><li><p>The agent executes again, feeds the result back</p></li><li><p>The LLM produces a final text response</p></li></ol><p>You didn&#8217;t write the loop. The agent handles it.</p><h3>Full example</h3><p>Here&#8217;s a complete working agent with two tools:</p><pre><code><code>import { Agent } from "@mariozechner/pi-agent-core";
import { getModel, streamSimple } from "@mariozechner/pi-ai";
import { Type } from "@mariozechner/pi-ai";
import type { AgentTool } from "@mariozechner/pi-agent-core";
import * as fs from "fs";

const readFileParams = Type.Object({
  path: Type.String({ description: "Path to the file" }),
});

const readFileTool: AgentTool&lt;typeof readFileParams&gt; = {
  name: "read_file",
  label: "Read File",
  description: "Read the contents of a file",
  parameters: readFileParams,
  execute: async (_id, params) =&gt; {
    try {
      const content = fs.readFileSync(params.path, "utf-8");
      return {
        content: [{ type: "text", text: content }],
        details: {},
      };
    } catch (err: any) {
      return {
        content: [{ type: "text", text: `Error: ${err.message}` }],
        details: {},
      };
    }
  },
};

const listFilesParams = Type.Object({
  path: Type.String({ description: "Directory path", default: "." }),
});

const listFilesTool: AgentTool&lt;typeof listFilesParams&gt; = {
  name: "list_files",
  label: "List Files",
  description: "List files in a directory",
  parameters: listFilesParams,
  execute: async (_id, params) =&gt; {
    const files = fs.readdirSync(params.path);
    return {
      content: [{ type: "text", text: files.join("\n") }],
      details: { count: files.length },
    };
  },
};

async function main() {
  const model = getModel("anthropic", "claude-opus-4-5");

  const agent = new Agent({
    initialState: {
      systemPrompt: "You can read files and list directories. Be concise.",
      model,
      tools: [readFileTool, listFilesTool],
      thinkingLevel: "off",
    },
    streamFn: streamSimple,
  });

  agent.subscribe((event) =&gt; {
    if (event.type === "message_update" &amp;&amp; event.assistantMessageEvent.type === "text_delta") {
      process.stdout.write(event.assistantMessageEvent.delta);
    }
    if (event.type === "tool_execution_start") {
      console.log(`\n[${event.toolName}] ${JSON.stringify(event.args)}`);
    }
  });

  await agent.prompt("What files are in the current directory? Read the package.json if it exists.");
  console.log();
}

main();</code></code></pre><h3>Steering and follow-ups</h3><p>If the agent is working and you want to redirect it:</p><pre><code><code>// Interrupt: delivered after the current tool finishes.
// Remaining pending tools are skipped.
agent.steer({
  role: "user",
  content: "Actually, skip that and read tsconfig.json instead.",
  timestamp: Date.now(),
});

// Follow-up: queued for after the agent finishes naturally.
// Doesn't interrupt current work.
agent.followUp({
  role: "user",
  content: "Now summarize what you found.",
  timestamp: Date.now(),
});</code></code></pre><p><code>steer</code> interrupts - it skips remaining tools and injects your message. <code>followUp</code> waits - it queues the message for after the agent naturally stops. OpenClaw uses steering for real-time user messages (someone typing while the agent works) and follow-ups for programmatic chaining.</p><h3>State management</h3><p>You can change the agent&#8217;s configuration at any time:</p><pre><code><code>agent.setModel(getModel("openai", "gpt-4o"));    // Switch providers mid-session
agent.setThinkingLevel("high");                    // Enable extended thinking
agent.setSystemPrompt("New instructions.");        // Update the system prompt
agent.setTools([...newTools]);                     // Swap the tool set
agent.replaceMessages(trimmedMessages);            // Replace conversation history</code></code></pre><p>The agent picks up changes on the next turn.</p><div><hr></div><h2>Layer 3: pi-coding-agent</h2><p>pi-agent-core gives you the loop. pi-coding-agent gives you a production-ready agent with built-in tools, session persistence, and extensibility. It&#8217;s built on top of pi-agent-core - when you use pi-coding-agent, you&#8217;re already using pi-agent-core under the hood. </p><p>Most users should start here and only drop down to pi-agent-core directly if they need a custom agent that doesn&#8217;t use the built-in coding tools or session system.</p><h3>Built-in tools</h3><p>pi-coding-agent has 7 built-in tools. Four are active by default (<code>codingTools</code>), three more are available but off by default:</p><p><strong>Default tools (active):</strong></p><p><strong>ToolWhat it does</strong><code>read</code>Read file contents and images (jpg, png, gif, webp). Images are returned as attachments. Text output is truncated to 2000 lines or 50KB. Supports <code>offset</code>/<code>limit</code> for paginating large files.<code>bash</code>Execute a shell command in the working directory. Returns stdout and stderr, truncated to the last 2000 lines or 50KB. Optional <code>timeout</code> in seconds.<code>edit</code>Replace exact text in a file. <code>oldText</code> must match exactly (including whitespace). For precise, surgical edits.<code>write</code>Write content to a file. Creates it if it doesn&#8217;t exist, overwrites if it does. Auto-creates parent directories.</p><p><strong>Additional tools (opt-in):</strong></p><p><strong>ToolWhat it does</strong><code>grep</code>Search file contents for a regex or literal pattern. Returns matching lines with file paths and line numbers. Respects <code>.gitignore</code>. Uses ripgrep under the hood.<code>find</code>Search for files by glob pattern. Returns matching paths relative to the search directory. Respects <code>.gitignore</code>.<code>ls</code>List directory contents. Entries sorted alphabetically with <code>/</code> suffix for directories. Includes dotfiles.</p><p>These are organized into presets:</p><pre><code><code>import { codingTools, readOnlyTools } from "@mariozechner/pi-coding-agent";

codingTools;    // [read, bash, edit, write]  - default
readOnlyTools;  // [read, grep, find, ls]     - exploration without modification</code></code></pre><p>Or select individual tools:</p><pre><code><code>import { allBuiltInTools } from "@mariozechner/pi-coding-agent";

// allBuiltInTools.read, allBuiltInTools.bash, allBuiltInTools.edit,
// allBuiltInTools.write, allBuiltInTools.grep, allBuiltInTools.find, allBuiltInTools.ls

const { session } = await createAgentSession({
  model,
  tools: [allBuiltInTools.read, allBuiltInTools.bash, allBuiltInTools.grep],
  sessionManager: SessionManager.inMemory(),
});</code></code></pre><h3>createAgentSession</h3><p><code>createAgentSession</code> wires everything together - model, tools, session persistence, settings:</p><pre><code><code>import { createAgentSession, SessionManager } from "@mariozechner/pi-coding-agent";
import { getModel, streamSimple } from "@mariozechner/pi-ai";

async function main() {
  const model = getModel("anthropic", "claude-opus-4-5");

  const { session } = await createAgentSession({
    model,
    thinkingLevel: "off",
    sessionManager: SessionManager.inMemory(),
  });

  session.agent.streamFn = streamSimple;

  session.subscribe((event) =&gt; {
    if (event.type === "message_update" &amp;&amp; event.assistantMessageEvent.type === "text_delta") {
      process.stdout.write(event.assistantMessageEvent.delta);
    }
    if (event.type === "tool_execution_start") {
      console.log(`\n[${event.toolName}]`);
    }
  });

  await session.prompt("What files are in the current directory? Summarize the package.json.");
  console.log();

  session.dispose();
}

main();</code></code></pre><p>That&#8217;s a working coding agent. It can read your files, run commands, edit code, and write new files. <code>SessionManager.inMemory()</code> means the session lives in memory and disappears when the process exits.</p><h3>Session persistence</h3><p>For durable sessions, point the SessionManager at a file:</p><pre><code><code>import * as path from "path";

const sessionFile = path.join(process.cwd(), ".sessions", "my-session.jsonl");
const sessionManager = SessionManager.open(sessionFile);

const { session } = await createAgentSession({
  model,
  sessionManager,
});</code></code></pre><p>Sessions are stored as <a href="https://jsonlines.org/">JSONL</a> files with a tree structure - each entry has an <code>id</code> and <code>parentId</code>. This enables branching: you can navigate to any previous point in the conversation and continue from there without losing history.</p><p><code>SessionManager</code> has several static factory methods. Pick one based on your use case and pass it to <code>createAgentSession</code>:</p><pre><code><code>// Option 1: In-memory (ephemeral, nothing written to disk)
const sessionManager = SessionManager.inMemory();

// Option 2: New persistent session in ~/.pi/agent/sessions/
const sessionManager = SessionManager.create(process.cwd());

// Option 3: Open a specific session file
const sessionManager = SessionManager.open("/path/to/session.jsonl");

// Option 4: Continue the most recent session (or create new if none exists)
const sessionManager = SessionManager.continueRecent(process.cwd());

// Then pass whichever one you chose:
const { session } = await createAgentSession({ model, sessionManager });</code></code></pre><p>You can also list existing sessions for a directory:</p><pre><code><code>const sessions = await SessionManager.list(process.cwd());</code></code></pre><p>Once you have a <code>SessionManager</code>, you rarely need to call its methods directly - <code>createAgentSession</code> handles most of the wiring. But if you&#8217;re building custom session logic (like OpenClaw does for multi-channel routing), these are the key methods:</p><pre><code><code>// Reconstruct the conversation from the JSONL file.
// Use this when you need to inspect or display the current conversation
// outside of the agent session (e.g., showing history in a web UI).
const { messages, thinkingLevel, model } = sessionManager.buildSessionContext();

// Get the last entry in the current branch.
// Useful for checking what the most recent message was,
// or grabbing an entry ID to branch from.
const leaf = sessionManager.getLeafEntry();

// Fork the conversation from a specific point.
// Everything after entryId is abandoned (but still in the file).
// The agent continues from that point on the next prompt.
// OpenClaw uses this for "retry from here" flows.
sessionManager.branch(entryId);

// Manually append a message to the session transcript.
// createAgentSession does this automatically during prompt(),
// but you'd use it to inject messages programmatically -
// e.g., adding a system notification or a cron-triggered prompt.
sessionManager.appendMessage(message);

// Get the full tree structure of the session.
// Each node has children, so you can render a branch selector
// or let users navigate conversation history.
const tree = sessionManager.getTree();</code></code></pre><p>OpenClaw uses one session file per channel thread - <code>~/.openclaw/agents/&lt;agentId&gt;/sessions/&lt;sessionId&gt;.jsonl</code> - so each conversation is independent and crash-safe (JSONL is append-only; you lose at most one line on a crash).</p><h3>Using tool factories</h3><p>The pre-built tool arrays like <code>codingTools</code> and <code>readOnlyTools</code> are singletons that operate on whatever directory your process is running from. If you need tools that operate on a specific directory instead, use the factory functions:</p><pre><code><code>import {
  createCodingTools,
  createReadOnlyTools,
  createReadTool,
  createBashTool,
  createGrepTool,
} from "@mariozechner/pi-coding-agent";

// Create preset groups scoped to a workspace
const customCodingTools = createCodingTools("/path/to/workspace");       // [read, bash, edit, write]
const customReadOnlyTools = createReadOnlyTools("/path/to/workspace");   // [read, grep, find, ls]

// Or create individual tools - there's a factory for each built-in tool
const customRead = createReadTool("/path/to/workspace");
const customBash = createBashTool("/path/to/workspace");
const customGrep = createGrepTool("/path/to/workspace");</code></code></pre><p>Each factory accepts an optional <code>operations</code> object to override the underlying I/O - useful if you want to run tools inside a Docker container, over SSH, or against a virtual filesystem:</p><pre><code><code>// Read files from a remote server instead of the local disk
const remoteRead = createReadTool("/workspace", {
  operations: {
    readFile: async (path) =&gt; fetchFileFromRemote(path),
    access: async (path) =&gt; checkRemoteFileExists(path),
  },
});

// Execute commands in a Docker sandbox instead of the host
const sandboxedBash = createBashTool("/workspace", {
  operations: {
    exec: async (command, cwd, opts) =&gt; runInDockerContainer(command, cwd, opts),
  },
});</code></code></pre><p>OpenClaw uses these factories to create workspace-scoped tools for each agent, then wraps them with additional middleware - permission checks, image normalization for the read tool, and Claude Code parameter compatibility aliases (<code>file_path</code> &#8594; <code>path</code>, <code>old_string</code> &#8594; <code>oldText</code>).</p><h3>Custom tools alongside built-in tools</h3><p>The built-in tools cover file operations and shell commands.</p><p>For anything else - deploying, calling APIs, querying databases - define your own tools and pass them via <code>customTools</code>. They&#8217;ll be available alongside the defaults:</p><pre><code><code>import { Type } from "@mariozechner/pi-ai";
import type { AgentTool } from "@mariozechner/pi-agent-core";

const deployParams = Type.Object({
  environment: Type.String({ description: "Target environment", default: "staging" }),
});

const deployTool: AgentTool&lt;typeof deployParams&gt; = {
  name: "deploy",
  label: "Deploy",
  description: "Deploy the application to production",
  parameters: deployParams,
  execute: async (_id, params, signal, onUpdate) =&gt; {
    onUpdate?.({
      content: [{ type: "text", text: `Deploying to ${params.environment}...` }],
      details: {},
    });

    // Your logic here - call an API, run a script, trigger a CI pipeline, etc.
    await new Promise((resolve) =&gt; setTimeout(resolve, 2000));

    return {
      content: [{ type: "text", text: `Deployed to ${params.environment} successfully.` }],
      details: { environment: params.environment, timestamp: Date.now() },
    };
  },
};

const { session } = await createAgentSession({
  model,
  customTools: [deployTool],
  sessionManager: SessionManager.inMemory(),
});</code></code></pre><p>The agent now has read, write, edit, bash <em>and</em> deploy.</p><h3>Compaction</h3><p>Long conversations exceed the model&#8217;s context window. pi-coding-agent handles this with compaction - summarizing old messages while keeping recent ones:</p><pre><code><code>import { estimateTokens } from "@mariozechner/pi-coding-agent";

// Check how many tokens the conversation uses
const totalTokens = session.messages.reduce(
  (sum, msg) =&gt; sum + estimateTokens(msg),
  0
);

// Manually trigger compaction - the optional string guides what the summary should preserve
if (totalTokens &gt; 100_000) {
  await session.compact("Preserve all file paths and code changes.");
}</code></code></pre><p>By default, <code>createAgentSession</code> enables auto-compaction - it triggers automatically when the context approaches the model&#8217;s window limit. The full message history stays in the JSONL file; only the in-memory context gets compacted.</p><h3>Extensions</h3><p>Tools let the LLM <em>do things</em>. Extensions let you modify <em>how the agent behaves</em> - without the LLM knowing. They hook into lifecycle events that fire during the agent loop: before messages are sent to the LLM, before compaction runs, when a tool is called, when a session starts. The LLM never sees extensions in its context; they operate behind the scenes.</p><p>This is where you put logic like: trimming old tool results so the context window stays focused, replacing the default compaction with a custom summarization pipeline, gating tool calls based on permissions, or injecting extra context based on the current state of the conversation.</p><p>An extension is a TypeScript module that exports a function receiving an <code>ExtensionAPI</code>:</p><pre><code><code>import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";

export default function myExtension(api: ExtensionAPI): void {
  // Fires before every LLM call. Lets you rewrite the message array.
  api.on("context", (event, ctx) =&gt; {
    const pruned = event.messages.filter((msg) =&gt; {
      // Drop large tool results older than 10 messages
      if (msg.role === "toolResult" &amp;&amp; event.messages.indexOf(msg) &lt; event.messages.length - 10) {
        const text = msg.content.map((c) =&gt; (c.type === "text" ? c.text : "")).join("");
        if (text.length &gt; 5000) return false;
      }
      return true;
    });
    return { messages: pruned };
  });

  // Replace the default compaction with your own summarization logic
  api.on("session_before_compact", async (event, ctx) =&gt; {
    const summary = await myCustomSummarize(event.messages);
    return { compaction: { summary, firstKeptEntryId: event.firstKeptEntryId, tokensBefore: event.tokensBefore } };
  });

  // Register a user-facing command (not an LLM tool)
  api.registerCommand("stats", {
    description: "Show session statistics",
    handler: async (_args, ctx) =&gt; {
      const stats = ctx.session.getSessionStats();
      console.log(`Messages: ${stats.totalMessages}, Cost: $${stats.cost.toFixed(4)}`);
    },
  });
}</code></code></pre><p>Key extension events include <code>context</code> (rewrite messages before the LLM sees them), <code>session_before_compact</code> (customize summarization), <code>tool_call</code> (intercept or gate tool invocations), <code>before_agent_start</code> (inject context or modify the prompt), and <code>session_start</code>/<code>session_switch</code> (react to session changes).</p><p>OpenClaw uses extensions for context pruning (silently trimming oversized tool results to save tokens) and compaction safeguards (replacing pi&#8217;s default summarization with a multi-stage pipeline that preserves file operation history and tool failure data).</p><div><hr></div><h2>Building something real</h2><p>Here&#8217;s a complete example that ties all three layers together: a codebase assistant that can read your project, answer questions, make changes, and remember the conversation across restarts.</p><p>Create <code>assistant.ts</code>:</p><pre><code><code>import {
  createAgentSession,
  SessionManager,
  estimateTokens,
} from "@mariozechner/pi-coding-agent";
import { getModel, streamSimple } from "@mariozechner/pi-ai";
import { Type } from "@mariozechner/pi-ai";
import type { AgentTool } from "@mariozechner/pi-agent-core";
import * as path from "path";
import * as fs from "fs";
import * as readline from "readline";

// --- Custom tool: search the web ---
const webSearchParams = Type.Object({
  query: Type.String({ description: "Search query" }),
});

const webSearchTool: AgentTool&lt;typeof webSearchParams&gt; = {
  name: "web_search",
  label: "Web Search",
  description: "Search the web for documentation, error messages, or general information",
  parameters: webSearchParams,
  execute: async (_id, params) =&gt; {
    // In production, call a search API (Brave, Serper, etc.)
    return {
      content: [{ type: "text", text: `[Search results for: "${params.query}" would appear here]` }],
      details: { query: params.query },
    };
  },
};

// --- Session persistence ---
const sessionDir = path.join(process.cwd(), ".sessions");
fs.mkdirSync(sessionDir, { recursive: true });

const sessionFile = path.join(sessionDir, "assistant.jsonl");
const sessionManager = SessionManager.open(sessionFile);

// --- Create the agent session ---
async function createAssistant() {
  const model = getModel("anthropic", "claude-opus-4-5");

  const { session } = await createAgentSession({
    model,
    thinkingLevel: "off",
    sessionManager,
    customTools: [webSearchTool],
  });

  session.agent.streamFn = streamSimple;

  return session;
}

// --- Event handler ---
function attachEventHandlers(session: Awaited&lt;ReturnType&lt;typeof createAssistant&gt;&gt;) {
  session.subscribe((event) =&gt; {
    switch (event.type) {
      case "message_update":
        if (event.assistantMessageEvent.type === "text_delta") {
          process.stdout.write(event.assistantMessageEvent.delta);
        }
        break;

      case "tool_execution_start":
        console.log(`\n  [${event.toolName}] ${summarizeArgs(event.args)}`);
        break;

      case "tool_execution_end":
        if (event.isError) {
          console.log(`  ERROR`);
        }
        break;

      case "auto_compaction_start":
        console.log("\n  [compacting context...]");
        break;

      case "agent_end":
        console.log();
        break;
    }
  });
}

function summarizeArgs(args: any): string {
  if (args?.path) return args.path;
  if (args?.command) return args.command.slice(0, 60);
  if (args?.query) return `"${args.query}"`;
  if (args?.pattern) return args.pattern;
  return JSON.stringify(args).slice(0, 60);
}

// --- REPL ---
async function main() {
  const session = await createAssistant();
  attachEventHandlers(session);

  const tokenCount = session.messages.reduce((sum, msg) =&gt; sum + estimateTokens(msg), 0);

  console.log("PI Assistant");
  console.log(`  Model: ${session.model?.id}`);
  console.log(`  Session: ${sessionFile}`);
  console.log(`  History: ${session.messages.length} messages, ~${tokenCount} tokens`);
  console.log(`  Tools: ${session.getActiveToolNames().join(", ")}`);
  console.log(`  Type "exit" to quit, "new" to reset session\n`);

  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });

  const ask = () =&gt; {
    rl.question("You: ", async (input) =&gt; {
      const trimmed = input.trim();

      if (trimmed === "exit") {
        session.dispose();
        rl.close();
        return;
      }

      if (trimmed === "new") {
        await session.newSession();
        console.log("Session reset.\n");
        ask();
        return;
      }

      if (!trimmed) {
        ask();
        return;
      }

      try {
        await session.prompt(trimmed);
      } catch (err: any) {
        console.error(`Error: ${err.message}`);
      }

      ask();
    });
  };

  ask();
}

main();</code></code></pre><p>Run it:</p><pre><code><code>npx tsx assistant.ts</code></code></pre><p>This gives you a persistent coding assistant in ~120 lines. It can read files, run commands, edit code, search the web, and it remembers your conversation across restarts. The session tree in the JSONL file preserves full history even through compaction.</p><p>A session looks like:</p><pre><code><code>PI Assistant
  Model: claude-opus-4-5
  Session: /your/project/.sessions/assistant.jsonl
  History: 0 messages, ~0 tokens
  Tools: read, bash, edit, write, web_search

You: What does this project do? Look at the README and main entry point.

  [read] README.md
  [read] src/index.ts

This is a TypeScript library that...

You: Find all TODO comments in the source code.

  [bash] grep -rn "TODO" src/

Found 3 TODOs:
- src/auth.ts:42 - TODO: add token refresh
- src/api.ts:18 - TODO: handle rate limits
- src/index.ts:7 - TODO: add graceful shutdown

You: Fix the token refresh TODO. Implement a proper refresh flow.

  [read] src/auth.ts
  [edit] src/auth.ts

Done. Added a `refreshToken()` function that...</code></code></pre><h3>Adapting this for production</h3><p>OpenClaw takes this same pattern and adds layers for production use:</p><p><strong>Multi-provider auth.</strong> Instead of a single <code>ANTHROPIC_API_KEY</code>, OpenClaw uses <code>AuthStorage</code> and <code>ModelRegistry</code> to manage credentials across providers and support OAuth flows:</p><pre><code><code>import { AuthStorage, ModelRegistry } from "@mariozechner/pi-coding-agent";

const authStorage = AuthStorage.create(path.join(agentDir, "auth.json"));
const modelRegistry = new ModelRegistry(authStorage, modelsConfigPath);

const { session } = await createAgentSession({
  authStorage,
  modelRegistry,
  model: modelRegistry.find("ollama", "llama3.1:8b"),
  // ...
});</code></code></pre><p><code>AuthStorage</code> reads from an <code>auth.json</code> file - a flat object keyed by provider name, where each value is either an API key or an OAuth credential:</p><pre><code><code>{
  "anthropic": { "type": "api_key", "key": "sk-ant-..." },
  "openai": { "type": "api_key", "key": "sk-..." },
  "devin": { "type": "api_key", "key": "cog_..." },
  "github-copilot": {
    "type": "oauth",
    "refresh": "gho_xxxxxxxxxxxx",
    "access": "ghu_yyyyyyyyyyyy",
    "expires": 1700000000000
  }
}</code></code></pre><p>The <code>key</code> field can be a literal value, an environment variable name, or a shell command prefixed with <code>!</code> (e.g., <code>"!op read 'op://vault/openai/key'"</code> for 1Password). OAuth tokens are auto-refreshed when expired.</p><p><code>ModelRegistry</code> reads from a <code>models.json</code> file that defines custom providers and models. This is how you add self-hosted models or providers that aren&#8217;t built into pi:</p><pre><code><code>{
  "providers": {
    "ollama": {
      "baseUrl": "http://localhost:11434/v1",
      "api": "openai-completions",
      "apiKey": "ollama",
      "models": [
        { "id": "llama3.1:8b" },
        { "id": "qwen2.5-coder:7b" }
      ]
    },
    "my-company-api": {
      "baseUrl": "https://llm.internal.company.com/v1",
      "api": "openai-completions",
      "apiKey": "COMPANY_LLM_KEY",
      "authHeader": true,
      "models": [
        { "id": "internal-model-v2" }
      ]
    }
  }
}</code></code></pre><p>Models defined here show up alongside the built-in catalog. <code>modelRegistry.find("ollama", "llama3.1:8b")</code> returns a fully typed <code>Model</code> you can pass to <code>createAgentSession</code>.</p><p><strong>Stream middleware.</strong> <code>session.agent.streamFn</code> is the function the agent calls every time it needs to talk to an LLM. By default it&#8217;s <code>streamSimple</code>, but you can wrap it to inject headers, tweak parameters, or add logging on a per-provider basis.</p><p>OpenClaw uses this to add OpenRouter attribution headers and enable Anthropic prompt caching:</p><pre><code><code>import { streamSimple } from "@mariozechner/pi-ai";
import type { StreamFn } from "@mariozechner/pi-agent-core";

const wrappedStreamFn: StreamFn = (model, context, options) =&gt; {
  const extraHeaders: Record&lt;string, string&gt; = {};

  // OpenRouter uses these for their public app rankings/leaderboard
  if (model.provider === "openrouter") {
    extraHeaders["X-Title"] = "My App";
    extraHeaders["HTTP-Referer"] = "https://myapp.com";
  }

  return streamSimple(model, context, {
    ...options,
    headers: { ...options?.headers, ...extraHeaders },
    cacheRetention: model.provider === "anthropic" ? "long" : "none",
  });
};

session.agent.streamFn = wrappedStreamFn;</code></code></pre><p><strong>Tool customization.</strong> The default built-in tools operate on <code>process.cwd()</code>, which is fine for a local CLI.</p><p>But in a multi-user product like OpenClaw, each agent session needs to be locked to a specific workspace directory so users can&#8217;t read or write outside their project. OpenClaw uses the tool factories to rebuild the file tools with a workspace root, keeping the same tool behavior but scoping all paths:</p><pre><code><code>import {
  codingTools,
  readTool,
  createReadTool,
  createWriteTool,
  createEditTool,
} from "@mariozechner/pi-coding-agent";
import type { AgentTool } from "@mariozechner/pi-agent-core";

function buildTools(workspace: string): AgentTool[] {
  return (codingTools as AgentTool[]).map((tool) =&gt; {
    if (tool.name === readTool.name) {
      return createReadTool(workspace);
    }
    if (tool.name === "write") {
      return createWriteTool(workspace);
    }
    if (tool.name === "edit") {
      return createEditTool(workspace);
    }
    return tool; // bash stays as-is
  });
}</code></code></pre><p><strong>Event routing.</strong> When the agent runs, it emits events - text tokens streaming in, tool calls starting and finishing, the agent completing its turn. In a terminal app, you&#8217;d just print these to stdout. But OpenClaw runs agents on behalf of users who are chatting via Telegram, Discord, or Slack, so it needs to translate these events into platform-specific messages. <code>session.subscribe()</code> gives you a callback for every event, and you decide what to do with each one:</p><pre><code><code>session.subscribe((event) =&gt; {
  switch (event.type) {
    case "message_update":
      if (event.assistantMessageEvent.type === "text_delta") {
        // Tokens arrive one at a time - buffer them, then send as one message
        messageBuffer.append(event.assistantMessageEvent.delta);
      }
      break;

    case "tool_execution_start":
      // Send tool call notification to the channel
      channel.sendNotification(`Running ${event.toolName}...`);
      break;

    case "agent_end":
      // Flush remaining buffered text
      messageBuffer.flush();
      break;
  }
});</code></code></pre><h3>Adding a terminal UI</h3><p>The <code>assistant.ts</code> example uses <code>readline</code> for input - it works, but you get no markdown rendering, no autocomplete, and raw <code>process.stdout.write</code> for streaming. <code>pi-tui</code> replaces all of that with a proper terminal UI: markdown with syntax highlighting, an editor with slash command and file path autocomplete, a loading spinner, and flicker-free differential rendering.</p><p>Here&#8217;s the same assistant upgraded to pi-tui. Create <code>assistant-tui.ts</code>:</p><pre><code><code>import {
  createAgentSession,
  SessionManager,
  estimateTokens,
} from "@mariozechner/pi-coding-agent";
import { getModel, streamSimple } from "@mariozechner/pi-ai";
import { Type } from "@mariozechner/pi-ai";
import type { AgentTool } from "@mariozechner/pi-agent-core";
import {
  TUI,
  ProcessTerminal,
  Editor,
  Markdown,
  Text,
  Loader,
  CombinedAutocompleteProvider,
} from "@mariozechner/pi-tui";
import type { EditorTheme, MarkdownTheme } from "@mariozechner/pi-tui";
import chalk from "chalk";
import * as path from "path";
import * as fs from "fs";

// --- Themes ---
const markdownTheme: MarkdownTheme = {
  heading: (s) =&gt; chalk.bold.cyan(s),
  link: (s) =&gt; chalk.blue(s),
  linkUrl: (s) =&gt; chalk.dim(s),
  code: (s) =&gt; chalk.yellow(s),
  codeBlock: (s) =&gt; chalk.green(s),
  codeBlockBorder: (s) =&gt; chalk.dim(s),
  quote: (s) =&gt; chalk.italic(s),
  quoteBorder: (s) =&gt; chalk.dim(s),
  hr: (s) =&gt; chalk.dim(s),
  listBullet: (s) =&gt; chalk.cyan(s),
  bold: (s) =&gt; chalk.bold(s),
  italic: (s) =&gt; chalk.italic(s),
  strikethrough: (s) =&gt; chalk.strikethrough(s),
  underline: (s) =&gt; chalk.underline(s),
};

const editorTheme: EditorTheme = {
  borderColor: (s) =&gt; chalk.dim(s),
  selectList: {
    selectedPrefix: (s) =&gt; chalk.blue(s),
    selectedText: (s) =&gt; chalk.bold(s),
    description: (s) =&gt; chalk.dim(s),
    scrollInfo: (s) =&gt; chalk.dim(s),
    noMatch: (s) =&gt; chalk.dim(s),
  },
};

// --- Custom tool ---
const webSearchParams = Type.Object({
  query: Type.String({ description: "Search query" }),
});

const webSearchTool: AgentTool&lt;typeof webSearchParams&gt; = {
  name: "web_search",
  label: "Web Search",
  description: "Search the web for documentation, error messages, or general information",
  parameters: webSearchParams,
  execute: async (_id, params) =&gt; ({
    content: [{ type: "text", text: `[Search results for: "${params.query}" would appear here]` }],
    details: { query: params.query },
  }),
};

// --- Session persistence ---
const sessionDir = path.join(process.cwd(), ".sessions");
fs.mkdirSync(sessionDir, { recursive: true });
const sessionFile = path.join(sessionDir, "assistant.jsonl");

// --- TUI setup ---
const tui = new TUI(new ProcessTerminal());

tui.addChild(new Text(chalk.bold("PI Assistant") + chalk.dim(" (Ctrl+C to exit)\n")));

const editor = new Editor(tui, editorTheme);
editor.setAutocompleteProvider(
  new CombinedAutocompleteProvider(
    [
      { name: "new", description: "Reset the session" },
      { name: "exit", description: "Quit the assistant" },
    ],
    process.cwd(),
  ),
);
tui.addChild(editor);
tui.setFocus(editor);

// --- Main ---
async function main() {
  const model = getModel("anthropic", "claude-opus-4-5");
  const sessionManager = SessionManager.open(sessionFile);

  const { session } = await createAgentSession({
    model,
    thinkingLevel: "off",
    sessionManager,
    customTools: [webSearchTool],
  });

  session.agent.streamFn = streamSimple;

  // Show session info
  const tokenCount = session.messages.reduce((sum, msg) =&gt; sum + estimateTokens(msg), 0);
  const children = tui.children;
  children.splice(children.length - 1, 0, new Text(
    chalk.dim(`  Model: ${model.id}\n`) +
    chalk.dim(`  Session: ${sessionFile}\n`) +
    chalk.dim(`  History: ${session.messages.length} messages, ~${tokenCount} tokens\n`) +
    chalk.dim(`  Tools: ${session.getActiveToolNames().join(", ")}\n`),
  ));
  tui.requestRender();

  // Streaming state
  let streamingMarkdown: Markdown | null = null;
  let streamingText = "";
  let loader: Loader | null = null;
  let isRunning = false;

  // Subscribe to agent events
  session.subscribe((event) =&gt; {
    switch (event.type) {
      case "agent_start":
        isRunning = true;
        editor.disableSubmit = true;
        loader = new Loader(tui, (s) =&gt; chalk.cyan(s), (s) =&gt; chalk.dim(s), "Thinking...");
        children.splice(children.length - 1, 0, loader);
        tui.requestRender();
        break;

      case "message_update":
        if (event.assistantMessageEvent.type === "text_delta") {
          // Remove loader on first text
          if (loader) {
            tui.removeChild(loader);
            loader = null;
          }
          // Create or update the streaming markdown component
          streamingText += event.assistantMessageEvent.delta;
          if (!streamingMarkdown) {
            streamingMarkdown = new Markdown(streamingText, 1, 0, markdownTheme);
            children.splice(children.length - 1, 0, streamingMarkdown);
          } else {
            streamingMarkdown.setText(streamingText);
          }
          tui.requestRender();
        }
        break;

      case "tool_execution_start": {
        if (loader) {
          tui.removeChild(loader);
          loader = null;
        }
        const args = event.args?.path || event.args?.command?.slice(0, 60) || event.args?.query || "";
        const toolMsg = new Text(chalk.dim(`  [${event.toolName}] ${args}`));
        children.splice(children.length - 1, 0, toolMsg);
        tui.requestRender();
        break;
      }

      case "agent_end":
        if (loader) {
          tui.removeChild(loader);
          loader = null;
        }
        streamingMarkdown = null;
        streamingText = "";
        isRunning = false;
        editor.disableSubmit = false;
        tui.requestRender();
        break;
    }
  });

  // Handle input submission
  editor.onSubmit = async (value: string) =&gt; {
    if (isRunning) return;
    const trimmed = value.trim();
    if (!trimmed) return;

    if (trimmed === "/exit") {
      session.dispose();
      tui.stop();
      process.exit(0);
    }

    if (trimmed === "/new") {
      await session.newSession();
      children.splice(2, children.length - 3); // Keep header, info, and editor
      children.splice(children.length - 1, 0, new Text(chalk.dim("  Session reset.\n")));
      tui.requestRender();
      return;
    }

    // Add user message to chat
    const userMsg = new Markdown(value, 1, 0, markdownTheme, (s) =&gt; chalk.bold(s));
    children.splice(children.length - 1, 0, userMsg);
    tui.requestRender();

    // Send to agent
    try {
      await session.prompt(trimmed);
    } catch (err: any) {
      children.splice(children.length - 1, 0, new Text(chalk.red(`Error: ${err.message}`)));
      editor.disableSubmit = false;
      tui.requestRender();
    }
  };

  tui.start();
}

main();</code></code></pre><p>Run it:</p><pre><code><code>npx tsx assistant-tui.ts</code></code></pre><p>The key differences from the <code>readline</code> version:</p><ul><li><p><strong>Markdown rendering.</strong> Agent responses render with syntax-highlighted code blocks, bold, italics, lists, and links - instead of raw text dumped to stdout.</p></li><li><p><strong>Streaming via </strong><code>setText</code><strong>.</strong> As tokens arrive, we append to a string and call <code>streamingMarkdown.setText()</code>. The TUI&#8217;s differential renderer updates only the changed lines - no flicker, no screen clearing.</p></li><li><p><strong>Editor with autocomplete.</strong> Type <code>/</code> and you get a dropdown of slash commands. Press <code>Tab</code> for file path completion. Multi-line input with <code>Shift+Enter</code>.</p></li><li><p><strong>Loading spinner.</strong> The <code>Loader</code> component shows an animated spinner while the agent thinks, then removes itself when text starts streaming.</p></li><li><p><strong>No manual cursor management.</strong> The TUI handles terminal state, cursor positioning, and cleanup. No <code>process.stdout.write</code> calls scattered through your event handler.</p></li></ul><p>The architecture is the same - <code>createAgentSession</code> + <code>session.subscribe()</code> + <code>session.prompt()</code>. The only change is <em>how</em> you render the events: instead of writing to stdout, you add and update <code>Markdown</code>, <code>Text</code>, and <code>Loader</code> components in the TUI&#8217;s component tree.</p><div><hr></div><h2>What&#8217;s next</h2><p>This guide covered the four packages you need to build a terminal-based agent. The remaining pi-mono packages extend the system in other directions:</p><ul><li><p><strong>pi-web-ui</strong> - Lit web components for browser-based chat interfaces. Drop-in <code>ChatPanel</code> component with streaming, file attachments, and artifact rendering (HTML/SVG/Markdown in sandboxed iframes).</p></li><li><p><strong>pi-mom</strong> - A Slack bot that delegates messages to pi-coding-agent. Per-channel agent isolation, Docker sandboxing, scheduled events, and self-managing tool installation.</p></li><li><p><strong>pi-pods</strong> - CLI for deploying open-source models on GPU pods via vLLM. Supports DataCrunch, RunPod, Vast.ai, and bare metal. Each deployed model exposes an OpenAI-compatible endpoint that pi-ai can consume.</p></li></ul><p>The <a href="https://github.com/badlogic/pi-mono/tree/main/packages/coding-agent">pi-coding-agent docs</a> cover the full extension API, skills system, and CLI usage. The <a href="https://github.com/badlogic/pi-mono/blob/main/AGENTS.md">pi-mono AGENTS.md</a> has detailed instructions for adding new LLM providers.</p><p>For a production example of building on these packages, see <a href="https://github.com/openclaw/openclaw">OpenClaw</a> - a multi-channel AI assistant that uses all four core packages to run agents across WhatsApp, Telegram, Discord, Slack, Signal, iMessage, Google Chat, Microsoft Teams, and more, with shared memory and persistent sessions.</p>]]></content:encoded></item><item><title><![CDATA[How Cognition Uses Devin to Build Devin ]]></title><description><![CDATA[Cloud Agents are hitting escape velocity, and the teams that figure this out first are going to pull away fast. This is how it actually looks inside a team that runs on cloud agents.]]></description><link>https://nader.substack.com/p/how-cognition-uses-devin-to-build</link><guid isPermaLink="false">https://nader.substack.com/p/how-cognition-uses-devin-to-build</guid><dc:creator><![CDATA[Nader Dabit]]></dc:creator><pubDate>Wed, 11 Feb 2026 23:34:04 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!dG3h!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F172e3d49-407c-4f15-b619-cb97cc2fabde_1800x900.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote><p>Originally <a href="https://x.com/dabit3/status/2021702398775812135">posted on X</a>.</p></blockquote><p>Hi I&#8217;m Nader and <a href="https://x.com/dabit3/status/2019127422357238182">I recently joined</a> <a href="https://x.com/@cognition">@cognition</a>, the creators of <a href="https://x.com/@DevinAI">@DevinAI</a>. </p><p>Devin is <a href="https://x.com/dabit3/status/2020564900834111518">a cloud agent platform</a> for engineering teams. You work with Devin like a teammate - give it tasks, review its PRs, and let it handle your backlog.</p><p>Last week, we merged 659 Devin PRs into our own codebase, up from 154 in our best week in 2025.</p><p>Many of you know Devin as the web app. Internally, we use it across every interface (web, Slack, Linear, CLI, API) depending on where the work starts. This post covers how we leverage all of them.</p><h2><strong><a href="https://cognition.ai/blog/how-cognition-uses-devin-to-build-devin#core-experience">Core Experience</a></strong></h2><p>The setup is this: we add any of the codebases we&#8217;d like Devin to manage. We&#8217;re then given a unified interface where we can work across all of our repos using natural language.</p><p>It&#8217;s designed as a conversational interface, so we can chat with it the same way we&#8217;d message a teammate, whether from Slack, a Linear or Jira ticket, or the web app. Tag @ Devin in any channel. Include attachments if needed. Communicate back and forth as we would in the regular chat interface.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!dG3h!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F172e3d49-407c-4f15-b619-cb97cc2fabde_1800x900.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!dG3h!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F172e3d49-407c-4f15-b619-cb97cc2fabde_1800x900.png 424w, /__u/substackcdn.com/image/fetch/$s_!dG3h!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F172e3d49-407c-4f15-b619-cb97cc2fabde_1800x900.png 848w, /__u/substackcdn.com/image/fetch/$s_!dG3h!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F172e3d49-407c-4f15-b619-cb97cc2fabde_1800x900.png 1272w, /__u/substackcdn.com/image/fetch/$s_!dG3h!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F172e3d49-407c-4f15-b619-cb97cc2fabde_1800x900.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!dG3h!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F172e3d49-407c-4f15-b619-cb97cc2fabde_1800x900.png" width="1456" height="728" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/172e3d49-407c-4f15-b619-cb97cc2fabde_1800x900.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:728,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="/__u/substackcdn.com/image/fetch/$s_!dG3h!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F172e3d49-407c-4f15-b619-cb97cc2fabde_1800x900.png 424w, /__u/substackcdn.com/image/fetch/$s_!dG3h!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F172e3d49-407c-4f15-b619-cb97cc2fabde_1800x900.png 848w, /__u/substackcdn.com/image/fetch/$s_!dG3h!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F172e3d49-407c-4f15-b619-cb97cc2fabde_1800x900.png 1272w, /__u/substackcdn.com/image/fetch/$s_!dG3h!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F172e3d49-407c-4f15-b619-cb97cc2fabde_1800x900.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>A powerful result is that anyone is able to contribute regardless of their technical expertise or role in the company, they don&#8217;t need to understand and set up Git or any command line tools to start contributing to our codebases.</p><p>Tag @ Devin with a request and we get a PR we can review and test out. If someone notices out of date documentation or a bug for example, they send a short message in Slack to get it fixed and move on with their day.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!Ckbr!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45724b87-898b-4542-8f3d-c9c5f2dfac3f_1800x600.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!Ckbr!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45724b87-898b-4542-8f3d-c9c5f2dfac3f_1800x600.png 424w, /__u/substackcdn.com/image/fetch/$s_!Ckbr!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45724b87-898b-4542-8f3d-c9c5f2dfac3f_1800x600.png 848w, /__u/substackcdn.com/image/fetch/$s_!Ckbr!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45724b87-898b-4542-8f3d-c9c5f2dfac3f_1800x600.png 1272w, /__u/substackcdn.com/image/fetch/$s_!Ckbr!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45724b87-898b-4542-8f3d-c9c5f2dfac3f_1800x600.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!Ckbr!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45724b87-898b-4542-8f3d-c9c5f2dfac3f_1800x600.png" width="1456" height="485" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/45724b87-898b-4542-8f3d-c9c5f2dfac3f_1800x600.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:485,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="/__u/substackcdn.com/image/fetch/$s_!Ckbr!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45724b87-898b-4542-8f3d-c9c5f2dfac3f_1800x600.png 424w, /__u/substackcdn.com/image/fetch/$s_!Ckbr!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45724b87-898b-4542-8f3d-c9c5f2dfac3f_1800x600.png 848w, /__u/substackcdn.com/image/fetch/$s_!Ckbr!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45724b87-898b-4542-8f3d-c9c5f2dfac3f_1800x600.png 1272w, /__u/substackcdn.com/image/fetch/$s_!Ckbr!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45724b87-898b-4542-8f3d-c9c5f2dfac3f_1800x600.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>This removes friction and barriers for rapid iteration and polish,and results in less context switching.</p><p>And when engineers see what their teammates are accomplishing with Devin, on the same codebase they work on every day, they have the &#8220;Oh, really? Devin can do that?&#8221; moment, so it spreads organically.</p><h2><strong><a href="https://cognition.ai/blog/how-cognition-uses-devin-to-build-devin#ask-devin-codebase-qa">Ask Devin (Codebase Q&amp;A)</a></strong></h2><p>Once a repository is added to Devin, it&#8217;s automatically indexed. Ask Devin becomes a window into that codebase.</p><p>We use this constantly for scoping work before starting sessions. The workflow: use Ask Devin to explore the code and clarify a goal, then start a session directly from the search interface.</p><p>Devin starts with clear context from our exploration, and the prompt is automatically tailored to our task.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!K-vf!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c49759b-051a-4eda-9f43-807c94a3b68a_1800x636.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!K-vf!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c49759b-051a-4eda-9f43-807c94a3b68a_1800x636.png 424w, /__u/substackcdn.com/image/fetch/$s_!K-vf!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c49759b-051a-4eda-9f43-807c94a3b68a_1800x636.png 848w, /__u/substackcdn.com/image/fetch/$s_!K-vf!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c49759b-051a-4eda-9f43-807c94a3b68a_1800x636.png 1272w, /__u/substackcdn.com/image/fetch/$s_!K-vf!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c49759b-051a-4eda-9f43-807c94a3b68a_1800x636.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!K-vf!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c49759b-051a-4eda-9f43-807c94a3b68a_1800x636.png" width="1456" height="514" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6c49759b-051a-4eda-9f43-807c94a3b68a_1800x636.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:514,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="/__u/substackcdn.com/image/fetch/$s_!K-vf!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c49759b-051a-4eda-9f43-807c94a3b68a_1800x636.png 424w, /__u/substackcdn.com/image/fetch/$s_!K-vf!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c49759b-051a-4eda-9f43-807c94a3b68a_1800x636.png 848w, /__u/substackcdn.com/image/fetch/$s_!K-vf!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c49759b-051a-4eda-9f43-807c94a3b68a_1800x636.png 1272w, /__u/substackcdn.com/image/fetch/$s_!K-vf!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c49759b-051a-4eda-9f43-807c94a3b68a_1800x636.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>This same workflow applies with Jira or Linear integrations. Tag Devin on a ticket. Devin analyzes the task, searches the codebase, and plans its approach. It generates a high-quality session prompt automatically.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!4nYi!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F46edb777-82db-4fea-b918-83fcb63560a2_1800x1110.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!4nYi!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F46edb777-82db-4fea-b918-83fcb63560a2_1800x1110.png 424w, /__u/substackcdn.com/image/fetch/$s_!4nYi!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F46edb777-82db-4fea-b918-83fcb63560a2_1800x1110.png 848w, /__u/substackcdn.com/image/fetch/$s_!4nYi!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F46edb777-82db-4fea-b918-83fcb63560a2_1800x1110.png 1272w, /__u/substackcdn.com/image/fetch/$s_!4nYi!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F46edb777-82db-4fea-b918-83fcb63560a2_1800x1110.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!4nYi!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F46edb777-82db-4fea-b918-83fcb63560a2_1800x1110.png" width="1456" height="898" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/46edb777-82db-4fea-b918-83fcb63560a2_1800x1110.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:898,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="/__u/substackcdn.com/image/fetch/$s_!4nYi!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F46edb777-82db-4fea-b918-83fcb63560a2_1800x1110.png 424w, /__u/substackcdn.com/image/fetch/$s_!4nYi!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F46edb777-82db-4fea-b918-83fcb63560a2_1800x1110.png 848w, /__u/substackcdn.com/image/fetch/$s_!4nYi!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F46edb777-82db-4fea-b918-83fcb63560a2_1800x1110.png 1272w, /__u/substackcdn.com/image/fetch/$s_!4nYi!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F46edb777-82db-4fea-b918-83fcb63560a2_1800x1110.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2><strong><a href="https://cognition.ai/blog/how-cognition-uses-devin-to-build-devin#automated-code-review">Automated Code Review</a></strong></h2><p>As we ship more code with agents, the bottleneck shifted from writing code to reviewing it. <a href="https://app.devin.ai/review">Devin Review</a> turns large, complex GitHub PRs into intuitively organized diffs and precise explanations.</p><p>We use it for every PR now.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!c1JM!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feaba2c75-69b1-46f7-a4f1-69257ab9c7d2_1920x1049.gif" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!c1JM!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feaba2c75-69b1-46f7-a4f1-69257ab9c7d2_1920x1049.gif 424w, /__u/substackcdn.com/image/fetch/$s_!c1JM!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feaba2c75-69b1-46f7-a4f1-69257ab9c7d2_1920x1049.gif 848w, /__u/substackcdn.com/image/fetch/$s_!c1JM!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feaba2c75-69b1-46f7-a4f1-69257ab9c7d2_1920x1049.gif 1272w, /__u/substackcdn.com/image/fetch/$s_!c1JM!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feaba2c75-69b1-46f7-a4f1-69257ab9c7d2_1920x1049.gif 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!c1JM!,w_1456,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feaba2c75-69b1-46f7-a4f1-69257ab9c7d2_1920x1049.gif" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/eaba2c75-69b1-46f7-a4f1-69257ab9c7d2_1920x1049.gif&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:null,&quot;width&quot;:null,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="/__u/substackcdn.com/image/fetch/$s_!c1JM!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feaba2c75-69b1-46f7-a4f1-69257ab9c7d2_1920x1049.gif 424w, /__u/substackcdn.com/image/fetch/$s_!c1JM!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feaba2c75-69b1-46f7-a4f1-69257ab9c7d2_1920x1049.gif 848w, /__u/substackcdn.com/image/fetch/$s_!c1JM!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feaba2c75-69b1-46f7-a4f1-69257ab9c7d2_1920x1049.gif 1272w, /__u/substackcdn.com/image/fetch/$s_!c1JM!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feaba2c75-69b1-46f7-a4f1-69257ab9c7d2_1920x1049.gif 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>Every time Devin posts a PR in Slack, it includes a Devin Review link, so the organized diff is always one click away.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!IBGG!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F35621c0c-7340-4f3d-af49-6208ce999516_1800x430.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!IBGG!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F35621c0c-7340-4f3d-af49-6208ce999516_1800x430.png 424w, /__u/substackcdn.com/image/fetch/$s_!IBGG!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F35621c0c-7340-4f3d-af49-6208ce999516_1800x430.png 848w, /__u/substackcdn.com/image/fetch/$s_!IBGG!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F35621c0c-7340-4f3d-af49-6208ce999516_1800x430.png 1272w, /__u/substackcdn.com/image/fetch/$s_!IBGG!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F35621c0c-7340-4f3d-af49-6208ce999516_1800x430.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!IBGG!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F35621c0c-7340-4f3d-af49-6208ce999516_1800x430.png" width="1456" height="348" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/35621c0c-7340-4f3d-af49-6208ce999516_1800x430.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:348,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="/__u/substackcdn.com/image/fetch/$s_!IBGG!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F35621c0c-7340-4f3d-af49-6208ce999516_1800x430.png 424w, /__u/substackcdn.com/image/fetch/$s_!IBGG!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F35621c0c-7340-4f3d-af49-6208ce999516_1800x430.png 848w, /__u/substackcdn.com/image/fetch/$s_!IBGG!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F35621c0c-7340-4f3d-af49-6208ce999516_1800x430.png 1272w, /__u/substackcdn.com/image/fetch/$s_!IBGG!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F35621c0c-7340-4f3d-af49-6208ce999516_1800x430.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>What makes Devin Review useful:</p><ul><li><p><a href="https://cognition.ai/blog/closing-the-agent-loop-devin-autofixes-review-comments">Autofix</a>. If Devin Review or a GitHub bot flags bugs, Devin automatically fixes the PR. Devin also tackles CI/lint issues until all checks pass, closing the agent loop.</p></li><li><p>Smart diff organization. Groups changes logically, putting related edits together instead of alphabetical order.</p></li><li><p>Copy and move detection. Detects when code has been copied or moved and displays changes cleanly, instead of full deletes and inserts.</p></li><li><p>Bug Catcher. Automatically analyzes PRs for potential issues and labels them by confidence level. Severe bugs require immediate attention. Non-severe bugs should still be reviewed. Flags are informational annotations.</p></li><li><p>Codebase-aware chat. Ask questions about the PR and get answers with relevant context from the rest of the codebase.</p></li></ul><p>For any GitHub PR link, you can replace <a href="https://github.com/">github.com</a> with <a href="https://devinreview.com/">devinreview.com</a> in the URL.</p><p>Once Auto-Review is configured, Devin starts automatically reviewing PRs when they&#8217;re opened, when new commits are pushed, or when someone is added as a reviewer.</p><h2><strong><a href="https://cognition.ai/blog/how-cognition-uses-devin-to-build-devin#design-system">Design System</a></strong></h2><p>Design systems drift. Someone hardcodes a hex value, another person builds a one-off button, and suddenly we&#8217;re maintaining multiple sources of truth. We handle this two ways.</p><p>First, anyone can fix violations the moment they spot them. Tag Devin in Slack with a screenshot and it migrates the component to match the design system.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!auaU!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F00d04e60-7c2e-4d4c-bff0-576f34941c0f_1800x1096.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!auaU!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F00d04e60-7c2e-4d4c-bff0-576f34941c0f_1800x1096.png 424w, /__u/substackcdn.com/image/fetch/$s_!auaU!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F00d04e60-7c2e-4d4c-bff0-576f34941c0f_1800x1096.png 848w, /__u/substackcdn.com/image/fetch/$s_!auaU!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F00d04e60-7c2e-4d4c-bff0-576f34941c0f_1800x1096.png 1272w, /__u/substackcdn.com/image/fetch/$s_!auaU!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F00d04e60-7c2e-4d4c-bff0-576f34941c0f_1800x1096.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!auaU!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F00d04e60-7c2e-4d4c-bff0-576f34941c0f_1800x1096.png" width="1456" height="887" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/00d04e60-7c2e-4d4c-bff0-576f34941c0f_1800x1096.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:887,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="/__u/substackcdn.com/image/fetch/$s_!auaU!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F00d04e60-7c2e-4d4c-bff0-576f34941c0f_1800x1096.png 424w, /__u/substackcdn.com/image/fetch/$s_!auaU!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F00d04e60-7c2e-4d4c-bff0-576f34941c0f_1800x1096.png 848w, /__u/substackcdn.com/image/fetch/$s_!auaU!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F00d04e60-7c2e-4d4c-bff0-576f34941c0f_1800x1096.png 1272w, /__u/substackcdn.com/image/fetch/$s_!auaU!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F00d04e60-7c2e-4d4c-bff0-576f34941c0f_1800x1096.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Second, we run <a href="https://docs.devin.ai/use-cases/gallery/design-system-violation-scanner">a daily audit</a>. Every morning Devin scans PRs merged in the last 24 hours, flags hardcoded colors, non-standard spacing, and components that should use the shared library. It creates Linear tickets for each violation, and optionally opens fix PRs automatically.</p><p>The combination means violations get caught whether someone notices them or not.</p><h2><strong><a href="https://cognition.ai/blog/how-cognition-uses-devin-to-build-devin#bug-triage-automation">Bug Triage Automation</a></strong></h2><p>When a bug lands in Linear, someone has to stop what they&#8217;re doing to investigate&#8212;reading the ticket, searching the codebase, checking recent commits&#8212;before any actual fixing happens. We automated that entire triage step.</p><p>We set up a <code>!triage-bug</code> playbook that captures how our engineers actually investigate: read the report, search for relevant code paths, check git history, then summarize findings with root cause and suggested fix. The automation fires when anyone adds the <code>Bug</code> label to a ticket&#8212;no assignment needed.</p><p>A ticket like &#8220;500 error on /contact after Friday&#8217;s deploy&#8221; turns into Devin automatically finding the file, spotting a recent regex refactor in git log, and posting a summary back to Linear: root cause, affected files, suggested fix approach.</p><p>We connect the Datadog MCP so Devin checks logs during its investigation. By the time an engineer picks up a bug, the investigation is already done.</p><h2><strong><a href="https://cognition.ai/blog/how-cognition-uses-devin-to-build-devin#end-to-end-bug-debugging">End-to-End Bug Debugging</a></strong></h2><p>Some bugs need more than triage. They need someone to pull logs, check the database, trace the code change, and write a fix. We connected Devin to Datadog and gave it read-only database access so it can run full investigations.</p><p>When a bug report comes in (like &#8220;Pro users seeing &#8216;undefined&#8217; on the billing page since Friday&#8217;s deploy&#8221;), Devin pulls error logs from Datadog, queries the read replica to verify data state, traces the breaking commit in git history, writes a fix with a regression test, and opens a PR.</p><p>The investigation that used to pull an engineer off their work for an hour now happens in the background. We review the PR, not the logs.</p><h2><strong><a href="https://cognition.ai/blog/how-cognition-uses-devin-to-build-devin#dana-data-analyst-agent">DANA (Data Analyst Agent)</a></strong></h2><p>DANA is a specialized version of Devin optimized for querying databases, analyzing data, and creating visualizations.</p><p>We use it for questions about our data warehouse, building dashboards, and answering data questions without pulling an engineer off their work. It&#8217;s become a go-to for non-engineering tasks too, like the &#8220;click a bunch of buttons to fill out a report&#8221; work that used to eat up time.</p><p>We&#8217;re able to access DANA from the web app by clicking the agent picker dropdown, or from Slack using /dana or <a href="https://x.com/@Devin">@Devin</a> !dana followed by our question.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!BANV!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdb14a9df-910f-4593-a2bd-3d303c078d08_1800x836.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!BANV!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdb14a9df-910f-4593-a2bd-3d303c078d08_1800x836.png 424w, /__u/substackcdn.com/image/fetch/$s_!BANV!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdb14a9df-910f-4593-a2bd-3d303c078d08_1800x836.png 848w, /__u/substackcdn.com/image/fetch/$s_!BANV!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdb14a9df-910f-4593-a2bd-3d303c078d08_1800x836.png 1272w, /__u/substackcdn.com/image/fetch/$s_!BANV!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdb14a9df-910f-4593-a2bd-3d303c078d08_1800x836.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!BANV!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdb14a9df-910f-4593-a2bd-3d303c078d08_1800x836.png" width="1456" height="676" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/db14a9df-910f-4593-a2bd-3d303c078d08_1800x836.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:676,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="/__u/substackcdn.com/image/fetch/$s_!BANV!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdb14a9df-910f-4593-a2bd-3d303c078d08_1800x836.png 424w, /__u/substackcdn.com/image/fetch/$s_!BANV!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdb14a9df-910f-4593-a2bd-3d303c078d08_1800x836.png 848w, /__u/substackcdn.com/image/fetch/$s_!BANV!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdb14a9df-910f-4593-a2bd-3d303c078d08_1800x836.png 1272w, /__u/substackcdn.com/image/fetch/$s_!BANV!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdb14a9df-910f-4593-a2bd-3d303c078d08_1800x836.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>We&#8217;ve learned to be specific about metrics, include time periods, and ask for visualizations when they&#8217;d help.</p><p>DANA connects to our data warehouse through MCP&#8212;Redshift, PostgreSQL, Snowflake, BigQuery, whatever we&#8217;re running&#8212;and maintains its own database knowledge so it already understands our schema before we ask anything. It&#8217;s optimized for concise, metrics-focused answers with built-in seaborn visualizations, so we get charts and insights back fast instead of waiting for an engineer to context-switch into a SQL client.</p><p>We&#8217;ve found it especially useful for the kind of ad-hoc questions that used to sit in someone&#8217;s queue for days. Questions like &#8220;why did signups drop Tuesday?&#8221; or &#8220;break down consumption by enterprise vs self-serve&#8221; can be answered by anyone on the team just asking in Slack, with the SQL included so we can validate the logic.</p><h2><strong><a href="https://cognition.ai/blog/how-cognition-uses-devin-to-build-devin#deepwiki">DeepWiki</a></strong></h2><p>With DeepWiki, Devin automatically indexes all repos and produces wikis with architecture diagrams, links to sources, and summaries of the codebase. Ask Devin uses information in the Wiki to better understand and find relevant context.</p><p>For public repos, <a href="https://deepwiki.com/">deepwiki.com</a> automatically generates architecture diagrams, source links, and documentation, no setup required.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!-gDN!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F12a40dbe-1897-4035-b1d5-852945e2b90e_1800x1120.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!-gDN!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F12a40dbe-1897-4035-b1d5-852945e2b90e_1800x1120.png 424w, /__u/substackcdn.com/image/fetch/$s_!-gDN!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F12a40dbe-1897-4035-b1d5-852945e2b90e_1800x1120.png 848w, /__u/substackcdn.com/image/fetch/$s_!-gDN!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F12a40dbe-1897-4035-b1d5-852945e2b90e_1800x1120.png 1272w, /__u/substackcdn.com/image/fetch/$s_!-gDN!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F12a40dbe-1897-4035-b1d5-852945e2b90e_1800x1120.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!-gDN!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F12a40dbe-1897-4035-b1d5-852945e2b90e_1800x1120.png" width="1456" height="906" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/12a40dbe-1897-4035-b1d5-852945e2b90e_1800x1120.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:906,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="/__u/substackcdn.com/image/fetch/$s_!-gDN!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F12a40dbe-1897-4035-b1d5-852945e2b90e_1800x1120.png 424w, /__u/substackcdn.com/image/fetch/$s_!-gDN!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F12a40dbe-1897-4035-b1d5-852945e2b90e_1800x1120.png 848w, /__u/substackcdn.com/image/fetch/$s_!-gDN!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F12a40dbe-1897-4035-b1d5-852945e2b90e_1800x1120.png 1272w, /__u/substackcdn.com/image/fetch/$s_!-gDN!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F12a40dbe-1897-4035-b1d5-852945e2b90e_1800x1120.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>We also maintain <a href="https://docs.devin.ai/work-with-devin/deepwiki-mcp">a free DeepWiki MCP</a>.</p><h2><strong><a href="https://cognition.ai/blog/how-cognition-uses-devin-to-build-devin#playbooks">Playbooks</a></strong></h2><p>A Playbook is like a custom system prompt for a repeated task. If we find ourselves repeating the same instructions across multiple sessions, that&#8217;s when we create a Playbook.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!zqXc!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F21737cdc-5e98-4dc2-8b1b-ad796acf4413_1800x1080.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!zqXc!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F21737cdc-5e98-4dc2-8b1b-ad796acf4413_1800x1080.png 424w, /__u/substackcdn.com/image/fetch/$s_!zqXc!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F21737cdc-5e98-4dc2-8b1b-ad796acf4413_1800x1080.png 848w, /__u/substackcdn.com/image/fetch/$s_!zqXc!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F21737cdc-5e98-4dc2-8b1b-ad796acf4413_1800x1080.png 1272w, /__u/substackcdn.com/image/fetch/$s_!zqXc!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F21737cdc-5e98-4dc2-8b1b-ad796acf4413_1800x1080.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!zqXc!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F21737cdc-5e98-4dc2-8b1b-ad796acf4413_1800x1080.png" width="1456" height="874" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/21737cdc-5e98-4dc2-8b1b-ad796acf4413_1800x1080.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:874,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="/__u/substackcdn.com/image/fetch/$s_!zqXc!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F21737cdc-5e98-4dc2-8b1b-ad796acf4413_1800x1080.png 424w, /__u/substackcdn.com/image/fetch/$s_!zqXc!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F21737cdc-5e98-4dc2-8b1b-ad796acf4413_1800x1080.png 848w, /__u/substackcdn.com/image/fetch/$s_!zqXc!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F21737cdc-5e98-4dc2-8b1b-ad796acf4413_1800x1080.png 1272w, /__u/substackcdn.com/image/fetch/$s_!zqXc!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F21737cdc-5e98-4dc2-8b1b-ad796acf4413_1800x1080.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Once anyone succeeds with Devin, others can replicate that success. A good Playbook includes:</p><ul><li><p>The outcome we want Devin to achieve</p></li><li><p>The steps required to get there</p></li><li><p>Specifications describing postconditions</p></li><li><p>Advice to correct Devin&#8217;s priors</p></li><li><p>Forbidden actions</p></li><li><p>Any required input or context from the person kicking it off</p></li></ul><p>We use Playbooks for complex recurring work&#8212;ingesting data into Redshift, running database migrations, integrating with Stripe, Plaid, and Modal.</p><h2><strong><a href="https://cognition.ai/blog/how-cognition-uses-devin-to-build-devin#mcp-marketplace">MCP Marketplace</a></strong></h2><p>MCP enables Devin to use hundreds of external tools and data sources. We use MCPs to dig through Sentry, Datadog, and Vercel logs. Connect database MCPs for data analysis in Slack. Pull context from tools like Notion, Airtable, and Linear.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!OwCc!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd4b1c275-75aa-4815-bc78-7739d3e3d4ed_1800x1080.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!OwCc!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd4b1c275-75aa-4815-bc78-7739d3e3d4ed_1800x1080.png 424w, /__u/substackcdn.com/image/fetch/$s_!OwCc!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd4b1c275-75aa-4815-bc78-7739d3e3d4ed_1800x1080.png 848w, /__u/substackcdn.com/image/fetch/$s_!OwCc!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd4b1c275-75aa-4815-bc78-7739d3e3d4ed_1800x1080.png 1272w, /__u/substackcdn.com/image/fetch/$s_!OwCc!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd4b1c275-75aa-4815-bc78-7739d3e3d4ed_1800x1080.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!OwCc!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd4b1c275-75aa-4815-bc78-7739d3e3d4ed_1800x1080.png" width="1456" height="874" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d4b1c275-75aa-4815-bc78-7739d3e3d4ed_1800x1080.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:874,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="/__u/substackcdn.com/image/fetch/$s_!OwCc!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd4b1c275-75aa-4815-bc78-7739d3e3d4ed_1800x1080.png 424w, /__u/substackcdn.com/image/fetch/$s_!OwCc!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd4b1c275-75aa-4815-bc78-7739d3e3d4ed_1800x1080.png 848w, /__u/substackcdn.com/image/fetch/$s_!OwCc!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd4b1c275-75aa-4815-bc78-7739d3e3d4ed_1800x1080.png 1272w, /__u/substackcdn.com/image/fetch/$s_!OwCc!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd4b1c275-75aa-4815-bc78-7739d3e3d4ed_1800x1080.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Many can be enabled with a single click&#8212;Vercel, Atlassian, Notion, Sentry, Neon, Asana, Jam, and more.</p><p>For example, we chart cumulative Linear integrations across all orgs by tagging Devin in Slack with the Redshift MCP&#8212;this gives us a weekly breakdown, key observations, and a Metabase link we can share.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!nL1J!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b8fde48-94a1-44fb-a79a-2911308a1178_1800x826.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!nL1J!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b8fde48-94a1-44fb-a79a-2911308a1178_1800x826.png 424w, /__u/substackcdn.com/image/fetch/$s_!nL1J!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b8fde48-94a1-44fb-a79a-2911308a1178_1800x826.png 848w, /__u/substackcdn.com/image/fetch/$s_!nL1J!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b8fde48-94a1-44fb-a79a-2911308a1178_1800x826.png 1272w, /__u/substackcdn.com/image/fetch/$s_!nL1J!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b8fde48-94a1-44fb-a79a-2911308a1178_1800x826.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!nL1J!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b8fde48-94a1-44fb-a79a-2911308a1178_1800x826.png" width="1456" height="668" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2b8fde48-94a1-44fb-a79a-2911308a1178_1800x826.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:668,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="/__u/substackcdn.com/image/fetch/$s_!nL1J!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b8fde48-94a1-44fb-a79a-2911308a1178_1800x826.png 424w, /__u/substackcdn.com/image/fetch/$s_!nL1J!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b8fde48-94a1-44fb-a79a-2911308a1178_1800x826.png 848w, /__u/substackcdn.com/image/fetch/$s_!nL1J!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b8fde48-94a1-44fb-a79a-2911308a1178_1800x826.png 1272w, /__u/substackcdn.com/image/fetch/$s_!nL1J!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b8fde48-94a1-44fb-a79a-2911308a1178_1800x826.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2><strong><a href="https://cognition.ai/blog/how-cognition-uses-devin-to-build-devin#session-insights">Session Insights</a></strong></h2><p>Session Insights analyzes completed Devin sessions and provides actionable recommendations for improvement.</p><p>After Devin completes a task, Session Insights examines:</p><ul><li><p>Issues and challenges (technical problems, communication gaps, scope creep)</p></li><li><p>Session timeline with key milestones and efficiency metrics</p></li><li><p>Action items including immediate improvements and process optimizations</p></li><li><p>Improved prompt suggestions with enhanced instructions</p></li></ul><p>We use insights from one session to inform the next. We can spin up new sessions directly from the insights using the improved prompts. Over time, sessions get more efficient.</p><h2><strong><a href="https://cognition.ai/blog/how-cognition-uses-devin-to-build-devin#api-access">API Access</a></strong></h2><p>Devin exposes a full REST API, so agents don&#8217;t need a human in the loop to start working. We connect Devin to our existing systems and trigger sessions programmatically:</p><ul><li><p>A crash log lands from Sentry &#8594; Devin investigates and opens a PR</p></li><li><p>A bug report is filed &#8594; Devin reproduces, diagnoses, and patches</p></li><li><p>A deployment fails &#8594; Devin analyzes logs and suggests a fix</p></li><li><p>A code review is requested &#8594; Devin reviews and leaves comments</p></li></ul><h2><strong><a href="https://cognition.ai/blog/how-cognition-uses-devin-to-build-devin#where-devin-excels">Where Devin Excels</a></strong></h2><p>Devin can take on most engineering work, including complex tasks. Success scales with specificity. Well-scoped tasks with clear criteria get the best outcomes.</p><p>Tasks we consistently use Devin for:</p><ul><li><p>High volume iterative improvements</p></li><li><p>Bug fixes and edge cases</p></li><li><p>Improving test coverage</p></li><li><p>Investigating CI failures</p></li><li><p>Lint errors and CVE remediation</p></li><li><p>PR review</p></li><li><p>Maintaining documentation</p></li><li><p>Remediating security vulnerabilities from static analysis</p></li><li><p>Codebase Q&amp;A</p></li></ul><h2><strong><a href="https://cognition.ai/blog/how-cognition-uses-devin-to-build-devin#tips-for-complex-tasks">Tips for Complex Tasks</a></strong></h2><ul><li><p>Large-scale challenges should be broken into smaller, isolated tasks across separate sessions.</p></li><li><p>Devin excels at building interfaces that work; styling them beautifully is still a human strength.</p></li><li><p>Mobile development works, but Devin doesn&#8217;t have a phone to test with.</p></li><li><p>For anything requiring extensive testing and validation, we make sure verification mechanisms are in place.</p></li></ul><h2><strong><a href="https://cognition.ai/blog/how-cognition-uses-devin-to-build-devin#getting-started">Getting Started</a></strong></h2><p>Minimum Setup</p><ol><li><p>Sign up at <a href="http://app.devin.ai/">app.devin.ai</a></p></li><li><p>Connect your GitHub, GitLab, or Bitbucket</p></li><li><p>Add your first repository</p></li><li><p>Start a session with a simple task</p></li></ol><p>Scaling Up As you get comfortable:</p><ul><li><p>Add Knowledge to teach Devin your codebase conventions</p></li><li><p>Create Playbooks for repeated tasks</p></li><li><p>Connect Slack for inline collaboration</p></li><li><p>Enable MCPs for your tools</p></li><li><p>Use Ask Devin to scope complex work before starting sessions</p></li></ul><p>Treat Devin like a team member. Give it context. Teach it your conventions. Let it handle the backlog while you focus on the work that requires senior judgment. An AI software engineer with clear context, working autonomously on well-scoped tasks, is a force multiplier.</p><p></p><p><em>Built by the team at </em><a href="https://x.com/@cognition">@cognition</a><em>. If you&#8217;re building with </em><a href="https://x.com/@DevinAI">@DevinAI</a><em>, we&#8217;d love to hear about it.</em></p>]]></content:encoded></item><item><title><![CDATA[Everything worth doing felt like this]]></title><description><![CDATA[Everything I&#8217;ve ever done publicly has felt uncomfortable.]]></description><link>https://nader.substack.com/p/everything-worth-doing-felt-like</link><guid isPermaLink="false">https://nader.substack.com/p/everything-worth-doing-felt-like</guid><dc:creator><![CDATA[Nader Dabit]]></dc:creator><pubDate>Wed, 11 Feb 2026 05:30:50 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!aQ6n!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd40f5972-2f71-4c94-860c-5648e16ba9f7_1024x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Everything I&#8217;ve ever done publicly has felt uncomfortable. Sharing videos, publishing blogs, open sourcing my code, changing jobs / industries publicly, speaking in front of large audiences, this message itself even feels uncomfortable to write.</p><p>I know everyone isn&#8217;t like this, but I&#8217;ve talked to enough people to know that this is a common feeling to have. </p><p>What I can say is this: the most important things that have happened in my career and life have been the result of taking action in the face of discomfort. It&#8217;s almost a gauge at this point, balancing it with my gut feeling + logic.</p><p>If my gut says do it and logically I agree with it, then I do it no matter how uncomfortable it feels. And it&#8217;s almost at that point I&#8217;ve learned that the more uncomfortable it feels, the more I should do it.</p><p>Yes there have been very painful situations that were due to me making very public mistakes, but the good by far has outweighed the bad.</p><p>I&#8217;m writing this to you but also as a reminder to my future self to stop waiting for the fear to go away, because it doesn&#8217;t, we just get better at moving with it.</p>]]></content:encoded></item><item><title><![CDATA[You Could've Invented OpenClaw
]]></title><description><![CDATA[Building a persistent AI assistant from first principles - sessions, tools, memory, and multi-channel messaging in ~400 lines of Python]]></description><link>https://nader.substack.com/p/you-couldve-invented-openclaw</link><guid isPermaLink="false">https://nader.substack.com/p/you-couldve-invented-openclaw</guid><dc:creator><![CDATA[Nader Dabit]]></dc:creator><pubDate>Wed, 11 Feb 2026 03:18:57 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!v9Ry!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4203b655-80c7-4c57-976f-3ecfe14e47cb_1826x790.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!v9Ry!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4203b655-80c7-4c57-976f-3ecfe14e47cb_1826x790.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!v9Ry!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4203b655-80c7-4c57-976f-3ecfe14e47cb_1826x790.png 424w, /__u/substackcdn.com/image/fetch/$s_!v9Ry!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4203b655-80c7-4c57-976f-3ecfe14e47cb_1826x790.png 848w, /__u/substackcdn.com/image/fetch/$s_!v9Ry!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4203b655-80c7-4c57-976f-3ecfe14e47cb_1826x790.png 1272w, /__u/substackcdn.com/image/fetch/$s_!v9Ry!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4203b655-80c7-4c57-976f-3ecfe14e47cb_1826x790.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!v9Ry!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4203b655-80c7-4c57-976f-3ecfe14e47cb_1826x790.png" width="1456" height="630" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4203b655-80c7-4c57-976f-3ecfe14e47cb_1826x790.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:630,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:360339,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://nader.substack.com/i/187589952?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4203b655-80c7-4c57-976f-3ecfe14e47cb_1826x790.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!v9Ry!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4203b655-80c7-4c57-976f-3ecfe14e47cb_1826x790.png 424w, /__u/substackcdn.com/image/fetch/$s_!v9Ry!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4203b655-80c7-4c57-976f-3ecfe14e47cb_1826x790.png 848w, /__u/substackcdn.com/image/fetch/$s_!v9Ry!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4203b655-80c7-4c57-976f-3ecfe14e47cb_1826x790.png 1272w, /__u/substackcdn.com/image/fetch/$s_!v9Ry!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4203b655-80c7-4c57-976f-3ecfe14e47cb_1826x790.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><blockquote><p>Originally <a href="https://x.com/dabit3/status/2021387483364151451?s=20">posted on X</a></p></blockquote><p>What makes OpenClaw powerful is surprisingly simple: it&#8217;s a gateway that connects an AI agent to your messaging apps, gives it tools to interact with your computer, and lets it remember who you are across conversations.</p><p>The complexity comes from handling multiple channels simultaneously, managing persistent sessions, coordinating multiple agents, and making the whole thing reliable enough to run 24/7 on your machine.</p><p>In this post, I&#8217;ll start from scratch and build up to OpenClaw&#8217;s architecture step by step, showing how you could have invented it yourself from first principles, using nothing but a messaging API, an LLM, and the desire to make AI actually useful <em>outside</em> the chat window.</p><p>End goal: understand how persistent AI assistants work, so you can build your own.</p><h2><strong>First, let&#8217;s establish the problem</strong></h2><p>When you use ChatGPT or Claude in a browser, there are several limitations:</p><p>It&#8217;s stateless. Every conversation starts from zero. It doesn&#8217;t know your name, your preferences, what you asked yesterday, or what project you&#8217;re working on. You&#8217;re constantly re-explaining context.</p><p>It&#8217;s passive. You go to it. It never comes to you. It can&#8217;t wake up at 7am and brief you on your calendar, monitor your email, or run a recurring task. It only works when you&#8217;re sitting in front of it.</p><p>It&#8217;s isolated. It can&#8217;t run commands on your machine, browse the web for you, control your apps, or send messages on your behalf. It lives in a text box with no hands.</p><p>It&#8217;s single-channel. Your real life happens across WhatsApp, Telegram, Discord, Slack, iMessage - but the AI lives in its own separate tab. There&#8217;s no way to text it where you already are, let alone have it maintain one continuous memory across all those surfaces.</p><p>What if instead, you had an AI that:</p><ul><li><p>Lived in the messaging apps you already use - all of them, with shared memory</p></li><li><p>Remembered your preferences, your projects, and your past conversations across sessions</p></li><li><p>Could run commands on your computer, browse the web, and control a real browser</p></li><li><p>Woke up on a schedule to handle recurring tasks without being asked</p></li><li><p>Ran on your own hardware - your laptop, a VPS, a Mac Mini - always on, under your control</p></li></ul><p>This is what OpenClaw does. It&#8217;s not a chatbot - it&#8217;s a personal AI assistant with a persistent identity, tools, and presence across every channel you use.</p><p>Let&#8217;s build one from scratch.</p><h2><strong>The Simplest Possible Bot</strong></h2><p>Let&#8217;s start with the absolute minimum: an AI that responds to messages on Telegram.</p><pre><code># bot-v0.py - The simplest possible AI bot
import os
import anthropic
from telegram import Update
from telegram.ext import Application, MessageHandler, filters

client = anthropic.Anthropic()

async def handle_message(update: Update, context):
    user_message = update.message.text

    response = client.messages.create(
        model=&#8221;claude-sonnet-4-5-20250929&#8221;,
        max_tokens=1024,
        messages=[{&#8221;role&#8221;: &#8220;user&#8221;, &#8220;content&#8221;: user_message}]
    )

    await update.message.reply_text(response.content[0].text)

app = Application.builder().token(os.getenv(&#8221;TELEGRAM_BOT_TOKEN&#8221;)).build()
app.add_handler(MessageHandler(filters.TEXT, handle_message))
app.run_polling()</code></pre><p>Run it, send a message on Telegram, and the AI responds. Simple.</p><p>But this is basically a worse version of the Claude web interface. Every message is independent. No memory. No tools. No personality.</p><p>What if we gave it memory?</p><h2><strong>Goal: Persistent Sessions</strong></h2><p>A problem with our simple bot is statelessness. Every message is a fresh conversation. Ask it &#8220;what did I say earlier?&#8221; and it has no idea.</p><p>The fix is sessions. Keep a conversation history per user.</p><pre><code># bot-v1.py - Bot with persistent sessions
import json
import os
import anthropic
from telegram import Update
from telegram.ext import Application, MessageHandler, filters

client = anthropic.Anthropic()
SESSIONS_DIR = &#8220;./sessions&#8221;
os.makedirs(SESSIONS_DIR, exist_ok=True)

def get_session_path(user_id):
    return os.path.join(SESSIONS_DIR, f&#8221;{user_id}.jsonl&#8221;)

def load_session(user_id):
    &#8220;&#8221;&#8220;Load conversation history from disk.&#8221;&#8220;&#8221;
    path = get_session_path(user_id)
    messages = []
    if os.path.exists(path):
        with open(path, &#8220;r&#8221;) as f:
            for line in f:
                if line.strip():
                    messages.append(json.loads(line))
    return messages

def append_to_session(user_id, message):
    &#8220;&#8221;&#8220;Append a single message to the session file.&#8221;&#8220;&#8221;
    path = get_session_path(user_id)
    with open(path, &#8220;a&#8221;) as f:
        f.write(json.dumps(message) + &#8220;\n&#8221;)

def save_session(user_id, messages):
    &#8220;&#8221;&#8220;Overwrite the session file with the full message list.&#8221;&#8220;&#8221;
    path = get_session_path(user_id)
    with open(path, &#8220;w&#8221;) as f:
        for message in messages:
            f.write(json.dumps(message) + &#8220;\n&#8221;)

async def handle_message(update: Update, context):
    user_id = str(update.effective_user.id)
    user_message = update.message.text

    # Load existing conversation
    messages = load_session(user_id)

    # Add new user message
    user_msg = {&#8221;role&#8221;: &#8220;user&#8221;, &#8220;content&#8221;: user_message}
    messages.append(user_msg)
    append_to_session(user_id, user_msg)

    # Call the AI with full history
    response = client.messages.create(
        model=&#8221;claude-sonnet-4-5-20250929&#8221;,
        max_tokens=4096,
        messages=messages
    )

    # Save assistant response
    assistant_msg = {&#8221;role&#8221;: &#8220;assistant&#8221;, &#8220;content&#8221;: response.content[0].text}
    append_to_session(user_id, assistant_msg)

    await update.message.reply_text(response.content[0].text)

app = Application.builder().token(os.getenv(&#8221;TELEGRAM_BOT_TOKEN&#8221;)).build()
app.add_handler(MessageHandler(filters.TEXT, handle_message))
app.run_polling()</code></pre><p>Now you can have an actual conversation:</p><pre><code><code>You: My name is Nader
Bot: Nice to meet you, Nader!

[hours later...]

You: What's my name?
Bot: Your name is Nader!
</code></code></pre><p>The key insight is the JSONL format. Each line is one message. Append-only. If the process crashes mid-write, you lose at most one line. This is exactly what OpenClaw uses for session transcripts:</p><pre><code><code>~/.openclaw/agents/&lt;agentId&gt;/sessions/&lt;sessionId&gt;.jsonl
</code></code></pre><p>Each session maps to a file. Each file is a conversation. Restart the process and everything is still there.</p><p>But we&#8217;ll hit a problem: conversations grow. Eventually they&#8217;ll exceed the model&#8217;s context window. We&#8217;ll come back to that.</p><h2><strong>Goal: Adding a Personality (SOUL.md)</strong></h2><p>Our bot works, but it has no personality. It&#8217;s a generic AI assistant. What if we wanted it to be <em>someone</em>?</p><p>OpenClaw solves this with <code>SOUL.md</code>: a markdown file that defines the agent&#8217;s identity, behavior, and boundaries.</p><pre><code># bot-v2.py - Bot with personality

SOUL = &#8220;&#8221;&#8220;
# Who You Are

**Name:** Jarvis
**Role:** Personal AI assistant

## Personality
- Be genuinely helpful, not performatively helpful
- Skip the &#8220;Great question!&#8221; - just help
- Have opinions. You&#8217;re allowed to disagree
- Be concise when needed, thorough when it matters

## Boundaries
- Private things stay private
- When in doubt, ask before acting externally
- You&#8217;re not the user&#8217;s voice - be careful about sending messages on their behalf

## Memory
Remember important details from conversations.
Write them down if they matter.
&#8220;&#8221;&#8220;

async def handle_message(update: Update, context):
    user_id = str(update.effective_user.id)
    messages = load_session(user_id)

    user_msg = {&#8221;role&#8221;: &#8220;user&#8221;, &#8220;content&#8221;: update.message.text}
    messages.append(user_msg)
    append_to_session(user_id, user_msg)

    response = client.messages.create(
        model=&#8221;claude-sonnet-4-5-20250929&#8221;,
        max_tokens=4096,
        system=SOUL,  # &lt;-- personality injected here
        messages=messages
    )

    assistant_msg = {&#8221;role&#8221;: &#8220;assistant&#8221;, &#8220;content&#8221;: response.content[0].text}
    append_to_session(user_id, assistant_msg)

    await update.message.reply_text(response.content[0].text)</code></pre><p>Now instead of a generic assistant, you&#8217;re talking to Jarvis. The SOUL gets injected as the system prompt on every API call.</p><p>In OpenClaw, the SOUL.md lives in the agent&#8217;s workspace:</p><pre><code><code>~/.openclaw/workspace/SOUL.md
</code></code></pre><p>It gets loaded at session start and injected into the system prompt. You can write anything you want in there. Give the agent an origin story. Define its core philosophy. List its behavioral rules.</p><p>The more specific your SOUL, the more consistent the agent&#8217;s behavior. &#8220;Be helpful&#8221; is vague. &#8220;Be the assistant you&#8217;d actually want to talk to. Concise when needed, thorough when it matters. Not a corporate drone. Not a sycophant. Just... good.&#8221; - that gives the model something to work with.</p><h2><strong>Goal: Adding Tools</strong></h2><p>A bot that can only talk is limited. What if it could <em>do things</em>?</p><p>The core idea: give the AI structured tools and let it decide when to use them.</p><pre><code>import subprocess

TOOLS = [
    {
        &#8220;name&#8221;: &#8220;run_command&#8221;,
        &#8220;description&#8221;: &#8220;Run a shell command on the user&#8217;s computer&#8221;,
        &#8220;input_schema&#8221;: {
            &#8220;type&#8221;: &#8220;object&#8221;,
            &#8220;properties&#8221;: {
                &#8220;command&#8221;: {&#8221;type&#8221;: &#8220;string&#8221;, &#8220;description&#8221;: &#8220;The command to run&#8221;}
            },
            &#8220;required&#8221;: [&#8221;command&#8221;]
        }
    },
    {
        &#8220;name&#8221;: &#8220;read_file&#8221;,
        &#8220;description&#8221;: &#8220;Read a file from the filesystem&#8221;,
        &#8220;input_schema&#8221;: {
            &#8220;type&#8221;: &#8220;object&#8221;,
            &#8220;properties&#8221;: {
                &#8220;path&#8221;: {&#8221;type&#8221;: &#8220;string&#8221;, &#8220;description&#8221;: &#8220;Path to the file&#8221;}
            },
            &#8220;required&#8221;: [&#8221;path&#8221;]
        }
    },
    {
        &#8220;name&#8221;: &#8220;write_file&#8221;,
        &#8220;description&#8221;: &#8220;Write content to a file&#8221;,
        &#8220;input_schema&#8221;: {
            &#8220;type&#8221;: &#8220;object&#8221;,
            &#8220;properties&#8221;: {
                &#8220;path&#8221;: {&#8221;type&#8221;: &#8220;string&#8221;, &#8220;description&#8221;: &#8220;Path to the file&#8221;},
                &#8220;content&#8221;: {&#8221;type&#8221;: &#8220;string&#8221;, &#8220;description&#8221;: &#8220;Content to write&#8221;}
            },
            &#8220;required&#8221;: [&#8221;path&#8221;, &#8220;content&#8221;]
        }
    },
    {
        &#8220;name&#8221;: &#8220;web_search&#8221;,
        &#8220;description&#8221;: &#8220;Search the web for information&#8221;,
        &#8220;input_schema&#8221;: {
            &#8220;type&#8221;: &#8220;object&#8221;,
            &#8220;properties&#8221;: {
                &#8220;query&#8221;: {&#8221;type&#8221;: &#8220;string&#8221;, &#8220;description&#8221;: &#8220;Search query&#8221;}
            },
            &#8220;required&#8221;: [&#8221;query&#8221;]
        }
    }
]

def execute_tool(name, input):
    if name == &#8220;run_command&#8221;:
        result = subprocess.run(
            input[&#8221;command&#8221;], shell=True,
            capture_output=True, text=True, timeout=30
        )
        return result.stdout + result.stderr

    elif name == &#8220;read_file&#8221;:
        with open(input[&#8221;path&#8221;], &#8220;r&#8221;) as f:
            return f.read()

    elif name == &#8220;write_file&#8221;:
        with open(input[&#8221;path&#8221;], &#8220;w&#8221;) as f:
            f.write(input[&#8221;content&#8221;])
        return f&#8221;Wrote to {input[&#8217;path&#8217;]}&#8221;

    elif name == &#8220;web_search&#8221;:
        # Simplified - use a real search API in practice
        return f&#8221;Search results for: {input[&#8217;query&#8217;]}&#8221;

    return f&#8221;Unknown tool: {name}&#8221;</code></pre><p>Now we need the agent loop. When the AI wants to use a tool, we execute it and feed the result back:</p><pre><code>def serialize_content(content):
    &#8220;&#8221;&#8220;Convert API response content blocks to JSON-serializable dicts.&#8221;&#8220;&#8221;
    serialized = []
    for block in content:
        if hasattr(block, &#8220;text&#8221;):
            serialized.append({&#8221;type&#8221;: &#8220;text&#8221;, &#8220;text&#8221;: block.text})
        elif block.type == &#8220;tool_use&#8221;:
            serialized.append({
                &#8220;type&#8221;: &#8220;tool_use&#8221;,
                &#8220;id&#8221;: block.id,
                &#8220;name&#8221;: block.name,
                &#8220;input&#8221;: block.input
            })
    return serialized

def run_agent_turn(messages, system_prompt):
    &#8220;&#8221;&#8220;Run one full agent turn (may involve multiple tool calls).&#8221;&#8220;&#8221;
    while True:
        response = client.messages.create(
            model=&#8221;claude-sonnet-4-5-20250929&#8221;,
            max_tokens=4096,
            system=system_prompt,
            tools=TOOLS,
            messages=messages
        )

        content = serialize_content(response.content)

        # If the AI is done (no tool use), return the text
        if response.stop_reason == &#8220;end_turn&#8221;:
            text = &#8220;&#8221;
            for block in response.content:
                if hasattr(block, &#8220;text&#8221;):
                    text += block.text
            messages.append({&#8221;role&#8221;: &#8220;assistant&#8221;, &#8220;content&#8221;: content})
            return text, messages

        # Process tool calls
        if response.stop_reason == &#8220;tool_use&#8221;:
            messages.append({&#8221;role&#8221;: &#8220;assistant&#8221;, &#8220;content&#8221;: content})

            tool_results = []
            for block in response.content:
                if block.type == &#8220;tool_use&#8221;:
                    print(f&#8221;  Tool: {block.name}({json.dumps(block.input)})&#8221;)
                    result = execute_tool(block.name, block.input)
                    tool_results.append({
                        &#8220;type&#8221;: &#8220;tool_result&#8221;,
                        &#8220;tool_use_id&#8221;: block.id,
                        &#8220;content&#8221;: str(result)
                    })

            messages.append({&#8221;role&#8221;: &#8220;user&#8221;, &#8220;content&#8221;: tool_results})</code></pre><p>Now we update <code>handle_message</code> to use the agent loop instead of calling the API directly:</p><pre><code>async def handle_message(update: Update, context):
    user_id = str(update.effective_user.id)
    messages = load_session(user_id)
    messages.append({&#8221;role&#8221;: &#8220;user&#8221;, &#8220;content&#8221;: update.message.text})

    response_text, messages = run_agent_turn(messages, SOUL)

    save_session(user_id, messages)
    await update.message.reply_text(response_text)</code></pre><p>Now you can text your bot:</p><pre><code><code>You: Create a file called hello.py that prints hello world, then run it

Bot: [uses write_file to create hello.py]
    [uses run_command to execute it]
    Done! I created hello.py and ran it. Output: "hello world"
</code></code></pre><p>The AI decided which tools to use, in what order, and synthesized the results into a natural response. All through a Telegram message.</p><p>OpenClaw&#8217;s production tool catalog is much larger &#8212; browser automation, inter-agent messaging, sub-agent spawning, and more -= but every tool follows this exact pattern: a schema, a description, and an execution function.</p><h2><strong>Goal: Permission Controls</strong></h2><p>We&#8217;re executing commands from Telegram messages. That&#8217;s terrifying. What if someone gets access to your Telegram account and tells the bot to <code>rm -rf /</code>?</p><p>We need a permission system. OpenClaw&#8217;s approach: an approval allowlist that remembers what you&#8217;ve approved.</p><p>We add these helpers alongside our existing code, then update the <code>run_command</code> case inside <code>execute_tool</code> to use them:</p><pre><code>import re

SAFE_COMMANDS = {&#8221;ls&#8221;, &#8220;cat&#8221;, &#8220;head&#8221;, &#8220;tail&#8221;, &#8220;wc&#8221;, &#8220;date&#8221;, &#8220;whoami&#8221;, &#8220;echo&#8221;}
DANGEROUS_PATTERNS = [r&#8221;\brm\b&#8221;, r&#8221;\bsudo\b&#8221;, r&#8221;\bchmod\b&#8221;, r&#8221;\bcurl.*\|.*sh&#8221;]

# Persistent allowlist
APPROVALS_FILE = &#8220;./exec-approvals.json&#8221;

def load_approvals():
    if os.path.exists(APPROVALS_FILE):
        with open(APPROVALS_FILE) as f:
            return json.load(f)
    return {&#8221;allowed&#8221;: [], &#8220;denied&#8221;: []}

def save_approval(command, approved):
    approvals = load_approvals()
    key = &#8220;allowed&#8221; if approved else &#8220;denied&#8221;
    if command not in approvals[key]:
        approvals[key].append(command)
    with open(APPROVALS_FILE, &#8220;w&#8221;) as f:
        json.dump(approvals, f, indent=2)

def check_command_safety(command):
    &#8220;&#8221;&#8220;Returns &#8216;safe&#8217;, &#8216;approved&#8217;, or &#8216;needs_approval&#8217;.&#8221;&#8220;&#8221;
    base_cmd = command.strip().split()[0] if command.strip() else &#8220;&#8221;
    if base_cmd in SAFE_COMMANDS:
        return &#8220;safe&#8221;

    approvals = load_approvals()
    if command in approvals[&#8221;allowed&#8221;]:
        return &#8220;approved&#8221;

    for pattern in DANGEROUS_PATTERNS:
        if re.search(pattern, command):
            return &#8220;needs_approval&#8221;

    return &#8220;needs_approval&#8221;</code></pre><p>Now update the <code>run_command</code> case in <code>execute_tool</code> to check permissions before executing:</p><pre><code>    if name == &#8220;run_command&#8221;:
        cmd = input[&#8221;command&#8221;]
        safety = check_command_safety(cmd)

        if safety == &#8220;needs_approval&#8221;:
            # In a real bot, you&#8217;d prompt the user via Telegram
            # and wait for their response. For simplicity, we log and deny.
            print(f&#8221;  &#9888;&#65039;  Blocked: {cmd} (needs approval)&#8221;)
            return &#8220;Permission denied. Command requires approval.&#8221;

        result = subprocess.run(
            cmd, shell=True, capture_output=True,
            text=True, timeout=30
        )
        return result.stdout + result.stderr</code></pre><p>When a command is safe or previously approved, it runs immediately. When it&#8217;s not, the agent gets told &#8220;permission denied&#8221; and can try a different approach. The approval gets persisted to <code>exec-approvals.json</code>, so you&#8217;re never asked twice for the same command.</p><p>OpenClaw extends this with glob patterns (approve <code>git *</code> once) and a three-tier model: &#8220;ask&#8221; (prompt user), &#8220;record&#8221; (log but allow), and &#8220;ignore&#8221; (auto-allow).</p><h2><strong>Goal: The Gateway</strong></h2><p>Here&#8217;s where it gets interesting. So far we have a Telegram bot. But what if you also want the AI on Discord? And WhatsApp? And Slack?</p><p>You could write separate bots for each platform. But then you&#8217;d have separate sessions, separate memory, separate configurations. The AI on Telegram wouldn&#8217;t know what you discussed on Discord.</p><p>The solution: a <strong>gateway</strong>. One central process that manages all channels.</p><p>Look at what we already have. Our <code>run_agent_turn</code> function doesn&#8217;t know anything about Telegram. It takes messages and returns text. That&#8217;s the key &#8212; the agent logic is already decoupled from the channel.</p><p>To prove it, let&#8217;s add a second interface. We&#8217;ll add a simple HTTP API alongside our Telegram bot, both talking to the same agent and the same sessions:</p><pre><code>from flask import Flask, request, jsonify
import threading

flask_app = Flask(__name__)

@flask_app.route(&#8221;/chat&#8221;, methods=[&#8221;POST&#8221;])
def chat():
    data = request.json
    user_id = data[&#8221;user_id&#8221;]
    messages = load_session(user_id)
    messages.append({&#8221;role&#8221;: &#8220;user&#8221;, &#8220;content&#8221;: data[&#8221;message&#8221;]})

    response_text, messages = run_agent_turn(messages, SOUL)

    save_session(user_id, messages)
    return jsonify({&#8221;response&#8221;: response_text})

# Run the HTTP API in a background thread
threading.Thread(target=lambda: flask_app.run(port=5000), daemon=True).start()

# Telegram bot runs as before
app = Application.builder().token(os.getenv(&#8221;TELEGRAM_BOT_TOKEN&#8221;)).build()
app.add_handler(MessageHandler(filters.TEXT, handle_message))
app.run_polling()</code></pre><p><strong>Try it out:</strong> Tell the bot your name on Telegram. Then query via HTTP using the same user ID (your Telegram user ID) to prove the session is shared:</p><pre><code># Via Telegram
You: My name is Nader
Bot: Nice to meet you, Nader!

# Via HTTP &#8212; use your Telegram user ID so it hits the same session
curl -X POST http://127.0.0.1:5000/chat \
  -H &#8220;Content-Type: application/json&#8221; \
  -d &#8216;{&#8221;user_id&#8221;: &#8220;YOUR_TELEGRAM_USER_ID&#8221;, &#8220;message&#8221;: &#8220;What is my name?&#8221;}&#8217;

{&#8221;response&#8221;: &#8220;Your name is Nader!&#8221;}</code></pre><p>Same agent, same sessions, same memory. Two different interfaces. That&#8217;s the gateway pattern.</p><p>The next step would be making this config-driven &#8212; a JSON file specifying which channels to start and how to authenticate them. That&#8217;s what OpenClaw does: its gateway manages Telegram, Discord, WhatsApp, Slack, Signal, iMessage, and more, all through a single config file. It also supports configurable session scoping &#8212; per-user, per-channel, or a single shared session &#8212; so the same person gets a unified experience across channels. We&#8217;ll keep our simple user-ID-as-session-key approach for now.</p><h2><strong>Goal: Context Compaction</strong></h2><p>Remember the growing session problem we flagged earlier? After chatting with your bot for weeks, the session file has thousands of messages. The total token count exceeds the model&#8217;s context window. Now what?</p><p>The fix: summarize old messages, keep recent ones. Add these two functions alongside your existing code:</p><pre><code>def estimate_tokens(messages):
    &#8220;&#8221;&#8220;Rough token estimate: ~4 chars per token.&#8221;&#8220;&#8221;
    return sum(len(json.dumps(m)) for m in messages) // 4

def compact_session(user_id, messages):
    &#8220;&#8221;&#8220;Summarize old messages when context gets too long.&#8221;&#8220;&#8221;
    if estimate_tokens(messages) &lt; 100_000:  # ~80% of a 128k window
        return messages  # No compaction needed

    split = len(messages) // 2
    old, recent = messages[:split], messages[split:]

    print(&#8221;  Compacting session history...&#8221;)

    summary = client.messages.create(
        model=&#8221;claude-sonnet-4-5-20250929&#8221;,
        max_tokens=2000,
        messages=[{
            &#8220;role&#8221;: &#8220;user&#8221;,
            &#8220;content&#8221;: (
                &#8220;Summarize this conversation concisely. Preserve:\n&#8221;
                &#8220;- Key facts about the user (name, preferences)\n&#8221;
                &#8220;- Important decisions made\n&#8221;
                &#8220;- Open tasks or TODOs\n\n&#8221;
                f&#8221;{json.dumps(old, indent=2)}&#8221;
            )
        }]
    )

    compacted = [{
        &#8220;role&#8221;: &#8220;user&#8221;,
        &#8220;content&#8221;: f&#8221;[Previous conversation summary]\n{summary.content[0].text}&#8221;
    }] + recent

    save_session(user_id, compacted)
    return compacted</code></pre><p>Now add the compaction check at the top of <code>handle_message</code>, right after loading the session:</p><pre><code>async def handle_message(update: Update, context):
    user_id = str(update.effective_user.id)
    messages = load_session(user_id)
    messages = compact_session(user_id, messages)  # &lt;-- add this line
    messages.append({&#8221;role&#8221;: &#8220;user&#8221;, &#8220;content&#8221;: update.message.text})

    response_text, messages = run_agent_turn(messages, SOUL)

    save_session(user_id, messages)
    await update.message.reply_text(response_text)</code></pre><p><strong>Try it out:</strong> To test compaction without chatting for hours, temporarily lower the threshold:</p><pre><code>if estimate_tokens(messages) &lt; 1000:  # lowered for testing</code></pre><p>Have a conversation of 10-15 messages, then watch the old messages get replaced with a summary. The bot still remembers key facts, but the session file is much smaller.</p><p>OpenClaw&#8217;s compaction is more sophisticated &#8212; it splits messages into chunks by token count, summarizes each chunk separately, and includes a safety margin for estimation inaccuracy &#8212; but the core idea is identical.</p><h2><strong>Goal: Long-Term Memory</strong></h2><p>Session history gives you conversation memory. But what happens when you reset a session or start a new one? Everything is gone.</p><p>We need a separate memory system &#8212; persistent knowledge that survives session resets. The approach: give the agent tools to save and search memories stored as files.</p><p>Add these two tools to the <code>TOOLS</code> list:</p><pre><code>{
    &#8220;name&#8221;: &#8220;save_memory&#8221;,
    &#8220;description&#8221;: &#8220;Save important information to long-term memory. Use for user preferences, key facts, and anything worth remembering across sessions.&#8221;,
    &#8220;input_schema&#8221;: {
        &#8220;type&#8221;: &#8220;object&#8221;,
        &#8220;properties&#8221;: {
            &#8220;key&#8221;: {
                &#8220;type&#8221;: &#8220;string&#8221;,
                &#8220;description&#8221;: &#8220;Short label, e.g. &#8216;user-preferences&#8217;, &#8216;project-notes&#8217;&#8221;
            },
            &#8220;content&#8221;: {
                &#8220;type&#8221;: &#8220;string&#8221;,
                &#8220;description&#8221;: &#8220;The information to remember&#8221;
            }
        },
        &#8220;required&#8221;: [&#8221;key&#8221;, &#8220;content&#8221;]
    }
},
{
    &#8220;name&#8221;: &#8220;memory_search&#8221;,
    &#8220;description&#8221;: &#8220;Search long-term memory for relevant information. Use at the start of conversations to recall context.&#8221;,
    &#8220;input_schema&#8221;: {
        &#8220;type&#8221;: &#8220;object&#8221;,
        &#8220;properties&#8221;: {
            &#8220;query&#8221;: {
                &#8220;type&#8221;: &#8220;string&#8221;,
                &#8220;description&#8221;: &#8220;What to search for&#8221;
            }
        },
        &#8220;required&#8221;: [&#8221;query&#8221;]
    }
}</code></pre><p>Add their cases to <code>execute_tool</code>:</p><pre><code>MEMORY_DIR = &#8220;./memory&#8221;

# Add these cases to execute_tool:

    elif name == &#8220;save_memory&#8221;:
        os.makedirs(MEMORY_DIR, exist_ok=True)
        filepath = os.path.join(MEMORY_DIR, f&#8221;{input[&#8217;key&#8217;]}.md&#8221;)
        with open(filepath, &#8220;w&#8221;) as f:
            f.write(input[&#8221;content&#8221;])
        return f&#8221;Saved to memory: {input[&#8217;key&#8217;]}&#8221;

    elif name == &#8220;memory_search&#8221;:
        query = input[&#8221;query&#8221;].lower()
        results = []
        if os.path.exists(MEMORY_DIR):
            for fname in os.listdir(MEMORY_DIR):
                if fname.endswith(&#8221;.md&#8221;):
                    with open(os.path.join(MEMORY_DIR, fname), &#8220;r&#8221;) as f:
                        content = f.read()
                    if any(word in content.lower() for word in query.split()):
                        results.append(f&#8221;--- {fname} ---\n{content}&#8221;)
        return &#8220;\n\n&#8221;.join(results) if results else &#8220;No matching memories found.&#8221;</code></pre><p>Finally, update the SOUL so the agent knows about memory:</p><pre><code>SOUL = &#8220;&#8221;&#8220;
# Who You Are
...existing personality...

## Memory
You have a long-term memory system.
- Use save_memory to store important information (user preferences, key facts, project details).
- Use memory_search at the start of conversations to recall context from previous sessions.
Memory files are stored in ./memory/ as markdown files.
&#8220;&#8221;&#8220;</code></pre><p><strong>Try it out:</strong></p><pre><code><code>You: Remember that my favorite restaurant is Elvies and I prefer to go on weekends.

Bot: [uses save_memory to write nader-profile.md]
     Got it &#8212; saved your restaurant preference.

[Reset the session or restart the bot]

You: Where should we go for dinner?

Bot: [uses memory_search for "restaurant dinner favorite"]
     How about Elvies? I know it's your favorite. Want to go this weekend?
</code></code></pre><p>The memory persists because it&#8217;s stored in files, not in the session. Reset the session, restart the bot &#8212; the memories are still there.</p><p>OpenClaw&#8217;s production memory uses vector search with embeddings for semantic matching (so &#8220;auth bug&#8221; matches &#8220;authentication issues&#8221;), but our keyword search works well for getting started.</p><h2><strong>Goal: Command Queue</strong></h2><p>Here&#8217;s a subtle but critical problem: what happens when two messages arrive at the same time?</p><p>Say you send &#8220;check my calendar&#8221; on Telegram and &#8220;what&#8217;s the weather&#8221; via the HTTP API simultaneously. Both try to load the same session, both try to append to it, and you get corrupted data.</p><p>The fix is simple: a per-session lock. Only one message processes at a time for each session. Different sessions can still run in parallel.</p><pre><code># Add to your bot
from collections import defaultdict

session_locks = defaultdict(threading.Lock)</code></pre><p>Now wrap the body of <code>handle_message</code> with the lock:</p><pre><code>async def handle_message(update: Update, context):
    user_id = str(update.effective_user.id)

    with session_locks[user_id]:
        messages = load_session(user_id)
        messages = compact_session(user_id, messages)
        messages.append({&#8221;role&#8221;: &#8220;user&#8221;, &#8220;content&#8221;: update.message.text})

        response_text, messages = run_agent_turn(messages, SOUL)

        save_session(user_id, messages)

    await update.message.reply_text(response_text)</code></pre><p>Do the same for the <code>/chat</code> HTTP endpoint:</p><pre><code>@flask_app.route(&#8221;/chat&#8221;, methods=[&#8221;POST&#8221;])
def chat():
    data = request.json
    user_id = data[&#8221;user_id&#8221;]

    with session_locks[user_id]:
        messages = load_session(user_id)
        messages = compact_session(user_id, messages)
        messages.append({&#8221;role&#8221;: &#8220;user&#8221;, &#8220;content&#8221;: data[&#8221;message&#8221;]})

        response_text, messages = run_agent_turn(messages, SOUL)

        save_session(user_id, messages)

    return jsonify({&#8221;response&#8221;: response_text})</code></pre><p>That&#8217;s it &#8212; five lines of setup. Messages for the same user queue up. Messages for different users run in parallel. No race conditions.</p><p>OpenClaw extends this with lane-based queues (separate lanes for messages, cron jobs, and sub-agents) so heartbeats never block real-time conversations.</p><h2><strong>Goal: Cron Jobs (Heartbeats)</strong></h2><p>So far our agent only responds when you talk to it. But what if you want it to check your email every morning? Or summarize your calendar before meetings?</p><p>You need scheduled execution. Let&#8217;s add heartbeats &#8212; recurring tasks that trigger the agent on a timer.</p><pre><code>import schedule
import time

def setup_heartbeats():
    &#8220;&#8221;&#8220;Configure recurring agent tasks.&#8221;&#8220;&#8221;

    def morning_briefing():
        print(&#8221;\n&#9200; Heartbeat: morning briefing&#8221;)
        # Use an isolated session key so cron doesn&#8217;t pollute main chat
        session_key = &#8220;cron:morning-briefing&#8221;

        with session_locks[session_key]:
            messages = load_session(session_key)
            messages.append({
                &#8220;role&#8221;: &#8220;user&#8221;,
                &#8220;content&#8221;: &#8220;Good morning! Check today&#8217;s date and give me a motivational quote.&#8221;
            })

            response_text, messages = run_agent_turn(messages, SOUL)
            save_session(session_key, messages)

        print(f&#8221;&#129302; {response_text}\n&#8221;)
        # In production, you&#8217;d send this to Telegram/Discord too

    schedule.every().day.at(&#8221;07:30&#8221;).do(morning_briefing)

    # Run the scheduler in a background thread
    def scheduler_loop():
        while True:
            schedule.run_pending()
            time.sleep(60)

    threading.Thread(target=scheduler_loop, daemon=True).start()

# Call during startup, before run_polling()
setup_heartbeats()</code></pre><p>The key insight: each heartbeat uses its own session key (<code>cron:morning-briefing</code>). This keeps scheduled tasks from cluttering your main conversation history. The heartbeat calls the same <code>run_agent_turn</code> function &#8212; it&#8217;s just another message, triggered by a timer instead of a human.</p><p><strong>Try it out:</strong> For testing, change the schedule to run every minute:</p><pre><code>schedule.every(1).minutes.do(morning_briefing)</code></pre><p>You&#8217;ll see the heartbeat fire in your terminal, and the agent will respond. Change it back to a daily schedule when you&#8217;re done testing.</p><p>OpenClaw supports full cron expressions (<code>30 7 * * *</code>) and routes heartbeats through a separate command queue lane so they never block real-time messages.</p><h2><strong>Goal: Multi-Agent</strong></h2><p>One agent is useful. But as you add more tasks, you&#8217;ll find a single personality and toolset can&#8217;t cover everything well. A research assistant needs different instructions than a general assistant.</p><p>The fix: multiple agent configurations with routing. Each agent has its own SOUL, its own session, and you switch between them based on the message.</p><pre><code>AGENTS = {
    &#8220;main&#8221;: {
        &#8220;name&#8221;: &#8220;Jarvis&#8221;,
        &#8220;soul&#8221;: SOUL,  # our existing SOUL
        &#8220;session_prefix&#8221;: &#8220;agent:main&#8221;,
    },
    &#8220;researcher&#8221;: {
        &#8220;name&#8221;: &#8220;Scout&#8221;,
        &#8220;soul&#8221;: &#8220;&#8221;&#8220;You are Scout, a research specialist.
Your job: find information and cite sources. Every claim needs evidence.
Use tools to gather data. Be thorough but concise.
Save important findings to memory for other agents to reference.&#8221;&#8220;&#8221;,
        &#8220;session_prefix&#8221;: &#8220;agent:researcher&#8221;,
    },
}

def resolve_agent(message_text):
    &#8220;&#8221;&#8220;Route messages to the right agent based on prefix commands.&#8221;&#8220;&#8221;
    if message_text.startswith(&#8221;/research &#8220;):
        return &#8220;researcher&#8221;, message_text[len(&#8221;/research &#8220;):]
    return &#8220;main&#8221;, message_text</code></pre><p>Update <code>handle_message</code> to route messages to the right agent:</p><pre><code>async def handle_message(update: Update, context):
    user_id = str(update.effective_user.id)
    agent_id, message_text = resolve_agent(update.message.text)
    agent = AGENTS[agent_id]
    session_key = f&#8221;{agent[&#8217;session_prefix&#8217;]}:{user_id}&#8221;

    with session_locks[session_key]:
        messages = load_session(session_key)
        messages = compact_session(session_key, messages)
        messages.append({&#8221;role&#8221;: &#8220;user&#8221;, &#8220;content&#8221;: message_text})

        response_text, messages = run_agent_turn(messages, agent[&#8221;soul&#8221;])

        save_session(session_key, messages)

    await update.message.reply_text(f&#8221;[{agent[&#8217;name&#8217;]}] {response_text}&#8221;)</code></pre><p><strong>Try it out:</strong></p><pre><code><code>You: What's the weather like?
[Jarvis] It's a nice day! I'd check a weather service for exact details.

You: /research What are the best practices for Python async programming?
[Scout] Here's what I found...
    [uses web_search, save_memory to gather and store findings]
    The key practices are: 1) Use asyncio.gather for concurrent tasks...

You: What did Scout find about Python async?
[Jarvis] [uses memory_search]
    Scout's research found that the key async best practices are...
</code></code></pre><p>Each agent has its own conversation history, but they share the same memory directory. Scout saves research findings; Jarvis can search for them later. They collaborate through shared files without needing direct messaging.</p><p>OpenClaw extends this with sub-agent spawning (a parent agent can spawn a child for a focused task) and inter-agent messaging, but the core pattern is the same: each agent is just a SOUL + session + tools.</p><h2><strong>Putting It All Together</strong></h2><p>Let&#8217;s combine everything we&#8217;ve built into a single runnable script. This is a clean standalone REPL that includes every feature from the tutorial: sessions, SOUL, tools, permissions, compaction, memory, command queue, cron, and multi-agent routing.</p><pre><code>#!/usr/bin/env python3
# mini-openclaw.py - A minimal OpenClaw clone
# Run: uv run --with anthropic --with schedule python mini-openclaw.py

import anthropic
import subprocess
import json
import os
import re
import threading
import time
import schedule
from collections import defaultdict
from datetime import datetime

client = anthropic.Anthropic()

# &#9472;&#9472;&#9472; Configuration &#9472;&#9472;&#9472;

WORKSPACE = os.path.expanduser(&#8221;~/.mini-openclaw&#8221;)
SESSIONS_DIR = os.path.join(WORKSPACE, &#8220;sessions&#8221;)
MEMORY_DIR = os.path.join(WORKSPACE, &#8220;memory&#8221;)
APPROVALS_FILE = os.path.join(WORKSPACE, &#8220;exec-approvals.json&#8221;)

# &#9472;&#9472;&#9472; Agents &#9472;&#9472;&#9472;

AGENTS = {
    &#8220;main&#8221;: {
        &#8220;name&#8221;: &#8220;Jarvis&#8221;,
        &#8220;model&#8221;: &#8220;claude-sonnet-4-5-20250929&#8221;,
        &#8220;soul&#8221;: (
            &#8220;You are Jarvis, a personal AI assistant.\n&#8221;
            &#8220;Be genuinely helpful. Skip the pleasantries. Have opinions.\n&#8221;
            &#8220;You have tools &#8212; use them proactively.\n\n&#8221;
            &#8220;## Memory\n&#8221;
            f&#8221;Your workspace is {WORKSPACE}.\n&#8221;
            &#8220;Use save_memory to store important information across sessions.\n&#8221;
            &#8220;Use memory_search at the start of conversations to recall context.&#8221;
        ),
        &#8220;session_prefix&#8221;: &#8220;agent:main&#8221;,
    },
    &#8220;researcher&#8221;: {
        &#8220;name&#8221;: &#8220;Scout&#8221;,
        &#8220;model&#8221;: &#8220;claude-sonnet-4-5-20250929&#8221;,
        &#8220;soul&#8221;: (
            &#8220;You are Scout, a research specialist.\n&#8221;
            &#8220;Your job: find information and cite sources. Every claim needs evidence.\n&#8221;
            &#8220;Use tools to gather data. Be thorough but concise.\n&#8221;
            &#8220;Save important findings with save_memory for other agents to reference.&#8221;
        ),
        &#8220;session_prefix&#8221;: &#8220;agent:researcher&#8221;,
    },
}

# &#9472;&#9472;&#9472; Tools &#9472;&#9472;&#9472;

TOOLS = [
    {
        &#8220;name&#8221;: &#8220;run_command&#8221;,
        &#8220;description&#8221;: &#8220;Run a shell command&#8221;,
        &#8220;input_schema&#8221;: {
            &#8220;type&#8221;: &#8220;object&#8221;,
            &#8220;properties&#8221;: {
                &#8220;command&#8221;: {&#8221;type&#8221;: &#8220;string&#8221;, &#8220;description&#8221;: &#8220;The command to run&#8221;}
            },
            &#8220;required&#8221;: [&#8221;command&#8221;]
        }
    },
    {
        &#8220;name&#8221;: &#8220;read_file&#8221;,
        &#8220;description&#8221;: &#8220;Read a file from the filesystem&#8221;,
        &#8220;input_schema&#8221;: {
            &#8220;type&#8221;: &#8220;object&#8221;,
            &#8220;properties&#8221;: {
                &#8220;path&#8221;: {&#8221;type&#8221;: &#8220;string&#8221;, &#8220;description&#8221;: &#8220;Path to the file&#8221;}
            },
            &#8220;required&#8221;: [&#8221;path&#8221;]
        }
    },
    {
        &#8220;name&#8221;: &#8220;write_file&#8221;,
        &#8220;description&#8221;: &#8220;Write content to a file (creates directories if needed)&#8221;,
        &#8220;input_schema&#8221;: {
            &#8220;type&#8221;: &#8220;object&#8221;,
            &#8220;properties&#8221;: {
                &#8220;path&#8221;: {&#8221;type&#8221;: &#8220;string&#8221;, &#8220;description&#8221;: &#8220;Path to the file&#8221;},
                &#8220;content&#8221;: {&#8221;type&#8221;: &#8220;string&#8221;, &#8220;description&#8221;: &#8220;Content to write&#8221;}
            },
            &#8220;required&#8221;: [&#8221;path&#8221;, &#8220;content&#8221;]
        }
    },
    {
        &#8220;name&#8221;: &#8220;save_memory&#8221;,
        &#8220;description&#8221;: &#8220;Save important information to long-term memory&#8221;,
        &#8220;input_schema&#8221;: {
            &#8220;type&#8221;: &#8220;object&#8221;,
            &#8220;properties&#8221;: {
                &#8220;key&#8221;: {&#8221;type&#8221;: &#8220;string&#8221;, &#8220;description&#8221;: &#8220;Short label (e.g. &#8216;user-preferences&#8217;)&#8221;},
                &#8220;content&#8221;: {&#8221;type&#8221;: &#8220;string&#8221;, &#8220;description&#8221;: &#8220;The information to remember&#8221;}
            },
            &#8220;required&#8221;: [&#8221;key&#8221;, &#8220;content&#8221;]
        }
    },
    {
        &#8220;name&#8221;: &#8220;memory_search&#8221;,
        &#8220;description&#8221;: &#8220;Search long-term memory for relevant information&#8221;,
        &#8220;input_schema&#8221;: {
            &#8220;type&#8221;: &#8220;object&#8221;,
            &#8220;properties&#8221;: {
                &#8220;query&#8221;: {&#8221;type&#8221;: &#8220;string&#8221;, &#8220;description&#8221;: &#8220;What to search for&#8221;}
            },
            &#8220;required&#8221;: [&#8221;query&#8221;]
        }
    },
]

# &#9472;&#9472;&#9472; Permission Controls &#9472;&#9472;&#9472;

SAFE_COMMANDS = {&#8221;ls&#8221;, &#8220;cat&#8221;, &#8220;head&#8221;, &#8220;tail&#8221;, &#8220;wc&#8221;, &#8220;date&#8221;, &#8220;whoami&#8221;,
                 &#8220;echo&#8221;, &#8220;pwd&#8221;, &#8220;which&#8221;, &#8220;git&#8221;, &#8220;python&#8221;, &#8220;node&#8221;, &#8220;npm&#8221;}

def load_approvals():
    if os.path.exists(APPROVALS_FILE):
        with open(APPROVALS_FILE) as f:
            return json.load(f)
    return {&#8221;allowed&#8221;: [], &#8220;denied&#8221;: []}

def save_approval(command, approved):
    approvals = load_approvals()
    key = &#8220;allowed&#8221; if approved else &#8220;denied&#8221;
    if command not in approvals[key]:
        approvals[key].append(command)
    with open(APPROVALS_FILE, &#8220;w&#8221;) as f:
        json.dump(approvals, f, indent=2)

def check_command_safety(command):
    base_cmd = command.strip().split()[0] if command.strip() else &#8220;&#8221;
    if base_cmd in SAFE_COMMANDS:
        return &#8220;safe&#8221;
    approvals = load_approvals()
    if command in approvals[&#8221;allowed&#8221;]:
        return &#8220;approved&#8221;
    return &#8220;needs_approval&#8221;

# &#9472;&#9472;&#9472; Tool Execution &#9472;&#9472;&#9472;

def execute_tool(name, tool_input):
    if name == &#8220;run_command&#8221;:
        cmd = tool_input[&#8221;command&#8221;]
        safety = check_command_safety(cmd)
        if safety == &#8220;needs_approval&#8221;:
            print(f&#8221;\n  &#9888;&#65039;  Command: {cmd}&#8221;)
            confirm = input(&#8221;  Allow? (y/n): &#8220;).strip().lower()
            if confirm != &#8220;y&#8221;:
                save_approval(cmd, False)
                return &#8220;Permission denied by user.&#8221;
            save_approval(cmd, True)
        try:
            result = subprocess.run(
                cmd, shell=True, capture_output=True, text=True, timeout=30
            )
            output = result.stdout + result.stderr
            return output if output else &#8220;(no output)&#8221;
        except subprocess.TimeoutExpired:
            return &#8220;Command timed out after 30 seconds&#8221;
        except Exception as e:
            return f&#8221;Error: {e}&#8221;

    elif name == &#8220;read_file&#8221;:
        try:
            with open(tool_input[&#8221;path&#8221;], &#8220;r&#8221;) as f:
                return f.read()[:10000]
        except Exception as e:
            return f&#8221;Error: {e}&#8221;

    elif name == &#8220;write_file&#8221;:
        try:
            os.makedirs(os.path.dirname(tool_input[&#8221;path&#8221;]) or &#8220;.&#8221;, exist_ok=True)
            with open(tool_input[&#8221;path&#8221;], &#8220;w&#8221;) as f:
                f.write(tool_input[&#8221;content&#8221;])
            return f&#8221;Wrote to {tool_input[&#8217;path&#8217;]}&#8221;
        except Exception as e:
            return f&#8221;Error: {e}&#8221;

    elif name == &#8220;save_memory&#8221;:
        os.makedirs(MEMORY_DIR, exist_ok=True)
        filepath = os.path.join(MEMORY_DIR, f&#8221;{tool_input[&#8217;key&#8217;]}.md&#8221;)
        with open(filepath, &#8220;w&#8221;) as f:
            f.write(tool_input[&#8221;content&#8221;])
        return f&#8221;Saved to memory: {tool_input[&#8217;key&#8217;]}&#8221;

    elif name == &#8220;memory_search&#8221;:
        query = tool_input[&#8221;query&#8221;].lower()
        results = []
        if os.path.exists(MEMORY_DIR):
            for fname in os.listdir(MEMORY_DIR):
                if fname.endswith(&#8221;.md&#8221;):
                    with open(os.path.join(MEMORY_DIR, fname), &#8220;r&#8221;) as f:
                        content = f.read()
                    if any(w in content.lower() for w in query.split()):
                        results.append(f&#8221;--- {fname} ---\n{content}&#8221;)
        return &#8220;\n\n&#8221;.join(results) if results else &#8220;No matching memories found.&#8221;

    return f&#8221;Unknown tool: {name}&#8221;

# &#9472;&#9472;&#9472; Session Management &#9472;&#9472;&#9472;

def get_session_path(session_key):
    os.makedirs(SESSIONS_DIR, exist_ok=True)
    safe_key = session_key.replace(&#8221;:&#8221;, &#8220;_&#8221;).replace(&#8221;/&#8221;, &#8220;_&#8221;)
    return os.path.join(SESSIONS_DIR, f&#8221;{safe_key}.jsonl&#8221;)

def load_session(session_key):
    path = get_session_path(session_key)
    messages = []
    if os.path.exists(path):
        with open(path, &#8220;r&#8221;) as f:
            for line in f:
                if line.strip():
                    try:
                        messages.append(json.loads(line))
                    except json.JSONDecodeError:
                        continue
    return messages

def append_message(session_key, message):
    with open(get_session_path(session_key), &#8220;a&#8221;) as f:
        f.write(json.dumps(message) + &#8220;\n&#8221;)

def save_session(session_key, messages):
    with open(get_session_path(session_key), &#8220;w&#8221;) as f:
        for msg in messages:
            f.write(json.dumps(msg) + &#8220;\n&#8221;)

# &#9472;&#9472;&#9472; Compaction &#9472;&#9472;&#9472;

def estimate_tokens(messages):
    return sum(len(json.dumps(m)) for m in messages) // 4

def compact_session(session_key, messages):
    if estimate_tokens(messages) &lt; 100_000:
        return messages
    split = len(messages) // 2
    old, recent = messages[:split], messages[split:]
    print(&#8221;\n  &#128230; Compacting session history...&#8221;)
    summary = client.messages.create(
        model=&#8221;claude-sonnet-4-5-20250929&#8221;,
        max_tokens=2000,
        messages=[{
            &#8220;role&#8221;: &#8220;user&#8221;,
            &#8220;content&#8221;: (
                &#8220;Summarize this conversation concisely. Preserve key facts, &#8220;
                &#8220;decisions, and open tasks:\n\n&#8221;
                f&#8221;{json.dumps(old, indent=2)}&#8221;
            )
        }]
    )
    compacted = [{
        &#8220;role&#8221;: &#8220;user&#8221;,
        &#8220;content&#8221;: f&#8221;[Conversation summary]\n{summary.content[0].text}&#8221;
    }] + recent
    save_session(session_key, compacted)
    return compacted

# &#9472;&#9472;&#9472; Command Queue &#9472;&#9472;&#9472;

session_locks = defaultdict(threading.Lock)

# &#9472;&#9472;&#9472; Agent Loop &#9472;&#9472;&#9472;

def serialize_content(content):
    serialized = []
    for block in content:
        if hasattr(block, &#8220;text&#8221;):
            serialized.append({&#8221;type&#8221;: &#8220;text&#8221;, &#8220;text&#8221;: block.text})
        elif block.type == &#8220;tool_use&#8221;:
            serialized.append({
                &#8220;type&#8221;: &#8220;tool_use&#8221;, &#8220;id&#8221;: block.id,
                &#8220;name&#8221;: block.name, &#8220;input&#8221;: block.input
            })
    return serialized

def run_agent_turn(session_key, user_text, agent_config):
    &#8220;&#8221;&#8220;Run a full agent turn: load session, call LLM in a loop, save.&#8221;&#8220;&#8221;
    with session_locks[session_key]:
        messages = load_session(session_key)
        messages = compact_session(session_key, messages)

        user_msg = {&#8221;role&#8221;: &#8220;user&#8221;, &#8220;content&#8221;: user_text}
        messages.append(user_msg)
        append_message(session_key, user_msg)

        for _ in range(20):  # max tool-use turns
            response = client.messages.create(
                model=agent_config[&#8221;model&#8221;],
                max_tokens=4096,
                system=agent_config[&#8221;soul&#8221;],
                tools=TOOLS,
                messages=messages
            )

            content = serialize_content(response.content)
            assistant_msg = {&#8221;role&#8221;: &#8220;assistant&#8221;, &#8220;content&#8221;: content}
            messages.append(assistant_msg)
            append_message(session_key, assistant_msg)

            if response.stop_reason == &#8220;end_turn&#8221;:
                return &#8220;&#8221;.join(
                    b.text for b in response.content if hasattr(b, &#8220;text&#8221;)
                )

            if response.stop_reason == &#8220;tool_use&#8221;:
                tool_results = []
                for block in response.content:
                    if block.type == &#8220;tool_use&#8221;:
                        print(f&#8221;  &#128295; {block.name}: {json.dumps(block.input)[:100]}&#8221;)
                        result = execute_tool(block.name, block.input)
                        display = str(result)[:150]
                        print(f&#8221;     &#8594; {display}&#8221;)
                        tool_results.append({
                            &#8220;type&#8221;: &#8220;tool_result&#8221;,
                            &#8220;tool_use_id&#8221;: block.id,
                            &#8220;content&#8221;: str(result)
                        })

                results_msg = {&#8221;role&#8221;: &#8220;user&#8221;, &#8220;content&#8221;: tool_results}
                messages.append(results_msg)
                append_message(session_key, results_msg)

        return &#8220;(max turns reached)&#8221;

# &#9472;&#9472;&#9472; Multi-Agent Routing &#9472;&#9472;&#9472;

def resolve_agent(message_text):
    &#8220;&#8221;&#8220;Route messages to the right agent based on prefix commands.&#8221;&#8220;&#8221;
    if message_text.startswith(&#8221;/research &#8220;):
        return &#8220;researcher&#8221;, message_text[len(&#8221;/research &#8220;):]
    return &#8220;main&#8221;, message_text

# &#9472;&#9472;&#9472; Cron / Heartbeats &#9472;&#9472;&#9472;

def setup_heartbeats():
    def morning_check():
        print(&#8221;\n&#9200; Heartbeat: morning check&#8221;)
        result = run_agent_turn(
            &#8220;cron:morning-check&#8221;,
            &#8220;Good morning! Check today&#8217;s date and give me a motivational quote.&#8221;,
            AGENTS[&#8221;main&#8221;]
        )
        print(f&#8221;&#129302; {result}\n&#8221;)

    schedule.every().day.at(&#8221;07:30&#8221;).do(morning_check)

    def scheduler_loop():
        while True:
            schedule.run_pending()
            time.sleep(60)

    threading.Thread(target=scheduler_loop, daemon=True).start()

# &#9472;&#9472;&#9472; REPL &#9472;&#9472;&#9472;

def main():
    for d in [WORKSPACE, SESSIONS_DIR, MEMORY_DIR]:
        os.makedirs(d, exist_ok=True)

    setup_heartbeats()

    session_key = &#8220;agent:main:repl&#8221;

    print(&#8221;Mini OpenClaw&#8221;)
    print(f&#8221;  Agents: {&#8217;, &#8216;.join(a[&#8217;name&#8217;] for a in AGENTS.values())}&#8221;)
    print(f&#8221;  Workspace: {WORKSPACE}&#8221;)
    print(&#8221;  Commands: /new (reset), /research &lt;query&gt;, /quit\n&#8221;)

    while True:
        try:
            user_input = input(&#8221;You: &#8220;).strip()
        except (EOFError, KeyboardInterrupt):
            print(&#8221;\nGoodbye!&#8221;)
            break

        if not user_input:
            continue
        if user_input.lower() in [&#8221;/quit&#8221;, &#8220;/exit&#8221;, &#8220;/q&#8221;]:
            print(&#8221;Goodbye!&#8221;)
            break
        if user_input.lower() == &#8220;/new&#8221;:
            session_key = f&#8221;agent:main:repl:{datetime.now().strftime(&#8217;%Y%m%d%H%M%S&#8217;)}&#8221;
            print(&#8221;  Session reset.\n&#8221;)
            continue

        agent_id, message_text = resolve_agent(user_input)
        agent_config = AGENTS[agent_id]
        sk = (
            f&#8221;{agent_config[&#8217;session_prefix&#8217;]}:repl&#8221;
            if agent_id != &#8220;main&#8221; else session_key
        )

        response = run_agent_turn(sk, message_text, agent_config)
        print(f&#8221;\n&#129302; [{agent_config[&#8217;name&#8217;]}] {response}\n&#8221;)

if __name__ == &#8220;__main__&#8221;:
    main()</code></pre><p>Save this as <code>mini-openclaw.py</code> and run it:</p><pre><code>uv run --with anthropic --with schedule python mini-openclaw.py</code></pre><p>Here&#8217;s what a session looks like:</p><pre><code><code>Mini OpenClaw
  Agents: Jarvis, Scout
  Workspace: ~/.mini-openclaw
  Commands: /new (reset), /research &lt;query&gt;, /quit

You: Remember that my favorite restaurant is Hai Cenato and I prefer 7pm reservations

  &#128295; save_memory: {"key": "user-preferences", "content": "Favorite restaurant...
     &#8594; Saved to memory: user-preferences

&#129302; [Jarvis] Got it. Saved your restaurant preference - Hai Cenato, 7pm reservations.

You: What's in my project directory?

  &#128295; run_command: {"command": "ls"}
     &#8594; src, package.json, README.md, node_modules, ...

&#129302; [Jarvis] Your project has a standard Node.js structure with src/, package.json,
   and the usual suspects.

You: /new
  Session reset.

You: Where do I like to eat?

  &#128295; memory_search: {"query": "restaurant favorite food"}
     &#8594; --- user-preferences.md ---
        Favorite restaurant: Hai Cenato...

&#129302; [Jarvis] You like Hai Cenato, and you prefer 7pm reservations.

You: /research What are the latest trends in AI agents?

  &#128295; web_search: {"query": "AI agent trends 2025"}
     &#8594; Search results for: AI agent trends 2025
  &#128295; save_memory: {"key": "research-ai-agents", ...}
     &#8594; Saved to memory: research-ai-agents

&#129302; [Scout] Here's what I found on current AI agent trends...
</code></code></pre><p>The memory persists across sessions. Agents collaborate through shared memory files. Commands require approval. Heartbeats run in the background. All in ~400 lines.</p><h2><strong>What We&#8217;ve Learned</strong></h2><p>Starting from a simple Telegram bot, we built every major component of a persistent AI assistant:</p><p><strong>Persistent sessions</strong> (JSONL files): Crash-safe conversation memory. Each session is one file, each line is one message. Restart the process and everything is still there.</p><p><strong>SOUL.md</strong> (system prompt): A personality file that transforms a generic AI into a specific agent with consistent behavior, boundaries, and style.</p><p><strong>Tools + Agent loop</strong>: Structured tool definitions that let the AI decide when to act. The agent loop calls the LLM, executes any requested tools, feeds results back, and repeats until done.</p><p><strong>Permission controls</strong>: An allowlist of safe commands plus persistent approvals, so dangerous operations require explicit consent.</p><p><strong>The gateway pattern</strong>: One central agent with multiple interfaces. Telegram, HTTP, or any other channel - they all talk to the same sessions and the same memory.</p><p><strong>Context compaction</strong>: When conversations outgrow the context window, summarize old messages and keep recent ones. The bot keeps its knowledge without hitting token limits.</p><p><strong>Long-term memory</strong>: File-based storage with save and search tools. Knowledge that survives session resets, accessible to any agent.</p><p><strong>Command queue</strong>: Per-session locking to prevent race conditions when multiple messages arrive simultaneously.</p><p><strong>Heartbeats</strong>: Scheduled agent runs on a timer, each with its own isolated session. The agent wakes up, does its task, and goes back to sleep.</p><p><strong>Multi-agent routing</strong>: Multiple agent configurations with different SOULs and session keys, routed by message content. Agents collaborate through shared memory files.</p><p>Each of these emerged from a practical problem:</p><ul><li><p>&#8220;The AI can&#8217;t remember anything&#8221; &#8594; Sessions</p></li><li><p>&#8220;It responds like a generic chatbot&#8221; &#8594; SOUL.md</p></li><li><p>&#8220;It can only talk, not act&#8221; &#8594; Tools + Agent loop</p></li><li><p>&#8220;It runs dangerous commands without asking&#8221; &#8594; Permission controls</p></li><li><p>&#8220;I want it on all my messaging apps&#8221; &#8594; Gateway</p></li><li><p>&#8220;The conversation got too long&#8221; &#8594; Compaction</p></li><li><p>&#8220;It forgets things between sessions&#8221; &#8594; Memory</p></li><li><p>&#8220;Two messages at once corrupt the data&#8221; &#8594; Command queue</p></li><li><p>&#8220;I want it to do things automatically&#8221; &#8594; Heartbeats</p></li><li><p>&#8220;One agent can&#8217;t do everything well&#8221; &#8594; Multi-agent</p></li></ul><p>This is how you could have invented OpenClaw.</p><h2><strong>Going Further</strong></h2><p>Our prototype covers the core architecture. Here&#8217;s how OpenClaw extends each idea for production use - features worth exploring once you&#8217;ve outgrown the basics.</p><h3><strong>Browser with Semantic Snapshots</strong></h3><p>Most AI assistants can&#8217;t see the web. OpenClaw gives the agent a browser via Playwright, but instead of sending screenshots (5MB each, expensive in tokens), it uses <strong>semantic snapshots</strong> - a text representation of the page&#8217;s accessibility tree:</p><pre><code># Simplified concept
snapshot = page.accessibility.snapshot()
# Output:
# - heading &#8220;Welcome to GitHub&#8221;
# - button &#8220;Sign In&#8221; [ref=1]
# - textbox &#8220;Email&#8221; [ref=2]
# - link &#8220;Forgot password?&#8221; [ref=3]</code></pre><p>Each interactive element gets a numbered <code>ref</code> ID. When the agent wants to click something, it says &#8220;click ref=1&#8221; which maps to exactly one element on the page. No guessing, no &#8220;click the blue button near the top.&#8221; And since the snapshot is text instead of an image, it&#8217;s roughly 100x smaller than a screenshot, which means far fewer tokens per page.</p><h3><strong>Session Scoping &amp; Identity Links</strong></h3><p>Our prototype uses user ID as the session key. OpenClaw supports configurable scoping:</p><ul><li><p><code>main</code> (default): All DMs share one session - simple, great for single-user setups.</p></li><li><p><code>per-peer</code>: Each person gets one session across all channels.</p></li><li><p><code>per-channel-peer</code>: Each person per channel gets their own session.</p></li></ul><p>Identity links let you merge sessions across channels for the same person, so Alice&#8217;s Telegram and Discord conversations share the same history.</p><h3><strong>Channel Plugin System</strong></h3><p>Our prototype hardcodes Telegram + HTTP. OpenClaw uses a plugin architecture where each channel (Telegram, Discord, WhatsApp, Slack, Signal, iMessage) is a separate adapter that normalizes messages into a common format. Adding a new channel means writing one adapter, not touching any agent logic.</p><h3><strong>Vector Memory Search</strong></h3><p>Our keyword search works, but misses semantic matches (&#8221;auth bug&#8221; won&#8217;t match &#8220;authentication issues&#8221;). OpenClaw&#8217;s production memory uses a hybrid approach: vector search via SQLite with embedding extensions for semantic similarity, plus FTS5 for exact keyword matches. Configurable embedding providers include OpenAI, local models, Gemini, and Voyage.</p><h3><strong>Sub-agent Spawning</strong></h3><p>Our multi-agent setup uses manual routing. OpenClaw lets agents spawn sub-agents programmatically - a parent agent calls <code>sessions_spawn</code>, the child runs in its own context with a timeout, and returns results to the parent. This enables patterns like &#8220;research this topic in depth&#8221; where the main agent delegates to a specialist and continues when it&#8217;s done.</p><h2><strong>Next Steps</strong></h2><p>If you want to build your own:</p><ol><li><p><strong>Start with one channel</strong>: get a Telegram or Discord bot working with sessions</p></li><li><p><strong>Add tools incrementally</strong>: start with file read/write, then add shell execution</p></li><li><p><strong>Add memory when you need it</strong>: once sessions reset, you&#8217;ll want persistent memory</p></li><li><p><strong>Add channels when you outgrow one</strong>: the gateway pattern emerges naturally</p></li><li><p><strong>Add agents when tasks specialize</strong>: don&#8217;t start with 10 agents, start with 2</p></li></ol><p>Or just use OpenClaw. It&#8217;s open source and handles all the edge cases we glossed over. But now you know how it works under the hood.</p>]]></content:encoded></item><item><title><![CDATA[The Cloud Agent Thesis]]></title><description><![CDATA[Cloud agents are a different category from local agents, and the differences compound.]]></description><link>https://nader.substack.com/p/the-cloud-agent-thesis</link><guid isPermaLink="false">https://nader.substack.com/p/the-cloud-agent-thesis</guid><dc:creator><![CDATA[Nader Dabit]]></dc:creator><pubDate>Sun, 08 Feb 2026 18:39:04 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!aQ6n!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd40f5972-2f71-4c94-860c-5648e16ba9f7_1024x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Most coding agents today run inside a developer&#8217;s local workflow. You open an IDE or terminal, the agent reads the repo on your machine, proposes edits, and you approve or redirect changes as they happen.</p><p>That works well when one person is actively working with the code, but the session stays tied to that person&#8217;s laptop, local setup, open repo, and attention. Starting ten refactors means managing ten local workflows. A security fix across several repos still needs someone to clone the right projects, install dependencies, and keep the work moving.</p><p>Cloud agents move those sessions to remote infrastructure, where teams can run many sessions in parallel across any number of repos, each with the right dependencies, environment variables, and config already set up. They can start work from Slack, Jira, Linear, GitHub, an API, or a CLI, then review the PRs when they are ready. One person can start ten refactors, a security team can patch the same issue across multiple codebases, and CI can start a debugging session when a check fails, without tying the work to one laptop.</p><p>Everyone has a way to contribute. A senior engineer can start a session from the CLI. A PM can tag the agent in Slack. A junior developer can kick off a migration from a Jira ticket. A designer can report a bug and have a fix ready for review by end of day. And not every task needs a person to start it: a failed GitHub check can launch a debugging session on its own, and a weekly automation can ask Devin to update dependencies and open a PR.</p><p>This gives more people a way to request code changes. They don&#8217;t need to set up a local environment, use Git, or clone the repo (or even be able to), they just need to be able to describe the task clearly enough for the agent to attempt it and for an engineer to review the result.</p><p>I&#8217;ve been thinking about this <a href="https://x.com/dabit3/status/2019127422357238182">since joining Cognition</a> and watching how the team uses Devin internally. The useful patterns are not only about helping engineers write code faster, they come from giving the whole team access to an agent that can work across repos without depending on anyone&#8217;s laptop.</p><p>The core idea is simple: cloud agents are not just local agents running somewhere else, they are a different category, and the differences compound.</p><p>What a cloud agent actually is</p><p>A cloud agent runs on remote infrastructure. It has its own shell, IDE, and browser. It connects to your repos, CI, deployment pipeline, issue trackers, observability tools, and whatever else your team uses.</p><p>You communicate with it the way you would communicate with a remote engineer: send a task, let it work, then review the output.</p><p>The local agent model, like Cursor, Devin Local, or Claude Code in your terminal, is a human and an AI sharing one environment. You are pair programming.</p><p>The cloud agent model is delegation. You describe the task, the agent works independently, and you get back a PR.</p><p>These approaches are not mutually exclusive. They are complementary. Local agents make individual developers faster. Cloud agents open up the codebase to the rest of the organization, which matters a lot more as teams scale.</p><p>More people can request changes</p><p>At <a href="https://x.com/cognition">Cognition</a>, non-engineers tag @Devin in Slack to fix documentation, report bugs, or request small features. They don&#8217;t need Git installed. They don&#8217;t need a local dev environment. They don&#8217;t need to know what a branch is.</p><p>Many small fixes get delayed because the person who finds the issue cannot make the change themselves. Someone notices a typo in the docs, a broken link, or a small product bug. Filing a ticket feels too heavy, and they do not have the tooling to open a PR. So the issue stays there.</p><p>A cloud agent removes that extra step. The person who finds the issue can tag the agent, describe the fix, and move on. The PR shows up. Someone reviews it. The work gets done.</p><p>One agent can work across repos</p><p>A local agent sees the repo a developer has open. A cloud agent can be configured for every repo in the org.</p><p>You can ask it questions about any codebase, start tasks in any repo, and let it handle the setup. An engineer on Team A can request a small fix in Team B&#8217;s repo without cloning it, installing dependencies, or stepping away from their current environment.</p><p>That matters for cross-team work. Small changes no longer require a full local setup just to get started.</p><p>Work can run in parallel</p><p>When you use a local agent, you are usually working with it in real time. You watch it work, approve edits, and correct it as it goes. That makes sense for architecture, complex debugging, and unclear requirements. Those tasks need someone with context making decisions.</p><p>A lot of engineering work does not need that level of attention. Targeted refactors, lint fixes, CVE remediation, test coverage, dependency upgrades, documentation updates, and migrations usually need clear instructions, tests, and review.</p><p>Cloud agents let you start those tasks without staying in the loop the whole time. You can describe ten tasks, start ten sessions, and come back to ten PRs. The limiting factor becomes review, tests, and merge decisions.</p><p>Events can start sessions</p><p>Sessions can start from events in the tools your team already uses.</p><p>A bug report lands in Slack, a CI check fails in GitHub, a Linear issue gets a bug label. A Sentry, Datadog, or PagerDuty alert hits a webhook. A weekly dependency update runs on a schedule.</p><p>Each event can start a new session with the relevant context attached. Conditions can filter when it runs, like only for a specific repo, only after a failed GitHub check, or only when a Linear issue has the right label.</p><p>The action can either start a new session, message an existing session, or send a notification. Cloud agents like Devin&#8217;s automation model supports triggers, conditions, and actions across Slack, GitHub, Linear, schedules, and webhooks.</p><p>That covers work that already starts from alerts, tickets, failed checks, support reports, and scheduled maintenance. When a check fails, an agent can investigate. When a vulnerability appears, an agent can open a fix PR. When a bug report lands in the right channel, an agent can triage it or start a repro.</p><p>Event-based workflows still need limits. Invocation caps, ACU budgets, filters, and permissions keep a noisy channel or repeated CI failure from creating too many sessions. With those controls in place, an event comes in, the agent gets the context, and the work starts.</p><p>Playbooks make repeated work easier</p><p>Once a team teaches the agent a workflow, other people can reuse it.</p><p>For example, a security engineer can write a Playbook for remediating vulnerabilities from SonarQube. After that, other teams can run the same workflow instead of asking the security engineer to repeat the same steps every time.</p><p>That is where cloud agents become more useful inside large companies. The value is not only one developer using one tool. The value is a shared agent that follows the company&#8217;s conventions, uses the right tools, respects permissions, and produces work the team can review.</p><p>Code Reviews</p><p>More agent sessions create more PRs. That increases review load.</p><p>The same cloud setup that runs coding agents can also run review agents. A review agent can group related diffs, detect moved code, catch obvious bugs, and answer questions about a change with codebase context.</p><p>Teams using cloud agents heavily need automated review. Without it, people either rubber-stamp PRs or spend too much time reviewing agent output.</p><p>Where the agent connects</p><p>A local agent connects to your filesystem and terminal. A cloud agent can connect to the tools your team already uses.</p><p>Slack and Teams can start tasks and send updates. Jira and Linear can provide ticket context. GitHub, GitLab, and Bitbucket can handle PRs and code review. Sentry, Datadog, and Vercel can provide logs for production debugging. Data warehouses can answer metrics questions without pulling an engineer into the request.</p><p>Because the agent runs in the cloud, these integrations can be configured once for the organization. Individual developers do not need to set them up on their own machines.</p><p>MCP makes this easier to extend. Connect Sentry, and the agent can inspect error logs. Connect a database, and someone on the team can ask a data question in Slack and get the SQL back with the answer, so they can check the logic.</p><p>What this means for pricing</p><p>Local agents are usually priced per seat because one developer uses the tool in their own workflow.</p><p>Cloud agents are usually priced by usage because tasks can come from many places. A PM can tag the agent in Slack. A Linear ticket can start a session. A CI failure can start a debugging task. Those requests do not map cleanly to one developer seat.</p><p>The unit of value is the work completed. That is why usage-based pricing makes more sense for cloud agents.</p><p>How teams get results</p><p>Deploying a cloud agent takes more than buying access. Teams get better results when they assign specific projects, write Playbooks, add Knowledge for repo conventions, track usage, and set permissions for different groups.</p><p>The best deployments start with high-ROI work: migrations, security remediation, flaky tests, dependency upgrades, documentation, and repetitive maintenance. Those tasks are easy to describe, easy to verify, and expensive to leave sitting in the backlog.</p><p>Some companies are reporting 5 to 6x faster migrations, 70 to 90 percent of security vulnerabilities handled automatically, and every PR reviewed by an agent. Those results come from giving the agent specific work and measuring the output.</p><p>Teams that only buy seats and wait for adoption usually get weaker results. Someone has to decide what work the agent should own, how people should use it, and how PRs should be reviewed.</p><p>Two ways to use coding agents</p><p>Use local agents when you want to sit with the code and make decisions while the edits happen (tight feedback loops)</p><p>Use cloud agents when the task is clear enough to run on its own and come back as a PR.</p><p>Both workflows matter. Local agents are useful for hands-on coding. Cloud agents are useful for maintenance work, cross-repo tasks, ticket-driven work, refactors, bugs, and requests from people who do not have a local dev environment.</p><p>The teams that use both have more options. They can keep developers in the loop when judgment matters and let agents handle the work that can be reviewed after the fact.</p><p>Cloud agents as shared engineering infrastructure</p><p>Cloud agents run in a separate environment, connect to team tools, accept tasks from multiple entry points, and return PRs.</p><p>They give engineers, PMs, designers, support, and other teams a way to request code work without setting up a local environment.</p><p>Local agents help developers while they are coding. Cloud agents help teams get work done when no one has the repo open.</p><p>At @cognition, we built Devin around this from the start.</p><p>Devin is available from Slack, CLI, API, Linear, and Jira. It is already deployed at organizations like Goldman Sachs, Citi, Ramp, Cal.com, Exa, and Eight Sleep.</p><p><a href="https://devin.ai/">Try Devin</a> or <a href="https://cognition.com/get-started#company">reach out</a> to see what this looks like across an engineering org.</p>]]></content:encoded></item><item><title><![CDATA[I Spent December Seeing the Future. Then I Joined It.]]></title><description><![CDATA[Original X post here.]]></description><link>https://nader.substack.com/p/i-spent-december-seeing-the-future</link><guid isPermaLink="false">https://nader.substack.com/p/i-spent-december-seeing-the-future</guid><dc:creator><![CDATA[Nader Dabit]]></dc:creator><pubDate>Wed, 04 Feb 2026 19:29:04 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!5T7S!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff2044a56-2124-4309-9a07-8cd80c1f573a_1510x1995.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote><p>Original X post <a href="https://x.com/dabit3/status/2019127422357238182">here</a>.</p></blockquote><p>I&#8217;m excited to share that I&#8217;m joining Cognition, the team behind <a href="https://x.com/@DevinAI">Devin</a>, <a href="https://x.com/@windsurf">Windsurf</a>, <a href="https://deepwiki.com/">DeepWiki</a>, and the recently launched <a href="https://app.devin.ai/review">Devin Review.</a></p><p>I&#8217;ve been building in the AI space since my days at AWS, but really became pilled <a href="https://x.com/dabit3/status/1622330852943319048?s=20">when I launched my first viral AI app in February 2023</a>.</p><p>After spending the holidays <a href="https://x.com/dabit3/status/2006489676924989860?s=20">building</a> with Opus 4.5 and Codex 5.2, it became clear we&#8217;d hit an inflection point. Conversations with friends and colleagues confirmed they were seeing it too.</p><p>We&#8217;re entering a world where the constraint on software development shifts from writing code to knowing what to build, architecting the solution, and orchestrating agents to execute it.</p><p>Thinking about the history of programming abstraction, we went from punch cards to assembly, assembly to C, C to Python. Each leap made software more accessible, and each time, doomsayers predicted the end of programming jobs. Instead, we got exponentially more software and exponentially more programmers.</p><p>AI coding tools are the next abstraction layer. And the companies that win won&#8217;t just have good models, they&#8217;ll have the right products, the right distribution, and the right understanding of how humans and AI should collaborate.</p><h2><strong>Why Cognition Got My Attention</strong></h2><p>There are a lot of impressive teams and products out there, and I spoke with several of them.</p><p>For me what stood out about Cognition was the combination - of team + momentum + products + strong PMF... and the relatively early stage vs TAM.</p><p>Devin has merged hundreds of thousands of pull requests in production codebases at companies like <a href="https://www.goldmansachs.com/">Goldman Sachs</a>, <a href="https://www.citi.com/">Citi</a>, <a href="https://ramp.com/">Ramp</a>, <a href="http://cal.com/">Cal.com</a>, <a href="https://exa.ai/">Exa</a>, and <a href="https://www.eightsleep.com/product/pod-cover/">Eight Sleep</a> (too many household names to count).</p><p>Cognition has gone from 0 to $10B valuation in 2 years. ARR has 100x&#8217;ed in the past year. The company is ripping.</p><p>Here&#8217;s an example of how <a href="https://x.com/@cognition">@cognition</a> uses Devin internally. Anyone can tag Devin to work on any feature or bug, Devin will create the PR and even record a video demoing the update for us to review... including on the Windsurf codebase, which is 1M LOC and some of the most complex UI software in the world:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!5T7S!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff2044a56-2124-4309-9a07-8cd80c1f573a_1510x1995.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!5T7S!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff2044a56-2124-4309-9a07-8cd80c1f573a_1510x1995.png 424w, /__u/substackcdn.com/image/fetch/$s_!5T7S!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff2044a56-2124-4309-9a07-8cd80c1f573a_1510x1995.png 848w, /__u/substackcdn.com/image/fetch/$s_!5T7S!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff2044a56-2124-4309-9a07-8cd80c1f573a_1510x1995.png 1272w, /__u/substackcdn.com/image/fetch/$s_!5T7S!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff2044a56-2124-4309-9a07-8cd80c1f573a_1510x1995.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!5T7S!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff2044a56-2124-4309-9a07-8cd80c1f573a_1510x1995.png" width="1456" height="1924" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f2044a56-2124-4309-9a07-8cd80c1f573a_1510x1995.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1924,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="/__u/substackcdn.com/image/fetch/$s_!5T7S!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff2044a56-2124-4309-9a07-8cd80c1f573a_1510x1995.png 424w, /__u/substackcdn.com/image/fetch/$s_!5T7S!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff2044a56-2124-4309-9a07-8cd80c1f573a_1510x1995.png 848w, /__u/substackcdn.com/image/fetch/$s_!5T7S!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff2044a56-2124-4309-9a07-8cd80c1f573a_1510x1995.png 1272w, /__u/substackcdn.com/image/fetch/$s_!5T7S!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff2044a56-2124-4309-9a07-8cd80c1f573a_1510x1995.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Devin executes the entire development lifecycle - writing, testing, and debugging code across multiple tasks in parallel - while <a href="https://app.devin.ai/review">Devin Review</a> intelligently organizes complex diffs and catches bugs with precision to scale human code understanding without letting in AI slop.</p><p><a href="https://x.com/windsurf">Windsurf</a> brings the same production-grade validation for synchronous workflows - hundreds of enterprise customers and hundreds of thousands of daily active users + a world-class engineering team <a href="https://x.com/windsurf/status/2017334552075890903?s=20">shipping consistent improvements and polish</a> with a clear long-term goal of making it the best development environment for agentic coding.</p><h2>Cognition is an <a href="https://www.latent.space/p/agent-labs?utm_source=publication-search">Agent Lab</a></h2><p>Cognition is distinct from model labs in a crucial way: they&#8217;re product-first, model-last. Where model labs primarily research and sell <em>models</em>, agent labs primarily research and sell <em>agents</em>.</p><p>Everything Cognition builds is aimed at one goal: end-to-end software agents that ship production code.</p><p>They didn&#8217;t raise money to train a foundation model and then figure out the product. They built tools that solve real problems across the software development lifecycle, then invested in the model and infrastructure work needed to make those tools exceptional.</p><p>Different parts of the workflow demand different tools:</p><p><strong><a href="https://devin.ai">Devin</a></strong>: Your async engineering team that never sleeps. Migrate 500 repos. Knock out all new tickets. Fix every CVE. Generate tests across your entire codebase. @ Devin in Slack, move on to higher-leverage work, and come back to a completed PR.</p><p><strong><a href="https://docs.devin.ai/work-with-devin/devin-review">Devin Review</a></strong>: As coding agents proliferate, code review - not code generation - becomes the bottleneck. Devin Review uses AI to intelligently organize complex diffs, detect bugs with precision, and provide codebase-aware explanations, scaling human understanding without letting in AI slop.</p><p><strong><a href="https://windsurf.com/">Windsurf</a></strong>: When you&#8217;re architecting a new system, making complex decisions, or working through ambiguous requirements, you want AI right there in your editor, collaborating with you in real-time. Tab completion, multi-file refactoring with Cascade, tight feedback loops.</p><p><strong><a href="https://deepwiki.com/">DeepWiki</a></strong>: When you need to understand unfamiliar code - whether onboarding to a new project or navigating a massive legacy codebase - DeepWiki provides instant, accurate documentation and technical search, eliminating the friction of tribal knowledge gaps.</p><p>With more products coming soon.</p><h2><strong>Low Hype, Real Traction</strong></h2><p>The business fundamentals are remarkable, in fact it&#8217;s a little crazy that X/AI Twitter is so unaware of how things are growing.</p><p>The pricing model is usage-based (Agent Compute Units, not seats), which perfectly aligns with customer value - as teams use the tools more and get more results, they pay more.</p><p>This has enabled positive unit economics from the start while still funding accessible $20/month individual developer plans, so it&#8217;s sustainable for both sides.</p><p>It&#8217;s common to see 5-10x contract expansions mid-year - not at renewal, but proactively as teams roll out more broadly.</p><p>One banking customer went from $1.5M to over $15M in eight months, tracking their planned expansion from a pilot team to their full organization. There have since been more customers like this.</p><p>In addition to what I&#8217;ve outlined above, the team itself is remarkable. I&#8217;ll be working closely with <a href="https://x.com/@russelljkaplan">@russelljkaplan</a> <a href="https://x.com/@theodormarcu">@theodormarcu</a> <a href="https://x.com/@itsandrewgao">@itsandrewgao</a> and <a href="https://x.com/cognition/affiliates">many others</a>. And yes, there are <a href="https://cognition.ai/#job-list__header-title:~:text=founding%20team%20has-,10%20IOI%20gold%20medals,-and%20includes%20leaders">many IOI gold medalists</a>.</p><p>I also get to <a href="https://www.swyx.io/cognition">work</a> with <a href="https://x.com/@swyx">@swyx</a> again which I&#8217;m <em>very</em> excited about.</p><p>And the culture: intensely competitive, hard working, highly collaborative, obsessed with both technical excellence and enterprise polish.</p><h2><strong>Software Abundance</strong></h2><p>Software has always been more expensive than we can afford to build. There are entire categories of applications that don&#8217;t exist simply because the economics don&#8217;t work - the value delivered doesn&#8217;t justify the engineering cost.</p><p>AI coding tools change that equation. When migrations are 10x faster, when security fixes are 20x faster, when test coverage can jump 40 percentage points with minimal human effort, suddenly a lot more software becomes economically viable.</p><p>I&#8217;ve heard this called &#8220;Software Abundance,&#8221; and I think that&#8217;s right. We&#8217;re moving from a world constrained by how fast we can type code to a world constrained by how clearly we can specify what we want built.</p><p>That shift requires new tools, new skills, and new educational infrastructure. Cognition is building the tools. I&#8217;m here to help developers master the skills. I&#8217;ll be working on growth, education, developer experience, documentation, and awareness.</p><p>If you&#8217;re working with Devin or Windsurf, or if you&#8217;re just trying to figure out how to be more productive in this new agentic world, I&#8217;d love to hear from you. If you&#8217;d like to meet in person, I&#8217;ll be spending a lot more time in SF.</p><p><em>P.S. - If you have ideas for tutorials, frameworks, or educational content you&#8217;d like to see around AI-assisted development, drop me a note. I&#8217;m building the curriculum as we go.</em></p><p><em>P.P.S -</em> <a href="/__u/jobs.ashbyhq.com/cognition">We&#8217;re hiring</a></p>]]></content:encoded></item><item><title><![CDATA[You Could've Invented Claude Code ]]></title><description><![CDATA[In this post, I'll start from scratch and build up to Claude Code's architecture step by step, along the way you'll learn how to powerful agents work under the hood.]]></description><link>https://nader.substack.com/p/you-couldve-invented-claude-code</link><guid isPermaLink="false">https://nader.substack.com/p/you-couldve-invented-claude-code</guid><dc:creator><![CDATA[Nader Dabit]]></dc:creator><pubDate>Fri, 09 Jan 2026 18:45:38 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!Wmof!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92d19e34-7cf3-420f-b76c-2039b3e6e634_1280x649.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote><p>This is cross-posted from <a href="https://x.com/dabit3/status/2009668398691582315?s=20">the original on x.</a> You can also view it in full markdown <a href="https://gist.github.com/dabit3/f390e6b06d9a682bf1b05c2405d15edb">here</a>.</p></blockquote><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!Wmof!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92d19e34-7cf3-420f-b76c-2039b3e6e634_1280x649.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!Wmof!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92d19e34-7cf3-420f-b76c-2039b3e6e634_1280x649.png 424w, /__u/substackcdn.com/image/fetch/$s_!Wmof!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92d19e34-7cf3-420f-b76c-2039b3e6e634_1280x649.png 848w, /__u/substackcdn.com/image/fetch/$s_!Wmof!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92d19e34-7cf3-420f-b76c-2039b3e6e634_1280x649.png 1272w, /__u/substackcdn.com/image/fetch/$s_!Wmof!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92d19e34-7cf3-420f-b76c-2039b3e6e634_1280x649.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!Wmof!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92d19e34-7cf3-420f-b76c-2039b3e6e634_1280x649.png" width="1280" height="649" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/92d19e34-7cf3-420f-b76c-2039b3e6e634_1280x649.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:649,&quot;width&quot;:1280,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:167004,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://nader.substack.com/i/184047616?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92d19e34-7cf3-420f-b76c-2039b3e6e634_1280x649.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!Wmof!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92d19e34-7cf3-420f-b76c-2039b3e6e634_1280x649.png 424w, /__u/substackcdn.com/image/fetch/$s_!Wmof!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92d19e34-7cf3-420f-b76c-2039b3e6e634_1280x649.png 848w, /__u/substackcdn.com/image/fetch/$s_!Wmof!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92d19e34-7cf3-420f-b76c-2039b3e6e634_1280x649.png 1272w, /__u/substackcdn.com/image/fetch/$s_!Wmof!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92d19e34-7cf3-420f-b76c-2039b3e6e634_1280x649.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p>While many people use Claude Code daily, few realize that the core of what makes it powerful is surprisingly simple: it&#8217;s a loop that lets an AI read files, run commands, and iterate until a task is done.</p><p>The complexity comes from handling edge cases, building a good UX, and integrating with real development workflows.</p><p>In this post, I&#8217;ll start from scratch and build up to Claude Code&#8217;s architecture step by step, showing how you could have invented it yourself from first principles, using nothing but a terminal, an LLM API, and the desire to make AI actually useful.</p><h2><strong>End Goal: Making AI actually useful</strong></h2><p>First, let&#8217;s establish the problem we&#8217;re trying to solve.</p><p>When you use ChatGPT or Claude in a browser, you&#8217;re doing a lot of manual labor:</p><ul><li><p>Copy-paste code from the chat into files</p></li><li><p>Run commands yourself, then copy errors back</p></li><li><p>Provide context by uploading files or pasting content</p></li><li><p>Manually iterate through the fix-test-debug cycle</p></li></ul><p>You&#8217;re essentially acting as the AI&#8217;s hands. The AI thinks; you execute.</p><p><strong>What if the AI could execute too?</strong></p><p>Imagine telling an AI: &#8220;Fix the bug in <a href="https://auth.py/">auth.py</a>&#8220; and walking away. When you come back, the bug is fixed. The AI read the file, understood it, tried a fix, ran the tests, saw them fail, tried another approach, and eventually succeeded.</p><p>This is what an <em>agent</em> does. It&#8217;s an AI that can:</p><ol><li><p>Take actions in the real world (read files, run commands)</p></li><li><p>Observe the results</p></li><li><p>Decide what to do next</p></li><li><p>Repeat until the task is complete</p></li></ol><p>Let&#8217;s build one from scratch.</p><h2><strong>The Simplest Possible Agent</strong></h2><p>Let&#8217;s start with the absolute minimum: an AI that can run a single bash command.</p><pre><code><code>#!/bin/bash
# agent-v0.sh - The simplest possible agent

PROMPT="$1"

# Ask Claude what command to run
RESPONSE=$(curl -s https://api.anthropic.com/v1/messages \
  -H "x-api-key: $ANTHROPIC_API_KEY" \
  -H "content-type: application/json" \
  -H "anthropic-version: 2023-06-01" \
  -d '{
    "model": "claude-opus-4-5-20251101",
    "max_tokens": 1024,
    "messages": [{"role": "user", "content": "'"$PROMPT"'\n\nRespond with ONLY a bash command. No markdown, no explanation, no code blocks."}]
  }')

# Extract the command from response
COMMAND=$(echo "$RESPONSE" | jq -r '.content[0].text')

echo "AI suggests: $COMMAND"
read -r -p "Run this command? (y/n) " CONFIRM

if [ "$CONFIRM" = "y" ]; then
  eval "$COMMAND"
fi</code></code></pre><p>Usage</p><pre><code><code>bash agent-v0.sh "list all Python files in this directory"
# AI suggests: ls *.py
# Run this command? (y/n)</code></code></pre><p>This is... not very useful. The AI can suggest <em>one</em> command, then you&#8217;re back to doing everything manually.</p><p>But here&#8217;s the key insight: <strong>what if we put this in a loop?</strong></p><h2><strong>Goal: Creating the agent loop</strong></h2><p>The fundamental insight behind all AI agents is the <strong>agent loop</strong>:</p><pre><code><code>while (task not complete):
    1. AI decides what to do next
    2. Execute that action
    3. Show AI the result
    4. Go back to step 1</code></code></pre><p>Let&#8217;s implement exactly this. The AI needs to tell us:</p><ul><li><p>What action to take</p></li><li><p>Whether it&#8217;s done</p></li></ul><p>We&#8217;ll use a simple JSON format:</p><pre><code><code>#!/bin/bash
# agent-v1.sh - Agent with a loop

SYSTEM_PROMPT='You are a helpful assistant that can run bash commands.

When the user gives you a task, respond with JSON in this exact format:
{"action": "bash", "command": "your command here"}

When the task is complete, respond with:
{"action": "done", "message": "explanation of what was accomplished"}

Only respond with JSON. No other text.'

# We'll build messages as a JSON array (using jq for proper escaping)
MESSAGES="[]"

run_agent() {
    local USER_MSG="$1"
    
    # Add initial user message using jq to handle escaping
    MESSAGES=$(echo "$MESSAGES" | jq --arg msg "$USER_MSG" '. + [{"role": "user", "content": $msg}]')
    
    while true; do
        # Build the request body properly with jq
        REQUEST_BODY=$(jq -n \
            --arg model "claude-opus-4-5-20251101" \
            --arg system "$SYSTEM_PROMPT" \
            --argjson messages "$MESSAGES" \
            '{model: $model, max_tokens: 1024, system: $system, messages: $messages}')
        
        # Call the API
        RESPONSE=$(curl -s https://api.anthropic.com/v1/messages \
          -H "x-api-key: $ANTHROPIC_API_KEY" \
          -H "content-type: application/json" \
          -H "anthropic-version: 2023-06-01" \
          -d "$REQUEST_BODY")
        # Echo the response for debugging
        AI_TEXT=$(echo "$RESPONSE" | jq -r '.content[0].text')
        
        # Add assistant message to history
        MESSAGES=$(echo "$MESSAGES" | jq --arg msg "$AI_TEXT" '. + [{"role": "assistant", "content": $msg}]')
        
        # Parse the action from the JSON response
        ACTION=/__u/nader.substack.com/$(echo "$AI_TEXT" | jq -r '.action // empty')
        
        if [ -z "$ACTION" ]; then
            echo "&#10060; Could not parse response: $AI_TEXT"
            break
        elif [ "$ACTION" = "done" ]; then
            echo "&#9989; $(echo "$AI_TEXT" | jq -r '.message')"
            break
        elif [ "$ACTION" = "bash" ]; then
            COMMAND=$(echo "$AI_TEXT" | jq -r '.command')
            echo "&#128295; Running: $COMMAND"
            
            # Execute and capture output
            OUTPUT=$(eval "$COMMAND" 2&gt;&amp;1)
            echo "$OUTPUT"
            
            # Feed result back to AI
            MESSAGES=$(echo "$MESSAGES" | jq --arg msg "Command output: $OUTPUT" '. + [{"role": "user", "content": $msg}]')
        else
            echo "&#10060; Unknown action: $ACTION"
            break
        fi
    done
}

run_agent "$1"</code></code></pre><p>Now we have something that can actually <em>iterate</em>:</p><pre><code><code>bash agent-v1.sh "Create a file called hello.py that prints hello world, then run it"

# &#128295; Running: echo 'print("hello world")' &gt; hello.py
# &#128295; Running: python hello.py
# hello world
# &#9989; Created hello.py and executed it successfully. It prints "hello world".</code></code></pre><p>The AI ran <em>two</em> commands and then told us it was done. We&#8217;ve created an agent loop!</p><p><strong>But wait! We&#8217;re executing arbitrary commands with no safety checks.</strong> The AI could rm -rf / and we&#8217;d blindly execute it.</p><h2><strong>Goal: Adding permission controls</strong></h2><p>Let&#8217;s add a human-in-the-loop for dangerous operations. First, we define a function that wraps command execution with a safety check:</p><pre><code><code># Add this function BEFORE run_agent() in your script
execute_with_permission() {
    local COMMAND="$1"
    
    # Check if command seems dangerous
    if echo "$COMMAND" | grep -qE 'rm |sudo |chmod |curl.*\|.*sh'; then
        # Use &gt;&amp;2 to print to stderr, so prompts display immediately
        # (stdout gets captured by the $(...) in the agent loop)
        echo "&#9888;&#65039;  Potentially dangerous command: $COMMAND" &gt;&amp;2
        echo "Allow? (y/n)" &gt;&amp;2
        read CONFIRM
        if [ "$CONFIRM" != "y" ]; then
            echo "DENIED BY USER"
            return 1
        fi
    fi
    
    eval "$COMMAND" 2&gt;&amp;1
}</code></code></pre><p>Then, inside the agent loop, we replace the direct eval call with our new function:</p><pre><code><code>        # BEFORE:
        OUTPUT=$(eval "$COMMAND" 2&gt;&amp;1)
        
        # AFTER (with permission check):
        OUTPUT=$(execute_with_permission "$COMMAND")</code></code></pre><p>That&#8217;s it! The function sits between the AI&#8217;s request and actual execution, giving you a chance to block dangerous commands. When denied, you can feed that back to the AI so it can try a different approach.</p><p>Try it out:</p><pre><code><code># Create a test file
echo 'print("hello world")' &gt; hello.py

# Ask the agent to delete it
bash agent-v1.sh "delete the file hello.py"

# &#128295; Running: rm hello.py
# &#9888;&#65039;  Potentially dangerous command: rm hello.py
# Allow? (y/n)</code></code></pre><p>Type y to allow the deletion, or n to block it.</p><p>This is the beginning of a permission system. Claude Code takes this much further with:</p><ul><li><p>Tool-specific permissions (file edits vs. bash commands)</p></li><li><p>Pattern-based allowlists (Bash(npm test:*) allows any npm test command)</p></li><li><p>Session-level &#8220;accept all&#8221; modes for when you trust the AI</p></li></ul><p>The key insight: <strong>the human should be able to control what the AI can do</strong>, but with enough granularity that it&#8217;s not annoying.</p><h2><strong>Goal: Beyond bash - Adding tools</strong></h2><p>Running bash commands is powerful, but it&#8217;s also:</p><ul><li><p><strong>Dangerous</strong>: unlimited access to the system</p></li><li><p><strong>Inefficient</strong>: reading a file shouldn&#8217;t spawn a subprocess</p></li><li><p><strong>Imprecise</strong>: output parsing is fragile</p></li></ul><p>What if we gave the AI <em>structured tools</em> instead?</p><p>We&#8217;ll switch to Python here since it handles JSON and API calls more cleanly:</p><pre><code><code># agent-v2.py - Agent with structured tools
import anthropic
import json
import os

client = anthropic.Anthropic()

TOOLS = [
    {
        "name": "read_file",
        "description": "Read the contents of a file",
        "input_schema": {
            "type": "object",
            "properties": {
                "path": {"type": "string", "description": "Path to the file"}
            },
            "required": ["path"]
        }
    },
    {
        "name": "write_file",
        "description": "Write content to a file",
        "input_schema": {
            "type": "object",
            "properties": {
                "path": {"type": "string", "description": "Path to the file"},
                "content": {"type": "string", "description": "Content to write"}
            },
            "required": ["path", "content"]
        }
    },
    {
        "name": "run_bash",
        "description": "Run a bash command",
        "input_schema": {
            "type": "object",
            "properties": {
                "command": {"type": "string", "description": "The command to run"}
            },
            "required": ["command"]
        }
    }
]

def execute_tool(name, input):
    """Execute a tool and return the result."""
    if name == "read_file":
        try:
            with open(input["path"], "r") as f:
                return f.read()
        except Exception as e:
            return f"Error: {e}"
    
    elif name == "write_file":
        try:
            with open(input["path"], "w") as f:
                f.write(input["content"])
            return f"Successfully wrote to {input['path']}"
        except Exception as e:
            return f"Error: {e}"
    
    elif name == "run_bash":
        import subprocess
        result = subprocess.run(
            input["command"], 
            shell=True, 
            capture_output=True, 
            text=True
        )
        return result.stdout + result.stderr

def run_agent(task):
    """Main agent loop."""
    messages = [{"role": "user", "content": task}]
    
    while True:
        response = client.messages.create(
            model="claude-opus-4-5-20251101",
            max_tokens=4096,
            tools=TOOLS,
            messages=messages
        )
        
        # Check if we're done
        if response.stop_reason == "end_turn":
            # Extract final text response
            for block in response.content:
                if hasattr(block, "text"):
                    print(f"&#9989; {block.text}")
            break
        
        # Process tool uses
        if response.stop_reason == "tool_use":
            # Add assistant's response to history
            messages.append({"role": "assistant", "content": response.content})
            
            tool_results = []
            for block in response.content:
                if block.type == "tool_use":
                    print(f"&#128295; {block.name}: {json.dumps(block.input)}")
                    result = execute_tool(block.name, block.input)
                    print(f"   &#8594; {result[:200]}...")  # Truncate for display
                    
                    tool_results.append({
                        "type": "tool_result",
                        "tool_use_id": block.id,
                        "content": result
                    })
            
            # Add results to conversation
            messages.append({"role": "user", "content": tool_results})

if __name__ == "__main__":
    import sys
    run_agent(sys.argv[1])</code></code></pre><p>Now we&#8217;re using Anthropic&#8217;s native tool use API. This is much better because:</p><ol><li><p><strong>Type safety</strong>: the AI knows exactly what parameters each tool accepts</p></li><li><p><strong>Explicit actions</strong>: reading a file is a <em>read_file</em> call, not cat</p></li><li><p><strong>Controlled surface area</strong>: we decide what tools exist</p></li></ol><p>Try it out:</p><pre><code><code># Create a test file for the agent to work with
cat &gt; main.py &lt;&lt; 'EOF'
def calculate(x, y):
    return x + y

def greet(name):
    print(f"Hello, {name}!")
EOF

# Run the agent
uv run --with anthropic python agent-v2.py "Read main.py and add a docstring to the first function"

# &#128295; read_file: {"path": "main.py"}
#    &#8594; def calculate(x, y):...
# &#128295; write_file: {"path": "main.py", "content": "def calculate(x, y):\n    \"\"\"Calculate..."}
#    &#8594; Successfully wrote to main.py
# &#9989; I've added a docstring to the calculate function explaining its purpose.</code></code></pre><h2><strong>Goal: Making edits precise</strong></h2><p>Our <strong>write_file</strong> tool has a problem: it replaces the entire file. If the AI makes a small change to a 1000-line file, it has to output all 1000 lines. This is:</p><ul><li><p><strong>Expensive</strong>: more output tokens = more cost</p></li><li><p><strong>Error-prone</strong>: the AI might accidentally drop lines</p></li><li><p><strong>Slow</strong>: generating that much text takes time</p></li></ul><p>What if we had a tool for <em>surgical edits</em>?</p><pre><code><code>{
    "name": "edit_file",
    "description": "Make a precise edit to a file by replacing a unique string",
    "input_schema": {
        "type": "object",
        "properties": {
            "path": {"type": "string"},
            "old_str": {"type": "string", "description": "Exact string to find (must be unique in file)"},
            "new_str": {"type": "string", "description": "String to replace it with"}
        },
        "required": ["path", "old_str", "new_str"]
    }
}</code></code></pre><p>Implementation:</p><pre><code><code>def edit_file(path, old_str, new_str):
    with open(path, "r") as f:
        content = f.read()
    
    # Ensure the string is unique
    count = content.count(old_str)
    if count == 0:
        return f"Error: '{old_str}' not found in file"
    if count &gt; 1:
        return f"Error: '{old_str}' found {count} times. Must be unique."
    
    new_content = content.replace(old_str, new_str)
    with open(path, "w") as f:
        f.write(new_content)
    
    return f"Successfully replaced text in {path}"</code></code></pre><p>This is exactly how Claude Code&#8217;s <a href="https://platform.claude.com/docs/en/agents-and-tools/tool-use/text-editor-tool#str-replace">str_replace</a> tool works. The requirement for uniqueness might seem annoying, but it&#8217;s actually a feature:</p><ul><li><p>Forces the AI to include enough context to be unambiguous</p></li><li><p>Creates a natural diff that&#8217;s easy for humans to review</p></li><li><p>Prevents accidental mass replacements</p></li></ul><h2><strong>Goal: Searching the Codebase</strong></h2><p>So far our agent can read files it knows about. But what about a task like &#8220;find where the authentication bug is&#8221;?</p><p>The AI needs to <em>search</em> the codebase. Let&#8217;s add tools for that.</p><pre><code><code>SEARCH_TOOLS = [
    {
        "name": "glob",
        "description": "Find files matching a pattern",
        "input_schema": {
            "type": "object",
            "properties": {
                "pattern": {"type": "string", "description": "Glob pattern (e.g., '**/*.py')"}
            },
            "required": ["pattern"]
        }
    },
    {
        "name": "grep",
        "description": "Search for a pattern in files",
        "input_schema": {
            "type": "object",
            "properties": {
                "pattern": {"type": "string", "description": "Regex pattern to search for"},
                "path": {"type": "string", "description": "Directory or file to search in"}
            },
            "required": ["pattern"]
        }
    }
]</code></code></pre><p>Now the AI can:</p><ol><li><p><strong>glob(&#8221;**/*.py&#8221;)</strong> &#8594; find all Python files</p></li><li><p><strong>grep(&#8221;def authenticate&#8221;, &#8220;src/&#8221;)</strong> &#8594; find authentication code</p></li><li><p><strong>read_file(&#8221;src/auth.py&#8221;)</strong> &#8594; fead the relevant file</p></li><li><p><strong>edit_file(...)</strong> &#8594; fix the bug</p></li></ol><p>This is the pattern: give the AI tools to <em>explore</em>, and it can navigate codebases it&#8217;s never seen before.</p><h2><strong>Goal: Context management</strong></h2><p>Here&#8217;s a problem you&#8217;ll hit quickly: <strong>context windows are finite</strong>.</p><p>If you&#8217;re working on a large codebase, the conversation might look like:</p><ul><li><p>User: &#8220;Fix the bug in authentication&#8221;</p></li><li><p>AI: reads 10 files, runs 20 commands, tries 3 approaches</p></li><li><p>...conversation is now 100,000 tokens</p></li><li><p>AI: <em>runs out of context and starts forgetting earlier information</em></p></li></ul><p>How do we handle this?</p><p><strong>Option 1: summarization (compaction)</strong></p><p>When context gets too long, summarize what happened:</p><pre><code><code>def compact_conversation(messages):
    """Summarize the conversation to free up context."""
    summary_prompt = """Summarize this conversation concisely, preserving:
    - The original task
    - Key findings and decisions
    - Current state of the work
    - What still needs to be done"""
    
    summary = client.messages.create(
        model="claude-opus-4-5-20251101",
        max_tokens=2000,
        messages=[
            {"role": "user", "content": f"{messages}\n\n{summary_prompt}"}
        ]
    )
    
    return [{"role": "user", "content": f"Previous work summary:\n{summary}"}]</code></code></pre><p><strong>Option 2: sub-agents (delegation)</strong></p><p>For complex tasks, spawn a sub-agent with its own context:</p><pre><code><code>def delegate_to_subagent(task, tools_allowed):
    """Spawn a sub-agent for a focused task."""
    result = run_agent(
        task=task,
        tools=tools_allowed,
        max_turns=10  # Prevent infinite loops
    )
    # Only return the result, not the full conversation
    return result.final_answer</code></code></pre><p>This is why Claude Code has the concept of <strong>subagents: </strong>specialized agents that handle focused tasks in their own context, returning just the results.</p><h2><strong>Goal: the system prompt</strong></h2><p>We&#8217;ve been glossing over something important: <strong>how does the AI know how to behave?</strong></p><p>The system prompt is where you encode:</p><ul><li><p>The AI&#8217;s identity and capabilities</p></li><li><p>Guidelines for tool usage</p></li><li><p>Project-specific context</p></li><li><p>Behavioral rules</p></li></ul><p>Here&#8217;s a simplified version of what makes Claude Code effective:</p><pre><code><code>SYSTEM_PROMPT = """You are an AI assistant that helps with software development tasks.
You have access to the following tools:
- read_file: Read file contents
- write_file: Create or overwrite files
- edit_file: Make precise edits to existing files
- glob: Find files by pattern
- grep: Search for patterns in files
- bash: Run shell commands

## Guidelines

### Before making changes:
1. Understand the task fully before acting
2. Read relevant files to understand context
3. Plan your approach

### When editing code:
1. Use edit_file for small changes (preferred)
2. Use write_file only for new files or complete rewrites
3. Run tests after changes when possible
4. If tests fail, analyze the error and iterate

### General principles:
- Be concise but thorough
- Explain your reasoning briefly
- Ask for clarification if the task is ambiguous
- If you're stuck, say so instead of guessing

## Current Directory
You are working in: {current_directory}
"""</code></code></pre><p>But here&#8217;s the problem: what if the project has specific conventions? What if the team uses a particular testing framework, or has a non-standard directory structure?</p><h2><strong>Goal: Project-Specific Context (<a href="https://claude.md/">CLAUDE.md</a>)</strong></h2><p>Claude Code solves this with <strong><a href="https://claude.md/">CLAUDE.md</a></strong> - a file at the project root that gets automatically included in context:</p><pre><code><code># CLAUDE.md

## Project Overview
This is a FastAPI application for user authentication.

## Key Commands
- `make test`: Run all tests
- `make lint`: Run linting
- `make dev`: Start development server

## Architecture
- `src/api/`: API routes
- `src/models/`: Database models
- `src/services/`: Business logic
- `tests/`: Test files (mirror src/ structure)

## Conventions
- All functions must have type hints
- Use pydantic for request/response models
- Write tests before implementing features (TDD)

## Known Issues
- The /auth/refresh endpoint has a race condition (see issue #142)</code></code></pre><p>Now the AI knows:</p><ul><li><p>How to run tests for <em>this</em> project</p></li><li><p>Where to find things</p></li><li><p>What conventions to follow</p></li><li><p>Known gotchas to watch out for</p></li></ul><p>This is one of Claude Code&#8217;s most powerful features: project knowledge that travels with the code.</p><h2><strong>Putting it all together</strong></h2><p>Let&#8217;s see what we&#8217;ve built. The core of an AI coding agent is this loop:</p><p><strong>1. Setup (runs once)</strong></p><ul><li><p>Load the <strong>system prompt</strong> with tool descriptions, behavioral guidelines, and project context (<a href="https://claude.md/">CLAUDE.md</a>)</p></li><li><p>Initialize an empty <strong>conversation history</strong></p></li></ul><p><strong>2. Agent Loop (repeats until done)</strong></p><ul><li><p>Send conversation history to the <strong>LLM</strong></p></li><li><p>LLM decides: <em>use a tool</em> or <em>respond to user</em></p></li><li><p>If <strong>tool use</strong>:</p></li></ul><ul><li><p>Check permissions (prompt user if dangerous)</p></li><li><p>Execute the tool (read_file, edit_file, bash, glob, grep, etc.)</p></li><li><p>Add the result to conversation history</p></li><li><p>Loop back to step 2</p></li></ul><ul><li><p>If <strong>final answer</strong>:</p><ul><li><p>Display response to user</p></li><li><p>Done</p></li></ul></li></ul><p>That&#8217;s it. Every AI coding agent, from our 50-line bash script to Claude Code, follows this pattern.</p><p>Now let&#8217;s build a complete, working mini-Claude Code that you can actually use. It combines everything we&#8217;ve learned: the agent loop, structured tools, permission checks, and an interactive REPL:</p><pre><code><code>#!/usr/bin/env python3
# mini-claude-code.py - A minimal Claude Code clone

import anthropic
import subprocess
import os
import json

client = anthropic.Anthropic()

TOOLS = [
    {
        "name": "read_file",
        "description": "Read the contents of a file",
        "input_schema": {
            "type": "object",
            "properties": {
                "path": {"type": "string", "description": "Path to the file"}
            },
            "required": ["path"]
        }
    },
    {
        "name": "write_file",
        "description": "Write content to a file (creates or overwrites)",
        "input_schema": {
            "type": "object",
            "properties": {
                "path": {"type": "string", "description": "Path to the file"},
                "content": {"type": "string", "description": "Content to write"}
            },
            "required": ["path", "content"]
        }
    },
    {
        "name": "list_files",
        "description": "List files in a directory",
        "input_schema": {
            "type": "object",
            "properties": {
                "path": {"type": "string", "description": "Directory path (default: current directory)"}
            }
        }
    },
    {
        "name": "run_command",
        "description": "Run a shell command",
        "input_schema": {
            "type": "object",
            "properties": {
                "command": {"type": "string", "description": "The command to run"}
            },
            "required": ["command"]
        }
    }
]

DANGEROUS_PATTERNS = ["rm ", "sudo ", "chmod ", "mv ", "cp ", "&gt; ", "&gt;&gt;"]

def check_permission(tool_name, tool_input):
    """Check if an action requires user permission."""
    if tool_name == "run_command":
        cmd = tool_input.get("command", "")
        if any(p in cmd for p in DANGEROUS_PATTERNS):
            print(f"\n&#9888;&#65039;  Potentially dangerous command: {cmd}")
            response = input("Allow? (y/n): ").strip().lower()
            return response == "y"
    elif tool_name == "write_file":
        path = tool_input.get("path", "")
        print(f"\n&#128221; Will write to: {path}")
        response = input("Allow? (y/n): ").strip().lower()
        return response == "y"
    return True

def execute_tool(name, tool_input):
    """Execute a tool and return the result."""
    if name == "read_file":
        path = tool_input["path"]
        try:
            with open(path, "r") as f:
                content = f.read()
            return f"Contents of {path}:\n{content}"
        except Exception as e:
            return f"Error reading file: {e}"

    elif name == "write_file":
        path = tool_input["path"]
        content = tool_input["content"]
        try:
            with open(path, "w") as f:
                f.write(content)
            return f"&#9989; Successfully wrote to {path}"
        except Exception as e:
            return f"Error writing file: {e}"

    elif name == "list_files":
        path = tool_input.get("path", ".")
        try:
            files = os.listdir(path)
            return f"Files in {path}:\n" + "\n".join(f"  {f}" for f in sorted(files))
        except Exception as e:
            return f"Error listing files: {e}"

    elif name == "run_command":
        cmd = tool_input["command"]
        try:
            result = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=30)
            output = result.stdout + result.stderr
            return f"$ {cmd}\n{output}" if output else f"$ {cmd}\n(no output)"
        except subprocess.TimeoutExpired:
            return f"Command timed out after 30 seconds"
        except Exception as e:
            return f"Error running command: {e}"

    return f"Unknown tool: {name}"

def agent_loop(user_message, conversation_history):
    """Run the agent loop until the task is complete."""
    conversation_history.append({"role": "user", "content": user_message})

    while True:
        # Call Claude
        response = client.messages.create(
            model="claude-opus-4-5-20251101",
            max_tokens=4096,
            system=f"You are a helpful coding assistant. Working directory: {os.getcwd()}",
            tools=TOOLS,
            messages=conversation_history
        )

        # Add assistant response to history
        conversation_history.append({"role": "assistant", "content": response.content})

        # Check if we're done (no tool use)
        if response.stop_reason == "end_turn":
            # Print the final text response
            for block in response.content:
                if hasattr(block, "text"):
                    print(f"\n&#129302; {block.text}")
            break

        # Process tool calls
        tool_results = []
        for block in response.content:
            if block.type == "tool_use":
                tool_name = block.name
                tool_input = block.input

                print(f"\n&#128295; {tool_name}: {json.dumps(tool_input)}")

                # Check permissions
                if not check_permission(tool_name, tool_input):
                    result = "Permission denied by user"
                    print(f"   &#128683; {result}")
                else:
                    result = execute_tool(tool_name, tool_input)
                    # Truncate long output for display
                    display = result[:200] + "..." if len(result) &gt; 200 else result
                    print(f"   &#8594; {display}")

                tool_results.append({
                    "type": "tool_result",
                    "tool_use_id": block.id,
                    "content": result
                })

        # Add tool results to conversation
        conversation_history.append({"role": "user", "content": tool_results})

    return conversation_history

def main():
    print("Mini Claude Code")
    print(" Type your requests, or 'quit' to exit.\n")

    conversation_history = []

    while True:
        try:
            user_input = input("You: ").strip()
        except (EOFError, KeyboardInterrupt):
            print("\nGoodbye!")
            break

        if not user_input:
            continue
        if user_input.lower() in ["quit", "exit", "q"]:
            print("Goodbye!")
            break

        conversation_history = agent_loop(user_input, conversation_history)

if __name__ == "__main__":
    main()</code></code></pre><p>Save this as <a href="https://mini-claude-code.py/">mini-claude-code.py</a> and run it:</p><pre><code><code>uv run --with anthropic python mini-claude-code.py</code></code></pre><p>Here&#8217;s what a session looks like:</p><pre><code><code>Mini Claude Code
 Type your requests, or 'quit' to exit.

You: create a python file that prints the fibonacci sequence up to n

&#128295; write_file: {"path": "fibonacci.py", "content": "def fibonacci(n):\n    ..."}

&#128221; Will write to: fibonacci.py
Allow? (y/n): y
   &#8594; &#9989; Successfully wrote to fibonacci.py

&#129302; I've created fibonacci.py with a function that prints the Fibonacci sequence.
   Would you like me to run it to test it?

You: yes, run it with n=10

&#128295; run_command: {"command": "python fibonacci.py 10"}
   &#8594; $ python fibonacci.py 10
     0 1 1 2 3 5 8 13 21 34

&#129302; The script works correctly! It printed the first 10 Fibonacci numbers.

You: quit
Goodbye!</code></code></pre><p>That&#8217;s a working mini Claude Code clone in ~150 lines. It has:</p><ul><li><p><strong>Interactive REPL</strong>: keeps conversation context between prompts</p></li><li><p><strong>Multiple tools</strong>: read, write, list files, run commands</p></li><li><p><strong>Permission checks</strong>: asks before writing files or running dangerous commands</p></li><li><p><strong>Conversation memory</strong>: each follow-up builds on previous context</p></li></ul><p>This is essentially what Claude Code does, plus:</p><ul><li><p>A polished terminal UI</p></li><li><p>Sophisticated permission system</p></li><li><p>Context compaction when conversations get long</p></li><li><p>Subagent delegation for complex tasks</p></li><li><p>Hooks for custom automation</p></li><li><p>Integration with git and other dev tools</p></li></ul><h2><strong>The Claude Agent SDK</strong></h2><p>If you want to build on this foundation without reinventing the wheel, Anthropic offers the <strong><a href="https://platform.claude.com/docs/en/agent-sdk/overview">Claude Agent SDK</a></strong>. It&#8217;s the same engine that powers Claude Code, exposed as a library.</p><p>Here&#8217;s what our simple agent looks like using the SDK:</p><pre><code><code>import { query } from "@anthropic-ai/claude-agent-sdk";

for await (const message of query({
  prompt: "Fix the bug in auth.py",
  options: {
    model: "claude-opus-4-5-20251101",
    allowedTools: ["Read", "Edit", "Bash", "Glob", "Grep"],
    maxTurns: 50
  }
})) {
  if (message.type === "assistant") {
    for (const block of message.message.content) {
      if ("text" in block) {
        console.log(block.text);
      } else if ("name" in block) {
        console.log(`Using tool: ${block.name}`);
      }
    }
  }
}</code></code></pre><p>The SDK handles:</p><ul><li><p>The agent loop (so you don&#8217;t have to)</p></li><li><p>All the built-in tools (Read, Write, Edit, Bash, Glob, Grep, etc.)</p></li><li><p>Permission management</p></li><li><p>Context tracking</p></li><li><p>Sub-agent coordination</p></li></ul><h2><strong>What We&#8217;ve Learned</strong></h2><p>Starting from a simple bash script, we discovered:</p><ol><li><p><strong>The agent loop</strong>: AI decides &#8594; execute &#8594; observe &#8594; repeat</p></li><li><p><strong>Structured tools</strong>: better than raw bash for safety and precision</p></li><li><p><strong>Surgical edits</strong>: str_replace beats full file rewrites</p></li><li><p><strong>Search tools</strong>: let the AI explore codebases</p></li><li><p><strong>Context management</strong>: compaction and delegation handle long tasks</p></li><li><p><strong>Project knowledge</strong>: </p><p><a href="https://claude.md/">CLAUDE.md</a></p><p> gives project-specific context</p></li></ol><p>Each of these emerged from a practical problem:</p><ul><li><p>&#8220;How do I make the AI do more than one thing?&#8221; &#8594; agent loop</p></li><li><p>&#8220;How do I prevent it from destroying my system?&#8221; &#8594; permission system</p></li><li><p>&#8220;How do I make edits efficient?&#8221; &#8594; str_replace tool</p></li><li><p>&#8220;How does it find code it doesn&#8217;t know about?&#8221; &#8594; search tools</p></li><li><p>&#8220;What happens when context runs out?&#8221; &#8594; compaction</p></li><li><p>&#8220;How does it know my project&#8217;s conventions?&#8221; &#8594; </p><p><a href="https://claude.md/">CLAUDE.md</a></p></li></ul><p>This is how you could have invented Claude Code. The core ideas are simple.</p><p>The complexity comes from handling edge cases, building a good UX, and integrating with real development workflows.</p><h2><strong>Next Steps</strong></h2><p>If you want to build your own agents:</p><ol><li><p><strong>Start simple</strong>: a basic agent loop with 2-3 tools</p></li><li><p><strong>Add tools incrementally</strong>: each new capability should solve a real problem</p></li><li><p><strong>Handle errors gracefully</strong>: tools fail; your agent should recover</p></li><li><p><strong>Test on real tasks</strong>: the edge cases will teach you what&#8217;s missing</p></li><li><p><strong>Consider using the Claude Agent SDK</strong>: why reinvent the wheel?</p></li></ol><p>The future of software development is agents that can actually <em>do</em> things. Now we know how they work!</p><p><em>Resources:</em></p><ul><li><p><a href="https://platform.claude.com/docs/en/agent-sdk/overview">Claude Agent SDK Documentation</a></p></li><li><p><a href="https://code.claude.com/docs">Claude Code Documentation</a></p></li><li><p><a href="https://docs.anthropic.com/">Anthropic API Reference</a></p></li></ul>]]></content:encoded></item><item><title><![CDATA[The Complete Guide to Building Agents with the Claude Agent SDK]]></title><description><![CDATA[The fast path to get up and running your own custom AI Agents.]]></description><link>https://nader.substack.com/p/the-complete-guide-to-building-agents</link><guid isPermaLink="false">https://nader.substack.com/p/the-complete-guide-to-building-agents</guid><dc:creator><![CDATA[Nader Dabit]]></dc:creator><pubDate>Thu, 08 Jan 2026 06:51:56 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!EFCT!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7c4b6acc-a9c2-4da0-ba7f-d46e4b5d40b1_1280x640.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote><p>This is cross-posted from <a href="https://x.com/dabit3/status/2009131298250428923?s=20">this post on x</a>.  You can also view it in markdown in it&#8217;s entirety <a href="https://gist.github.com/dabit3/93a5afe8171753d0dbfd41c80033171d">here</a>. </p></blockquote><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!EFCT!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7c4b6acc-a9c2-4da0-ba7f-d46e4b5d40b1_1280x640.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!EFCT!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7c4b6acc-a9c2-4da0-ba7f-d46e4b5d40b1_1280x640.png 424w, /__u/substackcdn.com/image/fetch/$s_!EFCT!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7c4b6acc-a9c2-4da0-ba7f-d46e4b5d40b1_1280x640.png 848w, /__u/substackcdn.com/image/fetch/$s_!EFCT!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7c4b6acc-a9c2-4da0-ba7f-d46e4b5d40b1_1280x640.png 1272w, /__u/substackcdn.com/image/fetch/$s_!EFCT!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7c4b6acc-a9c2-4da0-ba7f-d46e4b5d40b1_1280x640.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!EFCT!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7c4b6acc-a9c2-4da0-ba7f-d46e4b5d40b1_1280x640.png" width="1280" height="640" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/7c4b6acc-a9c2-4da0-ba7f-d46e4b5d40b1_1280x640.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:640,&quot;width&quot;:1280,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:38995,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://nader.substack.com/i/183878353?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7c4b6acc-a9c2-4da0-ba7f-d46e4b5d40b1_1280x640.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!EFCT!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7c4b6acc-a9c2-4da0-ba7f-d46e4b5d40b1_1280x640.png 424w, /__u/substackcdn.com/image/fetch/$s_!EFCT!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7c4b6acc-a9c2-4da0-ba7f-d46e4b5d40b1_1280x640.png 848w, /__u/substackcdn.com/image/fetch/$s_!EFCT!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7c4b6acc-a9c2-4da0-ba7f-d46e4b5d40b1_1280x640.png 1272w, /__u/substackcdn.com/image/fetch/$s_!EFCT!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7c4b6acc-a9c2-4da0-ba7f-d46e4b5d40b1_1280x640.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p>If you&#8217;ve used Claude Code, you&#8217;ve seen what an AI agent can actually do: read files, run commands, edit code, figure out the steps to accomplish a task.</p><p>And you know it doesn&#8217;t just help you write code, it takes ownership of problems and works through them the way a thoughtful engineer would.</p><p>The <a href="https://platform.claude.com/docs/en/agent-sdk/overview">Claude Agent SDK</a> is the same engine, yours to point at whatever problem you want, so you can easily build agents of your own.</p><p>It&#8217;s the infrastructure behind Claude Code, exposed as a library. You get the agent loop, the built-in tools, the context management, basically everything you&#8217;d otherwise have to build yourself.</p><p>This guide walks through building a code review agent from scratch. By the end, you&#8217;ll have something that can analyze a codebase, find bugs and security issues, and return structured feedback.</p><p>More importantly, you&#8217;ll understand how the SDK works so you can build whatever you actually need.</p><h2><strong>What we&#8217;re building</strong></h2><p>Our code review agent will:</p><ol><li><p>Analyze a codebase for bugs and security issues</p></li><li><p>Read files and search through code autonomously</p></li><li><p>Provide structured, actionable feedback</p></li><li><p>Track its progress as it works</p></li></ol><h2><strong>The stack</strong></h2><p>&#8226; <strong>Runtime</strong> - <a href="https://docs.anthropic.com/en/docs/claude-code">Claude Code CLI</a><br>&#8226; <strong>SDK</strong> - <a href="https://www.npmjs.com/package/@anthropic-ai/claude-agent-sdk">@anthropic-ai/claude-agent-sdk</a> <br>&#8226; <strong>Language</strong> - TypeScript <br>&#8226; <strong>Model</strong> - Claude Opus 4.5</p><h2><strong>What the SDK gives you</strong></h2><p>If you&#8217;ve built agents with the raw API, you know the pattern: call the model, check if it wants to use a tool, execute the tool, feed the result back, repeat until done. This can get tedious when building anything non-trivial.</p><p>The SDK handles that loop:</p><pre><code><code>// Without the SDK: You manage the loop
let response = await client.messages.create({...});
while (response.stop_reason === "tool_use") {
  const result = yourToolExecutor(response.tool_use);
  response = await client.messages.create({ tool_result: result, ... });
}

// With the SDK: Claude manages it
for await (const message of query({ prompt: "Fix the bug in auth.py" })) {
  console.log(message); // Claude reads files, finds bugs, edits code
}</code></code></pre><p>You also get working tools out of the box:</p><p>&#8226; <strong>Read - r</strong>ead any file in the working directory<br>&#8226; <strong>Write - c</strong>reate new files<br>&#8226; <strong>Edit - m</strong>ake precise edits to existing files<br>&#8226; <strong>Bash - r</strong>un terminal commands<br>&#8226; <strong>Glob- f</strong>ind files by pattern<br>&#8226; <strong>Grep - s</strong>earch file contents with regex<br>&#8226; <strong>WebSearch - s</strong>earch the web<br>&#8226; <strong>WebFetch - f</strong>etch and parse web pages</p><p>You don&#8217;t have to implement any of this yourself.</p><h2><strong>Prerequisites</strong></h2><ol><li><p>Node.js 18+ installed</p></li><li><p>An Anthropic API key (<a href="https://console.anthropic.com/">get one here</a>)</p></li></ol><h2><strong>Getting started</strong></h2><p><strong>Step 1: Install Claude Code CLI</strong></p><p>The Agent SDK uses Claude Code as its runtime:</p><pre><code><code>npm install -g @anthropic-ai/claude-code</code></code></pre><p>After installing, run <strong>claude</strong> in your terminal and follow the prompts to authenticate.</p><p><strong>Step 2: Create your project</strong></p><pre><code><code>mkdir code-review-agent &amp;&amp; cd code-review-agent
npm init -y
npm install @anthropic-ai/claude-agent-sdk
npm install -D typescript @types/node tsx</code></code></pre><p>Step 3: Set your API key</p><pre><code><code>export ANTHROPIC_API_KEY=your-api-key</code></code></pre><h2><strong>Your first agent</strong></h2><p>Create <strong>agent.ts</strong>:</p><pre><code><code>import { query } from "@anthropic-ai/claude-agent-sdk";

async function main() {
  for await (const message of query({
    prompt: "What files are in this directory?",
    options: {
      model: "opus",
      allowedTools: ["Glob", "Read"],
      maxTurns: 250
    }
  })) {
    if (message.type === "assistant") {
      for (const block of message.message.content) {
        if ("text" in block) {
          console.log(block.text);
        }
      }
    }
    
    if (message.type === "result") {
      console.log("\nDone:", message.subtype);
    }
  }
}

main();</code></code></pre><p>Run it:</p><pre><code><code>npx tsx agent.ts
</code></code></pre><p>Claude will use the <strong>Glob</strong> tool to list files and tell you what it found.</p><h2><strong>Understanding the message stream</strong></h2><p>The <strong>query() </strong>function returns an async generator that streams messages as Claude works. Here are the key message types:</p><pre><code><code>for await (const message of query({ prompt: "..." })) {
  switch (message.type) {
    case "system":
      // Session initialization info
      if (message.subtype === "init") {
        console.log("Session ID:", message.session_id);
        console.log("Available tools:", message.tools);
      }
      break;
      
    case "assistant":
      // Claude's responses and tool calls
      for (const block of message.message.content) {
        if ("text" in block) {
          console.log("Claude:", block.text);
        } else if ("name" in block) {
          console.log("Tool call:", block.name);
        }
      }
      break;
      
    case "result":
      // Final result
      console.log("Status:", message.subtype); // "success" or error type
      console.log("Cost:", message.total_cost_usd);
      break;
  }
}</code></code></pre><h2><strong>Building a code review agent</strong></h2><p>Now let&#8217;s build something useful. Create <strong>review-agent.ts</strong>:</p><pre><code><code>import { query } from "@anthropic-ai/claude-agent-sdk";

async function reviewCode(directory: string) {
  console.log(`\n&#128269; Starting code review for: ${directory}\n`);
  
  for await (const message of query({
    prompt: `Review the code in ${directory} for:
1. Bugs and potential crashes
2. Security vulnerabilities  
3. Performance issues
4. Code quality improvements

Be specific about file names and line numbers.`,
    options: {
      model: "opus",
      allowedTools: ["Read", "Glob", "Grep"],
      permissionMode: "bypassPermissions", // Auto-approve read operations
      maxTurns: 250
    }
  })) {
    // Show Claude's analysis as it happens
    if (message.type === "assistant") {
      for (const block of message.message.content) {
        if ("text" in block) {
          console.log(block.text);
        } else if ("name" in block) {
          console.log(`\n&#128193; Using ${block.name}...`);
        }
      }
    }
    
    // Show completion status
    if (message.type === "result") {
      if (message.subtype === "success") {
        console.log(`\n&#9989; Review complete! Cost: $${message.total_cost_usd.toFixed(4)}`);
      } else {
        console.log(`\n&#10060; Review failed: ${message.subtype}`);
      }
    }
  }
}

// Review the current directory
reviewCode(".");</code></code></pre><p><strong>Testing It Out</strong></p><p>Create a file with some intentional issues. Create <strong>example.ts</strong>:</p><pre><code><code>function processUsers(users: any) {
  for (let i = 0; i &lt;= users.length; i++) { // Off-by-one error
    console.log(users[i].name.toUpperCase()); // No null check
  }
}

function connectToDb(password: string) {
  const connectionString = `postgres://admin:${password}@localhost/db`;
  console.log("Connecting with:", connectionString); // Logging sensitive data
}

async function fetchData(url) { // Missing type annotation
  const response = await fetch(url);
  return response.json(); // No error handling
}</code></code></pre><p>Run the review:</p><p>bash</p><pre><code><code>npx tsx review-agent.ts</code></code></pre><p>Claude will identify the bugs, security issues, and suggest fixes.</p><h2><strong>Adding Structured Output</strong></h2><p>For programmatic use, you&#8217;ll want structured data. The SDK supports JSON Schema output:</p><pre><code><code>import { query } from "@anthropic-ai/claude-agent-sdk";

const reviewSchema = {
  type: "object",
  properties: {
    issues: {
      type: "array",
      items: {
        type: "object",
        properties: {
          severity: { type: "string", enum: ["low", "medium", "high", "critical"] },
          category: { type: "string", enum: ["bug", "security", "performance", "style"] },
          file: { type: "string" },
          line: { type: "number" },
          description: { type: "string" },
          suggestion: { type: "string" }
        },
        required: ["severity", "category", "file", "description"]
      }
    },
    summary: { type: "string" },
    overallScore: { type: "number" }
  },
  required: ["issues", "summary", "overallScore"]
};

async function reviewCodeStructured(directory: string) {
  for await (const message of query({
    prompt: `Review the code in ${directory}. Identify all issues.`,
    options: {
      model: "opus",
      allowedTools: ["Read", "Glob", "Grep"],
      permissionMode: "bypassPermissions",
      maxTurns: 250,
      outputFormat: {
        type: "json_schema",
        schema: reviewSchema
      }
    }
  })) {
    if (message.type === "result" &amp;&amp; message.subtype === "success") {
      const review = message.structured_output as {
        issues: Array&lt;{
          severity: string;
          category: string;
          file: string;
          line?: number;
          description: string;
          suggestion?: string;
        }&gt;;
        summary: string;
        overallScore: number;
      };
      
      console.log(`\n&#128202; Code Review Results\n`);
      console.log(`Score: ${review.overallScore}/100`);
      console.log(`Summary: ${review.summary}\n`);
      
      for (const issue of review.issues) {
        const icon=/__u/nader.substack.com/issue.severity === "critical" ? "&#128308;" :
                     issue.severity === "high" ? "&#128992;" :
                     issue.severity === "medium" ? "&#128993;" : "&#128994;";
        console.log(`${icon} [${issue.category.toUpperCase()}] ${issue.file}${issue.line ? `:${issue.line}` : ""}`);
        console.log(`   ${issue.description}`);
        if (issue.suggestion) {
          console.log(`   &#128161; ${issue.suggestion}`);
        }
        console.log();
      }
    }
  }
}

reviewCodeStructured(".");</code></code></pre><h2><strong><a href="https://platform.claude.com/docs/en/agent-sdk/permissions">Handling permissions</a></strong></h2><p>By default, the SDK asks for approval before executing tools. You can customize this:</p><p><strong>Permission modes</strong></p><pre><code><code>options: {
  // Standard mode - prompts for approval
  permissionMode: "default",
  
  // Auto-approve file edits
  permissionMode: "acceptEdits",
  
  // No prompts (use with caution)
  permissionMode: "bypassPermissions"
}</code></code></pre><p><strong>Custom permission handler</strong></p><p>For fine-grained control, use <strong>canUseTool</strong>:</p><pre><code><code>options: {
  canUseTool: async (toolName, input) =&gt; {
    // Allow all read operations
    if (["Read", "Glob", "Grep"].includes(toolName)) {
      return { behavior: "allow", updatedInput: input };
    }
    
    // Block writes to certain files
    if (toolName === "Write" &amp;&amp; input.file_path?.includes(".env")) {
      return { behavior: "deny", message: "Cannot modify .env files" };
    }
    
    // Allow everything else
    return { behavior: "allow", updatedInput: input };
  }
}</code></code></pre><h2><strong><a href="https://platform.claude.com/docs/en/agent-sdk/subagents">Creating subagents</a></strong></h2><p>For complex tasks, you can create specialized subagents:</p><pre><code><code>import { query, AgentDefinition } from "@anthropic-ai/claude-agent-sdk";

async function comprehensiveReview(directory: string) {
  for await (const message of query({
    prompt: `Perform a comprehensive code review of ${directory}. 
Use the security-reviewer for security issues and test-analyzer for test coverage.`,
    options: {
      model: "opus",
      allowedTools: ["Read", "Glob", "Grep", "Task"], // Task enables subagents
      permissionMode: "bypassPermissions",
      maxTurns: 250,
      agents: {
        "security-reviewer": {
          description: "Security specialist for vulnerability detection",
          prompt: `You are a security expert. Focus on:
- SQL injection, XSS, CSRF vulnerabilities
- Exposed credentials and secrets
- Insecure data handling
- Authentication/authorization issues`,
          tools: ["Read", "Grep", "Glob"],
          model: "sonnet"
        } as AgentDefinition,
        
        "test-analyzer": {
          description: "Test coverage and quality analyzer",
          prompt: `You are a testing expert. Analyze:
- Test coverage gaps
- Missing edge cases
- Test quality and reliability
- Suggestions for additional tests`,
          tools: ["Read", "Grep", "Glob"],
          model: "haiku" // Use faster model for simpler analysis
        } as AgentDefinition
      }
    }
  })) {
    if (message.type === "assistant") {
      for (const block of message.message.content) {
        if ("text" in block) {
          console.log(block.text);
        } else if ("name" in block &amp;&amp; block.name === "Task") {
          console.log(`\n&#129302; Delegating to: ${(block.input as any).subagent_type}`);
        }
      }
    }
  }
}

comprehensiveReview(".");</code></code></pre><h2><strong><a href="https://platform.claude.com/docs/en/agent-sdk/sessions">Session management</a></strong></h2><p>For multi-turn conversations, capture and resume sessions:</p><pre><code><code>import { query } from "@anthropic-ai/claude-agent-sdk";

async function interactiveReview() {
  let sessionId: string | undefined;
  
  // Initial review
  for await (const message of query({
    prompt: "Review this codebase and identify the top 3 issues",
    options: {
      model: "opus",
      allowedTools: ["Read", "Glob", "Grep"],
      permissionMode: "bypassPermissions",
      maxTurns: 250
    }
  })) {
    if (message.type === "system" &amp;&amp; message.subtype === "init") {
      sessionId = message.session_id;
    }
    // ... handle messages
  }
  
  // Follow-up question using same session
  if (sessionId) {
    for await (const message of query({
      prompt: "Now show me how to fix the most critical issue",
      options: {
        resume: sessionId, // Continue the conversation
        allowedTools: ["Read", "Glob", "Grep"],
        maxTurns: 250
      }
    })) {
      // Claude remembers the previous context
    }
  }
}</code></code></pre><h2><strong><a href="https://platform.claude.com/docs/en/agent-sdk/hooks">Using hooks</a></strong></h2><p>Hooks let you intercept and customize agent behavior:</p><pre><code><code>import { query, HookCallback, PreToolUseHookInput } from "@anthropic-ai/claude-agent-sdk";

// Hook callbacks receive three arguments:
// 1. input - details about the event (tool name, arguments, etc.)
// 2. toolUseId - correlates PreToolUse and PostToolUse events for the same call
// 3. context - contains AbortSignal for cancellation
const auditLogger: HookCallback = async (input, toolUseId, { signal }) =&gt; {
  if (input.hook_event_name === "PreToolUse") {
    const preInput = input as PreToolUseHookInput;
    console.log(`[AUDIT] ${new Date().toISOString()} - ${preInput.tool_name}`);
  }
  return {}; // Return empty object to allow the operation
};

const blockDangerousCommands: HookCallback = async (input, toolUseId, { signal }) =&gt; {
  if (input.hook_event_name === "PreToolUse") {
    const preInput = input as PreToolUseHookInput;
    if (preInput.tool_name === "Bash") {
      const command = (preInput.tool_input as any).command || "";
      if (command.includes("rm -rf") || command.includes("sudo")) {
        return {
          hookSpecificOutput: {
            hookEventName: "PreToolUse",
            permissionDecision: "deny",  // Block the tool from executing
            permissionDecisionReason: "Dangerous command blocked"
          }
        };
      }
    }
  }
  return {};
};

for await (const message of query({
  prompt: "Clean up temporary files",
  options: {
    model: "opus",
    allowedTools: ["Bash", "Glob"],
    maxTurns: 50,
    hooks: {
      // PreToolUse fires before each tool executes
      // Other hooks: PostToolUse, Stop, SessionStart, SessionEnd, etc.
      PreToolUse: [
        // Each entry has an optional matcher (regex) and an array of callbacks
        // No matcher = runs for ALL tool calls
        { hooks: [auditLogger] },
        
        // matcher: 'Bash' = only runs when tool_name matches 'Bash'
        // Use regex for multiple tools: 'Bash|Write|Edit'
        { matcher: "Bash", hooks: [blockDangerousCommands] }
      ]
    }
  }
})) {
  if (message.type === "assistant") {
    for (const block of message.message.content) {
      if ("text" in block) {
        console.log(block.text);
      }
    }
  }
}</code></code></pre><h2><strong>Custom tool calling</strong></h2><p>Tools are how agents interact with the world - reading files, calling APIs, querying databases, running code. The SDK includes built-in tools for common operations (filesystem, shell, web), but most agents will need custom tools to access your own systems.</p><p><strong>The raw API pattern</strong></p><p>Without the SDK, you manage the tool loop yourself:</p><pre><code>// 1. Define tools with their schemas
const tools = [{
  name: "get_weather",
  description: "Get current weather for a city",
  input_schema: {
    type: "object",
    properties: {
      city: { type: "string", description: "City name" }
    },
    required: ["city"]
  }
}];

// 2. Write an executor for each tool
function executeTool(name: string, input: any): string {
  if (name === "get_weather") {
    return fetchWeatherAPI(input.city);
  }
  throw new Error(`Unknown tool: ${name}`);
}

// 3. Run the agent loop
const messages = [{ role: "user", content: "What's the weather in Tokyo?" }];

let response = await client.messages.create({
  model: "claude-opus-4-5-20251101",
  tools,
  messages
});

while (response.stop_reason === "tool_use") {
  messages.push({ role: "assistant", content: response.content });
  
  const toolResults = response.content
    .filter(block =&gt; block.type === "tool_use")
    .map(toolUse =&gt; ({
      type: "tool_result",
      tool_use_id: toolUse.id,
      content: executeTool(toolUse.name, toolUse.input)
    }));
  
  messages.push({ role: "user", content: toolResults });
  response = await client.messages.create({ model, tools, messages });
}

const textBlock = response.content.find(block =&gt; block.type === "text");
if (textBlock &amp;&amp; textBlock.type === "text") {
  console.log("Final response:", textBlock.text);
}</code></pre><p>Key points:</p><ul><li><p>Claude decides when to use tools based on the user&#8217;s request and tool descriptions</p></li><li><p>You execute the tools and return results</p></li><li><p>The loop continues until Claude has enough information (<code>stop_reason: "end_turn"</code>)</p></li><li><p>Message history grows with each iteration - the API is stateless, so every request needs the full conversation</p></li></ul><p><strong>What the SDK handles</strong></p><p>When you use built-in tools like <code>allowedTools: ["Read", "Glob"]</code>, the SDK manages all of this automatically - definitions, execution, and the loop.</p><p>For custom tools, you need a way to define them so the SDK can do the same. That&#8217;s what MCP provides.</p><h2><strong>Adding custom tools with <a href="https://platform.claude.com/docs/en/agent-sdk/mcp">MCP</a></strong></h2><p>Extend Claude with custom tools using Model Context Protocol:</p><pre><code><code>import { query, tool, createSdkMcpServer } from "@anthropic-ai/claude-agent-sdk";
import { z } from "zod";

// Create your custom MCP server
const customServer = createSdkMcpServer({
  name: "code-metrics",
  version: "1.0.0",
  tools: [
    // Define a custom tool using the `tool` helper
    // Arguments: name, description, input schema, handler function
    tool(
      "analyze_complexity",
      "Calculate cyclomatic complexity for a file",
      {
        // Zod schema defines what inputs the tool accepts
        filePath: z.string().describe("Path to the file to analyze")
      },
      // Handler function - runs when Claude calls the tool
      async (args) =&gt; {
        // In real implementation, calculate actual complexity 
        const complexity = Math.floor(Math.random() * 20) + 1;
        
        // Return format required by MCP - array of content blocks
        return {
          content: [{
            type: "text",
            text: `Cyclomatic complexity for ${args.filePath}: ${complexity}`
          }]
        };
      }
    )
  ]
});

async function analyzeCode(filePath: string) {
  for await (const message of query({
    prompt: `Analyze the complexity of ${filePath}`,
    options: {
      model: "opus",
      
      // Register the custom MCP server
      // The key ("code-metrics") becomes part of the tool name
      mcpServers: {
        "code-metrics": customServer
      },
      
      // Specify which tools Claude can use
      // MCP tools follow the pattern: mcp__&lt;server-name&gt;__&lt;tool-name&gt;
      allowedTools: ["Read", "mcp__code-metrics__analyze_complexity"],
      
      // Maximum number of back-and-forth turns before stopping
      maxTurns: 50
    }
  })) {
    // Handle assistant messages (Claude's responses and tool calls)
    if (message.type === "assistant") {
      for (const block of message.message.content) {
        // Text blocks contain Claude's written responses
        if ("text" in block) {
          console.log(block.text);
        }
      }
    }
    
    // Handle the final result when the agent loop completes
    if (message.type === "result") {
      console.log("Done:", message.subtype); // "success" or an error type
    }
  }
}

analyzeCode("main.ts");</code></code></pre><h2><strong><a href="https://platform.claude.com/docs/en/agent-sdk/cost-tracking">Cost tracking</a></strong></h2><p>Track API costs for billing:</p><pre><code><code>for await (const message of query({ prompt: "..." })) {
  if (message.type === "result" &amp;&amp; message.subtype === "success") {
    console.log("Total cost:", message.total_cost_usd);
    console.log("Token usage:", message.usage);
    
    // Per-model breakdown (useful with subagents)
    for (const [model, usage] of Object.entries(message.modelUsage)) {
      console.log(`${model}: $${usage.costUSD.toFixed(4)}`);
    }
  }
}</code></code></pre><h2><strong>Production code review agent</strong></h2><p>Here&#8217;s a production-ready agent that ties everything together:</p><pre><code><code>import { query, AgentDefinition } from "@anthropic-ai/claude-agent-sdk";

interface ReviewResult {
  issues: Array&lt;{
    severity: "low" | "medium" | "high" | "critical";
    category: "bug" | "security" | "performance" | "style";
    file: string;
    line?: number;
    description: string;
    suggestion?: string;
  }&gt;;
  summary: string;
  overallScore: number;
}

const reviewSchema = {
  type: "object",
  properties: {
    issues: {
      type: "array",
      items: {
        type: "object",
        properties: {
          severity: { type: "string", enum: ["low", "medium", "high", "critical"] },
          category: { type: "string", enum: ["bug", "security", "performance", "style"] },
          file: { type: "string" },
          line: { type: "number" },
          description: { type: "string" },
          suggestion: { type: "string" }
        },
        required: ["severity", "category", "file", "description"]
      }
    },
    summary: { type: "string" },
    overallScore: { type: "number" }
  },
  required: ["issues", "summary", "overallScore"]
};

async function runCodeReview(directory: string): Promise&lt;ReviewResult | null&gt; {
  console.log(`\n${"=".repeat(50)}`);
  console.log(`&#128269; Code Review Agent`);
  console.log(`&#128193; Directory: ${directory}`);
  console.log(`${"=".repeat(50)}\n`);

  let result: ReviewResult | null = null;

  for await (const message of query({
    prompt: `Perform a thorough code review of ${directory}.

Analyze all source files for:
1. Bugs and potential runtime errors
2. Security vulnerabilities
3. Performance issues
4. Code quality and maintainability

Be specific with file paths and line numbers where possible.`,
    options: {
      model: "opus",
      allowedTools: ["Read", "Glob", "Grep", "Task"],
      permissionMode: "bypassPermissions",
      maxTurns: 250,
      outputFormat: {
        type: "json_schema",
        schema: reviewSchema
      },
      agents: {
        "security-scanner": {
          description: "Deep security analysis for vulnerabilities",
          prompt: `You are a security expert. Scan for:
- Injection vulnerabilities (SQL, XSS, command injection)
- Authentication and authorization flaws
- Sensitive data exposure
- Insecure dependencies`,
          tools: ["Read", "Grep", "Glob"],
          model: "sonnet"
        } as AgentDefinition
      }
    }
  })) {
    // Progress updates
    if (message.type === "assistant") {
      for (const block of message.message.content) {
        if ("name" in block) {
          if (block.name === "Task") {
            console.log(`&#129302; Delegating to: ${(block.input as any).subagent_type}`);
          } else {
            console.log(`&#128194; ${block.name}: ${getToolSummary(block)}`);
          }
        }
      }
    }

    // Final result
    if (message.type === "result") {
      if (message.subtype === "success" &amp;&amp; message.structured_output) {
        result = message.structured_output as ReviewResult;
        console.log(`\n&#9989; Review complete! Cost: $${message.total_cost_usd.toFixed(4)}`);
      } else {
        console.log(`\n&#10060; Review failed: ${message.subtype}`);
      }
    }
  }

  return result;
}

function getToolSummary(block: any): string {
  const input = block.input || {};
  switch (block.name) {
    case "Read": return input.file_path || "file";
    case "Glob": return input.pattern || "pattern";
    case "Grep": return `"${input.pattern}" in ${input.path || "."}`;
    default: return "";
  }
}

function printResults(result: ReviewResult) {
  console.log(`\n${"=".repeat(50)}`);
  console.log(`&#128202; REVIEW RESULTS`);
  console.log(`${"=".repeat(50)}\n`);
  
  console.log(`Score: ${result.overallScore}/100`);
  console.log(`Issues Found: ${result.issues.length}\n`);
  console.log(`Summary: ${result.summary}\n`);
  
  const byCategory = {
    critical: result.issues.filter(i =&gt; i.severity === "critical"),
    high: result.issues.filter(i =&gt; i.severity === "high"),
    medium: result.issues.filter(i =&gt; i.severity === "medium"),
    low: result.issues.filter(i =&gt; i.severity === "low")
  };
  
  for (const [severity, issues] of Object.entries(byCategory)) {
    if (issues.length === 0) continue;
    
    const icon=/__u/nader.substack.com/severity === "critical" ? "&#128308;" :
                 severity === "high" ? "&#128992;" :
                 severity === "medium" ? "&#128993;" : "&#128994;";
    
    console.log(`\n${icon} ${severity.toUpperCase()} (${issues.length})`);
    console.log("-".repeat(30));
    
    for (const issue of issues) {
      const location = issue.line ? `${issue.file}:${issue.line}` : issue.file;
      console.log(`\n[${issue.category}] ${location}`);
      console.log(`  ${issue.description}`);
      if (issue.suggestion) {
        console.log(`  &#128161; ${issue.suggestion}`);
      }
    }
  }
}

// Run the review
async function main() {
  const directory = process.argv[2] || ".";
  const result = await runCodeReview(directory);
  
  if (result) {
    printResults(result);
  }
}

main().catch(console.error);</code></code></pre><p>Run it:</p><pre><code><code>npx tsx review-agent.ts ./src
</code></code></pre><h2><strong>What&#8217;s next</strong></h2><p>The code review agent covers the essentials: query(), allowedTools, structured output, subagents, and permissions. </p><p>If you want to go deeper:</p><p>More capabilities</p><ul><li><p><a href="https://platform.claude.com/docs/en/agent-sdk/file-checkpointing">File checkpointing</a> - track and revert file changes</p></li><li><p><a href="https://platform.claude.com/docs/en/agent-sdk/skills">Skills</a> - package reusable capabilities</p></li></ul><p>Production deployment</p><ul><li><p><a href="https://platform.claude.com/docs/en/agent-sdk/hosting">Hosting</a> - deploy in containers and CI/CD</p></li><li><p><a href="https://platform.claude.com/docs/en/agent-sdk/secure-deployment">Secure deployment</a> - sandboxing and credential management</p></li></ul><p>Full reference</p><ul><li><p><a href="https://platform.claude.com/docs/en/agent-sdk/typescript">TypeScript SDK reference </a></p></li><li><p><a href="https://platform.claude.com/docs/en/agent-sdk/python">Python SDK reference</a></p></li></ul><blockquote><p>This guide covers V1 of the SDK. </p><p><a href="https://platform.claude.com/docs/en/agent-sdk/typescript-v2-preview">V2</a> is currently in development. I will update this guide with V2 once it&#8217;s released and stable.<br><br>If you&#8217;re interested in building verifiable agents, check out the work we&#8217;re doing at EigenCloud <a href="https://developers.eigencloud.xyz/?utm_source=x&amp;utm_medium=social&amp;utm_campaign=claude_sdk_tutorial">here</a></p></blockquote>]]></content:encoded></item><item><title><![CDATA[Building Apps with World ID]]></title><description><![CDATA[How to build a sybil-resistant app with World ID device authentication]]></description><link>https://nader.substack.com/p/building-apps-with-world-id</link><guid isPermaLink="false">https://nader.substack.com/p/building-apps-with-world-id</guid><dc:creator><![CDATA[Nader Dabit]]></dc:creator><pubDate>Fri, 16 Feb 2024 22:27:06 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!yaF2!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feb3db25c-6fb3-484a-a382-fc01402d276c_1700x606.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!yaF2!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feb3db25c-6fb3-484a-a382-fc01402d276c_1700x606.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!yaF2!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feb3db25c-6fb3-484a-a382-fc01402d276c_1700x606.png 424w, /__u/substackcdn.com/image/fetch/$s_!yaF2!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feb3db25c-6fb3-484a-a382-fc01402d276c_1700x606.png 848w, /__u/substackcdn.com/image/fetch/$s_!yaF2!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feb3db25c-6fb3-484a-a382-fc01402d276c_1700x606.png 1272w, /__u/substackcdn.com/image/fetch/$s_!yaF2!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feb3db25c-6fb3-484a-a382-fc01402d276c_1700x606.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!yaF2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feb3db25c-6fb3-484a-a382-fc01402d276c_1700x606.png" width="1456" height="519" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/eb3db25c-6fb3-484a-a382-fc01402d276c_1700x606.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:519,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:278241,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!yaF2!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feb3db25c-6fb3-484a-a382-fc01402d276c_1700x606.png 424w, /__u/substackcdn.com/image/fetch/$s_!yaF2!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feb3db25c-6fb3-484a-a382-fc01402d276c_1700x606.png 848w, /__u/substackcdn.com/image/fetch/$s_!yaF2!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feb3db25c-6fb3-484a-a382-fc01402d276c_1700x606.png 1272w, /__u/substackcdn.com/image/fetch/$s_!yaF2!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feb3db25c-6fb3-484a-a382-fc01402d276c_1700x606.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><blockquote><p>You can test out the app we&#8217;ll be building in this tutorial <a href="https://worldid-auth.vercel.app/">here</a>. The final codebase is <a href="https://github.com/dabit3/worldid-auth">here</a>. </p></blockquote><p>World ID enables you to prove you&#8217;re a unique and real human on the internet while keeping your identity private.</p><p>For developers, this translates to providing a means of sybil resistance that is becoming both increasingly important, and more challenging, in a world of accelerating AI and automation.</p><p>I&#8217;ve <a href="https://github.com/dabit3/sybil-form">experimented </a>with many sybil resistance solutions in the past such as GitCoin Passport and the tradeoffs in UX and usability seem very challenging, and there doesn&#8217;t yet seem to be a silver bullet, but I feel like World ID&#8217;s approach is quite promising.</p><p>In the past, WorldCoin brought to mind The Orb, which was controversial to some people and therefore not something that could be used today, at scale, as a single means of identity verification for any app wanting to scale globally in a permissionless fashion.</p><p>This is not the case today, as <em>anyone with a mobile device</em> can use the WorldID App to provide unique credentials that would be orders of magnitude harder and more expensive to sybil attack than traditional methods like social authentication or anything on-chain, without the Orb.</p><h2>Identity methods </h2><p>There are 3 different authentication methods available through World ID</p><h4>World ID Device</h4><p>Allows apps to accept World IDs that haven't yet been verified with an Orb, but are held on trusted devices. This us useful in lower stake scenarios.</p><p>This is the method we&#8217;ll be using in this tutorial.</p><h4>World ID Orb</h4><p>Orbs are devices that verify humanness and uniqueness. The classic Orb signal allows users to prove they are holding an Orb-verified World ID on a trusted device. This is more secure than device only verification.</p><h4>World ID Orb+</h4><p>To enable higher security, apps can require face authentication when using World ID, similar to unlocking your phone. This ensures only the issued person can use their ID for that action. Authentication is performed locally on the user&#8217;s device thanks to Personal Custody.</p><h2><strong>Use Cases</strong></h2><h4>User Verification</h4><p>Use World ID to verify your existing user base if you already have users, or offer a way for users to sign up in another fashion.</p><h4>User Sign in</h4><p>Use World ID as an authentication method similar to how you might use Google , Facebook, or GitHub login.</p><h4>User Actions</h4><p><a href="https://docs.worldcoin.org/id/incognito-actions">Incognito Actions</a> verify arbitrary action a user might take in your app, where each action is independent of other actions. Here are some examples:</p><ul><li><p>Claiming a one-time discount for a purchase (each discount is independent of other discounts)</p></li><li><p>Allowing users to vote in a poll, but only vote per user (each vote is independent, votes from the same person cannot be linked)</p></li><li><p>Sharing an invitation to join a platform, limited to five total invites</p></li></ul><p>Similarly to how you might confirm or manage this type of authorization in a database, WorldID </p><h2>What we&#8217;re building</h2><p>We&#8217;ll be building a simple app that allows users to sign in to an app using their mobile device.</p><p>We&#8217;ll verify the user on the server using the WorldID API, and integrate it client-side using the World ID ID Kit <a href="https://docs.worldcoin.org/reference/idkit">SDK for React</a>.</p><p>The user will also be given the option to also connect their wallet to fetch their social profile from Lens or Farcster using Airstack&#8217;s API.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!cRtf!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fec0ddd8b-3997-4a62-a539-2add0fefa49f_2740x1624.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!cRtf!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fec0ddd8b-3997-4a62-a539-2add0fefa49f_2740x1624.png 424w, /__u/substackcdn.com/image/fetch/$s_!cRtf!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fec0ddd8b-3997-4a62-a539-2add0fefa49f_2740x1624.png 848w, /__u/substackcdn.com/image/fetch/$s_!cRtf!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fec0ddd8b-3997-4a62-a539-2add0fefa49f_2740x1624.png 1272w, /__u/substackcdn.com/image/fetch/$s_!cRtf!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fec0ddd8b-3997-4a62-a539-2add0fefa49f_2740x1624.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!cRtf!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fec0ddd8b-3997-4a62-a539-2add0fefa49f_2740x1624.png" width="1456" height="863" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ec0ddd8b-3997-4a62-a539-2add0fefa49f_2740x1624.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:863,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:205899,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!cRtf!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fec0ddd8b-3997-4a62-a539-2add0fefa49f_2740x1624.png 424w, /__u/substackcdn.com/image/fetch/$s_!cRtf!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fec0ddd8b-3997-4a62-a539-2add0fefa49f_2740x1624.png 848w, /__u/substackcdn.com/image/fetch/$s_!cRtf!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fec0ddd8b-3997-4a62-a539-2add0fefa49f_2740x1624.png 1272w, /__u/substackcdn.com/image/fetch/$s_!cRtf!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fec0ddd8b-3997-4a62-a539-2add0fefa49f_2740x1624.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>We&#8217;ll be going the extra mile and adding some nice styling using the ShadCN UI library which is easy to set up with a new Next.js project.</p><p>Let&#8217;s start building.</p><h3>Prerequisites</h3><p>To follow along, you&#8217;ll need the following credentials.</p><ol><li><p><a href="/__u/cloud.walletconnect.com/app">WalletConnect Project ID</a></p></li><li><p><a href="https://app.airstack.xyz/profile-settings/api-keys">Airstack API Key</a></p></li><li><p><a href="https://developer.worldcoin.org/">World ID App ID</a></p></li></ol><p>When creating your World ID App, choose &#8220;Production&#8221; and &#8220;Cloud&#8221;:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!EaqZ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F27c21a7a-8ce0-4b20-a38b-5fb2b8887da9_1368x1578.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!EaqZ!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F27c21a7a-8ce0-4b20-a38b-5fb2b8887da9_1368x1578.png 424w, /__u/substackcdn.com/image/fetch/$s_!EaqZ!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F27c21a7a-8ce0-4b20-a38b-5fb2b8887da9_1368x1578.png 848w, /__u/substackcdn.com/image/fetch/$s_!EaqZ!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F27c21a7a-8ce0-4b20-a38b-5fb2b8887da9_1368x1578.png 1272w, /__u/substackcdn.com/image/fetch/$s_!EaqZ!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F27c21a7a-8ce0-4b20-a38b-5fb2b8887da9_1368x1578.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!EaqZ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F27c21a7a-8ce0-4b20-a38b-5fb2b8887da9_1368x1578.png" width="534" height="615.9736842105264" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/27c21a7a-8ce0-4b20-a38b-5fb2b8887da9_1368x1578.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1578,&quot;width&quot;:1368,&quot;resizeWidth&quot;:534,&quot;bytes&quot;:149465,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!EaqZ!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F27c21a7a-8ce0-4b20-a38b-5fb2b8887da9_1368x1578.png 424w, /__u/substackcdn.com/image/fetch/$s_!EaqZ!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F27c21a7a-8ce0-4b20-a38b-5fb2b8887da9_1368x1578.png 848w, /__u/substackcdn.com/image/fetch/$s_!EaqZ!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F27c21a7a-8ce0-4b20-a38b-5fb2b8887da9_1368x1578.png 1272w, /__u/substackcdn.com/image/fetch/$s_!EaqZ!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F27c21a7a-8ce0-4b20-a38b-5fb2b8887da9_1368x1578.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p>After you create your World ID App ID, we&#8217;ll need to create a World ID incognito action. Set the Action Identifer to <strong>auth</strong> and set the Maximum Verifications Per Person to <strong>Unlimited.</strong></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!mr8R!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F39cb804c-0842-4725-a430-41ce6d043df8_2776x1634.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!mr8R!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F39cb804c-0842-4725-a430-41ce6d043df8_2776x1634.png 424w, /__u/substackcdn.com/image/fetch/$s_!mr8R!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F39cb804c-0842-4725-a430-41ce6d043df8_2776x1634.png 848w, /__u/substackcdn.com/image/fetch/$s_!mr8R!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F39cb804c-0842-4725-a430-41ce6d043df8_2776x1634.png 1272w, /__u/substackcdn.com/image/fetch/$s_!mr8R!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F39cb804c-0842-4725-a430-41ce6d043df8_2776x1634.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!mr8R!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F39cb804c-0842-4725-a430-41ce6d043df8_2776x1634.png" width="1456" height="857" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/39cb804c-0842-4725-a430-41ce6d043df8_2776x1634.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:857,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:266380,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!mr8R!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F39cb804c-0842-4725-a430-41ce6d043df8_2776x1634.png 424w, /__u/substackcdn.com/image/fetch/$s_!mr8R!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F39cb804c-0842-4725-a430-41ce6d043df8_2776x1634.png 848w, /__u/substackcdn.com/image/fetch/$s_!mr8R!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F39cb804c-0842-4725-a430-41ce6d043df8_2776x1634.png 1272w, /__u/substackcdn.com/image/fetch/$s_!mr8R!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F39cb804c-0842-4725-a430-41ce6d043df8_2776x1634.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>We&#8217;ll be using both the App ID as well as the action later when we configure our environment variables.</p><h2>Project setup</h2><p>To get started, create a new Next.js app using the Next.js CLI:</p><pre><code>npx create-next-app worldid-app

&#10004; Would you like to use TypeScript? <strong>Yes</strong>
&#10004; Would you like to use ESLint? <strong>Yes</strong>
&#10004; Would you like to use Tailwind CSS? <strong>Yes</strong>
&#10004; Would you like to use `src/` directory? <strong>No</strong>
&#10004; Would you like to use App Router? <strong>Yes</strong>
&#10004; Would you like to customize the default import alias (@/*)? <strong>No</strong></code></pre><p>Next, change into the new directory and install the following dependencies using npm, yarn, bun, or your favorite package manager:</p><pre><code>npm install @worldcoin/idkit @airstack/node connectkit wagmi viem@2.x @tanstack/react-query next-themes</code></pre><p>Next, add the following to tsconfig.json to disable the <strong>noImplicitAny</strong> TypeScript rule for the purposes of this tutorial:</p><pre><code>"compilerOptions": {
  ...   
  "noImplicitAny": false
}</code></pre><p>Finally, initialize <a href="https://ui.shadcn.com/">ShadCN UI:</a></p><pre><code>npx shadcn-ui@latest init

&#10004; Which style would you like to use? &#8250; <strong>New York</strong>
&#10004; Which color would you like to use as base color? &#8250; <strong>Slate</strong>
&#10004; Would you like to use CSS variables for colors? <strong>yes</strong></code></pre><p>Now, install the following three components using the ShadCN UI CLI:</p><pre><code>npx shadcn-ui@latest add button sonner separator</code></pre><h4>Environment variables</h4><p>Create a file named .env.local and add the following values:</p><pre><code>AIRSTACK_API_KEY="your-airstack-api-key"
NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID="your-walletconnect-project-id"

# WorldCoin ID Action Name
NEXT_PUBLIC_WC_ACTION="auth"

# Worldcoin ID App Name
# Remove "_app" from the WorldCoin App ID before setting
NEXT_PUBLIC_WLD_APP_ID="your-worldcoin-app-id"</code></pre><h3>API Routes</h3><p>Next, we&#8217;ll create our API routes. These will be the back end API endpoints that we&#8217;ll be using to interact with the World ID API and the Airstack API.</p><h4>World ID verification</h4><p>The first route we&#8217;ll create is for verifying a user using the World ID API.</p><p>Create a folder in the <strong>app</strong> directory named verify. In the <strong>verify</strong> directory create a file named <strong>route.ts</strong> and add the following code:</p><pre><code>//  <strong>app/api/verify/route.ts</strong>
import { NextResponse, NextRequest } from "next/server"

const endpoint = `https://developer.worldcoin.org/api/v1/verify/app_${process.env.NEXT_PUBLIC_WLD_APP_ID}`

export async function POST(req:NextRequest) {
  const body = await req.json()
  const reqBody = {
    merkle_root: body.merkle_root,
    nullifier_hash: body.nullifier_hash,
    proof: body.proof,
    verification_level: body.verification_level,
    signal: body.signal ?? "",
    action: process.env.NEXT_PUBLIC_WC_ACTION
  }

  const verifyRes = await fetch(endpoint, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify(reqBody)
  })
  const wldResponse = await verifyRes.json()
  if (verifyRes.status == 200) {
    return NextResponse.json({ code: "success", wldResponse });
  } else {
    return NextResponse.json({ code: "failure", wldResponse });
  }
}</code></pre><p>This route verifies the API call coming from the client, which includes the <strong>proof</strong>, <strong>nullifier hash</strong>, and <strong>merkle root</strong>. *see definition of types <a href="https://docs.worldcoin.org/reference/idkit#types">here</a>.</p><p>The <strong>action</strong> is the action that was configured earlier, which allows this verification to be executed an unlimited number of times. </p><p>The <strong>signal</strong> is a string to be included in the zero-knowledge proof. This is typically used for on-chain actions, and we are optionally passing in a wallet address which could be verified on the server along with a signature for anything you&#8217;d like to do on-chain.</p><h4>User lookup</h4><p>We have an optional route at <strong>/api/get-user</strong> that calls the Airstack API with the user&#8217;s address to find out if they hold any web3 social accounts, if they do we can address them by their on-chain social profile name.</p><pre><code>//  <strong>app/api/get-user/route.ts</strong>

import { NextRequest, NextResponse } from 'next/server'
import { init, fetchQuery } from "@airstack/node";

init(process.env.AIRSTACK_API_KEY || '')

export async function POST(req: NextRequest) {
  try {
    const { address } = await req.json()
    const query = `
      query GetAllSocials {
        Socials(
          input: {filter: {userAssociatedAddresses: {_eq: "${address}"}}, blockchain: ethereum}
        ) {
          Social {
            blockchain
            dappName
            profileName
            userAssociatedAddresses
            userId
            userCreatedAtBlockTimestamp
          }
        }
      }
    `
    const { data, error } = await fetchQuery(query)
    return NextResponse.json({ data, error})
  } catch (error) {
    console.log('error', error)
    return NextResponse.json({ error})
  }
}</code></pre><h2>Building the UI</h2><p>We&#8217;re going to build the UI with some nice features like a light/dark mode as well as responsive styling using Tailwind breakpoints. Bear with me as this we will be working across quite a few files.</p><h4>components/theme-provider.tsx</h4><p>This lets us set up light / dark themeing</p><pre><code>"use client"
 
import * as React from "react"
import { ThemeProvider as NextThemesProvider } from "next-themes"
import { type ThemeProviderProps } from "next-themes/dist/types"
 
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
  return &lt;NextThemesProvider {...props}&gt;{children}&lt;/NextThemesProvider&gt;
}</code></pre><h4>components/mode-toggle.tsx</h4><p>Lets us toggle between light and dark mode. This button will live in our nav.</p><pre><code>"use client"

import * as React from "react"
import { MoonIcon, SunIcon } from "@radix-ui/react-icons"
import { useTheme } from "next-themes"

import { Button } from "@/components/ui/button"


export function ModeToggle() {
  const { setTheme, theme } = useTheme()
  const nextTheme = theme === "light" ? "dark" : "light"

  return (
    &lt;Button onClick={() =&gt; setTheme(nextTheme)} variant="outline" size="icon" className="rounded-full"&gt;
      {
        theme === 'light' ? &lt;SunIcon className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" /&gt; : &lt;MoonIcon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" /&gt;
      } 
    &lt;/Button&gt;
  )
}</code></pre><h4>components/connectkit-provider.tsx</h4><p>This is where we configure <a href="https://wagmi.sh/">Wagmi</a> and <a href="https://docs.family.co/connectkit">ConnectKit</a></p><pre><code>'use client'
import { WagmiProvider, createConfig } from "wagmi";
import { ConnectKitProvider as Provider, getDefaultConfig } from "connectkit";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";

const config = createConfig(
  getDefaultConfig({
    walletConnectProjectId: process.env.NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID || "",
    appName: "WorldCoin ID Connect",
  }),
);

const queryClient = new QueryClient();

export const ConnectKitProvider = ({ children }: { children: any }) =&gt; {
  return (
    &lt;WagmiProvider config={config}&gt;
      &lt;QueryClientProvider client={queryClient}&gt;
        &lt;Provider&gt;
          {children }
        &lt;/Provider&gt;
      &lt;/QueryClientProvider&gt;
    &lt;/WagmiProvider&gt;
  );
};</code></pre><h4>app/context.ts</h4><p>This is the global state that we&#8217;ll be using in our app. This includes the user&#8217;s wallet address, social profile, and a function to set their profile (all optional as they can opt for only World ID verification).</p><pre><code>import { createContext } from 'react'

export const AppContext = createContext({
  address: '',
  profile: null,
  setProfile: () =&gt; null,
})</code></pre><h4>app/context-provider.tsx</h4><p>This is the component that allows us to update and manage the global state configured in <strong>app/context.ts</strong></p><pre><code>'use client'
import { AppContext } from './context'
import { useAccount } from "wagmi"
import { useState, useEffect } from 'react'

export function ContextProvider ({ children }) {
  const { address } = useAccount()
  const [profile, setProfile] = useState&lt;any&gt;(null)
  const [verified, setVerified] = useState(false)

  useEffect(() =&gt; {
    if (address) fetchProfile()
  }, [address])

  async function fetchProfile() {
    try {
     const response = await fetch('/api/get-user', {
      method: 'POST',
      body: JSON.stringify({ address }),
     }).then((res) =&gt; res.json())
     if (response?.data?.Socials?.Social) {
        setProfile(response.data.Socials.Social[response.data.Socials.Social.length - 1])
     } else {
      setProfile({
        profileName: address
      })
     }
    } catch (err) {
      console.log('error fetching profile', err)
    }
  }
  
  return (
    &lt;AppContext.Provider
      value={{
        address,
        profile,
        setProfile,
        setVerified,
        verified
      } as any }
    &gt;
      {children}
    &lt;/AppContext.Provider&gt;
  )
}</code></pre><h4>app/nav.tsx</h4><p>This is the navigation bar.</p><pre><code>'use client'
import Image from 'next/image'
import Link from 'next/link'
import { ModeToggle } from '@/components/mode-toggle'
import { AppContext } from './context'
import { useContext, useState, useEffect } from 'react'
import { Button } from '@/components/ui/button'
import { useDisconnect } from 'wagmi'
import { GitHubLogoIcon } from '@radix-ui/react-icons'
export function Nav() {
  const [isClient, setIsClient] = useState(false)
  const { address, setProfile, setVerified, verified } = useContext&lt;any&gt;(AppContext)
  const { disconnect } = useDisconnect()
  useEffect(() =&gt; {
    setIsClient(true)
  }, [])
  if (!isClient) return null
  return (
    &lt;div
      className="flex border-b px-6 py-3"
    &gt;
      &lt;div className='flex flex-1 items-center'&gt;
        &lt;Link href="/__u/nader.substack.com/"&gt;
          &lt;div className='flex items-center'&gt;
            &lt;Image
              src="/__u/nader.substack.com/worldcoin.svg"
              width={40}
              className="dark:invert"
              height={40}
              alt="Worldcoin Logo"
            /&gt;
              &lt;p className='md:ml-1 text-sm'&gt;
              &lt;span className='font-bold'&gt;World&lt;/span&gt; Auth
            &lt;/p&gt;
          &lt;/div&gt;
        &lt;/Link&gt;
        &lt;Link
          href="https://worldcoin.org/world-id"
          target="_blank"
          rel="noopener noreferrer"
        &gt;
          &lt;p
            className='
            hover:text-foreground md:ml-8
            ml-5 text-sm text-muted-foreground
            '
          &gt;
            Learn More
          &lt;/p&gt;
        &lt;/Link&gt;
        &lt;Link
          href="https://github.com/dabit3/worldid-auth"
          target='_blank'
          rel="noopener noreferrer"
        &gt;
          &lt;GitHubLogoIcon
            className="md:ml-8 ml-5 hover:text-foreground text-muted-foreground"
          /&gt;
        &lt;/Link&gt;
      &lt;/div&gt;
      &lt;div className='flex items-center'&gt;
        {
          (verified || address) &amp;&amp; (
            &lt;Button onClick={
              () =&gt; {
                disconnect()
                setProfile(null)
                setVerified(false)
              }
            } className='text-xs' variant='link'&gt;
              Disconnect
            &lt;/Button&gt;
          )
        }
        &lt;ModeToggle /&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  )
}</code></pre><p>For the WorldCoin logo, create <strong>worldcoin.svg</strong> in the <strong>public</strong> folder and add this:</p><pre><code>&lt;?xml version="1.0" encoding="utf-8"?&gt;
&lt;!-- Generator: Adobe Illustrator 26.4.1, SVG Export Plug-In . SVG Version: 6.00 Build 0)  --&gt;
&lt;svg version="1.1" id="katman_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
&#9; viewBox="0 0 445.2 315.2" style="enable-background:new 0 0 445.2 315.2;" xml:space="preserve"&gt;
&lt;path
class="st0" d="M327.6,115.2c-3-7.5-6.8-14.6-11.3-21.3c-20.3-30-54.7-49.7-93.6-49.7c-62.4,0-112.9,50.6-112.9,112.9
&#9;c0,62.4,50.6,113,112.9,113c39,0,73.3-19.7,93.6-49.7c4.5-6.6,8.2-13.7,11.3-21.2c5.2-13,8.1-27.2,8.1-42
&#9;C335.6,142.4,332.8,128.2,327.6,115.2z M312.5,145.7H183.2c2-7,5.7-13.2,10.7-18.1c7.6-7.6,18.1-12.3,29.7-12.3H303
&#9;C307.9,124.6,311.1,134.8,312.5,145.7z M222.1,66.1c25.7,0,49,10.7,65.6,27.9h-61.3c-17.5,0-33.3,7.1-44.7,18.5
&#9;c-8.9,8.9-15.1,20.3-17.4,33.2h-32.5C137.4,100.8,175.7,66.1,222.1,66.1z M222.1,248.4c-46.4,0-84.7-34.7-90.4-79.6h32.5
&#9;c5.4,29.4,31.2,51.7,62.2,51.7h61.3C271.2,237.7,247.9,248.4,222.1,248.4z M223.6,199.3c-19.2,0-35.4-12.9-40.4-30.5h129.3
&#9;c-1.4,10.9-4.7,21.1-9.5,30.5H223.6z"/&gt;
&lt;/svg&gt;
</code></pre><h4>app/layout.tsx</h4><p>This is the component that configures the nav, global state, and providers we&#8217;ve created and passes everything down to be available in our routes. You can think of this as the entrypoint to our app.</p><pre><code>import type { Metadata } from 'next'
import { Inter } from 'next/font/google'
import { Nav } from './nav'
import { ConnectKitProvider } from '@/components/connectkit-provider'
import { ThemeProvider } from "@/components/theme-provider"
import { ContextProvider } from './context-provider'
import './globals.css'

const inter = Inter({ subsets: ['latin'] })
export const metadata: Metadata = {
  title: 'World ID Auth',
  description: 'Example of World ID device authentication',
}

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    &lt;html lang="en" suppressHydrationWarning&gt;
      &lt;body className={inter.className} style={{ height: 'calc(100vh - 60px)' }}&gt;
        &lt;ThemeProvider
          attribute="class"
          defaultTheme="dark"
          enableSystem
          disableTransitionOnChange
        &gt;
          &lt;ConnectKitProvider&gt;
            &lt;ContextProvider&gt;
              &lt;Nav /&gt;
              {children}
            &lt;/ContextProvider&gt;
          &lt;/ConnectKitProvider&gt;
        &lt;/ThemeProvider&gt;
      &lt;/body&gt;
    &lt;/html&gt;
  )
}</code></pre><h4>app/page.tsx</h4><p>The last component we need to set up is the main view, located at app/page.tsx.</p><blockquote><p>This is a large file, you can also view the code <a href="https://github.com/dabit3/worldid-auth/blob/main/app/page.tsx">here</a> on GitHub</p></blockquote><p>This component is the only route we&#8217;ll be rendering in our app, and contains all of the functionality we&#8217;ll need to make this work.</p><pre><code>'use client'
import { CubeIcon } from '@radix-ui/react-icons'
import { Button } from '@/components/ui/button'
import { Separator } from '@/components/ui/separator'
import { useModal } from "connectkit"
import { useAccount } from "wagmi";
import { useState, useEffect, useContext, useRef } from 'react'
import { IDKitWidget, VerificationLevel } from '@worldcoin/idkit'
import { Toaster } from "@/components/ui/sonner"
import { toast } from "sonner"
import { AppContext } from './context'
import Link from 'next/link'

export default function Home() {
  const { address } = useAccount()
  const [isLoaded, setIsLoaded] = useState(false)
  const { setOpen } = useModal()
  const { profile, verified, setVerified } = useContext&lt;any&gt;(AppContext)
  const buttonRef = useRef&lt;any&gt;(null)

  useEffect(() =&gt; {
    setIsLoaded(true)
  }, [])

  useEffect(() =&gt; {
    if (address &amp;&amp; !verified) {
      buttonRef.current?.click()
    }
  }, [address])

  async function handleVerify(data: any) {
    data.signal = address
    const response = await fetch('/api/verify', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(data)
    }).then(res =&gt; res.json())
    if (response.code === 'success') {
      setVerified(true)
      toast("Successfully authenticated with World ID.")
    } else {
      toast("Authenticated failed with World ID.")
      console.log('error:', response.wldResponse)
    }
  }
  if (!isLoaded) return null

  return (
    &lt;main className="
    p-4 sm:p-12 flex flex-1 h-full"&gt;
      &lt;div className="
      rounded-xl border flex-col md:flex-row flex flex-1
      "&gt;
       &lt;div
       className='
       p-6 sm:p-8 rounded-tl-xl rounded-tr-xl md:rounded-tr-none
       md:rounded-tl-xl md:rounded-bl-xl flex flex-1 bg-secondary-100 bg-zinc-900'
       &gt;
        &lt;div className='flex items-start flex-col'&gt;
          &lt;div className='flex justify-center flex-1 '&gt;
            &lt;CubeIcon
             className='w-7 h-7 text-white' /&gt;
            &lt;p className='
            text-white
            ml-3 text-lg'&gt;Acme Inc&lt;/p&gt;
          &lt;/div&gt;
          &lt;div&gt;
           &lt;p className='text-white'&gt;
           Sybil-resistant identity verification powered by &lt;Link rel='no-opener' target="_blank" href="https://docs.worldcoin.org/"&gt;World ID&lt;/Link&gt;.
           &lt;/p&gt;
          &lt;/div&gt;
        &lt;/div&gt;
       &lt;/div&gt;
       {
        verified &amp;&amp; (
          &lt;div
          className='
          border-l items-center justify-center
          flex flex-1 flex-col p-6 sm:p-8 '
          &gt;
            &lt;p&gt;
              &#127881; &amp;nbsp;Successfully authenticated with World ID.
            &lt;/p&gt;
            {
              address &amp;&amp; profile &amp;&amp; (
                &lt;p className='text-sm mt-3 text-muted-foreground'&gt;
                  welcome, {profile.profileName}
                &lt;/p&gt;
              )
            }
          &lt;/div&gt;
        )
       }

      {
        !verified &amp;&amp; (
          &lt;div
          className='
          md:rounded-bl-none rounded-bl-xl border-l
          items-center justify-center flex flex-1 flex-col p-6 sm:p-8 '
          &gt;
            &lt;div className='flex flex-col items-center'&gt;
              &lt;p className='font-medium text-2xl'&gt;Login or create account&lt;/p&gt;
              &lt;p className='text-sm text-muted-foreground mt-2'&gt;
              Connect and verify with World ID.
              &lt;/p&gt;
            &lt;/div&gt;
            &lt;div className='mt-5 flex flex-col'&gt;
              &lt;IDKitWidget
                app_id={`app_${process.env.NEXT_PUBLIC_WLD_APP_ID}`}
                action=/__u/nader.substack.com/%7Bprocess.env.NEXT_PUBLIC_WC_ACTION || ''}
                onSuccess={(message) =&gt; console.log(message)}
                handleVerify={handleVerify}
                signal={address}
                verification_level={VerificationLevel.Device}
              &gt;
                {({ open }) =&gt; (
                  &lt;Button
                    ref={buttonRef}
                    className='w-[290px] sm:w-[320px] '
                    onClick={open}
                  &gt;Verify with World ID&lt;/Button&gt;
                )}
              &lt;/IDKitWidget&gt;
              {
                !address &amp;&amp; (
                  &lt;&gt;
                    &lt;div className='relative py-4'&gt;
                      &lt;Separator
                        className='
                          mt-6 absolute inset-0 flex items-center
                        '
                      /&gt;
                      &lt;div className='
                       relative flex justify-center text-xs uppercase
                      '&gt;
                        &lt;p className='
                          bg-background px-2 text-muted-foreground
                        '&gt;
                          or
                        &lt;/p&gt;
                      &lt;/div&gt;
                    &lt;/div&gt;
                    &lt;Button
                    onClick={() =&gt; setOpen(true)}
                    variant="outline" className='
                    w-[290px] sm:w-[320px] 
                    rounded-lg'&gt;
                      Verify with Wallet &amp; World ID
                    &lt;/Button&gt;
                  &lt;/&gt;
                )
              }
              &lt;Link
                href="https://apps.apple.com/no/app/world-app-worldcoin-wallet/id1560859847"
                target='_blank'
                rel='noopener noreferrer'
              &gt;
                &lt;p className='
                  text-muted-foreground text-xs mt-4 text-center
                '&gt;
                  Get the WorldID App -&amp;gt;
                &lt;/p&gt;
              &lt;/Link&gt;
            &lt;/div&gt;
          &lt;/div&gt;
        )
      }
      &lt;/div&gt;
      &lt;Toaster /&gt;
    &lt;/main&gt;
  )
}</code></pre><p>The main thing to call out in the above code is the <em>IDKitWidget </em>component<em>.</em></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!Plb9!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa4fce7a8-3808-42de-b819-eeb3bd2fd9be_3256x1524.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!Plb9!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa4fce7a8-3808-42de-b819-eeb3bd2fd9be_3256x1524.png 424w, /__u/substackcdn.com/image/fetch/$s_!Plb9!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa4fce7a8-3808-42de-b819-eeb3bd2fd9be_3256x1524.png 848w, /__u/substackcdn.com/image/fetch/$s_!Plb9!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa4fce7a8-3808-42de-b819-eeb3bd2fd9be_3256x1524.png 1272w, /__u/substackcdn.com/image/fetch/$s_!Plb9!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa4fce7a8-3808-42de-b819-eeb3bd2fd9be_3256x1524.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!Plb9!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa4fce7a8-3808-42de-b819-eeb3bd2fd9be_3256x1524.png" width="1456" height="681" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a4fce7a8-3808-42de-b819-eeb3bd2fd9be_3256x1524.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:681,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:320483,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!Plb9!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa4fce7a8-3808-42de-b819-eeb3bd2fd9be_3256x1524.png 424w, /__u/substackcdn.com/image/fetch/$s_!Plb9!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa4fce7a8-3808-42de-b819-eeb3bd2fd9be_3256x1524.png 848w, /__u/substackcdn.com/image/fetch/$s_!Plb9!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa4fce7a8-3808-42de-b819-eeb3bd2fd9be_3256x1524.png 1272w, /__u/substackcdn.com/image/fetch/$s_!Plb9!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa4fce7a8-3808-42de-b819-eeb3bd2fd9be_3256x1524.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>This is the component that renders and configures our interaction with our server. The <strong>handleVerify</strong> function is what you should look at to understand how the API call and the response are handled.</p><h3>Testing it out</h3><p>That&#8217;s it! You should be able to test it out by running npm run dev.</p><blockquote><p>View a live demo <a href="https://worldid-auth.vercel.app/">here</a>, and the final codebase <a href="https://github.com/dabit3/worldid-auth">here</a>.</p></blockquote>]]></content:encoded></item><item><title><![CDATA[Introducing React Native AI]]></title><description><![CDATA[React Native AI is a full stack framework for building cross-platform mobile AI apps]]></description><link>https://nader.substack.com/p/introducing-react-native-ai</link><guid isPermaLink="false">https://nader.substack.com/p/introducing-react-native-ai</guid><dc:creator><![CDATA[Nader Dabit]]></dc:creator><pubDate>Mon, 27 Nov 2023 13:45:45 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!34wf!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b170af7-b389-4298-97cf-3ace6f96eb8c_1200x634.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!34wf!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b170af7-b389-4298-97cf-3ace6f96eb8c_1200x634.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!34wf!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b170af7-b389-4298-97cf-3ace6f96eb8c_1200x634.png 424w, /__u/substackcdn.com/image/fetch/$s_!34wf!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b170af7-b389-4298-97cf-3ace6f96eb8c_1200x634.png 848w, /__u/substackcdn.com/image/fetch/$s_!34wf!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b170af7-b389-4298-97cf-3ace6f96eb8c_1200x634.png 1272w, /__u/substackcdn.com/image/fetch/$s_!34wf!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b170af7-b389-4298-97cf-3ace6f96eb8c_1200x634.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!34wf!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b170af7-b389-4298-97cf-3ace6f96eb8c_1200x634.png" width="1200" height="634" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6b170af7-b389-4298-97cf-3ace6f96eb8c_1200x634.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:634,&quot;width&quot;:1200,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:26325,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!34wf!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b170af7-b389-4298-97cf-3ace6f96eb8c_1200x634.png 424w, /__u/substackcdn.com/image/fetch/$s_!34wf!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b170af7-b389-4298-97cf-3ace6f96eb8c_1200x634.png 848w, /__u/substackcdn.com/image/fetch/$s_!34wf!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b170af7-b389-4298-97cf-3ace6f96eb8c_1200x634.png 1272w, /__u/substackcdn.com/image/fetch/$s_!34wf!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b170af7-b389-4298-97cf-3ace6f96eb8c_1200x634.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><a href="https://github.com/dabit3/react-native-ai">React Native AI</a> is a full stack framework for building cross-platform mobile AI apps supporting LLM real-time / streaming text and chat UIs, image services + natural language to image with multiple models, and image processing.</p><blockquote><p>Check out the social announcement <a href="https://twitter.com/dabit3/status/1729139843806957857">here</a>, and the repo <a href="https://github.com/dabit3/react-native-ai">here</a>. Please give it a &#11088;&#65039; if you think it&#8217;s cool!</p><p>Video tutorial is also now available <a href="https://www.youtube.com/watch?v=zf3NnTU5pr4">here</a>.</p></blockquote><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!H6UX!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0d29ba9f-6347-4637-b1e1-4f8e3b9decf7_1742x810.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!H6UX!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0d29ba9f-6347-4637-b1e1-4f8e3b9decf7_1742x810.png 424w, /__u/substackcdn.com/image/fetch/$s_!H6UX!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0d29ba9f-6347-4637-b1e1-4f8e3b9decf7_1742x810.png 848w, /__u/substackcdn.com/image/fetch/$s_!H6UX!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0d29ba9f-6347-4637-b1e1-4f8e3b9decf7_1742x810.png 1272w, /__u/substackcdn.com/image/fetch/$s_!H6UX!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0d29ba9f-6347-4637-b1e1-4f8e3b9decf7_1742x810.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!H6UX!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0d29ba9f-6347-4637-b1e1-4f8e3b9decf7_1742x810.png" width="1456" height="677" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/0d29ba9f-6347-4637-b1e1-4f8e3b9decf7_1742x810.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:677,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:725613,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!H6UX!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0d29ba9f-6347-4637-b1e1-4f8e3b9decf7_1742x810.png 424w, /__u/substackcdn.com/image/fetch/$s_!H6UX!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0d29ba9f-6347-4637-b1e1-4f8e3b9decf7_1742x810.png 848w, /__u/substackcdn.com/image/fetch/$s_!H6UX!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0d29ba9f-6347-4637-b1e1-4f8e3b9decf7_1742x810.png 1272w, /__u/substackcdn.com/image/fetch/$s_!H6UX!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0d29ba9f-6347-4637-b1e1-4f8e3b9decf7_1742x810.png 1456w" sizes="100vw"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>I&#8217;ve spent the past few months building and launching various AI experiments and products like <a href="https://twitter.com/RoamAround_io">RoamAround</a> ($10M valuation, 7 million+ users) and <a href="https://twitter.com/dabit3/status/1712491646154088629">AI Buddy</a> ($1,000+ MRR, 2,000 + users).</p><p>During that time I&#8217;ve learned a lot about how to piece together everything needed to build and launch successful AI apps, lately with a focus on mobile and launching to the App stores.</p><p>What I realized was that the base needed to build most of my ideas was the same, but experimenting took a long time because I needed to rebuild these basic components over and over.</p><p>React Native AI came out of my need to have a basic, configurable framework to easily spin up new ideas without having to rebuild all of the basic components from scratch.</p><h3>Some of these components and features:</h3><ol><li><p><strong>Streaming / real-time responses.</strong> Handling and debugging these on both the client and the server can be time-consuming.</p></li><li><p><strong>LLM normalization.</strong> The various LLM providers respond in different formats. The idea is to handle these on the server and send back to the client with a consistent interface.</p></li><li><p><strong>OpenAI Assistants.</strong> OpenAI Assistants provide a powerful new way to build on LLMs, but the API for doing so is more complex than using a basic LLM. There needs to be a string of API calls and state management for this to work. Having this all set up and ready to use will save me a lot of time in the future.</p></li><li><p><strong>Image processing.</strong> Dealing with image uploads and downloads, saving to disk on the server and saving to the device, can be complicated and gets time consuming to debug. Having a plug and play interface and utility functions speeds everything up. </p></li><li><p><strong>Chat UI.</strong> Rebuilding components like buttons, text inputs, and chat interfaces is time consuming and tedious. Having a pre-built UI and components for these repeatable UI elements saves a lot of time.</p></li><li><p><strong>Theming / styling. </strong>React Native AI comes with 5 prebuilt themes and makes swapping themes out very simple, by adding just a few lines of code. When launching your app, you can ship with just a single theme or give users the option to switch between themes.</p></li></ol><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!5Sf3!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4cc88ea2-2312-4682-a81d-3c744823fd78_1500x2081.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!5Sf3!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4cc88ea2-2312-4682-a81d-3c744823fd78_1500x2081.png 424w, /__u/substackcdn.com/image/fetch/$s_!5Sf3!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4cc88ea2-2312-4682-a81d-3c744823fd78_1500x2081.png 848w, /__u/substackcdn.com/image/fetch/$s_!5Sf3!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4cc88ea2-2312-4682-a81d-3c744823fd78_1500x2081.png 1272w, /__u/substackcdn.com/image/fetch/$s_!5Sf3!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4cc88ea2-2312-4682-a81d-3c744823fd78_1500x2081.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!5Sf3!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4cc88ea2-2312-4682-a81d-3c744823fd78_1500x2081.png" width="1456" height="2020" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4cc88ea2-2312-4682-a81d-3c744823fd78_1500x2081.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:2020,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:953453,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!5Sf3!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4cc88ea2-2312-4682-a81d-3c744823fd78_1500x2081.png 424w, /__u/substackcdn.com/image/fetch/$s_!5Sf3!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4cc88ea2-2312-4682-a81d-3c744823fd78_1500x2081.png 848w, /__u/substackcdn.com/image/fetch/$s_!5Sf3!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4cc88ea2-2312-4682-a81d-3c744823fd78_1500x2081.png 1272w, /__u/substackcdn.com/image/fetch/$s_!5Sf3!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4cc88ea2-2312-4682-a81d-3c744823fd78_1500x2081.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h3>Service providers</h3><p>React Native AI uses the following services:</p><ul><li><p><a href="https://platform.openai.com/">OpenAI</a> ChatGPT for 2 types of LLMs as well as OpenAI Assistant, code interpreter, and retrieval</p></li><li><p><a href="https://www.fal.ai/">Fal.ai</a> for an array of image models.</p></li><li><p><a href="https://console.anthropic.com/">Anthropic</a> Claude for 2 types of LLMs</p></li><li><p><a href="https://cohere.com/">Cohere</a> for 2 types of LLMs including web which gives you access to live, online data</p></li><li><p><a href="https://www.bytescale.com/">Bytescale</a> for image processing</p></li></ul><h3>Other technology</h3><p>React Native AI is made possible by great open source tech like <a href="https://reactnative.dev/">React Native</a>, <a href="https://docs.expo.dev/">Expo</a>, <a href="https://nodejs.org/en/">Node.js</a>, and <a href="https://expressjs.com/">Express</a>.</p><h2>Getting started</h2><p>To get started with React Native AI, run the following command:</p><pre><code>npx rn-ai</code></pre><p>You&#8217;ll be prompted to configure your environment variables either at setup or later. You don&#8217;t have to set up <em><strong>all</strong></em> environment variables, you only need to enable the services you&#8217;d like to try out.</p>]]></content:encoded></item><item><title><![CDATA[Shared Humanity]]></title><description><![CDATA[A Palestinian-American Perspective on the Palestinian-Israeli Conflict]]></description><link>https://nader.substack.com/p/shared-humanity</link><guid isPermaLink="false">https://nader.substack.com/p/shared-humanity</guid><dc:creator><![CDATA[Nader Dabit]]></dc:creator><pubDate>Sun, 22 Oct 2023 21:01:00 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!Xm1j!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbe2bf983-652e-4b8b-a971-db636ebeb9a3_1200x1550.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>The past couple of weeks have been devastating. It has been hard for me to focus on anything related to work or tech. I have also felt like a coward because I have a large platform, but have not raised my voice about what is going on in Israel and Palestine.</p><p>Countless people are having their lives ripped apart, both Israelis and Palestinians. During this time, I&#8217;ve tiptoed around sharing my opinion because I&#8217;ve worried how I might be affected. I kiss my kids to sleep, in a safe beautiful home, a privileged life, going about my day without speaking up about the horror that is happening as I type this. </p><p>This quote from Martin Luther King Jr. keeps running through my head - <em><strong>"There comes a time when silence is betrayal."</strong></em></p><p>I&#8217;m half Palestinian, with a Palestinian wife and kids, having grown up visiting Palestine many times as a kid and through adulthood. My father, who&#8217;s family&#8217;s home, business, and possessions were ripped away from them during the <a href="https://en.wikipedia.org/wiki/Nakba">Nakba</a>, walked through the desert from <a href="https://en.wikipedia.org/wiki/Lod">Lod</a> to <a href="https://en.wikipedia.org/wiki/Ramallah">Ramallah</a> after their home was taken, and lived there as refugees before migrating to America in the 1970&#8217;s.</p><p>My family and I have lived in Mississippi most of our lives, a place where racism persists. Over time there have been many incidents, the most extreme was me being beaten unconscious and into the hospital for being a &#8220;camel jockey&#8221; and &#8220;sand n*gger&#8221; while walking out of bible class in 11th grade, almost losing vision in one of my eyes. These experiences have driven me to identify more closely with my Palestinian heritage. I have strong opinions about the situation happening there.</p><p>I hold my Jewish, Israeli, and Palestinian family and friends dear. They have all always treated me with kindness and hospitality, in the US, Israel, and throughout the Middle East.</p><p>It feels impossible to speak about this situation because I know that no matter what I say, there will be people who strongly disagree.</p><p>I think that it&#8217;s ok to condemn certain actions of the Israeli Government while still loving and empathizing with my Jewish brothers, sisters, and friends &#8211; supporting their right to live a safe, happy life, in their own country, Israel.</p><p>As for Hamas, their barbaric acts of terrorism, their attacks and murders of innocent people in the past and on October 7 are horrific and I condemn them. They have caused immeasurable suffering for the Palestinian people, and have weakened the Palestinian cause. They should immediately release all hostages in an attempt to negotiate a ceasefire.</p><p>Some might argue that the conflict happening there for generations is complicated, but what happened to spark the current wave of violence was not - it was horrendous and brutal, and without justification. It is never ok to target children or civilians.</p><p>I also condemn the atrocities being perpetrated by the Israeli government in Gaza. The collective punishment of a besieged and helpless people is evil, cruel, and is in flagrant violation of international humanitarian law.  It is, by definition, a war crime <a href="https://ihl-databases.icrc.org/en/ihl-treaties/gciv-1949/article-33">[1]</a> <a href="https://www.icc-cpi.int/sites/default/files/RS-Eng.pdf">[2]</a> .</p><p>There are people in this world who argue that Hamas murdering and terrorizing innocent people is wrong, while cheering on the people in a proper militant uniform of <em>their</em> choice doing the same thing. In reality, both are horrific and unjust.</p><p>If Israel has a right to defend itself against brutal attacks on civilians and breaches in international law (it does), then does Palestine get afforded the same rights? The answer from the US, Israeli, and many western nations has been a resounding <strong>NO</strong>. <a href="https://twitter.com/YousefMunayyer/status/1718775373595328670">These laws do not apply to Israeli government or militant operations</a>.</p><p>For instance, here is a non-comprehensive list of incidents since 2009, where many women, children, health workers, and members of the press were murdered:</p><ul><li><p>2014 when four Palestinian boys, aged between 9 and 11, were murdered by an Israeli airstrike while playing on the beach.</p></li><li><p>2014, multiple acts of violence involving United Nations Relief and Works Agency schools. In one case in Jabalia, at least 15 people were killed and approximately 100 injured when the school was mortared.</p></li><li><p>Between 2018-2019, over 200 killed in the &#8220;Great March of Return" protests</p></li><li><p>2009 bombing of Al-Quds Hospital, resulting in the death of at least 42 people, with over 50 others injured.</p></li><li><p>2009, when 29 members of the Samouni family were killed when their house was mortared and many other family members injured.</p></li><li><p>2014 bombing of a Market in Shuja'iyya where at least 17 people died, and approximately 200 were injured.</p></li><li><p>2012 airstrike on the home of the the Al-Dalou family, killing 12 people and several others injured.</p></li><li><p>2014 destruction of the Gaza Power Plant, the subsequent lack of electricity severely affected medical services, water supply, and sanitation, indirectly affecting thousands.</p></li><li><p>2014 when nine people were killed, and at least 15 injured when a civilian Cafe was bombed in Khan Yunis.</p></li><li><p>2009 when Israel bombed the Al-Fakhura School, resulting in over 40 deaths and more than 50 injuries.</p></li><li><p>The systematic forced evictions and stealing of land and homes from Palestinians to give to Israelis and Israeli settlers. Too many incidents to list.</p></li></ul><p>No media interviews with their families, no tears from anchors, no Palestinian flags projected onto government buildings, no statements of solidarity from companies, and no &#8220;<a href="https://twitter.com/search?q=israel%20has%20a%20right%20to%20defend%20itself&amp;src=typed_query">Palestine has a right to defend itself</a>&#8221;.</p><p>Beyond the killings, the humanitarian crisis which existed before this war started has become much worse, with water, food, electricity, internet, medicine, and communications cut off or prohibitively restricted, civilian homes, businesses, and infrastructure destroyed. The looming ground invasion and displacement of over 1 million Palestinians from their homes will only compound the situation.</p><p>If this were happening to any other population in the world <a href="https://twitter.com/JustDKG/status/1716528565074698386">it would be rightly condemned, immediately and unequivocally</a>.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!Xm1j!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbe2bf983-652e-4b8b-a971-db636ebeb9a3_1200x1550.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!Xm1j!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbe2bf983-652e-4b8b-a971-db636ebeb9a3_1200x1550.jpeg 424w, /__u/substackcdn.com/image/fetch/$s_!Xm1j!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbe2bf983-652e-4b8b-a971-db636ebeb9a3_1200x1550.jpeg 848w, /__u/substackcdn.com/image/fetch/$s_!Xm1j!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbe2bf983-652e-4b8b-a971-db636ebeb9a3_1200x1550.jpeg 1272w, /__u/substackcdn.com/image/fetch/$s_!Xm1j!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbe2bf983-652e-4b8b-a971-db636ebeb9a3_1200x1550.jpeg 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!Xm1j!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbe2bf983-652e-4b8b-a971-db636ebeb9a3_1200x1550.jpeg" width="366" height="472.75" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/be2bf983-652e-4b8b-a971-db636ebeb9a3_1200x1550.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1550,&quot;width&quot;:1200,&quot;resizeWidth&quot;:366,&quot;bytes&quot;:415535,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!Xm1j!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbe2bf983-652e-4b8b-a971-db636ebeb9a3_1200x1550.jpeg 424w, /__u/substackcdn.com/image/fetch/$s_!Xm1j!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbe2bf983-652e-4b8b-a971-db636ebeb9a3_1200x1550.jpeg 848w, /__u/substackcdn.com/image/fetch/$s_!Xm1j!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbe2bf983-652e-4b8b-a971-db636ebeb9a3_1200x1550.jpeg 1272w, /__u/substackcdn.com/image/fetch/$s_!Xm1j!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbe2bf983-652e-4b8b-a971-db636ebeb9a3_1200x1550.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h3>All innocent life is equal</h3><p>How valuable is a human life? It has been incredibly hurtful for me to witness the stark contrasts in the answer to this question across different groups of people around the world.</p><p>Here in the United States, it has not been surprising to see a population who collectively shrugged off the murder of hundreds of thousands of innocent Iraqi children and civilians in a war based on the false pretenses of their government, to only show empathy toward non-Arab civilian deaths in this conflict &#8211; these past weeks but also over the past few decades.</p><p>The lack of empathy in much of the Western world is something many people find deeply troubling. In the United States we have collectively mourned only one side of this tragedy. It is important to recognize, honor, and mourn the Israeli lives lost and the people suffering there. But why are we not doing the same for innocent Palestinians?  Over the past 2 weeks alone, it is estimated that over 1,700 Palestinian children have been killed. This does not mean we should not mourn the Israeli lives lost, it just seems inhumane to choose to mourn based solely on ethnicity, race, or religion &#8211; especially when 50% of those being killed are kids.</p><p>The fact that I&#8217;ve realized is this &#8211; there are people who support the terrorism, murder, and collective punishment of innocent people, as long as it&#8217;s performed against the proper ethnicity. To them, Palestinian lives are considered to be sub-human.</p><p>This goes both ways. If you are angry about what has been happening to the people of Gaza over the past few weeks but not also enraged about the innocent Israelis who were killed, you are no better.</p><h3>Human rights</h3><p>Both Israelis and Palestinians deserve peace and human rights. Israel is a small country, with a small number of people compared to the surrounding Arab nations. Many of these surrounding countries are full of people with animosity towards the Israeli government. While this animosity is mainly directed towards the actions of their government over the years and not the individuals themselves, I can&#8217;t imagine how it might feel to live in Israel as a Jew knowing that many of your neighbors dislike you, and openly argue that your country should not even exist.</p><p>What I have personally witnessed and experienced are Arabs and Israelis living together in peace in Tel Aviv, Jerusalem, Haifa, and many other places there. To argue that peace between these people is not possible is to dismiss this reality.</p><p>So if this is possible, why do terrorists exist in places like Gaza? Does terrorism exist in a vacuum? There is never justification for terrorism. Is it wrong though to analyze and find the answer to this question? Terrorism is bad, so diagnosing ways to eliminate it seems like a righteous thing to do. </p><p>This conversation is one the most controversial in all of this. Those wanting to spark more fear, hatred, and division will use it to do so, but for the intellectually honest it is straightforward to understand.</p><p>Radicalism fueled by a combination of anger and hopelessness breeds terrorism. Where does this hopelessness and anger come from?</p><h4><strong>Displacement, forced evictions, and settlements</strong></h4><p>Palestinians <a href="https://www.amnesty.org/en/latest/campaigns/2017/06/israel-occupation-50-years-of-dispossession/">are systematically forcefully evicted and displaced from homes and land that they own</a>. These displacements usually occur without adequate notice or compensation, leaving families homeless.</p><p>While people are being forced out of their homes and land, they watch as more and more <a href="https://en.wikipedia.org/wiki/Israeli_settlement">settlements</a> are built on the land that was once theirs. The United Nations has consistently stated that Israeli settlements are illegal under international law and are an obstacle to peace.</p><p>Settlement expansion still continues relentlessly. Estimates say that there are now over 140 official settlements and about 120 "outposts" (smaller, usually unauthorized settlements). By the end of 2022, the populations living in these settlements was estimated to be over 475,000.</p><p>In 2022, Israel approved plans for 4,427 new settler housing units, the highest number since <a href="https://peacenow.org.il/en/about-us/who-are-we">Peace Now</a> started tracking in 2012.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!vc69!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c355987-35e1-4511-b224-5d58c5733b05_2604x758.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!vc69!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c355987-35e1-4511-b224-5d58c5733b05_2604x758.png 424w, /__u/substackcdn.com/image/fetch/$s_!vc69!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c355987-35e1-4511-b224-5d58c5733b05_2604x758.png 848w, /__u/substackcdn.com/image/fetch/$s_!vc69!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c355987-35e1-4511-b224-5d58c5733b05_2604x758.png 1272w, /__u/substackcdn.com/image/fetch/$s_!vc69!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c355987-35e1-4511-b224-5d58c5733b05_2604x758.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!vc69!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c355987-35e1-4511-b224-5d58c5733b05_2604x758.png" width="1456" height="424" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/3c355987-35e1-4511-b224-5d58c5733b05_2604x758.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:424,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:159810,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!vc69!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c355987-35e1-4511-b224-5d58c5733b05_2604x758.png 424w, /__u/substackcdn.com/image/fetch/$s_!vc69!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c355987-35e1-4511-b224-5d58c5733b05_2604x758.png 848w, /__u/substackcdn.com/image/fetch/$s_!vc69!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c355987-35e1-4511-b224-5d58c5733b05_2604x758.png 1272w, /__u/substackcdn.com/image/fetch/$s_!vc69!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c355987-35e1-4511-b224-5d58c5733b05_2604x758.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h4><strong>Detention without trial</strong></h4><p>Many Palestinians, including minors, have been detained over the years, often without charges or trials. Some detainees have faced <a href="https://en.wikipedia.org/wiki/Israeli_torture_in_the_occupied_territories">torture</a>.</p><h4><strong>Collective punishment</strong></h4><p>The Israeli government enacts blockades, closures, the demolitions of homes, and the destruction of civilian infrastructure as collective punishment for the actions of a few individuals.</p><p>The Gaza blockade in particular has had a devastating impact on the civilians living there, restricting access to basic necessities like food, water, and medical supplies. There are people who have never been allowed to leave Gaza during their lifetimes, a strip of land 25 miles long with a maximum width of 7.5 miles.</p><p>Collective punishment is not just cruel, it is also prohibited under international law.</p><h4><strong>Restriction and control of movement</strong></h4><p>Palestinians face severe restrictions on their movement. These restrictions hinder access to essential services, education, and employment opportunities.</p><p>People living in the West Bank do not face a full blockade, but still have to receive approval and permits to move throughout the region, facing numerous roadblocks, checkpoints, barriers, and inspections run by the Israeli military along the way.</p><h4><strong>Excessive use of force</strong></h4><p>When people decide to collectively speak up or perform activism against any of these injustices, Israeli security forces often use excessive and disproportionate force against Palestinian protesters and civilians, including the use of live ammunition.</p><h4>Human rights depend on the human</h4><p>The message to the Palestinian population has been loud and clear: human rights are applied selectively based on borders, races, ethnicities, and religions</p><h3>Israel&#8217;s right to exist</h3><p>The Jewish people have maintained a presence there for thousands of years, long before the creation of the state of Israel. Israel is the only Jewish homeland that exists. Millions of Jewish refugees fleeing persecution settled in Israel, providing a vital sanctuary. They have faced centuries of persecution around the world. Beyond WW2, many Israelis are themselves refugees or the descendants of refugees <a href="https://en.wikipedia.org/wiki/Jewish_exodus_from_the_Muslim_world">from many parts of the Arab world</a>.</p><p>Most Israelis that I know acknowledge the right for Palestinians to also have their own country, and want to live peacefully alongside them. They respect, are friends with, and do business with each other.</p><p>I support a <a href="https://en.m.wikipedia.org/wiki/Two-state_solution">two state solution</a>. To get there, compromises need to be made by both Israelis and Palestinians, and the extremists from both sides need to be ousted completely.</p><p>To say this is a gross oversimplification of course, and does not take into account the complexities of local governments and politics on both sides, religion, US involvement, Zionism, or the financial support of Hamas by Iran and Saudi Arabia over the years among many other things. I encourage everyone to do their own research, dive into the history, and try to understand both sides. I still have hope that there can be peace. </p><h3>Fighting for what is yours</h3><p>One of my strongly held opinions is that people have the right to stand up for themselves and fight for what&#8217;s theirs. I believe this whether you are American, Mexican, Israeli, Moroccan, Bengali, or yes &#8211;&nbsp;even Palestinian.</p><p>This is not an unpopular opinion. Indeed, protections for property owners, including provisions allowing lethal force in some cases, <a href="https://www.ncsl.org/civil-and-criminal-justice/self-defense-and-stand-your-ground">have even been codified in the laws of at least 28 states and Puerto Rico</a>.</p><p>These are some of the most popular and celebrated laws from Conservatives in America today. So why do we see a reversal from certain people about this issue when being applied to other nationalities, nations, and ethnicities?</p><p>I cannot say what I would do in the situation of someone in Palestine who&#8217;s had their home taken from them, but terrorism or violence against innocent people is never acceptable or justified.</p><p>What I can share is what I would do in my own personal situation and life, as a civilian living in the United States. If someone stole, then lived on my family&#8217;s land, while attempting to imprison me and my family for the rest of our lives, I would fight back against the people attempting to take it from me, by any means necessary. Again, this is a very popular sentiment in the US &#8211; something enshrined into US law. </p><h3>Extremism</h3><p>Though many of the images and videos I&#8217;ve seen online are grim, I&#8217;m thankful for social media in times like this. We get a real and uncut view of what is happening. We see and hear perspectives we would have never had in the past from mainstream media. Social media has shined a light on many injustices over the years that we would have never known about without it.</p><p>Social media is also full of people trying to convince you that their side is right. The truth is, it&#8217;s nearly impossible to change someone&#8217;s mind during these interactions, but getting an uncensored view of everything happening on all sides does help paint a more clear picture.</p><p>Many of these people are sharing cherry-picked or extreme examples of one side or the other doing or saying something to make their case, but these extremes do not represent the whole and are meant to stir more division. We&#8217;ve already seen violence against both Jews and Arabs outside of the region probably due to the rise in propaganda from both sides.</p><p>Nuance is where the truth lies, but it is the enemy of the extremist. Regardless of the actual truth, extremists will take any situation, statement, message, or incident, and try to spin it to into something that can be simplified in order to suit their own agenda and narrative. Including this writing.</p><h3>The end</h3><p>I have been afraid to share my views because I know how controversial of a topic this is. I know I might lose friends, followers, and job opportunities because of my views. But if I can shed more light on the situation, and bring even another ounce of humanity into it, it will have been worth it.</p><p>I encourage you to look into and research the history of this conflict yourself, especially listening to people with opposing opinions and lived experiences, to come to your own conclusion. </p>]]></content:encoded></item><item><title><![CDATA[Supercharge Your GPT Model: Custom Data Fine-Tuning using Node.js]]></title><description><![CDATA[How to Customize Your GPT Model: A Simple Guide to Personalized Training Data Integration]]></description><link>https://nader.substack.com/p/supercharge-your-gpt-model-custom</link><guid isPermaLink="false">https://nader.substack.com/p/supercharge-your-gpt-model-custom</guid><dc:creator><![CDATA[Nader Dabit]]></dc:creator><pubDate>Sun, 19 Mar 2023 08:13:33 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!aQ6n!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd40f5972-2f71-4c94-860c-5648e16ba9f7_1024x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>While GPT and ChatGPT are already powerful on their own, they are limited in that the training data is not up to date and could be missing information on niche topics. I wanted to learn how to easily train a GPT model using arbitrary, custom data, so I did. Next, I decided to document the process for other people to use.</p><p>In this tutorial you&#8217;ll learn how to fine-tune GPT with your own custom data from scratch.</p><blockquote><p>To view the final codebase for this tutorial, click <a href="https://github.com/dabit3/gpt-fine-tuning-with-nodejs">here.</a> The video for this tutorial is available <a href="https://www.youtube.com/watch?v=Sb7U32kXMB0">here</a>.</p></blockquote><h2>API</h2><p>The OpenAI GPT APIs and SDKs make it easy to fine-tune a model using either Python, Node.js, or just an HTTP request. There are also quite a few <a href="https://platform.openai.com/docs/libraries/community-libraries">community-maintained libraries</a> for other languages like PHP and Ruby.</p><p>In this tutorial we&#8217;ll be using the <a href="https://github.com/openai/openai-node">OpenAI Node.js SDK</a>. This SDK is simple to use and allows you to make API calls to OpenAI in just a couple of lines of code.</p><p>For instance, to retrieve a completion we can just call the <code>createCompletion</code> method:</p><pre><code>const response = await openai.createCompletion({
  model: 'davinci',
  prompt: "What is Lens Protocol",
  max_tokens: 200
})</code></pre><p>In our case, we&#8217;ll just be replacing the <code>davinci </code>model with our own model that will use our custom data set we&#8217;ve uploaded for training.</p><h3>Prerequisites</h3><p>To follow along with this tutorial, you&#8217;ll need to have the following installed on your machine:</p><ul><li><p>Python</p></li><li><p>Node.js</p></li></ul><p>You&#8217;ll also need an OpenAI API Key. You can get one at <a href="https://openai.com">https://openai.com</a>.</p><h2>Getting started</h2><p>To get started, we&#8217;ll create a new Node.js project in an empty directory:</p><pre><code>mkdir custom-ai-model
cd custom-ai-model
npm init --y</code></pre><p>Update the new <strong>package.json</strong> file to use ES Modules by adding this line of configuration:</p><pre><code>"type": "module",</code></pre><p>Next, set the environment variable to hold your OpenAI API Key. You can set this in your terminal session or in a configuration file like <code>.bashrc</code> or <code>.zshrc</code>:</p><pre><code>export OPENAI_KEY="your-api-key"</code></pre><p>Next, we&#8217;ll install the <code>openai</code> npm package:</p><pre><code>npm install openai</code></pre><p>Finally we&#8217;ll install the OpenAI CLI and the OpenAI datalib:</p><pre><code>pip install --upgrade openai openai"[datalib]"</code></pre><p>Now we&#8217;re ready to start writing some code!</p><h2>Creating the training data</h2><p>There are <a href="https://platform.openai.com/docs/models/gpt-3">a handful of models available</a> from OpenAI, but when fine-tuning the original GPT-3 base models are currently the only models available to start from, like <strong>davinci</strong>, <strong>curie</strong>, <strong>babbage</strong>, and <strong>ada</strong>. We&#8217;ll be using <strong>davinci</strong>.</p><p>Your data needs to be created in a <a href="https://jsonlines.org/examples/">JSONL</a> document in the following format, with each declaration separated in a new line:</p><pre><code>{"prompt": "&lt;prompt text&gt;", "completion": "&lt;ideal generated text&gt;"}
{"prompt": "&lt;prompt text&gt;", "completion": "&lt;ideal generated text&gt;"}
{"prompt": "&lt;prompt text&gt;", "completion": "&lt;ideal generated text&gt;"}</code></pre><p>It is recommended to provide at least a few hundred of these examples, most sources recommending 500 or more.</p><p>In my experience, creating a few dozen inputs will at least give you some basic result that you can test out in this tutorial, but it is not enough to use as a production ready product. </p><blockquote><p><em>From<a href="https://platform.openai.com/docs/guides/fine-tuning/general-best-practices"> the docs</a>: &#8230; performance tends to linearly increase with every doubling of the number of examples. Increasing the number of examples is usually the best and most reliable way of improving performance.</em></p></blockquote><p>Once you&#8217;ve created your document, OpenAI provides <a href="https://platform.openai.com/docs/guides/fine-tuning/cli-data-preparation-tool">a command line utility</a> which will improve the format of your data and is recommended to run before you upload.</p><pre><code>openai tools fine_tunes.prepare_data -f &lt;LOCAL_FILE&gt;</code></pre><blockquote><p>You can view, and even copy and use, my example data set <a href="https://gist.github.com/dabit3/f273d3accdec6bc6edfaf6d983942ffc">here</a> for this tutorial.</p></blockquote><p>Once your data set is ready, we can create a script to upload it to OpenAI.</p><h2>Creating the API interface</h2><p>This file will define the API that we&#8217;ll be using to interact with the OpenAI APIs.</p><p>Since we&#8217;ll be re-using this code over and over, we&#8217;ll separate it into it&#8217;s own file and just import it into our other files.</p><p>Create a file named <strong>api.js</strong> and add the following code:</p><pre><code>/* api.js */
import { Configuration, OpenAIApi } from 'openai'
const openaiApiKey = process.env.OPENAI_KEY
const configuration = new Configuration({
  apiKey: openaiApiKey
})

export const openai = new OpenAIApi(configuration)</code></pre><h2>Uploading the training data</h2><p>Now that we&#8217;ve created the API, we can use it to upload our file to use later.</p><p>Create a file named <strong>uploadFile.js</strong> and add the following code:</p><pre><code>/* uploadFile.js */
import { openai } from './api.js'
import fs from 'fs'

async function upload() {
  try {
    const response = await openai.createFile(
      fs.createReadStream('./data_prepared.jsonl'),
      "fine-tune"
    );
    console.log('File ID: ', response.data.id)
  } catch (err) {
    console.log('err: ', err)
  }
}

upload()</code></pre><p>Next, run the script:</p><pre><code>node uploadFile.js</code></pre><p>When the upload is complete, take note of the the File ID that is logged out, we&#8217;ll be needing it in the next step.</p><h2>Creating the Fine Tune</h2><p>Now that we&#8217;ve uploaded the training data and have the file ID, we can use it to custom train our model.</p><p>To do so, create a new file named <strong>createFineTune.js</strong> and add the following code:</p><pre><code>/* createFineTune.js */
import { openai } from './api.js'

async function createFineTune() {
  try {
    const response = await openai.createFineTune({
      training_file: 'your-file-id',
      model: 'davinci'
    })
    console.log('response: ', response)
  } catch (err) {
    console.log('error: ', err.response.data.error)
  }
}

createFineTune()</code></pre><p>Next, execute the script to start fine tuning your model:</p><pre><code>node createFineTune.js</code></pre><h2>Listing your Fine Tunes</h2><p>Once the fine tune is created, it will take some time to process. We can get the status of the fine tune, as well as the model ID, by calling the <code>listFineTunes</code> API method.</p><p>Create a new file named <strong>listFineTunes.js</strong> and add the following code:</p><pre><code>/* listFineTunes.js */
import { openai } from './api.js'

async function listFineTunes() {
  try {
    const response = await openai.listFineTunes()
    console.log('data: ', response.data.data)
  } catch (err) {
    console.log('error:', err)
  }
}

listFineTunes()</code></pre><p>Run the script.</p><pre><code>node listFineTunes.js</code></pre><p>It will take the process a few minutes to finish. Once the process is completed and you run the script again, you should see the <code>fine_tuned_model</code> field populated with the new model ID.</p><p>Take note of this fine_tuned_model ID, you&#8217;ll need it in the next step.</p><h2>Testing it out</h2><p>Now that the fine tune has processed and our. new model is ready, we can try it out!</p><p>Create a new file named <strong>createCompletion.js</strong>. Here, add the following code. Be sure to update the <code>fine_tuned_model</code> value with your model name:</p><pre><code>/* createCompletion.js */
import { openai } from './api.js'

async function createCompletion() {
  try {
    const response = await openai.createCompletion({
      model: 'your-custom-model-name',
      prompt: 'What is Lens Protocol',
      max_tokens: 200
    })
    if (response.data) {
      console.log('choices: ', response.data.choices)
    }
  } catch (err) {
    console.log('err: ', err)
  }
}

createCompletion()</code></pre><p>Run the script:</p><pre><code>node createCompletion.js</code></pre><h2>&#127881; Congratulations</h2><p>You&#8217;ve successfully deployed your own custom model using GPT3!</p><blockquote><p>To view the final codebase for this tutorial, click <a href="https://github.com/dabit3/gpt-fine-tuning-with-nodejs">here.</a></p></blockquote>]]></content:encoded></item><item><title><![CDATA[Building High Impact Developer Communities]]></title><description><![CDATA[A GUIDE TO GROWING DEVELOPER ECOSYSTEMS]]></description><link>https://nader.substack.com/p/building-high-impact-developer-communities</link><guid isPermaLink="false">https://nader.substack.com/p/building-high-impact-developer-communities</guid><dc:creator><![CDATA[Nader Dabit]]></dc:creator><pubDate>Wed, 19 Oct 2022 12:08:47 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!CJ66!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F0f1de12c-86f6-4ebc-9267-e2f5087bbfe6_1794x1008.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I&#8217;ve spent the past 10 years of my life building developer communities in some capacity. From meetups, to coding schools, to conferences, and from FAANG, to blockchain protocols, to DAOs, I&#8217;ve learned a lot of valuable lessons from my failures and successes along the way.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!CJ66!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F0f1de12c-86f6-4ebc-9267-e2f5087bbfe6_1794x1008.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!CJ66!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F0f1de12c-86f6-4ebc-9267-e2f5087bbfe6_1794x1008.png 424w, /__u/substackcdn.com/image/fetch/$s_!CJ66!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F0f1de12c-86f6-4ebc-9267-e2f5087bbfe6_1794x1008.png 848w, /__u/substackcdn.com/image/fetch/$s_!CJ66!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F0f1de12c-86f6-4ebc-9267-e2f5087bbfe6_1794x1008.png 1272w, /__u/substackcdn.com/image/fetch/$s_!CJ66!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F0f1de12c-86f6-4ebc-9267-e2f5087bbfe6_1794x1008.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!CJ66!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F0f1de12c-86f6-4ebc-9267-e2f5087bbfe6_1794x1008.png" width="1456" height="818" data-attrs="{&quot;src&quot;:&quot;https://bucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com/public/images/0f1de12c-86f6-4ebc-9267-e2f5087bbfe6_1794x1008.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:818,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:3658963,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!CJ66!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F0f1de12c-86f6-4ebc-9267-e2f5087bbfe6_1794x1008.png 424w, /__u/substackcdn.com/image/fetch/$s_!CJ66!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F0f1de12c-86f6-4ebc-9267-e2f5087bbfe6_1794x1008.png 848w, /__u/substackcdn.com/image/fetch/$s_!CJ66!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F0f1de12c-86f6-4ebc-9267-e2f5087bbfe6_1794x1008.png 1272w, /__u/substackcdn.com/image/fetch/$s_!CJ66!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F0f1de12c-86f6-4ebc-9267-e2f5087bbfe6_1794x1008.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Due to the flywheel of network effects, strong developer communities are one of the most sought after components when building out a developer-facing software company or product. In fact, network effects are one of the most important things that come to mind when understanding the value of developer communities:</p><p><strong>Network effect - &#8220;A phenomenon whereby a product or service gains additional value as more people use it.&#8221;</strong></p><p>In software, and particularly in open source, the value of an ecosystem continues to grow stronger and stronger as more developers build within it.</p><p>A good analogy would be a popular ingredient like Sriracha hot sauce entering the market that becomes so popular that everyone starts creating recipes based on that brand itself, using it as a core building block. A new, better hot sauce could enter the market, but everyone knows and trusts the older brand, and there are 1000&#8217;s of recipes who call it out specifically, with dishes even named after it. It is hard to compete once a brand has achieved market saturation.</p><p>We often see superior software products enter the market, while remaining unable to gain market share in their category due to the fact that they have no interoperability with everything else that has been integrated with an older, and what could be considered inferior, product &#8211; but one with a larger and more mature developer ecosystem</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!9QHg!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fe4536044-5a02-488a-a737-670a6d7087ff_2870x1606.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!9QHg!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fe4536044-5a02-488a-a737-670a6d7087ff_2870x1606.png 424w, /__u/substackcdn.com/image/fetch/$s_!9QHg!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fe4536044-5a02-488a-a737-670a6d7087ff_2870x1606.png 848w, /__u/substackcdn.com/image/fetch/$s_!9QHg!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fe4536044-5a02-488a-a737-670a6d7087ff_2870x1606.png 1272w, /__u/substackcdn.com/image/fetch/$s_!9QHg!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fe4536044-5a02-488a-a737-670a6d7087ff_2870x1606.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!9QHg!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fe4536044-5a02-488a-a737-670a6d7087ff_2870x1606.png" width="1456" height="815" data-attrs="{&quot;src&quot;:&quot;https://bucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com/public/images/e4536044-5a02-488a-a737-670a6d7087ff_2870x1606.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:815,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:9301608,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!9QHg!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fe4536044-5a02-488a-a737-670a6d7087ff_2870x1606.png 424w, /__u/substackcdn.com/image/fetch/$s_!9QHg!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fe4536044-5a02-488a-a737-670a6d7087ff_2870x1606.png 848w, /__u/substackcdn.com/image/fetch/$s_!9QHg!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fe4536044-5a02-488a-a737-670a6d7087ff_2870x1606.png 1272w, /__u/substackcdn.com/image/fetch/$s_!9QHg!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fe4536044-5a02-488a-a737-670a6d7087ff_2870x1606.png 1456w" sizes="100vw"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>One of my favorite things to point out is that <em><strong>the more people building in an ecosystem, the more people build in that ecosystem</strong></em>. So how do you bootstrap a strong and resilient ecosystem, and do it as quickly as possible?</p><p>This is what this post is about.</p><h2>To understand my perspective </h2><p>I think it&#8217;s worth understanding my own story and background of how I got into the industry. Or, we can just jump to the main points.</p><p>One of the most popular tweets I have ever made was outlining my own personal journey as a developer:</p><div class="twitter-embed" data-attrs="{&quot;url&quot;:&quot;https://twitter.com/dabit3/status/1259471051429478400&quot;,&quot;full_text&quot;:&quot;My path as a developer:\n\n- HS dropout @ 17\n- College dropout @ 19\n- Started coding @ 29\n- Fired from first job @ 30\n- Landed $60k job @ 33\n- Told $80k was too high @ 34\n- Landed $120k consulting contract @ 36\n- $400k as independent consultant @ 37\n- Doing cool shit at FAANG @ 39&quot;,&quot;username&quot;:&quot;dabit3&quot;,&quot;name&quot;:&quot;nader (&#129521;, &#128640;) | nader.lens&quot;,&quot;profile_image_url&quot;:&quot;&quot;,&quot;date&quot;:&quot;Sun May 10 13:11:13 +0000 2020&quot;,&quot;photos&quot;:[],&quot;quoted_tweet&quot;:{},&quot;reply_count&quot;:0,&quot;retweet_count&quot;:2778,&quot;like_count&quot;:16250,&quot;impression_count&quot;:0,&quot;expanded_url&quot;:{},&quot;video_url&quot;:null,&quot;video_preview_media_key&quot;:null,&quot;belowTheFold&quot;:true}" data-component-name="Twitter2ToDOM"></div><p>This tweet resonated with a lot of people because it showed how late I started programming, the fact that I did not have a formal education, and that I was not ashamed of either of those things.</p><p>The main takeaway I like to point out from my experience getting my foot in the door is that I am self taught, but that I owe all of what I know to people who came before me that I label <em><strong>bridge builders</strong></em>.</p><p>A talk that had probably the most impact on me in my life was a keynote titled <strong>Building Bridges</strong> by <a href="https://twitter.com/aaronfrost">Aaron Frost</a> at NG Vegas 2015.</p><div id="youtube2-E8TkRKWGSVc" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;E8TkRKWGSVc&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/E8TkRKWGSVc?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><p>In this talk Aaron cemented powerful values that had already been living in my head for sometime &#8212; that many of us are here, living happy and succesful lives, due only to the fact that there have been countless people that have helped us out along the way.</p><p>This poem in particular touched me a lot:</p><h3><strong>The Bridge Builder</strong></h3><p>BY <a href="https://www.poetryfoundation.org/poets/will-allen-dromgoole">WILL ALLEN DROMGOOLE</a></p><p>An old man going a lone highway,<br>Came, at the evening cold and gray,<br>To a chasm vast and deep and wide.<br>Through which was flowing a sullen tide<br>The old man crossed in the twilight dim,<br>The sullen stream had no fear for him;<br>But he turned when safe on the other side<br>And built a bridge to span the tide.</p><p>&#8220;Old man,&#8221; said a fellow pilgrim near,<br>&#8220;You are wasting your strength with building here;<br>Your journey will end with the ending day,<br>You never again will pass this way;<br>You&#8217;ve crossed the chasm, deep and wide,<br>Why build this bridge at evening tide?&#8221;</p><p>The builder lifted his old gray head;<br>&#8220;Good friend, in the path I have come,&#8221; he said,<br>&#8220;There followed after me to-day<br>A youth whose feet must pass this way.<br>This chasm that has been as naught to me<br>To that fair-haired youth may a pitfall be;<br>He, too, must cross in the twilight dim;<br><em><strong>Good friend, I am building this bridge for him!&#8221;</strong></em></p><p>For most my life I had rarely been able to give anything back as I myself was still figuring things out for myself. But as I found myself gaining some tiny traction in the software industry, I became deeply compelled to start doing everything I could to give back in any way I could.</p><p>As a self-taught developer, I also felt so much gratitude to those who came before me that I became enthusiastic and almost obsessed with passing on anything and everything that I picked up that helped me along the way in the hope that it would also help those who came after me.</p><h3>Building bridges as a developer</h3><p>I realized that as a developer, there are many ways you can be a bridge builder:</p><ul><li><p>Teaching / speaking at meetups and conferences</p></li><li><p>Answering questions on stack overflow</p></li><li><p>Mentoring new developers</p></li><li><p>Open sourcing your work</p></li><li><p>Blogging / videos about what you&#8217;ve learned</p></li><li><p>Building communities focused on public goods</p></li><li><p>Sharing ideas and opportunities on social media</p></li></ul><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!xvKZ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F5b2a89c0-0ff4-441d-8f1e-146b9174fc37_1788x1006.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!xvKZ!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F5b2a89c0-0ff4-441d-8f1e-146b9174fc37_1788x1006.png 424w, /__u/substackcdn.com/image/fetch/$s_!xvKZ!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F5b2a89c0-0ff4-441d-8f1e-146b9174fc37_1788x1006.png 848w, /__u/substackcdn.com/image/fetch/$s_!xvKZ!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F5b2a89c0-0ff4-441d-8f1e-146b9174fc37_1788x1006.png 1272w, /__u/substackcdn.com/image/fetch/$s_!xvKZ!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F5b2a89c0-0ff4-441d-8f1e-146b9174fc37_1788x1006.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!xvKZ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F5b2a89c0-0ff4-441d-8f1e-146b9174fc37_1788x1006.png" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://bucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com/public/images/5b2a89c0-0ff4-441d-8f1e-146b9174fc37_1788x1006.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:3628951,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!xvKZ!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F5b2a89c0-0ff4-441d-8f1e-146b9174fc37_1788x1006.png 424w, /__u/substackcdn.com/image/fetch/$s_!xvKZ!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F5b2a89c0-0ff4-441d-8f1e-146b9174fc37_1788x1006.png 848w, /__u/substackcdn.com/image/fetch/$s_!xvKZ!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F5b2a89c0-0ff4-441d-8f1e-146b9174fc37_1788x1006.png 1272w, /__u/substackcdn.com/image/fetch/$s_!xvKZ!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F5b2a89c0-0ff4-441d-8f1e-146b9174fc37_1788x1006.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The goal being to genuinely help those who come after you to achieve what you have achieved <em><strong>and more</strong></em>.</p><p>Some of the ways that I built bridges in my career:</p><ul><li><p>Created <a href="https://www.meetup.com/jackson-area-web-and-app-developers/">first MS coding meetup meetup</a> 2014</p></li><li><p>Created <a href="https://twitter.com/Codesouthlabs">first MS coding school</a> in 2014 (failed)</p></li><li><p>#1 on Stack Overflow<a href="https://stackoverflow.com/tags/react-native/topusers"> for React Native</a></p></li><li><p>Built and open sourced <a href="https://github.com/react-native-elements/react-native-elements">React Native Elements</a></p></li><li><p>15,000,000 views on technical blog</p></li><li><p>Founded <a href="https://twitter.com/developer_dao">Developer DAO</a></p></li></ul><h2>Bridges to networks</h2><p>Something I did not realize was happening early on during this time was how valuable the networks I began organically building were becoming.</p><p>As you make this part of your day to day, you often lose track of how many people you&#8217;ve helped in some way. Often people will find your blog posts or video,s use it to kick start a company or land a job. Every time you help someone become succesful, they remember and hold on to that experience. </p><p>Over time you build out an enormous network. If you&#8217;ve done things right, people also begin to gain trust in you &#8212; or at least trust that you&#8217;re being honest and straightforward even when you are wrong.</p><p>During my career, moving from engineering, to running my own company, to participating in DevRel at scale, what I came to realize was this:</p><h3>The most important component of building high impact developer communities is Building Bridges.</h3><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!vz-0!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F596dafda-0e61-4cf6-a1ca-179bb5c9ea14_1792x1006.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!vz-0!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F596dafda-0e61-4cf6-a1ca-179bb5c9ea14_1792x1006.png 424w, /__u/substackcdn.com/image/fetch/$s_!vz-0!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F596dafda-0e61-4cf6-a1ca-179bb5c9ea14_1792x1006.png 848w, /__u/substackcdn.com/image/fetch/$s_!vz-0!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F596dafda-0e61-4cf6-a1ca-179bb5c9ea14_1792x1006.png 1272w, /__u/substackcdn.com/image/fetch/$s_!vz-0!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F596dafda-0e61-4cf6-a1ca-179bb5c9ea14_1792x1006.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!vz-0!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F596dafda-0e61-4cf6-a1ca-179bb5c9ea14_1792x1006.png" width="1456" height="817" data-attrs="{&quot;src&quot;:&quot;https://bucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com/public/images/596dafda-0e61-4cf6-a1ca-179bb5c9ea14_1792x1006.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:817,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:3553931,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!vz-0!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F596dafda-0e61-4cf6-a1ca-179bb5c9ea14_1792x1006.png 424w, /__u/substackcdn.com/image/fetch/$s_!vz-0!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F596dafda-0e61-4cf6-a1ca-179bb5c9ea14_1792x1006.png 848w, /__u/substackcdn.com/image/fetch/$s_!vz-0!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F596dafda-0e61-4cf6-a1ca-179bb5c9ea14_1792x1006.png 1272w, /__u/substackcdn.com/image/fetch/$s_!vz-0!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F596dafda-0e61-4cf6-a1ca-179bb5c9ea14_1792x1006.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>Framework for Developer Impact</h2><p>FOR THE INDIVIDUAL</p><p>Before we get into building communities, let&#8217;s first start with the individual. To make this simple, I created a visual hierarchy that to me can be a framework to aspire to as an individual working to build a great developer community.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!nIo2!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F06fa1849-ea45-4e16-837d-d37e721d9066_3600x3200.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!nIo2!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F06fa1849-ea45-4e16-837d-d37e721d9066_3600x3200.jpeg 424w, /__u/substackcdn.com/image/fetch/$s_!nIo2!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F06fa1849-ea45-4e16-837d-d37e721d9066_3600x3200.jpeg 848w, /__u/substackcdn.com/image/fetch/$s_!nIo2!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F06fa1849-ea45-4e16-837d-d37e721d9066_3600x3200.jpeg 1272w, /__u/substackcdn.com/image/fetch/$s_!nIo2!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F06fa1849-ea45-4e16-837d-d37e721d9066_3600x3200.jpeg 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!nIo2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F06fa1849-ea45-4e16-837d-d37e721d9066_3600x3200.jpeg" width="1456" height="1294" data-attrs="{&quot;src&quot;:&quot;https://bucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com/public/images/06fa1849-ea45-4e16-837d-d37e721d9066_3600x3200.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1294,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:287226,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!nIo2!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F06fa1849-ea45-4e16-837d-d37e721d9066_3600x3200.jpeg 424w, /__u/substackcdn.com/image/fetch/$s_!nIo2!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F06fa1849-ea45-4e16-837d-d37e721d9066_3600x3200.jpeg 848w, /__u/substackcdn.com/image/fetch/$s_!nIo2!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F06fa1849-ea45-4e16-837d-d37e721d9066_3600x3200.jpeg 1272w, /__u/substackcdn.com/image/fetch/$s_!nIo2!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F06fa1849-ea45-4e16-837d-d37e721d9066_3600x3200.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>While I believe starting from the bottom and going up is a good approach, there are always hacks that enable higher impact while skipping some of the in-between. </p><h2>Framework for Developer Impact</h2><p>FOR THE COMPANY // TEAM // PRODUCT</p><p>While I have had experience in the past year taking part in building a shared community with Developer DAO, most of my experience comes from building communities around software ecosystems and products, so that is really the main focus of this post and the following framework.</p><h3>1. Build something useful</h3><p>This should be obvious (but obviously isn&#8217;t), but you first need to have something that people want.</p><p>Instead of trying to build a community around something mediocre or worse, you should go back to the drawing board and experiment more.</p><h3>2. Have amazing docs</h3><p>Once you have a quality product, your documentation will be the main entrypoint to your developer ecosystem. If you&#8217;re lucky enough to get developers to notice you, you absolutely have to give them the best shot possible at being successful by having pristine docs.</p><p>It&#8217;s unreal how many times I&#8217;ve seen multi-million dollar companies spend countless hours (and large amounts of money) to build an incredible piece of software, while having documentation that loses 99% of the developers who try and use it for the first time.</p><p>Your developer docs should have a no-brainer quick-start guide that makes no assumptions and gets developers from zero to one, preferably all in a single page without needing to navigate anywhere.</p><p>Giving new developers this mental win and quality first impression is unbelievably important, and should be top of mind when building out your documentation. </p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!i_Or!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fe9e68af0-83d5-4540-bbd4-c8fbef9ad56a_1792x1008.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!i_Or!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fe9e68af0-83d5-4540-bbd4-c8fbef9ad56a_1792x1008.png 424w, /__u/substackcdn.com/image/fetch/$s_!i_Or!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fe9e68af0-83d5-4540-bbd4-c8fbef9ad56a_1792x1008.png 848w, /__u/substackcdn.com/image/fetch/$s_!i_Or!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fe9e68af0-83d5-4540-bbd4-c8fbef9ad56a_1792x1008.png 1272w, /__u/substackcdn.com/image/fetch/$s_!i_Or!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fe9e68af0-83d5-4540-bbd4-c8fbef9ad56a_1792x1008.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!i_Or!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fe9e68af0-83d5-4540-bbd4-c8fbef9ad56a_1792x1008.png" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://bucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com/public/images/e9e68af0-83d5-4540-bbd4-c8fbef9ad56a_1792x1008.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:3539456,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!i_Or!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fe9e68af0-83d5-4540-bbd4-c8fbef9ad56a_1792x1008.png 424w, /__u/substackcdn.com/image/fetch/$s_!i_Or!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fe9e68af0-83d5-4540-bbd4-c8fbef9ad56a_1792x1008.png 848w, /__u/substackcdn.com/image/fetch/$s_!i_Or!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fe9e68af0-83d5-4540-bbd4-c8fbef9ad56a_1792x1008.png 1272w, /__u/substackcdn.com/image/fetch/$s_!i_Or!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fe9e68af0-83d5-4540-bbd4-c8fbef9ad56a_1792x1008.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h3>3. Provide a place for conversations to happen.</h3><p>Most teams know this at this point, but there should be a dedicated channel or platform for everyone in the community to gather, ask questions, and form relationships.</p><p>You can go beyond this and create curated community chats with the top builders within your community. A good way of going about this is creating a private Telegram group where high profile people and engineers from your team are invited along with the most active and high quality builders in your community. This gives builders the opportunity to have direct contact to answer important technical questions, but also gives them a feeling of importance as you are placing them in a special group. It is a win-win and not a heavy lift.</p><h3>4. Optimize for many-to-many relationships</h3><p>Once you have the foundation in place to bring developers success, it&#8217;s time to focus on building many-to-many relationships with your community.</p><p>There is only so much an individual can do to have an impact. Building a strong developer community means being active on social media, providing high quality documentation, creating tutorials and videos, collaborating with creators and builders in the ecosystem, speaking and teaching at conferences and events, &#8230; the list goes on.</p><p>Instead of trying to do all of this yourself, or with with your team (regardless of the size), find ways to incentivize community members to take on some of this responsibility.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!t80n!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F87ed0412-4234-4404-b3cf-bb8ce02e8b60_3000x1650.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!t80n!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F87ed0412-4234-4404-b3cf-bb8ce02e8b60_3000x1650.jpeg 424w, /__u/substackcdn.com/image/fetch/$s_!t80n!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F87ed0412-4234-4404-b3cf-bb8ce02e8b60_3000x1650.jpeg 848w, /__u/substackcdn.com/image/fetch/$s_!t80n!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F87ed0412-4234-4404-b3cf-bb8ce02e8b60_3000x1650.jpeg 1272w, /__u/substackcdn.com/image/fetch/$s_!t80n!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F87ed0412-4234-4404-b3cf-bb8ce02e8b60_3000x1650.jpeg 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!t80n!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F87ed0412-4234-4404-b3cf-bb8ce02e8b60_3000x1650.jpeg" width="1456" height="801" data-attrs="{&quot;src&quot;:&quot;https://bucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com/public/images/87ed0412-4234-4404-b3cf-bb8ce02e8b60_3000x1650.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:801,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:767323,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!t80n!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F87ed0412-4234-4404-b3cf-bb8ce02e8b60_3000x1650.jpeg 424w, /__u/substackcdn.com/image/fetch/$s_!t80n!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F87ed0412-4234-4404-b3cf-bb8ce02e8b60_3000x1650.jpeg 848w, /__u/substackcdn.com/image/fetch/$s_!t80n!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F87ed0412-4234-4404-b3cf-bb8ce02e8b60_3000x1650.jpeg 1272w, /__u/substackcdn.com/image/fetch/$s_!t80n!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F87ed0412-4234-4404-b3cf-bb8ce02e8b60_3000x1650.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The way this is done is by identifying and incentivizing high quality the superstars within your community.</p><h3>5. Identify and incentivize your superstars.</h3><p>Superstars in your community can be OSS developers, blog authors, YouTubers, speakers, teachers, succesful teams building software with your product, or trusted developers with their own large networks.</p><p>Ambassador programs have become a popular way to do this, providing builders with exclusive privileges,  special Discord roles, swag, and VIP access to private events.</p><p>Beyond this, you can do things like have profile people within your team share their work and wins publicly, create private one-on-one communication channels with engineering or leadership support, or just straight up pay or hire them on a contract basis.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!t2yq!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fd073a6ce-8c8d-4dc0-9b42-477d415661ca_1790x1002.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!t2yq!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fd073a6ce-8c8d-4dc0-9b42-477d415661ca_1790x1002.png 424w, /__u/substackcdn.com/image/fetch/$s_!t2yq!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fd073a6ce-8c8d-4dc0-9b42-477d415661ca_1790x1002.png 848w, /__u/substackcdn.com/image/fetch/$s_!t2yq!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fd073a6ce-8c8d-4dc0-9b42-477d415661ca_1790x1002.png 1272w, /__u/substackcdn.com/image/fetch/$s_!t2yq!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fd073a6ce-8c8d-4dc0-9b42-477d415661ca_1790x1002.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!t2yq!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fd073a6ce-8c8d-4dc0-9b42-477d415661ca_1790x1002.png" width="1456" height="815" data-attrs="{&quot;src&quot;:&quot;https://bucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com/public/images/d073a6ce-8c8d-4dc0-9b42-477d415661ca_1790x1002.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:815,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:3550869,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!t2yq!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fd073a6ce-8c8d-4dc0-9b42-477d415661ca_1790x1002.png 424w, /__u/substackcdn.com/image/fetch/$s_!t2yq!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fd073a6ce-8c8d-4dc0-9b42-477d415661ca_1790x1002.png 848w, /__u/substackcdn.com/image/fetch/$s_!t2yq!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fd073a6ce-8c8d-4dc0-9b42-477d415661ca_1790x1002.png 1272w, /__u/substackcdn.com/image/fetch/$s_!t2yq!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fd073a6ce-8c8d-4dc0-9b42-477d415661ca_1790x1002.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>How to effectively identify these standout community members? You can of course do this manually, but <a href="https://orbit.love/">Orbit</a> does a pretty good job of automating this. I&#8217;ve used this in the past with some success and would recommend trying it out to identify who your most active community members and contributors are. You can also learn more about the Orbit Model of community <a href="https://orbitmodel.com/">here</a>.</p><h3>6. Optimize for scalable digital content</h3><p>This idea is simple yet teams rarely truly understand how powerful it is.</p><p>If you think about the ROI for attending an event vs creating something digital that is of high value / quality, almost all events are blown completely out of the water. As you&#8217;ll see in the next section this doesn&#8217;t mean you should not prioritize some budget for IRL events, it just means you need to think about them in a different light.</p><p>Let me give you an example:</p><p>At events like ETHCC, Token2049, and ETH Denver, I witnessed over and over people who flew across the world (literally), spent thousands of dollars, and checked out of their day-to-day work only to sit on a panel or present on a stage with a crowd of 5 - 30 people.</p><p>In contrast, I&#8217;ve seen high quality educational content reach 10,000 to 100,000 people on average.</p><p>A video clip usually costs ~50x less to produce and receives ~100x as many viewers as an in person talk. This means mathematically, even if we&#8217;re scaling these numbers down, in-person events are around 500x less scalable and efficient.</p><p>Imagine sending 5-10 people to speak at most of these events, blowing through hundreds of thousands (often millions) per year, only to hit around 1% of the reach as someone sitting in front of their computer writing a tweet or recording a video.</p><p>Take this seriously. Weave it into your DNA. Make it part of your strategy. Every time a dollar is spent, ask yourself this: could this dollar be spent elsewhere to have a higher impact? When talking about events, the answer is usually <strong>yes</strong>.</p><h3>7. Use IRL events to build relationships, not educate</h3><blockquote><p>I wrote more in depth about my event strategy <a href="https://x.com/dabit3/status/1812801918768456031">here</a>.</p></blockquote><p>I still think in-person events are important, but for different reasons than I did in the past.</p><p>Outside of hackathons, use IRL events to meet and form stronger relationships with developers, founders, community members, etc&#8230; That&#8217;s literally it.</p><p>The one nuance I&#8217;d call out regarding educational events is that in-person hackathons offer an opportunity to educate as well as form relationships, and I think are valuable.</p><p>I think some realistic numbers to keep in mind might be 2 in-person hackathons per year, and an in-person event per year in each main region you are interested in (e.g. USA, Europe, APAC, Asia, etc..).</p><p>This might equal 4-7 in-person events per year for your main or DevRel team, and for everything else, it can be scaled down dramatically by sending a small handful (1-3) of high impact team members to other important events.</p><p>By taking this approach, you&#8217;ll free up resources to focus on more scalable content and educational resources.</p><h3>8. Prioritize diversity</h3><p>The world is a large and diverse place. We often grow up in and around environments that are bubbles of what is actually out there in the real world.</p><p>Regardless of how much we read, experience, or go out of our way to understand other people&#8217;s lived experience, we will never be able to have the perspectives of people <em>who have lived different lived experiences</em>.</p><p>More diversity of lived experiences results in more diverse ideas and synergies, as well as an understanding of the wants and needs of people on the other side of the screen of the products that we are building.</p><p>We should go out of our way to optimize for diversity in all aspects of our community, from gender, to sexual orientation, to geographic region, to income level, to &#8230; well everything!</p><p>From a straight up business / logical perspective, this will also create a more welcoming space, enabling a larger pool of developers to feel comfortable joining and being a part of your community.</p><h3>9. Help people where they are</h3><p>EVEN IF THEY ARE NOT BUILDING WITH YOUR PRODUCT.</p><p>A huge mistake many community builders make is that they make it all about themselves or about their own products.</p><p>If you want developers to listen to you, you have to build trust. It&#8217;s hard to build trust if you are constantly only shilling your own product at every opportunity.</p><p>It&#8217;s simple really. Find opportunities to be there to help developers, and actually try to help them regardless of what the solution is.</p><p>This means that if someone has a question that has nothing to do with what you do or what you are working on, do your best to help them, and point them to the proper solutions even if it means pointing them to a competitor, and if it means you never get to talk about what you are doing</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!VRvH!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fcae9624f-1ddf-4cc4-ac69-76cb0f82d76c_1790x1002.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!VRvH!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fcae9624f-1ddf-4cc4-ac69-76cb0f82d76c_1790x1002.png 424w, /__u/substackcdn.com/image/fetch/$s_!VRvH!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fcae9624f-1ddf-4cc4-ac69-76cb0f82d76c_1790x1002.png 848w, /__u/substackcdn.com/image/fetch/$s_!VRvH!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fcae9624f-1ddf-4cc4-ac69-76cb0f82d76c_1790x1002.png 1272w, /__u/substackcdn.com/image/fetch/$s_!VRvH!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_webp, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fcae9624f-1ddf-4cc4-ac69-76cb0f82d76c_1790x1002.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!VRvH!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fcae9624f-1ddf-4cc4-ac69-76cb0f82d76c_1790x1002.png" width="1456" height="815" data-attrs="{&quot;src&quot;:&quot;https://bucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com/public/images/cae9624f-1ddf-4cc4-ac69-76cb0f82d76c_1790x1002.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:815,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:3509140,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!VRvH!, /__u/nader.substack.com/w_424, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fcae9624f-1ddf-4cc4-ac69-76cb0f82d76c_1790x1002.png 424w, /__u/substackcdn.com/image/fetch/$s_!VRvH!, /__u/nader.substack.com/w_848, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fcae9624f-1ddf-4cc4-ac69-76cb0f82d76c_1790x1002.png 848w, /__u/substackcdn.com/image/fetch/$s_!VRvH!, /__u/nader.substack.com/w_1272, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fcae9624f-1ddf-4cc4-ac69-76cb0f82d76c_1790x1002.png 1272w, /__u/substackcdn.com/image/fetch/$s_!VRvH!, /__u/nader.substack.com/w_1456, /__u/nader.substack.com/c_limit, /__u/nader.substack.com/f_auto, /__u/nader.substack.com/q_auto:good, /__u/nader.substack.com/fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fcae9624f-1ddf-4cc4-ac69-76cb0f82d76c_1790x1002.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h3>10. Be transparent about tradeoffs</h3><p>Another part of building trust is being just as transparent and straightforward as you can be about the negatives of your product as you are the positives.</p><p>When someone asks you about, or you have the opportunity to talk about, what you are working on, being transparent about both sides builds trust in that you are indeed telling the truth.</p><p>While these last two points might seem obvious to you, you&#8217;d be surprised at how many teams do not act in this way.</p><h3>11. Contract out your weaknesses</h3><p>At the time of this writing, DevRel (Developer Advocacy and Developer Relations) is among the most in demand roles that I know of in the software industry and the demand continues to grow.</p><p>A good DevRel is good at programming, communication, writing, speaking, content creation, social media, and many other things.</p><p>When hiring for Developer Relations roles its often hard to find someone who is able to do all of these things and do them well. Even if you do find the right person, most people usually have a handful of things they are naturally better at, and hiring for a handful of these specific qualities often enables you to find people who are exceptional at certain tasks.</p><p>Instead of looking for those few unicorns, consider instead hiring one high quality individual who can manage the DevRel responsibilities, then outsource the rest.</p><p>There are actually numerous benefits to this approach:</p><ol><li><p><strong>It&#8217;s less expensive.</strong> Instead of hiring someone full time, you can contract out individual tasks like creating a tutorial, video series, or OSS library.</p></li><li><p><strong>There&#8217;s less risk. </strong>Instead of hiring someone full time and buying in with all of the things that go along with a full time employee, like benefits and such, you are on a month-to-month contract that has no obligations to continue at all if they are not performing well.</p></li><li><p><strong>More specialization. </strong>You can hire for the exact things you need.</p></li><li><p><strong>Better talent.</strong> Because contracting is less expensive, you can theoretically pay more to get the highest talent available. If you are paying someone $200K / year salary + benefits + etc.., this comes out to around $16,000.00 per month. This type of money opens the door to some of the highest quality talent around. If your budget is higher, you will be able to hire almost anyone, but to be honest you can bring in high quality specialists for around half of that per month on a contract basis.</p></li><li><p>If you break the above budget down and instead decide to use it to incentivize community members, it also goes quite a long way. Imagine if you decided to pay your top 15 community contributors $1,000 per month for work they were already essentially doing?</p></li></ol><h2>END</h2><p></p><p></p>]]></content:encoded></item></channel></rss>