記事一覧へ
ある程度の数のツールを持つエージェントを構築したことがあれば、3つの壁にぶつかっているはずです:
- ツールが多すぎることによるコンテキスト汚染
- スコープが重なることによるパフォーマンス低下
- コンテキストがすべてツール指示になってしまうため、メインエージェントが自分の仕事を忘れる
異論があるなら、Slack、Google Drive、Notionに同時アクセスできるエージェントを試してみてください。検索が機能するか教えてください。
これらの問題を解決する新しいプロトコルをテストしていましたが、初期結果が十分良好だったので広く共有します。
## 3つの壁
**コンテキスト汚染。** すべてのツールが貴重なコンテキストを消費します。
スキーマ、説明、使用例、これらすべてがシステムプロンプトに収まります。Slackツールキットは8〜12のツール。Gmailは6〜10。Calendarがさらに6。Drive、GitHub、CRM、ウェブ。カスタムなものを追加する前に50ツールに達します。20を超えると、モデルは存在しないツールをハルシネーションしたり、誤った形でツールを呼び出し始めます。
**スコープが曖昧では合成できない。** 2つのツールが両方ともworkspace引数を取ります:一方はSlackのもの、もう一方はGoogleのもの。あるMCPでのsearchが別のMCPでのsearchと衝突します。send_messageはSlack、メール、またはCRMである可能性があります。
エージェントは半分の確率で間違いを選び、どんな命名規則も修正できません。なぜなら同じ単語が異なるソースで正当に異なる意味を持つからです。自分がコントロールしないソース(MCPサーバー、サードパーティSDK、他のチーム)からツールを合成した瞬間、重複が生じ、モデルには信頼できる曖昧さ解消の方法がなくなります。
**ツール使用ロジックがメインエージェントに集中している。** これが最も深い壁であり、最も多くの問題を引き起こすものです。エージェントがSlackをうまく使うためには、システムプロンプトがSlackを説明しなければなりません:DMを送る前にユーザーIDを調べる、投稿する前にチャンネル名をIDに変換する。これだけでSlack固有のガイダンスが数百トークンになります。Gmailでも同じことをします。Calendarでも。
システムプロンプトはあらゆるAPIの癖の和集合になります。ユーザーがSlackについてだけ聞いた時でも、すべてのターンですべてのルールを運びます。メインエージェントはユーザーの質問と呼び出す可能性のあるすべてのAPIの仕組みを両方推論することを強いられます。ソースを追加するということはプロンプトを編集して他に影響が出ないことを祈ることを意味します。
## 欠けていた層
現在の標準的なエージェントの形は:Agent <- Tools
またはMCPを使って:Agent <- MCP server <- Tools
またはSkillを使って:Agent <- Skill instructions <- Tools
どの場合も、エージェントはすべてのソースの生のツール面を見ます。すべてのSlackツール、すべてのDriveツール、すべてのCRMツール。エージェントのプロンプトにはそれらすべての使い方を含める必要があります。
私がテストしてきた形は、その間に薄い層を置きます:
Agent <-> ContextProvider
各ContextProviderは1つのソースをラップします(例:Slack、FileSystem、Drive)
呼び出し側のエージェントには、ちょうど2つのツールだけを公開します:
- query_<source>(question) — 自然言語での読み取り
- update_<source>(instruction) — 自然言語での書き込み
それだけです。メインエージェントはSlackの12のツールを見ません。query_slackとupdate_slackだけを見ます。Driveの癖を見ません。query_driveを見ます。10のソースを追加しても、エージェントのツール面は最大2Nで線形に保たれます。
各ツールの背後には、そのソース専用にスコープされたサブエージェントがあります。サブエージェントはソースのツール、ソースの癖、書き込み前のルックアップパターン、ページネーションの奇妙さを所有します。独自のコンテキストで実行し、回答を返し、メインエージェントはクリーンな結果を得ます。
```python
from agno.agent import Agent
from agno.context.slack import SlackContextProvider
from agno.context.gdrive import GDriveContextProvider
from agno.context.database import DatabaseContextProvider
slack = SlackContextProvider(id="slack", token=...)
drive = GDriveContextProvider(id="drive", service_account_file=...)
crm = DatabaseContextProvider(id="crm", sql_engine=engine)
agent = Agent(
model=...,
tools=[*slack.get_tools(), *drive.get_tools(), *crm.get_tools()],
)
```
エージェントが見るのは4つのツールです:query_slack、query_drive、query_crm、update_crm。
## Skillについては?
Skillは壁3への素晴らしい試みです。Skillはタスク固有の指示(「Slackの使い方はこうです」)を、システムプロンプトに常時保持するのではなく、必要に応じてモデルがロードするモジュールにパッケージ化します。
Skillはタスクの知識を常時オンのプロンプトから、よりコンディショナルなものへ移動させます。しかしSlackのツールはSkillが呼び出された後もエージェントに降ってきます。2つのSkillをロードすると、searchはまだ衝突します。実際、気づかないうちに競合するSkillがエージェントを台無しにする確率がより高くなります。
ContextProvider + Skillは一緒により上手く機能します。
Slack ContextProviderのサブエージェントはそれ自体がSlack Skillをロードできます。そこでSkillが最もよく機能します。Slackに対して実際に実行しているものの文脈の中で、答えを欲しがっているだけのメインエージェントの中ではなく。
分け方は大まかに言えば:Skillはタスクのやり方を圧縮します。ContextProviderはメインエージェントが委任を決めるまでタスクが存在することを隠します。
## 例
完全なクックブックのセットがcookbook/12_contextにあります。
**すぐに使えるソース。**
Filesystem (00)、database (04)、Slack (05)、Google Drive (07)、GitHub (12)、そしてExaまたはParallel経由のウェブ、直接SDKまたはMCPエンドポイント (01, 02, 03, 11)。すべてのプロバイダーは同じquery_<id> / update_<id>の形式に従います。
**実際のセキュリティを持つ読み取り/書き込み分離。**
04_database_read_write.pyはSQLite DBを起動し、エージェントにコンタクトを挿入させ、読み返させ、直接SQLで検証させます。読み取りと書き込みは別々のエンジンを持つ別々のサブエージェントを通ります。12_github.pyは実際のリポジトリで同じ形を行います:クローン上の読み取り専用サブエージェントで読み、<prefix>/<task>ブランチのセッションごとのworktreeで操作するサブエージェントで書き、PRで終わります。エージェントはmainブランチにpushできませんが、読み取りはできます。
**合成型マルチソース。**
09_web_plus_slack.pyはオーケストレーションコードなしでは平面的なツールレイアウトにできない形です。エージェントはSlackチャンネルからトピックを引き出し、トピックごとのウェブ検索を実行し、各内部スレッドを外部リファレンスに結びつけるブリーフィングを返します。
**MCPラッパー。**
06_mcp_server.pyは任意のMCPサーバー(stdioまたはHTTP)を単一のquery_<id>ツールとしてラップします。サブエージェントの指示は接続時のサーバーのlist_tools()レスポンスから構築されるため、呼び出し側のエージェントは古いツールドキュメントを見ることがありません。これが50ツールのMCPサーバーをメインエージェントの視点から1ツールに圧縮する手法です。
## 驚きとオープンクエスチョン
いくつかの興味深い観察。
**サブエージェントは予想より安価でした。** 追加ホップが支配的になると思っていました。そうなりませんでした。メインエージェントのコンテキストが非常に小さくなるため、呼び出しが速くかつ優れたものになり、サブエージェントはそのソースに触れるターンでのみ起動します。Scoutのワークロードでは、低ソース数で合計トークンはほぼ横ばいで、ソース数が増えるにつれて改善します。実時間レイテンシは私が測定したすべてのソース数で低下しました。
**メインエージェントのプロンプトが大幅に小さくなりました。** 少しのオーケストレーションロジックが必要だと思っていましたが、違いました。統一されたサーフェスで、ルーティングルールは「正しいquery_<source>を選ぶ」に収束します。
gpt-5.4はすぐに使え、ソースの使い方についてゼロのガイダンスを必要としません。これは、私がこのパターンについてなぜこんなに興奮しているかを本当に理解するために実際に見る必要がある種の魔法です。
**合成が機能します。** 2つのプロバイダーが同じターンで互いを読み取ることができます(議論のためにquery_slack、ドキュメントのためにquery_drive)、そしてメインエージェントが統合を書きます。複数のソースはずっとうまく一緒に機能します。
**ソースの追加は1行。** 削除も1行。バックエンドのスワップ(ウェブプロバイダーのExaからParallel)はプロバイダーの内部に留まります。エージェントは気づきません。そしてソースは機能します。エージェントのプロンプトをその使い方で更新する必要がないからです。
まだ取り組んでいることがいくつかあります。
**メインエージェントのプロンプトはどこまで薄くできるか?** evalでこれをヒルクライムして、どこまで押し進められるか見ています。まったく指示を与えない世界はあるでしょうか?
**セッション内の呼び出し間のキャッシュ。** 同じquery_<source>("who's on the X channel")は2ターン後に作業をやり直すべきではありません。
**ホップを越えて持続するユーザー単位の認証。** 部分的に解決済み(ScoutはサブエージェントにuUser_id、session_id、metadata、dependenciesを渡します)。OAuthの形のソースについてはさらに作業が必要です。
**代わりに基礎となるツールを公開するタイミング。** ソースによっては、エージェントが直接ツール呼び出しを駆動することで恩恵を受けることがあります。通常、ソースが小さくてスキーマコストが低く、エージェントの推論が制限要因である場合です。プロトコルにはこのためのモードがあります。境界がどこにあるかはまだ把握中です。
リンク:
Examples
Scout

