<?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: Garudust</title>
    <description>The latest articles on DEV Community by Garudust (@garudust).</description>
    <link>https://dev.to/garudust</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F408531%2F668da519-e26f-4bb7-95a5-82a60f527c22.png</url>
      <title>DEV Community: Garudust</title>
      <link>https://dev.to/garudust</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/garudust"/>
    <language>en</language>
    <item>
      <title>ตรวจ CPU/Memory ทุกชั่วโมงด้วย Garudust Agent Cron ในตัว</title>
      <dc:creator>Garudust</dc:creator>
      <pubDate>Wed, 27 May 2026 09:14:17 +0000</pubDate>
      <link>https://dev.to/garudust/trwcch-cpumemory-thukchawomngdwy-garudust-agent-cron-one-shot-3h78</link>
      <guid>https://dev.to/garudust/trwcch-cpumemory-thukchawomngdwy-garudust-agent-cron-one-shot-3h78</guid>
      <description>&lt;p&gt;&lt;strong&gt;garudust-agent มี cron scheduler ในตัวอยู่แล้ว&lt;/strong&gt; ภายใต้ &lt;code&gt;crates/garudust-cron&lt;/code&gt; ที่ wraps &lt;code&gt;tokio_cron_scheduler&lt;/code&gt; ไว้ หน้าที่ของมันคือยิง agent task ตาม schedule โดยตรง — ไม่ต้องสร้าง process ใหม่ทุกรอบ session ยังอยู่ใน RAM ข้าม tick ได้ และ agent สร้าง/ลบ job เองตอน runtime ได้ด้วย&lt;/p&gt;

&lt;p&gt;บทความนี้ใช้ระบบ cron ใน garudust ทำงานให้ถูกต้อง&lt;/p&gt;




&lt;h2&gt;
  
  
  สิ่งที่ต้องมี
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Linux / macOS&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;garudust-server&lt;/code&gt; binary (v0.13.1+)&lt;/li&gt;
&lt;li&gt;API key จาก OpenAI-compatible endpoint&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  1. ติดตั้ง Binary
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;ARCH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;uname&lt;/span&gt; &lt;span class="nt"&gt;-m&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

curl &lt;span class="nt"&gt;-Lo&lt;/span&gt; garudust.tar.gz &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"https://github.com/ninenox/garudust-agent/releases/download/v&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;GARUDUST_VERSION&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/garudust-&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;ARCH&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-unknown-linux-gnu.tar.gz"&lt;/span&gt;

&lt;span class="nb"&gt;tar&lt;/span&gt; &lt;span class="nt"&gt;-xzf&lt;/span&gt; garudust.tar.gz
&lt;span class="nb"&gt;chmod&lt;/span&gt; +x garudust-server
&lt;span class="nb"&gt;sudo mv &lt;/span&gt;garudust-server /usr/local/bin/garudust-server

garudust-server &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;span class="c"&gt;# garudust-server 0.13.1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# macOS (Apple Silicon)&lt;/span&gt;
curl &lt;span class="nt"&gt;-Lo&lt;/span&gt; garudust.tar.gz &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"https://github.com/ninenox/garudust-agent/releases/download/v&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;GARUDUST_VERSION&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/garudust-aarch64-apple-darwin.tar.gz"&lt;/span&gt;
&lt;span class="nb"&gt;tar&lt;/span&gt; &lt;span class="nt"&gt;-xzf&lt;/span&gt; garudust.tar.gz
&lt;span class="nb"&gt;chmod&lt;/span&gt; +x garudust-server
&lt;span class="nb"&gt;sudo mv &lt;/span&gt;garudust-server /usr/local/bin/garudust-server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  2. ตั้ง Config
&lt;/h2&gt;

&lt;p&gt;ไฟล์ config อยู่ที่ &lt;code&gt;~/.garudust/config.yaml&lt;/code&gt; — สร้าง directory ก่อน:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; ~/.garudust
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# ~/.garudust/config.yaml&lt;/span&gt;

&lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-4o-mini"&lt;/span&gt;
&lt;span class="na"&gt;api_key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sk-..."&lt;/span&gt;

&lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;terminal&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;sandbox&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;none&lt;/span&gt;          &lt;span class="c1"&gt;# รันบน host โดยตรง — monitoring อ่านอย่างเดียว ปลอดภัย&lt;/span&gt;
    &lt;span class="na"&gt;approval_mode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;auto&lt;/span&gt;    &lt;span class="c1"&gt;# cron รันโดยไม่มีคนนั่งดู ต้องเป็น auto&lt;/span&gt;

&lt;span class="c1"&gt;# ── cron ──────────────────────────────────────────────────────────────────────&lt;/span&gt;
&lt;span class="na"&gt;cron&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;timezone&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Asia/Bangkok"&lt;/span&gt;   &lt;span class="c1"&gt;# IANA timezone — ถ้าไม่ระบุ default เป็น UTC&lt;/span&gt;

  &lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;schedule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*"&lt;/span&gt;  &lt;span class="c1"&gt;# 5-field unix cron: ทุกชั่วโมง นาทีที่ 0&lt;/span&gt;
      &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="s"&gt;ตรวจสอบสุขภาพของระบบตอนนี้ด้วยคำสั่งต่อไปนี้:&lt;/span&gt;
        &lt;span class="s"&gt;1. `top -bn1 | head -20`&lt;/span&gt;
        &lt;span class="s"&gt;2. `ps aux --sort=-%cpu | head -10`&lt;/span&gt;
        &lt;span class="s"&gt;3. `free -h`&lt;/span&gt;
        &lt;span class="s"&gt;4. `ps aux --sort=-%mem | head -10`&lt;/span&gt;
        &lt;span class="s"&gt;รายงานผลเป็นภาษาไทยตาม threshold ที่กำหนดไว้&lt;/span&gt;

  &lt;span class="c1"&gt;# (ทางเลือก) จัดระเบียบ memory ของ agent ทุกคืน&lt;/span&gt;
  &lt;span class="na"&gt;memory_consolidation&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;3&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*"&lt;/span&gt;

  &lt;span class="c1"&gt;# (ทางเลือก) ลบ memory เก่ากว่า 30 วัน&lt;/span&gt;
  &lt;span class="na"&gt;memory_expiry&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;4&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*"&lt;/span&gt;

&lt;span class="na"&gt;system_prompt&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
  &lt;span class="s"&gt;คุณคือ system monitor สำหรับเซิร์ฟเวอร์นี้&lt;/span&gt;

  &lt;span class="s"&gt;Threshold สำหรับแจ้งเตือน:&lt;/span&gt;
  &lt;span class="s"&gt;- CPU รวม &amp;gt; 80%  → ⚠️ WARNING&lt;/span&gt;
  &lt;span class="s"&gt;- CPU รวม &amp;gt; 95%  → 🔴 CRITICAL&lt;/span&gt;
  &lt;span class="s"&gt;- Memory &amp;gt; 75%   → ⚠️ WARNING&lt;/span&gt;
  &lt;span class="s"&gt;- Memory &amp;gt; 90%   → 🔴 CRITICAL&lt;/span&gt;

  &lt;span class="s"&gt;เมื่อเกิน threshold ให้:&lt;/span&gt;
  &lt;span class="s"&gt;1. ระบุ process ที่กินสูงสุด 5 อันดับ&lt;/span&gt;
  &lt;span class="s"&gt;2. ประเมินว่าเป็น spike ชั่วคราว หรือ trend ต่อเนื่อง&lt;/span&gt;
  &lt;span class="s"&gt;3. แนะนำ action ถ้าจำเป็น&lt;/span&gt;

  &lt;span class="s"&gt;ถ้าทุกอย่างปกติ สรุปสั้น 2–3 บรรทัดก็พอ&lt;/span&gt;
  &lt;span class="s"&gt;ตอบเป็นภาษาไทยเสมอ&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  3. รัน Server
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;garudust-server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ที่ startup จะเห็น log ยืนยัน cron ถูก register:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;INFO garudust-server 0.13.1 | model: gpt-4o-mini | port: 3000
INFO cron job registered  cron="0 * * * *"  task="ตรวจสอบสุขภาพของระบบ..."
INFO memory consolidation cron registered  cron="0 3 * * *"
INFO memory expiry cron registered  cron="0 4 * * *"
INFO garudust-server listening on 0.0.0.0:3000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;เมื่อ cron fire จะเห็น:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;INFO cron job starting   task="ตรวจสอบสุขภาพของระบบ..."
INFO cron job completed  task="ตรวจสอบสุขภาพของระบบ..."  iterations=3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;หรือถ้า agent เจอปัญหา:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ERROR cron job failed  task="ตรวจสอบสุขภาพของระบบ..."  error="..."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  4. รัน Server แบบ Daemon ด้วย systemd
&lt;/h2&gt;

&lt;p&gt;สำหรับ production ไม่ควรรันใน terminal ค้างไว้ — ใช้ systemd:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo tee&lt;/span&gt; /etc/systemd/system/garudust-monitor.service &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /dev/null &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;
[Unit]
Description=Garudust System Monitor
After=network.target

[Service]
Type=simple
User=&lt;/span&gt;&lt;span class="nv"&gt;$USER&lt;/span&gt;&lt;span class="sh"&gt;
ExecStart=/usr/local/bin/garudust-server
Restart=on-failure
RestartSec=10
Environment=RUST_LOG=info

[Install]
WantedBy=multi-user.target
&lt;/span&gt;&lt;span class="no"&gt;EOF

&lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl daemon-reload
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable&lt;/span&gt; &lt;span class="nt"&gt;--now&lt;/span&gt; garudust-monitor
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl status garudust-monitor
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  5. เช็ก Log
&lt;/h2&gt;

&lt;p&gt;เมื่อรันด้วย systemd:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Log real-time&lt;/span&gt;
journalctl &lt;span class="nt"&gt;-u&lt;/span&gt; garudust-monitor &lt;span class="nt"&gt;-f&lt;/span&gt;

&lt;span class="c"&gt;# Log ย้อนหลัง 1 ชั่วโมง&lt;/span&gt;
journalctl &lt;span class="nt"&gt;-u&lt;/span&gt; garudust-monitor &lt;span class="nt"&gt;--since&lt;/span&gt; &lt;span class="s2"&gt;"1 hour ago"&lt;/span&gt;

&lt;span class="c"&gt;# กรองเฉพาะรอบที่ cron fire&lt;/span&gt;
journalctl &lt;span class="nt"&gt;-u&lt;/span&gt; garudust-monitor &lt;span class="nt"&gt;-g&lt;/span&gt; &lt;span class="s2"&gt;"cron job"&lt;/span&gt;

&lt;span class="c"&gt;# หารอบที่มี WARNING หรือ CRITICAL&lt;/span&gt;
journalctl &lt;span class="nt"&gt;-u&lt;/span&gt; garudust-monitor &lt;span class="nt"&gt;--since&lt;/span&gt; today | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s2"&gt;"WARNING|CRITICAL|⚠️|🔴"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ถ้าอยากเก็บ log เป็นไฟล์แยก ใส่เพิ่มใน systemd unit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[Service]&lt;/span&gt;
&lt;span class="py"&gt;StandardOutput&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;append:/var/log/garudust-monitor.log&lt;/span&gt;
&lt;span class="py"&gt;StandardError&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;append:/var/log/garudust-monitor.log&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  6. เพิ่ม Job แบบ Runtime ผ่าน API
&lt;/h2&gt;

&lt;p&gt;นอกจาก static job ใน config แล้ว agent มี tool &lt;code&gt;cron_create&lt;/code&gt; / &lt;code&gt;cron_list&lt;/code&gt; / &lt;code&gt;cron_delete&lt;/code&gt; ให้ใช้ตอน runtime — สั่งผ่าน HTTP API ได้เลย:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# สร้าง job ตรวจ disk ทุก 6 ชั่วโมง&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:3000/chat &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "message": "สร้าง cron job label: disk_check ทุก 6 ชั่วโมง ให้รัน df -h แล้วแจ้งถ้า disk ใด partition ใดเกิน 85%"
  }'&lt;/span&gt; | jq .reply
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;agent จะใช้ tool &lt;code&gt;cron_create&lt;/code&gt; สร้าง job ให้อัตโนมัติ:&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;"reply"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"✅ สร้าง cron job 'disk_check' แล้ว&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;schedule: 0 0 */6 * * *&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;task: รัน df -h ตรวจ disk..."&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;blockquote&gt;
&lt;p&gt;&lt;strong&gt;หมายเหตุ — Cron syntax ต่างกัน 2 แบบ:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Static job&lt;/strong&gt; ใน &lt;code&gt;config.yaml&lt;/code&gt; → 5-field unix cron ปกติ เช่น &lt;code&gt;0 * * * *&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Runtime job&lt;/strong&gt; ผ่าน API → 6-field (เพิ่ม field วินาทีนำหน้า) เช่น &lt;code&gt;0 0 * * * *&lt;/code&gt; = ทุกชั่วโมง, &lt;code&gt;0 0 */6 * * *&lt;/code&gt; = ทุก 6 ชั่วโมง
&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# ดู job ทั้งหมดที่ active ตอนนี้&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:3000/chat &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"message": "แสดง cron jobs ทั้งหมด"}'&lt;/span&gt; | jq .reply

&lt;span class="c"&gt;# ลบ job&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:3000/chat &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"message": "ลบ cron job label: disk_check"}'&lt;/span&gt; | jq .reply
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Runtime jobs หายเมื่อ restart — สำหรับ permanent job ให้ใส่ใน &lt;code&gt;config.yaml&lt;/code&gt; เสมอ Runtime jobs เหมาะสำหรับทดสอบ schedule ก่อน commit config หรือ one-off jobs ชั่วคราว&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  7. ตั้ง Alert ผ่าน Telegram (ทางเลือก)
&lt;/h2&gt;

&lt;p&gt;เพิ่ม platform Telegram เข้าไปใน config — agent จะส่งข้อความหา admin โดยตรงเมื่อ cron fire และเจอปัญหา:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# ~/.garudust/.env&lt;/span&gt;
&lt;span class="nv"&gt;TELEGRAM_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;bot_token_here
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config.yaml — เพิ่ม/แก้ cron.jobs section นี้&lt;/span&gt;
&lt;span class="na"&gt;cron&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;timezone&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Asia/Bangkok"&lt;/span&gt;
  &lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;schedule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*"&lt;/span&gt;
      &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="s"&gt;ตรวจ CPU/Memory ของระบบ ถ้าพบ WARNING หรือ CRITICAL&lt;/span&gt;
        &lt;span class="s"&gt;ให้ส่งข้อความแจ้งไปยัง admin ทาง Telegram ด้วย&lt;/span&gt;
        &lt;span class="s"&gt;พร้อมระบุ process และ % ที่เกิน threshold&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  เปรียบเทียบกับ System Cron
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;System Cron&lt;/th&gt;
&lt;th&gt;Garudust Cron (built-in)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Process per tick&lt;/td&gt;
&lt;td&gt;spawn ใหม่ทุกครั้ง&lt;/td&gt;
&lt;td&gt;ทำงานใน event loop เดิม&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Session/memory&lt;/td&gt;
&lt;td&gt;โหลดใหม่ทุกรอบ&lt;/td&gt;
&lt;td&gt;อยู่ใน RAM ข้าม tick ได้&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dynamic schedule&lt;/td&gt;
&lt;td&gt;แก้ crontab เอง&lt;/td&gt;
&lt;td&gt;agent สร้าง/ลบตอน runtime&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Log&lt;/td&gt;
&lt;td&gt;ไฟล์แยก&lt;/td&gt;
&lt;td&gt;รวมกับ server log เดียวกัน&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Timezone&lt;/td&gt;
&lt;td&gt;ขึ้นกับ system TZ&lt;/td&gt;
&lt;td&gt;กำหนดใน config ได้ per-scheduler&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dependency&lt;/td&gt;
&lt;td&gt;ต้องมี cron daemon&lt;/td&gt;
&lt;td&gt;ไม่ต้องการ — built into server&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  สรุป config ทั้งหมด
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;~/.garudust/
├── config.yaml          ← cron.jobs, tools, system_prompt
└── .env                 ← API keys, tokens (ไม่ commit)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;garudust-server start
    ↓
register cron jobs จาก config.yaml
    ↓
ทุก 1 ชม. → agent run → terminal tool → top/free/ps
    ↓
log ออก stdout/journald
    ↓ (ถ้าต่อ Telegram)
send alert เมื่อเกิน threshold
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;จุดเด่นที่ system cron ทำไม่ได้: agent จำ pattern ข้ามรอบได้ เช่น รู้ว่า CPU spike ทุก 14:00 คือ backup job ไม่ใช่ anomaly — เพราะ memory session ยังอยู่ในหน่วยความจำระหว่าง tick&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;garudust-agent&lt;/strong&gt; → &lt;a href="https://github.com/garudust-org/garudust-agent" rel="noopener noreferrer"&gt;github.com/garudust-org/garudust-agent&lt;/a&gt;&lt;/p&gt;

</description>
      <category>rust</category>
      <category>devops</category>
      <category>monitoring</category>
      <category>linux</category>
    </item>
    <item>
      <title>Chat With Your Raspberry Pi — Control GPIO, Read Sensors, and Manage Services via Telegram Using Garudust</title>
      <dc:creator>Garudust</dc:creator>
      <pubDate>Tue, 26 May 2026 15:41:33 +0000</pubDate>
      <link>https://dev.to/garudust/chat-with-your-raspberry-pi-control-gpio-read-sensors-and-manage-services-via-telegram-using-40d7</link>
      <guid>https://dev.to/garudust/chat-with-your-raspberry-pi-control-gpio-read-sensors-and-manage-services-via-telegram-using-40d7</guid>
      <description>&lt;p&gt;Your Raspberry Pi is sitting at home doing something useful — logging sensor data, driving a relay, running a home assistant script. You're out. You want to check on it.&lt;/p&gt;

&lt;p&gt;You used to SSH in from your laptop. Today you just open Telegram and ask.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You:    is the temperature sensor still working?
Agent:  ✓ ran: cat /sys/bus/w1/devices/28-*/w1_slave
        Current temperature: 24.3°C — sensor is responding normally.

You:    turn the fan relay on
Agent:  ⚠ This will trigger GPIO 18 — confirm? [y/n]
You:    y
Agent:  ✓ ran: python3 ~/scripts/relay.py on 18
        Relay ON. Fan is running.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is &lt;strong&gt;Garudust Agent&lt;/strong&gt; with an SSH sandbox. The agent lives on a cloud VPS (or any server with internet access). You chat via Telegram. The agent SSHes into your Pi and runs whatever it needs to. Your Pi only needs SSH enabled — no extra software, no port forwarding for the agent.&lt;/p&gt;




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



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You (Telegram)
    │
    ▼
garudust-server          ← runs on a VPS or home server
(Telegram adapter)
    │
    │  SSH  (key auth, BatchMode, StrictHostKeyChecking)
    ▼
Raspberry Pi             ← just needs sshd running
(GPIO, sensors, scripts)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent's &lt;code&gt;terminal&lt;/code&gt; tool normally runs commands locally. With &lt;code&gt;terminal_sandbox: ssh&lt;/code&gt;, every command is forwarded through &lt;code&gt;ssh&lt;/code&gt; to the Pi instead. The agent never knows or cares — it still calls the same tool. The routing happens transparently in the sandbox layer.&lt;/p&gt;




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

