---
title: "Kobako：讓 Block 可以穿透沙盒"
date: 2026-11-04T00:00:00+08:00
publishDate: 2026-11-04T00:00:00+08:00
lastmod: 2026-11-02T22:04:55+08:00
tags: ["LLM","AI","經驗","Ruby","WebAssembly","Gem","mruby","Rust"]
series: "kobako"
toc: true
cover: "https://blog.aotoki.me/posts/2026/11/04/kobako-block-through-sandbox/cover_hu_f8f86b40e3d1952e.webp"
permalink: "https://blog.aotoki.me/posts/2026/11/04/kobako-block-through-sandbox/"
language: "zh-tw"
---

[Kobako：Handle 機制](https://blog.aotoki.me/posts/2026/10/14/kobako-handle-mechanism/)對許多支援腳本語言的專案並不算太特殊的機制，但是 Block 概念算是 Ruby 語言特有的能力，如果使用沙盒（Sandbox）時無法善用，那就太可惜。

然而，像這樣的寫法真的做得到嗎？

```ruby
sandbox.bind("Seq::Map", ->(items, &blk) { items.map(&blk) })
sandbox.eval("Seq::Map.call([1, 2, 3]) { |x| x * 2 }").value

# => [2, 4, 6]
```

我們竟然可以在沙盒裡面用一個 Block 接收外部的呼叫。

## Enumerator::Yielder

為了讓這件事情實現，我們需要有一種「幫忙把 Block 交接」這樣的設計，剛好在 CRuby 裡面有一個物件可以表達這樣的行為。

```ruby
y = Enumerator::Yielder.new do |x|
  puts x
end

[1, 2, 3].each(&y)
```

上面看起來並不特殊，使用 Lambda 似乎也能做到相同的效果，但這裡最抽象的地方是 `Enumerator::Yielder.new` 收到的 Block 是使用他的人。

我們可以來看 `Enumerator` 的例子

```ruby
e = Enumerator.new do |y|
  pp y # => Enumerator::Yielder
  y << 1
  y.yield 2
end

e.each { |x| puts x }
```

在這個情境 `y` 會是一個 `Enumerator::Yielder` 物件，他收到的 Block 是來自 `e.each` 的那一個，利用這個特性，就可以把沙盒中的那一個 Block 變成外部可以使用的版本。

## Kobako::Transport::Yielder

當我們在 mruby 端呼叫一個 Block 是屬於外部物件時，會先發起向外的呼叫，並且把這個內部的 Proc 指標記錄下來，等待外部呼叫。

```ruby
sandbox.eval("Sequence.map { |x| x * 2 }")
```

此時對於 CRuby 來說，看到的是一個 mruby 的記憶體位置，表示 mruby 中的 `Proc` 也就是 `{ |x| x * 2 }` 這一個 Block 但是無法呼叫。

Kobako 會把這個無法呼叫的記憶體位置，用 `Kobako::Transport::Yielder` 包裝起來，變成跟 `Enumerator::Yielder` 相同的概念。

```ruby
sandbox.bind("Sequence", Enumerator.new { |y| y << 1 })
sandbox.eval("Sequence.map { |x| x * 2 }").value # [2]
```

在這個情境中 Kobako 巧妙的把宿主（Host）和客體（Guest）的呼叫隱藏起來，只留下 CRuby 原生的 Yielder 概念，讓 Block 看起來在內外都是可用的。

> 範例的寫法在新版的 Kobako 會失敗，為了加強安全性，只有使用者明確定義的方法才會預設被放行，如果是 `Enumerator` 這類原生的 `#each` 和 `#map` 會被拒絕，這是因為有多個逃逸漏洞，都是利用這類邊界達成，因此在新版禁止使用。

## 語言特性

在 AI 能夠快速、大量實作功能的當下，我認為這種善用語言特性的機制反而更加難得，因為他不是單純的堆疊特性，而是仔細思考如果要讓使用者獲得直覺、原生的體驗可以怎樣設計。

這個特性在很多地方都非常有用，像是搭配 Rails 時，我們可以輕鬆的做出這樣的設計。

```ruby
class MetricService
  def initialize(metric)
    @metrics = Metric.where(metric: metric)
  end

  def each(&block) = @metrics.each(&block)
  def map(&block) = @metrics.map(&block)

  def by_day
    @metrics = @metrics.group(:date)
    self
  end

  def count
    @metrics = @metrics.count
    self
  end

  def sum_of(type)
    @metrics = @metrics.sum(type)
    self
  end
end

sandbox.bind("Metric", MetricService.new(:page_view))
sandbox.eval("Metric.by_day.sum_of(:value).map { ... }").value
```

只需要用 CRuby 包裝特定的物件，就可以讓 mruby 像是原生的方式處理資料，這對於 Agent 來說會比讀出資料在自己處理更容易，因為可以直接寫一段程式來處理資料，就不需要把中間過程留在 Context（上下文）消耗額外的 Token 來處理。

這也是 Kobako 最初的想法，要讓 Ruby 生態系有一個可以使用的 [Code Mode](https://blog.cloudflare.com/zh-tw/code-mode-mcp)，不依賴容器技術等機制，而是簡單、輕量就可以使用的選擇。
