<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Daniel Benner</title><description>Harness · Architecture · Productivity</description><link>https://danielbenner.de/</link><language>en</language><item><title>A Causal Graph as AI Memory</title><link>https://danielbenner.de/articles/causal-graph-as-ai-memory/</link><guid isPermaLink="true">https://danielbenner.de/articles/causal-graph-as-ai-memory/</guid><description>Why I replaced my context.md with a DOT graph that the AI maintains — and why flat files stop working.</description><pubDate>Sun, 05 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In a &lt;a href=&quot;https://danielbenner.de/articles/from-markdown-notes-to-ai-chief-of-staff&quot;&gt;previous post&lt;/a&gt;, I described using a &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;context&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;md&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt; file as the AI’s working memory — a flat list of open threads split into “In Flight,” “Blocked,” and “Needs Attention.” Each entry was a one-liner: what it is plus current status.&lt;/p&gt;
&lt;p&gt;It worked for about two months. Then it stopped working.&lt;/p&gt;
&lt;h2 id=&quot;where-flat-context-breaks&quot;&gt;Where Flat Context Breaks&lt;/h2&gt;
&lt;p&gt;The problem isn’t that a markdown list can’t hold enough items. It’s that it can’t express &lt;em&gt;how things are connected&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Say you notice code quality problems across the team. That observation spawns several threads: a CI pipeline overhaul, a hiring push for a backend engineer, structured 1:1s, and a decision to restrict an AI auto-fix tool that was generating bad PRs. The CI pipeline enables trunk-based development. The hire leads to onboarding. The 1:1s surface a performance issue with one developer, and give another the space to build a bug triage skill. The AI auto-fix restriction evolves into a proper agent-ticket integration.&lt;/p&gt;
&lt;p&gt;In &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;context&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;md&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt;, these are separate bullet points:&lt;/p&gt;
&lt;figure&gt;&lt;pre tabindex=&quot;0&quot;&gt;&lt;code style=&quot;display:grid&quot;&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;## In Flight&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; CI pipeline — live, main is default branch&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; Backend engineer — hired, onboarding in progress&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; 1:1s — running across team&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; Dev A — performance improvement plan&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; Bug triage skill — config-vs-code classifier&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; Project management migration — agent integration next&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Every session, the AI reads this list and knows &lt;em&gt;what’s happening&lt;/em&gt;. But it doesn’t know &lt;em&gt;why&lt;/em&gt;. It can’t see that the CI pipeline exists because of quality problems, that the bug triage skill emerged from the 1:1s with one developer, or that the project management migration is what unblocks the agent-ticket integration. The backstory gets re-explained in conversations. The file doesn’t carry it.&lt;/p&gt;
&lt;p&gt;The second problem is decay. Flat lists don’t have a natural cleanup lifecycle. Resolved items sit there until someone removes them. “Someone” in practice means me, and I have better things to do than gardening a markdown file. So stale items linger, and the AI treats them as active.&lt;/p&gt;
&lt;h2 id=&quot;the-graph&quot;&gt;The Graph&lt;/h2&gt;
&lt;p&gt;The replacement is a directed graph in DOT format. One file — &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;compass&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;context&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;index&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;dot&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt; — maintained entirely by the AI. I never edit it.&lt;/p&gt;
&lt;p&gt;Nodes are inflection points. Not routine progress, not daily updates — moments where something actually changed: a decision was made, something shipped, a thread got blocked, or a new workstream spawned from an existing one.&lt;/p&gt;
&lt;figure&gt;&lt;pre tabindex=&quot;0&quot;&gt;&lt;code style=&quot;display:grid&quot;&gt;&lt;span&gt;&lt;span&gt;n_20260115_quality_problems [&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    label=&quot;Code quality problems observed across team&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    timestamp=&quot;2026-01-15&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    status=&quot;resolved&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    detail=&quot;Sloppy code getting through, bugs in production.&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            Triggered multiple improvement tracks.&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span&gt;n_20260129_ci_decision [&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    label=&quot;Decided: trunk-based dev for new product,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            keep old branching model for legacy&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    timestamp=&quot;2026-01-29&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    status=&quot;resolved&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    detail=&quot;New product needs fast iteration.&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            Legacy needs stability.&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span&gt;n_20260205_ci_live [&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    label=&quot;CI pipeline live — main is default branch&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    timestamp=&quot;2026-02-05&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    status=&quot;resolved&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    detail=&quot;Develop branch deleted.&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            Features must work when merged.&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Edges encode causation:&lt;/p&gt;
&lt;figure&gt;&lt;pre tabindex=&quot;0&quot;&gt;&lt;code style=&quot;display:grid&quot;&gt;&lt;span&gt;&lt;span&gt;n_20260115_quality_problems -&amp;gt; n_20260129_ci_decision [type=&quot;led_to&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;n_20260129_ci_decision -&amp;gt; n_20260205_ci_live [type=&quot;led_to&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;n_20260205_ci_live -&amp;gt; n_20260223_stacked_prs [type=&quot;enabled&quot;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Five edge types cover most relationships: &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;led_to&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt;, &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;spawned&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt;, &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;enabled&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt;, &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;blocked_by&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt;, &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;invalidated&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt;. Subgraphs cluster related nodes into workstreams. Cross-thread edges connect workstreams when they interact — a hire in one thread enables a capability in another.&lt;/p&gt;
&lt;p&gt;The convention that makes it work: &lt;strong&gt;leaves are the current state&lt;/strong&gt;. A node with no outgoing edges to other active nodes is where things stand right now. Trace backwards from any leaf, and you get the causal chain of how it got there.&lt;/p&gt;
&lt;h2 id=&quot;what-gets-a-node&quot;&gt;What Gets a Node&lt;/h2&gt;
&lt;p&gt;Not everything. This was the hardest convention to get right.&lt;/p&gt;
&lt;p&gt;A node is an &lt;strong&gt;inflection point&lt;/strong&gt; — a moment that changed the trajectory of a workstream. Specifically:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A decision was made&lt;/li&gt;
&lt;li&gt;Something shipped or went live&lt;/li&gt;
&lt;li&gt;A thread got blocked or unblocked&lt;/li&gt;
&lt;li&gt;A new thread was spawned from an existing one&lt;/li&gt;
&lt;li&gt;New information changed the picture&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;“Made progress on feature X” is not a node. “Feature X shipped” is. “Had a good conversation with Y” is not a node. “Y accepted the role change” is. The graph captures the shape of what happened, not the texture.&lt;/p&gt;
&lt;p&gt;The AI makes this judgment call on its own, and it’s right about 90% of the time. The other 10% is usually over-persisting — creating a node for something that was really just routine progress. That’s the easier error to correct.&lt;/p&gt;
&lt;h2 id=&quot;pruning-and-archiving&quot;&gt;Pruning and Archiving&lt;/h2&gt;
&lt;p&gt;This is where the flat-list decay problem gets solved.&lt;/p&gt;
&lt;p&gt;When all leaves of a subgraph are resolved or abandoned, and the subgraph is older than roughly three weeks, it gets collapsed: the full graph moves to an archive file, and a single summary node stays in the index.&lt;/p&gt;
&lt;figure&gt;&lt;pre tabindex=&quot;0&quot;&gt;&lt;code style=&quot;display:grid&quot;&gt;&lt;span&gt;&lt;span&gt;n_archive_agent_pipeline [&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    label=&quot;Agent pipeline — proven and working in production&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    timestamp=&quot;2026-02-08 to 2026-03-19&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    status=&quot;resolved&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    detail=&quot;Research, state machine, QA agent.&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            Pipeline working hands-off.&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            See compass/context/archive/agent-pipeline.dot&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;The archive is still there if the AI needs to trace deep history. But the working graph stays focused on what’s active. Currently I have eight archived subgraphs compressed into summary nodes, and the index stays under 300 lines.&lt;/p&gt;
&lt;p&gt;The cleanup is natural — it follows from the status conventions rather than requiring manual gardening.&lt;/p&gt;
&lt;h2 id=&quot;why-dot&quot;&gt;Why DOT&lt;/h2&gt;
&lt;p&gt;People ask why not JSON or YAML. A few reasons.&lt;/p&gt;
&lt;p&gt;DOT is scannable. Open the file and the structure is immediately visible — nodes, edges, subgraphs, labels. JSON would be a wall of nested objects. YAML would be fragile about indentation.&lt;/p&gt;
&lt;p&gt;Claude reasons well about DOT. It can parse the graph structure, follow edges, identify leaves, and produce valid updates without a schema definition or parsing library. The format is simple enough that an LLM handles it natively.&lt;/p&gt;
&lt;p&gt;It renders. If you ever want to visualize the graph, &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;dot&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; -&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;Tpng&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; index&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;dot&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; -&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;o&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; graph&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;png&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt; works. I rarely do this, but it’s useful for debugging when subgraphs get tangled.&lt;/p&gt;
&lt;p&gt;And it has just enough structure without being rigid. Nodes have attributes (label, timestamp, status, detail) but the format doesn’t enforce a schema. If I need a new attribute, I add it. No migrations.&lt;/p&gt;
&lt;h2 id=&quot;what-it-enables&quot;&gt;What It Enables&lt;/h2&gt;
&lt;p&gt;The graph changes what the AI can do at session start. Instead of reading a flat list and knowing &lt;em&gt;what’s happening&lt;/em&gt;, it reads a causal narrative and knows &lt;em&gt;the story&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;This matters most for advice and planning. When I ask about a workstream, the AI doesn’t just report status — it can explain the causal chain, point to decisions that constrained the current state, and notice when threads that should be connected aren’t.&lt;/p&gt;
&lt;p&gt;It also makes session handoffs sharper. The previous post described &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;context&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;md&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt; as “briefing notes for an incoming shift.” The graph is more like handing over a case file — not just the current situation, but the chain of events that produced it.&lt;/p&gt;
&lt;p&gt;Two specific things it catches that flat files miss:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Stale threads.&lt;/strong&gt; A node sitting at &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;active&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt; status with no recent children is visibly stale — there’s been no inflection point in weeks. The graph makes this structural rather than requiring someone to notice a dusty bullet point.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Cross-thread dependencies.&lt;/strong&gt; When one workstream blocks or enables another, that edge exists in the graph. The AI can surface it: “the project management migration you’ve been putting off is what unblocks the bug triage workflow.” In a flat list, those are just two unrelated bullet points.&lt;/p&gt;
&lt;h2 id=&quot;tradeoffs&quot;&gt;Tradeoffs&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;It’s an AI-only file.&lt;/strong&gt; I don’t read or edit the graph. This is by design — the AI maintains it as part of session wrap-up, and I review the effects (better session starts, better advice) rather than the artifact. But it means the memory system is opaque to me. If the AI misrepresents something, I might not notice until it gives bad advice.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Inflection point judgment is imperfect.&lt;/strong&gt; The AI tends to over-persist rather than under-persist. A weekly conversation that was really just a status check sometimes gets a node. The fix is easy — delete or merge the node — but it requires noticing.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;DOT gets big.&lt;/strong&gt; Without the archiving lifecycle, the graph would grow indefinitely. Even with archiving, a busy quarter can produce a 400-line index. The subgraph structure keeps it navigable, but there’s a complexity ceiling.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It requires the session-end hook.&lt;/strong&gt; The graph doesn’t maintain itself — it’s updated by a background agent that runs after each session. If that hook fails or isn’t configured, the graph goes stale. The system has a dependency on infrastructure that a markdown file doesn’t.&lt;/p&gt;
&lt;h2 id=&quot;the-evolution&quot;&gt;The Evolution&lt;/h2&gt;
&lt;p&gt;The progression was: no context → flat markdown list → causal graph. Each step solved a real problem with the previous approach.&lt;/p&gt;
&lt;p&gt;The flat list solved cold starts — AI stopped asking “what are you working on?” every session. The graph solved narrative loss — AI stopped asking “why are you doing it this way?” and “what happened with X?”&lt;/p&gt;
&lt;p&gt;Whether you need the graph depends on how many concurrent threads you’re managing and how long they live. If you’re tracking three things over two weeks, &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;context&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;md&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt; is fine. If you’re tracking twenty things over three months with causal dependencies between them, the flat file will eventually fail you the same way it failed me.&lt;/p&gt;
&lt;p&gt;The building blocks haven’t changed from the &lt;a href=&quot;https://danielbenner.de/articles/minimal-ai-friendly-notes&quot;&gt;original system&lt;/a&gt;: plain text, simple conventions, full transparency. The graph is just a better data structure for the job.&lt;/p&gt;</content:encoded><category>AI</category><category>Productivity</category><category>Tools</category></item><item><title>📚 The Art of Action — Stephen Bungay</title><link>https://danielbenner.de/books/the-art-of-action/</link><guid isPermaLink="true">https://danielbenner.de/books/the-art-of-action/</guid><description>The core argument is strong and immediately practical — the three gaps framework and leading with intent over instructions clicked right away. But the book is way too long for what it has to say. You get the idea early, and then it&apos;s variations on the same theme for another 200 pages.</description><pubDate>Sun, 29 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;div class=&quot;my-6 border-l-2 border-accent pl-6 py-2&quot;&gt; &lt;h2 class=&quot;text-sm font-bold uppercase tracking-widest mb-2&quot; style=&quot;color:var(--color-accent)&quot;&gt;The Three Gaps&lt;/h2&gt; &lt;div class=&quot;text-sm leading-relaxed&quot;&gt; &lt;p&gt;Execution breaks down in three distinct ways:&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Knowledge gap&lt;/strong&gt; — between plans and outcomes. What we know vs what we’d need to know.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Alignment gap&lt;/strong&gt; — between plans and actions. What we intend vs what people do.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Effects gap&lt;/strong&gt; — between actions and outcomes. What we do vs what results we get.&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;Organizations instinctively reach for the wrong lever: more information for the knowledge gap, more detailed instructions for alignment, tighter controls for effects. Each response tries to eliminate uncertainty rather than navigate it — and makes things worse.&lt;/p&gt;&lt;p&gt;The appropriate responses: accept uncertainty and build adaptive capacity (knowledge), clarify intent not instructions (alignment), tighter feedback loops not more reporting (effects).&lt;/p&gt; &lt;/div&gt; &lt;/div&gt;
&lt;div class=&quot;my-6 border-l-2 border-accent pl-6 py-2&quot;&gt; &lt;h2 class=&quot;text-sm font-bold uppercase tracking-widest mb-2&quot; style=&quot;color:var(--color-accent)&quot;&gt;Detail Is Not Clarity&lt;/h2&gt; &lt;div class=&quot;text-sm leading-relaxed&quot;&gt; &lt;p&gt;More detail in instructions doesn’t make them clearer. It makes them more brittle. Every detail bakes in an assumption about the environment. When conditions change — and they will — some details become irrelevant, some impossible, some actively counterproductive.&lt;/p&gt;&lt;p&gt;The alternative: communicate intent. What are we trying to achieve? Why does it matter? What constraints must be respected? Then let people closest to the work figure out how. They have information the planner doesn’t, and they can adapt as conditions change.&lt;/p&gt; &lt;/div&gt; &lt;/div&gt;
&lt;div class=&quot;my-6 border-l-2 border-accent pl-6 py-2&quot;&gt; &lt;h2 class=&quot;text-sm font-bold uppercase tracking-widest mb-2&quot; style=&quot;color:var(--color-accent)&quot;&gt;Simplicity Precedes Clarity&lt;/h2&gt; &lt;div class=&quot;text-sm leading-relaxed&quot;&gt; &lt;p&gt;To make something clear, you first have to make it simple. A complex message delivered with perfect articulation is still complex. People won’t misunderstand because you were unclear — they’ll misunderstand because there was too much to parse.&lt;/p&gt;&lt;p&gt;This reframes the leader’s job: it’s not a communication problem, it’s a thinking problem. The hard work is deciding what to leave out. The clarity follows.&lt;/p&gt; &lt;/div&gt; &lt;/div&gt;
&lt;div class=&quot;my-6 border-l-2 border-accent pl-6 py-2&quot;&gt; &lt;h2 class=&quot;text-sm font-bold uppercase tracking-widest mb-2&quot; style=&quot;color:var(--color-accent)&quot;&gt;Briefing and Backbriefing&lt;/h2&gt; &lt;div class=&quot;text-sm leading-relaxed&quot;&gt; &lt;p&gt;The protocol that makes intent-based leadership actually work. A brief passes down three things: the context and what the level above is trying to achieve, your own intent (the what and why, not how), and the boundaries that must be respected.&lt;/p&gt;&lt;p&gt;The key move: share your superior’s intent &lt;em&gt;and&lt;/em&gt; your own. People understand not just what you want but what the layer above wants — so they can make intelligent decisions when your specific intent becomes irrelevant.&lt;/p&gt;&lt;p&gt;Backbriefing closes the loop. The receiver explains back what they understood, how they plan to achieve it, and what risks they see. Misunderstandings surface before execution. If their plan differs from what you imagined but achieves the intent — you shut up and let them do it. You don’t correct the how if the what and why are right.&lt;/p&gt; &lt;/div&gt; &lt;/div&gt;</content:encoded><category>Books</category></item><item><title>📚 The Choice — Eliyahu Goldratt</title><link>https://danielbenner.de/books/the-choice/</link><guid isPermaLink="true">https://danielbenner.de/books/the-choice/</guid><description>The core ideas are strong — inherent simplicity, the thinking traps around compromise and blame. But for a book that&apos;s supposed to be about life, it leans almost entirely on business cases. The argument is that if it works for complex organizations it works for simpler ones too. Fair enough, but then show me.</description><pubDate>Tue, 10 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;div class=&quot;my-6 border-l-2 border-accent pl-6 py-2&quot;&gt; &lt;h2 class=&quot;text-sm font-bold uppercase tracking-widest mb-2&quot; style=&quot;color:var(--color-accent)&quot;&gt;Inherent Simplicity&lt;/h2&gt; &lt;div class=&quot;text-sm leading-relaxed&quot;&gt; &lt;p&gt;Goldratt’s foundational claim: complex systems are governed by very few root causes. The more interconnected the parts, the fewer degrees of freedom — everything constrains everything else, which paradoxically reduces the system to a small number of governing variables.&lt;/p&gt;&lt;p&gt;This isn’t optimism. It’s a structural argument about interdependent systems. The simplicity is hidden — you have to work through the complexity to find it. But it’s there.&lt;/p&gt;&lt;p&gt;Implications: if your fix requires orchestrating dozens of changes, you probably haven’t found the real cause. Complex solutions signal incomplete understanding.&lt;/p&gt; &lt;/div&gt; &lt;/div&gt;
&lt;div class=&quot;my-6 border-l-2 border-accent pl-6 py-2&quot;&gt; &lt;h2 class=&quot;text-sm font-bold uppercase tracking-widest mb-2&quot; style=&quot;color:var(--color-accent)&quot;&gt;Humble and Arrogant&lt;/h2&gt; &lt;div class=&quot;text-sm leading-relaxed&quot;&gt; &lt;p&gt;Two stances, held simultaneously:&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Humble toward reality&lt;/strong&gt; — your current model is incomplete. Don’t defend your assumptions.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Arrogant toward your capability&lt;/strong&gt; — reality is knowable. The complexity can be understood.&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;Most people invert this: arrogant about their understanding (defending their current view) and humble about their capability (believing the problem is too complex to solve).&lt;/p&gt;&lt;p&gt;This also reframes luck. Opportunities exist everywhere, but you only spot them when you perceive reality clearly rather than through the fog of assumptions. Luck = preparedness + opportunity.&lt;/p&gt; &lt;/div&gt; &lt;/div&gt;
&lt;div class=&quot;my-6 border-l-2 border-accent pl-6 py-2&quot;&gt; &lt;h2 class=&quot;text-sm font-bold uppercase tracking-widest mb-2&quot; style=&quot;color:var(--color-accent)&quot;&gt;Compromise as a Thinking Trap&lt;/h2&gt; &lt;div class=&quot;text-sm leading-relaxed&quot;&gt; &lt;p&gt;Accepting that conflicts require compromise is itself a barrier to clear thinking. When you settle for a compromise, you stop looking for the false assumption that created the apparent conflict in the first place.&lt;/p&gt;&lt;p&gt;It feels mature — pragmatic, reasonable. But it’s intellectual surrender. You’re accepting a constraint without verifying it exists.&lt;/p&gt;&lt;p&gt;The alternative isn’t naive win-win optimism. It’s disciplined questioning: &lt;em&gt;what assumption would have to be false for both sides to get what they need?&lt;/em&gt;&lt;/p&gt;&lt;p&gt;Every compromise narrows the solution space for future decisions. You optimize within boundaries that were never real.&lt;/p&gt; &lt;/div&gt; &lt;/div&gt;
&lt;div class=&quot;my-6 border-l-2 border-accent pl-6 py-2&quot;&gt; &lt;h2 class=&quot;text-sm font-bold uppercase tracking-widest mb-2&quot; style=&quot;color:var(--color-accent)&quot;&gt;From Complexity to Blame&lt;/h2&gt; &lt;div class=&quot;text-sm leading-relaxed&quot;&gt; &lt;p&gt;The belief that reality is inherently complex doesn’t just produce worse solutions — it poisons relationships. The chain:&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Assume complexity&lt;/strong&gt; — no clean answer exists&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Accept zero-sum framing&lt;/strong&gt; — your gain is my loss&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Settle for compromise&lt;/strong&gt; — we each give up something&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Blame when it fails&lt;/strong&gt; — I sacrificed and it didn’t work, because of you&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;Each step feels reasonable in isolation. Together, they create a cycle of resentment. In a finite-cake world, compromise means loss — and when the outcome is bad, someone must be responsible for your loss.&lt;/p&gt;&lt;p&gt;Flip the first assumption — believe in inherent simplicity — and the whole chain unravels. You stop treating others as adversaries and start looking for root causes together.&lt;/p&gt; &lt;/div&gt; &lt;/div&gt;
&lt;div class=&quot;my-6 border-l-2 border-accent pl-6 py-2&quot;&gt; &lt;h2 class=&quot;text-sm font-bold uppercase tracking-widest mb-2&quot; style=&quot;color:var(--color-accent)&quot;&gt;The Complexity Assumption Trap&lt;/h2&gt; &lt;div class=&quot;text-sm leading-relaxed&quot;&gt; &lt;p&gt;Assuming a problem is irreducibly complex becomes self-fulfilling. You stop looking too early, never find the root cause, and the persistent mess confirms your belief that it was complex all along.&lt;/p&gt;&lt;p&gt;What gets lost: root causes stay hidden, leverage points go unused, effort scatters across non-constraints, and repeated failure erodes confidence — reinforcing the belief the problem is unsolvable.&lt;/p&gt;&lt;p&gt;The fix is methodological: assume the few root causes exist and keep looking. This doesn’t guarantee you’ll find them — but assuming complexity guarantees you won’t.&lt;/p&gt; &lt;/div&gt; &lt;/div&gt;</content:encoded><category>Books</category></item><item><title>📚 Turn the Ship Around! — L. David Marquet</title><link>https://danielbenner.de/books/turn-the-ship-around/</link><guid isPermaLink="true">https://danielbenner.de/books/turn-the-ship-around/</guid><description>Good storytelling — the submarine setting makes it more engaging than yet another business book. The core ideas are true and valuable, but shallow. The whole model assumes highly trained experts who just lack ownership. In most real companies, the harder problem is competence gaps, not motivation gaps.</description><pubDate>Sun, 15 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;div class=&quot;my-6 border-l-2 border-accent pl-6 py-2&quot;&gt; &lt;h2 class=&quot;text-sm font-bold uppercase tracking-widest mb-2&quot; style=&quot;color:var(--color-accent)&quot;&gt;Information Locality and Decision Rights&lt;/h2&gt; &lt;div class=&quot;text-sm leading-relaxed&quot;&gt; &lt;p&gt;Decisions require information. You have two options:&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Centralize information&lt;/strong&gt; — move data up to where decisions are made&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Decentralize decisions&lt;/strong&gt; — move authority down to where information exists&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;Centralizing information is slow, lossy, and expensive. By the time context reaches the top, it’s stale. Nuance gets compressed. You build bureaucracy just to shuttle information around.&lt;/p&gt;&lt;p&gt;Pushing decisions to the edge is more efficient — no transmission delay, no compression loss. But it only works when people are competent enough to decide well, and aligned enough that distributed decisions don’t fragment.&lt;/p&gt; &lt;/div&gt; &lt;/div&gt;
&lt;div class=&quot;my-6 border-l-2 border-accent pl-6 py-2&quot;&gt; &lt;h2 class=&quot;text-sm font-bold uppercase tracking-widest mb-2&quot; style=&quot;color:var(--color-accent)&quot;&gt;Three Pillars of Decentralized Decisions&lt;/h2&gt; &lt;div class=&quot;text-sm leading-relaxed&quot;&gt; &lt;p&gt;Decentralizing authority without supporting conditions creates chaos. Three pillars must hold together:&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Control&lt;/strong&gt; — actual authority to decide&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Competence&lt;/strong&gt; — capability to decide well&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Clarity&lt;/strong&gt; — shared intent so decisions align&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;Missing any one breaks the system. Control without competence produces mistakes. Control without clarity produces fragmentation. Competence and clarity without control is wasted potential — people who could decide but aren’t allowed to.&lt;/p&gt; &lt;/div&gt; &lt;/div&gt;
&lt;div class=&quot;my-6 border-l-2 border-accent pl-6 py-2&quot;&gt; &lt;h2 class=&quot;text-sm font-bold uppercase tracking-widest mb-2&quot; style=&quot;color:var(--color-accent)&quot;&gt;Inverting the Direction of Initiative&lt;/h2&gt; &lt;div class=&quot;text-sm leading-relaxed&quot;&gt; &lt;p&gt;Traditional leadership pushes down: leader gives orders, subordinate executes. The alternative flips the active party.&lt;/p&gt;&lt;p&gt;Instead of leaders extracting information upward and pushing decisions down, subordinates surface decisions in ready-to-validate form: &lt;em&gt;“I intend to…”&lt;/em&gt; The leader’s role shifts from directing to enabling — intervening only when needed.&lt;/p&gt;&lt;p&gt;This is more efficient because the person with the information packages it themselves. No lossy compression, no waiting for the next status meeting.&lt;/p&gt; &lt;/div&gt; &lt;/div&gt;</content:encoded><category>Books</category></item><item><title>Skills Over Tools: Why CLI Wrappers Beat MCP Servers for AI Agents</title><link>https://danielbenner.de/articles/skills-over-tools/</link><guid isPermaLink="true">https://danielbenner.de/articles/skills-over-tools/</guid><description>A markdown file that explains a CLI is more flexible than a fixed tool definition or a whole MCP server.</description><pubDate>Sat, 14 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The standard approach to giving AI agents capabilities is tool definitions — fixed function signatures with typed parameters. MCP servers formalize this further: a dedicated process that exposes a set of tools over a protocol.&lt;/p&gt;
&lt;p&gt;Both work. Both are also unnecessarily rigid. There’s a simpler pattern that’s more flexible and requires less infrastructure: a skill file that teaches the agent how to use an existing CLI tool.&lt;/p&gt;
&lt;h2 id=&quot;the-three-approaches&quot;&gt;The Three Approaches&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Fixed tools&lt;/strong&gt; are function signatures baked into the agent’s configuration. A browser tool might expose &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;navigate&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;url&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt;, &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;click&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;selector&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt;, &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;extract_text&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;selector&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt;. Each action is a separate tool call with a defined schema.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;MCP servers&lt;/strong&gt; package these tools into a standalone process. The agent discovers available tools via the protocol, calls them with structured JSON, gets structured JSON back. It’s tools-as-a-service.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Skills&lt;/strong&gt; are instruction files — markdown documents that describe how to use a CLI tool via the agent’s existing shell access. No protocol, no server, no schema. Just documentation the agent reads and follows.&lt;/p&gt;
&lt;h2 id=&quot;what-a-skill-looks-like&quot;&gt;What a Skill Looks Like&lt;/h2&gt;
&lt;p&gt;I built &lt;a href=&quot;https://github.com/devbydaniel/bb&quot;&gt;bb&lt;/a&gt;, a browser automation CLI. Instead of wrapping it in an MCP server, I wrote a skill file — a markdown document that explains the commands:&lt;/p&gt;
&lt;figure&gt;&lt;pre tabindex=&quot;0&quot;&gt;&lt;code style=&quot;display:grid&quot;&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;# Browse&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;Browse the web using &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;`bb`&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, a browser automation CLI&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;that drives a persistent headless Chrome instance.&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;## Quick reference&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;bb open https://example.com      # navigate and read&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;bb text &quot;.article-body&quot;          # extract specific content&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;bb click &quot;button.load-more&quot;      # interact&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;bb wait &quot;.results&quot;               # wait for dynamic content&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;bb screenshot page.png           # capture&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;bb stop                          # clean up&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;The file covers the most common commands and patterns — not all of them. It doesn’t have to. If the agent needs a command that isn’t in the skill file, it runs &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;bb&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; --&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;help&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt; and figures it out. The skill provides the fast path; the CLI’s own documentation covers everything else.&lt;/p&gt;
&lt;h2 id=&quot;why-this-works-better&quot;&gt;Why This Works Better&lt;/h2&gt;
&lt;h3 id=&quot;no-capability-ceiling&quot;&gt;No Capability Ceiling&lt;/h3&gt;
&lt;p&gt;Fixed tools define a finite set of operations. If the tool definition doesn’t include “wait for network idle then extract text from a specific selector,” the agent can’t do it. Someone has to anticipate every useful combination and define a tool for each.&lt;/p&gt;
&lt;p&gt;With a CLI, the agent composes commands freely:&lt;/p&gt;
&lt;figure&gt;&lt;pre tabindex=&quot;0&quot;&gt;&lt;code style=&quot;display:grid&quot;&gt;&lt;span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;bb&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; open&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; https://example.com&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;bb&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; wait&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; &quot;.results&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;bb&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; text&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; &quot;.results .item:first-child&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;No one had to define a &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;navigate_wait_and_extract_first_item&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt; tool. The agent read the available commands and composed them to fit the task.&lt;/p&gt;
&lt;h3 id=&quot;commands-chain-naturally&quot;&gt;Commands Chain Naturally&lt;/h3&gt;
&lt;p&gt;CLI tools compose with pipes and subshells. A single line can do what would take multiple round-trip tool calls:&lt;/p&gt;
&lt;figure&gt;&lt;pre tabindex=&quot;0&quot;&gt;&lt;code style=&quot;display:grid&quot;&gt;&lt;span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;bb&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; text&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; &quot;.item&quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; | &lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;grep&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; &quot;in stock&quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; | &lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;wc&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; -l&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Count of in-stock items, one command. With fixed tools, this is three separate calls: extract text, filter results, count matches — each one a full round trip through the tool interface.&lt;/p&gt;
&lt;p&gt;The agent already knows bash. Piping, redirection, &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;grep&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt;, &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;jq&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt;, &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;awk&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt; — these aren’t features anyone needs to build. They’re already there, and they work with any CLI that outputs plain text.&lt;/p&gt;
&lt;h3 id=&quot;no-server-to-run&quot;&gt;No Server to Run&lt;/h3&gt;
&lt;p&gt;MCP servers are processes. They need to start, stay running, handle connections, manage state. That’s operational overhead for what amounts to “let the agent click buttons in a browser.”&lt;/p&gt;
&lt;p&gt;A CLI is already running infrastructure. &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;bb&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt; auto-starts Chrome on first use and persists state in &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;~&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;bb&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;/&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt;. The agent calls it like any other command. No daemon to manage, no protocol to speak.&lt;/p&gt;
&lt;h3 id=&quot;the-agent-already-has-a-shell&quot;&gt;The Agent Already Has a Shell&lt;/h3&gt;
&lt;p&gt;This is the part that gets overlooked. Agents that can execute bash commands already have the most flexible tool interface possible. A shell command can do anything a tool definition can do, plus everything else.&lt;/p&gt;
&lt;p&gt;Adding an MCP server for browser automation means: install the server, configure the connection, handle the protocol overhead — all to give the agent capabilities it could have through &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;bb&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; open&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; https:&lt;/span&gt;&lt;span style=&quot;color:#EEF0F98F;font-style:italic&quot;&gt;//example.com&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt;.&lt;/p&gt;
&lt;h3 id=&quot;skill-files-are-self-documenting&quot;&gt;Skill Files Are Self-Documenting&lt;/h3&gt;
&lt;p&gt;A tool definition is a schema. A skill file is documentation. The agent doesn’t just know &lt;em&gt;what&lt;/em&gt; it can call — it knows &lt;em&gt;when&lt;/em&gt; and &lt;em&gt;how&lt;/em&gt; to use each command effectively.&lt;/p&gt;
&lt;p&gt;From the &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;bb&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt; skill:&lt;/p&gt;
&lt;figure&gt;&lt;pre tabindex=&quot;0&quot;&gt;&lt;code style=&quot;display:grid&quot;&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;## Tips&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; Start with &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;`bb open`&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; — it navigates and extracts readable&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;  content in one step.&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; For SPAs, use &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;`bb open &amp;lt;url&amp;gt;`&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; then &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;`bb wait &amp;lt;selector&amp;gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;  before extracting.&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; Use &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;`bb ax-tree`&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; when you need to understand page structure&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;  without reading raw HTML.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;This kind of contextual guidance doesn’t fit in a tool schema. In an MCP server, it lives in description fields that are often too short to be genuinely useful. In a skill file, there’s no length limit. Write whatever the agent needs to know.&lt;/p&gt;
&lt;h3 id=&quot;updates-are-trivial&quot;&gt;Updates Are Trivial&lt;/h3&gt;
&lt;p&gt;Adding a new command to &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;bb&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt; means updating the skill file — a markdown edit. No schema changes, no server redeployment, no version negotiation. The agent reads the updated file next time it needs to browse.&lt;/p&gt;
&lt;h2 id=&quot;when-tools-and-mcp-servers-make-sense&quot;&gt;When Tools and MCP Servers Make Sense&lt;/h2&gt;
&lt;p&gt;This isn’t universal. Fixed tools work well when:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The agent doesn’t have shell access.&lt;/strong&gt; Web-based agents or sandboxed environments need structured tool interfaces.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;You need strict input validation.&lt;/strong&gt; Tool schemas enforce types and required fields at the protocol level.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Multiple agents share the same server.&lt;/strong&gt; MCP’s discovery protocol helps when you have many consumers.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But for a coding agent that already runs in a terminal — which is most of them — skills over CLIs are simpler, more flexible, and require less maintenance.&lt;/p&gt;
&lt;h2 id=&quot;the-pattern&quot;&gt;The Pattern&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Build the capability as a CLI with plain-text output.&lt;/li&gt;
&lt;li&gt;Write a skill file that documents usage, patterns, and tips.&lt;/li&gt;
&lt;li&gt;Let the agent read the skill and call the CLI through bash.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The agent gets full access to the tool’s capabilities without anyone having to predict which combinations of operations it might need. The CLI handles the complexity. The skill file provides the context. The shell connects them.&lt;/p&gt;
&lt;p&gt;No servers. No schemas. No protocol overhead. Just commands and documentation.&lt;/p&gt;</content:encoded><category>AI</category><category>CLI</category><category>Architecture</category></item><item><title>From Audio to Action Items in Under a Minute</title><link>https://danielbenner.de/articles/automated-meeting-processing/</link><guid isPermaLink="true">https://danielbenner.de/articles/automated-meeting-processing/</guid><description>A CLI tool records meetings, transcribes them, and an AI agent processes the results into my operating system — no manual notes required.</description><pubDate>Tue, 10 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I stopped taking meeting notes. Not because I got lazy — because I automated the entire chain from raw audio to structured action items, decisions logged, and references updated.&lt;/p&gt;
&lt;p&gt;The setup has two parts: a Go CLI tool called &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;meetingcli&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt; that handles recording and transcription, and an AI agent skill that processes the transcript into my &lt;a href=&quot;https://danielbenner.de/articles/from-markdown-notes-to-ai-chief-of-staff&quot;&gt;personal operating system&lt;/a&gt;. End to end, a one-hour meeting goes from audio to fully processed in about a minute after I hang up.&lt;/p&gt;
&lt;h2 id=&quot;the-recording-problem&quot;&gt;The Recording Problem&lt;/h2&gt;
&lt;p&gt;Meeting notes are lossy. You’re either paying attention or writing things down — rarely both. And even good notes miss nuance, exact phrasing, and the stuff you didn’t realize was important until later.&lt;/p&gt;
&lt;p&gt;The alternative is recording, but most tools dump you with a giant audio file or a mediocre transcript locked inside some SaaS platform. I wanted something local, private, and plugged into my existing workflow.&lt;/p&gt;
&lt;h2 id=&quot;meetingcli&quot;&gt;meetingcli&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://danielbenner.de/products/meetingcli&quot;&gt;&lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;meetingcli&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt;&lt;/a&gt; is a Go CLI that does three things: record, transcribe, and summarize. One command, no UI.&lt;/p&gt;
&lt;figure&gt;&lt;pre tabindex=&quot;0&quot;&gt;&lt;code style=&quot;display:grid&quot;&gt;&lt;span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;meeting&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; start&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; --name&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; &quot;product-sync&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F98F;font-style:italic&quot;&gt;# talk for an hour&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F98F;font-style:italic&quot;&gt;# Ctrl+C to stop&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F98F;font-style:italic&quot;&gt;# → transcription and summary happen automatically&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;h3 id=&quot;dual-audio-capture&quot;&gt;Dual Audio Capture&lt;/h3&gt;
&lt;p&gt;The tricky part of meeting recording on macOS is capturing both sides of the conversation. &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;meetingcli&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt; runs two audio streams in parallel:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;System audio&lt;/strong&gt; via Apple’s ScreenCaptureKit — this captures whatever’s coming out of your speakers or headphones. It taps directly into the OS audio mixer, so it works with Bluetooth headphones, external DACs, whatever. The implementation is Objective-C bridged into Go via cgo.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Microphone audio&lt;/strong&gt; via ffmpeg from the default input device.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Both streams record to separate WAV files, then get merged into a single &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;recording&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;wav&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt; using ffmpeg’s &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;amix&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt; filter. The system audio capture does real-time downsampling from 48kHz stereo float32 to 16kHz mono int16 — keeps file sizes reasonable without losing speech clarity.&lt;/p&gt;
&lt;p&gt;Each meeting lands in its own timestamped folder:&lt;/p&gt;
&lt;figure&gt;&lt;pre tabindex=&quot;0&quot;&gt;&lt;code style=&quot;display:grid&quot;&gt;&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;~/&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;meetings&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;2026&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;02&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;06_14&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;00&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;-00&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;_product&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;sync&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;├── &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;recording&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;wav&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;      # &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;merged&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; audio&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;├── &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;system&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;wav&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;         # &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;system&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt; audio&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;raw&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;├── &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;mic&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;wav&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;            # &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;mic&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt; audio&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;raw&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;├── &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;transcript&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;md&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;      # &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;diarized&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; transcript&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;└── &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;summary&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;md&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;         # &lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;AI&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;generated&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; summary&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;h3 id=&quot;transcription&quot;&gt;Transcription&lt;/h3&gt;
&lt;p&gt;The merged audio gets sent to Mistral’s Voxtral API with speaker diarization enabled. The transcript comes back with speaker labels, so you get a readable conversation rather than a wall of text:&lt;/p&gt;
&lt;figure&gt;&lt;pre tabindex=&quot;0&quot;&gt;&lt;code style=&quot;display:grid&quot;&gt;&lt;span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;**Speaker 0:**&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;We need to decide on the auth model before shipping the API redesign.&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;**Speaker 1:**&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;I think JWT with refresh tokens is the way to go. OAuth adds complexity we don&apos;t need yet.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Voxtral handles multilingual audio well — useful when half your meetings are in German and the other half in English.&lt;/p&gt;
&lt;h3 id=&quot;summary&quot;&gt;Summary&lt;/h3&gt;
&lt;p&gt;After transcription, the full text goes to Claude Haiku 4.5 for summarization. This produces a quick-reference summary with key topics, decisions, and action items. Useful for a glance, but the real value comes from what happens next.&lt;/p&gt;
&lt;h2 id=&quot;processing-transcripts-into-the-system&quot;&gt;Processing Transcripts Into the System&lt;/h2&gt;
&lt;p&gt;Recording and transcribing is table stakes. The interesting part is what happens to the transcript after.&lt;/p&gt;
&lt;p&gt;I have an AI agent skill called &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;process&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;meetings&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt; that reads transcripts and updates my entire operating system accordingly. When I run it, it spawns a parallel sub-agent for each meeting recorded that day. Each sub-agent reads the transcript and does six things:&lt;/p&gt;
&lt;h3 id=&quot;1-creates-a-meeting-note&quot;&gt;1. Creates a Meeting Note&lt;/h3&gt;
&lt;p&gt;A concise summary goes into the relevant project folder — participants, key topics, decisions, action items with owners. Not a rehash of the transcript, but the stuff that matters a week from now.&lt;/p&gt;
&lt;h3 id=&quot;2-updates-the-compass&quot;&gt;2. Updates the Compass&lt;/h3&gt;
&lt;p&gt;The &lt;a href=&quot;https://danielbenner.de/articles/from-markdown-notes-to-ai-chief-of-staff&quot;&gt;compass&lt;/a&gt; is my AI’s memory system. The agent updates &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;decisions&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;md&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt; when significant decisions were made, updates the context graph when project status changed, and captures any preferences I expressed.&lt;/p&gt;
&lt;p&gt;If I said “let’s drop the OAuth requirement and go with API keys” in a meeting, that decision gets logged with reasoning. Next time AI helps me with the auth system, it knows what was decided and why.&lt;/p&gt;
&lt;h3 id=&quot;3-updates-references&quot;&gt;3. Updates References&lt;/h3&gt;
&lt;p&gt;Project briefs, people files, and other reference documents get updated when durable understanding changes. New team member mentioned? Their file gets created. Project scope shifted? The brief gets updated.&lt;/p&gt;
&lt;h3 id=&quot;4-flags-tasks&quot;&gt;4. Flags Tasks&lt;/h3&gt;
&lt;p&gt;This one is deliberately conservative. The agent only creates tasks that are directly my responsibility — not things assigned to other team members, not things I just need to be aware of. It checks existing tasks first to avoid duplicates.&lt;/p&gt;
&lt;h3 id=&quot;5-updates-collections&quot;&gt;5. Updates Collections&lt;/h3&gt;
&lt;p&gt;If I mentioned a restaurant I liked or a hike worth doing, it gets added to my collections. Small thing, but it means casual mentions don’t get lost.&lt;/p&gt;
&lt;h3 id=&quot;6-zettelkasten-connections&quot;&gt;6. Zettelkasten Connections&lt;/h3&gt;
&lt;p&gt;Occasionally a meeting surfaces a genuinely interesting concept or pattern. The agent checks if it connects to existing notes in my knowledge base. Most meetings don’t produce anything here — that’s by design.&lt;/p&gt;
&lt;h2 id=&quot;the-full-pipeline&quot;&gt;The Full Pipeline&lt;/h2&gt;
&lt;p&gt;Here’s what a typical day looks like:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Join a meeting. Run &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;meeting&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; start&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt;. Talk.&lt;/li&gt;
&lt;li&gt;Hang up. Ctrl+C. Transcription and summary happen automatically.&lt;/li&gt;
&lt;li&gt;At the end of the day (or whenever), run the process-meetings skill.&lt;/li&gt;
&lt;li&gt;Sub-agents process each meeting in parallel. My compass, references, tasks, and notes all get updated.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Total manual effort: typing &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;meeting&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; start&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt; and pressing Ctrl+C.&lt;/p&gt;
&lt;h2 id=&quot;why-this-works&quot;&gt;Why This Works&lt;/h2&gt;
&lt;p&gt;Three design choices make the difference:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Local-first recording.&lt;/strong&gt; The audio files live on my machine. No uploading hour-long recordings to some startup’s servers. The only thing that leaves my machine is the audio going to Mistral for transcription and the text going to Anthropic for summarization.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Separation of capture and processing.&lt;/strong&gt; &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;meetingcli&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt; does one job well: turn audio into text. The AI agent does the interpretation. This means I can improve either side independently. Better transcription model? Swap the API call. Better processing logic? Update the skill prompt.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Transparent output.&lt;/strong&gt; Everything is markdown files in known locations. No database, no proprietary format. The meeting folders are just folders. The compass updates are just file edits. I can inspect, override, or correct anything.&lt;/p&gt;
&lt;h2 id=&quot;building-meetingcli&quot;&gt;Building meetingcli&lt;/h2&gt;
&lt;p&gt;The tool is open source and installable via Homebrew:&lt;/p&gt;
&lt;figure&gt;&lt;pre tabindex=&quot;0&quot;&gt;&lt;code style=&quot;display:grid&quot;&gt;&lt;span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;brew&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; install&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; devbydaniel/tap/meeting&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;The Go codebase follows hexagonal architecture — the recording, transcription, and summarization are separate use cases with clean interfaces. The macOS audio capture is the most interesting piece technically: an Objective-C implementation of ScreenCaptureKit bridged into Go, handling real-time audio format conversion in the capture callback.&lt;/p&gt;
&lt;p&gt;If you’re curious about the implementation or want to adapt it for your own workflow, the &lt;a href=&quot;https://github.com/devbydaniel/meetingcli&quot;&gt;source is on GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;whats-missing&quot;&gt;What’s Missing&lt;/h2&gt;
&lt;p&gt;Speaker identification. Voxtral gives you “Speaker 0” and “Speaker 1,” not names. I could map these manually or build a voice fingerprinting step, but honestly the context usually makes it obvious who said what.&lt;/p&gt;
&lt;p&gt;The summary step inside &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;meetingcli&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt; is redundant with the agent processing. I might remove it or make it optional — the agent produces better, more contextualized output anyway.&lt;/p&gt;
&lt;p&gt;And the processing skill currently requires me to trigger it manually. It could watch the meetings directory and process automatically when a new transcript appears. Haven’t needed that yet — running it once at end of day works fine.&lt;/p&gt;</content:encoded><category>AI</category><category>Productivity</category><category>Tools</category><category>Go</category></item><item><title>Building a Personal AI Agent with Raspberry Pi and Telegram</title><link>https://danielbenner.de/articles/building-a-personal-ai-agent-with-raspberry-pi-and-telegram/</link><guid isPermaLink="true">https://danielbenner.de/articles/building-a-personal-ai-agent-with-raspberry-pi-and-telegram/</guid><description>You don&apos;t need OpenClaw. A Raspberry Pi, a Telegram bot, and git hooks get you a persistent AI assistant you actually understand.</description><pubDate>Sun, 08 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;OpenClaw is everywhere right now. 145,000 GitHub stars, CNBC coverage, AI agents posting on their own social network. It manages your emails, browses the web, schedules your calendar, shops for you — “the AI that actually does things.”&lt;/p&gt;
&lt;p&gt;It’s also a mass of dependencies you’ll never fully understand, running on your operating system with access to your private data. Palo Alto Networks called it a “lethal trifecta” of security risks. The creator himself says it’s not ready for non-technical users.&lt;/p&gt;
&lt;p&gt;Here’s my counterproposal: build it yourself. Not because OpenClaw is bad — the ambition is right. But because personal AI infrastructure is &lt;em&gt;personal&lt;/em&gt;. You want to understand every piece. And the architecture is simpler than you’d think.&lt;/p&gt;
&lt;h2 id=&quot;the-setup&quot;&gt;The Setup&lt;/h2&gt;
&lt;p&gt;A Raspberry Pi. A Telegram bot. Git hooks. That’s the stack.&lt;/p&gt;
&lt;figure&gt;&lt;pre tabindex=&quot;0&quot;&gt;&lt;code style=&quot;display:grid&quot;&gt;&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;┌─────────────┐       ┌──────────────────┐&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;│  &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;Telegram&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;    │◄─────►│  &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;Raspberry&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; Pi&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;     │&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;│  (&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;you&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)       │       │                  │&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;└─────────────┘       │  ┌──────────────┐ │&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;                      │  │ &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;Telegram&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; Bot&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;  │ │&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;                      │  │ ↕             │ │&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;                      │  │ &lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;AI&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; Agent&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;      │ │&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;                      │  │ ↕             │ │&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;                      │  │ &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;Git&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; Repo&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;      │ │&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;                      │  │ (&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;notes&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;tasks&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/ │ │&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;                      │  │  &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;compass&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/)    │ │&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;                      │  └──────────────┘ │&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;                      └──────────────────┘&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;The Pi runs 24/7. The Telegram bot is your interface — from your phone, your laptop, anywhere. The git repo is the agent’s memory: your notes, context, tasks, and workflows, synced via git push/pull at session boundaries.&lt;/p&gt;
&lt;h2 id=&quot;why-a-raspberry-pi&quot;&gt;Why a Raspberry Pi&lt;/h2&gt;
&lt;p&gt;Three reasons.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It’s always on.&lt;/strong&gt; Unlike your laptop, it doesn’t sleep. An agent that can check your calendar at 7am and send you a briefing needs a host that’s running at 7am.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It’s yours.&lt;/strong&gt; Your data stays on hardware you own, on your network. No cloud provider reading your emails, no SaaS with a terms-of-service change waiting to happen.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It’s cheap.&lt;/strong&gt; A Pi 5 costs €80. Running an LLM through an API costs a few dollars a month. Compare that to the compute overhead of OpenClaw’s full stack.&lt;/p&gt;
&lt;h2 id=&quot;the-session-model&quot;&gt;The Session Model&lt;/h2&gt;
&lt;p&gt;The interesting architectural decision is how the agent maintains state across conversations.&lt;/p&gt;
&lt;figure&gt;&lt;pre tabindex=&quot;0&quot;&gt;&lt;code style=&quot;display:grid&quot;&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;Session&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; start:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;  1.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; git&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt; pull&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; latest&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; notes&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;compass&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;context&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;  2.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; Read&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; compass&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/ &lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;files&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;rehydrate&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; memory&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;  3.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; Ready&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; to&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; work&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;Session&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; end:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;  1.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; Update&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; compass&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;context&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt; with&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; what&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; happened&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;  2.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; Log&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; decisions&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;preferences&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;follow&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;ups&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;  3.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; git&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt; push&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;persist&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; everything&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;This is the same model I described in &lt;a href=&quot;https://danielbenner.de/articles/from-markdown-notes-to-ai-chief-of-staff&quot;&gt;From Markdown Notes to AI Chief of Staff&lt;/a&gt;, but with a key difference: the session start and end aren’t me opening and closing a terminal. They’re the Telegram bot receiving a message and going idle.&lt;/p&gt;
&lt;p&gt;Git gives you version history for free. Every state change is a commit. You can diff what the agent changed, revert bad updates, see how context evolved over weeks. Try getting that from OpenClaw’s “persistent memory.”&lt;/p&gt;
&lt;h2 id=&quot;the-telegram-bot&quot;&gt;The Telegram Bot&lt;/h2&gt;
&lt;p&gt;Telegram’s Bot API is one of the better-designed APIs out there. A minimal bot that forwards messages to an AI agent and returns responses is maybe 100 lines of Python. Add command handlers for specific workflows:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;morning&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt; — run the morning kickoff routine&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;tasks&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt; — show today’s tasks&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;email&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt; — triage inbox&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;prep&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; John&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt; — briefing before a meeting with John&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Each command maps to a skill — a markdown file describing a multi-step workflow. The bot reads the skill, passes it to the LLM along with the relevant context files, and executes the steps.&lt;/p&gt;
&lt;p&gt;The critical insight: the Telegram bot isn’t the agent. It’s a thin shell. The intelligence lives in the LLM, the context lives in git, and the workflows live in markdown files. The bot just connects them.&lt;/p&gt;
&lt;h2 id=&quot;what-you-get-that-openclaw-doesnt-give-you&quot;&gt;What You Get That OpenClaw Doesn’t Give You&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Comprehension.&lt;/strong&gt; Every component is something you built or configured. When something breaks — and it will — you know where to look. OpenClaw has hundreds of contributors and a codebase that’s growing faster than anyone can review. That’s fine for a web framework. It’s uncomfortable for something with access to your email.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Control.&lt;/strong&gt; You decide exactly what the agent can access. Want it to read your calendar but not send emails? Don’t give it the email tool. With OpenClaw, you’re trusting that the permission model works as advertised across a plugin ecosystem built by strangers.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Simplicity.&lt;/strong&gt; The entire system is a Telegram bot, a handful of CLI tools, and a git repo of markdown files. There’s no plugin registry, no social network for AI agents, no cryptocurrency tokens. It does what you need and nothing else.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Learning.&lt;/strong&gt; This is the one people underestimate. Building your own agent teaches you how LLMs actually work in production — context windows, tool use, prompt design, state management, failure modes. Using OpenClaw teaches you how to configure OpenClaw.&lt;/p&gt;
&lt;h2 id=&quot;what-openclaw-gets-right&quot;&gt;What OpenClaw Gets Right&lt;/h2&gt;
&lt;p&gt;Credit where it’s due: the vision of an AI agent that operates across your entire digital life is correct. The idea that it should be open-source is correct. The emphasis on persistent memory is correct.&lt;/p&gt;
&lt;p&gt;The disagreement is about approach. OpenClaw tries to be everything for everyone, which means it’s complex, hard to secure, and impossible to fully understand. The alternative is to build exactly what you need, understand every piece, and extend it when your needs change.&lt;/p&gt;
&lt;h2 id=&quot;the-architecture-concretely&quot;&gt;The Architecture, Concretely&lt;/h2&gt;
&lt;p&gt;Here’s what runs on the Pi:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Telegram bot&lt;/strong&gt; (Python, ~200 lines) — receives messages, routes commands, returns responses&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Agent orchestration&lt;/strong&gt; — something like &lt;a href=&quot;https://github.com/badlogic/pi-mono/tree/main/packages/coding-agent&quot;&gt;pi&lt;/a&gt; works well here. It’s a coding agent with tool use, extensions, and skill support that you can drive programmatically via its SDK. The Telegram bot spawns a pi session per conversation, hands it the context from the git repo, and lets it execute tool calls. Lightweight, hackable, and you can read the entire source.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CLI tools&lt;/strong&gt; — small scripts for email (IMAP/SMTP), calendar (CalDAV), tasks, web search&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Git repo&lt;/strong&gt; — notes, compass files, skill definitions, references&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cron jobs&lt;/strong&gt; — morning briefing, inbox check, follow-up reminders&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;That’s it. No Docker orchestration, no plugin framework, no vector database, no agent-to-agent social network. Each piece is simple enough to hold in your head.&lt;/p&gt;
&lt;h2 id=&quot;getting-started&quot;&gt;Getting Started&lt;/h2&gt;
&lt;p&gt;If you’ve been following along with the &lt;a href=&quot;https://danielbenner.de/articles/minimal-ai-friendly-notes&quot;&gt;note-taking system&lt;/a&gt; and &lt;a href=&quot;https://danielbenner.de/articles/from-markdown-notes-to-ai-chief-of-staff&quot;&gt;operational layer&lt;/a&gt;, you already have the hard part: structured context that AI can work with.&lt;/p&gt;
&lt;p&gt;The remaining steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Set up a Pi with SSH access&lt;/li&gt;
&lt;li&gt;Clone your notes repo&lt;/li&gt;
&lt;li&gt;Create a Telegram bot via BotFather&lt;/li&gt;
&lt;li&gt;Write a minimal bot that forwards messages to an LLM API&lt;/li&gt;
&lt;li&gt;Add git pull/push hooks around sessions&lt;/li&gt;
&lt;li&gt;Add CLI tools one at a time as you need them&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Start small. A bot that can answer questions about your notes and tasks is already useful. Add email access when you trust the setup. Add calendar integration when you need it. Each addition is a small, understandable increment.&lt;/p&gt;
&lt;h2 id=&quot;the-point&quot;&gt;The Point&lt;/h2&gt;
&lt;p&gt;OpenClaw proves there’s demand for personal AI agents. But it also demonstrates the standard industry pattern: take a fundamentally simple idea, add every feature anyone might want, and ship a system that no single person can understand or secure.&lt;/p&gt;
&lt;p&gt;Personal infrastructure should be personal. You should be able to read every line of code that has access to your email. You should know exactly what gets persisted and where. You should be able to explain the entire architecture in a five-minute conversation.&lt;/p&gt;
&lt;p&gt;A Raspberry Pi, a Telegram bot, and git hooks. That’s not a limitation — that’s the design.&lt;/p&gt;</content:encoded><category>AI</category><category>Productivity</category><category>Tools</category></item><item><title>From Markdown Notes to AI Chief of Staff</title><link>https://danielbenner.de/articles/from-markdown-notes-to-ai-chief-of-staff/</link><guid isPermaLink="true">https://danielbenner.de/articles/from-markdown-notes-to-ai-chief-of-staff/</guid><description>What happens when you keep extending a transparent system: it stops being notes and starts running your day.</description><pubDate>Sun, 01 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A couple months ago I wrote about &lt;a href=&quot;https://danielbenner.de/articles/minimal-ai-friendly-notes&quot;&gt;a note-taking system built on plain markdown and 50 lines of bash&lt;/a&gt;. The thesis: when your entire system is transparent to AI, it can write whatever tooling you need. No plugins, no APIs — just files and scripts.&lt;/p&gt;
&lt;p&gt;That thesis was correct. But it undersold what happens when you keep going.&lt;/p&gt;
&lt;p&gt;The system isn’t notes anymore. It’s an operational layer. AI agents run morning routines, process meeting transcripts, triage inboxes, maintain their own memory across sessions, and flag when I’ve dropped a commitment. The same markdown files, the same transparency principle — but the ceiling turned out to be much higher than “better notes.”&lt;/p&gt;
&lt;h2 id=&quot;the-cold-start-problem&quot;&gt;The Cold Start Problem&lt;/h2&gt;
&lt;p&gt;The original system had a gap. Every conversation with AI started from zero. It didn’t know what I was working on, what I’d decided last week, or what mattered to me. I’d spend the first few messages re-establishing context, or worse — get advice that contradicted a decision I’d already made.&lt;/p&gt;
&lt;p&gt;Notes existed, but they were organized for &lt;em&gt;me&lt;/em&gt; to find things. Not for AI to orient itself.&lt;/p&gt;
&lt;p&gt;The fix was a folder called &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;compass&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt; with four files:&lt;/p&gt;
&lt;figure&gt;&lt;pre tabindex=&quot;0&quot;&gt;&lt;code style=&quot;display:grid&quot;&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;compass&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;├── &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;goals&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;md&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;        # &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;What&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; I&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&apos;m trying to achiev&lt;/span&gt;&lt;span style=&quot;color:#FFFFFF&quot;&gt;e&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;├── &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;context&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;md&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;      # &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;What&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&apos;s in flight right no&lt;/span&gt;&lt;span style=&quot;color:#FFFFFF&quot;&gt;w&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;├── &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;preferences&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;md&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;  # &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;How&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; I&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; work&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; and&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; communicate&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;└── &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;decisions&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;md&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;    # &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;What&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&apos;s been decided and wh&lt;/span&gt;&lt;span style=&quot;color:#FFFFFF&quot;&gt;y&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;h3 id=&quot;contextmd--the-session-handoff&quot;&gt;context.md — the session handoff&lt;/h3&gt;
&lt;p&gt;This is the most important file. It’s a queue of open threads — not a project wiki, not a journal. Three sections: In Flight, Blocked/Waiting, Needs Attention. Each entry is a one-liner: what it is plus current status.&lt;/p&gt;
&lt;figure&gt;&lt;pre tabindex=&quot;0&quot;&gt;&lt;code style=&quot;display:grid&quot;&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;## In Flight&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; API redesign — open question on auth model blocks shipping&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; Onboarding flow rewrite — new design approved, implementation started&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; Hiring — backend role, two candidates in pipeline&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;## Blocked / Waiting&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; Enterprise pilot — blocked on their internal security review&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;## Needs Attention&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; Monitoring gaps — no alerting on payment failures, needs setup&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;When AI reads this at the start of a session, it knows what I’m dealing with. It can prioritize, notice conflicts, and avoid suggesting things I’ve already rejected. The file acts as a handoff between sessions — like briefing notes for an incoming shift.&lt;/p&gt;
&lt;h3 id=&quot;preferencesmd--behavioral-memory&quot;&gt;preferences.md — behavioral memory&lt;/h3&gt;
&lt;p&gt;When I tell AI “don’t do X” or “I prefer Y,” that correction lasts exactly one session. Next time, same mistake. &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;preferences&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;md&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt; makes corrections permanent:&lt;/p&gt;
&lt;figure&gt;&lt;pre tabindex=&quot;0&quot;&gt;&lt;code style=&quot;display:grid&quot;&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;## Communication&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; Prefers brief, direct answers — no filler, no excessive caveats&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; No emojis unless asked&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;## Decision Making&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; Pragmatic over perfect — &quot;good enough now&quot; beats &quot;ideal later&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;## Behavioral Corrections&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; 2026-01-15: &quot;Don&apos;t summarize what I just said back to me&quot; — context&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;The AI reads this file, adjusts its behavior, and the correction sticks. Over time the file becomes a fairly accurate profile of how you work.&lt;/p&gt;
&lt;h3 id=&quot;decisionsmd--the-decision-log&quot;&gt;decisions.md — the decision log&lt;/h3&gt;
&lt;p&gt;This one prevents the most insidious problem: re-litigating settled questions. When a significant decision gets made, it’s logged with reasoning and alternatives considered. Two weeks later when I’m second-guessing a technical or business decision, the AI can point to the entry and say “here’s why you decided this.”&lt;/p&gt;
&lt;h2 id=&quot;references-stable-context&quot;&gt;References: Stable Context&lt;/h2&gt;
&lt;p&gt;Notes are dated and temporal. But some context is durable — it applies across sessions and doesn’t change with each meeting.&lt;/p&gt;
&lt;figure&gt;&lt;pre tabindex=&quot;0&quot;&gt;&lt;code style=&quot;display:grid&quot;&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;references&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;├── &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;professional&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;self&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;md&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;     # &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;Who&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; I&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; am&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;background&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;├── &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;products&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;md&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;              # &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;Product&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; descriptions&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;├── &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;projects&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/                # &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;Living&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; project&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; briefs&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;└── &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;people&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;    ├── &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;internal&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/            # &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;Team&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; members&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;    ├── &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;partners&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/            # &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;External&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; partners&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;    ├── &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;candidates&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/          # &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;Hiring&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; pipeline&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;    └── &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;customers&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/           # &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;Customer&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; accounts&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Project briefs get updated in place as understanding evolves — architecture decisions, shifted strategy, changed ownership. They’re not notes about a project. They’re the canonical source of truth for what the project &lt;em&gt;is&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;People files are the same idea. When I’m about to meet someone, the AI can read their file and tell me: here’s who they are, here’s what we discussed last time, here’s what’s pending between us. No prep work on my part.&lt;/p&gt;
&lt;h2 id=&quot;skills-workflows-as-markdown&quot;&gt;Skills: Workflows as Markdown&lt;/h2&gt;
&lt;p&gt;This is where it gets interesting.&lt;/p&gt;
&lt;p&gt;A “skill” is a markdown file that teaches AI a multi-step workflow. Not code. Not a plugin. Just instructions that reference tools.&lt;/p&gt;
&lt;p&gt;Here’s the structure of the morning kickoff skill:&lt;/p&gt;
&lt;figure&gt;&lt;pre tabindex=&quot;0&quot;&gt;&lt;code style=&quot;display:grid&quot;&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;# Morning Kickoff&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;Create a daily &quot;today&quot; page with an overview of the day ahead.&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;## Steps&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;1.&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; Get today&apos;s date and determine the day of week&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;2.&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; Create the daily file at &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;`daily/YYYYMMDD--dayofweek.md`&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;3.&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; Gather information using the relevant skills:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;   -&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; Check work calendar and personal calendar&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;   -&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; Check today&apos;s tasks, upcoming tasks, deadlines&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;   -&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; Check email for anything unread or requiring attention&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;4.&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; Read all compass files to fully load context&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;That’s it. The AI reads this, follows the steps, calls the appropriate tools (email, calendar, task manager), and produces a daily briefing. No code to maintain. No API integration to debug.&lt;/p&gt;
&lt;p&gt;I have about 20 of these now:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Morning kickoff&lt;/strong&gt; — daily briefing with calendar, tasks, emails, and rehydrated context&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Weekly review&lt;/strong&gt; — audit progress against goals, clean up stale threads, identify stuck items&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Inbox triage&lt;/strong&gt; — process work email, personal email, and task inbox in sequence&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Process meetings&lt;/strong&gt; — pull transcripts from Notion, extract tasks, create meeting notes, flag Zettelkasten connections&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;People prep&lt;/strong&gt; — briefing about a specific person before a 1:1&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Follow-ups&lt;/strong&gt; — scan notes and emails for commitments that aren’t tracked as tasks&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Wrap-up&lt;/strong&gt; — end-of-session routine that saves context and logs decisions&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The realization that made this click: most operational work is a checklist. And checklists are exactly what AI is good at following — especially when the tools and context are transparent.&lt;/p&gt;
&lt;p&gt;Why markdown instead of code? Because I can edit a workflow in 30 seconds. Adding a step, changing the order, adjusting what gets checked — it’s just editing a text file. And the AI reads it natively. No parsing, no compilation, no framework.&lt;/p&gt;
&lt;h2 id=&quot;the-session-end-hook&quot;&gt;The Session-End Hook&lt;/h2&gt;
&lt;p&gt;The skills above all require me to invoke them. The session-end hook doesn’t.&lt;/p&gt;
&lt;p&gt;It’s an extension that fires automatically when a session ends. It spawns a background AI agent that reads the session transcript and decides what’s worth persisting. It updates compass files, project briefs, and people references — without me asking.&lt;/p&gt;
&lt;p&gt;The prompt for this agent is specific about what to look for:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Status changes&lt;/strong&gt; — did a project move forward, get blocked, or resolve?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Decisions&lt;/strong&gt; — was something significant decided?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Preferences&lt;/strong&gt; — did I correct the AI’s behavior?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;People info&lt;/strong&gt; — did new context surface about someone I work with?&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;And specific about what to ignore: routine debugging, trivial file edits, implementation details.&lt;/p&gt;
&lt;p&gt;This closes a loop that matters: the system maintains itself. I don’t have to remember to update context after each session. I don’t have to decide whether something was worth logging. The agent handles triage, and I review the results.&lt;/p&gt;
&lt;h2 id=&quot;a-typical-day&quot;&gt;A Typical Day&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Morning.&lt;/strong&gt; I run &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;morning&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;kickoff&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt;. AI checks both calendars, pulls tasks and deadlines, scans email for anything urgent, reads compass files to rehydrate context. Produces a daily page I can glance at over coffee.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;During work.&lt;/strong&gt; When I ask for advice — on priorities, architecture decisions, how to handle a conversation — the AI already knows what I’m working on, what’s been decided, and how I prefer to operate. The advice is grounded, not generic.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;After meetings.&lt;/strong&gt; &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;process&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;meetings&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt; pulls transcripts from Notion, creates summary notes, extracts follow-up tasks, and checks them against my task manager. If a discussion maps to an existing Zettelkasten concept, it flags the connection.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;End of day.&lt;/strong&gt; Either I run &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;wrap&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;up&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt; explicitly, or the session-end hook captures context automatically. Open threads get updated. Decisions get logged. Nothing falls through the cracks — or at least, fewer things do.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Weekly.&lt;/strong&gt; &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;weekly&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;review&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt; compares what actually happened against goals. Cleans up stale context. Surfaces stuck items I’ve been unconsciously avoiding.&lt;/p&gt;
&lt;h2 id=&quot;the-principle&quot;&gt;The Principle&lt;/h2&gt;
&lt;p&gt;The original post argued that transparency enables tooling. That’s still true, but it’s the small version. The bigger version: transparency enables delegation.&lt;/p&gt;
&lt;p&gt;When AI can see the full system, has persistent memory, and has procedures to follow — it’s not a chat assistant anymore. It’s an operational layer. It maintains its own context. It runs routines. It catches things you’d miss.&lt;/p&gt;
&lt;p&gt;The building blocks are the same: plain text, simple conventions, no proprietary formats. But the capability ceiling is determined by what you teach it to do, not by what the tool vendor ships.&lt;/p&gt;
&lt;h2 id=&quot;tradeoffs&quot;&gt;Tradeoffs&lt;/h2&gt;
&lt;p&gt;Same ones as before, plus a few new ones:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Terminal-native.&lt;/strong&gt; If you don’t live in a terminal, there’s friction. I’m building a Telegram bot to extend this outside the IDE, but it’s not there yet.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Designing for AI is a skill.&lt;/strong&gt; You’re not just organizing notes for yourself anymore. You’re thinking about what context an AI agent needs, how to structure handoffs, what’s worth persisting. It’s a different discipline.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Trust calibration.&lt;/strong&gt; The session-end hook updates files autonomously. Mostly it’s accurate. Occasionally it overpersists or misreads significance. You need to review its work, especially early on.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tool surface area.&lt;/strong&gt; This works because I have CLI tools for email, calendar, tasks, and meeting transcripts. Each new integration is a new tool to build or configure. The system is only as capable as its access.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;whats-next&quot;&gt;What’s Next&lt;/h2&gt;
&lt;p&gt;The gap I’m most interested in closing: proactive behavior. Right now, AI acts when I invoke it. The morning kickoff runs because I type the command. But the information is all there — calendar, tasks, deadlines, commitments. An agent could notice that I have a meeting in an hour with someone I haven’t prepped for, or that a deadline is approaching with no progress on the task.&lt;/p&gt;
&lt;p&gt;Moving from “AI that helps when asked” to “AI that notices when something needs attention” is the next step. The foundation — transparent files, persistent memory, structured workflows — is already in place. The trigger just needs to shift from manual to event-driven.&lt;/p&gt;
&lt;p&gt;Fifty lines of bash was the starting point. The system it grew into is unrecognizable from those origins. But the principle hasn’t changed: keep everything transparent, and AI will surprise you with what it can do.&lt;/p&gt;</content:encoded><category>AI</category><category>Productivity</category><category>Tools</category></item><item><title>📚 The Middle Passage — James Hollis</title><link>https://danielbenner.de/books/the-middle-passage/</link><guid isPermaLink="true">https://danielbenner.de/books/the-middle-passage/</guid><description>Enjoyed this a lot. The perspective and argument are basically identical to Falling Upward, but without being religious — which made it land harder for me. I could relate to many of the arguments in a way that felt immediate rather than abstract.</description><pubDate>Sun, 01 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;div class=&quot;my-6 border-l-2 border-accent pl-6 py-2&quot;&gt; &lt;h2 class=&quot;text-sm font-bold uppercase tracking-widest mb-2&quot; style=&quot;color:var(--color-accent)&quot;&gt;Provisional Personality&lt;/h2&gt; &lt;div class=&quot;text-sm leading-relaxed&quot;&gt; &lt;p&gt;The identity you build in the first half of life isn’t really yours. It’s assembled from parental imprinting, cultural scripts, and adaptive strategies you developed as a child to stay safe, loved, and accepted. People-pleasing, overachieving, withdrawal, control — these survival mechanisms crystallize into personality traits you mistake for who you are.&lt;/p&gt;&lt;p&gt;The key point: this personality &lt;em&gt;works&lt;/em&gt;. It gets you through childhood, school, career, relationships. It’s functional. But it’s a collage of other people’s expectations and your adaptive responses to them. The middle passage begins when this provisional self starts breaking down — when the strategies that got you here stop working, and the life you built feels hollow despite looking right from the outside.&lt;/p&gt; &lt;/div&gt; &lt;/div&gt;
&lt;div class=&quot;my-6 border-l-2 border-accent pl-6 py-2&quot;&gt; &lt;h2 class=&quot;text-sm font-bold uppercase tracking-widest mb-2&quot; style=&quot;color:var(--color-accent)&quot;&gt;The Midlife Crisis as Summons&lt;/h2&gt; &lt;div class=&quot;text-sm leading-relaxed&quot;&gt; &lt;p&gt;Hollis reframes the midlife crisis not as a breakdown but as a summons. The provisional personality produces diminishing returns. Achievement fails to satisfy. The shadow — everything you repressed to maintain the functional self — accumulates pressure until it forces its way into consciousness.&lt;/p&gt;&lt;p&gt;The crisis presents a fork. Refuse the summons: double down on old strategies, numb out, chase a younger version of the same life. New car, new partner, same patterns. Or accept the summons: let the provisional personality collapse, endure the disorientation, and begin asking who you actually are beneath the scripts.&lt;/p&gt;&lt;p&gt;Depression and anxiety aren’t pathology here — they’re signals. The psyche saying the life you’re living is too small for the person you’ve become.&lt;/p&gt; &lt;/div&gt; &lt;/div&gt;
&lt;div class=&quot;my-6 border-l-2 border-accent pl-6 py-2&quot;&gt; &lt;h2 class=&quot;text-sm font-bold uppercase tracking-widest mb-2&quot; style=&quot;color:var(--color-accent)&quot;&gt;Individuation&lt;/h2&gt; &lt;div class=&quot;text-sm leading-relaxed&quot;&gt; &lt;p&gt;Jung’s term for the process of becoming who you actually are. Not self-improvement — not optimizing the provisional personality — but dismantling it to find what’s underneath.&lt;/p&gt;&lt;p&gt;This means confronting your shadow (the repressed parts running your life from the basement), withdrawing projections (recognizing that what triggers you in others is often your own unowned material), and differentiating from complexes (the emotionally charged patterns like “I’m not good enough” that hijack behavior).&lt;/p&gt;&lt;p&gt;Whatever you sacrificed to maintain the provisional personality now demands its due. The creative impulse you buried, the values you suppressed, the way of being you abandoned to fit in. It’s grueling work, never complete. The unconscious is inexhaustible. The point is the ongoing relationship with it, not arrival.&lt;/p&gt; &lt;/div&gt; &lt;/div&gt;</content:encoded><category>Books</category></item><item><title>📚 Falling Upward — Richard Rohr</title><link>https://danielbenner.de/books/falling-upward/</link><guid isPermaLink="true">https://danielbenner.de/books/falling-upward/</guid><description>Genuinely learned something interesting about Christianity and the spiritual journey — wouldn&apos;t have expected that from a book this explicitly religious. The core ideas are worth knowing. But Rohr repeats and rephrases the same arguments endlessly. Could be 60% shorter. Might work better for readers who need the repetition to let it sink in.</description><pubDate>Sun, 25 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;div class=&quot;my-6 border-l-2 border-accent pl-6 py-2&quot;&gt; &lt;h2 class=&quot;text-sm font-bold uppercase tracking-widest mb-2&quot; style=&quot;color:var(--color-accent)&quot;&gt;Transcend and Include&lt;/h2&gt; &lt;div class=&quot;text-sm leading-relaxed&quot;&gt; &lt;p&gt;Genuine development doesn’t replace earlier stages — it builds on and contains them. If you’ve authentically moved to a more mature perspective, you’ve necessarily passed through the earlier ones. They live within you.&lt;/p&gt;&lt;p&gt;This makes judgment structurally incoherent. You’d be condemning a version of yourself.&lt;/p&gt;&lt;p&gt;Flip side: if someone is superior or dismissive about their “advanced” views, that’s evidence they haven’t actually integrated the earlier stage. They’ve bypassed rather than transcended. True development brings humility automatically.&lt;/p&gt; &lt;/div&gt; &lt;/div&gt;
&lt;div class=&quot;my-6 border-l-2 border-accent pl-6 py-2&quot;&gt; &lt;h2 class=&quot;text-sm font-bold uppercase tracking-widest mb-2&quot; style=&quot;color:var(--color-accent)&quot;&gt;Teachings as Scaffolding&lt;/h2&gt; &lt;div class=&quot;text-sm leading-relaxed&quot;&gt; &lt;p&gt;Religious teachings are entry points, not destinations. They get you “in the right ballpark” but eventually need to be transcended through contemplation.&lt;/p&gt;&lt;p&gt;The distinction:&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Kataphatic&lt;/strong&gt; — what we &lt;em&gt;can&lt;/em&gt; say about God (doctrines, images, concepts)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Apophatic&lt;/strong&gt; — what we &lt;em&gt;cannot&lt;/em&gt; say, the mystery beyond language&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;This isn’t modern revisionism. It’s the older contemplative tradition — Desert Fathers, Meister Eckhart, John of the Cross. The conceptual frameworks eventually become obstacles if you cling to them.&lt;/p&gt; &lt;/div&gt; &lt;/div&gt;
&lt;div class=&quot;my-6 border-l-2 border-accent pl-6 py-2&quot;&gt; &lt;h2 class=&quot;text-sm font-bold uppercase tracking-widest mb-2&quot; style=&quot;color:var(--color-accent)&quot;&gt;Pushed vs Pulled&lt;/h2&gt; &lt;div class=&quot;text-sm leading-relaxed&quot;&gt; &lt;p&gt;What motivates us shifts between the two halves of life:&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;First half&lt;/strong&gt;: pushed from behind — by ego, wounds, fear, the need to prove yourself&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Second half&lt;/strong&gt;: pulled from ahead — by soul, meaning, calling&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;Early-life drive isn’t bad, but it’s often compensatory — proving worth, escaping shame. Second-half motivation feels different: less frantic, arising from abundance rather than lack.&lt;/p&gt; &lt;/div&gt; &lt;/div&gt;</content:encoded><category>Books</category></item><item><title>📚 High Output Management — Andy Grove</title><link>https://danielbenner.de/books/high-output-management/</link><guid isPermaLink="true">https://danielbenner.de/books/high-output-management/</guid><description>The first third felt more descriptive than actionable. The planning chapter was solid, and I got real value from the concepts around hybrid organization, dual reporting, and task-relevant maturity. But compared to Goldratt&apos;s books, far less practical — more management philosophy than concrete toolkit.</description><pubDate>Mon, 19 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;div class=&quot;my-6 border-l-2 border-accent pl-6 py-2&quot;&gt; &lt;h2 class=&quot;text-sm font-bold uppercase tracking-widest mb-2&quot; style=&quot;color:var(--color-accent)&quot;&gt;Hybrid Organization&lt;/h2&gt; &lt;div class=&quot;text-sm leading-relaxed&quot;&gt; &lt;p&gt;Neither pure functional (group by expertise) nor pure mission-oriented (group by objective) structures work at scale. Grove argues for combining both:&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Mission-oriented units&lt;/strong&gt; handle customer-facing work — speed and autonomy to respond to markets&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Functional units&lt;/strong&gt; provide centralized services where scale matters — HR, legal, specialized expertise&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;The question isn’t “which structure?” but “which structure for which work?”&lt;/p&gt; &lt;/div&gt; &lt;/div&gt;
&lt;div class=&quot;my-6 border-l-2 border-accent pl-6 py-2&quot;&gt; &lt;h2 class=&quot;text-sm font-bold uppercase tracking-widest mb-2&quot; style=&quot;color:var(--color-accent)&quot;&gt;Dual Reporting&lt;/h2&gt; &lt;div class=&quot;text-sm leading-relaxed&quot;&gt; &lt;p&gt;The hybrid creates matrix management — individuals reporting to both a functional manager (expertise, standards, career) and a mission manager (priorities, delivery, outcomes).&lt;/p&gt;&lt;p&gt;This friction is a feature. It forces trade-off conversations to happen explicitly rather than letting one priority silently dominate. The matrix doesn’t eliminate conflict — it institutionalizes it. The alternative isn’t harmony, it’s hidden dysfunction.&lt;/p&gt; &lt;/div&gt; &lt;/div&gt;
&lt;div class=&quot;my-6 border-l-2 border-accent pl-6 py-2&quot;&gt; &lt;h2 class=&quot;text-sm font-bold uppercase tracking-widest mb-2&quot; style=&quot;color:var(--color-accent)&quot;&gt;Task-Relevant Maturity&lt;/h2&gt; &lt;div class=&quot;text-sm leading-relaxed&quot;&gt; &lt;p&gt;Adjust management style based on someone’s experience with a &lt;em&gt;specific task&lt;/em&gt; — not their general seniority.&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Low TRM&lt;/strong&gt;: Structured, directive. Tell them what, when, how. Monitor closely.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Medium TRM&lt;/strong&gt;: Coaching. Two-way communication, set goals together, regular but less intensive monitoring.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;High TRM&lt;/strong&gt;: Delegative. Set objectives and constraints, then step back. Monitor at milestones.&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;A senior engineer might need high TRM style for backend architecture but low TRM style for their first people management responsibility. Mismatching style to TRM fails in both directions.&lt;/p&gt; &lt;/div&gt; &lt;/div&gt;
&lt;div class=&quot;my-6 border-l-2 border-accent pl-6 py-2&quot;&gt; &lt;h2 class=&quot;text-sm font-bold uppercase tracking-widest mb-2&quot; style=&quot;color:var(--color-accent)&quot;&gt;Pairing Indicators&lt;/h2&gt; &lt;div class=&quot;text-sm leading-relaxed&quot;&gt; &lt;p&gt;Any single metric creates incentive to game it. Pair complementary indicators that create natural tension:&lt;/p&gt;




















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Quantity&lt;/th&gt;&lt;th&gt;Quality&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Units produced&lt;/td&gt;&lt;td&gt;Defect rate&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Tickets resolved&lt;/td&gt;&lt;td&gt;Reopened rate&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Features shipped&lt;/td&gt;&lt;td&gt;Bug count&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;Neither metric can be gamed in isolation because optimizing one at the expense of the other becomes immediately visible.&lt;/p&gt; &lt;/div&gt; &lt;/div&gt;
&lt;div class=&quot;my-6 border-l-2 border-accent pl-6 py-2&quot;&gt; &lt;h2 class=&quot;text-sm font-bold uppercase tracking-widest mb-2&quot; style=&quot;color:var(--color-accent)&quot;&gt;Checkpoint vs Outcome KRs&lt;/h2&gt; &lt;div class=&quot;text-sm leading-relaxed&quot;&gt; &lt;p&gt;Grove’s original Key Results were &lt;strong&gt;checkpoints&lt;/strong&gt; — concrete milestones. Modern OKR literature reframes them as &lt;strong&gt;outcome measurements&lt;/strong&gt; — metrics indicating progress.&lt;/p&gt;&lt;p&gt;Different failure modes:&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;Checkpoints force sequencing discipline but can become output-focused without asking if it mattered&lt;/li&gt;
&lt;li&gt;Outcomes keep focus on impact but are harder to pace against&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;Execution mode (known path) favors checkpoints. Innovation mode (still searching) favors outcomes. Mismatch between style and context is why most OKR implementations fail.&lt;/p&gt; &lt;/div&gt; &lt;/div&gt;
&lt;div class=&quot;my-6 border-l-2 border-accent pl-6 py-2&quot;&gt; &lt;h2 class=&quot;text-sm font-bold uppercase tracking-widest mb-2&quot; style=&quot;color:var(--color-accent)&quot;&gt;Tension with Theory of Constraints&lt;/h2&gt; &lt;div class=&quot;text-sm leading-relaxed&quot;&gt; &lt;p&gt;Grove measures productivity at the individual work unit level — improving any unit’s productivity improves the whole.&lt;/p&gt;&lt;p&gt;Goldratt would disagree. In any system with dependencies, only the constraint determines throughput. Optimizing non-constraints creates waste: inventory piles up, WIP increases, resources consumed producing things that can’t flow through.&lt;/p&gt;&lt;p&gt;Grove’s indicator pairing partially guards against this, but he never asks “is this work unit even the bottleneck?” before optimizing it.&lt;/p&gt; &lt;/div&gt; &lt;/div&gt;</content:encoded><category>Books</category></item><item><title>📚 The Sleep Solution — Chris Winter</title><link>https://danielbenner.de/books/the-sleep-solution/</link><guid isPermaLink="true">https://danielbenner.de/books/the-sleep-solution/</guid><description>Read this because of my insomnia. Turns out I just needed to get my shit together. It&apos;s already working. Fact-based, no exaggerated attention-grabbing nonsense.</description><pubDate>Sun, 18 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;div class=&quot;my-6 border-l-2 border-accent pl-6 py-2&quot;&gt; &lt;h2 class=&quot;text-sm font-bold uppercase tracking-widest mb-2&quot; style=&quot;color:var(--color-accent)&quot;&gt;Sleep State Misperception&lt;/h2&gt; &lt;div class=&quot;text-sm leading-relaxed&quot;&gt; &lt;p&gt;Many people who believe they “don’t sleep” are actually sleeping more than they realize. Light sleep and brief awakenings can feel like being awake, creating a gap between perceived and actual sleep time.&lt;/p&gt;&lt;p&gt;The test: if you feel reasonably functional the next day, you slept more than you think. True sleep deprivation makes functioning nearly impossible.&lt;/p&gt;&lt;p&gt;The anxiety about insomnia often causes more harm than the actual sleep loss.&lt;/p&gt; &lt;/div&gt; &lt;/div&gt;
&lt;div class=&quot;my-6 border-l-2 border-accent pl-6 py-2&quot;&gt; &lt;h2 class=&quot;text-sm font-bold uppercase tracking-widest mb-2&quot; style=&quot;color:var(--color-accent)&quot;&gt;Vigilance vs Sleepiness&lt;/h2&gt; &lt;div class=&quot;text-sm leading-relaxed&quot;&gt; &lt;p&gt;Vigilance and sleepiness are separate neural systems, not opposites on a single spectrum.&lt;/p&gt;&lt;p&gt;Sleepiness is a homeostatic pressure that builds the longer you’re awake. Vigilance is an alerting response triggered by stimuli — novelty, stress, checking your phone, getting frustrated in bed.&lt;/p&gt;&lt;p&gt;“I’m not tired anymore” often means vigilance temporarily kicked in. The underlying sleep pressure is still there. Wait it out.&lt;/p&gt; &lt;/div&gt; &lt;/div&gt;
&lt;div class=&quot;my-6 border-l-2 border-accent pl-6 py-2&quot;&gt; &lt;h2 class=&quot;text-sm font-bold uppercase tracking-widest mb-2&quot; style=&quot;color:var(--color-accent)&quot;&gt;Insomnia ≠ Sleep Deprivation&lt;/h2&gt; &lt;div class=&quot;text-sm leading-relaxed&quot;&gt; &lt;p&gt;Insomnia is difficulty sleeping when and how you want to. Sleep deprivation is actually getting very little sleep. They’re conflated constantly, but they’re fundamentally different.&lt;/p&gt;&lt;p&gt;True sleep deprivation (like lab studies where subjects are kept awake) causes severe impairment. Most people with insomnia are still getting meaningful sleep — the distress and preoccupation around sleep is often the core problem, not the sleep itself.&lt;/p&gt;&lt;p&gt;Importing scary research about sleep deprivation into your insomnia amplifies anxiety, which makes sleep harder, which increases anxiety.&lt;/p&gt; &lt;/div&gt; &lt;/div&gt;
&lt;div class=&quot;my-6 border-l-2 border-accent pl-6 py-2&quot;&gt; &lt;h2 class=&quot;text-sm font-bold uppercase tracking-widest mb-2&quot; style=&quot;color:var(--color-accent)&quot;&gt;The Insomniac Identity Trap&lt;/h2&gt; &lt;div class=&quot;text-sm leading-relaxed&quot;&gt; &lt;p&gt;Building an identity around being “a bad sleeper” creates a self-reinforcing cycle:&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;Experience some sleep difficulty&lt;/li&gt;
&lt;li&gt;Absorb alarming media about sleep deprivation consequences&lt;/li&gt;
&lt;li&gt;Develop anxiety and hypervigilance around sleep&lt;/li&gt;
&lt;li&gt;Anxiety disrupts sleep further&lt;/li&gt;
&lt;li&gt;Adopt “insomniac” identity as explanation&lt;/li&gt;
&lt;li&gt;Identity reinforces belief that good sleep isn’t possible&lt;/li&gt;
&lt;li&gt;Return to step 3&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;Breaking it requires separating what you’re experiencing from who you are.&lt;/p&gt; &lt;/div&gt; &lt;/div&gt;</content:encoded><category>Books</category></item><item><title>📚 It&apos;s Not Luck — Eliyahu Goldratt</title><link>https://danielbenner.de/books/its-not-luck/</link><guid isPermaLink="true">https://danielbenner.de/books/its-not-luck/</guid><description>The Thinking Processes are genuinely useful — I&apos;ve already applied them at work. The presentation is awkward though, especially the family dialogues, and some trees get rushed over without enough detail. Less entertaining than The Goal, but the concepts are highly applicable. Still very worth the read.</description><pubDate>Tue, 06 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;div class=&quot;my-6 border-l-2 border-accent pl-6 py-2&quot;&gt; &lt;h2 class=&quot;text-sm font-bold uppercase tracking-widest mb-2&quot; style=&quot;color:var(--color-accent)&quot;&gt;The Thinking Processes&lt;/h2&gt; &lt;div class=&quot;text-sm leading-relaxed&quot;&gt; &lt;p&gt;While &lt;em&gt;The Goal&lt;/em&gt; gave us the Five Focusing Steps for physical constraints, &lt;em&gt;It’s Not Luck&lt;/em&gt; introduces the Thinking Processes — a logical toolkit for solving complex problems, especially policy constraints and conflicts.&lt;/p&gt;&lt;p&gt;Five tools, five questions:&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Current Reality Tree&lt;/strong&gt; — What to change?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Evaporating Cloud&lt;/strong&gt; — What to change to?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Future Reality Tree&lt;/strong&gt; — Will it work?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Prerequisite Tree&lt;/strong&gt; — What’s in the way?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Transition Tree&lt;/strong&gt; — What actions?&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;Each produces output that feeds the next. Skip steps and you fail in predictable ways.&lt;/p&gt; &lt;/div&gt; &lt;/div&gt;
&lt;div class=&quot;my-6 border-l-2 border-accent pl-6 py-2&quot;&gt; &lt;h2 class=&quot;text-sm font-bold uppercase tracking-widest mb-2&quot; style=&quot;color:var(--color-accent)&quot;&gt;Why Known Problems Persist&lt;/h2&gt; &lt;div class=&quot;text-sm leading-relaxed&quot;&gt; &lt;p&gt;If you know about a problem and it persists, something is stopping you from solving it. That something is almost always a perceived trade-off.&lt;/p&gt;&lt;p&gt;The interesting question shifts from “why does this exist?” to “why haven’t we solved it?”&lt;/p&gt;&lt;p&gt;Most answers reduce to: &lt;em&gt;“I believe I can’t have X without losing Y.”&lt;/em&gt; That belief is either a valid constraint or an assumption worth challenging.&lt;/p&gt; &lt;/div&gt; &lt;/div&gt;
&lt;div class=&quot;my-6 border-l-2 border-accent pl-6 py-2&quot;&gt; &lt;h2 class=&quot;text-sm font-bold uppercase tracking-widest mb-2&quot; style=&quot;color:var(--color-accent)&quot;&gt;The Evaporating Cloud&lt;/h2&gt; &lt;div class=&quot;text-sm leading-relaxed&quot;&gt; &lt;p&gt;Most conflicts aren’t real — they rest on hidden assumptions. The cloud makes them visible.&lt;/p&gt;&lt;figure&gt;&lt;pre tabindex=&quot;0&quot;&gt;&lt;code style=&quot;display:grid&quot;&gt;&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;                A: &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;Common&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; Objective&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;                        │&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;             ┌──────────┴──────────┐&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;             │                     │&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;             v&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;                     v&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;       B: &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;Requirement&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;        C: &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;Requirement&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;             │                     │&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;             v&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;                     v&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;       D: &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;Position&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; ◄── &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;conflict&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; ──► &lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;D&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&apos;: Positio&lt;/span&gt;&lt;span style=&quot;color:#FFFFFF&quot;&gt;n&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;p&gt;Both sides want the same objective (A) but believe they need contradictory things (D vs D’). Surface the assumptions behind each arrow. Invalidate one, and the conflict evaporates.&lt;/p&gt; &lt;/div&gt; &lt;/div&gt;
&lt;div class=&quot;my-6 border-l-2 border-accent pl-6 py-2&quot;&gt; &lt;h2 class=&quot;text-sm font-bold uppercase tracking-widest mb-2&quot; style=&quot;color:var(--color-accent)&quot;&gt;CRT to EC: The Handoff&lt;/h2&gt; &lt;div class=&quot;text-sm leading-relaxed&quot;&gt; &lt;p&gt;The Current Reality Tree maps cause-effect chains from symptoms to root cause. But finding the root cause isn’t enough — you need to understand why it persists.&lt;/p&gt;&lt;p&gt;Root causes persist because of perceived conflicts. The EC structures that conflict. CRT answers &lt;em&gt;what&lt;/em&gt;, EC answers &lt;em&gt;why we haven’t fixed it&lt;/em&gt;.&lt;/p&gt; &lt;/div&gt; &lt;/div&gt;
&lt;div class=&quot;my-6 border-l-2 border-accent pl-6 py-2&quot;&gt; &lt;h2 class=&quot;text-sm font-bold uppercase tracking-widest mb-2&quot; style=&quot;color:var(--color-accent)&quot;&gt;Future Reality Tree&lt;/h2&gt; &lt;div class=&quot;text-sm leading-relaxed&quot;&gt; &lt;p&gt;The FRT is a flight simulator for your solution. Take the injection from the EC, trace its effects forward through cause-effect logic.&lt;/p&gt;&lt;p&gt;Two things to check:&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;Does it eliminate the undesirable effects from the CRT?&lt;/li&gt;
&lt;li&gt;Does it create new problems (negative branch reservations)?&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;For every “what could go wrong,” either add a preventive injection or modify the solution. Critics become contributors — they’re stress-testing your logic, not attacking your idea.&lt;/p&gt; &lt;/div&gt; &lt;/div&gt;
&lt;div class=&quot;my-6 border-l-2 border-accent pl-6 py-2&quot;&gt; &lt;h2 class=&quot;text-sm font-bold uppercase tracking-widest mb-2&quot; style=&quot;color:var(--color-accent)&quot;&gt;From Objectives to Actions&lt;/h2&gt; &lt;div class=&quot;text-sm leading-relaxed&quot;&gt; &lt;p&gt;The Prerequisite Tree captures obstacles (“we can’t because…”) and converts each into an intermediate objective — a state that overcomes the obstacle. Not actions, states.&lt;/p&gt;&lt;p&gt;The Transition Tree then breaks each intermediate objective into concrete action sequences with explicit cause-effect logic: given reality and need, if we do X, then Y results.&lt;/p&gt;&lt;p&gt;PRT answers “what conditions must exist.” TT answers “what exactly do we do.”&lt;/p&gt; &lt;/div&gt; &lt;/div&gt;
&lt;div class=&quot;my-6 border-l-2 border-accent pl-6 py-2&quot;&gt; &lt;h2 class=&quot;text-sm font-bold uppercase tracking-widest mb-2&quot; style=&quot;color:var(--color-accent)&quot;&gt;The Full Sequence&lt;/h2&gt; &lt;div class=&quot;text-sm leading-relaxed&quot;&gt; &lt;figure&gt;&lt;pre tabindex=&quot;0&quot;&gt;&lt;code style=&quot;display:grid&quot;&gt;&lt;span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;CRT&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; ──► &lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;EC&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; ──► &lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;FRT&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; ──► &lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;PRT&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; ──► &lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;TT&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; ──► &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;Implementation&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; │       │       │       │       │&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; │       │       │       │       └─ &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;What&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; actions&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;?&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; │       │       │       └───────── &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;What&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&apos;s in the way&lt;/span&gt;&lt;span style=&quot;color:#FFFFFF&quot;&gt;?&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; │       │       └───────────────── &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;Will&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; it&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; work&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;?&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; │       └───────────────────────── &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;What&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; to&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; change&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; to&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;?&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; └───────────────────────────────── &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;What&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; to&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; change&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;?&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;p&gt;Skip CRT: solve the wrong problem. Skip EC: fight symptoms. Skip FRT: solution creates new problems. Skip PRT: get stuck on obstacles. Skip TT: vague plan, inconsistent execution.&lt;/p&gt; &lt;/div&gt; &lt;/div&gt;</content:encoded><category>Books</category></item><item><title>📚 The Phoenix Project — Gene Kim, Kevin Behr, George Spafford</title><link>https://danielbenner.de/books/the-phoenix-project/</link><guid isPermaLink="true">https://danielbenner.de/books/the-phoenix-project/</guid><description>The story was more gripping than I&apos;d like to admit. Unlike Critical Chain, this one hit home — insane projects with management attention, too much knowledge stuck in one person&apos;s head. I can directly apply most of what&apos;s in here.</description><pubDate>Sat, 03 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;div class=&quot;my-6 border-l-2 border-accent pl-6 py-2&quot;&gt; &lt;h2 class=&quot;text-sm font-bold uppercase tracking-widest mb-2&quot; style=&quot;color:var(--color-accent)&quot;&gt;The Three Ways&lt;/h2&gt; &lt;div class=&quot;text-sm leading-relaxed&quot;&gt; &lt;p&gt;Three principles for high-performing IT:&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;First Way (Flow)&lt;/strong&gt; — Optimize the whole system, not local work centers&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Second Way (Feedback)&lt;/strong&gt; — Fast, peer-to-peer feedback along the value stream&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Third Way (Learning)&lt;/strong&gt; — Continual improvement through experimentation and failure&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;The First Way establishes left-to-right flow. The Second Way adds right-to-left feedback. The Third Way creates conditions for improvement. Without flow, chaos. Without feedback, no course correction. Without learning, stagnation.&lt;/p&gt; &lt;/div&gt; &lt;/div&gt;
&lt;div class=&quot;my-6 border-l-2 border-accent pl-6 py-2&quot;&gt; &lt;h2 class=&quot;text-sm font-bold uppercase tracking-widest mb-2&quot; style=&quot;color:var(--color-accent)&quot;&gt;The First Way: Flow&lt;/h2&gt; &lt;div class=&quot;text-sm leading-relaxed&quot;&gt; &lt;p&gt;Optimize work movement through the entire system — Dev through Ops to customer.&lt;/p&gt;&lt;p&gt;Local optimizations destroy system throughput. A faster upstream just builds bigger queues at the bottleneck. “Important” means business value, not what IT finds technically interesting.&lt;/p&gt;&lt;p&gt;Never pass defects downstream. Stopping to fix at the source is faster than letting problems propagate. It feels slower. It isn’t.&lt;/p&gt; &lt;/div&gt; &lt;/div&gt;
&lt;div class=&quot;my-6 border-l-2 border-accent pl-6 py-2&quot;&gt; &lt;h2 class=&quot;text-sm font-bold uppercase tracking-widest mb-2&quot; style=&quot;color:var(--color-accent)&quot;&gt;The Second Way: Feedback&lt;/h2&gt; &lt;div class=&quot;text-sm leading-relaxed&quot;&gt; &lt;p&gt;Information flowing right to left so upstream learns consequences.&lt;/p&gt;&lt;p&gt;The emphasis: fast, direct feedback between work centers. Not via management noticing patterns. This is Toyota’s andon cord — worker sees defect, pulls cord, production stops. Local actors, immediate response.&lt;/p&gt;&lt;p&gt;Feedback value is inversely proportional to delay. Finding out your code breaks production three weeks later is useless — context is gone. Finding out in minutes lets you fix it while you still remember what you did.&lt;/p&gt; &lt;/div&gt; &lt;/div&gt;
&lt;div class=&quot;my-6 border-l-2 border-accent pl-6 py-2&quot;&gt; &lt;h2 class=&quot;text-sm font-bold uppercase tracking-widest mb-2&quot; style=&quot;color:var(--color-accent)&quot;&gt;The Third Way: Learning&lt;/h2&gt; &lt;div class=&quot;text-sm leading-relaxed&quot;&gt; &lt;p&gt;Creating conditions where improvement happens.&lt;/p&gt;&lt;p&gt;Two forms: experimentation (trying things, accepting some will fail) and failure practice (incident response, blameless post-mortems, building resilience).&lt;/p&gt;&lt;p&gt;Both require time and safety. Time because improvement work needs protected capacity. Safety because people won’t surface problems or try risky ideas if failure means punishment.&lt;/p&gt;&lt;p&gt;The goal isn’t a system that never fails. It’s a system that learns faster than the competition.&lt;/p&gt; &lt;/div&gt; &lt;/div&gt;
&lt;div class=&quot;my-6 border-l-2 border-accent pl-6 py-2&quot;&gt; &lt;h2 class=&quot;text-sm font-bold uppercase tracking-widest mb-2&quot; style=&quot;color:var(--color-accent)&quot;&gt;The Brent Problem&lt;/h2&gt; &lt;div class=&quot;text-sm leading-relaxed&quot;&gt; &lt;p&gt;When Brent takes on work “only Brent can do,” Brent gets smarter and everyone else misses an opportunity to catch up.&lt;/p&gt;&lt;p&gt;Every time the expert takes on work:&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;Expert gets better (more context, deeper understanding)&lt;/li&gt;
&lt;li&gt;Everyone else stays where they are&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;The gap widens. More work flows to the expert. They become more irreplaceable. More work flows to them. Vicious cycle.&lt;/p&gt;&lt;p&gt;“Only Brent can do this” usually means “Brent can do this fastest right now.” The question: optimizing for this week or this year?&lt;/p&gt;&lt;p&gt;Breaking the cycle means letting others struggle. Slower throughput now for higher capacity later. Treating knowledge transfer as real work, not overhead.&lt;/p&gt; &lt;/div&gt; &lt;/div&gt;
&lt;div class=&quot;my-6 border-l-2 border-accent pl-6 py-2&quot;&gt; &lt;h2 class=&quot;text-sm font-bold uppercase tracking-widest mb-2&quot; style=&quot;color:var(--color-accent)&quot;&gt;Man, Machine, Method, Metric&lt;/h2&gt; &lt;div class=&quot;text-sm leading-relaxed&quot;&gt; &lt;p&gt;The 4 Ms — what’s necessary for any work center to function:&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Man&lt;/strong&gt; — Person with right skills and availability&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Machine&lt;/strong&gt; — Tools and resources needed&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Method&lt;/strong&gt; — Documented process&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Metric&lt;/strong&gt; — How you measure and track&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;When work isn’t flowing: Do we have someone? Do they have tools? Is there a clear process? Can we measure what’s happening?&lt;/p&gt;&lt;p&gt;People assume the constraint is the person. Often it’s process or tooling.&lt;/p&gt; &lt;/div&gt; &lt;/div&gt;</content:encoded><category>Books</category></item><item><title>Shrink Your Dependency Surface: Use Cases Over Services</title><link>https://danielbenner.de/articles/small-dependency-surface-use-cases/</link><guid isPermaLink="true">https://danielbenner.de/articles/small-dependency-surface-use-cases/</guid><description>Use case architecture keeps dependency surfaces minimal, preventing circular dependencies as your app scales.</description><pubDate>Fri, 02 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Every time you add a method to a service, you expand the dependency surface for everyone who uses it. A &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;TaskService&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt; with 20 methods means every consumer depends on all 20 — even if they only need one. As your application grows, this coupling compounds. Then the circular dependencies appear.&lt;/p&gt;
&lt;h2 id=&quot;services-grow-surfaces-expand&quot;&gt;Services Grow, Surfaces Expand&lt;/h2&gt;
&lt;p&gt;The traditional service pattern starts clean:&lt;/p&gt;
&lt;figure&gt;&lt;pre tabindex=&quot;0&quot;&gt;&lt;code style=&quot;display:grid&quot;&gt;&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; TaskService&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt; struct&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;    repo&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;TaskRepository&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;func&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8;font-style:italic&quot;&gt;s &lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;TaskService&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Create&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8;font-style:italic&quot;&gt;title&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt; string&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;) (&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;Task&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;) { ... }&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Then requirements arrive. Tasks need projects. Tasks need areas. Tasks need tags, recurrence, completion logic. The service grows:&lt;/p&gt;
&lt;figure&gt;&lt;pre tabindex=&quot;0&quot;&gt;&lt;code style=&quot;display:grid&quot;&gt;&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; TaskService&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt; struct&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;    repo&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;           *&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;TaskRepository&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;    projectService&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; ProjectService&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;    areaService&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;    AreaService&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;func&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8;font-style:italic&quot;&gt;s &lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;TaskService&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Create&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(...) &lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;        { ... }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;func&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8;font-style:italic&quot;&gt;s &lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;TaskService&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Complete&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(...) &lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;      { ... }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;func&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8;font-style:italic&quot;&gt;s &lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;TaskService&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;SetProject&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(...) &lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;    { ... }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;func&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8;font-style:italic&quot;&gt;s &lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;TaskService&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;SetArea&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(...) &lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;       { ... }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;func&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8;font-style:italic&quot;&gt;s &lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;TaskService&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;AddTag&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(...) &lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;        { ... }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;func&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8;font-style:italic&quot;&gt;s &lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;TaskService&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;SetRecurrence&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(...) &lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; { ... }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F98F;font-style:italic&quot;&gt;// ... 15 more methods&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Each new method potentially adds cross-domain dependencies. Every consumer of &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;TaskService&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt; now implicitly depends on &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;ProjectService&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt; and &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;AreaService&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt; — even if they only call &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;Create&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt;.&lt;/p&gt;
&lt;h2 id=&quot;when-surfaces-collide&quot;&gt;When Surfaces Collide&lt;/h2&gt;
&lt;p&gt;This gets worse when bounded contexts need each other. Consider:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Creating a task requires validating the project exists&lt;/li&gt;
&lt;li&gt;Completing a project requires completing all its tasks&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The traditional approach:&lt;/p&gt;
&lt;figure&gt;&lt;pre tabindex=&quot;0&quot;&gt;&lt;code style=&quot;display:grid&quot;&gt;&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; TaskService&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt; struct&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;    projectService&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; ProjectService&lt;/span&gt;&lt;span style=&quot;color:#EEF0F98F;font-style:italic&quot;&gt;  // Need to validate project&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; ProjectService&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt; struct&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;    taskService&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; TaskService&lt;/span&gt;&lt;span style=&quot;color:#EEF0F98F;font-style:italic&quot;&gt;  // Need to complete child tasks&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Circular import. The compiler refuses.&lt;/p&gt;
&lt;p&gt;The frustrating part: &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;CreateTask&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt; only needs project &lt;em&gt;lookup&lt;/em&gt;. &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;CompleteProject&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt; only needs task &lt;em&gt;completion&lt;/em&gt;. These operations don’t overlap. But because both are bundled into monolithic services, the entire service becomes the dependency unit.&lt;/p&gt;
&lt;h2 id=&quot;use-case-architecture&quot;&gt;Use Case Architecture&lt;/h2&gt;
&lt;p&gt;The fix: make each operation its own struct with exactly the dependencies it needs.&lt;/p&gt;
&lt;figure&gt;&lt;pre tabindex=&quot;0&quot;&gt;&lt;code style=&quot;display:grid&quot;&gt;&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; ProjectLookup&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt; interface&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;    Execute&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8;font-style:italic&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt; string&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;) (&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;Task&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; CreateTask&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt; struct&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;    Repo&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;          *&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;TaskRepository&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;    ProjectLookup&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; ProjectLookup&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;func&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8;font-style:italic&quot;&gt;c &lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;CreateTask&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Execute&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8;font-style:italic&quot;&gt;title&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt; string&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8;font-style:italic&quot;&gt;opts&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;CreateOptions&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;) (&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;Task&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; opts&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;ProjectName&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; != &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;        p&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;err&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; :=&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; c&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;ProjectLookup&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Execute&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;opts&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;ProjectName&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;        if&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; err&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; != &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;nil&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;            return&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; nil&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;err&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F98F;font-style:italic&quot;&gt;        // assign task to project&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F98F;font-style:italic&quot;&gt;    // ... create the task&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;The interface is defined &lt;em&gt;by the consumer&lt;/em&gt;, not the project domain. &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;CreateTask&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt; doesn’t depend on everything projects can do — just on “something that can look up a project by name.”&lt;/p&gt;
&lt;h2 id=&quot;solving-circular-dependencies&quot;&gt;Solving Circular Dependencies&lt;/h2&gt;
&lt;p&gt;With use case architecture, the circular dependency disappears:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;CreateTask&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt; needs &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;ProjectLookup&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt; (interface) → satisfied by &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;GetProjectByName&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;CompleteProject&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt; needs &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;TaskCompleter&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt; (interface) → satisfied by &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;CompleteTasks&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;No circular imports because:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Neither domain knows the other’s full API&lt;/li&gt;
&lt;li&gt;Interfaces are consumer-defined, not provider-defined&lt;/li&gt;
&lt;li&gt;Each use case declares exactly what it needs&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The dependency graph stays a DAG. Always.&lt;/p&gt;
&lt;h2 id=&quot;the-wiring&quot;&gt;The Wiring&lt;/h2&gt;
&lt;p&gt;Dependencies resolve in a neutral location—a composition root:&lt;/p&gt;
&lt;figure&gt;&lt;pre tabindex=&quot;0&quot;&gt;&lt;code style=&quot;display:grid&quot;&gt;&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;func&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt; New&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8;font-style:italic&quot;&gt;db&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;database&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;DB&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;App&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;    taskRepo&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; :=&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; task&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;NewRepository&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;db&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;    areaRepo&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; :=&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; area&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;NewRepository&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;db&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;    getAreaByName&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; :=&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt; &amp;amp;&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;areausecases&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;GetAreaByName&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;Repo&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;areaRepo&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;    getProjectByName&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; :=&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt; &amp;amp;&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;taskusecases&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;GetProjectByName&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;Repo&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;taskRepo&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;    createTask&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; :=&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt; &amp;amp;&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;taskusecases&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;CreateTask&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;        Repo&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;:          &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;taskRepo&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;        ProjectLookup&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;getProjectByName&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;        AreaLookup&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;:    &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;getAreaByName&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt; &amp;amp;&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;App&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;        CreateTask&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;:       &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;createTask&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;        GetProjectByName&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;getProjectByName&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F98F;font-style:italic&quot;&gt;        // ... other use cases&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Each use case gets exactly what it needs. &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;GetProjectByName&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt; satisfies the &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;ProjectLookup&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt; interface that &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;CreateTask&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt; defined. No service layer coordinates everything—use cases compose directly.&lt;/p&gt;
&lt;h2 id=&quot;trade-offs&quot;&gt;Trade-offs&lt;/h2&gt;
&lt;p&gt;This pattern has costs:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;More files.&lt;/strong&gt; One file per operation. A domain with 10 operations has 10 use case files instead of one service file.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Verbose wiring.&lt;/strong&gt; The composition root grows as use cases multiply. In a real application, &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;app&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;go&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt; might wire 30+ use cases.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Requires discipline.&lt;/strong&gt; Use cases must stay focused. The temptation to add “just one more dependency” defeats the purpose.&lt;/p&gt;
&lt;p&gt;The benefits:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Easy to test.&lt;/strong&gt; Mock only the interfaces a use case declares. &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;CreateTask&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt; tests mock &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;ProjectLookup&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt; and &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;AreaLookup&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt;—nothing else.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Easy to understand.&lt;/strong&gt; Open a file, see one operation. No scrolling through a 500-line service to find the method you care about.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Changes stay isolated.&lt;/strong&gt; Modifying &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;CreateTask&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt; doesn’t affect &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;CompleteTasks&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt;. Different operations can evolve independently.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;No circular dependency risk.&lt;/strong&gt; The architecture makes cycles impossible by construction.&lt;/p&gt;
&lt;h2 id=&quot;the-core-insight&quot;&gt;The Core Insight&lt;/h2&gt;
&lt;p&gt;The unit of dependency should match the unit of operation. Services bundle unrelated operations, creating artificial coupling between consumers and producers. Use cases keep dependencies minimal and directional—each operation depends only on what it actually needs.&lt;/p&gt;
&lt;p&gt;As your application grows, the dependency surface stays proportional to individual operations, not to the sum of everything each domain can do.&lt;/p&gt;</content:encoded><category>Go</category><category>Architecture</category><category>DDD</category></item><item><title>📚 Critical Chain — Eliyahu Goldratt</title><link>https://danielbenner.de/books/the-critical-chain/</link><guid isPermaLink="true">https://danielbenner.de/books/the-critical-chain/</guid><description>Seeing the five steps from The Goal applied to project management was useful — helps the framework stick. Worth the read if you&apos;re managing large-scale projects. Less relevant for me, and the solutions felt less concrete than in the original.</description><pubDate>Wed, 31 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;div class=&quot;my-6 border-l-2 border-accent pl-6 py-2&quot;&gt; &lt;h2 class=&quot;text-sm font-bold uppercase tracking-widest mb-2&quot; style=&quot;color:var(--color-accent)&quot;&gt;Critical Chain vs Critical Path&lt;/h2&gt; &lt;div class=&quot;text-sm leading-relaxed&quot;&gt; &lt;p&gt;Critical Path considers task dependencies. Critical Chain considers task dependencies &lt;em&gt;and&lt;/em&gt; resource dependencies.&lt;/p&gt;&lt;figure&gt;&lt;pre tabindex=&quot;0&quot;&gt;&lt;code style=&quot;display:grid&quot;&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;Critical&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt; Path&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;ignores&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; resources&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;):&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;Task&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; A&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; ──────┐&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;             ├──→ &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;Task&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; C&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;Task&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; B&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; ──────┘&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;Duration: &lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;max&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;A&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;B&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;) + &lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;C&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;Critical&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt; Chain&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;same&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; person&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; does&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; A&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; and&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; B&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;):&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;Task&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; A&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; ──────→ &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;Task&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; B&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; ──────→ &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;Task&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; C&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;Duration: &lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;A&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; + &lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;B&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; + &lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;C&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;p&gt;Critical Path gives you an optimistic fiction. Critical Chain gives you what will actually happen.&lt;/p&gt; &lt;/div&gt; &lt;/div&gt;
&lt;div class=&quot;my-6 border-l-2 border-accent pl-6 py-2&quot;&gt; &lt;h2 class=&quot;text-sm font-bold uppercase tracking-widest mb-2&quot; style=&quot;color:var(--color-accent)&quot;&gt;The Five Steps Applied to Projects&lt;/h2&gt; &lt;div class=&quot;text-sm leading-relaxed&quot;&gt; &lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Identify&lt;/strong&gt; — Find the critical chain: the longest path through both task and resource dependencies.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Exploit&lt;/strong&gt; — Strip hidden safety from estimates. Eliminate multitasking on critical chain tasks. Never let critical chain resources wait for inputs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Subordinate&lt;/strong&gt; — Everything else serves the chain. Feeding work delivers before it’s needed. Resources prioritize chain work over everything.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Elevate&lt;/strong&gt; — Add resources, parallelize, cut scope. Only after exploitation is maxed out.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Repeat&lt;/strong&gt; — The chain shifts. Back to step 1.&lt;/li&gt;
&lt;/ol&gt; &lt;/div&gt; &lt;/div&gt;
&lt;div class=&quot;my-6 border-l-2 border-accent pl-6 py-2&quot;&gt; &lt;h2 class=&quot;text-sm font-bold uppercase tracking-widest mb-2&quot; style=&quot;color:var(--color-accent)&quot;&gt;The Buffer System&lt;/h2&gt; &lt;div class=&quot;text-sm leading-relaxed&quot;&gt; &lt;p&gt;Instead of hiding safety in each task, consolidate it:&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Project Buffer&lt;/strong&gt; — End of the critical chain. Protects the delivery date.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Feeding Buffer&lt;/strong&gt; — Where non-critical paths join the chain. Protects against delays in feeding work.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Resource Buffer&lt;/strong&gt; — Alerts key resources their task is coming. Not time, just a heads-up.&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;Same total safety time, dramatically different outcomes.&lt;/p&gt; &lt;/div&gt; &lt;/div&gt;
&lt;div class=&quot;my-6 border-l-2 border-accent pl-6 py-2&quot;&gt; &lt;h2 class=&quot;text-sm font-bold uppercase tracking-widest mb-2&quot; style=&quot;color:var(--color-accent)&quot;&gt;Why Consolidated Buffers Work&lt;/h2&gt; &lt;div class=&quot;text-sm leading-relaxed&quot;&gt; &lt;p&gt;Three reasons distributed safety fails:&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Statistical aggregation.&lt;/strong&gt; Uncertainties don’t all materialize. Pooled buffers let variations cancel out. A 10-day project buffer protects better than 10 × 1-day task buffers. Insurance math.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Behavioral effects.&lt;/strong&gt; Parkinson’s Law: work expands to fill available time. Student Syndrome: people delay until pressure builds. Built-in safety gets consumed whether needed or not. Consolidated buffers are only touched when actually required.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Visibility.&lt;/strong&gt; Distributed safety hides problems until too late. Consolidated buffers give you “60% consumed with 40% done” — a clear signal to act.&lt;/p&gt; &lt;/div&gt; &lt;/div&gt;
&lt;div class=&quot;my-6 border-l-2 border-accent pl-6 py-2&quot;&gt; &lt;h2 class=&quot;text-sm font-bold uppercase tracking-widest mb-2&quot; style=&quot;color:var(--color-accent)&quot;&gt;The Late Start Principle&lt;/h2&gt; &lt;div class=&quot;text-sm leading-relaxed&quot;&gt; &lt;p&gt;Starting late is better than starting early. Counterintuitive, but the math checks out.&lt;/p&gt;&lt;p&gt;Early start problems:&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;Student Syndrome means early start just moves procrastination earlier&lt;/li&gt;
&lt;li&gt;Parkinson’s Law means extra time gets used whether needed or not&lt;/li&gt;
&lt;li&gt;Early finishes never get passed on — people polish, delay reporting, or start something else&lt;/li&gt;
&lt;li&gt;More WIP means more context switching and resource collisions&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;Late start with buffers:&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;Minimizes work-in-progress&lt;/li&gt;
&lt;li&gt;Reduces resource contention&lt;/li&gt;
&lt;li&gt;Avoids premature work on requirements that might change&lt;/li&gt;
&lt;li&gt;Buffer provides protection without the behavioral problems&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;The rule: start as late as possible while buffers absorb variation. Starting early feels safer. Starting late with buffers &lt;em&gt;is&lt;/em&gt; safer.&lt;/p&gt; &lt;/div&gt; &lt;/div&gt;</content:encoded><category>Books</category></item><item><title>📚 The Goal — Eliyahu Goldratt</title><link>https://danielbenner.de/books/the-goal/</link><guid isPermaLink="true">https://danielbenner.de/books/the-goal/</guid><description>Came at exactly the right time. I was stressed about our delivery process — too much to do, nothing getting done. The book brought clarity and order to the messy problem I&apos;d been wrangling with in my head.</description><pubDate>Sun, 28 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;div class=&quot;my-6 border-l-2 border-accent pl-6 py-2&quot;&gt; &lt;h2 class=&quot;text-sm font-bold uppercase tracking-widest mb-2&quot; style=&quot;color:var(--color-accent)&quot;&gt;The Core Insight&lt;/h2&gt; &lt;div class=&quot;text-sm leading-relaxed&quot;&gt; &lt;p&gt;System throughput is determined by the bottleneck. Improving anything else is an illusion of progress.&lt;/p&gt;&lt;figure&gt;&lt;pre tabindex=&quot;0&quot;&gt;&lt;code style=&quot;display:grid&quot;&gt;&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;Wide&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;] → [&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;Wide&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;] → [&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;NARROW&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;] → [&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;Wide&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;] → &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;Output&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;p&gt;Widening the wide sections changes nothing. This is obvious when drawn as pipes, yet organizations constantly pour resources into non-constraints and wonder why output doesn’t improve.&lt;/p&gt; &lt;/div&gt; &lt;/div&gt;
&lt;div class=&quot;my-6 border-l-2 border-accent pl-6 py-2&quot;&gt; &lt;h2 class=&quot;text-sm font-bold uppercase tracking-widest mb-2&quot; style=&quot;color:var(--color-accent)&quot;&gt;Five Focusing Steps&lt;/h2&gt; &lt;div class=&quot;text-sm leading-relaxed&quot;&gt; &lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Identify&lt;/strong&gt; — Find the bottleneck.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Exploit&lt;/strong&gt; — Squeeze every drop of capacity from it without spending money.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Subordinate&lt;/strong&gt; — Everything else serves the constraint. Deliberately hold back non-bottlenecks.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Elevate&lt;/strong&gt; — Now invest. Only after exploitation is maxed out.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Repeat&lt;/strong&gt; — A new constraint emerges. Back to step 1.&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;Step 3 is where most organizations fail. Holding resources back feels wasteful. It isn’t.&lt;/p&gt; &lt;/div&gt; &lt;/div&gt;
&lt;div class=&quot;my-6 border-l-2 border-accent pl-6 py-2&quot;&gt; &lt;h2 class=&quot;text-sm font-bold uppercase tracking-widest mb-2&quot; style=&quot;color:var(--color-accent)&quot;&gt;Finding Bottlenecks&lt;/h2&gt; &lt;div class=&quot;text-sm leading-relaxed&quot;&gt; &lt;p&gt;Look for where inventory piles up. The constraint is always busy while downstream resources are starved. Follow the queues.&lt;/p&gt;&lt;p&gt;The constraint isn’t always a machine or person. It can be a policy, a skill (only one person knows how to do X), or the market itself.&lt;/p&gt; &lt;/div&gt; &lt;/div&gt;
&lt;div class=&quot;my-6 border-l-2 border-accent pl-6 py-2&quot;&gt; &lt;h2 class=&quot;text-sm font-bold uppercase tracking-widest mb-2&quot; style=&quot;color:var(--color-accent)&quot;&gt;Activation vs Utilization&lt;/h2&gt; &lt;div class=&quot;text-sm leading-relaxed&quot;&gt; &lt;p&gt;Keeping everyone busy is not the same as being productive. A worker upstream of the bottleneck running at 100% just builds piles of WIP. A worker downstream starves regardless.&lt;/p&gt;&lt;p&gt;Idle time at non-bottlenecks isn’t waste. It’s the point.&lt;/p&gt; &lt;/div&gt; &lt;/div&gt;
&lt;div class=&quot;my-6 border-l-2 border-accent pl-6 py-2&quot;&gt; &lt;h2 class=&quot;text-sm font-bold uppercase tracking-widest mb-2&quot; style=&quot;color:var(--color-accent)&quot;&gt;Drum-Buffer-Rope&lt;/h2&gt; &lt;div class=&quot;text-sm leading-relaxed&quot;&gt; &lt;p&gt;The hiking analogy: put the slowest kid (Herbie) at the front and tie a rope to him. The whole group moves at his pace without spreading apart.&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Drum&lt;/strong&gt; — The bottleneck sets the pace&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Buffer&lt;/strong&gt; — Inventory in front of the constraint so it never starves&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rope&lt;/strong&gt; — Ties input to bottleneck capacity&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;Only regulate what enters the system. If the bottleneck processes 10 units/hour, release 10 units/hour at the start. Everything in between will sort itself out.&lt;/p&gt; &lt;/div&gt; &lt;/div&gt;
&lt;div class=&quot;my-6 border-l-2 border-accent pl-6 py-2&quot;&gt; &lt;h2 class=&quot;text-sm font-bold uppercase tracking-widest mb-2&quot; style=&quot;color:var(--color-accent)&quot;&gt;Throughput Accounting&lt;/h2&gt; &lt;div class=&quot;text-sm leading-relaxed&quot;&gt; &lt;p&gt;Traditional cost accounting rewards overproduction — inventory is an asset on the balance sheet. Managers game this.&lt;/p&gt;&lt;p&gt;TOC flips it:&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Throughput (T)&lt;/strong&gt; — Revenue minus truly variable costs&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Inventory (I)&lt;/strong&gt; — Money stuck in the system&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Operating Expense (OE)&lt;/strong&gt; — Money spent to turn I into T&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;Net Profit = T - OE. ROI = (T - OE) / I.&lt;/p&gt;&lt;p&gt;The priority for decisions: increase throughput first (biggest leverage), reduce inventory second (frees cash), reduce operating expense last (often counterproductive if it touches the constraint).&lt;/p&gt; &lt;/div&gt; &lt;/div&gt;</content:encoded><category>Books</category></item><item><title>A Note-Taking System That Works With AI, Not Against It</title><link>https://danielbenner.de/articles/minimal-ai-friendly-notes/</link><guid isPermaLink="true">https://danielbenner.de/articles/minimal-ai-friendly-notes/</guid><description>When your entire system is 50 lines of bash, AI can write whatever tooling you need.</description><pubDate>Thu, 11 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Obsidian, Notion, Roam — there’s no shortage of good note-taking apps. But I wanted something where AI could not just read my notes, but write tooling for them. So I built a system that’s about 50 lines of bash, and AI can extend it trivially.&lt;/p&gt;
&lt;h2 id=&quot;the-idea&quot;&gt;The Idea&lt;/h2&gt;
&lt;p&gt;This isn’t anti-Obsidian. Obsidian is great, and it uses markdown too. The difference is what happens when you want a new feature.&lt;/p&gt;
&lt;p&gt;In most apps, you either wait for it, find a plugin, or learn the plugin API and build it yourself. In a system that’s just files and scripts, you ask Claude “write me a script that does X” and it works. No plugins to learn, no API to integrate—just scripts that operate on files.&lt;/p&gt;
&lt;p&gt;When the whole system is transparent, AI can reason about it completely.&lt;/p&gt;
&lt;h2 id=&quot;the-structure&quot;&gt;The Structure&lt;/h2&gt;
&lt;p&gt;Notes are plain markdown files with date-prefixed names:&lt;/p&gt;
&lt;figure&gt;&lt;pre tabindex=&quot;0&quot;&gt;&lt;code style=&quot;display:grid&quot;&gt;&lt;span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;20251210&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;--&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;project&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;kickoff&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;md&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;20251211&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;--&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;api&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;design&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;notes&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;md&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Organized in folders by project or area:&lt;/p&gt;
&lt;figure&gt;&lt;pre tabindex=&quot;0&quot;&gt;&lt;code style=&quot;display:grid&quot;&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;notes&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;├── &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;active&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;│   ├── &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;client&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;project&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;│   ├── &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;side&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;project&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;│   └── &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;personal&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;├── &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;archive&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;└── &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;scripts&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Active projects live in &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;active&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt;. When a project wraps up, it moves to &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;archive&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt;. The &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;scripts&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt; folder holds the tooling.&lt;/p&gt;
&lt;p&gt;Dependencies: &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;fzf&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt; for fuzzy selection, &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;bat&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt; for syntax-highlighted previews, &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;ripgrep&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt; for fast search. Standard Unix tools.&lt;/p&gt;
&lt;h2 id=&quot;five-scripts-i-found-useful&quot;&gt;Five Scripts I Found Useful&lt;/h2&gt;
&lt;p&gt;These are the actions I needed. Your list might be different — that’s the point.&lt;/p&gt;
&lt;h3 id=&quot;nn--new-note&quot;&gt;nn — New Note&lt;/h3&gt;
&lt;p&gt;Pick a folder, enter a title, creates a dated markdown file and opens it.&lt;/p&gt;
&lt;figure&gt;&lt;pre tabindex=&quot;0&quot;&gt;&lt;code style=&quot;display:grid&quot;&gt;&lt;span&gt;&lt;span style=&quot;color:#EEF0F98F;font-style:italic&quot;&gt;#!/bin/bash&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;NOTES_DIR&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;/Users/daniel/notes&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;ACTIVE_DIR&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;$NOTES_DIR&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;/active&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;PICKER&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;fzf&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;folders&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;=$(&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;find&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;$ACTIVE_DIR&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; -mindepth&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; -maxdepth&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; -type&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; d&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; -exec&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; basename&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; {}&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; \; | &lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;sort&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; [ -z &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;$folders&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; ]; &lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;then&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;    echo &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;No folders found in &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;$ACTIVE_DIR&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;    exit &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;1&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;fi&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;folder&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;=$(echo &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;$folders&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; | &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;$PICKER&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; --prompt=&quot;Select folder: &quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; [ -z &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;$folder&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; ]; &lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;then&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;    echo &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;No folder selected&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;    exit &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;1&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;fi&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;read &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;-p&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; &quot;Title: &quot;&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; title&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; [ -z &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;$title&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; ]; &lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;then&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;    echo &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;Title cannot be empty&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;    exit &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;1&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;fi&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;date_stamp&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;=$(&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;date&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; +%Y%m%d&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;slug&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;=$(echo &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;$title&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; | &lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;tr&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; &apos;[:upper:]&apos;&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; &apos;[:lower:]&apos;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; | &lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;tr&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; &apos; &apos;&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; &apos;-&apos;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; | &lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;tr&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; -cd&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; &apos;a-z0-9-&apos;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;filename&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;${&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;date_stamp&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;}--${&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;slug&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;}.md&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;filepath&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;${&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;ACTIVE_DIR&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;}/${&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;folder&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;}/${&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;filename&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;}&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;echo &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;# ${&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;title&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;}&quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; &amp;gt; &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;$filepath&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;echo &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; &amp;gt;&amp;gt; &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;$filepath&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;echo &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;Created: &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;$filepath&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;EDITOR&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;:-&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;vim&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;} &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;$filepath&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;h3 id=&quot;nf--find-note&quot;&gt;nf — Find Note&lt;/h3&gt;
&lt;p&gt;Fuzzy-find notes by filename with preview. Use &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;p&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt; to pick a specific folder first.&lt;/p&gt;
&lt;figure&gt;&lt;pre tabindex=&quot;0&quot;&gt;&lt;code style=&quot;display:grid&quot;&gt;&lt;span&gt;&lt;span style=&quot;color:#EEF0F98F;font-style:italic&quot;&gt;#!/bin/bash&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;NOTES_DIR&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;/Users/daniel/notes&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;ACTIVE_DIR&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;$NOTES_DIR&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;/active&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;PICKER&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;fzf&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;pick_folder&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;false&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; arg&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt; in&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8;font-style:italic&quot;&gt;$@&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;; &lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;do&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;  case&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; $arg&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt; in&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;    -p&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;--pick&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;pick_folder&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; ;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;  esac&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;done&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; $pick_folder&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;; &lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;then&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;  folders&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;=$(&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;find&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;$ACTIVE_DIR&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; -mindepth&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; -maxdepth&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; -type&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; d&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; -exec&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; basename&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; {}&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; \; | &lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;sort&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; [ -z &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;$folders&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; ]; &lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;then&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;    echo &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;No folders found in &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;$ACTIVE_DIR&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;    exit &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;1&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;  fi&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;  folder&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;=$(echo &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;$folders&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; | &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;$PICKER&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; --prompt=&quot;Select folder: &quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; [ -z &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;$folder&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; ]; &lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;then&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;    echo &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;No folder selected&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;    exit &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;1&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;  fi&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;  search_dir&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;$ACTIVE_DIR&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;$folder&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;else&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;  search_dir&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;$ACTIVE_DIR&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;fi&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;notes&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;=$(&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;find&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;$search_dir&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; -name&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; &quot;*.md&quot;&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; -type&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; f&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; | &lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;sort&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; [ -z &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;$notes&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; ]; &lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;then&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;  echo &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;No notes found&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;  exit &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;1&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;fi&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;filepath&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;=$(echo &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;$notes&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; | &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;$PICKER&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; --prompt=&quot;Select note: &quot;&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; --preview=&quot;bat --color=always --style=plain {}&quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; [ -z &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;$filepath&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; ]; &lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;then&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;  echo &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;No note selected&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;  exit &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;1&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;fi&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;EDITOR&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;:-&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;vim&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;} &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;$filepath&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;h3 id=&quot;ns--search-content&quot;&gt;ns — Search Content&lt;/h3&gt;
&lt;p&gt;Full-text search across all notes. Opens at the matching line.&lt;/p&gt;
&lt;figure&gt;&lt;pre tabindex=&quot;0&quot;&gt;&lt;code style=&quot;display:grid&quot;&gt;&lt;span&gt;&lt;span style=&quot;color:#EEF0F98F;font-style:italic&quot;&gt;#!/bin/bash&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;NOTES_DIR&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;/Users/daniel/notes&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;ACTIVE_DIR&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;$NOTES_DIR&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;/active&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;PICKER&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;fzf&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;pick_folder&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;false&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; arg&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt; in&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8;font-style:italic&quot;&gt;$@&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;; &lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;do&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;  case&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; $arg&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt; in&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;    -p&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;--pick&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;pick_folder&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; ;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;  esac&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;done&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; $pick_folder&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;; &lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;then&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;  folders&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;=$(&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;find&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;$ACTIVE_DIR&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; -mindepth&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; -maxdepth&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; -type&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; d&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; -exec&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; basename&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; {}&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; \; | &lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;sort&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; [ -z &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;$folders&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; ]; &lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;then&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;    echo &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;No folders found in &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;$ACTIVE_DIR&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;    exit &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;1&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;  fi&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;  folder&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;=$(echo &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;$folders&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; | &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;$PICKER&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; --prompt=&quot;Select folder: &quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; [ -z &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;$folder&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; ]; &lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;then&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;    echo &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;No folder selected&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;    exit &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;1&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;  fi&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;  search_dir&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;$ACTIVE_DIR&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;$folder&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;else&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;  search_dir&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;$ACTIVE_DIR&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;fi&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;result&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;=$(&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;rg&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; --line-number&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; --color=always&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; .&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;$search_dir&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; | &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;$PICKER&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; --ansi&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; --prompt=&quot;Search content: &quot;&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; --preview=&quot;bat --color=always --style=plain --highlight-line {2} {1}&quot;&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; --delimiter=:&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; --preview-window=+&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;2}-10&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; [ -z &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;$result&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; ]; &lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;then&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;  echo &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;No match selected&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;  exit &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;1&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;fi&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;filepath&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;=$(echo &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;$result&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; | &lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;cut&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; -d:&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; -f1&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;line&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;=$(echo &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;$result&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; | &lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;cut&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; -d:&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; -f2&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;EDITOR&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;:-&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;vim&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;} &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;+&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;$line&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;$filepath&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;h3 id=&quot;np--new-project&quot;&gt;np — New Project&lt;/h3&gt;
&lt;p&gt;Create a new folder in &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;active&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt;.&lt;/p&gt;
&lt;figure&gt;&lt;pre tabindex=&quot;0&quot;&gt;&lt;code style=&quot;display:grid&quot;&gt;&lt;span&gt;&lt;span style=&quot;color:#EEF0F98F;font-style:italic&quot;&gt;#!/bin/bash&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;NOTES_DIR&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;/Users/daniel/notes&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;ACTIVE_DIR&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;$NOTES_DIR&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;/active&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;read &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;-p&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; &quot;Folder name: &quot;&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; folder&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; [ -z &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;$folder&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; ]; &lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;then&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;  echo &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;Folder name cannot be empty&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;  exit &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;1&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;fi&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;slug&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;=$(echo &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;$folder&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; | &lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;tr&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; &apos;[:upper:]&apos;&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; &apos;[:lower:]&apos;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; | &lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;tr&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; &apos; &apos;&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; &apos;-&apos;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; | &lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;tr&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; -cd&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; &apos;a-z0-9-&apos;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;mkdir&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; -p&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;$ACTIVE_DIR&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;$slug&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;echo &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;Created: &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;$ACTIVE_DIR&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;$slug&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;h3 id=&quot;na--archive-project&quot;&gt;na — Archive Project&lt;/h3&gt;
&lt;p&gt;Move a folder from &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;active&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt; to &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;archive&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt;.&lt;/p&gt;
&lt;figure&gt;&lt;pre tabindex=&quot;0&quot;&gt;&lt;code style=&quot;display:grid&quot;&gt;&lt;span&gt;&lt;span style=&quot;color:#EEF0F98F;font-style:italic&quot;&gt;#!/bin/bash&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;NOTES_DIR&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;/Users/daniel/notes&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;ACTIVE_DIR&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;$NOTES_DIR&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;/active&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;ARCHIVE_DIR&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;$NOTES_DIR&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;/archive&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;PICKER&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;fzf&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;folders&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;=$(&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;find&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;$ACTIVE_DIR&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; -mindepth&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; -maxdepth&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; -type&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; d&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; -exec&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; basename&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; {}&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; \; | &lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;sort&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; [ -z &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;$folders&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; ]; &lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;then&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;  echo &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;No folders found in &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;$ACTIVE_DIR&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;  exit &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;1&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;fi&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;folder&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;=$(echo &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;$folders&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; | &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;$PICKER&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; --prompt=&quot;Select folder to archive: &quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; [ -z &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;$folder&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; ]; &lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;then&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;  echo &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;No folder selected&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;  exit &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;1&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;fi&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;mkdir&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; -p&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;$ARCHIVE_DIR&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;mv&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;$ACTIVE_DIR&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;$folder&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;$ARCHIVE_DIR&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;/&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;echo &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;Archived: &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;$folder&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;I also have Neovim commands that mirror these &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;NoteNew&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt;, &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;NoteFind&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt;, &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;NoteSearch&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt;, and so on. AI generated those too.&lt;/p&gt;
&lt;h2 id=&quot;ai-can-write-the-rest&quot;&gt;AI Can Write the Rest&lt;/h2&gt;
&lt;p&gt;This is the key part. When you need a new feature, you don’t search for plugins or learn an API. You describe what you want.&lt;/p&gt;
&lt;p&gt;“Write me a script that generates a weekly summary of notes created in the last 7 days.”&lt;/p&gt;
&lt;p&gt;“Write me a script that finds notes containing a specific phrase and lists them with context.”&lt;/p&gt;
&lt;p&gt;“Write me a script that exports a project folder to a single markdown file for sharing.”&lt;/p&gt;
&lt;p&gt;Claude can see the folder structure, understand the naming convention, and write a working script in seconds. No abstractions to navigate, no plugin architecture to satisfy. Just files and bash.&lt;/p&gt;
&lt;p&gt;You don’t need to anticipate every feature. When you need something, ask.&lt;/p&gt;
&lt;h2 id=&quot;tradeoffs&quot;&gt;Tradeoffs&lt;/h2&gt;
&lt;p&gt;There are real downsides:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;No native mobile app.&lt;/strong&gt; A simple markdown viewer / editor like 1Writer is enough for me though.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No sync built-in.&lt;/strong&gt; I use git. Syncthing would also work. But you have to set it up.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No graph views.&lt;/strong&gt; I never found these useful, but if you did — AI could generate one.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Requires terminal comfort.&lt;/strong&gt; If fzf and vim aren’t in your vocabulary, there’s friction.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;why-this-works&quot;&gt;Why This Works&lt;/h2&gt;
&lt;p&gt;The system isn’t better because it’s minimal. It’s better because it’s transparent.&lt;/p&gt;
&lt;p&gt;AI works best when it can see and modify the full system. No hidden state, no proprietary formats, no plugin APIs to reverse-engineer. Just files and scripts that do exactly what they say.&lt;/p&gt;
&lt;p&gt;Five scripts was enough for me. Yours might be different. That’s the point.&lt;/p&gt;</content:encoded><category>AI</category><category>Productivity</category><category>Tools</category></item><item><title>Building the First Open Source Legal MCP Server for German Law</title><link>https://danielbenner.de/articles/legal-mcp-german-law/</link><guid isPermaLink="true">https://danielbenner.de/articles/legal-mcp-german-law/</guid><description>German law is publicly available but practically inaccessible. Here&apos;s how to fix that with semantic search and MCP.</description><pubDate>Sat, 29 Nov 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;German law is technically public. Every federal statute sits on &lt;a href=&quot;https://www.gesetze-im-internet.de&quot;&gt;gesetze-im-internet.de&lt;/a&gt;, maintained by the Federal Ministry of Justice.&lt;/p&gt;
&lt;p&gt;But “public” doesn’t mean “accessible.” Finding relevant law means knowing which of 6,800+ legal codes to search, which section applies, and how to parse dense legal German. Legal professionals spend years building this mental map.&lt;/p&gt;
&lt;p&gt;AI assistants could help—if they had structured access to the data. That’s what &lt;a href=&quot;https://github.com/ayunis-core/ayunis-legal-mcp&quot;&gt;legal-mcp&lt;/a&gt; does: it gives AI semantic search over German federal law via the Model Context Protocol.&lt;/p&gt;
&lt;h2 id=&quot;the-problem-german-law-is-a-labyrinth&quot;&gt;The Problem: German Law Is a Labyrinth&lt;/h2&gt;
&lt;h3 id=&quot;the-data-source&quot;&gt;The Data Source&lt;/h3&gt;
&lt;p&gt;Gesetze-im-internet.de is Germany’s official legal database. It contains every federal law, ordinance, and regulation—6,852 legal codes as of this writing. The data is structured XML, downloadable as ZIP archives, following a DTD called &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;gii&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;norm&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;dtd&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;This sounds usable. It isn’t.&lt;/p&gt;
&lt;h3 id=&quot;why-its-hard-to-use&quot;&gt;Why It’s Hard to Use&lt;/h3&gt;
&lt;p&gt;The official site offers only exact string matching. No semantic search. This creates a fundamental mismatch between how people think about legal questions and how the system expects them to search.&lt;/p&gt;
&lt;p&gt;Consider: you want to know about returning a defective product. Is that in the BGB (Civil Code)? The HGB (Commercial Code)? The VVG (Insurance Contract Act)? Even if you know it’s the BGB, do you search § 437 (Buyer’s Rights), § 439 (Subsequent Performance), or § 323 (Withdrawal from Contract)?&lt;/p&gt;
&lt;p&gt;The answer is all of them, in conjunction. German legal texts are dense with cross-references: ”§ 433 BGB in Verbindung mit § 275 BGB.” The structure assumes you already know the structure.&lt;/p&gt;
&lt;p&gt;Then there’s Rechtsdeutsch—legal German. It’s precise but archaic, using vocabulary that doesn’t match everyday language. Searching for “return defective product” won’t find “Nacherfüllung” (subsequent performance), even though that’s exactly what you need.&lt;/p&gt;
&lt;h3 id=&quot;the-real-challenge&quot;&gt;The Real Challenge&lt;/h3&gt;
&lt;p&gt;Keyword search fails for legal text because legal precision and natural language operate on different axes.&lt;/p&gt;
&lt;p&gt;What people ask: “What are my rights if a product is broken?”&lt;/p&gt;
&lt;p&gt;What the law says: “Der Käufer kann als Nacherfüllung nach seiner Wahl die Beseitigung des Mangels oder die Lieferung einer mangelfreien Sache verlangen.”&lt;/p&gt;
&lt;p&gt;No shared keywords. Same concept. This is a vector search problem.&lt;/p&gt;
&lt;h2 id=&quot;the-solution-mcp--vector-search&quot;&gt;The Solution: MCP + Vector Search&lt;/h2&gt;
&lt;h3 id=&quot;what-mcp-does&quot;&gt;What MCP Does&lt;/h3&gt;
&lt;p&gt;Model Context Protocol is Anthropic’s standard for giving AI structured tool access. Instead of dumping law into prompts (context limits make this impossible anyway), you give AI tools to search on demand.&lt;/p&gt;
&lt;p&gt;Legal-mcp exposes four tools:&lt;/p&gt;
&lt;figure&gt;&lt;pre tabindex=&quot;0&quot;&gt;&lt;code style=&quot;display:grid&quot;&gt;&lt;span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;@mcp&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;tool&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt; def&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt; search_legal_texts&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#FFD493;font-style:italic&quot;&gt;    query&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;: str = &lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Field&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8;font-style:italic&quot;&gt;description&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;The search query text&quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#FFD493;font-style:italic&quot;&gt;    code&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;: str = &lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Field&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8;font-style:italic&quot;&gt;description&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;Legal code identifier (e.g., &apos;bgb&apos;, &apos;stgb&apos;)&quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#FFD493;font-style:italic&quot;&gt;    limit&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;: int = &lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Field&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8;font-style:italic&quot;&gt;default&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8;font-style:italic&quot;&gt;ge&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8;font-style:italic&quot;&gt;le&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;20&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#FFD493;font-style:italic&quot;&gt;    cutoff&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;: float = &lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Field&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8;font-style:italic&quot;&gt;default&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;0.7&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8;font-style:italic&quot;&gt;ge&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;0.0&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8;font-style:italic&quot;&gt;le&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;2.0&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;) -&amp;gt; List[LegalTextResult]:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;    &quot;&quot;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;    Perform semantic search on German legal texts.&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;    Searches through legal codes using semantic similarity to find relevant&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;    sections based on the query text. Lower similarity scores indicate better matches.&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;    &quot;&quot;&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;The others: &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;get_legal_section&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt; for exact retrieval, &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;get_available_codes&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt; for discovery, and &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;import_legal_code&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt; for adding new codes to the database.&lt;/p&gt;
&lt;h3 id=&quot;why-vector-search-matters&quot;&gt;Why Vector Search Matters&lt;/h3&gt;
&lt;p&gt;Vector embeddings capture semantic meaning. The embedding model (Qwen3-Embedding-4B, 2560 dimensions, running locally via Ollama) converts text into high-dimensional vectors where similar concepts cluster together.&lt;/p&gt;
&lt;p&gt;“What are my rights if a product is broken?” produces a vector that’s geometrically close to vectors for warranty and defect provisions—even without shared words.&lt;/p&gt;
&lt;p&gt;This transforms legal search from “hope you know the exact terminology” to “describe what you’re looking for.”&lt;/p&gt;
&lt;h3 id=&quot;the-architecture&quot;&gt;The Architecture&lt;/h3&gt;
&lt;pre class=&quot;mermaid&quot;&gt;flowchart TB
    mcp[&quot;MCP Server&amp;lt;br/&amp;gt;(FastMCP, port 8001)&amp;lt;br/&amp;gt;Tools for AI assistants&quot;]
    store[&quot;Store API&amp;lt;br/&amp;gt;(FastAPI, port 8000)&amp;lt;br/&amp;gt;Search, import, manage legal texts&quot;]
    pg[&quot;Postgres&amp;lt;br/&amp;gt;pgvector&quot;]
    ollama[&quot;Ollama&amp;lt;br/&amp;gt;embed&quot;]
    scraper[&quot;Scraper&amp;lt;br/&amp;gt;XML parse&quot;]

    mcp --&amp;gt;|HTTP| store
    store --&amp;gt; pg
    store --&amp;gt; ollama
    store --&amp;gt; scraper
&lt;/pre&gt;
&lt;p&gt;Three deployable components. The MCP server is a thin HTTP client that exposes tools. The Store API contains all business logic—search, import, embedding coordination. PostgreSQL with pgvector stores the legal texts and their embeddings.&lt;/p&gt;
&lt;p&gt;The separation matters. MCP servers should be stateless tool interfaces. The heavy lifting happens elsewhere.&lt;/p&gt;
&lt;h2 id=&quot;parsing-german-legal-xml&quot;&gt;Parsing German Legal XML&lt;/h2&gt;
&lt;p&gt;The &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;gii&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;norm&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;dtd&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt; format is comprehensive. It’s not just paragraphs—it’s nested structures with specific legal semantics:&lt;/p&gt;
&lt;figure&gt;&lt;pre tabindex=&quot;0&quot;&gt;&lt;code style=&quot;display:grid&quot;&gt;&lt;span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;@dataclass&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; Metadaten&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;    &quot;&quot;&quot;Represents metadata for a legal norm&quot;&quot;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;    jurabk: List[str]  &lt;/span&gt;&lt;span style=&quot;color:#EEF0F98F;font-style:italic&quot;&gt;# Legal abbreviation(s)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;    amtabk: Optional[str] = &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;None&lt;/span&gt;&lt;span style=&quot;color:#EEF0F98F;font-style:italic&quot;&gt;  # Official abbreviation&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;    ausfertigung_datum: Optional[str] = &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;None&lt;/span&gt;&lt;span style=&quot;color:#EEF0F98F;font-style:italic&quot;&gt;  # Promulgation date&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;    fundstelle: List[Fundstelle] = &lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;field&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8;font-style:italic&quot;&gt;default_factory&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;=list)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;    kurzue: Optional[str] = &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;None&lt;/span&gt;&lt;span style=&quot;color:#EEF0F98F;font-style:italic&quot;&gt;  # Short title&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;    langue: Optional[str] = &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;None&lt;/span&gt;&lt;span style=&quot;color:#EEF0F98F;font-style:italic&quot;&gt;  # Long title&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;    gliederungseinheit: Optional[Gliederungseinheit] = &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;None&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;    enbez: Optional[str] = &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;None&lt;/span&gt;&lt;span style=&quot;color:#EEF0F98F;font-style:italic&quot;&gt;  # Section designation&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;    titel: Optional[str] = &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;None&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;    standangabe: List[Standangabe] = &lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;field&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8;font-style:italic&quot;&gt;default_factory&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;=list)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;@dataclass&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; FormattedText&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;    &quot;&quot;&quot;Represents formatted text with structure preserved&quot;&quot;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;    content: str&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;    paragraphs: List[str] = &lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;field&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8;font-style:italic&quot;&gt;default_factory&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;=list)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;    tables: List[Table] = &lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;field&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8;font-style:italic&quot;&gt;default_factory&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;=list)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;    footnote_refs: List[str] = &lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;field&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8;font-style:italic&quot;&gt;default_factory&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;=list)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Multiple title formats (short, long, official). Footnotes that are legally significant. Tables in legislation (yes, really—tax brackets, fee schedules). Status information tracking amendments and repeals.&lt;/p&gt;
&lt;p&gt;The parser handles about 20 distinct element types across 800 lines of code. Most of that complexity is invisible to end users, but it’s necessary to extract clean, searchable text from the source.&lt;/p&gt;
&lt;h3 id=&quot;extracting-structure&quot;&gt;Extracting Structure&lt;/h3&gt;
&lt;p&gt;German legal texts use a consistent notation: § 1, § 2, and so on. Subsections are marked with parenthetical numbers in the text itself:&lt;/p&gt;
&lt;figure&gt;&lt;pre tabindex=&quot;0&quot;&gt;&lt;code style=&quot;display:grid&quot;&gt;&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt; _extract_sub_section&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF;font-style:italic&quot;&gt;self&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFD493;font-style:italic&quot;&gt;section&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;: str) -&amp;gt; str:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F98F;font-style:italic&quot;&gt;    # if section number is present, the str begins with (n)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF;font-style:italic&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; section.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;startswith&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;(&quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;):&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF;font-style:italic&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; section.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;split&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;(&quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)[&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;].&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;split&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;)&quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)[&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF;font-style:italic&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; &quot;&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Simple, but it preserves the granularity that makes legal search useful. You can retrieve § 433 BGB (the whole section on purchase contracts) or § 433 BGB Abs. 1 (just the seller’s obligations).&lt;/p&gt;
&lt;h2 id=&quot;the-embedding-pipeline&quot;&gt;The Embedding Pipeline&lt;/h2&gt;
&lt;h3 id=&quot;batch-processing&quot;&gt;Batch Processing&lt;/h3&gt;
&lt;p&gt;Legal codes vary wildly in size. The Grundgesetz (Constitution) has 146 articles. The BGB (Civil Code) has over 2,300 sections. The Sozialgesetzbuch (Social Code) is split across twelve books.&lt;/p&gt;
&lt;p&gt;Embedding everything at once hits request size limits. The solution is straightforward: process in batches.&lt;/p&gt;
&lt;figure&gt;&lt;pre tabindex=&quot;0&quot;&gt;&lt;code style=&quot;display:grid&quot;&gt;&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt; def&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt; generate_embeddings&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF;font-style:italic&quot;&gt;self&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFD493;font-style:italic&quot;&gt;texts&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;: List[str]) -&amp;gt; Sequence[Sequence[float]]:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;    all_embeddings: List[Sequence[float]] = []&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;    batch_size = &lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;self&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.settings.ollama_batch_size  &lt;/span&gt;&lt;span style=&quot;color:#EEF0F98F;font-style:italic&quot;&gt;# default: 50&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF;font-style:italic&quot;&gt;    for&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; i &lt;/span&gt;&lt;span style=&quot;color:#54B9FF;font-style:italic&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; range(&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, len(texts), batch_size):&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;        batch = texts[i : i + batch_size]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;        logger.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;info&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;            f&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;Generating embeddings for batch &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;i // batch_size + &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;1}&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;/&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;            f&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(len(texts) + batch_size - &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;) // batch_size&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;len(batch)&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; texts)&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;        )&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;        response = &lt;/span&gt;&lt;span style=&quot;color:#54B9FF;font-style:italic&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; self&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.client.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;embed&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8;font-style:italic&quot;&gt;            model&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;self&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.model,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8;font-style:italic&quot;&gt;            input&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;=batch,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;        )&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;        all_embeddings.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;extend&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(response.embeddings)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF;font-style:italic&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; all_embeddings&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Default batch size is 50. Configurable up to 500 for systems with more memory. The tradeoff is straightforward: larger batches are faster but risk 413 errors from nginx or Ollama’s request handling.&lt;/p&gt;
&lt;h3 id=&quot;why-local-inference&quot;&gt;Why Local Inference&lt;/h3&gt;
&lt;p&gt;Using Ollama for embeddings means no API costs and no rate limits. Legal text volumes are substantial—importing the 267 codes relevant for public administration involves embedding tens of thousands of sections.&lt;/p&gt;
&lt;p&gt;The tradeoff: you need a GPU for reasonable performance. On an M1 Mac, embedding the BGB takes about 10 minutes. On a system with a dedicated GPU, it’s under a minute.&lt;/p&gt;
&lt;h2 id=&quot;search-in-practice&quot;&gt;Search in Practice&lt;/h2&gt;
&lt;p&gt;pgvector stores embeddings as native PostgreSQL columns. Search becomes a query:&lt;/p&gt;
&lt;figure&gt;&lt;pre tabindex=&quot;0&quot;&gt;&lt;code style=&quot;display:grid&quot;&gt;&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt; def&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt; semantic_search&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#ACAFFF;font-style:italic&quot;&gt;    self&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#FFD493;font-style:italic&quot;&gt;    query_embedding&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;: Sequence[float],&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#FFD493;font-style:italic&quot;&gt;    code&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;: str,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#FFD493;font-style:italic&quot;&gt;    limit&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;: int = &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;10&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#FFD493;font-style:italic&quot;&gt;    cutoff&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;: Optional[float] = &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;None&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;) -&amp;gt; List[Tuple[LegalTextDB, float]]:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;    &quot;&quot;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;    Uses cosine distance to find the most similar legal texts.&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;    Results are filtered by code and ordered by similarity (closest first).&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;    pgvector&apos;s &amp;lt;=&amp;gt; operator returns cosine distance where:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;    - 0 means identical vectors&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;    - smaller values mean more similar&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;    - 2 means completely opposite vectors&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;    &quot;&quot;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;    distance_expr = LegalTextDB.text_vector.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;cosine_distance&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(query_embedding)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;    query = (&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;        select&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(LegalTextDB, distance_expr.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;label&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;distance&quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;        .&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;filter&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(LegalTextDB.code == code)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;        .&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;order_by&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;distance&quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;        .&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;limit&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(limit)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;    )&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF;font-style:italic&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; cutoff &lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;is&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt; not&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; None&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;        query = query.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;filter&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(distance_expr &amp;lt;= cutoff)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;    result = &lt;/span&gt;&lt;span style=&quot;color:#54B9FF;font-style:italic&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; self&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.session.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;execute&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(query)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF;font-style:italic&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; [(row[&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;], float(row[&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;])) &lt;/span&gt;&lt;span style=&quot;color:#54B9FF;font-style:italic&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; row &lt;/span&gt;&lt;span style=&quot;color:#54B9FF;font-style:italic&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; result.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;all&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;()]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;The cutoff parameter lets users control precision vs. recall. Lower values (0.3-0.5) return only very similar results. Higher values (0.7-1.0) cast a wider net.&lt;/p&gt;
&lt;p&gt;Once imported, search is fast. pgvector supports IVFFlat indexing for approximate nearest neighbor search, scaling to millions of vectors without degradation.&lt;/p&gt;
&lt;h2 id=&quot;what-this-enables&quot;&gt;What This Enables&lt;/h2&gt;
&lt;h3 id=&quot;example-query-flow&quot;&gt;Example Query Flow&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;User asks Claude: “What are my warranty rights under German law?”&lt;/li&gt;
&lt;li&gt;Claude calls &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;search_legal_texts&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;query&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;warranty rights defect product&quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;code&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;bgb&quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;MCP server embeds the query, searches vectors, returns relevant sections&lt;/li&gt;
&lt;li&gt;Claude synthesizes a response citing § 437, § 439, § 323 BGB&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The AI doesn’t need to know German legal structure. It asks in natural language and gets structured results it can reason about.&lt;/p&gt;
&lt;h3 id=&quot;use-cases&quot;&gt;Use Cases&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Legal research assistance.&lt;/strong&gt; “What does German law say about…” questions that would otherwise require knowing which code to search.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Compliance checking.&lt;/strong&gt; “Does this business practice comply with…” queries that need to find relevant regulations.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Public administration.&lt;/strong&gt; The repository includes a curated list of 267 legal codes relevant for government work—procurement law, administrative procedure, data protection, and so on.&lt;/p&gt;
&lt;h2 id=&quot;trade-offs-and-limitations&quot;&gt;Trade-offs and Limitations&lt;/h2&gt;
&lt;h3 id=&quot;what-this-isnt&quot;&gt;What This Isn’t&lt;/h3&gt;
&lt;p&gt;It’s not legal advice. The system retrieves text; it doesn’t interpret it. Legal interpretation requires understanding context, case law, and scholarly commentary that isn’t in the raw statutes.&lt;/p&gt;
&lt;p&gt;It’s not comprehensive. Federal law only—no state law, no case law, no EU regulations. Those would require different data sources and parsers.&lt;/p&gt;
&lt;p&gt;It’s not real-time. You import codes as snapshots. When laws change, you re-import.&lt;/p&gt;
&lt;h3 id=&quot;performance-considerations&quot;&gt;Performance Considerations&lt;/h3&gt;
&lt;p&gt;Initial import is slow. Embedding a large legal code like the BGB involves thousands of API calls to the embedding model. Plan for this during setup, not during queries.&lt;/p&gt;
&lt;p&gt;Search is fast once indexed. The pgvector IVFFlat index handles similarity search in milliseconds, even with thousands of sections.&lt;/p&gt;
&lt;p&gt;Local inference trades money for hardware. No API costs, but you need a capable GPU for reasonable import times.&lt;/p&gt;
&lt;h3 id=&quot;why-open-source&quot;&gt;Why Open Source&lt;/h3&gt;
&lt;p&gt;Legal information should be accessible. The data is public; the tooling to use it should be too.&lt;/p&gt;
&lt;p&gt;Others can extend it. State law, case law, other countries’ legal systems—the pattern (MCP + vector search + domain data) is reusable.&lt;/p&gt;
&lt;p&gt;The specific implementation matters less than the approach: structure domain knowledge so AI can search it semantically, then expose that capability through a standard protocol.&lt;/p&gt;
&lt;h2 id=&quot;the-pattern&quot;&gt;The Pattern&lt;/h2&gt;
&lt;p&gt;German law is public but not accessible. Semantic search + MCP makes it usable by AI.&lt;/p&gt;
&lt;p&gt;The same pattern applies anywhere domain expertise gates access to public information. Medical literature. Patent databases. Building codes. Any corpus where keyword search fails because the vocabulary of experts differs from the vocabulary of questions.&lt;/p&gt;
&lt;p&gt;The repository is at &lt;a href=&quot;https://github.com/ayunis-core/ayunis-legal-mcp&quot;&gt;github.com/ayunis-core/ayunis-legal-mcp&lt;/a&gt;.&lt;/p&gt;</content:encoded><category>AI</category><category>MCP</category><category>Python</category></item><item><title>Hexagonal Architecture Makes AI Assistants Extremely Efficient</title><link>https://danielbenner.de/articles/hexagonal-architecture-ai/</link><guid isPermaLink="true">https://danielbenner.de/articles/hexagonal-architecture-ai/</guid><description>The same structure that makes code maintainable makes it navigable for LLMs.</description><pubDate>Thu, 20 Nov 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The pitch for hexagonal architecture usually centers on testability and flexibility. Swap out your database. Mock your external services. Keep your domain pure.&lt;/p&gt;
&lt;p&gt;Those arguments are valid. But there’s an underappreciated reason to adopt this pattern: it makes AI coding assistants extremely efficient.&lt;/p&gt;
&lt;p&gt;I’ve been using Claude and Copilot across various codebases. They’re useful everywhere. But in &lt;a href=&quot;https://github.com/ayunis-core/ayunis-core&quot;&gt;Ayunis Core&lt;/a&gt;—a NestJS backend built on strict hexagonal principles—they’re noticeably faster and more accurate. Less fumbling. Fewer wrong guesses. More code that works on the first try.&lt;/p&gt;
&lt;p&gt;The structure removes ambiguity. AI spends less time searching for where things live and makes fewer incorrect assumptions about how components interact.&lt;/p&gt;
&lt;h2 id=&quot;hexagonal-architecture-in-60-seconds&quot;&gt;Hexagonal Architecture in 60 Seconds&lt;/h2&gt;
&lt;p&gt;Quick refresher for context.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Ports&lt;/strong&gt; are interfaces that define what your application needs from the outside world. A repository port defines persistence operations. An email port defines how to send messages.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Adapters&lt;/strong&gt; implement those interfaces. A PostgreSQL adapter fulfills the repository port. An SMTP adapter fulfills the email port.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The domain&lt;/strong&gt; sits at the center, containing pure business logic with no knowledge of infrastructure.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The dependency rule&lt;/strong&gt;: everything points inward. Infrastructure depends on application. Application depends on domain. Never the reverse.&lt;/p&gt;
&lt;pre class=&quot;mermaid&quot;&gt;flowchart TB
    P[Presenters] --&amp;gt; A[Application]
    A --&amp;gt; D[Domain]
    I[Infrastructure] --&amp;gt; A
    I -.-|implements| ports([Ports])
    A -.-|defines| ports
&lt;/pre&gt;
&lt;p&gt;That’s the essential mental model.&lt;/p&gt;
&lt;h2 id=&quot;the-ayunis-core-structure&quot;&gt;The Ayunis Core Structure&lt;/h2&gt;
&lt;p&gt;Here’s how this looks in practice. Ayunis Core is a NestJS backend with 1,106 TypeScript files across 47 modules. The structure:&lt;/p&gt;
&lt;figure&gt;&lt;pre tabindex=&quot;0&quot;&gt;&lt;code style=&quot;display:grid&quot;&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;src&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;├── &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;domain&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/           # &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;15&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; domain&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; modules&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;│   └── &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;agents&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/       # &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;Example&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; module&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;│       ├── &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;domain&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/           # &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;Pure&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; business&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; logic&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;│       ├── &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;application&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/      # &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;Use&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; cases&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; + &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;ports&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;│       │   ├── &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;ports&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;│       │   └── &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;use&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;cases&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;│       ├── &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;infrastructure&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/   # &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;Adapters&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;│       │   └── &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;persistence&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;│       └── &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;presenters&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/       # &lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;HTTP&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; layer&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;│           └── &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;http&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;├── &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;common&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/           # &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;Cross&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;cutting&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; infrastructure&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;└── &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;iam&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/              # &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;Auth&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; &amp;amp; &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;org&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; management&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Every domain module follows the same four-layer pattern. Every layer has a clear purpose. No exceptions.&lt;/p&gt;
&lt;p&gt;Some metrics that matter for AI context:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Average domain entity: 67 lines&lt;/li&gt;
&lt;li&gt;Average use case: 50-100 lines&lt;/li&gt;
&lt;li&gt;Each use case lives in its own directory with its command/query and tests&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Small, focused files. Predictable locations. Explicit contracts everywhere.&lt;/p&gt;
&lt;h2 id=&quot;why-ai-assistants-love-this-structure&quot;&gt;Why AI Assistants Love This Structure&lt;/h2&gt;
&lt;h3 id=&quot;the-file-tree-is-documentation&quot;&gt;The File Tree Is Documentation&lt;/h3&gt;
&lt;p&gt;AI can understand the entire system topology from the directory structure alone.&lt;/p&gt;
&lt;figure&gt;&lt;pre tabindex=&quot;0&quot;&gt;&lt;code style=&quot;display:grid&quot;&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;domain&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;agents&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;application&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;use&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;cases&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;create&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;agent&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;├── &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;create&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;agent&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;command&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;ts&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;├── &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;create&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;agent&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;use&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;case&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;ts&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;└── &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;create&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;agent&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;use&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;case&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;spec&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;ts&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;The path &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;domain&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;agents&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;application&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;use&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;cases&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;create&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;agent&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt; tells you exactly what that code does. No searching. No “let me look for where agents are created.” The structure is the documentation.&lt;/p&gt;
&lt;p&gt;Same principle applies everywhere:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;infrastructure&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;persistence&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;local&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt; → local database adapter&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;application&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;ports&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;agent&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;repository&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;ts&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt; → the contract for agent persistence&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;presenters&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;http&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;dto&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt; → HTTP request/response shapes&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When I ask Claude to add a feature, it reads the directory tree first. In Ayunis Core, that tree provides a complete map. In less structured codebases, the tree is noise—you need to read actual files to understand relationships.&lt;/p&gt;
&lt;h3 id=&quot;small-files-keep-context-lean&quot;&gt;Small Files Keep Context Lean&lt;/h3&gt;
&lt;p&gt;LLMs have context limits. Every token matters. Smaller files mean more relevant code fits in the context window.&lt;/p&gt;
&lt;p&gt;Compare:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Ayunis Core&lt;/strong&gt;: Domain entities average 67 lines. Use cases are single-purpose, 50-100 lines.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Typical NestJS&lt;/strong&gt;: Service files commonly hit 500+ lines, mixing CRUD operations, business logic, and infrastructure concerns.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When Claude reads &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;create&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;agent&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;use&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;case&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;ts&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt;, it gets everything relevant to creating an agent and nothing else. No scrolling past 400 lines of unrelated operations to find the logic that matters.&lt;/p&gt;
&lt;p&gt;This compounds. When AI can hold more relevant files in context, it reasons better about how they interact.&lt;/p&gt;
&lt;h3 id=&quot;abstract-ports-constrain-the-solution-space&quot;&gt;Abstract Ports Constrain the Solution Space&lt;/h3&gt;
&lt;p&gt;Here’s a port definition from Ayunis Core:&lt;/p&gt;
&lt;figure&gt;&lt;pre tabindex=&quot;0&quot;&gt;&lt;code style=&quot;display:grid&quot;&gt;&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt; abstract&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt; class&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; AgentRepository&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;  abstract&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt; create&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8;font-style:italic&quot;&gt;agent&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;Agent&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;): &lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;Promise&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;Agent&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;&amp;gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;  abstract&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt; findOne&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8;font-style:italic&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;UUID&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8;font-style:italic&quot;&gt;userId&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;UUID&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;): &lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;Promise&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;Agent&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; | &lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;null&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;&amp;gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;  abstract&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt; update&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8;font-style:italic&quot;&gt;agent&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;Agent&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;): &lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;Promise&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;Agent&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;&amp;gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;  abstract&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt; delete&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8;font-style:italic&quot;&gt;agentId&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;UUID&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8;font-style:italic&quot;&gt;userId&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;UUID&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;): &lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;Promise&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;&amp;gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;When AI sees this abstract class, it knows exactly what operations exist. It can’t accidentally use a method that doesn’t exist. It can’t hallucinate a &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;findByName&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt; that was never implemented.&lt;/p&gt;
&lt;p&gt;The payoff: AI suggests &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;agentRepository&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;create&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;agent&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt; and it’s guaranteed to work. The type system and the port contract together eliminate entire categories of errors.&lt;/p&gt;
&lt;p&gt;In codebases without clear abstractions, AI frequently suggests methods that don’t exist or calls with wrong signatures. It’s guessing based on common patterns. With explicit ports, there’s nothing to guess.&lt;/p&gt;
&lt;h3 id=&quot;strict-layering-means-predictable-dependencies&quot;&gt;Strict Layering Means Predictable Dependencies&lt;/h3&gt;
&lt;p&gt;The dependency rule isn’t just architectural preference—it’s a constraint AI can observe and follow.&lt;/p&gt;
&lt;p&gt;When adding a new feature, AI correctly:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Creates the domain entity in &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;domain&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;Adds the port in &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;application&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;ports&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;Creates the use case in &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;application&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;use&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;cases&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;Implements the adapter in &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;infrastructure&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;Wires everything in the module&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;It doesn’t try to import TypeORM decorators into a domain entity. It doesn’t call infrastructure code from a use case. The visible structure makes violations obvious.&lt;/p&gt;
&lt;p&gt;I’ve watched Claude implement features in Ayunis Core with zero guidance on the architecture. It infers the pattern from what exists and follows it. In less structured codebases, the same assistant needs explicit instructions about where to put things—and still makes mistakes.&lt;/p&gt;
&lt;h3 id=&quot;mappers-make-transformations-explicit&quot;&gt;Mappers Make Transformations Explicit&lt;/h3&gt;
&lt;p&gt;Every layer has its own data shapes. Domain entities aren’t the same as database records. DTOs aren’t the same as domain entities. The mappings between them are explicit:&lt;/p&gt;
&lt;figure&gt;&lt;pre tabindex=&quot;0&quot;&gt;&lt;code style=&quot;display:grid&quot;&gt;&lt;span&gt;&lt;span style=&quot;color:#EEF0F98F;font-style:italic&quot;&gt;// Domain ↔ Database&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; AgentMapper&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;  toDomain&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8;font-style:italic&quot;&gt;record&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;AgentRecord&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;): &lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;Agent&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F98F;font-style:italic&quot;&gt;    /* ... */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;  toRecord&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8;font-style:italic&quot;&gt;agent&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;Agent&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;): &lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;AgentRecord&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F98F;font-style:italic&quot;&gt;    /* ... */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F98F;font-style:italic&quot;&gt;// Domain ↔ HTTP&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; AgentDtoMapper&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;  toDto&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8;font-style:italic&quot;&gt;agent&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;Agent&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;): &lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;AgentResponseDto&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F98F;font-style:italic&quot;&gt;    /* ... */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;No magic. No auto-mapping. No implicit conversions that work until they don’t.&lt;/p&gt;
&lt;p&gt;AI sees exactly what shape data has at each layer. It doesn’t need to guess how a database record becomes a domain entity—the mapper is right there, explicitly named, in a predictable location.&lt;/p&gt;
&lt;h2 id=&quot;a-concrete-example&quot;&gt;A Concrete Example&lt;/h2&gt;
&lt;p&gt;Adding a feature to Ayunis Core with AI assistance.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Scenario&lt;/strong&gt;: Add “agent templates”—predefined agent configurations users can clone.&lt;/p&gt;
&lt;p&gt;I describe what I want. Claude:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Reads the &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;agents&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt; module structure&lt;/li&gt;
&lt;li&gt;Creates &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;agent&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;template&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;entity&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;ts&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt; in &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;domain&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;Creates &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;AgentTemplateRepository&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt; port in &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;application&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;ports&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;Creates &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;CreateAgentFromTemplate&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt; use case&lt;/li&gt;
&lt;li&gt;Implements &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;LocalAgentTemplateRepository&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt; in &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;infrastructure&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;persistence&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;local&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;Adds the controller endpoint in &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;presenters&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;http&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;Wires the providers in &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;agents&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;module&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;ts&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;It followed the pattern because the pattern was visible. I didn’t need a prompt explaining hexagonal architecture or a reference document describing where files go. The structure taught the structure.&lt;/p&gt;
&lt;h2 id=&quot;the-trade-offs&quot;&gt;The Trade-offs&lt;/h2&gt;
&lt;p&gt;Being honest: hexagonal architecture has costs.&lt;/p&gt;
&lt;h3 id=&quot;more-files-more-boilerplate&quot;&gt;More Files, More Boilerplate&lt;/h3&gt;
&lt;p&gt;Creating a new use case means at minimum:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The command/query class&lt;/li&gt;
&lt;li&gt;The use case class&lt;/li&gt;
&lt;li&gt;A test file&lt;/li&gt;
&lt;li&gt;Module provider registration&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Simple CRUD operations feel over-engineered. For a basic “update user email” feature, you’ll create several files where a single service method might suffice in a simpler architecture.&lt;/p&gt;
&lt;p&gt;The setup cost is real. Don’t hexagonal-ify a weekend project or a script you’ll run twice.&lt;/p&gt;
&lt;h3 id=&quot;learning-curve&quot;&gt;Learning Curve&lt;/h3&gt;
&lt;p&gt;Developers unfamiliar with the pattern need time. “Where does this go?” is a common question for the first few weeks.&lt;/p&gt;
&lt;p&gt;But here’s the thing: the structure eventually answers those questions. Once you internalize the four layers and the dependency rule, the answers become obvious. The explicit structure that feels like overhead early becomes self-documenting later.&lt;/p&gt;
&lt;h3 id=&quot;nestjs-friction&quot;&gt;NestJS Friction&lt;/h3&gt;
&lt;p&gt;NestJS wants services. Hexagonal architecture wants use cases. They can coexist, but there’s ceremony:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Abstract classes for ports (to support NestJS DI)&lt;/li&gt;
&lt;li&gt;Module providers mapping ports to adapters&lt;/li&gt;
&lt;li&gt;Some awkwardness around request-scoped dependencies&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It works. But it’s not what the framework was designed for.&lt;/p&gt;
&lt;h2 id=&quot;getting-started&quot;&gt;Getting Started&lt;/h2&gt;
&lt;p&gt;If you want to try this:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Start small.&lt;/strong&gt; Pick one module to restructure. Establish the four-layer pattern:&lt;/p&gt;
&lt;figure&gt;&lt;pre tabindex=&quot;0&quot;&gt;&lt;code style=&quot;display:grid&quot;&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;feature&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;├── &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;domain&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;│   └── &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;feature&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;entity&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;ts&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;├── &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;application&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;│   ├── &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;ports&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;│   │   └── &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;feature&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;repository&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;ts&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;│   └── &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;use&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;cases&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;│       └── &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;create&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;feature&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;├── &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;infrastructure&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;│   └── &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;persistence&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;│       └── &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;local&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;feature&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;repository&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;ts&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;└── &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;presenters&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;    └── &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;http&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;        └── &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;feature&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;controller&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;ts&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;&lt;strong&gt;Name things consistently:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;*.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;entity&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;ts&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt; — domain models&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;*.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;use&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;case&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;ts&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt; — application logic&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;*.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;port&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;ts&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt; or &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;*.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;repository&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;ts&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt; — abstractions&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;*.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;record&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;ts&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt; — database schemas (separate from entities)&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;*.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;mapper&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;ts&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt; — layer transformations&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Add ports for external dependencies.&lt;/strong&gt; Any time you’d inject a service that talks to the outside world—database, API, file system—create a port first. Implement the adapter separately.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Let it grow organically.&lt;/strong&gt; You don’t need to restructure your entire codebase. The pattern pays off module by module.&lt;/p&gt;
&lt;h2 id=&quot;the-argument&quot;&gt;The Argument&lt;/h2&gt;
&lt;p&gt;The traditional case for hexagonal architecture: testability, flexibility, separation of concerns. All valid.&lt;/p&gt;
&lt;p&gt;The newer case: it maximizes what you get from AI assistants.&lt;/p&gt;
&lt;p&gt;Structure your code so AI can navigate it efficiently—explicit contracts, small focused files, predictable locations, no magic—and you’ll spend less time correcting its guesses and more time shipping.&lt;/p&gt;</content:encoded><category>Architecture</category><category>AI</category></item><item><title>Parse, Decode, Validate: A Clean Pattern for Form Handling in Go</title><link>https://danielbenner.de/articles/form-validation-go/</link><guid isPermaLink="true">https://danielbenner.de/articles/form-validation-go/</guid><description>A simple three-step pattern that brings clarity and consistency to your web applications.</description><pubDate>Sun, 30 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Form validation in Go handlers tends to sprawl. One handler validates immediately, another waits until halfway through the business logic, a third barely validates at all. The inconsistency creates bugs and makes the codebase harder to reason about.&lt;/p&gt;
&lt;p&gt;The fix is a consistent three-step pattern: Parse → Decode → Validate. Put it at the beginning of every handler, every time.&lt;/p&gt;
&lt;h2 id=&quot;implementation-a-practical-walkthrough&quot;&gt;Implementation: A Practical Walkthrough&lt;/h2&gt;
&lt;h3 id=&quot;dtos-as-the-foundation&quot;&gt;DTOs as the Foundation&lt;/h3&gt;
&lt;p&gt;Define a struct for each form. Place it at the top of the handler file - it serves as documentation for what the endpoint accepts:&lt;/p&gt;
&lt;figure&gt;&lt;pre tabindex=&quot;0&quot;&gt;&lt;code style=&quot;display:grid&quot;&gt;&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; taskUpdateForm&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt; struct&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;    Title&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;  string&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; `schema:&quot;title&quot; validate:&quot;required,min=1,max=200&quot;`&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;    Status&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt; string&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; `schema:&quot;status&quot; validate:&quot;required,oneof=pending in_progress completed&quot;`&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;The &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;schema&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt; tags map form field names to struct fields. The &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;validate&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt; tags enforce constraints: title is required with length limits, status must be one of the allowed values.&lt;/p&gt;
&lt;h3 id=&quot;the-validation-workflow&quot;&gt;The Validation Workflow&lt;/h3&gt;
&lt;h4 id=&quot;step-1-parse&quot;&gt;Step 1: Parse&lt;/h4&gt;
&lt;figure&gt;&lt;pre tabindex=&quot;0&quot;&gt;&lt;code style=&quot;display:grid&quot;&gt;&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; err&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; :=&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; r&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;ParseForm&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(); &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;err&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; != &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;nil&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;    h&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Error&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;().&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Err&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;err&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;).&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Msg&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;error parsing form&quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;    http&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Error&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;w&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;Bad request&quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;http&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;StatusBadRequest&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;    return&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Extract form data from the request. Fail early if parsing fails.&lt;/p&gt;
&lt;h4 id=&quot;step-2-decode&quot;&gt;Step 2: Decode&lt;/h4&gt;
&lt;figure&gt;&lt;pre tabindex=&quot;0&quot;&gt;&lt;code style=&quot;display:grid&quot;&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;decoder&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; :=&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; schema&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;NewDecoder&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; form&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; taskUpdateForm&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; err&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; :=&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; decoder&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Decode&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;form&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;r&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;PostForm&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;); &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;err&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; != &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;nil&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;    h&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Error&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;().&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Err&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;err&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;).&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Msg&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;error decoding form&quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;    http&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Error&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;w&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;Bad request&quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;http&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;StatusBadRequest&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;    return&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/gorilla/schema&quot;&gt;Gorilla Schema&lt;/a&gt; maps form fields to struct fields, handling type conversions.&lt;/p&gt;
&lt;h4 id=&quot;step-3-validate&quot;&gt;Step 3: Validate&lt;/h4&gt;
&lt;figure&gt;&lt;pre tabindex=&quot;0&quot;&gt;&lt;code style=&quot;display:grid&quot;&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;validate&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; :=&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; validator&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;New&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; err&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; :=&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; validate&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Struct&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;form&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;); &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;err&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; != &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;nil&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;    h&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Error&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;().&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Err&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;err&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;).&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Msg&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;validation error&quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;    validationErrors&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; :=&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; err&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.(&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;validator&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;ValidationErrors&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;    errorMessages&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; :=&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt; make&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;([]&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;len&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;validationErrors&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;    for&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; _&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;e&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; :=&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt; range&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; validationErrors&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;        switch&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; e&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Tag&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;        case&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; &quot;required&quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;            errorMessages&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt; append&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;errorMessages&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;fmt&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Sprintf&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;%s is required&quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;e&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Field&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;()))&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;        case&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; &quot;oneof&quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;            errorMessages&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt; append&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;errorMessages&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;fmt&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Sprintf&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;%s must be one of: pending, in_progress, completed&quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;e&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Field&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;()))&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;        default&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;            errorMessages&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt; append&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;errorMessages&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;fmt&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Sprintf&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;Invalid value for %s&quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;e&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Field&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;()))&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;    http&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Error&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;w&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;strings&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Join&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;errorMessages&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;, &quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;), &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;http&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;StatusBadRequest&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;    return&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/go-playground/validator&quot;&gt;Go-Playground Validator&lt;/a&gt; runs the validation rules from your struct tags. Extract specific error details to return useful messages instead of generic failures.&lt;/p&gt;
&lt;h3 id=&quot;complete-example&quot;&gt;Complete Example&lt;/h3&gt;
&lt;figure&gt;&lt;pre tabindex=&quot;0&quot;&gt;&lt;code style=&quot;display:grid&quot;&gt;&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; taskUpdateForm&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt; struct&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;    Title&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;  string&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; `schema:&quot;title&quot; validate:&quot;required,min=1,max=200&quot;`&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;    Status&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt; string&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; `schema:&quot;status&quot; validate:&quot;required,oneof=pending in_progress completed&quot;`&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;func&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8;font-style:italic&quot;&gt;h &lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;Handler&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;HandleTaskUpdate&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8;font-style:italic&quot;&gt;w&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; http&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;ResponseWriter&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8;font-style:italic&quot;&gt;r&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;http&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;Request&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;    ctx&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; :=&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; r&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Context&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;    taskID&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; :=&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; chi&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;URLParam&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;r&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;taskID&quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F98F;font-style:italic&quot;&gt;    // parse&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; err&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; :=&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; r&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;ParseForm&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(); &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;err&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; != &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;nil&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;        h&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Error&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;().&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Err&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;err&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;).&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Msg&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;error parsing form&quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;        http&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Error&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;w&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;Bad request&quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;http&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;StatusBadRequest&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;        return&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F98F;font-style:italic&quot;&gt;    // decode&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;    var&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; form&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; taskUpdateForm&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; err&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; :=&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; h&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;decoder&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Decode&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;form&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;r&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;PostForm&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;); &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;err&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; != &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;nil&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;        h&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Error&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;().&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Err&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;err&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;).&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Msg&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;error decoding form&quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;        http&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Error&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;w&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;Bad request&quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;http&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;StatusBadRequest&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;        return&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F98F;font-style:italic&quot;&gt;    // validate&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; err&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; :=&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; h&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;validate&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Struct&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;form&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;); &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;err&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; != &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;nil&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;        validationErrors&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; :=&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; err&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.(&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;validator&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;ValidationErrors&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;        errorMessages&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; :=&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt; make&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;([]&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;len&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;validationErrors&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;        for&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; _&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;e&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; :=&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt; range&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; validationErrors&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;            switch&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; e&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Tag&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;            case&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; &quot;required&quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;                errorMessages&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt; append&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;errorMessages&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;fmt&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Sprintf&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;%s is required&quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;e&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Field&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;()))&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;            case&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; &quot;oneof&quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;                errorMessages&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt; append&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;errorMessages&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;fmt&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Sprintf&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;%s must be one of: pending, in_progress, completed&quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;e&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Field&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;()))&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;            default&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;                errorMessages&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt; append&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;errorMessages&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;fmt&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Sprintf&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;Invalid value for %s&quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;e&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Field&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;()))&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;            }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;        http&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Error&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;w&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;strings&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Join&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;errorMessages&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;, &quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;), &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;http&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;StatusBadRequest&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;        return&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F98F;font-style:italic&quot;&gt;    // business logic starts here&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;    task&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;err&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; :=&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; h&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;taskService&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Update&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;ctx&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;taskID&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;form&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;Title&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;form&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;Status&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; err&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; != &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;nil&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;        h&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Error&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;().&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Err&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;err&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;).&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Msg&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;error updating task&quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;        http&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Error&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;w&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;Internal server error&quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;http&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;StatusInternalServerError&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;        return&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;    render&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;JSON&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;w&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;r&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;task&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;The structure is always the same: extract context, parse, decode, validate, then business logic. Validation errors never leak into your core logic because invalid requests return early.&lt;/p&gt;
&lt;h2 id=&quot;why-this-works&quot;&gt;Why This Works&lt;/h2&gt;
&lt;p&gt;The pattern does a few things well:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Consistency&lt;/strong&gt; - Every handler follows the same structure. Easy to read, easy to modify.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Separation&lt;/strong&gt; - Validation happens before business logic. Invalid data can’t corrupt state.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Testability&lt;/strong&gt; - Clear boundaries make unit tests straightforward.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It’s not clever. That’s the point. Simple patterns survive contact with growing codebases.&lt;/p&gt;</content:encoded><category>Go</category><category>Web Development</category></item><item><title>A Four-Part Framework for AI Prompts</title><link>https://danielbenner.de/articles/prompt-engineering/</link><guid isPermaLink="true">https://danielbenner.de/articles/prompt-engineering/</guid><description>A simple structure for system prompts that actually works.</description><pubDate>Fri, 28 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;While building an in-product AI assistant, I went down the prompt engineering rabbit hole. Elaborate role-playing scenarios, complex formatting tricks, instructions so detailed they could pass as legal contracts. Our prompt grew to 2,000 words with nested bullet points three levels deep.&lt;/p&gt;
&lt;p&gt;The result: the same errors kept happening. Complexity wasn’t solving the fundamental issues.&lt;/p&gt;
&lt;p&gt;After stripping it back to first principles, a simple four-part structure emerged that worked better than everything we’d tried before:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Context about role and environment&lt;/li&gt;
&lt;li&gt;Tools and when to use them&lt;/li&gt;
&lt;li&gt;Common mistakes to avoid&lt;/li&gt;
&lt;li&gt;General guidelines and boundaries&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;That’s the entire framework. Here’s how each part works.&lt;/p&gt;
&lt;h2 id=&quot;part-1-context-and-role&quot;&gt;Part 1: Context and Role&lt;/h2&gt;
&lt;p&gt;Orient the AI to its environment and purpose. Answer: What platform is this? Who uses it? What’s the assistant’s role? What problems should it help with?&lt;/p&gt;
&lt;p&gt;What makes this effective:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Factual, not fictional.&lt;/strong&gt; Don’t ask the AI to “imagine” it’s something. State reality: “You are the X Assistant, designed to help users do Y.” No elaborate personas.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Relevant, not comprehensive.&lt;/strong&gt; Don’t explain every feature. Focus on what’s relevant to the assistant’s function.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Scope boundaries.&lt;/strong&gt; By stating what the assistant helps with, you implicitly define what’s outside scope.&lt;/p&gt;
&lt;p&gt;Keep this section short—a paragraph of context, 3-4 sentences on supported use cases. Too much detail here is counterproductive. Domain knowledge belongs in tools or a knowledge base, not the system prompt.&lt;/p&gt;
&lt;h2 id=&quot;part-2-tools-and-when-to-use-them&quot;&gt;Part 2: Tools and When to Use Them&lt;/h2&gt;
&lt;p&gt;Most approaches either list tools without context or bury usage guidelines in complex instructions. The key distinction:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Tool definition:&lt;/strong&gt; What it does (functionality)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;System prompt:&lt;/strong&gt; When to use it (contextual guidance)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Example tool definition:&lt;/p&gt;
&lt;figure&gt;&lt;pre tabindex=&quot;0&quot;&gt;&lt;code style=&quot;display:grid&quot;&gt;&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;Receives resource data as csv file. The output of this tool is the filepath and the csv header.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Same tool in the system prompt:&lt;/p&gt;
&lt;figure&gt;&lt;pre tabindex=&quot;0&quot;&gt;&lt;code style=&quot;display:grid&quot;&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; Use when a user asks about a specific resource to find its ID&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; Use to help users find available resources matching certain criteria&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; Use to verify if a resource exists before attempting operations on it&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;The first explains functionality. The second ties it to user scenarios.&lt;/p&gt;
&lt;p&gt;Without this contextual guidance, the assistant often chose inappropriate tools or failed to use tools when helpful. For example, when asked “Can I book the conference room next Tuesday?”, the assistant would try to create a booking without first checking availability—a clear case for &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;GetResourceData&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt; followed by &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;GetAvailability&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;Adding contextual usage guidance dramatically improved tool selection. Describe tools not just by what they do, but by the user needs they address.&lt;/p&gt;
&lt;h2 id=&quot;part-3-common-mistakes-to-avoid&quot;&gt;Part 3: Common Mistakes to Avoid&lt;/h2&gt;
&lt;p&gt;A straightforward list of behaviors you’ve observed the assistant doing wrong. Format:&lt;/p&gt;
&lt;figure&gt;&lt;pre tabindex=&quot;0&quot;&gt;&lt;code style=&quot;display:grid&quot;&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; DON&apos;T [specific behavior]. INSTEAD [correct approach].&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Examples:&lt;/p&gt;
&lt;figure&gt;&lt;pre tabindex=&quot;0&quot;&gt;&lt;code style=&quot;display:grid&quot;&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; DON&apos;T create a booking without checking availability. INSTEAD, use GetAvailability first.&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; DON&apos;T provide generic responses to error reports. INSTEAD, ask for specific error messages.&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; DON&apos;T guess resource IDs or permissions. INSTEAD, use lookup tools to verify.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Why this works:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Direct.&lt;/strong&gt; No ambiguity in “DON’T do X.”&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Specific.&lt;/strong&gt; Each item addresses observed behavior, not hypotheticals.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Actionable.&lt;/strong&gt; Pairing “don’t” with “instead” redirects toward correct approaches.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Issues we struggled to fix through complex prompt adjustments disappeared when explicitly called out in this format. The assistant was guessing at information it didn’t have—we added “DON’T guess how features work, always check the help center first” and the problem vanished.&lt;/p&gt;
&lt;p&gt;This section grows organically from real usage. Start with 3-4 items addressing the most pressing issues. Ours has grown to about ten.&lt;/p&gt;
&lt;h2 id=&quot;part-4-guidelines-and-boundaries&quot;&gt;Part 4: Guidelines and Boundaries&lt;/h2&gt;
&lt;p&gt;How the assistant should behave across all interactions: tone, boundaries, handling limitations.&lt;/p&gt;
&lt;figure&gt;&lt;pre tabindex=&quot;0&quot;&gt;&lt;code style=&quot;display:grid&quot;&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; Address users formally&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; When uncertain, state limitations rather than guessing&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; If a request falls outside capabilities, explain and offer to connect with support&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; Focus on helpful and accurate over verbose&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;The critical element: how to handle limitations. Every AI has boundaries. Without explicit guidance, assistants make up answers or overstep their role.&lt;/p&gt;
&lt;p&gt;Example: our assistant receives questions about municipal regulations that vary by location and change over time. Rather than guess (and provide potentially incorrect information), guidelines instruct it to acknowledge when a question requires specialized knowledge and suggest appropriate resources.&lt;/p&gt;
&lt;p&gt;This section doesn’t need to be long—5-6 bullet points covering core principles for behavior across all scenarios.&lt;/p&gt;
&lt;h2 id=&quot;why-this-works&quot;&gt;Why This Works&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Separation of concerns.&lt;/strong&gt; Each section addresses one aspect: context, capabilities, pitfalls, behavior. Adding a new tool only touches the tools section. A new mistake only touches the mistakes section.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Practical over theoretical.&lt;/strong&gt; The “common mistakes” section evolves from observed failures, not hypothetical edge cases.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Maintainable.&lt;/strong&gt; Clear structure makes it obvious where new information belongs.&lt;/p&gt;
&lt;p&gt;Results after implementing this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;30% more user requests completed without human intervention&lt;/li&gt;
&lt;li&gt;60% reduction in incorrect information&lt;/li&gt;
&lt;li&gt;25% increase in user satisfaction&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The simplicity also makes the prompt accessible to the whole team. Customer support notices a problem? They can suggest a specific addition to the mistakes section without needing to understand “prompt engineering.”&lt;/p&gt;
&lt;h2 id=&quot;the-takeaway&quot;&gt;The Takeaway&lt;/h2&gt;
&lt;p&gt;Effective prompting isn’t about complexity—it’s about clarity and structure. Four sections: context, tools, mistakes, guidelines. Update based on observed failures. Keep each section focused on its specific purpose.&lt;/p&gt;
&lt;p&gt;The best prompt isn’t the one that anticipates every scenario. It’s the one that provides clear guidance and evolves easily as needs change.&lt;/p&gt;</content:encoded><category>LLMs</category><category>AI</category></item><item><title>Role-Based Access Control in Go with Chi</title><link>https://danielbenner.de/articles/rbac/</link><guid isPermaLink="true">https://danielbenner.de/articles/rbac/</guid><description>A lightweight RBAC implementation with no external dependencies.</description><pubDate>Fri, 21 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Most RBAC implementations are overcomplicated. Hierarchical roles, conditional permissions, attribute-based controls—useful in some contexts, but overkill for many applications.&lt;/p&gt;
&lt;p&gt;This implementation is minimal: roles and permissions as enums, a map between them, and middleware that checks permissions. No external dependencies. The entire system fits in one file.&lt;/p&gt;
&lt;h2 id=&quot;the-rbac-package&quot;&gt;The RBAC Package&lt;/h2&gt;
&lt;h3 id=&quot;roles-and-permissions&quot;&gt;Roles and Permissions&lt;/h3&gt;
&lt;p&gt;Define roles and permissions as typed string constants:&lt;/p&gt;
&lt;figure&gt;&lt;pre tabindex=&quot;0&quot;&gt;&lt;code style=&quot;display:grid&quot;&gt;&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;package&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; rbac&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; Role&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt; string&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;func&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8;font-style:italic&quot;&gt;r &lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;Role&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;String&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;() &lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;	return&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt; string&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;r&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;	RoleAdmin&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;   Role&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; &quot;admin&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;	RoleManager&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; Role&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; &quot;manager&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; Permission&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt; string&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;func&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8;font-style:italic&quot;&gt;p &lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;Permission&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;String&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;() &lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;	return&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt; string&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;	PermissionManageAccess&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;      Permission&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; &quot;manage_access&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;	PermissionManageReleaseNote&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; Permission&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; &quot;manage_release_note&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;h3 id=&quot;role-permission-mapping&quot;&gt;Role-Permission Mapping&lt;/h3&gt;
&lt;figure&gt;&lt;pre tabindex=&quot;0&quot;&gt;&lt;code style=&quot;display:grid&quot;&gt;&lt;span&gt;&lt;span style=&quot;color:#EEF0F98F;font-style:italic&quot;&gt;// rolePermissions maps roles to their granted permissions&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; rolePermissions&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt; map&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;Role&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;][]&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;Permission&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;	RoleAdmin&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;		PermissionManageAccess&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;		PermissionManageReleaseNote&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;	},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;	RoleManager&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;		PermissionManageReleaseNote&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;	},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F98F;font-style:italic&quot;&gt;// HasPermission checks if a role has a specific permission&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;func&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8;font-style:italic&quot;&gt;r &lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;Role&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;HasPermission&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8;font-style:italic&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; Permission&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;bool&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;    permissions&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;exists&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; :=&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; rolePermissions&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;r&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; !&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;exists&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; false&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;    for&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; _&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;permission&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; :=&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt; range&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; permissions&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;        if&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; permission&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; == &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;            return&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; true&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; false&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;h3 id=&quot;the-middleware&quot;&gt;The Middleware&lt;/h3&gt;
&lt;figure&gt;&lt;pre tabindex=&quot;0&quot;&gt;&lt;code style=&quot;display:grid&quot;&gt;&lt;span&gt;&lt;span style=&quot;color:#EEF0F98F;font-style:italic&quot;&gt;// RequirePermission creates middleware that checks if the user has the required permission&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;func&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt; RequirePermission&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8;font-style:italic&quot;&gt;permission&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; Permission&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;func&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;http&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;Handler&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;http&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;Handler&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt; func&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8;font-style:italic&quot;&gt;next&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; http&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;Handler&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;http&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;Handler&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; http&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;HandlerFunc&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;func&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8;font-style:italic&quot;&gt;w&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; http&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;ResponseWriter&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8;font-style:italic&quot;&gt;r&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;http&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;Request&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F98F;font-style:italic&quot;&gt;            // Get user role from context (set by auth middleware)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;            userRole&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;ok&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; :=&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; r&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Context&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;().&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Value&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(„&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;userRole&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;“).(&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;Role&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;            if&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; !&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;ok&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;                http&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Error&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;w&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, „&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;Unauthorized&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;“, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;http&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;StatusUnauthorized&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;                return&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;            }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F98F;font-style:italic&quot;&gt;            // Check if the role has the required permission&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;            if&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; !&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;userRole&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;HasPermission&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;permission&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;                http&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Error&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;w&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, „&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;Forbidden&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;“, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;http&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;StatusForbidden&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;                return&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;            }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F98F;font-style:italic&quot;&gt;            // User has permission, proceed to the next handler&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;            next&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;ServeHTTP&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;w&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;r&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;        })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;The middleware extracts the user’s role from the request context (set by auth middleware) and checks if that role has the required permission.&lt;/p&gt;
&lt;h2 id=&quot;using-it-with-chi&quot;&gt;Using It with Chi&lt;/h2&gt;
&lt;h3 id=&quot;auth-middleware-integration&quot;&gt;Auth Middleware Integration&lt;/h3&gt;
&lt;p&gt;Your auth middleware needs to add the user’s role to the request context:&lt;/p&gt;
&lt;figure&gt;&lt;pre tabindex=&quot;0&quot;&gt;&lt;code style=&quot;display:grid&quot;&gt;&lt;span&gt;&lt;span style=&quot;color:#EEF0F98F;font-style:italic&quot;&gt;// AuthMiddleware authenticates the user and adds their info to the context&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;func&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt; AuthMiddleware&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8;font-style:italic&quot;&gt;next&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; http&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;Handler&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;http&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;Handler&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; http&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;HandlerFunc&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;func&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8;font-style:italic&quot;&gt;w&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; http&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;ResponseWriter&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8;font-style:italic&quot;&gt;r&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;http&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;Request&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F98F;font-style:italic&quot;&gt;        // Get token from request (e.g., from cookie or Authorization header)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;        token&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; :=&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt; getTokenFromRequest&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;r&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F98F;font-style:italic&quot;&gt;        // Validate token and get user info&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;        user&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;err&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; :=&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt; validateToken&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;token&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;        if&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; err&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; != &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;nil&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;            http&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Error&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;w&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, „&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;Unauthorized&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;“, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;http&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;StatusUnauthorized&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;            return&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F98F;font-style:italic&quot;&gt;        // Create a new context with the user role&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;        ctx&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; :=&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; context&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;WithValue&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;r&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Context&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(), „&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;userRole&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;“, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;user&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;Role&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F98F;font-style:italic&quot;&gt;        // Call the next handler with the updated context&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;        next&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;ServeHTTP&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;w&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;r&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;WithContext&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;ctx&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;    })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;h3 id=&quot;protecting-routes&quot;&gt;Protecting Routes&lt;/h3&gt;
&lt;figure&gt;&lt;pre tabindex=&quot;0&quot;&gt;&lt;code style=&quot;display:grid&quot;&gt;&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;func&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt; SetupRoutes&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8;font-style:italic&quot;&gt;r&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;chi&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;Mux&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F98F;font-style:italic&quot;&gt;    // Apply authentication middleware to all routes&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;    r&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Use&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;AuthMiddleware&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F98F;font-style:italic&quot;&gt;    // Public routes accessible to all authenticated users&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;    r&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Get&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(„&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;release&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;notes&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;“, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;handlers&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;ListReleaseNotes&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;    r&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Get&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(„&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;profile&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;“, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;handlers&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;ViewProfile&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F98F;font-style:italic&quot;&gt;    // Routes that require specific permissions&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;    r&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Group&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;func&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8;font-style:italic&quot;&gt;r&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; chi&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;Router&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F98F;font-style:italic&quot;&gt;        // Only users with user management permission can access these routes&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;        r&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Use&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;rbac&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;RequirePermission&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;rbac&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;PermissionUserManagement&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;        r&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Get&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(„&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;users&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;“, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;handlers&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;ListUsers&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;        r&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Post&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(„&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;users&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;“, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;handlers&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;CreateUser&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;        r&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Put&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(„&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;users&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;}“, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;handlers&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;UpdateUser&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;        r&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Delete&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(„&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;users&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;}“, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;handlers&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;DeleteUser&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;    })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Chi’s router groups let you apply RBAC middleware to entire groups of routes. The permission check happens in middleware, so handlers focus solely on business logic.&lt;/p&gt;
&lt;h3 id=&quot;request-flow&quot;&gt;Request Flow&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Request hits &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;AuthMiddleware&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt; → validates token, adds role to context&lt;/li&gt;
&lt;li&gt;Request hits &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;RequirePermission&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt; middleware → checks role has permission&lt;/li&gt;
&lt;li&gt;If permitted, request reaches handler&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Clean separation: authentication (who are you?), authorization (what can you do?), business logic (how do we do it?).&lt;/p&gt;
&lt;h2 id=&quot;why-this-works&quot;&gt;Why This Works&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Simple.&lt;/strong&gt; A few dozen lines, no dependencies.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Explicit.&lt;/strong&gt; Route definitions show required permissions at a glance.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Extensible.&lt;/strong&gt; New roles/permissions = update constants and map. Middleware unchanged.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The pattern works with any Go router. Roles, permissions, and middleware-based checks are the core concepts—the specific router doesn’t matter.&lt;/p&gt;</content:encoded><category>Go</category><category>Web Development</category></item><item><title>Rolling Your Own Authentication in Go</title><link>https://danielbenner.de/articles/roll-your-own-auth/</link><guid isPermaLink="true">https://danielbenner.de/articles/roll-your-own-auth/</guid><description>Authentication isn&apos;t magic. Here&apos;s how to build it from scratch.</description><pubDate>Thu, 13 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;“Don’t roll your own auth.”&lt;/p&gt;
&lt;p&gt;The advice exists for good reason—authentication mistakes can be catastrophic. But the warning has morphed into something else: a belief that authentication is inherently complex and best left to third-party services.&lt;/p&gt;
&lt;p&gt;It isn’t. Most auth libraries require adapting your entire application to their patterns. For standard username/password authentication with sessions, that’s overkill.&lt;/p&gt;
&lt;p&gt;The core components are straightforward when broken down. Two resources were particularly useful:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://lucia-auth.com&quot;&gt;The Lucia Auth Website&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://thecopenhagenbook.com&quot;&gt;The Copenhagen Book&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This isn’t about reinventing cryptography. It’s about correctly implementing well-established patterns.&lt;/p&gt;
&lt;h2 id=&quot;core-components&quot;&gt;Core Components&lt;/h2&gt;
&lt;p&gt;An authentication system answers one question: “Are you who you claim to be?” Here’s what that requires:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Password Storage&lt;/strong&gt; - Bcrypt hashing. Store the hash, never the password.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Secure Random Generation&lt;/strong&gt; - Unpredictable tokens for sessions and CSRF.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Session Management&lt;/strong&gt; - Remember authenticated users across requests.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rate Limiting&lt;/strong&gt; - Prevent brute force attacks.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CSRF Protection&lt;/strong&gt; - Ensure requests originate from your application.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Auth Handlers&lt;/strong&gt; - Registration and login endpoints.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Auth Middleware&lt;/strong&gt; - Gate access to protected routes.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Each component has a single responsibility. No monolithic auth service to adapt your application around.&lt;/p&gt;
&lt;h2 id=&quot;password-storage&quot;&gt;Password Storage&lt;/h2&gt;
&lt;p&gt;Never store passwords in plain text. Use bcrypt.&lt;/p&gt;
&lt;p&gt;Unlike general-purpose hashing (SHA-256), bcrypt is intentionally slow. This makes brute-force attacks computationally expensive. The cost factor is adjustable—increase it as hardware gets faster.&lt;/p&gt;
&lt;figure&gt;&lt;pre tabindex=&quot;0&quot;&gt;&lt;code style=&quot;display:grid&quot;&gt;&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;package&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; password&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;	&quot;golang.org/x/crypto/bcrypt&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;func&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt; HashPassword&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8;font-style:italic&quot;&gt;password&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt; string&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;) (&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;	passwordBytes&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; :=&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; []&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;byte&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;password&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;	hashed&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;err&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; :=&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; bcrypt&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;GenerateFromPassword&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;passwordBytes&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;bcrypt&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;MinCost&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;	if&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; err&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; != &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;nil&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;		return&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; &quot;&quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;err&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;	return&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt; string&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;hashed&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;), &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;nil&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;func&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt; DoPasswordsMatch&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8;font-style:italic&quot;&gt;hashedPassword&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8;font-style:italic&quot;&gt;test&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt; string&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;bool&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;	if&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; err&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; :=&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; bcrypt&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;CompareHashAndPassword&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;([]&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;byte&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;hashedPassword&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;), []&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;byte&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;test&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)); &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;err&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; != &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;nil&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;		return&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; false&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;	return&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; true&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;&lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;HashPassword&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt; creates the hash at registration. &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;DoPasswordsMatch&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt; verifies it at login.&lt;/p&gt;
&lt;h2 id=&quot;secure-random-generation&quot;&gt;Secure Random Generation&lt;/h2&gt;
&lt;p&gt;Session tokens need to be unpredictable. Use &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;crypto&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;rand&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt;, not &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;math&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;rand&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt;. Standard random functions are predictable if you know the seed—cryptographically secure generators aren’t.&lt;/p&gt;
&lt;figure&gt;&lt;pre tabindex=&quot;0&quot;&gt;&lt;code style=&quot;display:grid&quot;&gt;&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;package&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; random&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;	&quot;crypto/rand&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;	&quot;crypto/sha256&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;	&quot;encoding/base32&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;	&quot;encoding/hex&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;func&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt; CreateRandomToken&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;() &lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;	bytes&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; :=&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt; make&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;([]&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;byte&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;15&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;	rand&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Read&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;bytes&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;	token&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; :=&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; base32&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;StdEncoding&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;EncodeToString&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;bytes&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;	return&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; token&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;func&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt; EncodeToken&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8;font-style:italic&quot;&gt;token&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt; string&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;	byteId&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; :=&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; sha256&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Sum256&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;([]&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;byte&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;token&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;	return&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; hex&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;EncodeToString&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;byteId&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;[:])&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;&lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;CreateRandomToken&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt; generates random bytes and base32-encodes them. &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;EncodeToken&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt; hashes the token before storage—if someone gets your database, they can’t use the stored session IDs directly.&lt;/p&gt;
&lt;h2 id=&quot;session-management&quot;&gt;Session Management&lt;/h2&gt;
&lt;p&gt;HTTP is stateless. Sessions maintain state across requests by associating a token with user data on the server.&lt;/p&gt;
&lt;p&gt;The flow:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;User logs in successfully&lt;/li&gt;
&lt;li&gt;Server generates a random session token&lt;/li&gt;
&lt;li&gt;Server stores user info associated with that token&lt;/li&gt;
&lt;li&gt;Server sends the token as a cookie&lt;/li&gt;
&lt;li&gt;Client includes the token in subsequent requests&lt;/li&gt;
&lt;li&gt;Server validates and retrieves the associated user&lt;/li&gt;
&lt;/ol&gt;
&lt;figure&gt;&lt;pre tabindex=&quot;0&quot;&gt;&lt;code style=&quot;display:grid&quot;&gt;&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;package&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; session&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;	&quot;your/project/random&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;func&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8;font-style:italic&quot;&gt;s &lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;service&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;CreateToken&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;() &lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F98F;font-style:italic&quot;&gt;    // create a random token through our random package&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;	token&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; :=&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; random&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;CreateRandomToken&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;	return&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; token&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;func&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8;font-style:italic&quot;&gt;s &lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;service&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;CreateSession&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8;font-style:italic&quot;&gt;token&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt; string&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8;font-style:italic&quot;&gt;userId&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; uuid&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;UUID&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F98F;font-style:italic&quot;&gt;    // create a new session record from a random token and user id&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;	sessionId&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; :=&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt; hashToken&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;token&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;	expiresAt&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; :=&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt; calcNextExpiry&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;	session&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; :=&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; Session&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;ExternalID&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;sessionId&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;ExpiresAt&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;expiresAt&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;UserID&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;userId&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;	return&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; s&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;repository&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Save&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;session&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;func&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8;font-style:italic&quot;&gt;s &lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;service&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;ValidateSession&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8;font-style:italic&quot;&gt;token&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt; string&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;) (&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;Session&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F98F;font-style:italic&quot;&gt;    // returns a session from a token, if there is any&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F98F;font-style:italic&quot;&gt;    // and resets the expiry if successful&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;	sessionId&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; :=&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt; hashToken&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;token&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;	session&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;err&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; :=&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; s&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;repository&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;FindByExternalId&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;sessionId&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;	if&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; err&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; != &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;nil&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;		return&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; nil&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;err&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;	if&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt; sessionIsExpired&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;session&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;		if&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; err&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; :=&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; s&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;repository&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Delete&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;session&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;ID&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;); &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;err&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; != &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;nil&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;			return&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; nil&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;err&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;		}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;		return&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; nil&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;s&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;repository&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;db&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;ErrRecordNotFound&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;	session&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;ExpiresAt&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt; calcNextExpiry&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;	if&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; err&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; :=&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; s&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;repository&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Save&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;session&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;); &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;err&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; != &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;nil&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;		return&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; nil&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;err&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;	return&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; session&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;nil&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Key details:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Hash the token before storing it. If your database leaks, attackers can’t use the session IDs directly.&lt;/li&gt;
&lt;li&gt;Sessions expire. Reset expiry on each request so active users stay logged in.&lt;/li&gt;
&lt;li&gt;Storage can be database, Redis, or in-memory (though in-memory won’t survive restarts).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;rate-limiting&quot;&gt;Rate Limiting&lt;/h2&gt;
&lt;p&gt;Without rate limiting, attackers can try thousands of passwords per minute. Rate limiting caps attempts per user/IP within a timeframe.&lt;/p&gt;
&lt;p&gt;This implementation uses a token bucket algorithm—each identifier gets a bucket that refills over time. Each attempt consumes from the bucket. Empty bucket means wait.&lt;/p&gt;
&lt;figure&gt;&lt;pre tabindex=&quot;0&quot;&gt;&lt;code style=&quot;display:grid&quot;&gt;&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;package&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; ratelimit&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;	&quot;errors&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;	&quot;math&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;	&quot;sync&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;	&quot;time&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; Bucket&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt; struct&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;	count&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;      float64&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;	refilledAt&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt; int64&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;	mu&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;         sync&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;Mutex&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;func&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8;font-style:italic&quot;&gt;b &lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;Bucket&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;consume&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8;font-style:italic&quot;&gt;cost&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8;font-style:italic&quot;&gt;maxValue&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt; float64&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8;font-style:italic&quot;&gt;refillIntervalMillis&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt; int64&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;bool&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F98F;font-style:italic&quot;&gt;	// lock the bucket&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;	b&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;mu&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Lock&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;	defer&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; b&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;mu&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Unlock&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F98F;font-style:italic&quot;&gt;	// calculate refill until now &amp;amp; refill bucket&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;	now&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; :=&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; time&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Now&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;().&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;UTC&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;().&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;UnixMilli&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;	refill&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; :=&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt; float64&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;now&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;b&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;refilledAt&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt; float64&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;refillIntervalMillis&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; maxValue&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;	b&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;count&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; math&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Min&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;b&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;count&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;refill&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;maxValue&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;	b&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;refilledAt&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; now&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F98F;font-style:italic&quot;&gt;	// deduct cost if possible&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;	if&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; b&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;count&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; &amp;gt;= &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;cost&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;		b&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;count&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; -=&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; cost&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;		return&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; true&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;	return&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; false&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Each email address and IP address gets its own bucket. Check capacity by attempting to deduct:&lt;/p&gt;
&lt;figure&gt;&lt;pre tabindex=&quot;0&quot;&gt;&lt;code style=&quot;display:grid&quot;&gt;&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; TokenBucketRateLimit&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt; struct&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;	refillIntervalMillis&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt; int64&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;	maxValue&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;             float64&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;	buckets&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;              map&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;Bucket&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;func&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8;font-style:italic&quot;&gt;tbr &lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;TokenBucketRateLimit&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Deduct&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8;font-style:italic&quot;&gt;bucketLabel&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt; string&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8;font-style:italic&quot;&gt;cost&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt; float64&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;	now&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; :=&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; time&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Now&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;().&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;UTC&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;().&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;UnixMilli&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F98F;font-style:italic&quot;&gt;	// check if the label already has a bucket&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;	bucket&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;ok&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; :=&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; tbr&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;buckets&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;bucketLabel&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;	if&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; !&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;ok&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F98F;font-style:italic&quot;&gt;        // no bucket yet -&amp;gt; create one, then deduct again&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;		bucket&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; :=&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; Bucket&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;count&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;tbr&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;maxValue&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;refilledAt&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;now&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;		tbr&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;buckets&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;bucketLabel&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt; &amp;amp;&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;bucket&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;		return&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; tbr&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Deduct&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;bucketLabel&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;cost&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F98F;font-style:italic&quot;&gt;    // bucket exists -&amp;gt; consume&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;	if&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; ok&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; :=&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; bucket&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;consume&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;cost&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;tbr&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;maxValue&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;tbr&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;refillIntervalMillis&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;); !&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;ok&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;		return&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; errors&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;New&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;rate limit reached&quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;	return&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; nil&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;The bucket approach allows brief bursts (mistyped passwords) while blocking sustained attacks.&lt;/p&gt;
&lt;h2 id=&quot;csrf-protection&quot;&gt;CSRF Protection&lt;/h2&gt;
&lt;p&gt;CSRF attacks trick authenticated users into submitting requests they didn’t intend. Your browser automatically includes session cookies, so a malicious site can submit forms to your app on behalf of logged-in users.&lt;/p&gt;
&lt;p&gt;The common fix is CSRF tokens in forms. A simpler alternative: check the &lt;span&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;Origin&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt; header.&lt;/p&gt;
&lt;figure&gt;&lt;pre tabindex=&quot;0&quot;&gt;&lt;code style=&quot;display:grid&quot;&gt;&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; request&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;Method&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; != &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;GET&quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;    originHeader&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; :=&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; r&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;Header&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Get&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;Origin&quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; originHeader&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; != &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;https://yourapp.com&quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;        w&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;WriteHeader&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;403&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;        return&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;This works when state-changing operations are restricted to POST/PUT/PATCH. For more complex scenarios, use proper CSRF tokens.&lt;/p&gt;
&lt;h2 id=&quot;registration-and-login-handlers&quot;&gt;Registration and Login Handlers&lt;/h2&gt;
&lt;p&gt;These handlers bring all the components together.&lt;/p&gt;
&lt;figure&gt;&lt;pre tabindex=&quot;0&quot;&gt;&lt;code style=&quot;display:grid&quot;&gt;&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;package&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; handler&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F98F;font-style:italic&quot;&gt;// !! logging, error handling and validation&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F98F;font-style:italic&quot;&gt;// left out for brevity and focus&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; registerForm&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt; struct&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;	Email&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;           string&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;	Password&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;        string&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;	ConfirmPassword&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt; string&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F98F;font-style:italic&quot;&gt;// allow 5 requests per minute&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; registerRateLimiter&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; ratelimit&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;New&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;60&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;func&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8;font-style:italic&quot;&gt;h &lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;Handler&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;HandleRegister&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8;font-style:italic&quot;&gt;w&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; http&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;ResponseWriter&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8;font-style:italic&quot;&gt;r&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;http&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;Request&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;	userService&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; :=&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; user&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;NewService&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;user&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;NewRepository&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;h&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;DB&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;	sessionService&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; :=&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; session&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;NewService&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;session&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;NewRepository&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;h&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;DB&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;	if&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; err&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; :=&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; r&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;ParseForm&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(); &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;err&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; != &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;nil&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;		http&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Error&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;w&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;Error parsing form&quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;http&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;StatusBadRequest&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;		return&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;	req&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; :=&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; registerForm&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;		Email&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;:           &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;r&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;FormValue&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;email&quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;		Password&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;:        &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;r&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;FormValue&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;password&quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;		ConfirmPassword&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;r&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;FormValue&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;confirm&quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F98F;font-style:italic&quot;&gt;    // check rate limit&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;	if&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; err&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; :=&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; registerRateLimiter&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Deduct&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;req&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;Email&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;); &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;err&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; != &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;nil&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;		http&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Error&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;w&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;Too many requests. Please try again later.&quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;http&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;StatusTooManyRequests&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;		return&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;	if&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; err&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; :=&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; registerRateLimiter&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Deduct&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;req&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;OrgName&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;); &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;err&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; != &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;nil&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;		http&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Error&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;w&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;Too many requests. Please try again later.&quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;http&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;StatusTooManyRequests&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;		return&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F98F;font-style:italic&quot;&gt;    // check password guidelines&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;	if&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; err&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; :=&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; password&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;IsValidPassword&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;req&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;Password&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;); &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;err&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; != &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;nil&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;		http&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Error&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;w&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;err&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Error&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(), &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;http&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;StatusBadRequest&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;		return&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F98F;font-style:italic&quot;&gt;    // check if passwords match&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;	if&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; req&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;Password&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; != &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;req&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;ConfirmPassword&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;		http&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Error&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;w&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;Passwords do not match&quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;http&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;StatusBadRequest&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;		return&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F98F;font-style:italic&quot;&gt;	// check if user already exists&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;	existing&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;err&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; :=&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; userService&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;GetByEmail&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;req&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;Email&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;	if&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; err&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; != &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;nil&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; &amp;amp;&amp;amp; !&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;errors&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Is&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;err&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;h&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;DB&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;ErrRecordNotFound&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;		http&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Error&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;w&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;Error creating user&quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;http&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;StatusBadRequest&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;		return&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;	if&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; existing&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; != &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;nil&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;		http&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Error&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;w&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;User already exists&quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;http&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;StatusBadRequest&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;		return&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F98F;font-style:italic&quot;&gt;	// create user&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;    pwHash&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; :=&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; password&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;HashPassword&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;req&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;Password&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;	user&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;err&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; :=&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; userService&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Create&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;req&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;Email&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;pwHash&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;	if&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; err&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; != &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;nil&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;		http&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Error&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;w&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;Error creating user&quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;http&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;StatusInternalServerError&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;		return&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F98F;font-style:italic&quot;&gt;    // create session&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;	token&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; :=&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; sessionService&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;CreateToken&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;	if&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; err&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; :=&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; sessionService&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Create&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;token&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;user&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;ID&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;); &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;err&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; != &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;nil&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;		http&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Error&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;w&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;Error creating session&quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;http&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;StatusInternalServerError&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;		return&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;	w&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;WriteHeader&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;http&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;StatusCreated&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Registration validates input, checks for existing users, hashes the password, creates the user, and starts a session.&lt;/p&gt;
&lt;p&gt;Login handler:&lt;/p&gt;
&lt;figure&gt;&lt;pre tabindex=&quot;0&quot;&gt;&lt;code style=&quot;display:grid&quot;&gt;&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;package&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; handler&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F98F;font-style:italic&quot;&gt;// !! logging, error handling and validation&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F98F;font-style:italic&quot;&gt;// left out for brevity and focus&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; loginForm&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt; struct&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;	Email&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;    string&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;	Password&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt; string&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; loginRateLimiter&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; ratelimit&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;New&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;60&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;func&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8;font-style:italic&quot;&gt;h &lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;Handler&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;HandleLogin&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8;font-style:italic&quot;&gt;w&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; http&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;ResponseWriter&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8;font-style:italic&quot;&gt;r&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;http&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;Request&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;	userService&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; :=&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; user&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;NewService&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;user&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;NewRepository&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;h&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;DB&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;	sessionService&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; :=&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; session&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;NewService&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;session&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;NewRepository&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;h&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;DB&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;	if&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; err&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; :=&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; r&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;ParseForm&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(); &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;err&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; != &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;nil&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;		http&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Error&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;w&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;Error parsing form&quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;http&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;StatusBadRequest&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;		return&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;	req&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; :=&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; loginForm&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;		Email&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;:    &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;r&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;FormValue&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;email&quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;		Password&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;r&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;FormValue&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;password&quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F98F;font-style:italic&quot;&gt;	// Check rate limit&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;	if&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; err&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; :=&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; loginRateLimiter&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Deduct&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;req&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;Email&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;); &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;err&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; != &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;nil&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;		http&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Error&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;w&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;Too many login attempts. Please try again later.&quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;http&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;StatusTooManyRequests&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;		return&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F98F;font-style:italic&quot;&gt;	// validate credentials&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;	user&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;err&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; :=&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; userService&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;GetByEmail&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;req&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;Email&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;	if&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; err&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; != &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;nil&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;		if&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; errors&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Is&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;err&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;h&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;DB&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;ErrRecordNotFound&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;			http&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Error&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;w&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;Wrong credentials&quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;http&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;StatusUnauthorized&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;			return&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;		}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;		http&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Error&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;w&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;Error accessing user&quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;http&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;StatusInternalServerError&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;		return&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;	if&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; match&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; :=&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; password&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;DoPasswordsMatch&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;user&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;Password&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;req&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;Password&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;); !&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;match&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;		http&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Error&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;w&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;Wrong credentials&quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;http&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;StatusUnauthorized&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;		return&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F98F;font-style:italic&quot;&gt;	// create session&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;	token&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; :=&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; sessionService&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;CreateToken&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;	if&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; err&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; :=&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; sessionService&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Create&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;token&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;user&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;ID&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;); &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;err&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; != &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;nil&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;		http&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Error&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;w&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;Error creating session&quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;http&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;StatusInternalServerError&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;		return&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F98F;font-style:italic&quot;&gt;	// set session cookie&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;	http&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;SetCookie&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;w&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;http&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;Cookie&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;		Name&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;:     &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;session&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;AuthCookieName&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;		Value&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;:    &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;token&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;		HttpOnly&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;		SameSite&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;http&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;SameSiteStrictMode&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;	})&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;	w&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;WriteHeader&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;http&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;StatusOK&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;	return&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Note the error handling: don’t tell users whether the email exists or the password is wrong—just “wrong credentials.” This prevents username enumeration.&lt;/p&gt;
&lt;h2 id=&quot;auth-middleware&quot;&gt;Auth Middleware&lt;/h2&gt;
&lt;p&gt;Middleware gates access to protected routes. Extract the session token, validate it, attach user info to the request context.&lt;/p&gt;
&lt;figure&gt;&lt;pre tabindex=&quot;0&quot;&gt;&lt;code style=&quot;display:grid&quot;&gt;&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;package&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; mw&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; contextKey&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt; string&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;	SessionIdKey&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;          contextKey&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; &quot;sessionId&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;	UserIDKey&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;             contextKey&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt; &quot;userId&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;func&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8;font-style:italic&quot;&gt;h &lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;Handler&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Authenticate&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8;font-style:italic&quot;&gt;next&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; http&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;Handler&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;http&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;Handler&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;	sessionService&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; :=&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; session&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;NewService&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;session&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;NewRepository&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;h&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;DB&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;	userService&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; :=&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; user&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;NewService&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;user&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;NewRepository&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;h&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;DB&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;	return&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; http&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;HandlerFunc&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;func&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8;font-style:italic&quot;&gt;w&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; http&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;ResponseWriter&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8;font-style:italic&quot;&gt;r&lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;http&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;Request&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F98F;font-style:italic&quot;&gt;		// get session cookie&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;		cookie&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;err&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; :=&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; r&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Cookie&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;&amp;lt;cookie-key&amp;gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;		if&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; err&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; != &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;nil&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;			http&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Redirect&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;w&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;r&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;/login&quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;http&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;StatusSeeOther&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;			return&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;		}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;		authToken&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; :=&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; cookie&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;Value&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;		session&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;err&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; :=&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; sessionService&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;ValidateSession&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;authToken&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;		if&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; err&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; != &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;nil&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;			if&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; errors&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Is&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;err&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;h&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;DB&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;ErrRecordNotFound&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;				http&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Redirect&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;w&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;r&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;/login&quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;http&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;StatusSeeOther&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;				return&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;			}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;			http&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Error&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;w&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;Error validating session&quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;http&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;StatusInternalServerError&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;			return&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;		}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;		user&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;err&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; :=&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; userService&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Get&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;session&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;UserID&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;		if&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; err&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; != &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;nil&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;			http&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Error&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;w&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;Error getting user&quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;http&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;StatusInternalServerError&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;			return&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;		}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;		ctx&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; :=&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; r&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Context&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;		ctx&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; context&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;WithValue&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;ctx&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;SessionIdKey&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;session&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;ID&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;String&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;())&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;		ctx&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; context&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;WithValue&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;ctx&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;UserIDKey&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;session&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;UserID&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;String&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;())&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;		r&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt; r&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;WithContext&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;ctx&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;		next&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;ServeHTTP&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;w&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;r&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;	})&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt; &lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Apply it to any route:&lt;/p&gt;
&lt;figure&gt;&lt;pre tabindex=&quot;0&quot;&gt;&lt;code style=&quot;display:grid&quot;&gt;&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;r&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;With&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;mwHandler&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;Authenticate&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;).&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Route&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;/dashboard&quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#54B9FF&quot;&gt;func&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8;font-style:italic&quot;&gt;r&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt; chi&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#ACAFFF&quot;&gt;Router&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;    r&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#00DAEF&quot;&gt;Get&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFD493&quot;&gt;&quot;/&quot;&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;handler&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4BF3C8&quot;&gt;HandleDashboard&lt;/span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#EEF0F9&quot;&gt;})&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Authentication tells you who the user is. Authorization (checking roles/permissions) tells you what they can do—a separate concern.&lt;/p&gt;
&lt;h2 id=&quot;when-to-roll-your-own&quot;&gt;When to Roll Your Own&lt;/h2&gt;
&lt;p&gt;Roll your own when:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Standard username/password with sessions is enough&lt;/li&gt;
&lt;li&gt;You want full control and minimal dependencies&lt;/li&gt;
&lt;li&gt;You have time to implement it correctly&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Use an existing solution when:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You need MFA, social logins, or enterprise SSO&lt;/li&gt;
&lt;li&gt;You’re on a tight deadline&lt;/li&gt;
&lt;li&gt;Security expertise on the team is limited&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;the-tradeoffs&quot;&gt;The Tradeoffs&lt;/h2&gt;
&lt;p&gt;Rolling your own means owning security updates and edge cases (password resets, account recovery, session invalidation across devices). Third-party auth means adapting to their patterns and depending on their uptime.&lt;/p&gt;
&lt;p&gt;Neither choice is universally right. For standard auth requirements, building it yourself is simpler than the conventional wisdom suggests. The complexity isn’t in the implementation—it’s in not skipping steps.&lt;/p&gt;</content:encoded><category>Go</category><category>Web Development</category></item><item><title>From NextJS to Go</title><link>https://danielbenner.de/articles/nextjs-to-golang/</link><guid isPermaLink="true">https://danielbenner.de/articles/nextjs-to-golang/</guid><description>Why I rewrote a side project from NextJS/Supabase to Go, and what I learned.</description><pubDate>Sun, 09 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I built a side project—in-product release notes—with NextJS and Supabase. Development was smooth. Production was not.&lt;/p&gt;
&lt;p&gt;Pages that loaded instantly in dev took seconds in production. Before I’d even launched, I hit Vercel’s image processing limits and Supabase’s edge function ceiling. The “lightweight” side project was heading toward €50/month in hosting costs.&lt;/p&gt;
&lt;p&gt;So I rewrote it: Go with HTML templates, HTMX, and Alpine, hosted on a €5/month VPS. I also built authentication from scratch.&lt;/p&gt;
&lt;p&gt;This wasn’t because NextJS and Supabase are bad—they’re not. They just weren’t right for this project. The performance issues and unexpected costs were the practical reasons. Curiosity about Go was the other reason.&lt;/p&gt;
&lt;h2 id=&quot;learning-go&quot;&gt;Learning Go&lt;/h2&gt;
&lt;p&gt;Resources that worked well, in order:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://go.dev/tour/welcome/1&quot;&gt;A Tour of Go&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://gobyexample.com&quot;&gt;Go by Example&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://go.dev/doc/code&quot;&gt;How to Write Go Code&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://go.dev/doc/effective_go&quot;&gt;Effective Go&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Go’s directness was immediately apparent. Where JavaScript offers a dozen ways to do the same thing, Go typically presents one or two. This felt constraining at first, then liberating. The emphasis on “idiomatic” patterns—one right way to do things—made the language easy to pick up.&lt;/p&gt;
&lt;h2 id=&quot;rolling-my-own-auth&quot;&gt;Rolling my own auth&lt;/h2&gt;
&lt;p&gt;Supabase Auth’s abstractions made debugging painful. So I built authentication from scratch.&lt;/p&gt;
&lt;p&gt;Two resources made this straightforward:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://lucia-auth.com&quot;&gt;Lucia Auth&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://thecopenhagenbook.com&quot;&gt;The Copenhagen Book&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The implementation: a session service, authentication middleware, rate limiting, and CSRF protection. More work than using a provider, but when something breaks, I know exactly where to look. I wrote more about this in &lt;a href=&quot;https://danielbenner.de/articles/roll-your-own-auth&quot;&gt;Rolling Your Own Authentication in Go&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;project-structure&quot;&gt;Project structure&lt;/h2&gt;
&lt;p&gt;In NextJS, client and server code blur together. In Go, the boundaries are clear: receive request, process, return response.&lt;/p&gt;
&lt;p&gt;Standard structure:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Handlers (HTTP requests)&lt;/li&gt;
&lt;li&gt;Services (business logic)&lt;/li&gt;
&lt;li&gt;Repositories (database)&lt;/li&gt;
&lt;li&gt;Models (data structures)&lt;/li&gt;
&lt;li&gt;Templates (HTML)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Libraries: &lt;a href=&quot;https://github.com/go-chi/chi&quot;&gt;Chi&lt;/a&gt; for routing, &lt;a href=&quot;https://github.com/go-playground/validator&quot;&gt;validator&lt;/a&gt; for validation, &lt;a href=&quot;https://github.com/gorilla/schema&quot;&gt;Gorilla schema&lt;/a&gt; for form handling.&lt;/p&gt;
&lt;p&gt;A &lt;a href=&quot;https://danielbenner.de/articles/form-validation-go&quot;&gt;consistent pattern emerged&lt;/a&gt; for handlers: parse → decode → validate → business logic → response.&lt;/p&gt;
&lt;p&gt;The hardest part was replacing React’s component model with Go templates. No state management, no component abstraction—just template partials and inheritance. It forced me to think differently about UI structure.&lt;/p&gt;
&lt;h2 id=&quot;what-surprised-me&quot;&gt;What surprised me&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Speed.&lt;/strong&gt; The first time I loaded the Go version, I thought it was broken—pages appeared so fast I assumed they hadn’t loaded. A dynamic app with auth, database queries, and business logic, responding instantly. I knew static sites could be fast. I didn’t expect this from a full application.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Simplicity.&lt;/strong&gt; In NextJS, I was constantly thinking about client vs server components, server actions, data fetching strategies, client/server state interplay. In Go: receive request, process, return response. Debugging got easier. Development got faster.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;AI compatibility.&lt;/strong&gt; Go’s consistency made AI tools significantly more reliable. Point an LLM at a Go handler, ask it to create another one in the same style—it works almost every time. The lack of abstraction layers means less for the model to misunderstand.&lt;/p&gt;
&lt;h2 id=&quot;on-abstractions&quot;&gt;On abstractions&lt;/h2&gt;
&lt;p&gt;Implementing features in Go—routing → parsing → validation → business logic → templates—made something clear: this is what NextJS does under the hood, just with layers of abstraction on top.&lt;/p&gt;
&lt;p&gt;Client components, server components, server actions, hooks, context, reducers—they’re abstractions over request-response. Powerful for large teams building complex applications. Overkill for many projects.&lt;/p&gt;
&lt;p&gt;Go templates have real limitations. No component model, no state management. Some interactions are harder to build. But the constraint forced me to understand web fundamentals more deeply—browser events, form submissions, AJAX—knowledge that transfers regardless of framework.&lt;/p&gt;
&lt;p&gt;Not everything in Go-land is perfect. GORM (the ORM I used) occasionally behaved in surprising ways. Sometimes I wished I’d just written raw SQL.&lt;/p&gt;
&lt;p&gt;Will I use React again? Yes—it’s the right tool for certain projects. But I’ll be more critical about whether its abstractions solve problems I actually have.&lt;/p&gt;
&lt;h2 id=&quot;the-result&quot;&gt;The result&lt;/h2&gt;
&lt;p&gt;The Go version is faster, cheaper (€5/month vs €50+), more maintainable, and easier to debug. When something breaks, I know where to look.&lt;/p&gt;
&lt;p&gt;The migration started as a practical fix for performance and cost problems. It became a useful reminder that simpler tools often work better—and that understanding what happens beneath the abstractions makes you better at using them.&lt;/p&gt;</content:encoded><category>Go</category><category>NextJS</category><category>Web Development</category></item></channel></rss>