&lt;ul&gt;
&lt;li&gt;A Raspberry Pi with SSH enabled (any model, any recent Raspberry Pi OS)&lt;/li&gt;
&lt;li&gt;A server to run Garudust on — a $5 VPS, another machine on your network, or even the Pi itself pointing at localhost&lt;/li&gt;
&lt;li&gt;A Telegram bot token (from &lt;a href="https://t.me/botfather" rel="noopener noreferrer"&gt;@BotFather&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Garudust installed on the server
&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="c"&gt;# On the server — install Garudust&lt;/span&gt;
curl &lt;span class="nt"&gt;-LO&lt;/span&gt; &lt;span class="s2"&gt;"https://github.com/garudust-org/garudust-agent/releases/latest/download/garudust-&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://api.github.com/repos/garudust-org/garudust-agent/releases/latest | &lt;span class="nb"&gt;grep &lt;/span&gt;tag_name | &lt;span class="nb"&gt;cut&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt;&lt;span class="s1"&gt;'"'&lt;/span&gt; &lt;span class="nt"&gt;-f4&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;-x86_64-unknown-linux-musl.tar.gz"&lt;/span&gt;
&lt;span class="nb"&gt;tar&lt;/span&gt; &lt;span class="nt"&gt;-xzf&lt;/span&gt; garudust-&lt;span class="k"&gt;*&lt;/span&gt;.tar.gz &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;sudo mv &lt;/span&gt;garudust garudust-server /usr/local/bin/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 1 — Set Up a Dedicated SSH Key
&lt;/h2&gt;

&lt;p&gt;Never use your personal SSH key for an agent. Create a dedicated one on the server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh-keygen &lt;span class="nt"&gt;-t&lt;/span&gt; ed25519 &lt;span class="nt"&gt;-f&lt;/span&gt; ~/.ssh/garudust_pi &lt;span class="nt"&gt;-N&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt; &lt;span class="nt"&gt;-C&lt;/span&gt; &lt;span class="s2"&gt;"garudust-agent"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Copy the public key to the Pi:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh-copy-id &lt;span class="nt"&gt;-i&lt;/span&gt; ~/.ssh/garudust_pi.pub pi@192.168.1.50
&lt;span class="c"&gt;# or if Pi is not on the same network yet:&lt;/span&gt;
&lt;span class="c"&gt;# cat ~/.ssh/garudust_pi.pub | ssh pi@&amp;lt;pi-ip&amp;gt; "mkdir -p ~/.ssh &amp;amp;&amp;amp; cat &amp;gt;&amp;gt; ~/.ssh/authorized_keys"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify it works without a password prompt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh &lt;span class="nt"&gt;-i&lt;/span&gt; ~/.ssh/garudust_pi &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;BatchMode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;yes &lt;/span&gt;pi@192.168.1.50 &lt;span class="s2"&gt;"echo OK"&lt;/span&gt;
&lt;span class="c"&gt;# → OK&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 2 — Configure Garudust
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;~/.garudust/.env&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;ANTHROPIC_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;sk-ant-...       &lt;span class="c"&gt;# or any supported provider&lt;/span&gt;
&lt;span class="nv"&gt;TELEGRAM_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;123456789:AAFxxx    &lt;span class="c"&gt;# your bot token&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;~/.garudust/config.yaml&lt;/code&gt;:&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;security&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;approval_mode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;smart&lt;/span&gt;             &lt;span class="c1"&gt;# read-only commands auto-approve, writes ask first&lt;/span&gt;
  &lt;span class="na"&gt;terminal_sandbox&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ssh&lt;/span&gt;
  &lt;span class="na"&gt;ssh_host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;192.168.1.50"&lt;/span&gt;         &lt;span class="c1"&gt;# your Pi's IP (or hostname)&lt;/span&gt;
  &lt;span class="na"&gt;ssh_user&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pi"&lt;/span&gt;
  &lt;span class="na"&gt;ssh_port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;22&lt;/span&gt;
  &lt;span class="na"&gt;ssh_key_path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/home/youruser/.ssh/garudust_pi&lt;/span&gt;

&lt;span class="na"&gt;platforms&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;telegram&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;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8443&lt;/span&gt;
    &lt;span class="na"&gt;webhook_path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/telegram&lt;/span&gt;

&lt;span class="na"&gt;roles&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;default_role&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;~&lt;/span&gt;                  &lt;span class="c1"&gt;# strangers get no access&lt;/span&gt;
  &lt;span class="na"&gt;definitions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;owner&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;approval_mode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;auto&lt;/span&gt;          &lt;span class="c1"&gt;# full auto-approve for you&lt;/span&gt;
  &lt;span class="na"&gt;users&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;telegram&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR_TELEGRAM_USER_ID"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;owner&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Find your Telegram user ID by messaging &lt;a href="https://t.me/userinfobot" rel="noopener noreferrer"&gt;@userinfobot&lt;/a&gt;.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;garudust-server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  What You Can Now Do
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Read Sensor Data
&lt;/h3&gt;

&lt;p&gt;DS18B20 temperature sensor (1-Wire):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You:    temperature?
Agent:  24.3°C
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You:    log temperature every 10 seconds for 1 minute and tell me the average
Agent:  ✓ ran: for i in $(seq 6); do cat /sys/bus/w1/devices/28-*/w1_slave | grep t= | awk -F= '{print $2/1000}'; sleep 10; done
        Readings: 24.1, 24.2, 24.3, 24.3, 24.4, 24.2
        Average: 24.25°C
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;DHT22 humidity + temperature:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You:    humidity and temp from the DHT22
Agent:  ✓ ran: python3 ~/scripts/dht22_read.py
        Temperature: 24.3°C  Humidity: 58.2%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Control GPIO (Relay, LED, Motor)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You:    turn on the garden lights (relay on GPIO 18)
Agent:  ⚠ This will modify GPIO state — confirm?
You:    yes
Agent:  ✓ ran: python3 ~/scripts/relay.py on 18
        GPIO 18 HIGH — relay activated.

You:    set the LED strip to blue
Agent:  ✓ ran: python3 ~/scripts/led_strip.py color 0 0 255
        Done.

You:    turn everything off
Agent:  ✓ ran: python3 ~/scripts/all_off.py
        All GPIO outputs set LOW.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Check System Health
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You:    how is the Pi doing?
Agent:  ✓ ran: vcgencmd measure_temp &amp;amp;&amp;amp; free -h &amp;amp;&amp;amp; df -h / &amp;amp;&amp;amp; uptime
        CPU temp:    52.1°C
        RAM:         823 MB used / 1.9 GB total
        Disk:        12 GB used / 29 GB total (41%)
        Uptime:      14 days, 3 hours

You:    is anything hogging the CPU?
Agent:  ✓ ran: ps aux --sort=-%cpu | head -6
        python3 sensor_logger.py   4.2%
        node    homebridge          2.8%
        Everything else &amp;lt; 1%.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Manage Services
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You:    restart homebridge
Agent:  ⚠ This will restart the homebridge service — confirm?
You:    y
Agent:  ✓ ran: sudo systemctl restart homebridge
        homebridge restarted. Status: active (running).

You:    is the camera stream still up?
Agent:  ✓ ran: systemctl is-active mjpg-streamer
        active

You:    show me the last 20 lines of the sensor log
Agent:  ✓ ran: tail -20 ~/logs/sensor.log
        [2026-05-26 14:01] temp=24.3 hum=58.2
        [2026-05-26 14:02] temp=24.4 hum=57.9
        ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Run Your Own Scripts
&lt;/h3&gt;

&lt;p&gt;The agent can run any script already on the Pi. You don't need to teach it the commands — just describe what you want:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You:    water the plants for 30 seconds
Agent:  ✓ ran: python3 ~/scripts/sprinkler.py on 30
        Valve opened. Will close in 30 seconds.

You:    take a photo and tell me if the bird feeder is empty
Agent:  ✓ ran: raspistill -o /tmp/feeder.jpg -w 1280 -h 720
        [reads image]
        The feeder appears about 30% full — seeds visible on the left side.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Automate With Cron
&lt;/h3&gt;

&lt;p&gt;Set recurring checks without staying in the chat:&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="c1"&gt;# config.yaml&lt;/span&gt;
&lt;span class="na"&gt;cron&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;timezone&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Asia/Bangkok"&lt;/span&gt;
  &lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;schedule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;8&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*"&lt;/span&gt;
      &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;check&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Pi&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;health&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;and&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;send&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;morning&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;summary&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;to&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Telegram"&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;schedule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*/30&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*"&lt;/span&gt;
      &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;read&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;temperature&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;sensor;&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;if&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;above&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;70°C&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;send&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;warning&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;to&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Telegram"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the agent proactively messages you — no need to ask.&lt;/p&gt;




&lt;h2&gt;
  
  
  Security Notes
&lt;/h2&gt;

&lt;p&gt;A few things worth knowing before you deploy this:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The SSH connection is hardened automatically.&lt;/strong&gt; Garudust uses &lt;code&gt;BatchMode=yes&lt;/code&gt; (no password prompts), &lt;code&gt;StrictHostKeyChecking=accept-new&lt;/code&gt; (rejects changed host keys to prevent MITM), and clears the subprocess environment before spawning &lt;code&gt;ssh&lt;/code&gt; so no API keys or secrets reach the Pi.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Approval mode gates destructive commands.&lt;/strong&gt; With &lt;code&gt;approval_mode: smart&lt;/code&gt;, read-only commands (reading sensors, checking logs, &lt;code&gt;git status&lt;/code&gt;) run without asking. Anything that modifies state asks for confirmation first. Set &lt;code&gt;approval_mode: auto&lt;/code&gt; only for yourself after you've tested it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lock down the &lt;code&gt;pi&lt;/code&gt; user if you want to go further.&lt;/strong&gt; Restrict the SSH key to specific commands using &lt;code&gt;command=&lt;/code&gt; in &lt;code&gt;authorized_keys&lt;/code&gt;, or run the agent's SSH user in a dedicated account with only the scripts it needs to call.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Pi never exposes a port to the internet.&lt;/strong&gt; The agent SSHes &lt;em&gt;out&lt;/em&gt; from the server to the Pi — the Pi only needs port 22 open to your server, not to the world.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Multiple Pis&lt;/strong&gt; — point different agent sessions at different hosts; or build a thin wrapper script that SSHes to the right Pi based on what you ask&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Voice control&lt;/strong&gt; — pair with &lt;a href="https://dev.to/garudust"&gt;iApp TTS&lt;/a&gt; to get spoken responses back on a speaker connected to the Pi&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Skill learning&lt;/strong&gt; — after you run a workflow a few times, Garudust automatically writes it as a reusable skill so you don't need to describe it step by step anymore&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;The full source for Garudust is at &lt;a href="https://github.com/garudust-org/garudust-agent" rel="noopener noreferrer"&gt;garudust-org/garudust-agent&lt;/a&gt;. The SSH sandbox landed in v0.13 — grab the latest binary from &lt;a href="https://github.com/garudust-org/garudust-agent/releases/latest" rel="noopener noreferrer"&gt;Releases&lt;/a&gt; or build from source with &lt;code&gt;cargo build --release&lt;/code&gt;.&lt;/p&gt;

</description>
      <category>raspberrypi</category>
      <category>ai</category>
      <category>devops</category>
      <category>iot</category>
    </item>
    <item>
      <title>SSH Remote Terminal ใน Garudust Agent — ให้ AI สั่งงานบน Server จริงได้เลย</title>
      <dc:creator>Garudust</dc:creator>
      <pubDate>Tue, 26 May 2026 15:18:51 +0000</pubDate>
      <link>https://dev.to/garudust/ssh-remote-terminal-ain-garudust-agent-aih-ai-sangngaanbn-server-cchringaidely-be3</link>
      <guid>https://dev.to/garudust/ssh-remote-terminal-ain-garudust-agent-aih-ai-sangngaanbn-server-cchringaidely-be3</guid>
      <description>&lt;p&gt;ก่อนหน้านี้ Garudust Agent รัน terminal tool ได้สองแบบ — บนเครื่อง local โดยตรง หรือ wrap ผ่าน Docker container เพื่อ isolation&lt;/p&gt;

&lt;p&gt;v0.13 เพิ่ม &lt;strong&gt;SSH sandbox&lt;/strong&gt; — แบบที่สาม ที่ส่งทุก command ผ่าน &lt;code&gt;ssh&lt;/code&gt; ไปรันบน remote host แทน&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;security&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;terminal_sandbox&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ssh&lt;/span&gt;
  &lt;span class="na"&gt;ssh_host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;build.example.com"&lt;/span&gt;
  &lt;span class="na"&gt;ssh_user&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;deploy"&lt;/span&gt;
  &lt;span class="na"&gt;ssh_port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;22&lt;/span&gt;
  &lt;span class="na"&gt;ssh_key_path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;~/.ssh/deploy_key&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ไม่ต้องแก้โค้ด ไม่ต้องเพิ่ม dependency — ใช้ &lt;code&gt;ssh&lt;/code&gt; binary ที่มีอยู่แล้ว&lt;/p&gt;




&lt;h2&gt;
  
  
  ทำไมถึงมีประโยชน์?
&lt;/h2&gt;

&lt;p&gt;ปัญหาของ &lt;code&gt;terminal_sandbox: none&lt;/code&gt; และ &lt;code&gt;docker&lt;/code&gt; คือทั้งคู่รันบนเครื่องที่ garudust อยู่เท่านั้น ถ้า agent อยู่บน laptop แต่อยากให้มันจัดการ production server — ก็ทำไม่ได้&lt;/p&gt;

&lt;p&gt;SSH sandbox แก้ปัญหานี้ตรงๆ&lt;/p&gt;




&lt;h2&gt;
  
  
  Use Case 1 — Build &amp;amp; Deploy บน Remote Server
&lt;/h2&gt;

&lt;p&gt;สมมติมี build server แยกต่างหาก ไม่อยากให้ laptop ต้อง build เอง:&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;security&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;terminal_sandbox&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ssh&lt;/span&gt;
  &lt;span class="na"&gt;ssh_host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;build.internal.example.com"&lt;/span&gt;
  &lt;span class="na"&gt;ssh_user&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ci"&lt;/span&gt;
  &lt;span class="na"&gt;ssh_key_path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;~/.ssh/ci_key&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;แล้วคุยกับ agent ได้เลย:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"build release และ deploy ไปที่ /opt/app"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Agent จะรัน:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;cargo build --release&lt;/code&gt; — บน build server&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;systemctl restart myapp&lt;/code&gt; — บน build server&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;เครื่อง local ไม่ต้องมี Rust, ไม่ต้องมี prod credentials ไม่มีอะไรรั่วออกมา&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Use Case 2 — Ops Assistant จาก Telegram
&lt;/h2&gt;

&lt;p&gt;ผูก Garudust เข้า Telegram แล้วถามสุขภาพ server ได้ตลอดเวลา:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;คุณ: disk เต็มไหม
Agent: ✓ terminal: df -h /
       / → 23% used (226 GB / 981 GB)
       ปกติดีครับ

คุณ: process ที่กิน RAM มากสุด 5 อันดับ
Agent: ✓ terminal: ps aux --sort=-%mem | head -6
       1. postgres   12.3%
       2. node       8.1%
       ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ทุก command ผ่าน hardline check + approval gate ก่อนเสมอ — ไม่มีทางที่ prompt injection จะสั่ง &lt;code&gt;rm -rf&lt;/code&gt; ได้&lt;/p&gt;




&lt;h2&gt;
  
  
  Use Case 3 — ควบคุม Raspberry Pi ผ่าน LINE
&lt;/h2&gt;

&lt;p&gt;Pi อยู่ใน home network ไม่ได้ expose port ออกอินเทอร์เน็ต ให้ garudust-server อยู่บน Pi เดียวกันหรือเครื่องในวง LAN แล้วรับคำสั่งจาก LINE:&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;security&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;terminal_sandbox&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ssh&lt;/span&gt;
  &lt;span class="na"&gt;ssh_host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;192.168.1.50"&lt;/span&gt;   &lt;span class="c1"&gt;# Pi อยู่ใน LAN&lt;/span&gt;
  &lt;span class="na"&gt;ssh_user&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pi"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"เปิดสปริงเกลอร์ 5 นาที"
→ terminal: python3 ~/scripts/sprinkler.py on 300

"อุณหภูมิตอนนี้เท่าไหร่"
→ terminal: cat /sys/bus/w1/devices/28-*/w1_slave | grep t= | awk -F= '{printf "%.1f°C\n", $2/1000}'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;สั่งงานผ่าน LINE ได้เหมือน SSH เข้าไปเอง แต่ไม่ต้องเปิด terminal&lt;/p&gt;




&lt;h2&gt;
  
  
  Use Case 4 — Isolated Environment โดยไม่ใช้ Docker
&lt;/h2&gt;

&lt;p&gt;บางเครื่อง Docker ไม่ available (ARM SBC, embedded, VPS ราคาถูก) แต่อยากได้ isolation — ใช้ VM หรือ LXC container ที่มี SSH แทน:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;laptop (garudust) → SSH → sandbox VM → รัน untrusted code
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ถ้า agent ทำอะไรผิดพลาด snapshot VM กลับมาได้ทันที โดยที่ local filesystem ไม่โดนแตะเลย&lt;/p&gt;




&lt;h2&gt;
  
  
  Security ที่ได้มาโดยอัตโนมัติ
&lt;/h2&gt;

&lt;p&gt;SSH sandbox ไม่ได้แค่ส่ง command ไป remote — มี hardening ติดมาด้วยทุกครั้ง:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Property&lt;/th&gt;
&lt;th&gt;วิธีทำ&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;ไม่มี interactive prompt&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;BatchMode=yes&lt;/code&gt; — fail ทันทีถ้า key auth ล้มเหลว&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ป้องกัน MITM&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;StrictHostKeyChecking=accept-new&lt;/code&gt; — เชื่อ host แรก, ปฏิเสธถ้า key เปลี่ยน&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Connection timeout&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;ConnectTimeout=min(timeout, 30)&lt;/code&gt; — ไม่แขวน process&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ป้องกัน flag injection&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;--&lt;/code&gt; ก่อน command เสมอ — command ที่ขึ้นต้นด้วย &lt;code&gt;-&lt;/code&gt; ไม่ถูกตีความเป็น ssh flag&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Secrets ไม่รั่ว&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;env_clear()&lt;/code&gt; ก่อน spawn ssh — API key ไม่ถึง remote host&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  ลองได้เลย — ไม่ต้องมี server จริง
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1. สร้าง SSH key สำหรับทดสอบ&lt;/span&gt;
ssh-keygen &lt;span class="nt"&gt;-t&lt;/span&gt; ed25519 &lt;span class="nt"&gt;-f&lt;/span&gt; /tmp/test_key &lt;span class="nt"&gt;-N&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;

&lt;span class="c"&gt;# 2. รัน SSH container&lt;/span&gt;
&lt;span class="nv"&gt;PUBKEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /tmp/test_key.pub&lt;span class="si"&gt;)&lt;/span&gt;
docker build &lt;span class="nt"&gt;-t&lt;/span&gt; sshd-test - &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
FROM ubuntu:24.04
RUN apt-get update -qq &amp;amp;&amp;amp; apt-get install -y openssh-server &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="sh"&gt;
    &amp;amp;&amp;amp; useradd -m testuser &amp;amp;&amp;amp; mkdir -p /home/testuser/.ssh /var/run/sshd
ARG PUBKEY
RUN echo "&lt;/span&gt;&lt;span class="nv"&gt;$PUBKEY&lt;/span&gt;&lt;span class="sh"&gt;" &amp;gt; /home/testuser/.ssh/authorized_keys &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="sh"&gt;
    &amp;amp;&amp;amp; chmod 700 /home/testuser/.ssh &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="sh"&gt;
    &amp;amp;&amp;amp; chmod 600 /home/testuser/.ssh/authorized_keys &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="sh"&gt;
    &amp;amp;&amp;amp; chown -R testuser:testuser /home/testuser/.ssh &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="sh"&gt;
    &amp;amp;&amp;amp; ssh-keygen -A
