Skip to content

Python Conversations Client Tutorial

This tutorial shows how to use the moraine-conversations Python binding as a high-level read-only client for Moraine conversation data. The binding exports ConversationClient and provides three primary methods: list_conversations_json, get_conversation_json, and search_conversations_json. init.py:L1-3lib.rs:L62-140

Prerequisites

You need a running Moraine stack (or any reachable ClickHouse endpoint with the Moraine schema) and Python 3.9+. A standard local setup is to start the stack with bin/moraine up. The binding package itself is configured for Python >=3.9 and uses maturin as the build backend. build-and-operations.md:L89-104pyproject.toml:L1-10

Install The Binding Locally

From the repo root:

cd bindings/python/moraine_conversations
python3 -m venv .venv
source .venv/bin/activate
pip install maturin
maturin develop

At that point, import moraine_conversations loads the compiled extension module in your virtualenv. README.md:L5-17pyproject.toml:L20-23

Create A Client

from moraine_conversations import ConversationClient

client = ConversationClient(
    url="http://127.0.0.1:8123",
    database="moraine",
    timeout_seconds=5.0,
)

The constructor accepts url, database, username, password, timeout_seconds, and max_results. lib.rs:L18-60

List Conversations By Date And Mode

import json

page = json.loads(
    client.list_conversations_json(
        from_unix_ms=1767261600000,
        to_unix_ms=1767500000000,
        mode="web_search",
        limit=50,
        cursor=None,
    )
)

for convo in page["items"]:
    print(convo["session_id"], convo["last_event_time"], convo["mode"])

next_cursor = page["next_cursor"]

mode must be one of web_search, mcp_internal, tool_calling, or chat. Modes are computed per session (exactly one mode per session), using first-match precedence over any matching event: web_search > mcp_internal > tool_calling > chat. In concrete terms: web_search covers web search events (web_search_call, search_results_received, or tool_use with WebSearch/WebFetch), mcp_internal covers Codex MCP internal search/open activity, tool_calling covers remaining tool activity (tool_call, tool_result, tool_use), and chat means none of those signals were present. Results are paginated with next_cursor. lib.rs:L62-85lib.rs:L148-155clickhouse_repo.rs:L451-471

Retrieve One Conversation By ID

import json

conversation = json.loads(client.get_conversation_json("sess_a", include_turns=True))
if conversation is None:
    print("not found")
else:
    print(conversation["summary"]["session_id"])
    print(len(conversation["turns"]))

The method returns JSON for Option<Conversation>, so a missing session is null in Python after json.loads. lib.rs:L87-98

Search Conversations (Whole-Conversation Ranking)

import json

results = json.loads(
    client.search_conversations_json(
        query="vector store migration",
        limit=10,
        min_score=0.0,
        min_should_match=1,
        from_unix_ms=1767261600000,
        to_unix_ms=1767500000000,
        mode="chat",
        include_tool_events=True,
        exclude_codex_mcp=False,
    )
)

for hit in results["hits"]:
    print(hit["session_id"], hit["score"], hit.get("best_event_uid"))

This search is ranked at the conversation/session level, not only single events. Internally, event scores are aggregated by session_id, and the best event per session is carried through as best_event_uid plus a snippet. lib.rs:L100-140clickhouse_repo.rs:L592-620

Run The Python Smoke Test

cd bindings/python/moraine_conversations
source .venv/bin/activate
pip install pytest
CARGO_HOME=/tmp/cargo-home maturin develop
pytest -q tests/test_smoke.py

The smoke test exercises list/get/search end-to-end against a mock ClickHouse HTTP endpoint. test_smoke.py:L120-166pyproject.toml:L17-18

Error Semantics

Invalid mode values raise a Python ValueError. Backend or query failures are surfaced as Python RuntimeError values with the underlying error text. lib.rs:L7-8lib.rs:L153-160