Python Conversation Forking
This guide covers conversation forking, letting users branch from any point in a conversation to explore alternative paths.
New to forking concepts? Read Forking first to understand how conversation forking works. This guide focuses on the Python LangChain implementation.
Prerequisites
Starting checkpoint: This guide starts from python/examples/langchain/doc-checkpoints/03-with-history
Make sure you’ve completed the previous guides:
- Python Getting Started - Minimal agent + memory-service checkpointer
- Python Conversation History - History recording and conversation APIs
Also complete Step 2 in Python Dev Setup (build local memory-service-langchain wheel + UV_FIND_LINKS); this is temporary until the package is released.
Conversation Forking
How Forking Works
Keep the chat endpoint the same shape as Quarkus and Spring (text/plain input). Fork creation is done by appending the first entry to a new conversation with fork metadata.
proxy = MemoryServiceProxy.from_env()
agent = stateful_agent
@app.post("/chat/{conversation_id}")
async def chat(conversation_id: str, request: Request) -> PlainTextResponse:
user_message = (await request.body()).decode("utf-8").strip()
if not user_message:
raise HTTPException(400, "message is required")
forked_at_conversation_id = request.query_params.get("forkedAtConversationId")
forked_at_entry_id = request.query_params.get("forkedAtEntryId")
with memory_service_scope(
conversation_id,
forked_at_conversation_id,
forked_at_entry_id,
):
history_middleware.append_user_history(
conversation_id,
user_message,
forked_at_conversation_id=forked_at_conversation_id,
forked_at_entry_id=forked_at_entry_id,
)
result = agent.invoke( What changed: The chat endpoint reads two optional query parameters — forkedAtConversationId and forkedAtEntryId — and passes them into memory_service_scope(conversation_id, forked_at_conversation_id, forked_at_entry_id).
Why: When fork parameters are present, the history middleware attaches them as fork metadata to the first entry it writes for this conversation. This tells the Memory Service that the new conversation branches off at a specific entry in another conversation, allowing the service to reconstruct the full lineage.
Listing Forks
Expose a forks endpoint so frontend clients can discover branches from a conversation:
limit=int(limit) if (limit := request.query_params.get("limit")) is not None else None,
query=request.query_params.get("query"),
)
return to_fastapi_response(response)
@app.get("/v1/conversations/{conversation_id}/forks")
async def list_conversation_forks(conversation_id: str, request: Request): Why: The Memory Service returns both the root conversation and all conversations that branched from it, ordered by creation time, making it straightforward for a frontend to render a conversation tree. Pagination parameters (afterCursor, limit) are forwarded directly so the response can be paged for conversations with many branches.
Try It With Curl
Define a helper to get a bearer token for bob:
function get-token() {
curl -sSfX POST http://localhost:8081/realms/memory-service/protocol/openid-connect/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "client_id=memory-service-client" \
-d "client_secret=change-me" \
-d "grant_type=password" \
-d "username=bob" \
-d "password=bob" \
| jq -r '.access_token'
}Create a turn on the source conversation:
curl -NsSfX POST http://localhost:9090/chat/16de0661-0dcf-4df2-9964-7f23c4de5fc6 \
-H "Content-Type: text/plain" \
-H "Authorization: Bearer $(get-token)" \
-d "Hello from the root conversation." Example output:
Sure, I can help with that. Fetch the entry id to fork from:
curl -sSfX GET http://localhost:9090/v1/conversations/16de0661-0dcf-4df2-9964-7f23c4de5fc6/entries \
-H "Authorization: Bearer $(get-token)" | jq Example output:
{
"data": [
{
"id": "38507d19-7f88-4c73-8ec1-076e85099dae"
},
{
"id": "5436d8eb-4159-4021-86c9-37d19b9b52fe"
}
],
"afterCursor": null
} Manual bash equivalent (stores the value in a shell variable):
FORK_ENTRY_ID="$(curl -sSfX GET http://localhost:9090/v1/conversations/16de0661-0dcf-4df2-9964-7f23c4de5fc6/entries \
-H "Authorization: Bearer $(get-token)" | jq -r '.data[0].id')"
echo "$FORK_ENTRY_ID"Create the forked conversation by calling chat with fork metadata:
curl -NsSfX POST "http://localhost:9090/chat/bb745717-236b-4c81-bc0a-766d10622e19?forkedAtConversationId=16de0661-0dcf-4df2-9964-7f23c4de5fc6&forkedAtEntryId=${FORK_ENTRY_ID}" \
-H "Content-Type: text/plain" \
-H "Authorization: Bearer $(get-token)" \
-d "Continue from this fork." Example output:
{
"afterCursor": null,
"data": [
{
"id": "38507d19-7f88-4c73-8ec1-076e85099dae",
"conversationId": "16de0661-0dcf-4df2-9964-7f23c4de5fc6",
"userId": "bob",
"clientId": "checkpoint-agent",
"channel": "history",
"contentType": "history",
"createdAt": "2026-03-06T14:58:32.36852Z",
"content": [
{
"role": "USER",
"text": "Hello from the root conversation."
}
]
},
{
"id": "5436d8eb-4159-4021-86c9-37d19b9b52fe",
"conversationId": "16de0661-0dcf-4df2-9964-7f23c4de5fc6",
"userId": "bob",
"clientId": "checkpoint-agent",
"channel": "history",
"contentType": "history",
"createdAt": "2026-03-06T14:58:32.426653Z",
"content": [
{
"role": "USER",
"text": "Hello from the root conversation."
}
]
},
{
"id": "88bcdd7d-3d16-4f66-a426-cb1948e52461",
"conversationId": "16de0661-0dcf-4df2-9964-7f23c4de5fc6",
"userId": "bob",
"clientId": "checkpoint-agent",
"channel": "history",
"contentType": "history",
"createdAt": "2026-03-06T14:58:32.471499Z",
"content": [
{
"role": "AI",
"text": "I am a Python memory-service demo agent."
}
]
}
]
} List forks for the source conversation through the Python proxy endpoint:
curl -sSfX GET http://localhost:9090/v1/conversations/16de0661-0dcf-4df2-9964-7f23c4de5fc6/forks \
-H "Authorization: Bearer $(get-token)" | jq Example output:
{
"data": [
{
"conversationId": "16de0661-0dcf-4df2-9964-7f23c4de5fc6"
},
{
"conversationId": "bb745717-236b-4c81-bc0a-766d10622e19"
}
],
"afterCursor": null
} Next Steps
- Response Recording and Resumption - Streaming responses with resume and cancel endpoints
- Sharing - Membership and ownership transfer APIs