<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:media="http://search.yahoo.com/mrss/"><channel><title>Kobako on 弦而時習之</title><link>https://blog.aotoki.me/series/kobako/</link><description>Recent content in Kobako on 弦而時習之</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><managingEditor>contact@aotoki.me (蒼時弦也)</managingEditor><webMaster>contact@aotoki.me (蒼時弦也)</webMaster><lastBuildDate>Mon, 15 Jun 2026 21:40:29 +0800</lastBuildDate><atom:link href="https://blog.aotoki.me/series/kobako/index.xml" rel="self" type="application/rss+xml"/><item><title>Kobako：Cold Start 原來能快 100 倍？</title><link>https://blog.aotoki.me/posts/2026/06/17/kobako-cold-start-100x-faster/</link><category>LLM</category><category>AI</category><category>經驗</category><category>Ruby</category><category>WebAssembly</category><category>Gem</category><category>效能</category><pubDate>Wed, 17 Jun 2026 00:00:00 +0800</pubDate><author>contact@aotoki.me (蒼時弦也)</author><guid>https://blog.aotoki.me/posts/2026/06/17/kobako-cold-start-100x-faster/</guid><description>&lt;p&gt;&lt;a href="https://github.com/elct9620/kobako"&gt;Kobako&lt;/a&gt; 是近期我針對 Ruby 生態系中，對 &lt;a href="https://blog.aotoki.me/tags/Harness-Engineering/"&gt;Harness Engineering&lt;/a&gt; 支援所開發基於 WebAssembly 和 mruby 的沙盒（Sandbox）用來填補 AI 所撰寫的程式碼，沒有可以安全運行的環境。&lt;/p&gt;
&lt;p&gt;關於 Kobako 的設計理念，我在 &lt;a href="https://blog.aotoki.me/posts/2026/05/20/kobako-ruby-sandbox-for-ai/"&gt;上一篇文章&lt;/a&gt; 已經介紹過，這次想聊的是效能。在初期的版本中 Cold Start（冷啟動，通常指初次啟動）大概要花上 500 ms 左右，這遠比最佳實踐通常會抓 200 ms 回應來說慢的不少，即使 AI 通常接受更慢的回應，但這不是等待 LLM 回應，仍該用過去的 API 標準來看待。&lt;/p&gt;</description><content:encoded>&lt;p&gt;&lt;a href="https://github.com/elct9620/kobako"&gt;Kobako&lt;/a&gt; 是近期我針對 Ruby 生態系中，對 &lt;a href="https://blog.aotoki.me/tags/Harness-Engineering/"&gt;Harness Engineering&lt;/a&gt; 支援所開發基於 WebAssembly 和 mruby 的沙盒（Sandbox）用來填補 AI 所撰寫的程式碼，沒有可以安全運行的環境。&lt;/p&gt;
&lt;p&gt;關於 Kobako 的設計理念，我在 &lt;a href="https://blog.aotoki.me/posts/2026/05/20/kobako-ruby-sandbox-for-ai/"&gt;上一篇文章&lt;/a&gt; 已經介紹過，這次想聊的是效能。在初期的版本中 Cold Start（冷啟動，通常指初次啟動）大概要花上 500 ms 左右，這遠比最佳實踐通常會抓 200 ms 回應來說慢的不少，即使 AI 通常接受更慢的回應，但這不是等待 LLM 回應，仍該用過去的 API 標準來看待。&lt;/p&gt;
&lt;h2 id="pre-compile"&gt;預先編譯&lt;/h2&gt;
&lt;p&gt;WebAssembly 經過多年發展後，已經有很多新的技術跟規格，其中 &lt;code&gt;cwasm&lt;/code&gt;（Wasmtime 把 &lt;code&gt;.wasm&lt;/code&gt; 預先編譯後的產物）就是一種 AOT（Ahead-of-Time，預先編譯）的處理方式。&lt;/p&gt;
&lt;p&gt;原因在於，我們編譯為 &lt;code&gt;wasm&lt;/code&gt; 格式後，是針對 WebAssembly 的虛擬機器所設計，如果拿來跑 mruby 大致上就是在一個 WebAssembly 虛擬機器中，再啟動一個 mruby 虛擬機器，套了兩層在運作。&lt;/p&gt;
&lt;p&gt;透過 &lt;code&gt;cwasm&lt;/code&gt; 處理，會先在運行的環境直接編譯成當下環境 CPU 對應的機器碼，那就無限接近於把 WebAssembly 虛擬機器消除，只留下 mruby 虛擬機器的成本，啟動速度就從 500 ms 縮減到 5 ms 左右，因此 Cold Start 的時間馬上快上 100 倍。&lt;/p&gt;
&lt;p&gt;簡單來說，Kobako 本身並沒有做太多處理，只是很單純的透過 WebAssembly 的標準，就極大的加速到跟一次簡單的 SQL 查詢差不多，對大部分應用幾乎等同無感。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;今年 RubyKaigi 中 Matz 的新專案 &lt;a href="https://github.com/matz/spinel"&gt;Spinel&lt;/a&gt; 正好就是一種 Ruby 的 AOT 編譯器，至於 &lt;code&gt;cwasm&lt;/code&gt; 我則是第一次接觸到，畢竟過往都還停留在 &lt;code&gt;wasm&lt;/code&gt; 的認識，這次跟 AI 協作意外學到新的概念。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="pre-boot"&gt;預先偷跑&lt;/h2&gt;
&lt;p&gt;當在裝置上啟動過一次 Kobako 後，後續的 Cold Start 就會因為 AOT 從 500 ms 變成只要 5 ms 就能啟動完畢，那麼 &lt;code&gt;Kobako::Sandbox.new&lt;/code&gt; 也能變快嗎？&lt;/p&gt;
&lt;p&gt;原本的設計上，我們會把每個 &lt;code&gt;Kobako::Sandbox.new&lt;/code&gt; 都獨立建立一個實例（Instance）並且在每次 &lt;code&gt;#run&lt;/code&gt; 或者 &lt;code&gt;#eval&lt;/code&gt; 時重新初始化 mruby 虛擬機器，確保可重用與乾淨隔離，但每多一個沙盒就會多佔用約 560 KB 的記憶體，對於希望「大量產生、刪除」的設計不太友善，當數量增加記憶體就會急劇消耗，而且還有一個初始化的時間約 130 µs 的成本在，即使這已經非常快。&lt;/p&gt;
&lt;p&gt;Wasmtime 提供了一個叫做 &lt;a href="https://crates.io/crates/wasmtime-wizer"&gt;wizer&lt;/a&gt; 的機制，我們可以預先把運行的記憶體準備好。這是因為每次啟動新的 mruby 虛擬機器時，對所有使用相同 &lt;code&gt;.wasm&lt;/code&gt; 的 &lt;code&gt;Kobako::Sandbox&lt;/code&gt; 是完全相同的記憶體，那麼預先把把 mruby 虛擬機器跑到啟動好的狀態存到 &lt;code&gt;.wasm&lt;/code&gt; 中，未來啟動時就不需要再次去計算。&lt;/p&gt;
&lt;p&gt;接下來搭配 Wasmtime 的 &lt;code&gt;InstancePre&lt;/code&gt; 和 &lt;code&gt;Copy-on-Write&lt;/code&gt; 兩個設計，就可以把啟動時間跟記憶體使用壓的更低。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;InstancePre&lt;/code&gt; - 把 Kobako 的 ABI（Application Binary Interface，應用程式二進位介面）先計算好，未來重新利用，不用每次都計算&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Copy-on-Write&lt;/code&gt; - 建立新的 &lt;code&gt;Kobako::Sandbox&lt;/code&gt; 時，使用同一份 Baked 的 mruby 虛擬機器，不另外建立&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;透過這些處理，做一次 &lt;code&gt;Kobako::Sandbox.new&lt;/code&gt; 的時間，就從 130 µs 降低到 30 µs 左右，記憶體使用從 560 KB 減少到 1 KB，因為不再需要每個沙盒都保存完整的記憶體，在有改變之前都會使用同一份基礎。&lt;/p&gt;
&lt;h2 id="design-decision"&gt;設計取捨&lt;/h2&gt;
&lt;p&gt;原本在我的預期中，會透過消耗相對多的記憶體去換取更快的速度，但結果反而出乎我的預期，在速度跟記憶體使用上都得到很大的提升，更快地幫助我把專案推進到可以正式上線的等級。&lt;/p&gt;
&lt;p&gt;尤其是這次用到的技術，很多都不是新技術，而是建構在已經被使用多年的機制。但透過 AI 的輔助，我很快的挖掘出這些技術，並且放置到恰當的位置。&lt;/p&gt;
&lt;p&gt;這對軟體工程師這個職業的影響，依舊還是對應我們所熟知的衝擊，有經驗的工程師更有價值，原因就是「無條件接受」跟「有意識篩選」是兩件事情，後者是專業工程師的素養，在這次改版之前，我已經直覺的「可以用 Cache 重用」這樣的直覺判斷，先減少「每次都要等 500 ms」變成只等一次。&lt;/p&gt;
&lt;p&gt;然而當整體功能完善後，考慮到每次啟動的時間跟佔用的記憶體，就讓我開始思考「還能更好嗎？」但受限於對 WebAssembly 技術不是熟悉的情境，透過 AI 大量搜集資料、探索，很快就確認 AOT、Bake 等技術是合理且有幫助的，分段處理（如果 AOT 不成立，做 Bake 大概也沒差異太大）一步一步推進來達到更好的成果。&lt;/p&gt;
&lt;p&gt;我認為這在 AI 時代是很值得思考的問題，我們從 AI 身上拿到的「加速」是什麼？以 Kobako 的案例，整個專案極大的提升背後，是快速搜集資料，以及對軟體開發知識的熟悉得出適當的判斷。&lt;/p&gt;</content:encoded></item><item><title>Kobako：讓 Agent 安全的操作 Rails</title><link>https://blog.aotoki.me/posts/2026/05/20/kobako-ruby-sandbox-for-ai/</link><category>LLM</category><category>AI</category><category>經驗</category><category>Ruby</category><category>Harness Engineering</category><category>Rails</category><pubDate>Wed, 20 May 2026 00:00:00 +0800</pubDate><author>contact@aotoki.me (蒼時弦也)</author><guid>https://blog.aotoki.me/posts/2026/05/20/kobako-ruby-sandbox-for-ai/</guid><description>&lt;p&gt;今年 &lt;a href="https://rubykaigi.org/2026/"&gt;RubyKaigi 2026&lt;/a&gt; 時，剛好在跟朋友聊 Harness Engineering 提到 Sandbox 的部分，覺得 Ruby 語言目前還沒有比較完善的解決方案，剛好第二天晚上的 Code Party 被分配到 &lt;a href="https://github.com/ruby/drb"&gt;druby&lt;/a&gt;（Ruby 語言內建的分散式系統）組別，讓我有了靈感，讓 &lt;a href="https://github.com/elct9620/kobako"&gt;Kobako&lt;/a&gt; 被設計出來。&lt;/p&gt;</description><content:encoded>&lt;p&gt;今年 &lt;a href="https://rubykaigi.org/2026/"&gt;RubyKaigi 2026&lt;/a&gt; 時，剛好在跟朋友聊 Harness Engineering 提到 Sandbox 的部分，覺得 Ruby 語言目前還沒有比較完善的解決方案，剛好第二天晚上的 Code Party 被分配到 &lt;a href="https://github.com/ruby/drb"&gt;druby&lt;/a&gt;（Ruby 語言內建的分散式系統）組別，讓我有了靈感，讓 &lt;a href="https://github.com/elct9620/kobako"&gt;Kobako&lt;/a&gt; 被設計出來。&lt;/p&gt;
&lt;h2 id="sandbox"&gt;沙盒選擇：為什麼是 WebAssembly&lt;/h2&gt;
&lt;p&gt;要打造 AI Agent 可以運行的環境有很多路線，像是虛擬機器（Virtual Machine）、容器技術、WebAssembly 都可以實現。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;目前有哪些路線，可以讀 &lt;a href="https://blog.aihao.tw/2026/04/20/agent-sandbox-landscape/"&gt;Agent Sandbox 沙箱架構: 論兩種設計模式與七種隔離方式&lt;/a&gt;這篇文章的整理&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;選擇哪一種技術，就會影響到後續技術發展的路線，以及可以做的事情，在搜集現有的解決方案跟技術後，最終只能走自己開發。&lt;/p&gt;
&lt;p&gt;舉例來說，Shopify 有 &lt;a href="https://github.com/Shopify/mruby-engine"&gt;mruby-engine&lt;/a&gt; 的技術，這是相當理想的選項之一，但是這個做法只能保證 Host（宿主）跟客體（Guest）不是直接互通的，如果運行的 Ruby 腳本針對記憶體或是一些攻擊，都很容易打穿，給 Agent 使用強度還是稍嫌不足。&lt;/p&gt;
&lt;p&gt;如果選虛擬機器或者容器技術，最大的困難是對環境的要求相對嚴苛，這種嚴苛對小專案或者開發都會有不少限制，所以最後 Kobako 選擇 WebAssembly 的路線，有類似虛擬機器的 WASI 標準，跟直接跑 mruby 來說，犧牲一些啟動、運行速度，換到更好的隔離性還算是划算。&lt;/p&gt;
&lt;p&gt;後來 Shopify 也有投資元在 &lt;a href="https://github.com/bytecodealliance/wasmtime-rb"&gt;wasmtime&lt;/a&gt; 讓 &lt;a href="https://github.com/ruby/ruby.wasm"&gt;ruby.wasm&lt;/a&gt; 可以替代原本 Shopify 的腳本功能，也因此 ruby.wasm 也是我考量的選項之一，但最終因為 ruby.wasm 本身的限制，讓我走向不同的路線。&lt;/p&gt;
&lt;h2 id="wasi"&gt;WASI 的限制與 Kobako 的取捨&lt;/h2&gt;
&lt;p&gt;WASI 全名是 WebAssembly System Interface，簡單來看，是一種讓我們在 WebAssembly 環境中獲得類似 UNIX 介面的架構，像是 ruby.wasm 就是使用 Preview 1 版本的標準，並且正在測試 Preview 2 的標準。&lt;/p&gt;
&lt;p&gt;然而，這個介面設計的非常的差，以 Preview 1 來說，除了有 Stdin / Stdout / Stderr 等介面，以及類似 VFS（Virtual File System）的機制外，基本上沒什麼能做的事情，也不支援 I/O 之類的行為，這也造成 Kobako 在評估是否要用 ruby.wasm 當基底時，只能放棄這條路線。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;即使 ruby.wasm 能用，也不一定是好的選項，因為本身需要載入 20 MB 左右的 Ruby 環境，加上啟動時間可能還比容器技術慢&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;目前 WASI 有 Preview 1 ~ Preview 3 個不同版本，後面要建置跟編譯也有不同類型的麻煩，最後 Kobako 選比較穩定和普及的 Preview 1 標準來實作，跟 ruby.wasm 的差異在於額外的 ABI（Application Binary Interface）來實現一個 In-Memory RPC 的溝通，讓 Sandbox 中的 Ruby 腳本能夠呼叫 Host 提供的方法。&lt;/p&gt;
&lt;p&gt;這也是沒有選擇 ruby.wasm 的主要原因，如果想增加 ABI 就必須完整編譯 ruby.wasm 背後的開發成本跟困難度會極大上升，但是 mruby 設計給嵌入式系統的特性反而就很適合。&lt;/p&gt;
&lt;p&gt;在今年 RubyKaigi 也有 &lt;a href="https://rubykaigi.org/2026/presentations/udzura.html"&gt;Uzumibi: Reinventing mruby for the Edges&lt;/a&gt; 這樣的主題，剛好呼應 Kobako 想要實現 Cloudflare 針對 MCP 問題所提出的 &lt;a href="https://blog.cloudflare.com/code-mode/"&gt;Code Mode&lt;/a&gt; 想法，同樣是從 Edge 環境運行為出發點，mruby 的規模也剛很好適合。&lt;/p&gt;
&lt;h2 id="rpc"&gt;RPC：讓 Sandbox 與 Host 溝通&lt;/h2&gt;
&lt;p&gt;Kobako 受到 druby 啟發的地方，就是 druby 巧妙的運用 Ruby 語言特性來實現 RPC 機制，以下是 Kobako 實際使用的範例。&lt;/p&gt;
&lt;div class="codeblock not-prose" data-controller="codeblock"&gt;
&lt;div data-codeblock-target="code"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-ruby" data-lang="ruby"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="no"&gt;User&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Data&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;define&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;2&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;3&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;sandbox&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Kobako&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Sandbox&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;4&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;sandbox&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;define&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:App&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:User&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Aotoki&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;5&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;6&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;sandbox&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;~&lt;/span&gt;&lt;span class="no"&gt;RUBY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;7&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;Hello, &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;App&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;8&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="no"&gt;RUBY&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;9&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# =&amp;gt; &amp;#34;Hello, Aotoki&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;button class="codeblock__copy"
data-action="codeblock#copy"
data-codeblock-target="button"
data-copy-text="複製"
data-copied-text="已複製"
data-copy-label="複製程式碼"
data-copied-label="程式碼已複製"
aria-label="複製程式碼"&gt;
複製
&lt;/button&gt;
&lt;/div&gt;
&lt;p&gt;在 Sandbox 中，要怎麼知道 Host 端提供了 &lt;code&gt;App::User&lt;/code&gt; 而且有 &lt;code&gt;#name&lt;/code&gt; 這個方法呢？&lt;/p&gt;
&lt;p&gt;Kobako 在每次 &lt;code&gt;Sandbox#run&lt;/code&gt; 的時候，都會自動注入像這樣的程式碼：&lt;/p&gt;
&lt;div class="codeblock not-prose" data-controller="codeblock"&gt;
&lt;div data-codeblock-target="code"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-ruby" data-lang="ruby"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;App&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;User&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Kobako&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;RPC&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;2&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# ...&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;button class="codeblock__copy"
data-action="codeblock#copy"
data-codeblock-target="button"
data-copy-text="複製"
data-copied-text="已複製"
data-copy-label="複製程式碼"
data-copied-label="程式碼已複製"
aria-label="複製程式碼"&gt;
複製
&lt;/button&gt;
&lt;/div&gt;
&lt;p&gt;然後，就沒有然後，我們的 Sandbox 環境就能自然地知道 &lt;code&gt;#name&lt;/code&gt; 可以呼叫，這是因為 Ruby 語言天生支援這樣的實作，因此 &lt;code&gt;Kobako::RPC::Client&lt;/code&gt; 實現了這樣的行為&lt;/p&gt;
&lt;div class="codeblock not-prose" data-controller="codeblock"&gt;
&lt;div data-codeblock-target="code"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-ruby" data-lang="ruby"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Kobako&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;RPC&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Client&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;2&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;method_missing&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;3&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;__kobako_rpc_call__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;4&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;5&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;button class="codeblock__copy"
data-action="codeblock#copy"
data-codeblock-target="button"
data-copy-text="複製"
data-copied-text="已複製"
data-copy-label="複製程式碼"
data-copied-label="程式碼已複製"
aria-label="複製程式碼"&gt;
複製
&lt;/button&gt;
&lt;/div&gt;
&lt;p&gt;這個 &lt;code&gt;__kobako_rpc_call__&lt;/code&gt; 是利用 Rust 所定義的 ABI，負責將 Method Call 轉到 WebAssembly 外面，等外面回覆後再轉發回 WebAssembly。透過這個機制，就能在隔離性跟可擴充性之間達到平衡。&lt;/p&gt;
&lt;p&gt;套用到 Rails 的情境，原本要整合 AI 需要重新設計跟定義工具，但是原本就有做不錯的 Service Object 封裝跟權限控管，就只需要利用 &lt;a href="https://github.com/ruby/rbs"&gt;RBS&lt;/a&gt; 向 AI 說明介面定義，讓 Agent 自動轉寫操作的腳本即可。&lt;/p&gt;
&lt;div class="codeblock not-prose" data-controller="codeblock"&gt;
&lt;div data-codeblock-target="code"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-ruby" data-lang="ruby"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 1&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;sandbox&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Kobako&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Sandbox&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 2&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;ns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sandbox&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;define&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:Merchant&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 3&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;ns&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:Product&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;ProductManagementService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;actor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;current_user&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 4&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# - ProductManagementService#where&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 5&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# - ProductManagementService#update&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 6&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# - ...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 7&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 8&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Agent Tool&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 9&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;execute_code&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;10&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;sandbox&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;11&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;12&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;13&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Merchant::Product.where(name: &amp;#34;%macOS%&amp;#34;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;14&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# =&amp;gt; [#&amp;lt;Product id=1&amp;gt;, #&amp;lt;Product id=2&amp;gt;]&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;button class="codeblock__copy"
data-action="codeblock#copy"
data-codeblock-target="button"
data-copy-text="複製"
data-copied-text="已複製"
data-copy-label="複製程式碼"
data-copied-label="程式碼已複製"
aria-label="複製程式碼"&gt;
複製
&lt;/button&gt;
&lt;/div&gt;
&lt;p&gt;這也是 Cloudflare 為什麼需要用他們的 Cloudflare Worker 底層技術，改造出 Code Mode 的使用方式，因為這樣可以很快的把大量 API 整合進去，但不需要提供大量的工具。&lt;/p&gt;
&lt;p&gt;Cloudflare Code Mode 提供一個值得思考的選擇，利用 LLM 本身的程式能力來解決問題，Kobako 則從 Ruby 生態系提取經驗，讓這種整合更加無縫。Kobako 專案目前也有 &lt;a href="https://github.com/elct9620/kobako/tree/main/examples/codemode"&gt;examples/codemode&lt;/a&gt; 的範例，可以使用 OpenAI 相容的 API 或者本地 Ollama / LMStudio 搭配 Gemma 4 體驗 KeyValue Store + WebFetch 的功能，有興趣的可以嘗試看看。&lt;/p&gt;</content:encoded></item></channel></rss>