マルチエージェントContext ProviderMCPエージェント設計harness-design
Context Provider:エージェントとツールの間に欠けていた層
♥ 121↻ 14🔖 461👁 15,700
原文を表示 / Show original
If you've built an agent with a decent number of tools, you've hit the three walls:
Context pollution from too many tools
Degrading performance from overlapping scopes
The main agent forgets its job because its context is all tool instructions
If you disagree, try giving an agent access to Slack, Google Drive, and Notion at the same time. Let me know if search works.
I've been testing a new protocol that fixes these issues, and the early results are good enough to share broadly.
## The three walls
Context pollution. Every tool takes up precious context.
Schemas, descriptions, example usage, all of it lands in the system prompt. A Slack toolkit is 8 to 12 tools. Gmail is 6 to 10. Calendar another 6. Drive, GitHub, your CRM, the web. You're at 50 tools before adding anything custom. Past 20, models start hallucinating tools that don't exist or calling tools with the wrong shape.
Blurry scopes don't compose. Two tools both take a workspace argument: one is Slack's, one is Google's. Search in one MCP collides with search in another. send_message could be Slack, email, or your CRM.
The agent picks wrong half the time, and no naming convention fixes it because the same word legitimately means different things in different sources. The minute you compose tools from sources you don't control (MCP servers, third-party SDKs, other teams), you get overlap, and the model has no reliable way to disambiguate.
Tool-use logic lives with the main agent. This is the deepest wall, and the one that causes the most issues. For an agent to use Slack well, the system prompt has to explain Slack: look up the user ID before you DM them, resolve a channel name to an ID before you post. That's hundreds of tokens of Slack-specific guidance. Now do that for Gmail. For Calendar.
The system prompt becomes the union of every API's quirks. Every turn carries every rule, even when the user just asked about Slack. The main agent is stuck reasoning about both the user's question and the mechanics of every API it might call. Adding a source means editing the prompt and praying nothing else regresses.
## The missing layer
Today the canonical agent shape is: Agent <- Tools
or, with MCP: Agent <- MCP server <- Tools
or, with Skills: Agent <- Skill instructions <- Tools
In all cases the agent sees the raw tool surface of every source. Every Slack tool, every Drive tool, every CRM tool. The agent's prompt has to contain how to use every one of them.
The shape I've been testing puts a thin layer in between:
Agent <-> ContextProvider
Each ContextProvider wraps one source (e.g. Slack, FileSystem, Drive)
To the calling agent, it exposes exactly two tools:
query_<source>(question) for natural-language reads
update_<source>(instruction) for natural-language writes
That's it. The main agent doesn't see Slack's twelve tools. It sees query_slack and update_slack. It doesn't see Drive's quirks. It sees query_drive. Add ten more sources and the agent's tool surface stays linear at max 2N.
Behind each tool is a sub-agent scoped to that one source. The sub-agent owns the source's tools, the source's quirks, the lookup-before-write patterns, the pagination weirdness. It runs in its own context, returns an answer, and the main agent gets a clean result.
```python
from agno.agent import Agent
from agno.context.slack import SlackContextProvider
from agno.context.gdrive import GDriveContextProvider
from agno.context.database import DatabaseContextProvider
slack = SlackContextProvider(id="slack", token=...)
drive = GDriveContextProvider(id="drive", service_account_file=...)
crm = DatabaseContextProvider(id="crm", sql_engine=engine)
agent = Agent(
model=...,
tools=[*slack.get_tools(), *drive.get_tools(), *crm.get_tools()],
)
```
The agent sees four tools: query_slack, query_drive, query_crm, update_crm.
## What about Skills?
Skills are a great attempt at wall 3. A skill packages task-specific instructions ("here's how to use Slack") into a module that the model loads when relevant, instead of carrying it in the system prompt full-time.
Skills move task knowledge out of the always-on prompt and into something more conditional. But the Slack tools will still land on the agent after the skill is invoked. Load 2 skills and search will still collide. In fact there's a higher chance of conflicting skills messing up your agent without you even realizing.
ContextProvider + skills work better together.
A Slack ContextProvider's sub-agent can itself load a Slack skill, and that's where the skill does its best work, in the context of the thing actually executing against Slack, not in the main agent that just wanted an answer.
The split is roughly: skills compress how to do a task. ContextProvider hides that there's a task until the main agent decides to delegate one.
## Examples
A full set of cookbooks live in cookbook/12_context.
Sources covered out of the box.
Filesystem (00), database (04), Slack (05), Google Drive (07), GitHub (12), and web via Exa or Parallel, direct SDK or MCP endpoint (01, 02, 03, 11). Every provider follows the same query_<id> / update_<id> shape.
Read/write split with real security.
04_database_read_write.py spins up a SQLite DB and has the agent insert a contact, read it back, and verify with direct SQL. Read and write go through separate sub-agents with separate engines. 12_github.py does the same shape over a real repo: reads through a read-only sub-agent on a clone, writes through a sub-agent that operates on a per-session worktree on a <prefix>/<task> branch and ends in a PR. The agent cannot push to the main branch, but can read from it.
Compositional multi-source.
09_web_plus_slack.py is the shape flat tool layouts can't do without orchestration code. The agent pulls topics from a Slack channel, runs a per-topic web search, and returns a briefing tying each internal thread to an external reference.
MCP wrapper.
06_mcp_server.py wraps any MCP server (stdio or HTTP) as a single query_<id> tool. The sub-agent's instructions are built from the server's list_tools() response at connect time, so the calling agent never sees stale tool docs. This is the move that collapses a 50-tool MCP server to 1 tool from the main agent's view.
## Surprises and open questions
A few interesting observations.
Sub-agents are cheaper than expected. I assumed the extra hops would dominate. They don't. The main agent's context is so much smaller that its calls are faster and better, and the sub-agent only fires on turns that touch its source. On Scout's workload, total tokens are roughly flat at low source counts and improve as the source count grows. Wall-clock latency drops at every source count I've measured.
The main agent's prompt got significantly smaller. I expected to need a bit of orchestration logic, but nope. With a uniform surface, the routing rules collapse to "pick the right query_<source>".
gpt-5.4 just works out of the box, and needs zero guidance on how to use a source. This is the kind of magic you need to see to really understand why i'm so excited about this pattern.
Composition just works. Two providers can read each other in the same turn (query_slack for the discussion, query_drive for the doc) and the main agent writes the synthesis. Multiple sources work much better together.
Adding a source is one line. Removing one is one line. Swapping a backend (Exa to Parallel for the web provider) stays inside the provider. The agent doesn't notice. And sources just work, because I don't need to update the agent's prompt on how to use them.
A few things I'm still working through.
How thin can the main agent's prompt get? I've been hill-climbing this with evals and seeing how far I can push it. Is there a world where I give zero instructions at all?
Caching across calls in a session. The same query_<source>("who's on the X channel") shouldn't re-do the work two turns later.
Per-user authentication that survives the hop. Partially solved (Scout passes user_id, session_id, metadata, and dependencies through to the sub-agent). More to do for OAuth-shaped sources.
When to expose underlying tools instead. Some sources benefit from the agent driving the tool calls directly, usually when the source is small enough that the schema cost is low and the agent's reasoning is the limiting factor. The protocol has a mode for this. I'm still figuring out where the line sits.
Links:
Examples
Scout