Session 2: Managing State in Python Applications
Synopsis
Covers how to represent and store session state, task progress, memory summaries, and interaction logs. Learners develop the application engineering mindset needed to make agentic systems reproducible and debuggable.
Session Content
Session 2: Managing State in Python Applications
Session Overview
Duration: ~45 minutes
Audience: Python developers with basic programming knowledge, beginning their GenAI and agentic development journey
Learning Goals
By the end of this session, learners will be able to:
- Explain what "state" means in Python applications
- Distinguish between stateless and stateful program design
- Manage state using variables, functions, classes, and dictionaries
- Persist state across interactions in simple GenAI applications
- Build a small Python chatbot loop that maintains conversational context
- Use the OpenAI Responses API with
gpt-5.4-miniin a state-aware application
Agenda
- Introduction to State in Applications
- Stateless vs Stateful Design
- Common Patterns for Managing State in Python
- State in GenAI Applications
- Hands-on Exercise 1: Tracking State in Plain Python
- Hands-on Exercise 2: Building a Stateful Chat Loop with OpenAI
- Wrap-up and Key Takeaways
1. Introduction to State in Applications
What is State?
State is the data an application remembers at a given moment.
Examples:
- A shopping cart's contents
- A logged-in user's name
- A chatbot's prior conversation
- A counter's current value
- A workflow's current step
In Python, state often lives in:
- Variables
- Lists and dictionaries
- Class instances
- Files or databases
- In-memory caches
Why State Matters
Without state, many useful applications would not work well:
- A chatbot would forget what you just said
- A game would reset every move
- A form would forget entered data
- An agent would lose track of task progress
For GenAI systems, state is especially important because it helps maintain:
- Conversation context
- User preferences
- Tool outputs
- Intermediate reasoning results
- Execution history
2. Stateless vs Stateful Design
Stateless Programs
A stateless program does not remember prior interactions unless all required input is passed in each time.
Example
def greet(name: str) -> str:
return f"Hello, {name}!"
This function does not remember anything from previous calls.
Stateful Programs
A stateful program stores information that affects future behavior.
Example
class Greeter:
def __init__(self):
self.count = 0
def greet(self, name: str) -> str:
self.count += 1
return f"Greeting #{self.count}: Hello, {name}!"
This class remembers how many times it has greeted someone.
Comparing the Two
| Aspect | Stateless | Stateful |
|---|---|---|
| Memory of prior actions | No | Yes |
| Simplicity | Usually simpler | Often more complex |
| Reproducibility | Easier | Can be harder |
| Use cases | Pure functions, utilities | Chats, workflows, sessions |
Design Insight
Stateless systems are often easier to test and scale.
Stateful systems are often more natural for real-world interactive applications.
A good engineer knows when to use each.
3. Common Patterns for Managing State in Python
3.1 State with Variables
The simplest form of state is a variable whose value changes over time.
counter = 0
counter += 1
counter += 1
print(counter)
Example output:
2
This works for small scripts, but can become hard to manage in larger applications.
3.2 State with Dictionaries
Dictionaries are great for storing flexible application state.
app_state = {
"user_name": "Ava",
"logged_in": True,
"messages_sent": 3,
}
print(app_state["user_name"])
print(app_state["messages_sent"])
Example output:
Ava
3
Why dictionaries are useful
- Easy to inspect
- Flexible structure
- Good for JSON serialization
- Common in API-driven applications
3.3 State with Functions and Parameters
A cleaner alternative to global state is passing state into functions and returning updated state.
def increment_counter(state: dict) -> dict:
new_state = state.copy()
new_state["counter"] += 1
return new_state
state = {"counter": 0}
state = increment_counter(state)
state = increment_counter(state)
print(state)
Example output:
{'counter': 2}
This pattern is helpful because it reduces hidden side effects.
3.4 State with Classes
Classes allow related data and behavior to live together.
class SessionState:
def __init__(self, user_name: str):
self.user_name = user_name
self.message_count = 0
def record_message(self) -> None:
self.message_count += 1
state = SessionState("Ava")
state.record_message()
state.record_message()
print(state.user_name)
print(state.message_count)
Example output:
Ava
2
Why classes help
- Group related logic
- Support encapsulation
- Useful for long-running sessions or agents
3.5 Persisting State to a File
In many applications, state must survive program restarts.
import json
state = {
"user_name": "Ava",
"message_count": 5
}
with open("state.json", "w", encoding="utf-8") as f:
json.dump(state, f, indent=2)
with open("state.json", "r", encoding="utf-8") as f:
loaded_state = json.load(f)
print(loaded_state)
Example output:
{'user_name': 'Ava', 'message_count': 5}
Common persistence options
- JSON files
- SQLite
- Redis
- PostgreSQL
- Cloud storage
For learning purposes, JSON is a great starting point.
4. State in GenAI Applications
State is one of the core building blocks of GenAI applications.
Examples of GenAI State
- Prior user messages
- Prior assistant responses
- System instructions
- Retrieved documents
- Tool outputs
- User preferences
- Temporary workflow memory
Why Chat Applications Need State
If the model only sees the latest user message, it loses context.
Without conversation state
User: "My name is Maya."
User: "What is my name?"
If only the second message is sent, the model cannot know the answer.
With conversation state
The app stores earlier messages and includes them in future requests.
Important Design Principle
The model itself does not automatically "remember" your local application's conversation history unless you provide or reference that state in your API usage.
Your Python application is responsible for managing it.
5. Hands-on Exercise 1: Tracking State in Plain Python
Objective
Build a small session tracker that stores:
- User name
- Number of actions
- Recent events
Concepts Practiced
- Dictionaries
- Updating state
- Keeping recent history
- Displaying current state
Starter Code
def create_state(user_name: str) -> dict:
"""
Create a new application state dictionary.
Args:
user_name: The name of the current user.
Returns:
A dictionary representing session state.
"""
return {
"user_name": user_name,
"action_count": 0,
"recent_events": []
}
def record_event(state: dict, event: str, max_events: int = 5) -> dict:
"""
Return an updated copy of state with a new event recorded.
Args:
state: Existing application state.
event: Description of the event to record.
max_events: Maximum number of recent events to keep.
Returns:
A new state dictionary.
"""
new_state = state.copy()
new_state["action_count"] += 1
# Copy the list so we do not mutate the original state in place.
events = list(new_state["recent_events"])
events.append(event)
# Keep only the most recent N events.
new_state["recent_events"] = events[-max_events:]
return new_state
def print_state(state: dict) -> None:
"""
Print the current session state in a readable format.
"""
print("\n--- CURRENT STATE ---")
print(f"User: {state['user_name']}")
print(f"Action count: {state['action_count']}")
print("Recent events:")
for item in state["recent_events"]:
print(f" - {item}")
def main() -> None:
"""
Demonstrate simple state management.
"""
state = create_state("Maya")
state = record_event(state, "Opened application")
state = record_event(state, "Viewed dashboard")
state = record_event(state, "Clicked help")
print_state(state)
if __name__ == "__main__":
main()
Example Output
--- CURRENT STATE ---
User: Maya
Action count: 3
Recent events:
- Opened application
- Viewed dashboard
- Clicked help
Exercise Tasks
- Add a
"theme"field to the state - Add a function to change the theme
- Add a timestamp string to each event
- Limit recent events to the last 3 items
- Print the state after each update
Challenge Extension
Modify the event list so each event is stored as a dictionary like:
{"timestamp": "2026-03-22 10:00:00", "event": "Opened application"}
6. Hands-on Exercise 2: Building a Stateful Chat Loop with OpenAI
Objective
Create a simple command-line chatbot that remembers previous messages using Python-managed state and the OpenAI Responses API.
What You Will Build
A Python program that:
- Stores conversation history in memory
- Sends user input to
gpt-5.4-mini - Includes prior messages in future turns
- Exits when the user types
quit
Prerequisites
Install the OpenAI Python SDK:
pip install openai
Set your API key:
export OPENAI_API_KEY="your_api_key_here"
On Windows PowerShell:
$env:OPENAI_API_KEY="your_api_key_here"
6.1 Core Idea
We will maintain conversation state in a Python list.
Each new user turn is appended to the list, then sent to the Responses API.
6.2 Full Example: In-Memory Stateful Chat
from openai import OpenAI
# Create a client using the API key from the OPENAI_API_KEY environment variable.
client = OpenAI()
def create_chat_state(system_prompt: str) -> dict:
"""
Create initial chatbot state.
Args:
system_prompt: High-level instruction for the assistant.
Returns:
A dictionary containing the conversation state.
"""
return {
"system_prompt": system_prompt,
"history": []
}
def add_user_message(state: dict, message: str) -> None:
"""
Append a user message to conversation history.
Args:
state: The chatbot state dictionary.
message: The user's text input.
"""
state["history"].append({
"role": "user",
"content": message
})
def add_assistant_message(state: dict, message: str) -> None:
"""
Append an assistant message to conversation history.
Args:
state: The chatbot state dictionary.
message: The assistant's response text.
"""
state["history"].append({
"role": "assistant",
"content": message
})
def build_input_messages(state: dict) -> list:
"""
Convert internal state into the input format for the Responses API.
Args:
state: The chatbot state dictionary.
Returns:
A list of message objects.
"""
messages = [
{
"role": "system",
"content": state["system_prompt"]
}
]
for item in state["history"]:
messages.append({
"role": item["role"],
"content": item["content"]
})
return messages
def get_model_response(state: dict) -> str:
"""
Send the current conversation to the model and return the assistant's reply.
Args:
state: The chatbot state dictionary.
Returns:
The assistant response text.
"""
response = client.responses.create(
model="gpt-5.4-mini",
input=build_input_messages(state)
)
return response.output_text
def main() -> None:
"""
Run a simple stateful command-line chat application.
"""
state = create_chat_state(
system_prompt=(
"You are a helpful Python tutor. "
"Answer clearly and briefly. "
"Remember details from the conversation."
)
)
print("Stateful Chatbot")
print("Type 'quit' to exit.\n")
while True:
user_input = input("You: ").strip()
if user_input.lower() == "quit":
print("Goodbye!")
break
if not user_input:
print("Please enter a message.")
continue
add_user_message(state, user_input)
assistant_reply = get_model_response(state)
add_assistant_message(state, assistant_reply)
print(f"Assistant: {assistant_reply}\n")
if __name__ == "__main__":
main()
Example Usage
Stateful Chatbot
Type 'quit' to exit.
You: My name is Maya.
Assistant: Nice to meet you, Maya! How can I help you with Python today?
You: What is my name?
Assistant: Your name is Maya.
You: quit
Goodbye!
6.3 Why This Works
The program keeps a history list in Python memory.
Each request includes:
- The system prompt
- All prior user messages
- All prior assistant messages
That means the model receives enough context to answer follow-up questions.
6.4 Improvement: Save and Load Chat State from JSON
This version persists conversation state so you can restart the app without losing history.
import json
from pathlib import Path
from openai import OpenAI
client = OpenAI()
STATE_FILE = Path("chat_state.json")
def create_chat_state(system_prompt: str) -> dict:
"""
Create a new chat state object.
"""
return {
"system_prompt": system_prompt,
"history": []
}
def load_state() -> dict:
"""
Load state from disk if available; otherwise create a fresh state.
Returns:
A chat state dictionary.
"""
if STATE_FILE.exists():
with STATE_FILE.open("r", encoding="utf-8") as f:
return json.load(f)
return create_chat_state(
system_prompt=(
"You are a helpful Python tutor. "
"Be concise, friendly, and context-aware."
)
)
def save_state(state: dict) -> None:
"""
Save state to disk as JSON.
Args:
state: The chat state dictionary.
"""
with STATE_FILE.open("w", encoding="utf-8") as f:
json.dump(state, f, indent=2, ensure_ascii=False)
def build_input_messages(state: dict) -> list:
"""
Convert chat state into a Responses API input list.
"""
messages = [
{
"role": "system",
"content": state["system_prompt"]
}
]
messages.extend(state["history"])
return messages
def main() -> None:
"""
Run a persistent, stateful chat loop.
"""
state = load_state()
print("Persistent Stateful Chatbot")
print("Type 'quit' to exit.")
print("Type 'reset' to clear conversation history.\n")
while True:
user_input = input("You: ").strip()
if user_input.lower() == "quit":
save_state(state)
print("State saved. Goodbye!")
break
if user_input.lower() == "reset":
state["history"] = []
save_state(state)
print("Conversation history cleared.\n")
continue
if not user_input:
print("Please enter a message.\n")
continue
state["history"].append({
"role": "user",
"content": user_input
})
response = client.responses.create(
model="gpt-5.4-mini",
input=build_input_messages(state)
)
assistant_text = response.output_text
state["history"].append({
"role": "assistant",
"content": assistant_text
})
save_state(state)
print(f"Assistant: {assistant_text}\n")
if __name__ == "__main__":
main()
Example Output
Persistent Stateful Chatbot
Type 'quit' to exit.
Type 'reset' to clear conversation history.
You: I am learning about Python classes.
Assistant: Great topic—Python classes help you bundle data and behavior together.
You: What was I learning about?
Assistant: You were learning about Python classes.
You: quit
State saved. Goodbye!
6.5 Best Practices for State in GenAI Apps
Keep state explicit
Avoid hidden global state when possible.
Store only what you need
Too much history can increase cost and latency.
Persist important state
If the app must recover after a restart, save it.
Validate state shape
Use consistent field names and expected formats.
Separate app state from model calls
This makes your code easier to test and maintain.
Consider trimming conversation history
In larger applications, retain only relevant recent messages or summaries.
7. Mini Lab: Add Structured Memory to the Chatbot
Objective
Extend the chatbot so it stores a user profile separately from message history.
Desired State Structure
{
"system_prompt": "...",
"user_profile": {
"name": "Maya",
"favorite_language": "Python"
},
"history": [...]
}
Suggested Tasks
- Add a
user_profiledictionary to state - Let the user type commands such as:
set name Mayaset favorite_language Python- Save these values in state
- Include profile information in the system prompt or as an extra message
- Ask the assistant to personalize its responses
Example Approach
def build_input_messages(state: dict) -> list:
"""
Build model input using system prompt, user profile, and conversation history.
"""
profile = state.get("user_profile", {})
profile_lines = []
if profile.get("name"):
profile_lines.append(f"User name: {profile['name']}")
if profile.get("favorite_language"):
profile_lines.append(f"Favorite language: {profile['favorite_language']}")
profile_text = "\n".join(profile_lines) if profile_lines else "No profile information available."
return [
{
"role": "system",
"content": state["system_prompt"]
},
{
"role": "system",
"content": f"Known user profile:\n{profile_text}"
},
*state["history"]
]
Example Interaction
You: set name Maya
Saved profile field: name = Maya
You: set favorite_language Python
Saved profile field: favorite_language = Python
You: What do you know about me?
Assistant: Your name is Maya, and your favorite language is Python.
8. Wrap-up
Key Ideas from This Session
- State is the data an application remembers over time
- Stateless functions are simple and predictable
- Stateful applications are essential for interactive systems
- Python state can be managed using variables, dictionaries, classes, and files
- GenAI apps rely heavily on well-managed conversation and session state
- The OpenAI Responses API can be used in Python to build state-aware applications
What Learners Should Now Be Able to Do
- Represent application state in Python
- Update state safely and clearly
- Persist state to a JSON file
- Build a simple stateful chatbot using
gpt-5.4-mini - Think more carefully about how context is preserved in AI apps
Useful Resources
- OpenAI Responses API migration guide: https://developers.openai.com/api/docs/guides/migrate-to-responses
- OpenAI API docs: https://platform.openai.com/docs
- OpenAI Python SDK: https://github.com/openai/openai-python
- Python
jsonmodule documentation: https://docs.python.org/3/library/json.html - Python
pathlibdocumentation: https://docs.python.org/3/library/pathlib.html
Suggested Homework
- Modify the persistent chatbot to store timestamps for every message
- Add a command to show the last 5 conversation turns
- Add a command to export the conversation to a text file
- Add a summary field that stores a short conversation summary after every 5 turns
- Refactor the chatbot state into a Python class
Quick Recap Questions
- What is application state?
- How is a stateful program different from a stateless one?
- Why do GenAI chat applications need state?
- When would you use a dictionary versus a class for state?
- Why might you save state to disk?
Back to Chapter | Back to Master Plan | Previous Session | Next Session