Intermediate12 min
Runtime & Context Injection
LangChain's Runtime object is a dependency injection system for tools and middleware. Instead of reaching for globals or thread-locals, you pass per-invocation config (user ID, tenant, feature flags) through context_schema and read it anywhere via runtime.context — without exposing it to the model.
Quick Reference
- →context_schema=MyContext on create_agent — registers the injectable context shape
- →agent.invoke(..., context=MyContext(user_id='u1')) — pass context at call time, per invocation
- →runtime.context — read injected config in tools (ToolRuntime[T]) and middleware (Runtime[T])
- →runtime.store — BaseStore for cross-session persistence; same object in tools and middleware
- →runtime.stream_writer — emit custom events to the 'custom' stream from inside a tool
- →runtime.context is None by default since langgraph >0.6.1 — always guard before accessing
- →wrap_model_call / @dynamic_prompt: access via request.runtime.context, not as a direct arg
When NOT to Use Context Injection
Context injection adds a dataclass, a schema registration, and a new parameter to every tool that needs it. Before reaching for it, check whether a simpler option covers your need:
choosing wrong causes silent bugs — context mutations are silently ignored
| If you need… | Use instead |
|---|---|
| Data that changes during a run (e.g., accumulated results) | AgentState — it's mutable within a run |
| Data that persists across sessions (e.g., user preferences) | runtime.store (BaseStore) — not context |
| A fixed system prompt for all users | system_prompt= on create_agent — no injection needed |
| A config that's the same for every request | A module-level constant or env var — no injection needed |
| Per-user config that changes per request | context_schema + context= — this is the right tool |
The rule of thumb
If the value is the same for every invocation, it's not context — it's configuration. If it changes per user or per request, it's context.