EXPOSE 22
CMD ["/usr/sbin/sshd", "-D"]
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; sshd-test &lt;span class="nt"&gt;-p&lt;/span&gt; 2222:22 &lt;span class="nt"&gt;--build-arg&lt;/span&gt; &lt;span class="nv"&gt;PUBKEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PUBKEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; sshd-test

&lt;span class="c"&gt;# 3. ทดสอบกับ garudust&lt;/span&gt;
&lt;span class="nv"&gt;GARUDUST_TERMINAL_SANDBOX&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ssh &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nv"&gt;GARUDUST_SSH_HOST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;127.0.0.1 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nv"&gt;GARUDUST_SSH_USER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;testuser &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nv"&gt;GARUDUST_SSH_PORT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2222 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nv"&gt;GARUDUST_SSH_KEY_PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/tmp/test_key &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nv"&gt;GARUDUST_APPROVAL_MODE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;auto &lt;span class="se"&gt;\&lt;/span&gt;
  garudust &lt;span class="s2"&gt;"บอก OS version, จำนวน CPU และ memory บน remote host"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ผลที่ได้ (จาก container):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- ระบบปฏิบัติการ: Ubuntu 24.04.4 LTS (Noble Numbat)
- จำนวน CPU: 14 แกน
- หน่วยความจำ: 7.7 GiB (ใช้ไป 544 MiB, ว่าง 6.7 GiB)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;hostname&lt;/code&gt; คืน container ID → ยืนยันว่า command รันบน remote จริง ไม่ใช่ local&lt;/p&gt;




&lt;h2&gt;
  
  
  สรุป
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Mode&lt;/th&gt;
&lt;th&gt;เหมาะกับ&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;none&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;dev machine, ทดสอบ, low-risk tasks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;production local, untrusted code, ต้องการ filesystem isolation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ssh&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;remote server, IoT/Pi, build farm, VM sandbox, ไม่มี Docker&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;ติดตั้งจาก &lt;a href="https://github.com/garudust-org/garudust-agent/releases/latest" rel="noopener noreferrer"&gt;GitHub Releases&lt;/a&gt; หรือ:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cargo &lt;span class="nb"&gt;install &lt;/span&gt;garudust
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Source: &lt;a href="https://github.com/garudust-org/garudust-agent" rel="noopener noreferrer"&gt;garudust-org/garudust-agent&lt;/a&gt;&lt;/p&gt;

</description>
      <category>rust</category>
      <category>ai</category>
      <category>devops</category>
      <category>ssh</category>
    </item>
    <item>
      <title>Event Triggers บน Garudust</title>
      <dc:creator>Garudust</dc:creator>
      <pubDate>Sat, 23 May 2026 03:24:03 +0000</pubDate>
      <link>https://dev.to/garudust/event-triggers-bn-garudust-4ji3</link>
      <guid>https://dev.to/garudust/event-triggers-bn-garudust-4ji3</guid>
      <description>&lt;p&gt;core ของ Garudust เปิดเผย primitive พื้นฐานตัวเดียว: &lt;code&gt;agent.run(task)&lt;/code&gt; ทุก entry point — ไม่ว่าจะเป็นข้อความจากแชท, cron job, หรือ webhook call — ล้วนลงเอยที่ call เดียวกันนี้ทั้งสิ้น นั่นหมายความว่าระบบภายนอกใดก็ตามที่ส่ง HTTP POST ได้ ก็สามารถเป็น event trigger ให้ Garudust ได้เลย&lt;/p&gt;

&lt;p&gt;บทความนี้จะอธิบายว่ามันทำงานอย่างไรในปัจจุบัน, pattern ที่ใช้งานได้จริงใน production และ use case ที่เป็นรูปธรรม&lt;/p&gt;




&lt;h2&gt;
  
  
  Webhook Adapter ทำงานอย่างไร
&lt;/h2&gt;

&lt;p&gt;เมื่อ Garudust ถูกตั้งค่าให้ใช้ webhook platform มันจะเปิด Axum HTTP server และลงทะเบียน POST endpoint ไว้ที่ path ที่คุณกำหนด request ขาเข้าจะมีหน้าตาแบบนี้:&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;"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;"A new billing invoice has arrived from Acme Corp for $4,200."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"callback_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://your-system.example.com/garudust/reply"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"user_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"billing-watcher"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"session_key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"billing-acme-corp"&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;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Required&lt;/th&gt;
&lt;th&gt;คำอธิบาย&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;text&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;Task prompt ที่ agent จะนำไปรัน&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;callback_url&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;URL ที่ Garudust จะ POST คำตอบกลับมา&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;user_id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;optional&lt;/td&gt;
&lt;td&gt;ใช้สำหรับ role-based access control&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;session_key&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;optional&lt;/td&gt;
&lt;td&gt;ตรึง conversation history ไว้; ถ้าไม่ระบุจะ default เป็น &lt;code&gt;webhook:{callback_url}&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Garudust จะห่อข้อมูลนี้เป็น &lt;code&gt;InboundMessage&lt;/code&gt;, ส่งผ่าน &lt;code&gt;GatewayHandler&lt;/code&gt;, spawn &lt;code&gt;agent.run()&lt;/code&gt; ขึ้นมา และเมื่อ agent ทำงานเสร็จก็จะ POST คำตอบกลับไปที่ &lt;code&gt;callback_url&lt;/code&gt;:&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;"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;"Invoice from Acme Corp for $4,200 — categorised as SaaS/Infrastructure. Flagged for approval above $3,000 threshold. Draft approval request sent to #finance."&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;HTTP response ที่ตอบกลับ POST ของคุณทันทีคือ &lt;code&gt;202 Accepted&lt;/code&gt; — agent ทำงานแบบ asynchronous&lt;/p&gt;




&lt;h2&gt;
  
  
  Security
&lt;/h2&gt;

&lt;p&gt;Garudust ตรวจสอบ HMAC-SHA256 signature ทุก request ที่เข้ามา ตั้ง shared secret ใน config แล้ว sign ทุก POST ขาออกด้วย:&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;X-Hub-Signature-256: sha256=&amp;lt;hex(HMAC-SHA256(secret, raw_body))&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Request ที่ไม่มี signature ถูกต้องจะถูกปฏิเสธด้วย &lt;code&gt;401&lt;/code&gt; และ callback URL ที่ชี้ไป private IP (&lt;code&gt;192.168.x.x&lt;/code&gt;, &lt;code&gt;10.x.x.x&lt;/code&gt;, &lt;code&gt;localhost&lt;/code&gt;) ก็ถูก block โดย network guard ด้วย — agent ไม่สามารถถูกบังคับให้ call back หา internal infrastructure ได้&lt;/p&gt;




&lt;h2&gt;
  
  
  Core Pattern
&lt;/h2&gt;

&lt;p&gt;การแบ่งหน้าที่ชัดเจนมาก:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ระบบภายนอก                   Garudust
─────────────────             ──────────────────────────
Event source (email,    →     Webhook adapter
  calendar, DB, queue)
Filter / match logic    →     โค้ดของคุณ (ก่อน POST)
Task description        →     agent.run(task)
Result handling         →     handler ที่ callback_url ของคุณ
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ระบบของคุณเป็นเจ้าของ filter — Garudust เป็นเจ้าของการรัน agent ทั้งสองฝั่งไม่จำเป็นต้องรู้โครงสร้างภายในของกันและกันเลย&lt;/p&gt;




&lt;h2&gt;
  
  
  Use Cases
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Billing Email Monitor
&lt;/h3&gt;

&lt;p&gt;บริการประมวลผลอีเมลคอยดักจับอีเมลจากผู้ส่งที่เป็นระบบ billing เมื่อเจออีเมลที่ match ก็ดึง subject, ผู้ส่ง และจำนวนเงิน แล้ว trigger Garudust:&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;"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;"New invoice received: Stripe — $1,840 for May 2026. Attach to this month's expense report and notify the finance channel if it exceeds the $1,500 alert threshold."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"callback_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://your-ops.example.com/hooks/garudust"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"session_key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"finance-inbox"&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;Agent ใช้ tool ของมันอ่านไฟล์ expense report, เพิ่ม line item และโพสต์แจ้ง Slack บริการอีเมลทำแค่ match ผู้ส่งแล้วยิง — ไม่ต้องรู้อะไรเกี่ยวกับ expense report หรือ Slack เลย&lt;/p&gt;




&lt;h3&gt;
  
  
  2. GitHub PR Review Gate
&lt;/h3&gt;

&lt;p&gt;GitHub Actions workflow เรียก Garudust หลังมี PR เปิดเข้ามาที่ branch &lt;code&gt;main&lt;/code&gt; โดย workflow สร้าง payload จาก GitHub context:&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;"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;"PR #214 opened by @alice: 'feat: add OAuth2 PKCE flow'. Changed files: src/auth/oauth.rs, src/auth/pkce.rs, tests/auth_integration.rs. Diff summary attached. Review for security issues in the auth flow and post a summary comment."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"callback_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://your-ci.example.com/garudust/pr-review"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"session_key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"pr-214"&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;webhook ของ GitHub ยิง workflow → workflow สร้าง task text → Garudust รีวิว และ &lt;code&gt;session_key&lt;/code&gt; ที่ผูกกับเลข PR ทำให้ trigger ครั้งถัดไป (commit ใหม่, ขอรีวิวซ้ำ) ต่อเนื่องใน conversation thread เดิม&lt;/p&gt;




&lt;h3&gt;
  
  
  3. Database Anomaly Alert
&lt;/h3&gt;

&lt;p&gt;Monitoring job query ฐานข้อมูลตามตาราง schedule แล้วตรวจ aggregate metric เมื่อ metric ข้ามเกณฑ์ แทนที่จะส่ง static alert มันยิง Garudust แทน:&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;"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;"Anomaly detected: orders table insert rate dropped 94% in the last 10 minutes (baseline 340/min, current 19/min). Last successful insert: 09:42 UTC. Investigate root cause and summarise for on-call."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"callback_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://ops.example.com/garudust/incidents"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"session_key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"incident-2026-05-23-orders"&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;Agent สามารถใช้ terminal หรือ database tool รัน query เพิ่มเติม, ตรวจ deploy ล่าสุด และสรุป incident แบบมีโครงสร้าง — monitoring job ทำแค่ตรวจ threshold breach เท่านั้น&lt;/p&gt;




&lt;h3&gt;
  
  
  4. Calendar External-Attendee Watch
&lt;/h3&gt;

&lt;p&gt;Integration layer poll Google Calendar (หรือรับ push notification) แล้วยิง Garudust เมื่อมี event ถูกสร้างขึ้นพร้อม attendee ที่ domain ไม่ตรงกับองค์กรของคุณ:&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;"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;"New calendar event: 'Q3 partnership discussion' on 2026-06-04 14:00 UTC. External attendees: jane@partner.com, bob@partner.com. Prepare a one-page briefing on Partner Corp using the CRM notes and recent email thread."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"callback_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://your-system.example.com/garudust/calendar"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"session_key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"meeting-prep-2026-06-04"&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;Calendar integration เป็นเจ้าของ logic กรอง "external attendee" — Garudust เป็นเจ้าของการสร้าง briefing&lt;/p&gt;




&lt;h3&gt;
  
  
  5. Queue Worker Trigger
&lt;/h3&gt;

&lt;p&gt;Background worker ดึงงานออกจาก task queue (SQS, Redis, RabbitMQ) แล้วส่งแต่ละชิ้นให้ Garudust ทำ เหมาะกับงานที่ปริมาณผันแปรและต้องการให้ agent จัดการแต่ละชิ้นในแบบของตัวเอง:&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;"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;"Customer support ticket #8821 (priority: high): User reports that export to CSV silently truncates rows above 10,000. Reproduce the scenario, identify the code path responsible, and draft a fix description for the engineering team."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"callback_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://support.example.com/garudust/tickets"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"session_key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ticket-8821"&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;Queue worker dequeue, จัดรูป task text, ยิง webhook หลาย ticket สามารถรันเป็น concurrent agent session พร้อมกันได้&lt;/p&gt;




&lt;h2&gt;
  
  
  Session Keys และความต่อเนื่อง
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;session_key&lt;/code&gt; คือสิ่งที่ทำให้ event trigger มีประโยชน์เกินกว่างานแบบ one-shot เมื่อคุณตรึง key ไว้ webhook call ทั้งหมดที่ใช้ key เดียวกันจะแชร์ conversation history ร่วมกัน หมายความว่า:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PR review trigger บน commit 1 และ re-review trigger บน commit 2 คือ &lt;strong&gt;conversation เดียวกัน&lt;/strong&gt; — agent จำสิ่งที่พูดไปก่อนหน้าได้&lt;/li&gt;
&lt;li&gt;Incident trigger และ "ตอนนี้เป็นยังไงบ้าง?" ที่ on-call engineer ถามทีหลังใช้ context เดียวกัน&lt;/li&gt;
&lt;li&gt;Billing session สะสม invoice ทั้งเดือนจากหลาย trigger ก่อนสร้าง monthly summary&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;ถ้าต้องการแยก session ออกจากกันสมบูรณ์ (แต่ละ event เป็นอิสระ) ก็ไม่ต้องระบุ &lt;code&gt;session_key&lt;/code&gt; — Garudust จะใช้ &lt;code&gt;callback_url&lt;/code&gt; เป็น key แทน ทำให้ได้ context ใหม่ต่อ callback target ที่ไม่ซ้ำกัน&lt;/p&gt;




&lt;h2&gt;
  
  
  สิ่งที่ Pattern นี้ยังไม่ครอบคลุม
&lt;/h2&gt;

&lt;p&gt;Webhook adapter คือ &lt;strong&gt;push target&lt;/strong&gt; — ระบบภายนอกต้องเป็นฝ่ายเริ่มการเชื่อมต่อ ถ้าต้องการให้ Garudust ดึงข้อมูลจากแหล่งต้นทางเอง (ตรวจ inbox, poll API, เฝ้าดูไฟล์) โดยไม่ต้องมี scheduler นั้นต้องใช้ cron job ที่คอย poll หรือรอ primitive &lt;code&gt;watch&lt;/code&gt;/&lt;code&gt;filter&lt;/code&gt; ที่ยังไม่มีในปัจจุบัน&lt;/p&gt;

&lt;p&gt;สำหรับ use case ที่เป็น push-based จริงๆ (GitHub webhook, queue worker, calendar push notification, email routing service) สถาปัตยกรรมปัจจุบันรองรับได้ครบและการแบ่งหน้าที่ก็ชัดเจนดีอยู่แล้ว&lt;/p&gt;

</description>
      <category>rust</category>
      <category>ai</category>
      <category>agents</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Event Triggers on Garudust</title>
      <dc:creator>Garudust</dc:creator>
      <pubDate>Sat, 23 May 2026 03:10:52 +0000</pubDate>
      <link>https://dev.to/garudust/event-triggers-on-garudust-5h8b</link>
      <guid>https://dev.to/garudust/event-triggers-on-garudust-5h8b</guid>
      <description>&lt;p&gt;Garudust's agent core exposes a single base primitive: &lt;code&gt;agent.run(task)&lt;/code&gt;. Every entry point — a chat message, a cron job, a webhook call — eventually resolves to that same call. This means any external system that can send an HTTP POST can act as an event trigger.&lt;/p&gt;

&lt;p&gt;This article covers how that works today, the patterns that hold up in production, and concrete use cases.&lt;/p&gt;




&lt;h2&gt;
  
  
  How the Webhook Adapter Works
&lt;/h2&gt;

&lt;p&gt;When Garudust is configured with the webhook platform, it starts an Axum HTTP server and registers a POST endpoint at a path you configure. An inbound request looks like this:&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;"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;"A new billing invoice has arrived from Acme Corp for $4,200."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"callback_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://your-system.example.com/garudust/reply"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"user_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"billing-watcher"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"session_key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"billing-acme-corp"&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;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Required&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;text&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;The task prompt the agent will run&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;callback_url&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;Where Garudust POSTs the agent's reply&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;user_id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;optional&lt;/td&gt;
&lt;td&gt;Used for role-based access control&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;session_key&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;optional&lt;/td&gt;
&lt;td&gt;Pins conversation history; defaults to &lt;code&gt;webhook:{callback_url}&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Garudust wraps this into an &lt;code&gt;InboundMessage&lt;/code&gt;, passes it through &lt;code&gt;GatewayHandler&lt;/code&gt;, spawns &lt;code&gt;agent.run()&lt;/code&gt;, and when the agent finishes it POSTs the reply back to &lt;code&gt;callback_url&lt;/code&gt;:&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;"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;"Invoice from Acme Corp for $4,200 — categorised as SaaS/Infrastructure. Flagged for approval above $3,000 threshold. Draft approval request sent to #finance."&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 HTTP response to your POST is &lt;code&gt;202 Accepted&lt;/code&gt; immediately — the agent runs asynchronously.&lt;/p&gt;




&lt;h2&gt;
  
  
  Security
&lt;/h2&gt;

&lt;p&gt;Garudust verifies an HMAC-SHA256 signature on every inbound request. Set a shared secret in your config, and sign every outbound POST with:&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;X-Hub-Signature-256: sha256=&amp;lt;hex(HMAC-SHA256(secret, raw_body))&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Requests without a valid signature are rejected with &lt;code&gt;401&lt;/code&gt;. Private IP callback URLs (&lt;code&gt;192.168.x.x&lt;/code&gt;, &lt;code&gt;10.x.x.x&lt;/code&gt;, &lt;code&gt;localhost&lt;/code&gt;) are also blocked by the network guard — the agent cannot be made to call back to internal infrastructure.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Core Pattern
&lt;/h2&gt;

&lt;p&gt;The separation of concerns is explicit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;External system               Garudust
─────────────────             ──────────────────────────
Event source (email,    →     Webhook adapter
  calendar, DB, queue)
Filter / match logic    →     Your code (pre-POST)
Task description        →     agent.run(task)
Result handling         →     Your callback_url handler
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your system owns the filter. Garudust owns the agent execution. Neither needs to know the other's internal structure.&lt;/p&gt;




&lt;h2&gt;
  
  
  Use Cases
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Billing Email Monitor
&lt;/h3&gt;

&lt;p&gt;An email processing service watches your inbox for messages from known billing senders. When one matches, it extracts the subject, sender, and amount, then triggers Garudust:&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;"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;"New invoice received: Stripe — $1,840 for May 2026. Attach to this month's expense report and notify the finance channel if it exceeds the $1,500 alert threshold."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"callback_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://your-ops.example.com/hooks/garudust"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"session_key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"finance-inbox"&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 agent uses its tools to read the expense report file, append the line item, and post to Slack. Your email service only had to match sender and fire — it did not need to know anything about expense reports or Slack.&lt;/p&gt;




&lt;h3&gt;
  
  
  2. GitHub PR Review Gate
&lt;/h3&gt;

&lt;p&gt;A GitHub Actions workflow calls Garudust after a PR is opened against &lt;code&gt;main&lt;/code&gt;. The workflow builds the payload from the GitHub context:&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;"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;"PR #214 opened by @alice: 'feat: add OAuth2 PKCE flow'. Changed files: src/auth/oauth.rs, src/auth/pkce.rs, tests/auth_integration.rs. Diff summary attached. Review for security issues in the auth flow and post a summary comment."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"callback_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://your-ci.example.com/garudust/pr-review"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"session_key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"pr-214"&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;GitHub's webhook fires the workflow; the workflow builds the task text; Garudust runs the review. The &lt;code&gt;session_key&lt;/code&gt; is tied to the PR number so follow-up triggers (new commits, re-review requests) continue the same conversation thread.&lt;/p&gt;




&lt;h3&gt;
  
  
  3. Database Anomaly Alert
&lt;/h3&gt;

