From atomic-agents
Guides creation of a BaseDynamicContextProvider that injects dynamic blocks (time, user, RAG docs, session state) into an agent's system prompt on every run().
How this skill is triggered — by the user, by Claude, or both
Slash command
/atomic-agents:create-atomic-context-providerThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
A context provider injects a named, titled block into the agent's system prompt at every `run()`. The base prompt stays static; the context is what changes between calls.
A context provider injects a named, titled block into the agent's system prompt at every run(). The base prompt stays static; the context is what changes between calls.
For deep material (caching strategies, async data sources, multi-agent sharing patterns), the authority is ../framework/references/context-providers.md. This skill is the action-oriented path: clarify → write → register.
framework skillframework skill: questions about Atomic Agents in general, or about something other than authoring a provider.Bundle into one message:
run() (default), every N seconds (cache), or refreshed externally before each call (async data).Skip what's already obvious from context.
Confirm in one short block:
<project>/context_providers.py (or alongside the agent that owns it).<Topic>Ctx(BaseDynamicContextProvider).run() calls (most common), set via a method, or await refresh() for async sources.from atomic_agents.context import BaseDynamicContextProvider
class UserCtx(BaseDynamicContextProvider):
def __init__(self):
super().__init__(title="User Context")
self.name: str = ""
self.role: str = ""
def get_info(self) -> str:
if not self.name:
return "No user is logged in."
return f"User: {self.name} (role: {self.role})"
get_info() is synchronous and runs on every agent.run() — keep it cheap. No HTTP, no DB queries, no file I/O. Cache slow sources (see "Cached" pattern below). For async data sources, await provider.refresh() from your loop before calling the agent.
Time — read-only, no state mutation needed:
from datetime import datetime, timezone
class TimeCtx(BaseDynamicContextProvider):
def __init__(self):
super().__init__(title="Current Time")
def get_info(self) -> str:
return datetime.now(timezone.utc).isoformat()
RAG / retrieved docs — set externally, read inside get_info():
class RAGCtx(BaseDynamicContextProvider):
def __init__(self):
super().__init__(title="Retrieved Documents")
self.docs: list[dict] = []
def set(self, docs: list[dict]) -> None:
self.docs = docs
def get_info(self) -> str:
if not self.docs:
return "No relevant documents retrieved."
return "\n\n".join(f"[{d['source']}] {d['content']}" for d in self.docs)
# In the calling code, just before agent.run():
rag.set(vector_db.search(query, k=4))
agent.run(query_input)
Session — mutable key/value state shared across agents:
class SessionCtx(BaseDynamicContextProvider):
def __init__(self):
super().__init__(title="Session")
self._data: dict[str, str] = {}
def set(self, key: str, value: str) -> None:
self._data[key] = value
def get_info(self) -> str:
if not self._data:
return "No session state."
return "\n".join(f"- {k}: {v}" for k, v in self._data.items())
Cached — for slow sources (DB schema, expensive computation):
import time
class DBSchemaCtx(BaseDynamicContextProvider):
def __init__(self, conn, ttl_seconds: int = 300):
super().__init__(title="Database Schema")
self._conn = conn
self._ttl = ttl_seconds
self._cached: str = ""
self._at: float = 0.0
def get_info(self) -> str:
now = time.time()
if not self._cached or now - self._at > self._ttl:
self._cached = render_schema(self._conn)
self._at = now
return self._cached
Async source — refresh outside, read sync inside:
class AsyncCtx(BaseDynamicContextProvider):
def __init__(self):
super().__init__(title="Async Data")
self._cached = ""
async def refresh(self) -> None:
self._cached = format(await fetch_remote())
def get_info(self) -> str:
return self._cached
# Caller
await ctx.refresh()
await agent.run_async(input_data)
ctx = UserCtx()
agent.register_context_provider("user", ctx)
# Mutate before each run as needed:
ctx.name = "Alice"; ctx.role = "admin"
agent.run(...)
Sharing one provider instance across agents is allowed — updates propagate to every agent that registered it:
shared = SessionCtx()
agent_a.register_context_provider("session", shared)
agent_b.register_context_provider("session", shared)
shared.set("locale", "en-GB") # visible to both agents
Inspect or unregister:
"user" in agent.context_providers
agent.unregister_context_provider("user")
Quick smoke test that the provider renders:
uv run python -c "from <project>.context_providers import UserCtx; c = UserCtx(); c.name='Alice'; c.role='admin'; print(c.get_info())"
Then confirm the rendered system prompt includes the provider's section by inspecting agent.system_prompt_generator.generate_prompt(...) or by running the agent and checking the first request's payload via the completion:kwargs hook (see ../framework/references/hooks.md).
Tell the user:
register_context_provider, and how to mutate it.create-atomic-agent skill.atomic-examples/deep-research/ and ../framework/references/orchestration.md.get_info() — runs on every agent.run(). Cache it or refresh externally.get_info() — raises at prompt time.register_context_provider(...) — the provider never reaches the prompt.For deeper material — multi-agent sharing patterns, dynamic-update research loops, async source patterns — load ../framework/references/context-providers.md.
npx claudepluginhub joshuarweaver/cascade-ai-ml-agents-agent-framework --plugin brainblend-ai-atomic-agentsExplains the components of LLM context (system prompts, tool definitions, message history) and principles like attention budgets and progressive disclosure. Useful when designing agent systems, debugging unexpected behavior, or reducing token costs.
Manages context, memory, and conversation state in Claude AI agents using Anthropic Agent SDK. Covers CLAUDE.md files, system prompts, and multi-turn chats.