Skip to main content
An agent is only as good as the context it has access to. You can write the perfect system prompt, but without the right context, the agent will not deliver good results. AgentOS gives you three primitives for getting context to the agent:
PrimitiveShapeWhen it loads
KnowledgeIndexed content (RAG)Auto-searched before each run, or via tool
DependenciesA dict you pass inInjected into the prompt and available to tools
Context providersLive external systemsA clean tool surface backed by source-scoped sub-agents
Knowledge is durable and pre-loaded. Dependencies are ephemeral and request-scoped. Context providers are dynamic and authenticated, and they’re the primary architectural move for any agent that touches more than one external system. Most production agents use some combination of the three.

Knowledge

Knowledge is what the agent looks up: documentation, policies, code conventions, table schemas. It lives in your database with embeddings and surfaces via similarity search.
from agno.knowledge import Knowledge
from agno.vector_db.pgvector import PgVector

agent = Agent(
    model=...,
    db=db,
    knowledge=Knowledge(
        vector_db=PgVector(table_name="my_kb", db_url="postgresql://..."),
    ),
    search_knowledge=True,              # recommended: expose as a callable tool
    add_knowledge_to_context=True,      # traditional RAG: auto-search before each run
)

agent.knowledge.add_content_from_path("docs/")
agent.knowledge.add_content_from_url("https://example.com/policies")
FlagBehavior
search_knowledge=TrueThe agent gets a search_knowledge_base(query) tool. Known as Agentic RAG.
add_knowledge_to_context=TrueAgentOS runs knowledge.search(message) before each turn and injects the top-k chunks into the prompt.
Knowledge supports hybrid search, reranking, and chunking.

Dependencies

Sometimes an agent needs runtime values that aren’t in knowledge: a feature flag, a tenant ID, a per-request DB connection, an API key scoped to the caller.
from agno.agent import Agent
from agno.run import RunContext
from agno.tools import tool

@tool
def get_config(run_context: RunContext, key: str) -> str:
    return str(run_context.dependencies["config"].get(key, "not set"))

agent = Agent(
    model=...,
    dependencies={
        "config": {"region": "us-east-1", "max_retries": 3},
        "feature_flags": {"beta_search": True},
        "tenant_id": "acme-corp",
    },
    add_dependencies_to_context=True,
    tools=[get_config],
)
add_dependencies_to_context=True injects them into the system prompt. Tools that take a RunContext parameter get access via run_context.dependencies. Per-request dependencies override the agent-level ones for a single run:
agent.run(
    "What region am I in?",
    dependencies={"region": "eu-west-1"},   # request-scoped
    user_id="user-123",
)
For a worked example, see the Injector agent.

Context providers

If you’ve built an agent with a real number of tools, you’ve hit three walls:
  1. Context pollution. Every tool description, schema, and example lands in the system prompt. Slack alone is 8 to 12 tools. Add Drive, GitHub, your CRM, and you’re at 50 tools before adding anything custom. Past 20, models start hallucinating tools, calling them with the wrong shape, or skipping the right one.
  2. Scope collisions. search in one toolkit 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.
  3. System-prompt sprawl. Using Slack well requires Slack-specific guidance: look up user IDs before DMing, resolve channel names, prefer conversations.history for channels and conversations.replies for threads. Multiply by every API. The system prompt becomes the union of every source’s quirks.