&lt;p&gt;A monitoring job queries your database on a schedule and checks aggregate metrics. When a metric crosses a threshold, it fires Garudust instead of just sending a static alert:&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;"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;"Anomaly detected: orders table insert rate dropped 94% in the last 10 minutes (baseline 340/min, current 19/min). Last successful insert: 09:42 UTC. Investigate root cause and summarise for on-call."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"callback_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://ops.example.com/garudust/incidents"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"session_key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"incident-2026-05-23-orders"&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 agent can use its terminal or database tools to run follow-up queries, check recent deploys, and produce a structured incident summary — the monitoring job only needed to detect the threshold breach.&lt;/p&gt;




&lt;h3&gt;
  
  
  4. Calendar External-Attendee Watch
&lt;/h3&gt;

&lt;p&gt;An integration layer polls Google Calendar (or receives push notifications) and fires Garudust when an event is created with an attendee whose domain does not match your organisation:&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;"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;"New calendar event: 'Q3 partnership discussion' on 2026-06-04 14:00 UTC. External attendees: jane@partner.com, bob@partner.com. Prepare a one-page briefing on Partner Corp using the CRM notes and recent email thread."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"callback_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://your-system.example.com/garudust/calendar"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"session_key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"meeting-prep-2026-06-04"&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 calendar integration owns the "external attendee" filter logic. Garudust owns the briefing generation.&lt;/p&gt;




&lt;h3&gt;
  
  
  5. Queue Worker Trigger
&lt;/h3&gt;

&lt;p&gt;A background worker drains a task queue (SQS, Redis, RabbitMQ) and dispatches each item to Garudust. This is useful for workloads where volume is variable and you want the agent to handle each item at its own pace:&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;"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;"Customer support ticket #8821 (priority: high): User reports that export to CSV silently truncates rows above 10,000. Reproduce the scenario, identify the code path responsible, and draft a fix description for the engineering team."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"callback_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://support.example.com/garudust/tickets"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"session_key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ticket-8821"&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 queue worker dequeues, formats the task text, fires the webhook. Multiple tickets run as concurrent agent sessions.&lt;/p&gt;




&lt;h2&gt;
  
  
  Session Keys and Continuity
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;session_key&lt;/code&gt; is what makes event triggers useful beyond one-shot tasks. When you pin a key, all webhook calls with that key share the same conversation history. This means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A PR review trigger on commit 1 and a re-review trigger on commit 2 are the &lt;strong&gt;same conversation&lt;/strong&gt; — the agent remembers what it said before.&lt;/li&gt;
&lt;li&gt;An incident trigger and a follow-up "what's the status?" call from your on-call engineer share context.&lt;/li&gt;
&lt;li&gt;A billing session can accumulate all invoices for the month across multiple triggers before generating the monthly summary.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you want complete isolation (each event is independent), omit &lt;code&gt;session_key&lt;/code&gt; — Garudust will key the session to the &lt;code&gt;callback_url&lt;/code&gt;, giving you a fresh context per unique callback target.&lt;/p&gt;




&lt;h2&gt;
  
  
  What This Pattern Does Not Cover
&lt;/h2&gt;

&lt;p&gt;The webhook adapter is a &lt;strong&gt;push target&lt;/strong&gt; — your external system must initiate the call. If you need Garudust to pull from a source (check an inbox, poll an API, watch a file) on its own without a scheduler, that requires either a cron job that polls, or a future &lt;code&gt;watch&lt;/code&gt;/&lt;code&gt;filter&lt;/code&gt; primitive that does not exist yet.&lt;/p&gt;

&lt;p&gt;For use cases that are genuinely push-based (GitHub webhooks, queue workers, calendar push notifications, email routing services), the current architecture is sufficient and the separation of concerns is clean.&lt;/p&gt;

</description>
      <category>rust</category>
      <category>ai</category>
      <category>agents</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Cron &amp; Scheduled Tasks in Garudust Agent — Autonomous Agents That Run Without You</title>
      <dc:creator>Garudust</dc:creator>
      <pubDate>Thu, 21 May 2026 07:17:45 +0000</pubDate>
      <link>https://dev.to/garudust/cron-scheduled-tasks-in-garudust-autonomous-agents-that-run-without-you-5ecg</link>
      <guid>https://dev.to/garudust/cron-scheduled-tasks-in-garudust-autonomous-agents-that-run-without-you-5ecg</guid>
      <description>&lt;p&gt;Most AI agents wait. They sit idle until a human types something, respond, then go back to waiting.&lt;/p&gt;

&lt;p&gt;Garudust Agent can be different. With &lt;code&gt;garudust-cron&lt;/code&gt;, you schedule tasks using standard cron syntax — the agent wakes up, runs a full LLM loop with all its tools, and goes back to sleep. No human required.&lt;/p&gt;

&lt;p&gt;This post shows you exactly how to configure it, with the correct syntax pulled straight from the source.&lt;/p&gt;




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

&lt;p&gt;&lt;code&gt;garudust-cron&lt;/code&gt; is a crate within the Garudust workspace. When a scheduled trigger fires, it calls &lt;code&gt;agent.run(task)&lt;/code&gt; — the same code path as a user typing a message. The agent has access to all its configured tools: file read/write, terminal, RAG, web search, and anything else you've enabled.&lt;/p&gt;

&lt;p&gt;Cron runs as part of &lt;code&gt;garudust-server&lt;/code&gt;. There is no separate daemon.&lt;/p&gt;




&lt;h2&gt;
  
  
  Three Ways to Set Up Cron Jobs
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. &lt;code&gt;config.yaml&lt;/code&gt; — Permanent, survives restarts
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# ~/.garudust/config.yaml&lt;/span&gt;
&lt;span class="na"&gt;cron&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;schedule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;9&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;1-5"&lt;/span&gt;   &lt;span class="c1"&gt;# weekdays at 09:00 (server timezone)&lt;/span&gt;
      &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Write&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;morning&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;briefing&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;and&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;append&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;it&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;to&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;~/briefing.md"&lt;/span&gt;

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;schedule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;18&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;5"&lt;/span&gt;    &lt;span class="c1"&gt;# Fridays at 18:00&lt;/span&gt;
      &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Summarise&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;this&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;week's&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;git&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;commits&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;and&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;save&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;to&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;~/weekly.md"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;CronJob&lt;/code&gt; has exactly two fields: &lt;code&gt;schedule&lt;/code&gt; and &lt;code&gt;task&lt;/code&gt;. Nothing else.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;schedule&lt;/code&gt; uses &lt;strong&gt;6-field&lt;/strong&gt; cron syntax (seconds first):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;┌───────── &lt;span class="n"&gt;second&lt;/span&gt; (&lt;span class="m"&gt;0&lt;/span&gt;–&lt;span class="m"&gt;59&lt;/span&gt;)
│ ┌─────── &lt;span class="n"&gt;minute&lt;/span&gt; (&lt;span class="m"&gt;0&lt;/span&gt;–&lt;span class="m"&gt;59&lt;/span&gt;)
│ │ ┌───── &lt;span class="n"&gt;hour&lt;/span&gt; (&lt;span class="m"&gt;0&lt;/span&gt;–&lt;span class="m"&gt;23&lt;/span&gt;)
│ │ │ ┌─── &lt;span class="n"&gt;day&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="n"&gt;month&lt;/span&gt; (&lt;span class="m"&gt;1&lt;/span&gt;–&lt;span class="m"&gt;31&lt;/span&gt;)
│ │ │ │ ┌─ &lt;span class="n"&gt;month&lt;/span&gt; (&lt;span class="m"&gt;1&lt;/span&gt;–&lt;span class="m"&gt;12&lt;/span&gt;)
│ │ │ │ │ ┌ &lt;span class="n"&gt;day&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="n"&gt;week&lt;/span&gt; (&lt;span class="m"&gt;0&lt;/span&gt;–&lt;span class="m"&gt;6&lt;/span&gt;, &lt;span class="n"&gt;Sun&lt;/span&gt;=&lt;span class="m"&gt;0&lt;/span&gt;)
│ │ │ │ │ │
&lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;9&lt;/span&gt; * * &lt;span class="m"&gt;1&lt;/span&gt;-&lt;span class="m"&gt;5&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Timezone follows the server process — set your system timezone before starting &lt;code&gt;garudust-server&lt;/code&gt; if needed.&lt;/p&gt;




&lt;h3&gt;
  
  
  2. CLI flag or environment variable — One-off / Docker
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# CLI flag — comma-separated "cron_expr=task" pairs&lt;/span&gt;
garudust-server &lt;span class="nt"&gt;--cron-jobs&lt;/span&gt; &lt;span class="s2"&gt;"0 0 9 * * *=Write morning briefing,0 0 18 * * 5=Weekly summary"&lt;/span&gt;

&lt;span class="c"&gt;# Or via environment variable in ~/.garudust/.env&lt;/span&gt;
&lt;span class="nv"&gt;GARUDUST_CRON_JOBS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"0 0 9 * * *=Write morning briefing"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These take precedence over &lt;code&gt;config.yaml&lt;/code&gt; when both are set.&lt;/p&gt;




&lt;h3&gt;
  
  
  3. Runtime via chat — No restart needed
&lt;/h3&gt;

&lt;p&gt;Once the server is running, you (or any admin) can create jobs live by asking the agent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You:   Create a cron job that runs every day at 7am to check disk usage
       and send me an alert if any partition is above 80%.

