<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Larry</title>
    <description>The latest articles on DEV Community by Larry (@larry_agent).</description>
    <link>https://dev.to/larry_agent</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4020008%2F10f27f21-b9a5-4a93-91b4-8c6ce94bfdc0.png</url>
      <title>DEV Community: Larry</title>
      <link>https://dev.to/larry_agent</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/larry_agent"/>
    <language>en</language>
    <item>
      <title>How we built a smart AI writing agent for blog articles</title>
      <dc:creator>Larry</dc:creator>
      <pubDate>Tue, 07 Jul 2026 17:47:30 +0000</pubDate>
      <link>https://dev.to/larry_agent/how-we-built-a-smart-ai-writing-agent-for-blog-articles-5dma</link>
      <guid>https://dev.to/larry_agent/how-we-built-a-smart-ai-writing-agent-for-blog-articles-5dma</guid>
      <description>&lt;p&gt;Most "AI writer" products do the same thing: prompt → blob of text → paste. It works for a demo and falls apart in production, because the value isn't the prose; it's the &lt;strong&gt;domain expert's judgment&lt;/strong&gt;, and a one-shot generation has nowhere to put it.&lt;/p&gt;

&lt;p&gt;We build &lt;a href="https://larry-agent.com/en" rel="noopener noreferrer"&gt;Larry&lt;/a&gt;, a tool that turns a lawyer's expertise into articles that get cited by Google, ChatGPT and Gemini. Here's the actual shape of our writing agent: three tools, an interview-first loop, and two patterns worth stealing for any "AI for experts" product.&lt;/p&gt;

&lt;h2&gt;
  
  
  The wrong shape: regenerate everything
&lt;/h2&gt;

&lt;p&gt;The instinct is: user asks for a change → send the whole article back to the model → get a whole new article. This is bad because every regeneration drifts. The intro you liked changes. The one accurate statistic gets "improved" into a hallucination. You lose the expert's voice on every pass.&lt;/p&gt;

&lt;p&gt;The fix is to treat the article like &lt;strong&gt;code&lt;/strong&gt;: give the agent an editing tool that makes &lt;em&gt;surgical&lt;/em&gt; changes to a document that already exists, not a firehose that rewrites it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The stack (boring on purpose)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Next.js&lt;/strong&gt; Route Handler (App Router) exposing the agent as a streaming SSE endpoint.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OpenRouter&lt;/strong&gt; with the OpenAI SDK, so the model is a config value, not a rewrite. We use a cheap creative model for the first draft and a stronger tool-calling model for the interactive agent.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Supabase (Postgres)&lt;/strong&gt;: the article lives in a &lt;code&gt;blog_posts&lt;/code&gt; row; the conversation and every tool call are persisted so the agent is stateful across turns.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tavily&lt;/strong&gt; for web search.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No agent framework. Just a &lt;code&gt;while&lt;/code&gt; loop that calls the model, runs any tool calls, feeds the results back, and repeats until the model stops asking for tools.&lt;/p&gt;

&lt;h2&gt;
  
  
  The three tools
&lt;/h2&gt;

&lt;p&gt;That's the entire toolbox. Resist adding more.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tools&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;read_article&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Read the current article (title, content, FAQ, tags, SEO metas) and author info.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;replace_in_field&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Replace text in a specific field of the article.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;field&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;title&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;content&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;faq&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tags&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;excerpt&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;meta_title&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;meta_description&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;example_validation&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;old_string&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Exact text to replace. Empty = replace the whole field.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;new_string&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;The new value.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;web_search&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Search the web for current facts. Returns a source URL + snippet.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;replace_in_field&lt;/code&gt; is the important one. &lt;code&gt;old_string&lt;/code&gt; must match &lt;strong&gt;exactly one occurrence&lt;/strong&gt; in the target field, the same contract as the &lt;code&gt;Edit&lt;/code&gt; tool in a coding agent. The agent reads the article, finds the sentence to fix, and patches only that sentence. Everything else is byte-for-byte preserved. Voice and facts stop drifting because 95% of the document is never re-touched.&lt;/p&gt;

&lt;h2&gt;
  
  
  The workflow: interview first, write last
&lt;/h2&gt;

&lt;p&gt;The system prompt enforces a strict sequence, and the golden rule is: &lt;strong&gt;never run a step that needs an answer without stopping for it.&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Analyze &amp;amp; research&lt;/strong&gt; the topic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Capture expertise.&lt;/strong&gt; Ask the lawyer 2–3 precise questions, and &lt;em&gt;always&lt;/em&gt; demand one concrete, anonymized real case or anecdote. Then &lt;strong&gt;wait&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Propose a plan.&lt;/strong&gt; Present the structure, then &lt;strong&gt;wait for approval&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write &amp;amp; optimize.&lt;/strong&gt; Fill every field with &lt;code&gt;replace_in_field&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Step 2 is where the product lives. Anyone can generate a generic article about a legal topic. The moat is the lawyer's own example: the exception, the mistake clients always make, the thing a Google search gets wrong. So the agent's first job isn't to write; it's to &lt;em&gt;interview&lt;/em&gt;. Here's the shape of that prompt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You are capturing a lawyer's expertise on a specific topic.
Ask 2–3 precise questions, ONE exchange at a time.
You MUST ask for one concrete, anonymized real case or anecdote
that illustrates the topic. Wait for the answer before proposing a plan.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Two patterns worth stealing
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. A validation gate the model can't fake.&lt;/strong&gt;&lt;br&gt;
Our quality bar requires a real anonymized example in the body. We enforce it with a boolean field, &lt;code&gt;example_validation&lt;/code&gt;, that's &lt;em&gt;also a tool target&lt;/em&gt;. The rule: the agent may only set it to &lt;code&gt;true&lt;/code&gt; &lt;strong&gt;after&lt;/strong&gt; re-reading the article with &lt;code&gt;read_article&lt;/code&gt; and confirming the example is actually present. The model can't self-certify from memory; it has to go look. It's a cheap way to turn a soft "please include an example" into a hard state transition.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. External URLs come from search only.&lt;/strong&gt;&lt;br&gt;
The single biggest hallucination risk in legal content is invented case law and fake source links. The guardrail is a hard rule: every external URL in the article must come from a real &lt;code&gt;web_search&lt;/code&gt; result, never from the model's memory. Research isn't optional polish; it's the only sanctioned source of links. (We also forbid citing specific law firms, for the same trust reason.)&lt;/p&gt;

&lt;p&gt;There's also a silent self-critique pass after each edit: the agent re-checks its own output against the quality rules (direct-answer intro, question-form headings, short paragraphs, the example, the call-to-action) and patches the gaps &lt;em&gt;before&lt;/em&gt; handing back, without narrating any of it to the user. The lawyer sees "done," not a checklist.&lt;/p&gt;

&lt;h2&gt;
  
  
  The takeaway
&lt;/h2&gt;

&lt;p&gt;The model is a commodity you swap with an env var. The product is everything around it: &lt;strong&gt;capture the expert's judgment through an interview, edit the document with surgical patches instead of regenerating it, and gate quality with tools the model can't bluff past.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you want to see the finished version aimed at lawyers, it's at &lt;a href="https://larry-agent.com/en" rel="noopener noreferrer"&gt;larry-agent.com&lt;/a&gt;. Curious what other expert domains this shape would fit. What would you point it at?&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>llm</category>
      <category>legaltech</category>
    </item>
  </channel>
</rss>
