docs/source/guides/state.rst
.. _managing_conversational_state:
The OpenAI Responses API (v1/responses) is designed for multi-turn conversations where context needs to persist across requests. Plano provides a unified v1/responses API that works with any LLM provider—OpenAI, Anthropic, Azure OpenAI, DeepSeek, or any OpenAI-compatible provider—while automatically managing conversational state for you.
Unlike the traditional Chat Completions API where you manually manage conversation history by including all previous messages in each request, Plano handles state management behind the scenes. This means you can use the Responses API with any model provider, and Plano will persist conversation context across requests—making it ideal for building conversational agents that remember context without bloating every request with full message history.
When a client calls the Responses API:
resp_id and stores the conversation state (messages, model, provider, timestamp).previous_resp_id from the previous response. Plano retrieves the stored conversation state, merges it with the new input, and sends the combined context to the LLM.This pattern dramatically reduces bandwidth and makes it easier to build multi-turn agents—Plano handles the state plumbing so you can focus on agent logic.
Example Using OpenAI Python SDK:
.. code-block:: python
from openai import OpenAI
# Point to Plano's Model Proxy endpoint
client = OpenAI(
api_key="test-key",
base_url="http://127.0.0.1:12000/v1"
)
# First turn - Plano creates a new conversation state
response = client.responses.create(
model="claude-sonnet-4-5", # Works with any configured provider
input="My name is Alice and I like Python"
)
# Save the response_id for conversation continuity
resp_id = response.id
print(f"Assistant: {response.output_text}")
# Second turn - Plano automatically retrieves previous context
resp2 = client.responses.create(
model="claude-sonnet-4-5", # Make sure its configured in plano_config.yaml
input="Please list all the messages you have received in our conversation, numbering each one.",
previous_response_id=resp_id,
)
print(f"Assistant: {resp2.output_text}")
# Output: "Your name is Alice and your favorite language is Python"
Notice how the second request only includes the new user message—Plano automatically merges it with the stored conversation history before sending to the LLM.
State storage is configured in the state_storage section of your plano_config.yaml:
.. literalinclude:: ../resources/includes/plano_config_state_storage_example.yaml :language: yaml :lines: 21-30 :linenos: :emphasize-lines: 3,6-10
Plano supports two storage backends:
.. note::
If you don't configure state_storage, conversation state management is disabled. The Responses API will still work, but clients must manually include full conversation history in each request (similar to the Chat Completions API behavior).
Memory storage keeps conversation state in-memory using a thread-safe HashMap. It's perfect for local development, demos, and testing, but all state is lost when Plano restarts.
Configuration
Add this to your plano_config.yaml:
.. code-block:: yaml
state_storage: type: memory
That's it. No additional setup required.
When to Use Memory Storage
Limitations
PostgreSQL storage provides durable, production-grade conversation state management. It works with both self-hosted PostgreSQL and Supabase (PostgreSQL-as-a-service), making it ideal for scaling multi-agent systems in production.
Prerequisites ^^^^^^^^^^^^^
Before configuring PostgreSQL storage, you need:
conversation_states table created in your databaseSetting Up the Database
Run the SQL schema to create the required table:
.. literalinclude:: ../resources/db_setup/conversation_states.sql :language: sql :linenos:
Using psql:
.. code-block:: bash
psql $DATABASE_URL -f docs/db_setup/conversation_states.sql
Using Supabase Dashboard:
docs/db_setup/conversation_states.sqlConfiguration ^^^^^^^^^^^^^
Once the database table is created, configure Plano to use PostgreSQL storage:
.. code-block:: yaml
state_storage: type: postgres connection_string: "postgresql://user:password@host:5432/database"
Using Environment Variables
You should never hardcode credentials. Use environment variables instead:
.. code-block:: yaml
state_storage: type: postgres connection_string: "postgresql://myuser:$[email protected]:5432/postgres"
Then set the environment variable before running Plano:
.. code-block:: bash
export DB_PASSWORD="your-secure-password"
./plano
.. warning::
Special Characters in Passwords: If your password contains special characters like #, @, or &, you must URL-encode them in the connection string. For example, P@ss#123 becomes P%40ss%23123.
Supabase Connection Strings ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Supabase requires different connection strings depending on your network setup. Most users should use the Session Pooler connection string.
IPv4 Networks (Most Common)
Use the Session Pooler connection string (port 5432):
.. code-block:: text
postgresql://postgres.[PROJECT-REF]:[PASSWORD]@aws-0-[REGION].pooler.supabase.com:5432/postgres
IPv6 Networks
Use the direct connection (port 5432):
.. code-block:: text
postgresql://postgres:[PASSWORD]@db.[PROJECT-REF].supabase.co:5432/postgres
Finding Your Connection String
[YOUR-PASSWORD] with your actual database passwordExample Configuration
.. code-block:: yaml
state_storage: type: postgres connection_string: "postgresql://postgres.[YOUR-PROJECT-REF]:$DB_PASSWORD@aws-0-[REGION].pooler.supabase.com:5432/postgres"
Then set the environment variable:
.. code-block:: bash
export DB_PASSWORD="<your-url-encoded-password>"
"Table 'conversation_states' does not exist"
Run the SQL schema from docs/db_setup/conversation_states.sql against your database.
Connection errors with Supabase
Permission errors
Ensure your database user has the following permissions:
.. code-block:: sql
GRANT SELECT, INSERT, UPDATE, DELETE ON conversation_states TO your_user;
State not persisting across requests
state_storage is configured in your plano_config.yamlprev_response_id={$response_id} from previous responsesagents <agents> that leverage conversational statefilter chains <filter_chain> for enriching conversation contextLLM Providers <llm_providers> guide for configuring model routing