Skip to content

Conversation

@eavanvalkenburg
Copy link
Member

@eavanvalkenburg eavanvalkenburg commented Feb 2, 2026

Summary

This ADR proposes unifying \ContextProvider, \ChatMessageStore, and \AgentThread\ into a single \ContextMiddleware\ concept for the Python SDK.

Problem

Currently, developers doing 'Context Engineering' must understand multiple abstractions:

  • \ContextProvider\ - Injects instructions, messages, and tools
  • \ChatMessageStore\ - Stores and retrieves conversation history
  • \AgentThread\ - Coordinates between them

Proposed Solution

A unified \ContextMiddleware\ using the onion/wrapper pattern (like existing \AgentMiddleware):

\\python
class RAGMiddleware(ContextMiddleware):
async def process(self, context: SessionContext, next) -> None:
# Pre-processing: add context
docs = await self.retrieve_documents(context.input_messages[-1].text)
context.add_messages(self.source_id, [ChatMessage.system(f'Context: {docs}')])

    await next(context)

    # Post-processing: store/audit
    await self.store_interaction(context.input_messages, context.response_messages)

agent = ChatAgent(
chat_client=client,
context_middleware=[
InMemoryStorageMiddleware('memory'),
RAGMiddleware('rag'),
]
)
\\

Key Decisions

Decision Rationale
Onion/wrapper pattern Familiar from existing middleware, natural pre/post processing
Agent owns config, Session owns pipeline Enables per-session factories
Mandatory \source_id\ Attribution in \context_messages\ dict
Default \InMemoryStorageMiddleware\ Zero-config conversation history
Single \StorageContextMiddleware\ class Configure for memory/audit/evaluation
Clean break (no shims) We're in preview

Migration Impact

Current New
\ContextProvider\ \ContextMiddleware\
\ChatMessageStore\ \StorageContextMiddleware\
\AgentThread\ \AgentSession\

See the full ADR in \docs/decisions/00XX-python-context-middleware.md\ for detailed design, code examples, and implementation workplan.

Related Issues

This ADR addresses the following issues:

Copilot AI review requested due to automatic review settings February 2, 2026 09:24
@markwallace-microsoft markwallace-microsoft added the documentation Improvements or additions to documentation label Feb 2, 2026
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new ADR proposing a unified ContextMiddleware abstraction for Python to replace ContextProvider, ChatMessageStore, and AgentThread, using an onion/wrapper middleware pipeline pattern.

Changes:

  • Introduces a proposed ContextMiddleware + SessionContext + pipeline design for composable context engineering.
  • Describes a StorageContextMiddleware approach for loading/storing conversation history and optional auditing.
  • Outlines migration impact and a phased implementation/testing plan.

@eavanvalkenburg eavanvalkenburg force-pushed the adr-python-context-middleware branch from e005fc3 to 761f2bf Compare February 4, 2026 11:12
@github-actions github-actions bot changed the title ADR: Unifying Context Management with ContextMiddleware (Python) Python: ADR: Unifying Context Management with ContextMiddleware (Python) Feb 4, 2026
@eavanvalkenburg eavanvalkenburg force-pushed the adr-python-context-middleware branch from ae59218 to fed0e4a Compare February 5, 2026 13:34
…dback

- Add Option 3: ContextHooks with before_run/after_run pattern
- Add detailed pros/cons for both wrapper and hooks approaches
- Add Open Discussion section on context compaction strategies
- Clarify response_messages is read-only (use AgentMiddleware for modifications)
- Add SimpleRAG examples showing input-only filtering
- Clarify default storage only added when NO middleware configured
- Add RAGWithBuffer examples for self-managed history
- Rename hook methods to before_run/after_run
- Add class hierarchy clarification for both options
- Merge detailed design sections (side-by-side comparison)
- Move detailed design before decision outcome
- Move compaction discussion after decision
- Add .NET implementation comparison (feature equivalence)
- Update .NET method names to match actual implementation
- Rename hook methods to before_run/after_run
- Fix storage context table for injected context
- Note that class and method names are open for discussion
- Add alternative method naming options table
- Include invoking/invoked as option matching current Python and .NET
…filtering

- Remove smart mode for load_messages (now explicit bool, default True)
- Add attribution marker in additional_properties for message filtering
- Update validation to warn on multiple or zero storage loaders
- Add note about ChatReducer naming from .NET
- Note that attribution should not be propagated to storage
- Option A: Instances in Session (current proposal)
- Option B: Instances in Agent, State in Session
  - B1: Simple dict state with optional return
  - B2: SessionState object with mutable wrapper
- Updated examples to use Hooks pattern (before_run/after_run)
- Added open discussion on hook factories in Option B model
Decision outcomes:
- Option 3 (Hooks pattern) with ContextPlugin class name
- Methods: before_run/after_run
- Option B1: Instances in Agent, State in Session (simple dict)
- Whole state dict passed to plugins (mutable, no return needed)
- Added trust note: plugins reason over messages, so they're trusted by default

Status changed from proposed to accepted.
Signature now: before_run(agent, session, context, state)
Simpler design: agent stores Sequence[ContextPlugin] and calls
before_run/after_run directly in the run method.
@eavanvalkenburg eavanvalkenburg force-pushed the adr-python-context-middleware branch from 2bc78ee to 1372be3 Compare February 9, 2026 10:26
…ports

- Add to_dict()/from_dict() on AgentSession with 'type' discriminator
- Present serialization as Option A (direct) vs Option B (through agent)
- Rewrite ownership section as 2x2 matrix (orthogonal decision)
- Move Instance Ownership Options before Decision Outcome
- Fix get_session to use service_session_id, split from create_session
- Add decorator-based provider convenience API (@before_run/@after_run)
- Add _ prefix naming strategy for all PR1 types (core + external)
- Constructor compatibility table for existing providers
- Add load_messages=False skip logic to all agent run loops
- Clarify abstract vs non-abstract in execution pattern samples
- Update auto-provision: trigger on conversation_id or store=True
- Document root package exports (ContextProvider, HistoryProvider, etc.)
- Rename section heading to 'Key Design Considerations'
self.session_id = session_id

@abstractmethod
async def process(self, context: SessionContext, next: ContextMiddlewareNext) -> None:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
async def process(self, context: SessionContext, next: ContextMiddlewareNext) -> None:
async def process(self, context: SessionContext, call_next: ContextMiddlewareNext) -> None:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation python

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants