<?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: Михаил</title>
    <description>The latest articles on DEV Community by Михаил (@_862f933aa9477a9d2d).</description>
    <link>https://dev.to/_862f933aa9477a9d2d</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%2F4042223%2F65619387-ba06-4fce-b5df-174cd84f15a8.png</url>
      <title>DEV Community: Михаил</title>
      <link>https://dev.to/_862f933aa9477a9d2d</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/_862f933aa9477a9d2d"/>
    <language>en</language>
    <item>
      <title>How We Added Confirmation Before an Agent Sends a Message</title>
      <dc:creator>Михаил</dc:creator>
      <pubDate>Thu, 23 Jul 2026 05:09:10 +0000</pubDate>
      <link>https://dev.to/_862f933aa9477a9d2d/how-we-added-confirmation-before-an-agent-sends-a-message-53dl</link>
      <guid>https://dev.to/_862f933aa9477a9d2d/how-we-added-confirmation-before-an-agent-sends-a-message-53dl</guid>
      <description>&lt;p&gt;How We Added Confirmation Before an Agent Sends a Message — Agent Lab Journal&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  Agent Lab Journal

    Guides
    Glossary
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Case study · Agent safety&lt;/p&gt;

&lt;h1&gt;
  
  
  How We Added Confirmation Before an Agent Sends a Message
&lt;/h1&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;      Intermediate
      7 min read
      Safe approval loop
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;A message prepared by an agent must not automatically become a message sent in its owner’s name. We separated drafting from delivery and placed an explicit, verifiable human decision between them.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem was not text generation
&lt;/h2&gt;

&lt;p&gt;Our agent could assemble a useful Telegram reply from the conversation context. The dangerous part came afterward: the same execution path also had access to the send operation. A mistaken recipient, an outdated draft, a misunderstood instruction, or a malicious fragment in the source material could therefore turn directly into an external action.&lt;/p&gt;

&lt;p&gt;This is an approval loop problem. Drafting is reversible; sending is not. Once a message reaches another person, deleting it may not undo screenshots, notifications, automated processing, or reputational damage.&lt;/p&gt;

&lt;p&gt;The safety requirement was simple to state:&lt;/p&gt;

&lt;p&gt;No agent-generated message may be sent until the owner has reviewed the exact text, the exact destination, and explicitly approved that specific version.&lt;/p&gt;

&lt;p&gt;The important words are “exact” and “specific.” A generic “yes” from an old conversation must not authorize a later draft, and approval for one recipient must not be reusable for another.&lt;/p&gt;

&lt;h2&gt;
  
  
  The concrete case
&lt;/h2&gt;

&lt;p&gt;The workflow began when an incoming Telegram message required a response. The agent received the conversation identifier and enough recent context to propose a reply. Previously, the handler could call the Telegram send method immediately after generation.&lt;/p&gt;

&lt;p&gt;We changed the flow into four distinct states:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Drafted: the agent produces text but has no permission to deliver it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Pending approval: the owner sees the recipient, draft, and approval controls.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Approved or rejected: the owner makes an explicit decision.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Sent or cancelled: a separate delivery component executes only an approved, unchanged request.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This separation establishes a human-in-the-loop boundary around the consequential action. It also follows the principle of least privilege: the drafting component can create pending records, while only the delivery component can use the messaging credential.&lt;/p&gt;

&lt;h2&gt;
  
  
  The approval record
&lt;/h2&gt;

&lt;p&gt;A pending approval must be durable. Keeping it only in model context or process memory makes restarts unsafe and makes it difficult to determine what was actually approved.&lt;/p&gt;

