OpenCodeでコーディングエージェントを実装した
はじめに
この記事は、さくらのAI Engine で Kimi K2.7 Code が使えるということで、今回は OpenCode を使って試してみた内容です。
最初に参考としたのは、Hugging Face が公開しているインシデント報告
Security incident disclosure — July 2026 です。
このレポートをそのまま再現したいわけではなく、Grok に読ませて exploit 的な要素をできるだけ落としつつ、
「環境が壊れてもゴールを見失わない」「失敗を情報として扱う」「表面的な手順より実質的な成功条件を優先する」といった、設計上の教訓だけを取り出して future_design.md に整理しました。
その上で、Kimi K2.7 Code にはコードを直接書かせるのではなく、まず design.spec.yaml を実装させる形にして、仕様から実装へ進めました。
さくらのAI Engine は、Chat completions を月 3,000 リクエストまで無料で使えて、
上限を超えても自動で課金されない仕様なので、今回のような「とりあえずやってみる」検証にはとても向いていました。
セットアップ
- エージェント: OpenCode(ローカル実行)
今回は、さくらのAI Engine を OpenCode から使えるようにするため、公式マニュアルの API 設定を参考にしながら、
すべての環境で共通に使えるよう opencode.json を作成しました。
OpenCode の設定ファイルは、グローバル設定の場合は
- macOS / Linux:
~/.config/opencode/opencode.json - Windows ネイティブ:
C:\Users\<ユーザー名>\.config\opencode\opencode.json
に置くのがデフォルトです。
さくらのAI Engine は OpenAI 互換 / Anthropic 互換のエンドポイントを提供しているので、OpenCode 側ではカスタム provider として登録し、利用するモデルや接続先をここでまとめて管理する形にしています。
{
"$schema": "https://opencode.ai/config.json",
"model": "sakura/kimi-k2.7-code",
"provider": {
"sakura": {
"npm": "@ai-sdk/openai-compatible",
"name": "Sakura AI Engine",
"options": {
"baseURL": "https://api.ai.sakura.ad.jp/v1"
}
}
}
}
なお、API Keyについては、
SAKURA_AI_API_KEY=さくらAIのAPIキー
を作成して、
# プロジェクト直下で実行
Get-Content .\.env | ForEach-Object {
$line = $_.Trim()
if ($line -eq "" -or $line.StartsWith("#")) { return }
$i = $line.IndexOf("=")
if ($i -lt 1) { return }
$name = $line.Substring(0, $i).Trim()
$value = $line.Substring($i + 1).Trim().Trim('"').Trim("'")
Set-Item -Path "Env:$name" -Value $value
}
で環境変数を読み込ませるようにしました。
sysdm.cplから、環境変数として設定してもいいと思います。
やったこと
design.spec.yaml 作成
まずは Grokに作ってもらっていたfuture_design.md をもとに、設計書 design.spec.yaml を作成してもらいました。
future_design.mdを読んで、設計書 design.spec.yamlを作成して。
自然言語でできるのがOpenCodeのいいところだと思っています。
コードを直接書かせる前に、先に設計を YAML に落としておくことで、あとから見返しやすく、実装範囲も整理しやすくなりました。
design.spec.yaml 抜粋
# ==============================================================================
# OPEN DESIGN QUESTIONS
# ==============================================================================
# Decisions Sakura must make (NOT answered by carroll-agentty)
open_questions:
architecture:
- id: "Q1_BUDGET_MANAGER"
question: "How to track and enforce the 3000-request limit?"
sub_questions:
- "Per-session? Per-goal? Per-day?"
- "What happens when budget exhausted? (pause/condense/fail)"
owner: "Sakura"
- id: "Q2_CONTEXT_CONDENSER"
question: "How to maximize information density per request?"
options:
- "Summarize previous turns"
- "Prune irrelevant history"
- "Use external memory (vector DB, file cache)"
metrics: "Information preservation vs. token reduction"
owner: "Sakura"
- id: "Q3_GOAL_PERSISTENCE"
question: "How to maintain long-term goals across sessions?"
options:
- "YAML state files"
- "Database"
- "Embedded in prompts"
owner: "Sakura"
- id: "Q4_SKILL_ROUTER"
question: "How to select the right skill at runtime?"
options:
- "Rule-based"
- "LLM-driven"
- "Hardcoded priority queue"
source: "Agents.yaml"
owner: "Sakura"
error_handling:
- id: "Q5_RETRY_STRATEGY"
question: "How many retries? With what backoff?"
sub_questions:
- "Per-request or per-goal?"
- "Exponential backoff vs. fixed delay?"
owner: "Sakura"
- id: "Q6_FAILURE_CLASSIFICATION"
question: "How to distinguish recoverable vs. terminal failures?"
examples:
- condition: "UIA input-not-found"
action: "retry?"
- condition: "Clipboard unchanged"
action: "ignore?"
- condition: "Timeout"
action: "retry or abort?"
owner: "Sakura"
- id: "Q7_STATE_ROLLBACK"
question: "If roundtrip fails mid-goal, can Sakura roll back to known-good state?"
owner: "Sakura"
future_multi_provider:
- id: "Q8_PROVIDER_ABSTRACTION"
question: "How to abstract multiple LLM providers (OpenAI, Anthropic, local)?"
considerations:
- "Keep venice-roundtrip as one provider among many?"
- "Unified request/response schema?"
owner: "Sakura"
- id: "Q9_PARALLEL_EXECUTION"
question: "Can Sakura call multiple providers in parallel for redundancy/speed?"
sub_questions:
- "How to merge/rank results?"
constraint: "Venice is still single-threaded (C3_NO_PARALLEL_PROVIDERS)"
owner: "Sakura"
observability:
- id: "Q10_LOGGING"
question: "What should Sakura log?"
candidates:
- "Request count"
- "Token usage"
- "Goal state transitions"
owner: "Sakura"
- id: "Q11_METRICS"
question: "Should Sakura expose metrics?"
candidates:
- "Success rate"
- "Latency"
- "Budget burn rate"
owner: "Sakura"
- id: "Q12_DEBUGGING"
question: "How to replay/inspect a failed goal execution?"
owner: "Sakura"
実装
まずは id1 だけを実装してもらい、動作を確認しました。問題なさそうだったので、そのまま残りも一気に作ってもらいました。
作業がひと段落した時点で、さくらのAI Engine の利用量は「アクセス 88 回」「入力トークン 5,108,326」になっていて、
ざっくり計算すると 300円弱分くらいのコード生成を、無償枠の中だけで回せていることになります。
え?さくらインターネット、大丈夫なの?
という気持ちになりつつも、「3,000リクエストまで無料(しかも自動課金なし)」という太っ腹仕様にかなり助けられました。
Philosophy 検証
# ==============================================================================
# DESIGN PHILOSOPHY
# ==============================================================================
# Core principles for S.A.K.U.R.A. (from docs/future_design.md)
philosophy:
name: "Persistent Goal Hacker"
core_trait: "Pursue the REAL success condition, not the superficial procedure"
principles:
- name: "Stubbornness"
description: "Never accept 'impossible' without exhausting alternatives"
- name: "Adaptability"
description: "Change tactics when one path fails; do not blindly retry the same approach"
- name: "Resilience"
description: "Recover from partial failures and resume progress"
- name: "Pragmatism"
description: "Choose the shortest path to the real goal, not the 'cleanest' code"
example:
scenario: "Roundtrip times out"
bad_response: "Fail the goal"
good_response: "Retry with shorter prompt OR split task into smaller sub-goals"
無料枠があまり使えきれなかったこともあり、Design Philosophyに準拠しているかの試験を実施してもらいました。
こちらも、試験用のyamlを作成してもらい、そのあと、
yamlを読んで、実行してください
で実行しました。
この検証まで含めたトータルでは、142 リクエスト/入力トークン 11,475,237 で一通りの実装+哲学テストまで完了しました。
無料枠の感触
がっつり長文を入れても、返ってくるコード自体はそこまでトークンを食わないようで、
「入力側が桁違いに大きくても、無料枠の 3,000 リクエストまではかなり余裕がある」という感触でした。
ざっくり計算すると、入力トークンだけでも本来なら数百円〜千円弱くらいのコード生成になるはずですが、
今回はさくらのAI Engine の「月 3,000 リクエスト無料(しかも自動課金なし)」の枠の中だけで全部試せています
OpenCodeの挙動がはやすぎて、Rate Limitで1分待たされることがたまにあるのですが、それでも、全然早いです。
非常にありがたいです。
学び
PowerShellと日本語(UTF-8)まわり
今回いちばんハマったのが、PowerShell での日本語文字化けでした。
結論として、コマンドの標準出力は ちゃんと UTF-8 の日本語を返しているのに、
呼び出し側(PowerShell)のリダイレクトや表示まわりで文字コードがねじれていることが多い、というのが分かりました。
ポイントだけまとめるとこんな感じです。
- コマンドの標準出力は UTF-8 で日本語を返せる
- しかし PowerShell の
>リダイレクトは UTF-16 LE (BOM FF FE) で書き出すことがあり、そのまま読むと文字化けする - エージェントのターミナル表示だけを見ると、
要紁EやE2人中3人のような謎文字列になりがちだが、これは表示側のコードページが原因のことが多い - 生バイトを読んでエンコーディングを確認すると、UTF-8 自体は壊れていないケースが多い
実際の対処としては、PowerShell からいきなり > でリダイレクトせず、
cmd /c "pwsh -NoProfile -File C:\path\to\Invoke-MyCommand.ps1 ... > %TEMP%\out.txt 2> %TEMP%\err.txt"
のように cmd 経由でファイルに書き出し、あとから File.ReadAllBytes で生バイトを読んで UTF-8 として検証する、という手順に落ち着きました。
教訓としては、
- 文字化けを見たら、まずは **「表示/保存側がおかしい」**と疑う
- 日本語を扱うときは絶対パス+生バイト検証をセットで取っておく
- コマンド側の「エンコーディング修正」にいきなり飛びつかず、先に PowerShell のパイプラインを疑う
というあたりです。
UTF-8 と UTF-16 が混ざる環境だと、 「どこで何のエンコーディングになっているか」 をちゃんと押さえておくのが大事だと痛感しました。
まとめ
Claude Code を使っていると、「あれ、あれ?」という間に使用量がどんどん消費されていったり、
従量課金の API をたたくとクレジットが溶けていったり、という経験がありました。
なので、正直なところ今回のさくらのAI Engineも「どれくらいかかるんだろう…?」と思いながら使い始めています。
結果としては、
え?さくらインターネット、こんなに安くて大丈夫なの?
という感想です。
Kimi K2.7 Code は、別のところで使うと本当にクレジットが溶けていくので、
のんびりコーディングな自分としては、今回のような無料枠がある環境で普段使いさせてもらえるととても助かります。
今回の機会を本当にありがとうございました。