BoxAgnts' middle layer — the Agent Toolbox — は、システムの頭脳と手です。これは、3つのことを担う6つのコアモジュールで構成されています:あなたの意図を理解し、適切なツールをディスパッチし、実行結果をフィードバックすること。この記事では、各モジュールのアーキテクチャ設計と主要な実装について深掘りします。
Architecture Overview: A Seven-Module Collaboration Network
Dashboardで「このRustプロジェクトのコード構造を分析して」と入力して送信すると、何が起きるでしょうか?
User Message
│
▼
┌─────────────────────────────────────────────────────────────┐
│ boxagnts-api Unified API Abstraction Layer │
│ LlmProvider trait → 20+ Providers → Message Normalization │
├─────────────────────────────────────────────────────────────┤
│ boxagnts-query Agent Query Loop │
│ run_query_loop() → Multi-turn Conversation → Tool Dispatch → Auto Recovery │
├─────────────────────────────────────────────────────────────┤
│ boxagnts-tools + tools-manager + wasm-tools │
│ Tool trait → Built-in Tools + WASM Tools → Execution │
├─────────────────────────────────────────────────────────────┤
│ boxagnts-gateway Gateway & Scheduling │
│ Cron Scheduler + Site Hosting │
├─────────────────────────────────────────────────────────────┤
│ boxagnts-workspace Memory & Configuration │
│ SQLite + JSON Config + Conversation History │
└─────────────────────────────────────────────────────────────┘
それぞれを分解して見ていきましょう。
boxagnts-api: Unified Multi-Model Abstraction Layer
これは、ミドルレイヤーと外部AIの世界の間にあるインターフェース層です。AIツール開発で最もつらい問題を解決します:すべてのモデル提供元のAPIは異なるが、あなたのコードはその代償を払うべきではない。
LlmProvider Trait: 多相性の基盤
すべてのプロバイダーアダプタが実装しなければならないコアとなるインターフェース:
#[async_trait]
pub trait LlmProvider: Send + Sync {
fn id(&self) -> &ProviderId; // 一意の識別子「anthropic」「openai」
fn name(&self) -> &str; // 人が読める名前
// 非ストリーミングリクエスト
async fn create_message(&self, request: ProviderRequest)
-> Result<ProviderResponse, ProviderError>;
// ストリーミングリクエスト(Pin<Box<dyn Stream>>を返す)
async fn create_message_stream(&self, request: ProviderRequest)
-> Result<Pin<Box<dyn Stream<Item = Result<StreamEvent, ProviderError>> + Send>>, ProviderError>;
// 利用可能なモデルを列挙
async fn list_models(&self) -> Result<Vec<ModelInfo>, ProviderError>;
}
このトレイト設計には、エレガントな側面が3つあります:
-
Async trait:
async_traitマクロを使用し、Tokioのasyncランタイムと互換 - Pin>の返却:動的ディスパッチを使って、異なるプロバイダーのストリーム型の違いを抽象化
-
統一されたエラー型付け:すべてのプロバイダーのエラーを
ProviderErrorに正規化
Unified Access for 20+ Providers
BoxAgnts は非常に幅広いモデル提供元をサポートしています:
| カテゴリ | プロバイダー | 独立した実装ファイル |
|---|---|---|
| 国際的なメインストリーム | OpenAI, Anthropic, Google, Azure, Bedrock | 個別ファイル |
| オープンソース互換 | Deepseek, Mistral, Groq, TogetherAI, Fireworks | openai_compat.rs |
| エンタープライズ向けサービス | Copilot, CodeX, Cohere, Perplexity | 個別ファイル |
| 国内プラットフォーム | MiniMax, Alibaba Cloud (Qwen), Zhipu, Moonshot, SiliconFlow | 個別ファイル |
| その他 | Venus, Nebius, Novita, OVHCloud | 個別ファイル |
重要なデザインパターン — Provider + Transformer デュアルレイヤーアーキテクチャ:
Raw User Message
│
▼
┌────────────────┐
│ Transformer │ ← 内部メッセージ形式をプロバイダー固有の形式へ変換
│ (per-provider)│
└───────┬────────┘
▼
┌────────────────┐
│ Provider │ ← 認証、HTTPリクエスト、ストリーム解析を担当
│ (per-provider)│
└───────┬────────┘
▼
AI Response
│
▼
┌────────────────┐
│ Transformer │ ← プロバイダー応答を内部の統一フォーマットへ変換
└────────────────┘
ProviderRegistry: Runtime Model Switching
QueryConfig には provider_registry フィールドがあり、実行時に動的にプロバイダーを選択できます。つまり、次のことが可能になります:
- Agent configでタスクごとに異なるモデルを設定する(要約には安価なモデル、推論には強力なモデル)
fallback_modelを使用して、プライマリモデルが過負荷になったときにバックアップモデルへ自動的に切り替えるModelRegistryを通じて、複数モデルのAPIキーとエンドポイントを管理する
API Key Management: セキュリティと利便性のバランス
BoxAgntsは各プロバイダごとに環境変数のマッピングを事前定義しています:
pub fn api_key_env_vars_for_provider(provider_id: &str) -> &'static [&'static str] {
match provider_id {
"anthropic" => &["ANTHROPIC_API_KEY"],
"openai" => &["OPENAI_API_KEY"],
"deepseek" => &["DEEPSEEK_API_KEY"],
"zhipu" => &["ZHIPU_API_KEY"],
"minimax" => &["MINIMAX_API_KEY"],
// ... 30+ providers
}
}
これは、APIキーを3つの方法(環境変数、設定ファイル、ダッシュボードUI)で注入できることを意味します。セキュリティ境界を維持しつつ、柔軟性を最大化します。
boxagnts-query: エージェントの中核エンジン
この層はBoxAgntsの絶対的な魂です。run_query_loop()関数は、完全なエージェント推論ループを実装しており、コードは約300行ですが、驚くほど多くのエッジケースを処理します。
メインループの骨組み
loop {
turn += 1;
// 0. キャンセルシグナルを確認
if cancel_token.is_cancelled() { return Cancelled; }
// 1. 最大ターン数の制限を確認
if turn > effective_max_turns { return EndTurn; }
// 2. 保留中のユーザーメッセージを注入(マルチモーダル・インタラクション)
if let Some(queue) = pending_messages.as_deref_mut() {
for text in queue.drain(..) { /* ユーザーメッセージとして追加 */ }
}
// 3. 自動コンテキスト圧縮
compact_state.maybe_compact(messages, config);
// 4. APIリクエストを構築
let request = build_request(messages, tools, config);
// 5. AIモデルへ送信(ストリーミング対応)
let response = client.create_message_stream(request).await;
// 6. 返信からContentBlocksを解析
for block in response.content {
match block {
ContentBlock::Text { text } => { /* テキストの応答を蓄積 */ }
ContentBlock::ToolUse { name, input, .. } => {
// ツールを照合して実行
let tool = find_tool(name);
let result = tool.execute(input, tool_ctx).await;
messages.push(tool_result); // 結果を会話に注入
}
ContentBlock::Thinking { thinking, .. } => {
// 深い思考コンテンツを処理(ユーザーには表示しない)
}
}
}
返却形式: {"translated": "翻訳されたHTML"}// 7. モデルが終了 → 最終メッセージを返す
if stop_reason == "end_turn" { return EndTurn; }
}
主要メカニズムの分析
トークン枯渇からのリカバリ
モデルが 1 回の応答でトークン割り当てを使い切った場合、クエリループは単に途中までの結果を返すだけではありません。代わりに、自動的に慎重に設計されたリカバリ用メッセージを送信します:
"出力トークンの上限に到達しました。直接再開してください — 謝罪も、あなたが行っていたことの要約も不要です。切り取られたところが思考の途中だった場合は、その途中から引き継いでください。残りの作業をより小さな部分に分割してください。"
このメッセージは設計が非常に抑制的です:「謝罪も、要約もせず、切り取られたところから引き継ぎ、タスクを分解する」— 最小限のトークンで最大限の指示を伝えています。無限ループを避けるため、最大 3 回までリトライします(MAX_TOKENS_RECOVERY_LIMIT = 3)。
自動コンテキストのコンパクション
compact.rs は、インテリジェントな圧縮戦略を実装しています。会話履歴がモデルのコンテキストウィンドウ上限に近づくと、初期のメッセージを要約します。ここでは、重要な情報(ファイルパス、エラーメッセージ、重要な意思決定など)を保持しつつ、冗長な中間手順は破棄します。この戦略により、非常に複雑なマルチターンのタスク(たとえばコードベース全体のリファクタリング)であっても、コンテキストオーバーフローによってエージェントが「記憶を失う」ことを防げます。
フォールバックモデルの仕組み
// query.rs — 過負荷エラー時にバックアップモデルへ自動切り替え
if is_overloaded_error(&err) && fallback_model.is_some() && !used_fallback {
effective_model = fallback_model;
used_fallback = true;
continue; // バックアップモデルで再試行
}
主要モデル(例:Claude Sonnet)が高負荷の期間に過負荷エラーを返した場合、システムは自動的にバックアップモデル(例:Deepseek)へ切り替えます。これにより、タスクが中断されないようにします。この仕組みはユーザーに対して完全に透明です。
予算管理
pub enum QueryOutcome {
BudgetExceeded { cost_usd: f64, limit_usd: f64},
// ...
}
各ターンの後、クエリループは累積コストが予算上限を超えていないかを確認します。すべての API 呼び出しは、モデルとトークンの消費を記録する CostTracker を通じて追跡され、コストを制御可能にします。予算超過が発生した場合は、意図せずに使い過ぎるのではなく、明確なエラーメッセージを返します。
マルチモーダルのコンテンツブロック
ContentBlock の列挙型は 14 種類のコンテンツタイプを定義しており、プレーンテキストから深い思考まで、あらゆる種類のやり取りを網羅します:
pub enum ContentBlock {
Text { text: String }, // プレーンテキスト
Image { source: ImageSource }, // 画像
ToolUse { id, name, input }, // ツール呼び出し
ToolResult { tool_use_id, content, is_error }, // ツール結果
Thinking { thinking, signature }, // 深い思考
Document { source, title, context }, // ドキュメント参照
UserLocalCommandOutput { command, output }, // シェルコマンドの出力
UserCommand { name, args }, // ユーザーコマンド
UserMemoryInput { key, value }, // ユーザーの記憶
SystemAPIError { message, retry_secs }, // APIエラー
CollapsedReadSearch { tool_name, paths }, // 折りたたまれた検索結果
TaskAssignment { id, subject, description }, // 下位タスクの割り当て
// ...
}
このきめ細かなコンテンツの型付けにより、フロントエンドは各タイプを専用の扱いでレンダリングできます。たとえば、エラーブロックは赤い枠になり、タスク割り当てブロックはシアン(青緑)の枠になります。折りたたまれた検索結果は、1 行の要約として表示されます。
マネージド・エージェント・モード(Manager-Executor)
これは BoxAgnts における最も印象的なミドルレイヤー設計の 1 つです。managed_orchestrator.rs は階層型のエージェントアーキテクチャを実装しています:
ユーザー
│
▼
┌───────────────────────┐
│ マネージャー・エージェント │ ← 強力なモデルを使用(例: Claude Opus)
│ タスクを分析 → 分解 → 割り当て │
└───────┬───────────────┘
│
┌────────┼────────┐
▼ ▼ ▼
┌────────┐┌────────┐┌────────┐
│Executor││Executor││Executor│ ← 経済的なモデルを使用(例: Claude Sonnet/Deepseek)
│Subtask1││Subtask2││Subtask3│
└────┬───┘└────┬───┘└────┬───┘
│ │ │
└────────┼─────────┘
▼
マネージャーが結果を集約
│
▼
最終出力
重要な設定
pub struct ManagedAgentConfig {
pub enabled: bool,
pub manager_model: String, // マネージャーのモデル(例: "claude-opus-4-6")
pub executor_model: String, // エグゼキューターのモデル(例: "claude-sonnet-4-6")
pub executor_max_turns: u32, // エグゼキューターごとの最大ターン数
pub max_concurrent_executors: u32, // 最大同時エグゼキューター数
pub total_budget_usd: Option<f64>, // 総予算の上限
pub executor_isolation: bool, // Git worktree を分離するかどうか
}
システムプロンプト・インジェクション
マネージャー・エージェントのシステムプロンプトは、その役割を正確に定義しています:
あなたはマネージャーであり、計画と推論のレイヤーです。
あなたは作業を調整しますが、ファイル/bash ツールを直接使ってタスクは実行しません。
すべての実装作業は、エグゼキューター・エージェントに委任されます(Agent ツール経由)。
各エグゼキューターは {executor_model} を使用し、最大 {max_turns} ターンまでです。
最大 {max_concurrent} 個のエグゼキューターを並列で実行できます。
エグゼキューターのプロンプトには「完全な自己完結性」が必要です――エグゼキューターはマネージャーの会話履歴を見ることができず、そのプロンプト内に必要なすべての文脈を含める必要があります。これにより文脈の漏えいを防ぎ、トークン消費を抑えられます。
boxagnts-tools + tools-manager: 一体化されたツール抽象化
ツール特性: アーキテクチャの要となるもの
これは、BoxAgnts のすべてにおいて最も重要なインターフェース定義です。新しいツールはすべてこの特性を実装するだけで済みます:
#[async_trait]
pub trait Tool: Send + Sync {
fn name(&self) -> &'static str;
fn description(&self) -> &'static str;
fn input_schema(&self) -> Value; // パラメータを定義する JSON スキーマ
async fn execute(&self, input: Value, ctx: &ToolContext) -> ToolResult;
}
ToolContext: ツールの実行環境
pub struct ToolContext {
pub cost_tracker: Arc<CostTracker>, // コスト追跡
pub session_id: Option<String>, // セッション ID
pub current_turn: Arc<AtomicUsize>, // 現在のターン
pub non_interactive: bool, // ノンインタラクティブ・モード
pub config: Config, // グローバル設定
pub managed_agent_config: Option<ManagedAgentConfig>,
pub allowed_outbound_hosts: Vec<String>, // アウトバウンド接続のホワイトリスト
pub block_url: Option<String>, // ブロックされた URL
}
ToolContext はツールの「パスポート」であり、権限、セッション、コスト、ネットワーキングなど、さまざまなコンテキスト情報を保持します。すべてのツールは、実行中に必要なシステム状態へこの経由でアクセスできます。
Central Tool Registry
// tools-manager/src/lib.rs
pub fn all_tools() -> Vec<Box<dyn Tool>> {
vec![
// Rust ネイティブツール
Box::new(AskUserQuestionTool),
Box::new(BriefTool),
Box::new(EnterPlanModeTool),
Box::new(ExitPlanModeTool),
Box::new(SleepTool),
Box::new(SkillTool),
Box::new(ToolSearchTool),
// WASM サンドボックスツール — 同じインターフェース、別実装
Box::new(WasmTool::new("read", "file-read-component.wasm", ...)),
Box::new(WasmTool::new("write", "file-write-component.wasm", ...)),
Box::new(WasmTool::new("edit", "file-edit-component.wasm", ...)),
Box::new(WasmTool::new("glob", "file-glob-component.wasm", ...)),
Box::new(WasmTool::new("bash", "bash-component.wasm", ...)),
Box::new(WasmTool::new("web_fetch", "web-fetch-component.wasm", ...)),
Box::new(WasmTool::new("js_exec", "boxedjs-execute-component.wasm", ...)),
]
}
Rust ネイティブツールと WASM ツールが同じ Vec<Box<dyn Tool>> に配置されていることに注目してください。AI モデルから見れば、両者は完全に同等です。これはインターフェース指向プログラミングの強みです。
boxagnts-gateway: Extending Time and Space Dimensions
Cron Scheduled Task Engine
cron/scheduler.rs は tokio_cron_scheduler に基づいて、完全なスケジュールタスクシステムを構築します。:
// コアとなるスケジューリングロジック
let cron_job = Job::new_async(cron_expr, move |_uuid, _lock| {
Box::pin(async move {
let handle = job::execute(prompt, model).await;
// タイムアウト付きでの実行 + 結果のログ出力
let result = timeout(Duration::from_secs(timeout), fut).await;
append_execution_log(job_id, job_name, success, message).await;
})
});
主な機能:
-
タイムアウト保護: 各タスクには独立したタイムアウト設定(デフォルト 180 秒)があります。
tokio::time::timeoutでラップされます -
キャンセル伝播: タイムアウト時に、
CancellationTokenにより実行中の Agent クエリをキャンセルします
返却形式: {"translated": "翻訳されたHTML"} - Execution logs: 各実行は時間、成功/失敗ステータス、結果の要約を記録します
- Dynamic management: タスクはいつでも追加・削除でき、有効化/無効化も可能です
Site Hosting System
site/store.rs で管理されるサイトデータは SQLite に永続化され、CRUD 操作をサポートします。フロントエンドの SitesPage と組み合わせることで、ユーザーは次のことができます:
- ダッシュボードでサイトを作成する(名前とパスを入力)
- AI エージェントにウェブコンテンツの生成を任せる
/sites/{name}/のパス経由でアクセスする
boxagnts-workspace: エージェントのメモリシステム
workspace モジュールは、すべての永続化と設定管理を処理します:
| 機能 | ストレージ | キー実装 |
|---|---|---|
| 会話履歴 | SQLite(rusqlite) | セッション単位で整理され、CRUD をサポート |
| ユーザー認証 | パスワードハッシュの保存 | リモートアクセス用に検証済み |
| グローバル設定 | JSON ファイル |
Settings::load() で読み込み |
| API キー | 環境変数 / JSON | 3 段階の優先順位: ENV > 設定 > デフォルト |
| AGENTS.md | ファイルシステム | 各会話のシステムプロンプトに注入 |
| Cron タスク | SQLite | 永続化ストレージ+起動時に読み込み |
| サイト設定 | SQLite | 永続化ストレージ+起動時に読み込み |
設計の要点: 設定と状態を分離しています。設定は JSON ファイル(人が読めて編集可能)で、状態は SQLite(効率的なクエリとトランザクション)です。この区別により、「設定ファイルの肥大化」というよくある落とし穴を回避できます。
QueryConfig: 完全な次元を持つクエリ制御
QueryConfig は、20 個のフィールドを持つ巨大な設定構造体で、エージェントのクエリのあらゆる次元をカバーします:
pub struct QueryConfig {
pub model: String, // モデル名
pub max_tokens: u32, // 最大出力トークン数
pub max_turns: u32, // 最大推論ターン数
pub system_prompt: Option<String>, // システムプロンプト
pub thinking_budget: Option<u32>, // 思考予算(深い推論)
pub temperature: Option<f32>, // 温度パラメータ
pub tool_result_budget: usize, // ツール結果の総文字数上限(50000)
pub effort_level: Option<EffortLevel>, // 努力量(thinking_budget に影響)
pub max_budget_usd: Option<f64>, // USD の予算上限
pub fallback_model: Option<String>, // バックアップモデル
pub agent_definition: Option<AgentDefinition>, // エージェント定義
pub managed_agents: Option<ManagedAgentConfig>, // 管理モード
pub output_style: OutputStyle, // 出力スタイル
// ... and more
}
この構造体は BoxAgnts の中核となる設計思想を示しています: ユーザーに制御を与えるが、妥当なデフォルトも提供する。各フィールドは上書き可能ですが、必須のものはありません — デフォルトが 90% の利用ケースをカバーします。
Summary
ミドルレイヤーの Agent Toolbox は BoxAgnts の機能の中核です:
| モジュール | 責務 | キーとなるハイライト |
|---|---|---|
| boxagnts-api | マルチモデルの統一アクセス | LlmProvider トレイト、20 以上の Provider、Transformer 変換 |
| boxagnts-query | エージェントの推論ループ | トークン回復、コンテキスト圧縮、フォールバック切り替え、予算制御 |
| managed_orchestrator | 管理されたエージェントのアーキテクチャ | Manager-Executor のレイヤリング、並列実行、予算管理 |
| boxagnts-tools | 統一ツール抽象化 | Tool トレイト、ToolContext |
| tools-manager | 中央集約型のツールレジストリ | Rust ネイティブ + WASM を Vec> として統一 |
| boxagnts-gateway | 時間と空間の拡張 | Cron スケジューラ、サイトホスティング |
| boxagnts-workspace | メモリシステム | SQLite + JSON のデュアルレイヤーストレージ |
関連リソース
- Boxagnts: https://github.com/guyoung/boxagnts