A ContextProvider is a thin layer between the agent and the tools that fixes all three:
AgentContextProviderTools
To the calling agent, each provider exposes exactly two tools: query_<id> for natural-language reads, and update_<id> for writes (or a clean read-only error). Behind the 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 calling agent gets a clean result.
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")
drive = GDriveContextProvider(id="drive")
crm = DatabaseContextProvider(id="crm", sql_engine=engine, readonly_engine=ro_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. Add ten more sources and the surface stays linear at 2N. The agent’s prompt doesn’t grow with the number of integrations.

Built-in providers

ProviderSourceReadWrite
FilesystemContextProviderA scoped local directoryquery_<id>
WebContextProviderWeb search and fetch (Exa or Parallel; SDK or MCP)query_<id>
DatabaseContextProviderAny SQL database via SQLAlchemyquery_<id>update_<id>
SlackContextProviderA Slack workspacequery_<id>update_<id>
GDriveContextProviderGoogle Drive via service account; all-drives awarequery_<id>
GitHubContextProviderA cloned GitHub repoquery_<id>update_<id> (PR-scoped)
MCPContextProviderAny MCP server (stdio, SSE, streamable-HTTP)query_<id>

Web has multiple backends

WebContextProvider takes a backend, so you can swap providers without touching the agent:
BackendWhatWhen
ExaBackendExa direct SDK. web_search + web_extract.You have an EXA_API_KEY and want full extraction payloads.
ExaMCPBackendExa public MCP server. Keyless, rate-limited.First experiment with no signup. Keyed for higher throughput.
ParallelBackendParallel direct SDK. web_search + web_extract.You prefer Parallel’s ranking and excerpt shape. Needs PARALLEL_API_KEY.
ParallelMCPBackendParallel public MCP. web_search + web_fetch (compressed markdown).Token-efficient output. Keyless for trial; keyed for higher limits.
from agno.context.web import WebContextProvider, ExaBackend

web = WebContextProvider(backend=ExaBackend(), id="web")

Read/write separation

Writable providers (Database, Slack, GitHub) run two sub-agents under the hood with minimum privilege per role:
  • Database: read sub-agent uses the readonly engine; write sub-agent uses the writable engine. The read path can’t mutate even if the model tries.
  • Slack: read sub-agent gets search_workspace, get_channel_history, get_thread, lookup tools. Write sub-agent gets send_message and the lookup tools it needs to resolve names. The reader never sees send_message. The writer never sees search.
  • GitHub: read sub-agent operates on a clone with read-only Workspace + git tools. Write sub-agent operates on a per-session worktree on a <prefix>/<task> branch and ends in a PR. The agent cannot push to the default branch.
These are infrastructure-level guarantees, not prompt instructions. They hold even if the model goes off-script.

Lifecycle

Some providers hold async resources: MCP sessions, watched inboxes, cloned repos. They implement asetup() and aclose(). Bracket their use so the resource is owned by a single task:
await provider.asetup()
try:
    # provider.get_tools() is now ready
    ...
finally:
    await provider.aclose()
In an AgentOS app, register providers with the runtime and they’re set up and torn down on the FastAPI lifespan automatically. Manual bracketing is for scripts and tests. Providers without async resources (FilesystemContextProvider, DatabaseContextProvider with sync engines, the SDK-based web backends) work without asetup / aclose.

Multi-provider in one agent

Three providers on one agent compose cleanly because each provider has its own namespace:
fs = FilesystemContextProvider(id="cookbooks", root="./docs")
web = WebContextProvider(backend=ExaMCPBackend(), id="web")
db = DatabaseContextProvider(id="releases", sql_engine=engine, readonly_engine=engine)

agent = Agent(
    model=...,
    tools=[*fs.get_tools(), *web.get_tools(), *db.get_tools()],
    instructions="\n".join([fs.instructions(), web.instructions(), db.instructions()]),
)
The agent sees query_cookbooks, query_web, query_releases and picks the right one per question. The compositional payoff lands when one provider’s output feeds another’s query: pull a topic from Slack, run a web search on it, synthesize the briefing in one turn.

Custom providers

When the built-ins don’t fit, subclass ContextProvider. The base class handles tool wrapping, name derivation, and error shaping. You implement aquery and astatus:
from agno.context import Answer, ContextProvider, Status

class FAQContextProvider(ContextProvider):
    async def astatus(self) -> Status:
        return Status(ok=True, detail=f"{len(FAQ)} entries")

    async def aquery(self, question: str) -> Answer:
        key = next((k for k in FAQ if k in question.lower()), None)
        return Answer(text=FAQ[key] if key else "No FAQ entry matches that.")

faq = FAQContextProvider(id="faq")
agent = Agent(model=..., tools=faq.get_tools())
The agent now has a query_faq tool. Same shape as every built-in provider.

Configurable sub-agent model

Every provider runs its own sub-agent on a configurable model. Default to a cheap one for the source-specific work and let the calling agent use a stronger model for synthesis:
from agno.models.openai import OpenAIResponses

slack = SlackContextProvider(id="slack", model=OpenAIResponses(id="gpt-5.4-mini"))

agent = Agent(model=OpenAIResponses(id="gpt-5.4"), tools=slack.get_tools())
The sub-agent does the tool work; the calling agent does the reasoning. On most workloads this is cheaper and faster than putting every source’s tools on one big agent.

Worked examples

The full cookbook set is in cookbook/12_context. Notable examples:
CookbookWhat it shows
00_filesystem.pyBrowse local files via FilesystemContextProvider
04_database_read_write.pyEnd-to-end SQLite round trip: write through update_<id>, read through query_<id>, verify via direct SQL
08_multi_provider.pyThree providers on one agent, no name collisions
09_web_plus_slack.pyCompositional: Slack topics feed per-topic web searches, agent synthesizes
10_custom_provider.pySubclass ContextProvider for your own source
12_github.pyGitHub: read public repo + edit-via-PR with branch-prefix safety
The Scout tutorial is a full agent built on context providers from the ground up.

Next

Human Approval →