&lt;p&gt;We used a record with fields equivalent to the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"generated-random-id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"pending"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"channel"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"telegram"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"recipient_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"resolved-chat-id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"recipient_label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"displayed-to-owner"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"message_text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Exact proposed message"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"message_hash"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sha256-of-canonical-payload"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"created_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"server-timestamp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"expires_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"server-timestamp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"created_by"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"agent"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"approved_by"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"approved_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sent_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"provider_message_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The message hash binds approval to both content and destination. Hash a canonical payload rather than the text alone:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;channel&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;recipient_id&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;message_text&lt;/span&gt;
&lt;span class="n"&gt;message_hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SHA256&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the recipient or text changes, the hash changes and the old approval is invalid. In that case the record returns to the pending state and must be reviewed again.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical implementation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Remove sending from the agent’s toolset
&lt;/h3&gt;

&lt;p&gt;The strongest control is architectural. Do not merely prompt the agent to ask first. A prompt is behavioral guidance, not an authorization boundary.&lt;/p&gt;

&lt;p&gt;Expose a draft operation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"create_message_draft"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Create a pending message for owner review. Does not send."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"parameters"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"recipient_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"message_text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep the actual provider token outside the agent runtime. The draft service needs database access, but no Telegram send credential.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Show what will happen
&lt;/h3&gt;

&lt;p&gt;The approval screen or owner notification should display:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;the channel and unambiguous recipient;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;the complete message without hidden truncation;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;attachments, formatting, reply target, and link previews when applicable;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;when the draft was created and when it expires;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;separate Approve, Edit, and Reject actions.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Do not make approval the default when the owner presses Enter, closes a dialog, or fails to respond. Silence means the draft remains pending until it expires.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Authenticate the decision
&lt;/h3&gt;

&lt;p&gt;An approval callback must be tied to the owner’s verified account. Treat callback data as untrusted input, even if it originated in your own interface.&lt;/p&gt;

&lt;p&gt;Use a random, single-use token or a signed value containing the approval ID, intended owner, and expiration. On receipt, the server must verify the owner identity, signature, expiry, current record status, and message hash.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;POST /approvals/{approval_id}/approve
Authorization: Bearer &amp;lt;owner-session&amp;gt;
If-Match: "&amp;lt;message-hash&amp;gt;"
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The server—not the client—must decide whether the request is valid.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Make the state transition atomic
&lt;/h3&gt;

&lt;p&gt;Two approval clicks or concurrent workers must not produce two sends. Use an atomic conditional update:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;message_approvals&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'approved'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;approved_by&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;owner_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;approved_at&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;CURRENT_TIMESTAMP&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;approval_id&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'pending'&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;message_hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;expected_hash&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;expires_at&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;CURRENT_TIMESTAMP&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Continue only if exactly one row changed. This is an idempotency safeguard at the approval boundary.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Deliver through a separate worker
&lt;/h3&gt;

&lt;p&gt;The delivery worker selects approved records, verifies the hash again, and claims one record before contacting Telegram:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pending -&amp;gt; approved -&amp;gt; sending -&amp;gt; sent
                   \-&amp;gt; failed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use a unique idempotency key derived from the approval ID where the provider supports it. If it does not, store the provider response immediately and design retry behavior conservatively. After an uncertain timeout, do not blindly resend: the first request may have succeeded.&lt;/p&gt;

&lt;h2&gt;
  
  
  Suggested policy configuration
&lt;/h2&gt;

&lt;p&gt;Keeping the rules in configuration makes the boundary visible and reviewable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;outbound_messages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;default_mode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;require_approval&lt;/span&gt;
  &lt;span class="na"&gt;approvers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;owner&lt;/span&gt;
  &lt;span class="na"&gt;approval_ttl_minutes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;30&lt;/span&gt;
  &lt;span class="na"&gt;approval_binds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;channel&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;recipient_id&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;message_text&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;attachments&lt;/span&gt;
  &lt;span class="na"&gt;edit_invalidates_approval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;reject_is_terminal&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;auto_send&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="na"&gt;audit_log&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;redact_credentials&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We recommend starting with no automatic-send exceptions. If a future workflow genuinely needs them, define a narrow allowlist based on action type and destination—not on the model’s confidence. Confidence is not authorization.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verification checklist
