ADR 0015: Context-Var Session Injection for Python SDK
- Date: 2026-09-21
- Status: Proposed
- Deciders: UndoLog Core Team
Context
The Python SDK requires _session=session as a keyword argument in every
decorated tool call. This creates two problems:
-
Signature pollution. The
_sessionparameter leaks UndoLog internals into the tool function signature. Frameworks that inspect function signatures (LangGraph, CrewAI, Semantic Kernel) may reject or mis-handle the extra parameter. -
Developer friction. Every decorated call must include
_session=session. Forgetting it raisesRuntimeErrorat call time, but the error message does not suggest the context-var alternative. This creates a constant source of friction when writing tool functions.
The TypeScript SDK solved this with AsyncLocalStorage. Python has the
equivalent primitive: contextvars.ContextVar.
Current state:
_sessionis a required keyword argument inundolog_tool- No context-variable mechanism exists in the SDK
- The session is manually threaded through every tool call
- Forgetting
_sessionraisesRuntimeErrorat call time
Decision
Use contextvars.ContextVar to propagate UndoLogSession through the
call stack. The _session parameter becomes optional: if provided
explicitly it takes precedence, otherwise the decorator falls back to
the context variable.
The public API is:
UndoLogContextclass holding aContextVar[UndoLogSession]run_with_session(session)async context manager that sets the varget_current_session()returns the current session orNonerequire_current_session()returns the session or raisesRuntimeError
Alternatives Considered
Alternative 1: ContextVar with Explicit Fallback (Chosen)
- Pros: Zero boilerplate for the common case. Backward compatible
with existing
_session=sessionusage. Explicit parameter takes precedence over context var, allowing targeted overrides. Standard library (contextvars), no dependencies. - Cons: Two code paths in the decorator (context var vs explicit parameter). Slightly more complex than a single required parameter.
- Chosen? Yes. Eliminates the developer-friction failure mode while remaining backward compatible.
Alternative 2: Thread-Local Storage
- Pros: Familiar pattern from WSGI frameworks.
- Cons: Not compatible with
asyncio. Thread locals are per-thread, not per-task. Multiple concurrent agent runs in the same thread would share state, causing race conditions. - Chosen? No. Incompatible with async Python.
Alternative 3: Function Attribute Injection
- Pros: Simple to implement. No new abstractions.
- Cons: Pollutes the function object. Not inherited by nested calls. Frameworks that inspect function attributes may be confused. Does not solve the developer-friction problem if a call bypasses the decorator.
- Chosen? No. Does not solve the core problem.
Alternative 4: Keep Required _session Parameter
- Pros: Simplest implementation. No new concepts.
- Cons: Signature pollution. Developer friction. Framework incompatibility. This is the status quo and the problem being solved.
- Chosen? No. This is the problem statement.
Consequences
Positive:
- Tool function signatures are clean: no
_sessionparameter required - Developer friction eliminated: no need to thread
_sessionthrough calls - Framework auto-instrumentation becomes possible (PY-3)
- Single-line integration:
async with run_with_session(session): - Backward compatible: existing
_session=sessionstill works
Negative:
- Two resolution paths in the decorator (context var vs explicit)
- Migration required for existing code (though not urgent)
contextvarsis Python 3.7+ only (not a constraint: SDK requires 3.10+)
Risks:
- Frameworks that create tasks with a custom empty
contextmay lose the context var. Mitigation: document thatrun_with_sessionmust wrap the entire agent loop.asyncio.create_task()copies the current context by default. - Testing requires care:
contextvarsare task-local, not thread-local. Tests must useasyncio.runorpytest-asyncioto isolate context.
References
- Python
contextvarsdocumentation: https://docs.python.org/3/library/contextvars.html - TypeScript SDK session implementation:
sdks/undolog-ts/src/session.ts - Plan item: PY-1 in
plan/python-sdk.md - Analysis: section 3.9 (blocker #9), section 5.2, section 6.3, section 9.2