MINARA

Agent ループ

ユーザーメッセージから最終回答までの 1 ターンの流れと、それを支える記録管理の仕組み

Agent ループはターンのオーケストレーターです。担う責務は 1 つ、ユーザーメッセージを受け取り、最終回答まで実行し、途中で起きたすべてを永続化することです。Minara のあらゆる機能、価格の確認、取引、ディープリサーチレポートは、すべてこのループを通ります。実装は apps/agent/src/agent/runtime.tsAgentRuntime クラスに集約されています。

なぜ Vercel AI SDK を使い、独自ループを書かないのか? ターンは短く境界が明確なシーケンスです。コンテキストを構築し、モデルを呼び出し、要求されたツールを実行し、結果をフィードバックして、停止するまで繰り返す。AI SDK の streamText はそのマルチステップツールループをそのまま表現しており、Anthropic、OpenAI、xAI、OpenRouter を単一のプロバイダー非依存インターフェースで扱えます。Minara が担う本質的なポリシー(許可するツール、発動するゲート、キャッシュ対象、コンテキストの圧縮方法)は streamText が呼び出すモジュール側に置き、ツール呼び出しと観測の機械的なイテレーションは SDK に委ねています。

agent loop diagram

実際の動作を確認するには: REPLHTTP ゲートウェイ のすべてのチャットターンがこのループを駆動します。初めての取引 のウォークスルーは、このループを端から端まで 1 ターン通した例です。

ターンパイプライン

AgentRuntime.run() はターン単位のパイプラインです。以下のフェーズを順番に実行し、両方のサーフェス(ゲートウェイ SSE と REPL)が消費する単一の AsyncGenerator<AgentEvent> を出力します。コールバックが散在するのではなく、イベントストリームが 1 本に統一されています。

1. ターン前処理

モデルを呼び出す前に、ランタイムは嗜好進化の昇格と入力スキャンを実行し、buildSystemPromptBlocks() でシステムプロンプトを組み立てます。プロンプトは文字列の連結ではなく宣言されたブロックから構築されるため、キャッシュ可能なプレフィックス(アイデンティティとスキルカタログ)がターンをまたいでバイト単位で同一に保たれ、Anthropic のプロンプトキャッシュが有効に機能します。動的な末尾部分(アクティブなスキル、シグナルコンテキスト、保留中の確認)はターンごとに再構築されます。ブロックの順序は実質的な制約です。キャッシュ済みプレフィックスを変化させる変更は、ウォームな呼び出しをキャッシュミスに変えてしまいます。ワークスペースのマークダウン(SOUL.mdMEMORY.mdHEARTBEAT.md)は動的ブロックの先頭に置かれるため、オペレーターが整備したグラウンドトゥルースが推論された層より優先されます(ワークスペース を参照)。

アクティブなスキルを決定するのはまずルーターです。キーワード、ライフサイクルステージ、アセットクラス、共起アクティベーションの各軸でスキルをスコアリングし、アクティブセットがモデルに見せるツールセットを確定します。

2. 圧縮ラダー

会話が長い場合、ランタイムはモデル呼び出しのコストを使う前にウィンドウに収まるよう、圧縮ラダー(境界スライス、コラプス、オートコンパクト、上位 N 件の再注入)を実行します。圧縮は、有償の要約より前に LLM を使わない刈り込みを優先します。完全な仕様は docs-src/context-management.md にあります。

3. streamText

ランタイムはアクティブなスキルが許可するツールと、そのターンで許可されたツールセットの積集合を渡して streamText を呼び出します。SDK が内部でツール呼び出しと観測のループを駆動し、stopWhen: stepCountIs(maxSteps) で停止します。各ステップの動作は次のとおりです。

  • prepareStep が呼び出し前に累積 token バジェットを調整します。
  • モデルがテキストとツール呼び出しを出力し、ツール呼び出しはレジストリ(次節)を通じてディスパッチされます。
  • onStepFinish が使用量を記録し、ステップイベントをストリームに出力します。

