4. Context
Explicitly define processes that reduce application & user state
An eight-factor AI application has explicit processes for transforming application and user state into model-digestible context. A context processor defines how raw application state is filtered, formatted, and bundled for model consumption. Context processing should be isolated, reproducible, and version-controlled.
Context in an eight-factor app has clear boundaries:
- If context processing is scattered through application code, it’s not an eight-factor app – it creates inconsistency and makes behavior unpredictable
- If context is passed without explicit processing, it risks overwhelming models or leaking sensitive information
A context processor handles several key responsibilities:
- Selecting relevant information from application state
- Formatting data for model consumption
- Enforcing context length limits
- Protecting sensitive information
- Maintaining consistency across interactions
Bad practice - implicit context handling:
def handle_user_request(user, request):
# Context mixed with business logic
prompt = f"""User {user.name} (member since {user.joined_date})
with preferences {user.preferences}
and history {user.past_interactions}
asks: {request}"""
return get_model_response(prompt)
Good practice - explicit context processing:
class UserContextProcessor:
def __init__(self, max_history_items=5):
self.max_history_items = max_history_items
def process(self, user, request) -> Context:
return Context({
"user": self.get_user_context(user),
"request": request,
"history": self.get_relevant_history(user)
})
def get_user_context(self, user):
return {
"name": user.name,
"preferences": self.filter_preferences(user.preferences),
"expertise_level": self.determine_expertise(user)
}
def get_relevant_history(self, user):
return user.get_recent_interactions(
limit=self.max_history_items,
relevant_to=request
)
# Usage
context_processor = UserContextProcessor()
context = context_processor.process(user, request)
response = agent.process(context)
Context processors should handle various types of context:
class ContextRegistry:
def __init__(self):
self.processors = {}
def register(self, name, processor):
self.processors[name] = processor
def process(self, context_type, data):
if context_type not in self.processors:
raise UnknownContextTypeError(context_type)
return self.processors[context_type].process(data)
# Different context types
registry.register("user", UserContextProcessor())
registry.register("document", DocumentContextProcessor())
registry.register("conversation", ConversationContextProcessor())
This approach provides several benefits:
- Consistent context processing across the application
- Clear boundaries for what information models can access
- Easier testing and validation of context processing
- Better privacy and security controls
- Reproducible model inputs
Context should be treated as a first-class concept:
- Context processors should be version controlled
- Context transformations should be logged
- Context size should be monitored
- Privacy rules should be enforced
- Context relevance should be measured
Some applications may need dynamic context selection:
class DynamicContextProcessor:
def process(self, data, request_type):
relevant_contexts = self.determine_relevant_contexts(request_type)
return Context.merge([
self.process_context(context_type, data)
for context_type in relevant_contexts
])
This pattern enables:
- Controlled information flow to models
- Consistent handling of application state
- Privacy by design
- Reproducible model behavior
- Efficient context management
- Clear boundaries for model knowledge
Context processors should be optimized for:
- Relevance - including only necessary information
- Efficiency - minimizing context size
- Privacy - protecting sensitive data
- Consistency - maintaining predictable format
- Traceability - logging context transformations