&lt;/h2&gt;

&lt;p&gt;We verified the control by checking state transitions and permissions rather than judging whether generated prose looked reasonable. A repeatable test plan should cover:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;A new draft cannot call the provider send endpoint.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;An unauthenticated user cannot approve a draft.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A different authenticated user cannot approve the owner’s draft.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Changing one character, attachment, channel, or recipient invalidates approval.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;An expired draft cannot move to approved.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Two simultaneous approval requests result in one successful transition.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Repeated delivery jobs do not intentionally send the same approved record twice.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A rejected draft can never be sent.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A service restart preserves pending, approved, and sent states correctly.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Logs record the decision without exposing credentials or unnecessary private content.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A useful manual test is to pause between preview and approval, edit the database record through the normal application path, and confirm that the stored hash no longer matches. The delivery worker should refuse the request and require fresh approval.&lt;/p&gt;

&lt;h2&gt;
  
  
  Failure cases we designed for
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The owner approves the wrong chat
&lt;/h3&gt;

&lt;p&gt;A display name alone is ambiguous. Show stable context such as the channel, chat type, and a recognizable recipient label. Bind the internal recipient identifier into the hash.&lt;/p&gt;

&lt;h3&gt;
  
  
  The draft changes after preview
&lt;/h3&gt;

&lt;p&gt;Any edit creates a new version and resets the status to pending. Never modify an approved record in place.&lt;/p&gt;

&lt;h3&gt;
  
  
  The approval button is clicked twice
&lt;/h3&gt;

&lt;p&gt;The second request should return the existing state without scheduling another delivery. Conditional updates and a unique delivery record prevent duplicate work.&lt;/p&gt;

&lt;h3&gt;
  
  
  The provider times out
&lt;/h3&gt;

&lt;p&gt;A timeout is an unknown result, not proof of failure. Mark it separately, retain the request details, and reconcile before retrying when possible.&lt;/p&gt;

&lt;h3&gt;
  
  
  The owner never responds
&lt;/h3&gt;

&lt;p&gt;The approval expires. It must not become approved through a timeout handler, scheduled task, or fallback branch.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prompt injection asks the agent to bypass review
&lt;/h3&gt;

&lt;p&gt;The request fails because the agent has no sending credential and no approval transition capability. This is why the permission boundary matters more than wording in the system prompt.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limitations
&lt;/h2&gt;

&lt;p&gt;An approval loop reduces unauthorized sending, but it does not guarantee that the owner will notice every factual, legal, privacy, or tone problem. The preview must provide enough context for a meaningful decision, and high-risk messages may need additional review.&lt;/p&gt;

&lt;p&gt;The pattern also introduces latency and interface work. It may be unsuitable for emergency automation where delay is itself dangerous. Such systems need a separately designed policy with tightly scoped actions, predefined destinations, monitoring, and an explicit risk decision.&lt;/p&gt;

&lt;p&gt;Finally, exactly-once delivery cannot always be guaranteed when an external provider accepts a request but loses the response. The local state machine can prevent ordinary duplicates, but ambiguous network failures still require reconciliation or a provider-supported idempotency mechanism.&lt;/p&gt;

&lt;h2&gt;
  
  
  The resulting safety boundary
&lt;/h2&gt;

&lt;p&gt;The final design made one rule enforceable: the agent proposes; the owner authorizes; a separate worker acts. Approval is authenticated, expires, applies to one immutable payload, and cannot be replayed.&lt;/p&gt;

&lt;p&gt;This pattern is useful beyond Telegram. The same boundary applies to email, calendar invitations, support replies, issue comments, purchases, document sharing, and any other action performed in a person’s name.&lt;/p&gt;

&lt;p&gt;Continue with the implementation patterns in Agent Lab Journal guides, or review the linked concepts in the glossary.&lt;/p&gt;