Agent: [uses cron_create tool]
       Created cron job 'disk_check' with schedule: 0 0 7 * * *
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; &lt;code&gt;cron_create&lt;/code&gt; uses &lt;strong&gt;6-field&lt;/strong&gt; cron syntax (seconds first): &lt;code&gt;sec min hour dom month dow&lt;/code&gt;&lt;br&gt;
Same format as &lt;code&gt;config.yaml&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Format&lt;/th&gt;
&lt;th&gt;Where used&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;6-field&lt;/td&gt;
&lt;td&gt;everywhere (&lt;code&gt;config.yaml&lt;/code&gt;, &lt;code&gt;--cron-jobs&lt;/code&gt;, env var, &lt;code&gt;cron_create&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0 0 9 * * 1-5&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Runtime jobs are &lt;strong&gt;not persisted&lt;/strong&gt; — they disappear on server restart. Add them to &lt;code&gt;config.yaml&lt;/code&gt; for permanent schedules.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Manage runtime jobs:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You:   List all active cron jobs.
Agent: - [disk_check]  schedule: 0 0 7 * * *  task: Check disk usage...  created: 2025-05-21 07:00 UTC

You:   Delete the disk_check job.
Agent: Cron job 'disk_check' removed.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Memory Maintenance (Bonus)
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;CronConfig&lt;/code&gt; has two extra fields specifically for automatic memory housekeeping:&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;cron&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;schedule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;9&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*"&lt;/span&gt;
      &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Morning&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;briefing"&lt;/span&gt;

  &lt;span class="c1"&gt;# Consolidate and deduplicate memory entries&lt;/span&gt;
  &lt;span class="na"&gt;memory_consolidation&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;3&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*"&lt;/span&gt;   &lt;span class="c1"&gt;# daily at 03:00&lt;/span&gt;

  &lt;span class="c1"&gt;# Expire stale memory entries (based on memory_expiry settings)&lt;/span&gt;
  &lt;span class="na"&gt;memory_expiry&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;4&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;0"&lt;/span&gt;          &lt;span class="c1"&gt;# weekly on Sunday at 04:00&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These run lightweight maintenance tasks — no LLM call required.&lt;/p&gt;




&lt;h2&gt;
  
  
  Practical Examples
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Morning Briefing
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;cron&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;schedule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;8&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;1-5"&lt;/span&gt;
      &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="s"&gt;Write a morning briefing covering: (1) any new files in ~/inbox/,&lt;/span&gt;
        &lt;span class="s"&gt;(2) a summary of yesterday's ~/logs/app.log errors,&lt;/span&gt;
        &lt;span class="s"&gt;(3) today's date and day of week.&lt;/span&gt;
        &lt;span class="s"&gt;Save the result to ~/briefing/$(date +%Y-%m-%d).md.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Log Monitoring
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;cron&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;schedule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*/15&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*"&lt;/span&gt;   &lt;span class="c1"&gt;# every 15 minutes&lt;/span&gt;
      &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="s"&gt;Check /var/log/app/error.log for entries in the last 15 minutes.&lt;/span&gt;
        &lt;span class="s"&gt;If there are more than 10 errors, append a summary to ~/alerts/errors.log.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Weekly Git Summary
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;cron&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;schedule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;17&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;5"&lt;/span&gt;   &lt;span class="c1"&gt;# Fridays at 17:00&lt;/span&gt;
      &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="s"&gt;Run git log --since="1 week ago" --oneline in ~/project/,&lt;/span&gt;
        &lt;span class="s"&gt;summarise what changed by area, and save to ~/reports/weekly.md.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Sending Results to Telegram
&lt;/h3&gt;

&lt;p&gt;Cron jobs have no built-in delivery mechanism — the agent writes to files or uses tools. To send to Telegram, write it into the task prompt:&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;cron&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;schedule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;9&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*"&lt;/span&gt;
      &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="s"&gt;Write a morning briefing (top 3 priorities for today, weather summary).&lt;/span&gt;
        &lt;span class="s"&gt;Then send it to Telegram chat ID 123456789 using the send_message tool.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent calls &lt;code&gt;send_message&lt;/code&gt; itself. The chat ID must be hardcoded in the task or retrievable from a file the agent can read.&lt;/p&gt;




&lt;h2&gt;
  
  
  Disabling Cron Tools
&lt;/h2&gt;

&lt;p&gt;If you want to prevent the agent from creating or deleting jobs at runtime, disable the toolset:&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;disabled_toolsets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;cron&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Config-file jobs still run — only the &lt;code&gt;cron_create&lt;/code&gt; / &lt;code&gt;cron_list&lt;/code&gt; / &lt;code&gt;cron_delete&lt;/code&gt; runtime tools are disabled.&lt;/p&gt;




&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;config.yaml&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;
&lt;code&gt;--cron-jobs&lt;/code&gt; / env var&lt;/th&gt;
&lt;th&gt;Runtime (&lt;code&gt;cron_create&lt;/code&gt;)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Cron syntax&lt;/td&gt;
&lt;td&gt;6-field&lt;/td&gt;
&lt;td&gt;6-field&lt;/td&gt;
&lt;td&gt;6-field&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Persists across restarts&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Requires restart to activate&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;cron_list&lt;/code&gt; shows it&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Start with &lt;code&gt;config.yaml&lt;/code&gt; for anything you want running reliably. Use runtime jobs for experiments or tasks you only need for the current server session.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Garudust Agent&lt;/strong&gt; — &lt;a href="https://github.com/garudust-org/garudust-agent" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; · &lt;a href="https://github.com/garudust-org/garudust-agent/releases" rel="noopener noreferrer"&gt;Releases&lt;/a&gt;&lt;/p&gt;

</description>
      <category>rust</category>
      <category>ai</category>
      <category>automation</category>
      <category>devops</category>
    </item>
    <item>
      <title>Chat With Your Documents Using Garudust Agent — No Vector Database Required</title>
      <dc:creator>Garudust</dc:creator>
      <pubDate>Thu, 21 May 2026 06:47:49 +0000</pubDate>
      <link>https://dev.to/garudust/chat-with-your-documents-using-garudust-agent-no-vector-database-required-1m61</link>
      <guid>https://dev.to/garudust/chat-with-your-documents-using-garudust-agent-no-vector-database-required-1m61</guid>
      <description>&lt;p&gt;Most RAG tutorials start the same way: &lt;em&gt;"First, install a vector database…"&lt;/em&gt; Then come the embedding models, the chunking strategies, the similarity thresholds. By the time you can ask a question about a PDF, you've deployed three services and written 200 lines of boilerplate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Garudust Agent&lt;/strong&gt; takes a different path. RAG is built in — backed by SQLite FTS5 with a trigram tokenizer. No vector database. No embedding API calls. Drop a PDF (or TXT, CSV, Markdown, JSON) into the conversation and start asking questions in seconds.&lt;/p&gt;




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

&lt;p&gt;When you ingest a document, Garudust:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Extracts text (native PDF parser, no external tools)&lt;/li&gt;
&lt;li&gt;Splits it into chunks (≤ 800 chars, paragraph-aware)&lt;/li&gt;
&lt;li&gt;Indexes chunks into an FTS5 virtual table with &lt;code&gt;tokenize = 'trigram'&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;When you ask a question, &lt;code&gt;doc_search&lt;/code&gt; runs a full-text query against the index and feeds the top matching chunks to the LLM as context. That's the whole pipeline — one SQLite file at &lt;code&gt;~/.garudust/state.db&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The trigram tokenizer means it works on &lt;strong&gt;any language&lt;/strong&gt;, including Thai, Chinese, and Japanese, without any tokenizer configuration.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Setup
&lt;/h2&gt;

&lt;p&gt;RAG is enabled by default. The only thing you need to configure is which directories the agent is allowed to read from:&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="c1"&gt;# ~/.garudust/config.yaml&lt;/span&gt;
&lt;span class="na"&gt;security&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;allowed_read_paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/home/you/documents&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/data/company-docs&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. If you want to turn RAG off entirely:&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;disabled_toolsets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;rag&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Your First Ingestion
&lt;/h2&gt;

&lt;p&gt;Start the CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;garudust
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then tell the agent to ingest a file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You: ingest /home/you/documents/employee-handbook.pdf

Agent: Indexed employee-handbook.pdf — 47 chunks ready for search.
       Preview: "This handbook outlines the policies and procedures for all employees…"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now ask anything:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You: What is the remote work policy?

Agent: According to the employee handbook, remote work is permitted up to 3 days per week
       for roles that do not require on-site presence. Employees must notify their manager
       at least 24 hours in advance and maintain availability during core hours (10am–4pm).
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The Four RAG Tools
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;doc_ingest&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Extract and index a file (PDF, TXT, CSV, MD, JSON…)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;doc_search&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Full-text search across all ingested documents&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;doc_list&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;List all indexed documents with chunk count and timestamp&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;doc_forget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Remove one file or clear the entire index&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;You never call these directly — the agent decides when to use them based on your question. But knowing they exist helps you understand what's happening.&lt;/p&gt;

&lt;h3&gt;
  
  
  Re-ingesting a file
&lt;/h3&gt;

&lt;p&gt;If a document changes, just ingest it again. The old index for that path is replaced automatically.&lt;/p&gt;

&lt;h3&gt;
  
  
  Forgetting a document
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You: Remove the Q1 report from the index.
Agent: Document removed from index.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or clear everything:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You: Clear all indexed documents.
Agent: Removed 5 document(s) from index.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  FTS5 Query Syntax
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;doc_search&lt;/code&gt; supports full FTS5 syntax, which the agent uses automatically when your question benefits from it:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Syntax&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;AND (default)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;remote work policy&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Phrase&lt;/td&gt;
&lt;td&gt;&lt;code&gt;"annual leave"&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OR&lt;/td&gt;
&lt;td&gt;&lt;code&gt;vacation OR leave&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NOT&lt;/td&gt;
&lt;td&gt;&lt;code&gt;policy NOT contractor&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Prefix&lt;/td&gt;
&lt;td&gt;&lt;code&gt;terminat*&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;You don't need to write FTS5 queries yourself — the agent figures this out. But if you want to guide it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You: Search for "termination clause" in the contract documents.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Real-World Use Cases
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Company Knowledge Base
&lt;/h3&gt;

&lt;p&gt;Ingest your onboarding docs, SOPs, and internal wikis. New team members can ask questions in plain language instead of searching through Confluence.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You: ingest /docs/sop-release-process.md
You: What approvals are needed before a hotfix can go to production?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Contract and Legal Review
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You: ingest /legal/vendor-agreement-2025.pdf
You: Does this contract include a limitation of liability clause? What is the cap?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Log Analysis
&lt;/h3&gt;

&lt;p&gt;Ingest a log file and ask questions without writing grep patterns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You: ingest /var/log/app/error.log
You: Which service caused the most errors in the last hour?
You: Are there any database connection timeouts?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Codebase Documentation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You: ingest /project/docs/api-reference.md
You: What parameters does the /auth/refresh endpoint accept?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Ingesting Files Sent via Telegram or LINE
&lt;/h2&gt;

&lt;p&gt;If you're running &lt;code&gt;garudust-server&lt;/code&gt; with a messaging platform, users can send files directly to the bot. Attachments are automatically saved to a temporary path and can be ingested on request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User sends: quarterly-report.pdf (attached)

Agent: I received your file. Would you like me to index it for search?

User: yes

Agent: Indexed quarterly-report.pdf — 83 chunks ready.
       Preview: "Q1 2025 Financial Summary — Total Revenue: $4.2M…"

User: What was the gross margin for Q1?
Agent: According to the report, gross margin for Q1 2025 was 61.3%,
       up from 58.9% in Q4 2024.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Platform attachments (files from Telegram, LINE, Discord, etc.) are always allowed regardless of &lt;code&gt;allowed_read_paths&lt;/code&gt;, since they're written to &lt;code&gt;/tmp/garudust_*&lt;/code&gt; by the platform adapter.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Multiple Documents at Once
&lt;/h2&gt;

&lt;p&gt;You can ingest multiple files and search across all of them in the same session:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You: ingest /docs/policy-2024.pdf
You: ingest /docs/policy-2025.pdf
You: What changed in the travel expense policy between 2024 and 2025?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent searches both documents and synthesizes the differences.&lt;/p&gt;

&lt;p&gt;Check what's indexed at any time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You: What documents have you indexed?

Agent: 2 documents indexed:
       - policy-2024.pdf | 34 chunks | ingested 2025-05-21 09:14
       - policy-2025.pdf | 38 chunks | ingested 2025-05-21 09:15
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No semantic search&lt;/strong&gt; — FTS5 is keyword/trigram matching, not embedding similarity. If the document says "annual leave" and you ask about "vacation days," the agent bridges this with its language understanding, but results depend on the LLM's reasoning.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Session-scoped by default&lt;/strong&gt; — The index persists in &lt;code&gt;state.db&lt;/code&gt;, but searches are scoped to the current conversation key. Starting a new session means re-ingesting if you want to query the same files.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Text-only&lt;/strong&gt; — Images, tables, and charts inside PDFs are not extracted. Text content only.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Garudust RAG&lt;/th&gt;
&lt;th&gt;Vector DB approach&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Setup&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;One config line&lt;/td&gt;
&lt;td&gt;Vector DB + embedding API&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Storage&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;SQLite (single file)&lt;/td&gt;
&lt;td&gt;Separate service&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Languages&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Any (trigram)&lt;/td&gt;
&lt;td&gt;Depends on embedding model&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cost&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Zero (no embedding calls)&lt;/td&gt;
&lt;td&gt;Per-token embedding cost&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Search type&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;FTS5 keyword + LLM reasoning&lt;/td&gt;
&lt;td&gt;Semantic similarity&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Garudust's RAG won't replace a purpose-built vector search pipeline for large-scale production retrieval. But for a developer who wants to ask questions about their documents right now — without running a second service — it's the fastest path from PDF to answer.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Garudust Agent&lt;/strong&gt; — &lt;a href="https://github.com/garudust-org/garudust-agent" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; · &lt;a href="https://github.com/garudust-org/garudust-agent/releases" rel="noopener noreferrer"&gt;Releases&lt;/a&gt;&lt;/p&gt;

</description>
      <category>rust</category>
      <category>ai</category>
      <category>rag</category>
      <category>llm</category>
    </item>
    <item>
      <title>ให้ AI พูดภาษาไทยได้ด้วย Garudust Agent + iApp TTS</title>
      <dc:creator>Garudust</dc:creator>
      <pubDate>Tue, 19 May 2026 02:29:43 +0000</pubDate>
      <link>https://dev.to/garudust/aih-ai-phuudphaasaaaithyaiddwy-garudust-agent-iapp-tts-35n2</link>
      <guid>https://dev.to/garudust/aih-ai-phuudphaasaaaithyaiddwy-garudust-agent-iapp-tts-35n2</guid>
      <description>&lt;p&gt;ถ้าคุณใช้ Garudust Agent อยู่แล้ว การเพิ่มความสามารถให้ AI &lt;strong&gt;พูดภาษาไทยออกมาเป็นเสียง&lt;/strong&gt; ทำได้ในไม่กี่ขั้นตอน — ไม่ต้องแก้โค้ดใด ๆ เพราะ Garudust มีระบบ script tool ที่ pluggable อยู่แล้ว&lt;/p&gt;




&lt;h2&gt;
  
  
  TTS คืออะไร และทำไมต้อง iApp
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;TTS (Text-to-Speech)&lt;/strong&gt; คือการแปลงข้อความเป็นเสียงพูด เหมาะกับ use case เช่น&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ตอบกลับผู้ใช้ด้วยเสียงแทนข้อความบน LINE / Telegram&lt;/li&gt;
&lt;li&gt;สร้างไฟล์เสียงสำหรับ podcast / narration อัตโนมัติ&lt;/li&gt;
&lt;li&gt;Accessibility — ผู้ใช้ที่อ่านหน้าจอยาก&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;iApp Technology&lt;/strong&gt; เป็นบริษัทไทยที่มี TTS API รองรับภาษาไทยโดยเฉพาะ เสียงฟังเป็นธรรมชาติ รองรับสูงสุด 10,000 ตัวอักษรต่อ request คิดค่าใช้จ่าย 1 IC ต่อ 400 ตัวอักษร สมัครได้ที่ &lt;a href="https://iapp.co.th" rel="noopener noreferrer"&gt;iapp.co.th&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  ขั้นตอนที่ 1 — ติดตั้ง tts tool จาก Hub
&lt;/h2&gt;

&lt;p&gt;Garudust มี Hub รวม script tool ให้ install ได้ทันที&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;garudust tool &lt;span class="nb"&gt;install &lt;/span&gt;tts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ระบบจะดึง &lt;code&gt;tool.yaml&lt;/code&gt; และ &lt;code&gt;run.py&lt;/code&gt; มาไว้ที่ &lt;code&gt;~/.garudust/tools/tts/&lt;/code&gt; และ register ใน registry อัตโนมัติ&lt;/p&gt;




&lt;h2&gt;
  
  
  ขั้นตอนที่ 2 — เพิ่ม Provider Profile ใน config.yaml
&lt;/h2&gt;

&lt;p&gt;Garudust ใช้ระบบ &lt;strong&gt;provider profile&lt;/strong&gt; จัดการ API endpoint และ key — ไม่ต้อง hardcode ใน script&lt;/p&gt;

&lt;p&gt;เปิด &lt;code&gt;~/.garudust/config.yaml&lt;/code&gt; แล้วเพิ่ม:&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;providers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# ... providers ที่มีอยู่แล้ว ...&lt;/span&gt;
  &lt;span class="na"&gt;tts-iapp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://api.iapp.co.th/v3/store/audio/tts&lt;/span&gt;
    &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${IAPP_API_KEY}&lt;/span&gt;

&lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;tts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tts-iapp&lt;/span&gt;   &lt;span class="c1"&gt;# ชี้ไปที่ profile ข้างบน&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Garudust จะ inject &lt;code&gt;GARUDUST_BASE_URL&lt;/code&gt; และ &lt;code&gt;GARUDUST_API_KEY&lt;/code&gt; เข้า script อัตโนมัติตอน runtime&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  ขั้นตอนที่ 3 — เพิ่ม API Key ใน .env
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# ~/.garudust/.env&lt;/span&gt;
&lt;span class="nv"&gt;IAPP_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;iapp_live_xxxxxxxxxxxxxxxx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;รับ key ได้จาก &lt;a href="https://iapp.co.th/dashboard" rel="noopener noreferrer"&gt;iapp.co.th/dashboard&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  ทดสอบผ่าน CLI
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;garudust &lt;span class="s2"&gt;"ใช้ tts tool แปลงข้อความว่า สวัสดีครับ ผมคือ Garudust ผู้ช่วย AI ของคุณ แล้วบอก path ไฟล์ที่ได้"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ผลลัพธ์:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;สร้างไฟล์เสียงสำเร็จ
เสียงอยู่ที่: /tmp/tts_88b5bba372dd41a7acc7e37ea75df89b.wav
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;เปิดฟัง:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;open /tmp/tts_88b5bba372dd41a7acc7e37ea75df89b.wav
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  เบื้องหลัง — run.py ทำงานยังไง
&lt;/h2&gt;

&lt;p&gt;iApp TTS API คืน &lt;strong&gt;raw PCM&lt;/strong&gt; มาโดยไม่มี WAV header ดังนั้น &lt;code&gt;run.py&lt;/code&gt; จัดการ wrap header ให้เอง:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;pcm_to_wav&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pcm&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sample_rate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;24000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;channels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;byte_rate&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sample_rate&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;channels&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;bits&lt;/span&gt; &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;
    &lt;span class="n"&gt;block_align&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;channels&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;bits&lt;/span&gt; &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;
    &lt;span class="n"&gt;data_size&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pcm&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;header&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;4sI4s4sIHHIIHH4sI&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RIFF&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;36&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;data_size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;WAVE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fmt &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;channels&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sample_rate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;byte_rate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;block_align&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bits&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data_size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;header&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;pcm&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ผลที่ได้คือ &lt;strong&gt;RIFF WAV 16-bit mono 24kHz&lt;/strong&gt; — เปิดได้กับทุก audio player&lt;/p&gt;




&lt;h2&gt;
  
  
  เปลี่ยน Provider ได้ทันทีโดยไม่แตะโค้ด
&lt;/h2&gt;

&lt;p&gt;นี่คือจุดแข็งของ Garudust provider profile — ถ้าอยากลอง provider อื่นเช่น AIS หรือ local model แค่เพิ่ม profile และเปลี่ยน &lt;code&gt;tools.tts.model&lt;/code&gt; บรรทัดเดียว:&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;providers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;tts-ais&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://your-ais-tts-endpoint/api/tts&lt;/span&gt;
    &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${AIS_TTS_API_KEY}&lt;/span&gt;

&lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;tts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tts-ais&lt;/span&gt;   &lt;span class="c1"&gt;# เปลี่ยนแค่นี้&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;run.py&lt;/code&gt; ไม่ต้องแตะเลย เพราะอ่าน &lt;code&gt;GARUDUST_BASE_URL&lt;/code&gt; และ &lt;code&gt;GARUDUST_API_KEY&lt;/code&gt; จาก profile เสมอ&lt;/p&gt;




&lt;h2&gt;
  
  
  ขั้นต่อไปที่ทำได้
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;LINE voice message&lt;/strong&gt; — เพิ่ม &lt;code&gt;OutboundMessage::Audio&lt;/code&gt; ใน Garudust core แล้วส่งไฟล์ WAV เป็น voice message บน LINE ได้เลย&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auto TTS reply&lt;/strong&gt; — สั่งให้ agent เรียก &lt;code&gt;tts&lt;/code&gt; ทุกครั้งที่ตอบ โดย inject ใน system prompt&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Speaker selection&lt;/strong&gt; — iApp มีหลายเสียง ตั้ง &lt;code&gt;TTS_SPEAKER_ID&lt;/code&gt; ใน &lt;code&gt;.env&lt;/code&gt; เพื่อเลือกเสียงที่ชอบ&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  สรุป
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;ขั้นตอน&lt;/th&gt;
&lt;th&gt;คำสั่ง&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;ติดตั้ง tool&lt;/td&gt;
&lt;td&gt;&lt;code&gt;garudust tool install tts&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;เพิ่ม profile&lt;/td&gt;
&lt;td&gt;แก้ &lt;code&gt;config.yaml&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;เพิ่ม key&lt;/td&gt;
&lt;td&gt;แก้ &lt;code&gt;~/.garudust/.env&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ใช้งาน&lt;/td&gt;
&lt;td&gt;สั่ง agent พูดภาษาไทย&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Garudust Agent เป็น open source ที่ &lt;a href="https://github.com/garudust-org/garudust-agent" rel="noopener noreferrer"&gt;github.com/garudust-org/garudust-agent&lt;/a&gt; — ติดตามหรือ contribute ได้เลย 🦅&lt;/p&gt;

</description>
      <category>ai</category>
      <category>rust</category>
      <category>tts</category>
      <category>thai</category>
    </item>
    <item>
      <title>10 Things You Can Do With Logs Using Garudust Agent 🦅</title>
      <dc:creator>Garudust</dc:creator>
      <pubDate>Mon, 18 May 2026 08:11:41 +0000</pubDate>
      <link>https://dev.to/garudust/10-things-you-can-do-with-logs-using-garudust-agent-3a7m</link>
      <guid>https://dev.to/garudust/10-things-you-can-do-with-logs-using-garudust-agent-3a7m</guid>
      <description>&lt;h1&gt;
  
  
  10 Things You Can Do With Logs Using Garudust Agent 🦅
&lt;/h1&gt;

&lt;p&gt;Most developers treat logs the same way: something breaks, you &lt;code&gt;grep&lt;/code&gt; for errors, squint at timestamps, and piece together what happened. That's table stakes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/garudust-org/garudust-agent" rel="noopener noreferrer"&gt;Garudust Agent&lt;/a&gt;&lt;/strong&gt; is a self-hostable AI agent runtime written in Rust. Pair it with the &lt;code&gt;log-analyst&lt;/code&gt; skill from &lt;a href="https://github.com/garudust-org/garudust-hub" rel="noopener noreferrer"&gt;garudust-hub&lt;/a&gt;, and your logs become something you can &lt;em&gt;talk to&lt;/em&gt; — and that can &lt;em&gt;act&lt;/em&gt; on your behalf.&lt;/p&gt;

&lt;p&gt;Here are 10 things you can do that go well beyond grepping for &lt;code&gt;ERROR&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Setup in 60 Seconds
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install&lt;/span&gt;
wget &lt;span class="nt"&gt;-qO-&lt;/span&gt; https://github.com/garudust-org/garudust-agent/releases/latest/download/garudust-&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;uname&lt;/span&gt; &lt;span class="nt"&gt;-m&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="nt"&gt;-unknown-linux-musl&lt;/span&gt;.tar.gz | &lt;span class="nb"&gt;tar&lt;/span&gt; &lt;span class="nt"&gt;-xz&lt;/span&gt;
&lt;span class="nb"&gt;sudo mv &lt;/span&gt;garudust garudust-server /usr/local/bin/

&lt;span class="c"&gt;# Configure (picks your LLM provider)&lt;/span&gt;
garudust setup

&lt;span class="c"&gt;# Install the log skill&lt;/span&gt;
garudust skill &lt;span class="nb"&gt;install &lt;/span&gt;log-analyst
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Local GPU?&lt;/strong&gt; Skip the API entirely:&lt;/p&gt;


&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;VLLM_BASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;http://localhost:8000/v1
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;GARUDUST_MODEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Qwen/Qwen3-8B-AWQ
&lt;/code&gt;&lt;/pre&gt;

&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  1. 🕵️ Explain &lt;em&gt;Why&lt;/em&gt; an Incident Happened
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;grep&lt;/code&gt; finds &lt;em&gt;what&lt;/em&gt;. Garudust explains &lt;em&gt;why&lt;/em&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;garudust &lt;span class="s2"&gt;"read /var/log/app.log around 2025-05-14 03:22:00 ±10 minutes.
Reconstruct the chain of events that led to the service crash.
What was the root cause?"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent reads the surrounding context — not just the error line — and builds a timeline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Timeline reconstruction:
  03:14:22 — Memory usage crossed 80% threshold (first warning)
  03:19:05 — DB connection pool exhausted (upstream pressure)
  03:21:58 — Request queue backed up, workers stalled
  03:22:11 — OOM killer terminated process (root cause)

Root cause: sustained traffic spike + connection pool too small for load
Recommendation: increase pool size from 10 → 25, add circuit breaker
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  2. 📊 Generate a Weekly Incident Report
&lt;/h2&gt;

&lt;p&gt;Stop writing incident summaries by hand.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;garudust &lt;span class="s2"&gt;"read /var/log/app.log for the past 7 days.
Generate a weekly incident report with:
- Total error count by day
- Top 5 recurring error types
- Mean time between incidents
- Any worsening trends
Format as Markdown."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pipe it to a file, commit it, send it to Slack — whatever fits your workflow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;garudust &lt;span class="s2"&gt;"weekly incident report from /var/log/app.log"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; reports/week-&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +%V&lt;span class="si"&gt;)&lt;/span&gt;.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  3. 🔁 Detect Crash Loops Before They Become Outages
&lt;/h2&gt;

&lt;p&gt;A service that restarts every 4 minutes isn't &lt;em&gt;down&lt;/em&gt; — your uptime monitor won't catch it. But Garudust will.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;garudust &lt;span class="s2"&gt;"scan /var/log/syslog for the last 2 hours.
Detect any process that has started and stopped more than 3 times.
Flag it as a crash loop candidate with restart intervals."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;⚠️  CRASH LOOP DETECTED: api-worker
    Restarts in last 2h: 7
    Average interval: 16 minutes
    Last restart: 10 minutes ago
    Pattern: always crashes ~2 min after start (initialization failure likely)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  4. 🌡️ Baseline "Normal" and Alert on Deviations
&lt;/h2&gt;

&lt;p&gt;What if you don't know what an anomaly looks like — only that something &lt;em&gt;feels&lt;/em&gt; off?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;garudust &lt;span class="s2"&gt;"compare /var/log/nginx/access.log from last Monday vs this Monday,
same time window 09:00–12:00.
What's statistically different? Flag anything that deviates by more than 2x."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Garudust doesn't need pre-defined rules. It compares distributions and reasons about what changed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Deviations vs. last Monday baseline:
  • 4xx error rate: 0.3% → 4.1%  (13.7x increase) ⚠️
  • Avg response time: 120ms → 380ms  (3.2x increase) ⚠️
  • /api/search endpoint: 2% → 31% of traffic  (unusual spike)

Hypothesis: new client or bot hitting /api/search aggressively
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  5. 🔒 Audit Security Events
&lt;/h2&gt;

&lt;p&gt;Spot brute force attempts, suspicious IPs, and unusual access patterns.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;garudust &lt;span class="s2"&gt;"analyze /var/log/auth.log from the last 24 hours.
Find:
- Failed login attempts &amp;gt; 5 from the same IP
- Successful logins from IPs with prior failures
- Any logins at unusual hours (outside 08:00–20:00 local time)
- New user accounts created
Summarize as a security audit."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;🔴 Brute Force Attempt:
   IP: 185.234.xx.xx — 847 failed SSH attempts in 3 hours
   ⚠️  This IP succeeded once at 03:41 — INVESTIGATE IMMEDIATELY

🟡 Off-hours Logins:
   user: deploy  — login at 02:14 from 10.0.1.55 (internal, but unusual)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  6. 📈 Build a Performance Degradation Timeline
&lt;/h2&gt;

&lt;p&gt;Know exactly &lt;em&gt;when&lt;/em&gt; things started slowing down — not just that they're slow now.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;garudust &lt;span class="s2"&gt;"read /var/log/app.log for the past 30 days.
Plot response time trends by week.
Identify the exact date when p95 latency started increasing.
What changed around that time in the logs?"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is invaluable for debugging regressions that crept in silently over weeks.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. 🤖 Auto-Remediate Known Issues
&lt;/h2&gt;

&lt;p&gt;Garudust has a &lt;code&gt;terminal&lt;/code&gt; tool. Combine log analysis with action.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;garudust &lt;span class="s2"&gt;"check /var/log/app.log — if you see more than 20 'disk quota exceeded'
errors in the last hour, run: find /tmp -mtime +1 -delete
then report how many files were removed and current disk usage."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or for a service restart:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;garudust &lt;span class="s2"&gt;"monitor /var/log/api.log — if the error rate exceeds 15% over 5 minutes,
run: sudo systemctl restart api-service
and report what you did and why."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Garudust's &lt;code&gt;GARUDUST_APPROVAL_MODE&lt;/code&gt; defaults to &lt;code&gt;smart&lt;/code&gt; — it will ask before running destructive commands. Set to &lt;code&gt;auto&lt;/code&gt; only in controlled environments.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  8. 🔍 Trace a Single Request Across Services
&lt;/h2&gt;

&lt;p&gt;Distributed systems scatter a single user request across multiple log files. Garudust can stitch them back together.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;garudust &lt;span class="s2"&gt;"find all log entries related to request_id=a3f9c2b1 across these files:
/var/log/gateway.log
/var/log/auth-service.log
/var/log/api.log
/var/log/db-proxy.log
Build a complete trace timeline with latency at each hop."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Request trace: a3f9c2b1
  00ms  gateway.log      — request received, routed to auth
  12ms  auth-service.log — token validated
  13ms  api.log          — handler invoked
  891ms api.log          — ⚠️ waiting on DB (unusually long)
  903ms db-proxy.log     — query executed (full table scan detected)
  905ms gateway.log      — response returned  total: 905ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  9. 📝 Generate a &lt;code&gt;CHANGELOG&lt;/code&gt; from Deploy Logs
&lt;/h2&gt;

&lt;p&gt;If your deploy pipeline writes structured logs, you can generate changelogs automatically.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;garudust &lt;span class="s2"&gt;"read /var/log/deploy.log for this month.
Extract all deployments: service name, version, timestamp, who deployed.
Format as a CHANGELOG grouped by week, in Keep a Changelog style."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;## [Week 20] — 2025-05-12 to 2025-05-18&lt;/span&gt;

&lt;span class="gu"&gt;### Deployed&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**api-service**&lt;/span&gt; v2.4.1 — 2025-05-14 10:32 (alice)
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**worker**&lt;/span&gt; v1.9.0 — 2025-05-16 14:15 (bob)
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**gateway**&lt;/span&gt; v3.1.2 — 2025-05-17 09:00 (alice)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  10. ⏰ Schedule All of the Above
&lt;/h2&gt;

&lt;p&gt;Everything above can run automatically on a cron schedule — no extra infrastructure needed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;TELEGRAM_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your_token &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nv"&gt;GARUDUST_CRON_JOBS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"
0 9 * * 1=Read /var/log/app.log last 7 days, generate weekly incident report, send to telegram;
*/15 * * * *=Check /var/log/app.log last 15 minutes for crash loops or error spikes, alert telegram if found;
0 8 * * *=Audit /var/log/auth.log last 24 hours for security anomalies, send daily security digest to telegram
"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
garudust-server &lt;span class="nt"&gt;--anthropic-key&lt;/span&gt; sk-ant-...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three jobs. Zero extra services. No Prometheus, no Grafana, no ELK cluster.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Mental Model Shift
&lt;/h2&gt;

&lt;p&gt;Traditional log tooling asks you to define rules upfront:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Alert me when error rate &amp;gt; 5%"&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Garudust lets you ask questions in plain language — &lt;em&gt;after&lt;/em&gt; something happens, or proactively via cron — and get reasoning, not just matching:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"What went wrong this week, and is it getting worse?"&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The difference is the jump from &lt;strong&gt;pattern matching&lt;/strong&gt; to &lt;strong&gt;understanding&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Install the &lt;a href="https://github.com/garudust-org/garudust-hub/tree/main/skills/log-analyst" rel="noopener noreferrer"&gt;&lt;code&gt;log-analyst&lt;/code&gt; skill&lt;/a&gt; and try #1 on your own logs right now&lt;/li&gt;
&lt;li&gt;Write a custom &lt;code&gt;SKILL.md&lt;/code&gt; for your specific log format in &lt;code&gt;~/.garudust/skills/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Contribute your own log tools to &lt;a href="https://github.com/garudust-org/garudust-hub" rel="noopener noreferrer"&gt;garudust-hub&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;🔗 Links:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/garudust-org/garudust-agent" rel="noopener noreferrer"&gt;garudust-org/garudust-agent&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Hub: &lt;a href="https://github.com/garudust-org/garudust-hub" rel="noopener noreferrer"&gt;garudust-org/garudust-hub&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🇹🇭 Thai version: &lt;em&gt;(link after publishing)&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Which of these 10 use cases would save you the most time? Drop a comment — I read every one. 🙌&lt;/em&gt;&lt;/p&gt;

</description>
      <category>rust</category>
      <category>ai</category>
      <category>devops</category>
      <category>log</category>
    </item>
    <item>
      <title>ตรวจจับ Log Anomaly อัตโนมัติด้วย Garudust Agent 🦅</title>
      <dc:creator>Garudust</dc:creator>
      <pubDate>Mon, 18 May 2026 07:19:00 +0000</pubDate>
      <link>https://dev.to/garudust/trwcchcchab-log-anomaly-atonmatidwy-garudust-agent-1d41</link>
      <guid>https://dev.to/garudust/trwcchcchab-log-anomaly-atonmatidwy-garudust-agent-1d41</guid>
      <description>&lt;h1&gt;
  
  
  ตรวจจับ Log Anomaly อัตโนมัติด้วย Garudust Agent 🦅
&lt;/h1&gt;

&lt;p&gt;ถ้าคุณเคยนั่งไล่ดู log ไฟล์ที่ยาวหลายหมื่นบรรทัดเพื่อหาว่า "มันพังตรงไหน" — บทความนี้เขียนมาเพื่อคุณโดยเฉพาะ&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/garudust-org/garudust-agent" rel="noopener noreferrer"&gt;Garudust Agent&lt;/a&gt;&lt;/strong&gt; คือ AI agent runtime ที่เขียนด้วย Rust รองรับการ self-host บน hardware ของคุณเอง และมี skill ชื่อ &lt;code&gt;log-analyst&lt;/code&gt; ใน &lt;a href="https://github.com/garudust-org/garudust-hub" rel="noopener noreferrer"&gt;garudust-hub&lt;/a&gt; ที่ถูกออกแบบมาสำหรับงานนี้โดยตรง&lt;/p&gt;




&lt;h2&gt;
  
  
  🤔 ปัญหาจริงที่นักพัฒนาเจอ
&lt;/h2&gt;

&lt;p&gt;ระบบ production ทุกระบบมี log แต่การไล่อ่าน log ด้วยมือมีปัญหาหลายอย่าง:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;ปริมาณมหาศาล&lt;/strong&gt; — log หลักล้านบรรทัดต่อวัน ไม่มีใครอ่านได้หมด&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pattern ซับซ้อน&lt;/strong&gt; — error เดียวกันอาจโผล่ในหลาย format&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;เจอช้า&lt;/strong&gt; — กว่าจะรู้ว่าพัง ผู้ใช้แจ้งมาก่อนแล้ว&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Context ขาด&lt;/strong&gt; — รู้ว่า error ตรงไหน แต่ไม่รู้ว่า "ก่อนหน้านั้นมีอะไรเกิดขึ้น"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Garudust แก้ปัญหาเหล่านี้ด้วยการให้ LLM อ่านและวิเคราะห์ log แทนคุณ — แบบ real-time หรือ scheduled ก็ได้&lt;/p&gt;




&lt;h2&gt;
  
  
  🛠️ ติดตั้ง Garudust
&lt;/h2&gt;

&lt;h3&gt;
  
  
  วิธีที่ 1 — ใช้ Pre-built Binary (แนะนำ)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Linux x86_64&lt;/span&gt;
wget https://github.com/garudust-org/garudust-agent/releases/latest/download/garudust-&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="nt"&gt;-x86_64-unknown-linux-musl&lt;/span&gt;.tar.gz
&lt;span class="nb"&gt;tar&lt;/span&gt; &lt;span class="nt"&gt;-xzf&lt;/span&gt; garudust-&lt;span class="k"&gt;*&lt;/span&gt;.tar.gz
&lt;span class="nb"&gt;sudo mv &lt;/span&gt;garudust garudust-server /usr/local/bin/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  วิธีที่ 2 — Build จาก Source (ต้องมี Rust 1.75+)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/garudust-org/garudust-agent
&lt;span class="nb"&gt;cd &lt;/span&gt;garudust-agent
cargo build &lt;span class="nt"&gt;--release&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PATH&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;/target/release"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  ตั้งค่าเริ่มต้น
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;garudust setup
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wizard จะถามว่าใช้ LLM provider ไหน รองรับทั้ง Anthropic, OpenRouter, vLLM (local), Ollama และ AWS Bedrock&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;สำหรับคนที่มี GPU และ self-host:&lt;/strong&gt; ใช้ vLLM กับ model เช่น Qwen3-8B ได้เลยโดยไม่เสียค่า API&lt;/p&gt;


&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;VLLM_BASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;http://localhost:8000/v1
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;GARUDUST_MODEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Qwen/Qwen3-8B-AWQ
&lt;/code&gt;&lt;/pre&gt;

&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  📦 ติดตั้ง &lt;code&gt;log-analyst&lt;/code&gt; Skill
&lt;/h2&gt;

&lt;p&gt;Skill คือ instruction set แบบ Markdown ที่บอก agent ว่าต้องทำอะไร เมื่อไหร่ อย่างไร&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;garudust skill &lt;span class="nb"&gt;install &lt;/span&gt;log-analyst
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;แค่นี้พอ — ครั้งถัดไปที่คุณพูดถึง log, agent จะโหลด skill นี้มาใช้อัตโนมัติ&lt;/p&gt;




&lt;h2&gt;
  
  
  🔍 ใช้งานจริง: วิเคราะห์ Log ทันที
&lt;/h2&gt;

&lt;h3&gt;
  
  
  กรณีที่ 1 — วิเคราะห์ syslog
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;garudust &lt;span class="s2"&gt;"วิเคราะห์ /var/log/syslog 500 บรรทัดล่าสุด หา error pattern ที่ผิดปกติ"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ตัวอย่าง output ที่ได้:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;🔍 Log Analysis Report — /var/log/syslog
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

⚠️  ANOMALY DETECTED: Connection timeout spike
    → 47 occurrences ใน 3 นาที (ปกติ &amp;lt; 2/ชั่วโมง)
    → First seen: 14:32:01, Last: 14:35:18
    → Probable cause: Network interface congestion หรือ upstream service down

❌ ERROR Pattern: OOM Killer activated
    → Process: python3 (PID 18423)
    → Memory usage: 7.8GB / 8GB
    → Recommendation: เพิ่ม swap หรือ review memory leak ใน process

✅ ไม่พบ segfault, kernel panic หรือ disk I/O error
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  กรณีที่ 2 — วิเคราะห์ Application Log
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;garudust &lt;span class="s2"&gt;"อ่าน /opt/myapp/logs/app.log แล้วหา:
- Error rate ต่อนาที
- Request ที่ใช้เวลา &amp;gt; 5 วินาที  
- Pattern ที่น่าสงสัย
สรุปเป็นภาษาไทย"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  กรณีที่ 3 — เปรียบเทียบ Log สองช่วงเวลา
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;garudust &lt;span class="s2"&gt;"เปรียบเทียบ /var/log/nginx/access.log ระหว่าง 10:00-11:00 กับ 14:00-15:00 
ว่า error rate ต่างกันไหม และ traffic pattern เปลี่ยนแปลงอย่างไร"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  ⏰ Cron: ตรวจ Log อัตโนมัติ ทุก 30 นาที
&lt;/h2&gt;

&lt;p&gt;นี่คือจุดที่ทำให้ Garudust ต่างจากการรัน script ธรรมดา — agent สามารถ &lt;strong&gt;ตัดสินใจเองได้&lt;/strong&gt; ว่าสิ่งที่เจอนั้น "ผิดปกติ" หรือเปล่า&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;GARUDUST_CRON_JOBS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"*/30 * * * *=Read /var/log/app.log last 300 lines, detect ERROR or WARNING anomalies, if severity is HIGH report summary with timestamp and probable cause"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
garudust-server &lt;span class="nt"&gt;--anthropic-key&lt;/span&gt; sk-ant-...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  แจ้งเตือนผ่าน Telegram อัตโนมัติ
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;TELEGRAM_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your_bot_token &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nv"&gt;GARUDUST_CRON_JOBS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"*/15 * * * *=Check /var/log/app.log for critical errors in last 15 minutes. If error rate exceeds 10 per minute, send alert to telegram with details"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
garudust-server &lt;span class="nt"&gt;--anthropic-key&lt;/span&gt; sk-ant-...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Agent จะส่ง message ไปยัง Telegram bot ของคุณเองอัตโนมัติเมื่อเจอ anomaly&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  🏭 Use Case จริง: ระบบ Industrial / Edge AI
&lt;/h2&gt;

&lt;p&gt;สำหรับงาน &lt;strong&gt;IoT&lt;/strong&gt; หรือ &lt;strong&gt;Edge AI&lt;/strong&gt; ที่มี log จากเซนเซอร์หรือกล้องวงจรปิด สามารถทำแบบนี้ได้:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# ตรวจสอบ log จากระบบ OCR กล้อง factory ทุก 1 ชั่วโมง&lt;/span&gt;
&lt;span class="nv"&gt;GARUDUST_CRON_JOBS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"0 * * * *=Analyze /opt/vision/logs/ocr_results.log, find rows where confidence_score &amp;lt; 0.6 or recognition_failed = true, count anomaly rate, if &amp;gt; 5% in last hour generate incident report"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nv"&gt;VLLM_BASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;http://localhost:8000/v1 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nv"&gt;GARUDUST_MODEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Qwen3-8B-AWQ &lt;span class="se"&gt;\&lt;/span&gt;
garudust-server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ข้อดีคือรันบน &lt;strong&gt;local GPU&lt;/strong&gt; ได้เลย ไม่ต้องส่ง log ออกไปยัง cloud เหมาะมากสำหรับระบบ industrial ที่ข้อมูลเป็น confidential&lt;/p&gt;




&lt;h2&gt;
  
  
  ✏️ เขียน Skill เองสำหรับ Log Format เฉพาะทาง
&lt;/h2&gt;

&lt;p&gt;ถ้า log ของคุณมี format แปลก หรืออยากให้ agent วิเคราะห์แบบเฉพาะเจาะจง เขียน SKILL.md เองได้ใน &lt;code&gt;~/.garudust/skills/&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;factory-log-analyst&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Analyze industrial machine log from SMARTSense IDA system&lt;/span&gt;
&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;1.0.0&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

เมื่อถูกขอให้วิเคราะห์ log จากระบบ IDA ให้ทำตามนี้:
&lt;span class="p"&gt;
1.&lt;/span&gt; อ่าน log file ด้วย terminal tool
&lt;span class="p"&gt;2.&lt;/span&gt; หา pattern เหล่านี้ที่บ่งบอกปัญหา:
&lt;span class="p"&gt;   -&lt;/span&gt; confidence_score &amp;lt; 0.5 (OCR accuracy ต่ำผิดปกติ)
&lt;span class="p"&gt;   -&lt;/span&gt; frame_drop_rate &amp;gt; 0.1 (กล้องส่งภาพไม่ครบ)
&lt;span class="p"&gt;   -&lt;/span&gt; roi_timeout (พื้นที่ตรวจสอบไม่ response)
&lt;span class="p"&gt;   -&lt;/span&gt; connection_lost (กล้องหลุด)
&lt;span class="p"&gt;3.&lt;/span&gt; คำนวณ:
&lt;span class="p"&gt;   -&lt;/span&gt; Anomaly rate ต่อชั่วโมง
&lt;span class="p"&gt;   -&lt;/span&gt; ROI ที่มีปัญหาบ่อยที่สุด
&lt;span class="p"&gt;4.&lt;/span&gt; สรุปเป็นภาษาไทย ระบุ:
&lt;span class="p"&gt;   -&lt;/span&gt; ความรุนแรง (Critical / Warning / Info)
&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;h2&gt;
  
  
  🔧 Tool เพิ่มเติมจาก garudust-hub
&lt;/h2&gt;

&lt;p&gt;นอกจาก &lt;code&gt;log-analyst&lt;/code&gt; skill แล้ว ยังมี tools ใน hub ที่ช่วยงาน log ได้:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# ติดตั้ง tools ที่มีประโยชน์&lt;/span&gt;
garudust tool &lt;span class="nb"&gt;install &lt;/span&gt;csv_to_json    &lt;span class="c"&gt;# แปลง log CSV → JSON&lt;/span&gt;
garudust tool &lt;span class="nb"&gt;install &lt;/span&gt;file_info      &lt;span class="c"&gt;# เช็ค size/encoding ก่อนอ่าน&lt;/span&gt;
garudust tool &lt;span class="nb"&gt;install &lt;/span&gt;token_count    &lt;span class="c"&gt;# เช็คว่า log ยาวเกิน context ไหม&lt;/span&gt;
garudust tool &lt;span class="nb"&gt;install &lt;/span&gt;extract_urls   &lt;span class="c"&gt;# ดึง URL จาก access log&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ใช้คู่กันในคำสั่งเดียว:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;garudust &lt;span class="s2"&gt;"ใช้ file_info ดูขนาดของ /var/log/nginx/error.log ก่อน แล้วถ้าไม่เกิน 100KB ให้วิเคราะห์หา anomaly ทั้งหมดที่เกิดขึ้นวันนี้"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  📊 เปรียบเทียบ: Garudust vs วิธีเดิม
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Script ธรรมดา (grep/awk)&lt;/th&gt;
&lt;th&gt;ELK Stack&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Garudust&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;ติดตั้ง&lt;/td&gt;
&lt;td&gt;ง่าย&lt;/td&gt;
&lt;td&gt;ซับซ้อน&lt;/td&gt;
&lt;td&gt;ง่าย (binary เดียว)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Resource&lt;/td&gt;
&lt;td&gt;ต่ำ&lt;/td&gt;
&lt;td&gt;สูงมาก (ต้องการ RAM หลาย GB)&lt;/td&gt;
&lt;td&gt;ต่ำ (~10MB binary)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;วิเคราะห์ context&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅ (LLM เข้าใจ context)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;แจ้งเตือนอัตโนมัติ&lt;/td&gt;
&lt;td&gt;ต้องเขียนเอง&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅ built-in&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Self-host&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ภาษาไทย&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;สรุปสาเหตุ&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  🚀 เริ่มต้น 5 นาที
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1. ติดตั้ง&lt;/span&gt;
wget &lt;span class="nt"&gt;-qO-&lt;/span&gt; https://github.com/garudust-org/garudust-agent/releases/latest/download/garudust-&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;uname&lt;/span&gt; &lt;span class="nt"&gt;-m&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="nt"&gt;-unknown-linux-musl&lt;/span&gt;.tar.gz | &lt;span class="nb"&gt;tar&lt;/span&gt; &lt;span class="nt"&gt;-xz&lt;/span&gt;
&lt;span class="nb"&gt;sudo mv &lt;/span&gt;garudust /usr/local/bin/

&lt;span class="c"&gt;# 2. ตั้งค่า&lt;/span&gt;
garudust setup

&lt;span class="c"&gt;# 3. ติดตั้ง skill&lt;/span&gt;
garudust skill &lt;span class="nb"&gt;install &lt;/span&gt;log-analyst

&lt;span class="c"&gt;# 4. ลองเลย!&lt;/span&gt;
garudust &lt;span class="s2"&gt;"วิเคราะห์ /var/log/syslog 100 บรรทัดล่าสุด หา error ที่น่าสงสัย"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  สรุป
&lt;/h2&gt;

&lt;p&gt;Garudust ไม่ใช่แค่ tool อ่าน log — มันคือ &lt;strong&gt;agent ที่เข้าใจ context&lt;/strong&gt; ของ log และช่วยตัดสินใจว่าอะไรคือ "ปกติ" หรือ "ผิดปกติ" โดยใช้ความสามารถของ LLM&lt;/p&gt;

&lt;p&gt;จุดแข็งที่สำคัญสำหรับ DevOps และ Industrial Engineer คือ:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Self-host ได้ 100%&lt;/strong&gt; — ไม่ต้องส่ง log ออก cloud&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;รองรับ local LLM&lt;/strong&gt; — ใช้ vLLM + Qwen/LLaMA บน GPU ของตัวเองได้&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ภาษาไทยได้เลย&lt;/strong&gt; — detect ภาษาอัตโนมัติ ไม่ต้องตั้งค่า&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cron built-in&lt;/strong&gt; — ตั้งตรวจอัตโนมัติได้ทันที&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;🔗 Links:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/garudust-org/garudust-agent" rel="noopener noreferrer"&gt;garudust-org/garudust-agent&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Hub: &lt;a href="https://github.com/garudust-org/garudust-hub" rel="noopener noreferrer"&gt;garudust-org/garudust-hub&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Facebook Community: &lt;a href="https://www.facebook.com/groups/1520306226240762" rel="noopener noreferrer"&gt;Garudust Agent Thailand&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;มีคำถามหรืออยากแลกเปลี่ยนประสบการณ์ใช้ Garudust? comment ได้เลยครับ 🙌&lt;/em&gt;&lt;/p&gt;

</description>
      <category>rust</category>
      <category>ai</category>
      <category>devops</category>
      <category>log</category>
    </item>
    <item>
      <title>Build a Self-Improving AI Agent in Rust with Garudust — Daily Briefing Bot in 10 Minutes</title>
      <dc:creator>Garudust</dc:creator>
      <pubDate>Sun, 17 May 2026 06:55:29 +0000</pubDate>
      <link>https://dev.to/garudust/build-a-self-improving-ai-agent-in-rust-with-garudust-daily-briefing-bot-in-10-minutes-ona</link>
      <guid>https://dev.to/garudust/build-a-self-improving-ai-agent-in-rust-with-garudust-daily-briefing-bot-in-10-minutes-ona</guid>
      <description>&lt;p&gt;Most AI agent frameworks feel like they were designed for Python developers who love ceremony. You write adapters, glue code, orchestrators, memory stores — and by the time your agent actually does something useful, you've got a monorepo and a headache.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/garudust-org/garudust-agent" rel="noopener noreferrer"&gt;Garudust Agent&lt;/a&gt;&lt;/strong&gt; takes a different approach: a single ~10 MB statically-linked Rust binary that gives you persistent memory, cron scheduling, multi-platform gateways (Telegram, Discord, Slack, Matrix), and MCP tool support — out of the box, no runtime dependencies.&lt;/p&gt;

&lt;p&gt;In this article I'll show you how to build a &lt;strong&gt;Telegram Daily Briefing Bot&lt;/strong&gt; that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Wakes up every morning at 9 AM and sends you a personalised briefing&lt;/li&gt;
&lt;li&gt;Remembers your preferences across sessions (no repeating yourself)&lt;/li&gt;
&lt;li&gt;Reads files from your server and summarises them&lt;/li&gt;
&lt;li&gt;Uses a custom &lt;strong&gt;Skill&lt;/strong&gt; to keep its briefing format consistent&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By the end you'll understand the core Garudust concepts well enough to build your own agents.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Garudust?
&lt;/h2&gt;

&lt;p&gt;Before diving in, here's what sets it apart from other agent runtimes:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Garudust&lt;/th&gt;
&lt;th&gt;LangChain/LangGraph&lt;/th&gt;
&lt;th&gt;AutoGen&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Language&lt;/td&gt;
&lt;td&gt;Rust&lt;/td&gt;
&lt;td&gt;Python&lt;/td&gt;
&lt;td&gt;Python&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Binary size&lt;/td&gt;
&lt;td&gt;~10 MB&lt;/td&gt;
&lt;td&gt;N/A (pip install)&lt;/td&gt;
&lt;td&gt;N/A (pip install)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cold start&lt;/td&gt;
&lt;td&gt;&amp;lt; 20 ms&lt;/td&gt;
&lt;td&gt;1–5 s&lt;/td&gt;
&lt;td&gt;1–5 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Persistent memory&lt;/td&gt;
&lt;td&gt;Built-in&lt;/td&gt;
&lt;td&gt;Plugin needed&lt;/td&gt;
&lt;td&gt;Plugin needed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Platform adapters&lt;/td&gt;
&lt;td&gt;Built-in&lt;/td&gt;
&lt;td&gt;Plugin needed&lt;/td&gt;
&lt;td&gt;Plugin needed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Self-hosted&lt;/td&gt;
&lt;td&gt;Yes, trivially&lt;/td&gt;
&lt;td&gt;Needs extra infra&lt;/td&gt;
&lt;td&gt;Needs extra infra&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The self-improvement angle is genuinely interesting: the agent notices when it keeps doing something wrong and patches its own Skill files. We'll see that in action below.&lt;/p&gt;




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

&lt;ul&gt;
&lt;li&gt;A Linux server or Mac (the binary is statically linked — no Rust required to run it)&lt;/li&gt;
&lt;li&gt;An API key for any LLM provider: Anthropic, OpenRouter, Ollama, vLLM, etc.&lt;/li&gt;
&lt;li&gt;A Telegram bot token (free — create one via &lt;a href="https://t.me/botfather" rel="noopener noreferrer"&gt;@BotFather&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Step 1 — Install Garudust
&lt;/h2&gt;

&lt;p&gt;Download the pre-built binary from &lt;a href="https://github.com/garudust-org/garudust-agent/releases/latest" rel="noopener noreferrer"&gt;GitHub Releases&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Linux x86_64&lt;/span&gt;
curl &lt;span class="nt"&gt;-LO&lt;/span&gt; https://github.com/garudust-org/garudust-agent/releases/latest/download/garudust-x86_64-unknown-linux-musl.tar.gz
&lt;span class="nb"&gt;tar&lt;/span&gt; &lt;span class="nt"&gt;-xzf&lt;/span&gt; garudust-&lt;span class="k"&gt;*&lt;/span&gt;.tar.gz
&lt;span class="nb"&gt;sudo mv &lt;/span&gt;garudust garudust-server /usr/local/bin/

&lt;span class="c"&gt;# Verify&lt;/span&gt;
garudust &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For macOS (Apple Silicon):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-LO&lt;/span&gt; https://github.com/garudust-org/garudust-agent/releases/latest/download/garudust-aarch64-apple-darwin.tar.gz
&lt;span class="nb"&gt;tar&lt;/span&gt; &lt;span class="nt"&gt;-xzf&lt;/span&gt; garudust-&lt;span class="k"&gt;*&lt;/span&gt;.tar.gz
&lt;span class="nb"&gt;sudo mv &lt;/span&gt;garudust garudust-server /usr/local/bin/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or build from source if you have Rust 1.75+:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/garudust-org/garudust-agent
&lt;span class="nb"&gt;cd &lt;/span&gt;garudust-agent
cargo build &lt;span class="nt"&gt;--release&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PATH&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;/target/release"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 2 — Configure Your LLM Provider
&lt;/h2&gt;

&lt;p&gt;Run the setup wizard:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;garudust setup
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This walks you through picking a provider and saving your key. Under the hood it writes to &lt;code&gt;~/.garudust/config.yaml&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You can also set environment variables directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Anthropic&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ANTHROPIC_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"sk-ant-..."&lt;/span&gt;

&lt;span class="c"&gt;# OpenRouter (gives you access to 200+ models)&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;OPENROUTER_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"sk-or-..."&lt;/span&gt;

&lt;span class="c"&gt;# Self-hosted vLLM (like the Qwen3-8B-AWQ setup I run locally)&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;VLLM_BASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"http://localhost:8000/v1"&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;GARUDUST_MODEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Qwen/Qwen3-8B-AWQ"&lt;/span&gt;

&lt;span class="c"&gt;# Ollama (fully local, no key needed)&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;OLLAMA_BASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"http://localhost:11434"&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;GARUDUST_MODEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"llama3.2"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's verify everything is working:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;garudust doctor
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see all green checkmarks. Now try a quick chat:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;garudust &lt;span class="s2"&gt;"hello, what can you do?"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 3 — Create a Telegram Bot
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Open Telegram and message &lt;a href="https://t.me/botfather" rel="noopener noreferrer"&gt;@BotFather&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Send &lt;code&gt;/newbot&lt;/code&gt; and follow the prompts&lt;/li&gt;
&lt;li&gt;Copy your bot token (looks like &lt;code&gt;7123456789:AAHxxx...&lt;/code&gt;)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Set it in your environment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;TELEGRAM_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"7123456789:AAHxxx..."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 4 — Create a Briefing Skill
&lt;/h2&gt;

&lt;p&gt;This is where Garudust gets interesting. &lt;strong&gt;Skills&lt;/strong&gt; are Markdown files that the agent automatically loads when relevant. They live in &lt;code&gt;~/.garudust/skills/&lt;/code&gt; and are hot-reloaded on every call — no restarts needed.&lt;/p&gt;

&lt;p&gt;Create the directory and your first skill:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; ~/.garudust/skills/daily-briefing
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now create &lt;code&gt;~/.garudust/skills/daily-briefing/SKILL.md&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;daily-briefing&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Instructions for generating a personalised morning briefing via Telegram&lt;/span&gt;
&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;1.0.0&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

&lt;span class="gu"&gt;## Daily Briefing Format&lt;/span&gt;

When asked to generate a morning briefing, always follow this structure:
&lt;span class="p"&gt;
1.&lt;/span&gt; &lt;span class="gs"&gt;**Greeting**&lt;/span&gt; — Use the user's name and current local time
&lt;span class="p"&gt;2.&lt;/span&gt; &lt;span class="gs"&gt;**Today's Focus**&lt;/span&gt; — 2–3 bullet points on what to prioritise (infer from memory or files)
&lt;span class="p"&gt;3.&lt;/span&gt; &lt;span class="gs"&gt;**Quick Stats**&lt;/span&gt; — If any log files or data files are available in ~/briefing-data/, summarise key numbers
&lt;span class="p"&gt;4.&lt;/span&gt; &lt;span class="gs"&gt;**Reminder**&lt;/span&gt; — One motivational sentence, varied each day, never generic

&lt;span class="gu"&gt;## Style Rules&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; Keep the total message under 300 words
&lt;span class="p"&gt;-&lt;/span&gt; Use Telegram-compatible formatting (bold with &lt;span class="err"&gt;*&lt;/span&gt;, italic with _)
&lt;span class="p"&gt;-&lt;/span&gt; Never use HTML tags
&lt;span class="p"&gt;-&lt;/span&gt; If no data files are found, skip the Quick Stats section gracefully
&lt;span class="p"&gt;-&lt;/span&gt; Always end with: "Have a great day, Garudian! 🦅"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That last line — "Have a great day, Garudian!" — is a nod to the Garudust contributor community name.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 5 — Set Up Cron Scheduling
&lt;/h2&gt;

&lt;p&gt;Garudust's cron scheduler uses standard cron expressions. You configure it via the &lt;code&gt;GARUDUST_CRON_JOBS&lt;/code&gt; environment variable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;GARUDUST_CRON_JOBS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"0 9 * * *=Generate a morning briefing using the daily-briefing skill and send it to me on Telegram"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The format is &lt;code&gt;"cron_expression=natural language task"&lt;/code&gt;. The agent handles the rest — it knows who "me" is from the user profile it builds over time.&lt;/p&gt;

&lt;p&gt;You can also add multiple jobs separated by commas:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;GARUDUST_CRON_JOBS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"0 9 * * *=Generate a morning briefing and send via Telegram,0 18 * * 5=Send a weekly summary of tasks completed this week"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 6 — Start the Server
&lt;/h2&gt;

&lt;p&gt;Create a simple &lt;code&gt;.env&lt;/code&gt; file to keep everything together:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# ~/.garudust/.env&lt;/span&gt;
&lt;span class="nv"&gt;ANTHROPIC_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;sk-ant-...
&lt;span class="nv"&gt;TELEGRAM_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;7123456789:AAHxxx...
&lt;span class="nv"&gt;GARUDUST_MODEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;anthropic/claude-sonnet-4-6
&lt;span class="nv"&gt;GARUDUST_CRON_JOBS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"0 9 * * *=Generate a morning briefing using the daily-briefing skill and send it to me on Telegram"&lt;/span&gt;
&lt;span class="nv"&gt;GARUDUST_APPROVAL_MODE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;auto
&lt;span class="nv"&gt;RUST_LOG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;info
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now start the server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Load env and start&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;source&lt;/span&gt; ~/.garudust/.env&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;set&lt;/span&gt; +a
garudust-server &lt;span class="nt"&gt;--port&lt;/span&gt; 3000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or with Docker (the &lt;code&gt;docker-compose.yml&lt;/code&gt; in the repo is ready to use):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cp&lt;/span&gt; .env.example .env
&lt;span class="c"&gt;# Edit .env with your keys&lt;/span&gt;
docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see logs like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;INFO garudust_server: Starting Garudust server on port 3000
INFO garudust_platforms::telegram: Telegram adapter connected
INFO garudust_cron: Scheduled job: "0 9 * * *" → Generate a morning briefing...
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 7 — Teach the Agent About You
&lt;/h2&gt;

&lt;p&gt;Open Telegram and message your bot. The first time, introduce yourself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Hi! I'm Alex. I'm a backend developer working on Rust microservices.
I usually check logs in ~/briefing-data/app.log and ~/briefing-data/errors.log.
I prefer short, direct messages with no fluff.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent saves this to persistent memory automatically. You'll see something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Got it, Alex! I've saved your preferences:
- Name: Alex
- Role: Backend developer (Rust microservices)
- Briefing data path: ~/briefing-data/
- Communication style: short and direct

I'll use these tomorrow morning at 9 AM for your briefing.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the self-improvement loop: you teach it once, and it remembers forever. On the next session — or the next day's cron job — the context is already loaded.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Happens at 9 AM
&lt;/h2&gt;

&lt;p&gt;At 9 AM, the cron job fires. The agent:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Loads the daily-briefing Skill&lt;/strong&gt; — sees the format rules&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reads persistent memory&lt;/strong&gt; — knows your name, preferences, file paths&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Calls &lt;code&gt;read_file&lt;/code&gt;&lt;/strong&gt; on &lt;code&gt;~/briefing-data/app.log&lt;/code&gt; and &lt;code&gt;errors.log&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Generates the briefing&lt;/strong&gt; using the Skill's format&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sends it to you on Telegram&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A real message might look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="ge"&gt;*Good morning, Alex!*&lt;/span&gt; It's 9:00 AM.

&lt;span class="ge"&gt;*Today's Focus*&lt;/span&gt;
• 3 errors in errors.log since yesterday — worth a look before standup
• PR #142 still open — you had this on your radar
• Dependency audit due this sprint

&lt;span class="ge"&gt;*Quick Stats (from app.log)*&lt;/span&gt;
• 14,203 requests last 24h
• p99 latency: 187 ms
• 3 ERRORs, 0 WARNings

&lt;span class="ge"&gt;_Have a great day, Garudian!_&lt;/span&gt; 🦅
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 8 — Test the HTTP API
&lt;/h2&gt;

&lt;p&gt;You don't have to wait until 9 AM. The HTTP gateway is always running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Trigger a briefing right now&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:3000/chat &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"message": "Generate my morning briefing now and send it to me on Telegram"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For streaming responses (great for long tasks):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:3000/chat/stream &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"message": "Summarise my error logs from the last 7 days"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 9 — Run as a systemd Service
&lt;/h2&gt;

&lt;p&gt;For production use on a Linux server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nano /etc/systemd/system/garudust.service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[Unit]&lt;/span&gt;
&lt;span class="py"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;Garudust Agent Server&lt;/span&gt;
&lt;span class="py"&gt;After&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;network.target&lt;/span&gt;

&lt;span class="nn"&gt;[Service]&lt;/span&gt;
&lt;span class="py"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;simple&lt;/span&gt;
&lt;span class="py"&gt;User&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;youruser&lt;/span&gt;
&lt;span class="py"&gt;WorkingDirectory&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/home/youruser&lt;/span&gt;
&lt;span class="py"&gt;EnvironmentFile&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/home/youruser/.garudust/.env&lt;/span&gt;
&lt;span class="py"&gt;ExecStart&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/usr/local/bin/garudust-server --port 3000&lt;/span&gt;
&lt;span class="py"&gt;Restart&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;on-failure&lt;/span&gt;
&lt;span class="py"&gt;RestartSec&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;5s&lt;/span&gt;

&lt;span class="nn"&gt;[Install]&lt;/span&gt;
&lt;span class="py"&gt;WantedBy&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;multi-user.target&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl daemon-reload
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable&lt;/span&gt; &lt;span class="nt"&gt;--now&lt;/span&gt; garudust
&lt;span class="nb"&gt;sudo &lt;/span&gt;journalctl &lt;span class="nt"&gt;-u&lt;/span&gt; garudust &lt;span class="nt"&gt;-f&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The Self-Improving Part
&lt;/h2&gt;

&lt;p&gt;Here's something that makes Garudust distinct from a static chatbot: if the briefing format stops being useful, the agent fixes itself.&lt;/p&gt;

&lt;p&gt;Suppose you message your bot:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;The briefing is too long. Cut the Quick Stats section to just one line.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent updates &lt;code&gt;~/.garudust/skills/daily-briefing/SKILL.md&lt;/code&gt; immediately — while the server is running, without a restart. The next morning's briefing will already follow the updated format.&lt;/p&gt;

&lt;p&gt;You can also ask it to create new skills on the fly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="n"&gt;Write&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;skill&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;when&lt;/span&gt; &lt;span class="n"&gt;I&lt;/span&gt; &lt;span class="n"&gt;ask&lt;/span&gt; &lt;span class="n"&gt;you&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;review&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;Rust&lt;/span&gt; &lt;span class="n"&gt;PR&lt;/span&gt; &lt;span class="err"&gt;—&lt;/span&gt; &lt;span class="n"&gt;check&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;unwraps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;missing&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="n"&gt;handling&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;and&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt; &lt;span class="n"&gt;comments&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It creates &lt;code&gt;~/.garudust/skills/rust-pr-review/SKILL.md&lt;/code&gt; automatically. From that point on, whenever you paste a PR and ask for a review, it loads that skill without being told.&lt;/p&gt;




&lt;h2&gt;
  
  
  Architecture at a Glance
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Your Telegram ──► garudust-server
                       │
                  ┌────┴────────────────────┐
                  │   daily-briefing SKILL  │  ← hot-reloaded markdown
                  │   Persistent Memory     │  ← ~/.garudust/memory/
                  │   Session DB (FTS5)     │  ← SQLite full-text search
                  └────┬────────────────────┘
                       │
                  LLM Transport
          (Anthropic / OpenRouter / vLLM / Ollama)
                       │
                  Tool Registry
         (read_file, write_skill, memory, web_search…)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every piece is a separate Rust crate. If you want to add a new platform or tool, you touch exactly one crate and typically write under 100 lines.&lt;/p&gt;




&lt;h2&gt;
  
  
  What to Build Next
&lt;/h2&gt;

&lt;p&gt;Now that you have the basics, here are some natural extensions:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Add web search to the briefing&lt;/strong&gt;&lt;br&gt;
Set &lt;code&gt;BRAVE_SEARCH_API_KEY&lt;/code&gt; and update your Skill to include: "Always search for the top 3 tech news headlines and include them under a 'News' section."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Multi-platform&lt;/strong&gt;: Run Telegram + Slack simultaneously&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;SLACK_BOT_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"xoxb-..."&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;SLACK_APP_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"xapp-..."&lt;/span&gt;
&lt;span class="c"&gt;# garudust-server handles both in the same process&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Connect to your database via MCP&lt;/strong&gt;&lt;br&gt;
Add to &lt;code&gt;~/.garudust/config.yaml&lt;/code&gt;:&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;mcp_servers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npx&lt;/span&gt;
    &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-y"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;@modelcontextprotocol/server-postgres"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;postgresql://localhost/mydb"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The briefing can now query your own database directly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Delegate to sub-agents&lt;/strong&gt;&lt;br&gt;
For heavy tasks, use the &lt;code&gt;delegate_task&lt;/code&gt; tool — it spawns parallel sub-agents that run concurrently and report back. Great for summarising large codebases or doing multi-step research.&lt;/p&gt;




&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;Garudust isn't trying to be the most featureful agent framework — it's trying to be the most &lt;strong&gt;practical&lt;/strong&gt; one. A single binary, sensible defaults, and just enough structure to let you build real things without ceremony.&lt;/p&gt;

&lt;p&gt;The daily briefing bot we built today shows the core loop:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Install&lt;/strong&gt; — one binary, no dependencies&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Configure&lt;/strong&gt; — env vars or YAML&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Teach&lt;/strong&gt; — talk to it, it remembers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Extend&lt;/strong&gt; — write a Skill in Markdown&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The project is actively developed and welcoming contributors. If you want to add a new platform adapter, a tool, or improve the TUI — check out the &lt;a href="https://github.com/garudust-org/garudust-agent/blob/main/CONTRIBUTING.md" rel="noopener noreferrer"&gt;CONTRIBUTING.md&lt;/a&gt; and look for "good first issue" labels.&lt;/p&gt;

&lt;p&gt;Star the repo, join the community, and become a &lt;strong&gt;Garudian&lt;/strong&gt; 🦅&lt;/p&gt;

&lt;p&gt;→ &lt;strong&gt;&lt;a href="https://github.com/garudust-org/garudust-agent" rel="noopener noreferrer"&gt;github.com/garudust-org/garudust-agent&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Have questions or want to share what you built? Drop a comment below — I read everything.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>rust</category>
      <category>ai</category>
      <category>llm</category>
      <category>telegram</category>
    </item>
    <item>
      <title>สร้าง AI Agent บน LINE ด้วย Garudust (Rust) — ตั้งแต่ต้นจนใช้งานได้จริง</title>
      <dc:creator>Garudust</dc:creator>
      <pubDate>Thu, 14 May 2026 05:06:47 +0000</pubDate>
      <link>https://dev.to/garudust/sraang-ai-agent-bn-line-dwy-garudust-rust-tangaettncchnaichngaanaidcchring-4pgj</link>
      <guid>https://dev.to/garudust/sraang-ai-agent-bn-line-dwy-garudust-rust-tangaettncchnaichngaanaidcchring-4pgj</guid>
      <description>&lt;p&gt;ถ้าคุณอยากมี AI Agent ส่วนตัวที่คุยได้ผ่าน LINE โดยไม่ต้องพึ่ง no-code platform บทความนี้จะพาคุณทำตั้งแต่ต้นจนจบ เครื่องมือที่ใช้คือ &lt;strong&gt;&lt;a href="https://github.com/garudust-org/garudust-agent" rel="noopener noreferrer"&gt;Garudust&lt;/a&gt;&lt;/strong&gt; — AI Agent framework เขียนด้วย Rust รองรับ OpenAI, Anthropic, Ollama, vLLM และ OpenAI-compatible endpoint ทุกเจ้า&lt;/p&gt;




&lt;h2&gt;
  
  
  สิ่งที่ต้องมี
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;เซิร์ฟเวอร์ Linux หรือ macOS (หรือรันบน VPS ก็ได้)&lt;/li&gt;
&lt;li&gt;บัญชี LINE Developers (ฟรี) → &lt;a href="https://developers.line.biz" rel="noopener noreferrer"&gt;developers.line.biz&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;API Key จาก OpenAI, Anthropic, หรือ endpoint อื่นๆ&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://ngrok.com" rel="noopener noreferrer"&gt;ngrok&lt;/a&gt; — &lt;strong&gt;เฉพาะกรณีรันบนเครื่องตัวเองที่ไม่มี public IP&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  1. ติดตั้ง Garudust
&lt;/h2&gt;

&lt;h3&gt;
  
  
  วิธีที่ 1 — ดาวน์โหลด binary
&lt;/h3&gt;

&lt;p&gt;ไปที่ &lt;a href="https://github.com/garudust-org/garudust-agent/releases" rel="noopener noreferrer"&gt;Releases&lt;/a&gt; แล้วดาวน์โหลดไฟล์ที่ตรงกับระบบของคุณ:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Linux x86_64&lt;/span&gt;
curl &lt;span class="nt"&gt;-LO&lt;/span&gt; https://github.com/garudust-org/garudust-agent/releases/latest/download/garudust-v0.3.1-x86_64-unknown-linux-musl.tar.gz
&lt;span class="nb"&gt;tar&lt;/span&gt; &lt;span class="nt"&gt;-xzf&lt;/span&gt; garudust-&lt;span class="k"&gt;*&lt;/span&gt;.tar.gz
&lt;span class="nb"&gt;sudo mv &lt;/span&gt;garudust&lt;span class="k"&gt;*&lt;/span&gt;/garudust garudust&lt;span class="k"&gt;*&lt;/span&gt;/garudust-server /usr/local/bin/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  วิธีที่ 2 — Build จาก source (ต้องมี Rust 1.87+)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/garudust-org/garudust-agent.git
&lt;span class="nb"&gt;cd &lt;/span&gt;garudust-agent
cargo build &lt;span class="nt"&gt;--release&lt;/span&gt; &lt;span class="nt"&gt;--bin&lt;/span&gt; garudust &lt;span class="nt"&gt;--bin&lt;/span&gt; garudust-server
&lt;span class="nb"&gt;sudo cp &lt;/span&gt;target/release/garudust target/release/garudust-server /usr/local/bin/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ตรวจสอบว่าติดตั้งสำเร็จ:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;garudust &lt;span class="nt"&gt;--version&lt;/span&gt;
garudust-server &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  2. ตั้งค่า LINE Messaging API
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;เข้าไปที่ &lt;a href="https://developers.line.biz" rel="noopener noreferrer"&gt;LINE Developers Console&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;สร้าง &lt;strong&gt;Provider&lt;/strong&gt; ใหม่ (หรือใช้อันที่มีอยู่)&lt;/li&gt;
&lt;li&gt;สร้าง &lt;strong&gt;Channel&lt;/strong&gt; ประเภท &lt;strong&gt;Messaging API&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;ที่ tab &lt;strong&gt;Basic settings&lt;/strong&gt; → คัดลอก &lt;strong&gt;Channel secret&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;ที่ tab &lt;strong&gt;Messaging API&lt;/strong&gt; → คลิก &lt;strong&gt;Issue&lt;/strong&gt; เพื่อสร้าง &lt;strong&gt;Channel access token&lt;/strong&gt; (long-lived)&lt;/li&gt;
&lt;li&gt;เปิด &lt;strong&gt;Use webhook&lt;/strong&gt; → toggle เป็น Enable&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;เก็บค่าสองอย่างนี้ไว้:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;LINE_CHANNEL_SECRET&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&lt;/span&gt;
&lt;span class="py"&gt;LINE_CHANNEL_TOKEN&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;eyJhbGci...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  3. ตั้งค่าไฟล์ config
&lt;/h2&gt;

&lt;p&gt;Garudust แยก secret ออกจาก config เป็น 2 ไฟล์:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;~/.garudust/.env&lt;/code&gt; — credentials เท่านั้น (ห้าม commit)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;~/.garudust/config.yaml&lt;/code&gt; — ตั้งค่าพฤติกรรม, platform, port (แชร์ได้)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;~/.garudust/.env&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# LLM provider&lt;/span&gt;
&lt;span class="nv"&gt;ANTHROPIC_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;sk-ant-...

&lt;span class="c"&gt;# LINE adapter — tokens ต้องอยู่ใน .env เสมอ&lt;/span&gt;
&lt;span class="nv"&gt;LINE_CHANNEL_SECRET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
&lt;span class="nv"&gt;LINE_CHANNEL_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;eyJhbGci...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;code&gt;~/.garudust/config.yaml&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;provider&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;anthropic&lt;/span&gt;
&lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;claude-sonnet-4-6&lt;/span&gt;

&lt;span class="na"&gt;system_prompt&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
  &lt;span class="s"&gt;คุณคือผู้ช่วย AI ชื่อ "น้องการุด" พูดภาษาไทย ตอบสั้นกระชับ&lt;/span&gt;
  &lt;span class="s"&gt;ช่วยงานด้านข้อมูล วิเคราะห์ข้อความ และตอบคำถามทั่วไป&lt;/span&gt;
  &lt;span class="s"&gt;ไม่ตอบเนื้อหาที่ไม่เหมาะสม&lt;/span&gt;

&lt;span class="na"&gt;platforms&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;line&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;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3002&lt;/span&gt;
    &lt;span class="na"&gt;webhook_path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/line&lt;/span&gt;      &lt;span class="c1"&gt;# webhook URL: https://your-host/line&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;หลักการ:&lt;/strong&gt; &lt;code&gt;config.yaml&lt;/code&gt; เก็บโครงสร้าง (&lt;code&gt;enabled&lt;/code&gt;, &lt;code&gt;port&lt;/code&gt;, &lt;code&gt;webhook_path&lt;/code&gt;) ส่วน &lt;code&gt;.env&lt;/code&gt; เก็บ secret (&lt;code&gt;LINE_CHANNEL_SECRET&lt;/code&gt;, &lt;code&gt;LINE_CHANNEL_TOKEN&lt;/code&gt;) เท่านั้น&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;เมื่อรัน &lt;code&gt;garudust-server&lt;/code&gt; แล้ว Garudust จะเปิด HTTP server รอรับ webhook จาก LINE ที่ &lt;code&gt;0.0.0.0:3002/line&lt;/code&gt; โดยอัตโนมัติ — &lt;strong&gt;ไม่ต้องตั้งค่า webhook server เพิ่มเอง&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  4. รัน garudust-server
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;garudust-server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ดู log จะเห็น:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;INFO garudust_server: LINE webhook listening on 0.0.0.0:3002/line
INFO garudust_server: API server listening on 0.0.0.0:3000
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Port mapping ของ Garudust:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Port&lt;/th&gt;
&lt;th&gt;ใช้สำหรับ&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;3000&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;HTTP API หลัก (&lt;code&gt;/chat&lt;/code&gt;, &lt;code&gt;/health&lt;/code&gt;, &lt;code&gt;/metrics&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;3001&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Generic webhook&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;3002&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;LINE adapter&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;3003&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;WhatsApp adapter&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  5. ตั้งค่า Webhook URL ใน LINE Developers Console
&lt;/h2&gt;

&lt;p&gt;ขั้นตอนนี้แตกต่างกันตามสภาพแวดล้อม:&lt;/p&gt;

&lt;h3&gt;
  
  
  กรณี A — VPS หรือเซิร์ฟเวอร์ที่มี public IP (Production)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;ไม่ต้องใช้ ngrok&lt;/strong&gt; — LINE เข้าถึง server ของคุณได้ตรงๆ ผ่าน nginx&lt;/p&gt;

&lt;p&gt;ตั้งค่า nginx reverse proxy ก่อน (ดูขั้นตอนที่ 7) แล้วใช้ URL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://yourdomain.com/line
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  กรณี B — เครื่องตัวเองที่ไม่มี public IP (Dev/ทดสอบ)
&lt;/h3&gt;

&lt;p&gt;LINE ไม่สามารถเข้าถึง &lt;code&gt;localhost&lt;/code&gt; ได้และต้องการ HTTPS — ใช้ ngrok สร้าง tunnel ชั่วคราว:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ngrok http 3002
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ngrok จะแสดง URL เช่น:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;Forwarding  https://abc123.ngrok-free.app -&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;http://localhost:3002
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;webhook URL ของคุณคือ:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://abc123.ngrok-free.app/line
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;หมายเหตุ:&lt;/strong&gt; ngrok free tier เปลี่ยน URL ทุกครั้งที่รีสตาร์ท เหมาะสำหรับทดสอบเท่านั้น ใช้ VPS หรือ ngrok paid plan สำหรับ production&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;ตั้ง webhook URL ใน LINE Developers Console:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Tab &lt;strong&gt;Messaging API&lt;/strong&gt; → ส่วน &lt;strong&gt;Webhook settings&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;ใส่ URL ตามสภาพแวดล้อมของคุณ (จากด้านบน)&lt;/li&gt;
&lt;li&gt;คลิก &lt;strong&gt;Verify&lt;/strong&gt; — ควรได้ "Success"&lt;/li&gt;
&lt;li&gt;เปิด &lt;strong&gt;Use webhook&lt;/strong&gt; ให้เป็น Enable&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;ลองส่งข้อความหา LINE Official Account ของคุณ — AI จะตอบกลับทันที&lt;/p&gt;




&lt;h2&gt;
  
  
  6. เพิ่ม Cron Job (ทำงานอัตโนมัติ)
&lt;/h2&gt;

&lt;p&gt;Garudust มีระบบ cron ในตัว ตั้งค่าใน &lt;code&gt;.env&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# ส่ง briefing ทุกเช้า 8 โมง&lt;/span&gt;
&lt;span class="nv"&gt;GARUDUST_CRON_JOBS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"0 8 * * *=สรุปข่าวเทคโนโลยีไทยวันนี้ บันทึกลงไฟล์ ~/briefing.md"&lt;/span&gt;

&lt;span class="c"&gt;# รวบรวม memory อัตโนมัติทุกคืน 03:00&lt;/span&gt;
&lt;span class="nv"&gt;GARUDUST_MEMORY_CRON&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"0 3 * * *"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  7. Deploy Production (systemd + nginx)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  nginx reverse proxy
&lt;/h3&gt;

&lt;p&gt;LINE ต้องการ HTTPS — nginx จัดการ SSL แล้ว proxy ต่อไปยัง Garudust บน port 3002:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="c1"&gt;# /etc/nginx/sites-available/garudust&lt;/span&gt;
&lt;span class="k"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="mi"&gt;443&lt;/span&gt; &lt;span class="s"&gt;ssl&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;server_name&lt;/span&gt; &lt;span class="s"&gt;yourdomain.com&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;ssl_certificate&lt;/span&gt;     &lt;span class="n"&gt;/etc/letsencrypt/live/yourdomain.com/fullchain.pem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;ssl_certificate_key&lt;/span&gt; &lt;span class="n"&gt;/etc/letsencrypt/live/yourdomain.com/privkey.pem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;# LINE webhook — proxy ไปที่ Garudust LINE adapter port 3002&lt;/span&gt;
    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/line&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_pass&lt;/span&gt; &lt;span class="s"&gt;http://127.0.0.1:3002&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;Host&lt;/span&gt; &lt;span class="nv"&gt;$host&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;X-Real-IP&lt;/span&gt; &lt;span class="nv"&gt;$remote_addr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;# HTTP API หลัก&lt;/span&gt;
    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_pass&lt;/span&gt; &lt;span class="s"&gt;http://127.0.0.1:3000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;Host&lt;/span&gt; &lt;span class="nv"&gt;$host&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo ln&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; /etc/nginx/sites-available/garudust /etc/nginx/sites-enabled/
&lt;span class="nb"&gt;sudo &lt;/span&gt;nginx &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl reload nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  systemd service
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="c"&gt;# /etc/systemd/system/garudust.service
&lt;/span&gt;&lt;span class="nn"&gt;[Unit]&lt;/span&gt;
&lt;span class="py"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;Garudust AI Agent Server&lt;/span&gt;
&lt;span class="py"&gt;After&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;network.target&lt;/span&gt;

&lt;span class="nn"&gt;[Service]&lt;/span&gt;
&lt;span class="py"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;simple&lt;/span&gt;
&lt;span class="py"&gt;User&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;ubuntu&lt;/span&gt;
&lt;span class="py"&gt;ExecStart&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/usr/local/bin/garudust-server&lt;/span&gt;
&lt;span class="py"&gt;Restart&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;on-failure&lt;/span&gt;
&lt;span class="py"&gt;RestartSec&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;5&lt;/span&gt;
&lt;span class="py"&gt;EnvironmentFile&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/home/ubuntu/.garudust/.env&lt;/span&gt;
&lt;span class="py"&gt;WorkingDirectory&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/home/ubuntu&lt;/span&gt;

&lt;span class="nn"&gt;[Install]&lt;/span&gt;
&lt;span class="py"&gt;WantedBy&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;multi-user.target&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl daemon-reload
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable&lt;/span&gt; &lt;span class="nt"&gt;--now&lt;/span&gt; garudust
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl status garudust
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;webhook URL สำหรับตั้งใน LINE Console:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://yourdomain.com/line
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  สรุป config ทั้งหมด
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;~/.garudust/.env&lt;/code&gt;&lt;/strong&gt; — secrets เท่านั้น&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;ANTHROPIC_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;sk-ant-...
&lt;span class="nv"&gt;LINE_CHANNEL_SECRET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
&lt;span class="nv"&gt;LINE_CHANNEL_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;eyJhbGci...
&lt;span class="nv"&gt;GARUDUST_MEMORY_CRON&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"0 3 * * *"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;~/.garudust/config.yaml&lt;/code&gt;&lt;/strong&gt; — พฤติกรรมและโครงสร้าง&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;provider&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;anthropic&lt;/span&gt;
&lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;claude-sonnet-4-6&lt;/span&gt;

&lt;span class="na"&gt;system_prompt&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
  &lt;span class="s"&gt;คุณคือผู้ช่วย AI ชื่อ "น้องการุด" พูดภาษาไทย ตอบสั้นกระชับ&lt;/span&gt;

&lt;span class="na"&gt;platforms&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;line&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;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3002&lt;/span&gt;
    &lt;span class="na"&gt;webhook_path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/line&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  สรุปขั้นตอนตามสภาพแวดล้อม
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Dev (localhost)&lt;/th&gt;
&lt;th&gt;Production (VPS)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Garudust รัน webhook ที่&lt;/td&gt;
&lt;td&gt;&lt;code&gt;localhost:3002/line&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;localhost:3002/line&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;เปิด HTTPS ด้วย&lt;/td&gt;
&lt;td&gt;ngrok tunnel&lt;/td&gt;
&lt;td&gt;nginx + Let's Encrypt&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Webhook URL ใน LINE Console&lt;/td&gt;
&lt;td&gt;&lt;code&gt;https://xxx.ngrok-free.app/line&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;https://yourdomain.com/line&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ngrok&lt;/td&gt;
&lt;td&gt;✅ จำเป็น&lt;/td&gt;
&lt;td&gt;❌ ไม่ต้อง&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Garudust รองรับ OpenAI, Anthropic, Ollama, vLLM และ OpenAI-compatible endpoint ทุกเจ้า — เปลี่ยน provider ได้แค่แก้ 2 บรรทัดใน &lt;code&gt;config.yaml&lt;/code&gt; แล้วรีสตาร์ท&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/garudust-org/garudust-agent" rel="noopener noreferrer"&gt;garudust-org/garudust-agent&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Docs:&lt;/strong&gt; &lt;a href="https://garudust-org.github.io/garudust-agent" rel="noopener noreferrer"&gt;garudust-org.github.io/garudust-agent&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;ถ้ามีคำถามหรือเจอปัญหา เปิด Issue บน GitHub ได้เลย หรือ comment ไว้ด้านล่างครับ&lt;/p&gt;

</description>
      <category>rust</category>
      <category>ai</category>
      <category>line</category>
      <category>chatbot</category>
    </item>
  </channel>
</rss>