streamText 呼び出し全体は、ステップがコンテキストウィンドウを超えた場合に刈り込んでリトライするオーバーフロー回復ガードで保護されています。

4. ツールディスパッチとゲーティング

モデルが出力した各ツール呼び出しはツールレジストリを通じてディスパッチされます。ゲートは 2 つあります。

  • パーミッションティア。 すべてのツールは PermissionTierREAD_ONLYCONFIRM_ONCEALWAYS_CONFIRMMANUAL_ONLY)を持ちます。ティアは強制適用であり、任意適用ではありません。
  • セーフティスタック。 資金移動ハンドラーは共有のプレビュー確認ヘルパーでゲートされ、取引パスはさらにフックパイプライン(token セーフティ、エクスポージャー、スリッページ、リスクマネージャーの上限、監査ログ)を通過します。ブロックされた呼び出しはモデルが通常のツール結果として受け取る構造化エラーを返し、監査ログには個別のエントリとして記録されます。6 段階のスタック全体の仕様は セーフティ & サンドボックス に記載されています。

ターン状態(ソース、使用済みツールの集合、リスク上限)は ToolCallContext に乗って伝達されるため、サブ Agent 呼び出しやスキル実行シーケンスを含むすべてのツール呼び出しが同じターン単位の状態を参照します。

5. ターン終了処理

streamText が停止すると、ランタイムは post_turn イベントを出力し、意思決定の記録、チャットターンの保存、ワークスペース状態の更新、ダッシュボードキャッシュの無効化、オブザーバビリティへの書き込みをファンアウトします。このレコードがオブザーバビリティツールの読み取り元となり、学習システムのリフレクション対象にもなります。

ターンの記録管理

ループを安全に拡張するための不変条件が 2 つあります。

  • コンテキストはターンごとに 1 つ。 リスク上限、シグナルコンテキスト、使用済みツールの集合は ToolCallContext に保持され、モジュールレベルの状態には置きません。並行するターン(REPL とゲートウェイは同時に実行できます)がお互いの記録管理に干渉することはありません。
  • キャッシュ済みプレフィックスは不変。 ターン中のスキルアクティベーションはシステムプロンプトを再構築するのではなくノートを追記します。プレフィックスを再構築するとプロンプトキャッシュが壊れるためです。ローディングや圧縮への変更は、キャッシュ済みプレフィックスへの接触を避けなければなりません。

ターン外での確定的実行

すべてのツール呼び出しが streamText を経由するわけではありません。スケジュール実行やイベント駆動のワークフローは、モデルのラウンドトリップなしに、レジストリを通じて登録済みのツールハンドラーを直接呼び出します。tool_call ワークフローステップは、ループが実行するのと同じハンドラーを、同じパーミッションティアとセーフティゲートのもとで実行しますが、実行内容を決定するためのモデル呼び出しは行いません。

これには 2 つの帰結があります。

  • 定型的な自動化(アラート、ブリーフィング、モニタリング)はコストを抑えられます。エンジンは明示的にモデルノードを持つステップにのみ token を使います。
  • 実行のセマンティクスは、モデルがスキーマを読んだ解釈ではなく、ツールの実装に従います。

ループとワークフローエンジンは 1 つのツールレジストリへの 2 つの入り口です。そのため、資金移動ステップは実行場所を問わず二段階確認を通過します。

関連ドキュメント

  • スキルシステム:ループが使用するルーティング、アクティベーション、ツールのホワイトリスト。
  • LLM 統合streamText の背後にあるプロバイダー抽象化とモデルルーティング。
  • セーフティ & サンドボックス:ディスパッチステップが適用するパーミッションティアと資金安全スタック。
  • ワークスペース:動的プロンプトブロックの先頭に置かれるグラウンドトゥルースのマークダウン。

目次