&lt;p&gt;© Agent Lab Journal&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Original article:&lt;/strong&gt; &lt;a href="https://agentlabjournal.online/en/agent-telegram-approval-case.html" rel="noopener noreferrer"&gt;https://agentlabjournal.online/en/agent-telegram-approval-case.html&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>automation</category>
      <category>agents</category>
    </item>
    <item>
      <title>How We Built an Autonomous AI Article Publication Pipeline</title>
      <dc:creator>Михаил</dc:creator>
      <pubDate>Wed, 22 Jul 2026 15:47:22 +0000</pubDate>
      <link>https://dev.to/_862f933aa9477a9d2d/how-we-built-an-autonomous-ai-article-publication-pipeline-4ijl</link>
      <guid>https://dev.to/_862f933aa9477a9d2d/how-we-built-an-autonomous-ai-article-publication-pipeline-4ijl</guid>
      <description>&lt;p&gt;How We Built an Autonomous AI Article Publication PipelineALAgent Lab Journal← GuidesGlossary&lt;br&gt;
ANALYSIS OF A REAL MISTAKE&lt;/p&gt;
&lt;h1&gt;
  
  
  How We Built an Autonomous AI Article Publication PipelineLevel: intermediateTime: 10 minutesOutcome: publication gate
&lt;/h1&gt;

&lt;p&gt;We asked an AI agent to prepare a series of articles. The texts were produced, but a review revealed that one article mentioned NotebookLM without linking to the glossary. Then we discovered another problem: llms.txt had not been updated after the materials were rewritten. These were not minor typos. The publication process had failed.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why the agent made a mistake
&lt;/h2&gt;

&lt;p&gt;The agent completed the local task—it wrote the HTML files—but did not check the entire pipeline. The rules existed in the context, but they had not been turned into mandatory automated checks. As a result, the system could say “done” even though the catalog, the map for LLMs, and the glossary were out of sync.&lt;/p&gt;
&lt;h2&gt;
  
  
  First solution: editorial rules
&lt;/h2&gt;

&lt;p&gt;We established a mandatory structure: a real-world problem at the beginning, a specific outcome, difficulty level, reading time, step-by-step actions, errors, verification, and limitations. We also added a separate rule: the first technical term must link to the lab glossary.&lt;br&gt;
This prevents an article from becoming a collection of generic tips. The reader understands what is broken, follows the steps, and verifies the result.&lt;/p&gt;
&lt;h2&gt;
  
  
  Second solution: a unified content map
&lt;/h2&gt;

&lt;p&gt;Each article has several representations: an HTML page for people, an entry in guides.html, a URL in sitemap.xml, and a brief entry in llms.txt. The last file helps AI systems quickly understand the journal’s structure and choose the appropriate material.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;new article
  ├── guide.html
  ├── guides.html
  ├── sitemap.xml
  ├── llms.txt
  └── glossary.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If an article’s title or meaning changes, all related representations must be updated. A single link in Git is not enough.&lt;/p&gt;

&lt;h2&gt;
  
  
  Third solution: an automated publication gate
&lt;/h2&gt;

&lt;p&gt;We added the scripts/check-publication.py script. It scans every guide-*.html file and checks for the required elements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;description and canonical URL;&lt;/li&gt;
&lt;li&gt;level, time, and expected outcome;&lt;/li&gt;
&lt;li&gt;a link to the glossary;&lt;/li&gt;
&lt;li&gt;the article’s presence in the catalog;&lt;/li&gt;
&lt;li&gt;the URL’s presence in the sitemap;&lt;/li&gt;
&lt;li&gt;the URL’s presence in llms.txt.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;agentlabjournal
python3 scripts/check-publication.py

PUBLICATION_GATE: OK
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If a link is missing, the script returns BLOCKED and identifies the specific file. A polished response from the model cannot conceal that result.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this is autonomy
&lt;/h2&gt;

&lt;p&gt;Autonomy does not mean “the agent does whatever it wants without oversight.” It means a system that performs permitted work on its own and stops itself when a mandatory condition has not been met. A person decides the objective, while the machine checks repeatable rules.&lt;/p&gt;

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

&lt;p&gt;One mistake led us to build a reproducible pipeline: rule → artifact → automated check → publication. The same approach can be applied to code, R&amp;amp;D experiments, email, and Telegram agents.&lt;br&gt;
← All guides · Lab glossary →We publish what works for us—and implement the same solutions for your business. We design AI automations, Telegram bots, chats, and AI agents for real-world processes. Discuss your project →Agent Lab JournalPrivacy&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Original article:&lt;/strong&gt; &lt;a href="https://agentlabjournal.online/en/article-autonomous-publication-pipeline.html" rel="noopener noreferrer"&gt;https://agentlabjournal.online/en/article-autonomous-publication-pipeline.html&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>automation</category>
      <category>agents</category>
    </item>
    <item>
      <title>How We Are Building an AI System That Improves Itself</title>
      <dc:creator>Михаил</dc:creator>
      <pubDate>Wed, 22 Jul 2026 15:47:13 +0000</pubDate>
      <link>https://dev.to/_862f933aa9477a9d2d/how-we-are-building-an-ai-system-that-improves-itself-11ad</link>
      <guid>https://dev.to/_862f933aa9477a9d2d/how-we-are-building-an-ai-system-that-improves-itself-11ad</guid>
      <description>&lt;p&gt;How We Are Building an AI System | Agent Lab JournalALAgent Lab Journal← HomeEN&lt;br&gt;
FIRST ARTICLE · JULY 22, 2026&lt;/p&gt;

&lt;h1&gt;
  
  
  How We Are Building an AI System That Improves ItselfLevel: beginnerReading time: 8 minutesOutcome: understand the Hermes architecture
&lt;/h1&gt;

&lt;p&gt;Hermes began as a personal assistant in Telegram. Over time, it gained memory, routing, financial controls, background agents, and an engineering framework for continuous improvement.&lt;/p&gt;

&lt;h2&gt;
  
  
  From Answers to a System
&lt;/h2&gt;

&lt;p&gt;The main problem with a conventional chatbot is that it responds to individual messages and quickly loses its working context. That is why we moved persistent facts and decisions into a wiki while keeping the sources unchanged in raw storage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Every Improvement Must Be Verified
&lt;/h2&gt;

&lt;p&gt;New ideas come from GitHub, YouTube, and technical materials. But an external source is data, not an instruction. It first goes into a separate notebook, then is evaluated for usefulness, cost, security, and testability.A system becomes stronger not when more tools are added, but when every change can be tested and rolled back.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Comes Next
&lt;/h2&gt;

&lt;p&gt;The next stage is a benchmarkBenchmarkA set of identical tasks used to compare system versions.Benefit: shows whether the solution has become more accurate, faster, or less expensive. based on real tasks: finances, email, conversation context, approvalApprovalExplicit confirmation from the owner before an important external action.For example, before publishing, sending an email, or making a payment. for external actions, and protection against prompt injectionPrompt injectionAn attempt to hide an instruction in text that makes the AI break the rules.For example, a README asks it to reveal secrets or execute a dangerous command.. Only after that can an experimental feature enter the production workflow.&lt;br&gt;
← Return to the journal · Lab glossary → · Reading paths →We publish what works for us—and implement the same solutions for your business. We design AI automations, Telegram bots, chats, and AI agents for real-world processes. Discuss your project →Agent Lab JournalReal experiments. Verifiable conclusions.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Original article:&lt;/strong&gt; &lt;a href="https://agentlabjournal.online/en/article-hermes.html" rel="noopener noreferrer"&gt;https://agentlabjournal.online/en/article-hermes.html&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>automation</category>
      <category>agents</category>
    </item>
  </channel>
</rss>
