A mathematical model for agent conversations, including message trees, internal and external state, integrity constraints, state transitions, and halting.

Introduction

From the user’s perspective, a conversation is a series of interlinked messages, forming a tree. Each message can be seen as a node, and the flow of the conversation is represented by edges connecting these nodes. It is this tree-like structure that gives users a clear and coherent view of the unfolding dialogue.

Take the JSON below as an example. It’s a conversation from ChatGPT, but with a lot of data left out:

Click here to see huge JSON
{
  "title": "Test repetition requested",
  "create_time": 1698438998.854503,
  "update_time": 1698439032.992005,
  "mapping": {
    "52b648d0-c232-494e-a8e1-b7e1a668a8d8": {
      "id": "52b648d0-c232-494e-a8e1-b7e1a668a8d8",
      "message": {
        "id": "52b648d0-c232-494e-a8e1-b7e1a668a8d8",
        "author": { "role": "system", "metadata": {} },
        "content": { "content_type": "text", "parts": [""] }
      },
      "parent": "aaa11385-8b7f-4ff8-bbd2-6dca3ba97332",
      "children": ["aaa2ec55-09f9-4fa4-8345-3e091c396c93"]
    },
    "aaa11385-8b7f-4ff8-bbd2-6dca3ba97332": {
      "id": "aaa11385-8b7f-4ff8-bbd2-6dca3ba97332",
      "children": ["52b648d0-c232-494e-a8e1-b7e1a668a8d8"]
    },
    "aaa2ec55-09f9-4fa4-8345-3e091c396c93": {
      "id": "aaa2ec55-09f9-4fa4-8345-3e091c396c93",
      "message": {
        "id": "aaa2ec55-09f9-4fa4-8345-3e091c396c93",
        "author": { "role": "user", "metadata": {} },
        "content": {
          "content_type": "text",
          "parts": ["Node 1.\nI'm doing a test. Just repeat what I say."]
        }
      },
      "parent": "52b648d0-c232-494e-a8e1-b7e1a668a8d8",
      "children": ["5df2792d-af5b-4a6d-b94e-22ccb4e67cf8"]
    },
    "5df2792d-af5b-4a6d-b94e-22ccb4e67cf8": {
      "id": "5df2792d-af5b-4a6d-b94e-22ccb4e67cf8",
      "message": {
        "id": "5df2792d-af5b-4a6d-b94e-22ccb4e67cf8",
        "author": { "role": "assistant", "metadata": {} },
        "content": { "content_type": "text", "parts": ["Node 1."] }
      },
      "parent": "aaa2ec55-09f9-4fa4-8345-3e091c396c93",
      "children": [
        "aaa231d2-cb1a-4028-a3fb-2799cbe8633e",
        "aaa2ec28-9cc9-4b41-9f63-4dcb413ff1da"
      ]
    },
    "aaa231d2-cb1a-4028-a3fb-2799cbe8633e": {
      "id": "aaa231d2-cb1a-4028-a3fb-2799cbe8633e",
      "message": {
        "id": "aaa231d2-cb1a-4028-a3fb-2799cbe8633e",
        "author": { "role": "user", "metadata": {} },
        "content": {
          "content_type": "text",
          "parts": ["Node 2.\nI'm doing a test. Just repeat what I say."]
        }
      },
      "parent": "5df2792d-af5b-4a6d-b94e-22ccb4e67cf8",
      "children": ["dee00795-4844-45fe-8205-f48302b73463"]
    },
    "dee00795-4844-45fe-8205-f48302b73463": {
      "id": "dee00795-4844-45fe-8205-f48302b73463",
      "message": {
        "id": "dee00795-4844-45fe-8205-f48302b73463",
        "author": { "role": "assistant", "metadata": {} },
        "content": {
          "content_type": "text",
          "parts": ["Node 2.\nI'm doing a test. Just repeat what I say."]
        }
      },
      "parent": "aaa231d2-cb1a-4028-a3fb-2799cbe8633e",
      "children": []
    },
    "aaa2ec28-9cc9-4b41-9f63-4dcb413ff1da": {
      "id": "aaa2ec28-9cc9-4b41-9f63-4dcb413ff1da",
      "message": {
        "id": "aaa2ec28-9cc9-4b41-9f63-4dcb413ff1da",
        "author": { "role": "user", "metadata": {} },
        "content": {
          "content_type": "text",
          "parts": ["Node 3.\nI'm doing a test. Just repeat what I say."]
        }
      },
      "parent": "5df2792d-af5b-4a6d-b94e-22ccb4e67cf8",
      "children": ["848b706b-166e-4ad0-866c-ef5f2cf21f10"]
    },
    "848b706b-166e-4ad0-866c-ef5f2cf21f10": {
      "id": "848b706b-166e-4ad0-866c-ef5f2cf21f10",
      "message": {
        "id": "848b706b-166e-4ad0-866c-ef5f2cf21f10",
        "author": { "role": "assistant", "metadata": {} },
        "create_time": 1698439020.982715,
        "content": { "content_type": "text", "parts": ["Node 3."] }
      },
      "parent": "aaa2ec28-9cc9-4b41-9f63-4dcb413ff1da",
      "children": ["aaa2570f-5d27-4489-a1e3-8db98dc249b6"]
    },
    "aaa2570f-5d27-4489-a1e3-8db98dc249b6": {
      "id": "aaa2570f-5d27-4489-a1e3-8db98dc249b6",
      "message": {
        "id": "aaa2570f-5d27-4489-a1e3-8db98dc249b6",
        "author": { "role": "user", "metadata": {} },
        "create_time": 1698439032.609726,
        "content": {
          "content_type": "text",
          "parts": ["Node 4.\nI'm doing a test. Just repeat what I say."]
        }
      },
      "parent": "848b706b-166e-4ad0-866c-ef5f2cf21f10",
      "children": ["17c26d4b-28a4-49e0-84cf-65752b233a5f"]
    },
    "17c26d4b-28a4-49e0-84cf-65752b233a5f": {
      "id": "17c26d4b-28a4-49e0-84cf-65752b233a5f",
      "message": {
        "id": "17c26d4b-28a4-49e0-84cf-65752b233a5f",
        "author": { "role": "assistant", "metadata": {} },
        "create_time": 1698439032.990924,
        "content": { "content_type": "text", "parts": ["Node 4."] }
      },
      "parent": "aaa2570f-5d27-4489-a1e3-8db98dc249b6",
      "children": []
    }
  },
  "moderation_results": [],
  "current_node": "17c26d4b-28a4-49e0-84cf-65752b233a5f",
  "conversation_id": "5e18bb0e-a8dc-4ebe-89ab-e3746b9782ea"
}

A few things should catch our attention:

  • The root node, aaa11385-8b7f-4ff8-bbd2-6dca3ba97332, does not have a parent.
  • Each field in the mapping object corresponds to a node in the tree. The parent and children fields point to related nodes by their IDs.

I wrote a script to convert any such saved conversation into a Mermaid diagram:

graph TD
52b648d0-c232-494e-a8e1-b7e1a668a8d8["system: null"]
52b648d0-c232-494e-a8e1-b7e1a668a8d8 --> aaa2ec55-09f9-4fa4-8345-3e091c396c93
style aaa11385-8b7f-4ff8-bbd2-6dca3ba97332 text-align:left
aaa11385-8b7f-4ff8-bbd2-6dca3ba97332["Root Node"]
aaa11385-8b7f-4ff8-bbd2-6dca3ba97332 --> 52b648d0-c232-494e-a8e1-b7e1a668a8d8
aaa2ec55-09f9-4fa4-8345-3e091c396c93["user: Node 1.\nI'm doing a test. Just repeat what I say."]
aaa2ec55-09f9-4fa4-8345-3e091c396c93 --> 5df2792d-af5b-4a6d-b94e-22ccb4e67cf8
5df2792d-af5b-4a6d-b94e-22ccb4e67cf8["assistant: Node 1."]
5df2792d-af5b-4a6d-b94e-22ccb4e67cf8 --> aaa231d2-cb1a-4028-a3fb-2799cbe8633e
5df2792d-af5b-4a6d-b94e-22ccb4e67cf8 --> aaa2ec28-9cc9-4b41-9f63-4dcb413ff1da
aaa231d2-cb1a-4028-a3fb-2799cbe8633e["user: Node 2.\nI'm doing a test. Just repeat what I say."]
aaa231d2-cb1a-4028-a3fb-2799cbe8633e --> dee00795-4844-45fe-8205-f48302b73463
dee00795-4844-45fe-8205-f48302b73463["assistant: Node 2.\nI'm doing a test. Just repeat what I say."]
aaa2ec28-9cc9-4b41-9f63-4dcb413ff1da["user: Node 3.\nI'm doing a test. Just repeat what I say."]
aaa2ec28-9cc9-4b41-9f63-4dcb413ff1da --> 848b706b-166e-4ad0-866c-ef5f2cf21f10
848b706b-166e-4ad0-866c-ef5f2cf21f10["assistant: Node 3."]
848b706b-166e-4ad0-866c-ef5f2cf21f10 --> aaa2570f-5d27-4489-a1e3-8db98dc249b6
aaa2570f-5d27-4489-a1e3-8db98dc249b6["user: Node 4.\nI'm doing a test. Just repeat what I say."]
aaa2570f-5d27-4489-a1e3-8db98dc249b6 --> 17c26d4b-28a4-49e0-84cf-65752b233a5f
17c26d4b-28a4-49e0-84cf-65752b233a5f["assistant: Node 4."]

The conversation tree

The conversation tree can be modeled using graph theory. It is a graph where nodes represent messages and directed edges represent the flow of the conversation.

Let’s define the conversation tree:

Definition

A conversation tree TT is defined as T=(V,E)T = (V, E) where:

  • VV is a set of nodes, where each node represents a message.
  • EE is a set of directed edges, where each edge represents a response or a follow-up to a message.

Nodes

Each node viVv_i \in V can be represented by:

vi=(ID,Author,Content,Timestamp,)v_i = (ID, Author, Content, Timestamp, \dots)

where:

  • IDID is a unique identifier for the message.
  • AuthorAuthor indicates the sender of the message.
  • ContentContent is the text of the message.
  • TimestampTimestamp is the time the message was sent.
  • The tuple can include additional metadata.

Edges

Each directed edge eijEe_{ij} \in E is an ordered pair (vi,vj)(v_i, v_j) where:

  • viv_i is the parent message.
  • vjv_j is the child message or the response to viv_i.

Root

There is a unique node rVr \in V such that no edges in EE point to rr. This node is called the root of the tree and represents the starting point of the conversation.

Properties

  • Acyclic: The graph does not contain any cycles. This ensures that the conversation has a clear flow without any looping back.
  • Directed: The edges have a direction, indicating the flow of the conversation.
  • Tree structure: There is exactly one path from the root to each node. This gives the conversation a hierarchical structure.

Functions

  • Parent function: P:VrVP: V \setminus {r} \rightarrow V assigns each non-root node viv_i its parent P(vi)P(v_i).
  • Children function: C:V2VC: V \rightarrow 2^V assigns each node viv_i its set of child nodes C(vi)C(v_i).

This mathematical formulation captures the essence of the conversation tree as presented in the JSON structure. By using graph theory, we can further analyze properties of the conversation, such as its depth, branching factor, and other relevant metrics.

Internal vs external state

The conversation tree only represents the user-facing part, or the external state, of the conversation. We call information that is part of the conversation but hidden from the user the internal state. For example, a hidden system prompt is part of the internal state.

Given the new definitions and constraints, we can refine the mathematical formulation to account for both the external and internal states of the conversation. Here’s an updated model:

Conversation state

The conversation state σ\sigma at time tt is defined as:

σ(t)=(σE(t),σI(t))\sigma(t) = (\sigma_E(t), \sigma_I(t))

where:

  • σE(t)\sigma_E(t) represents the external state at time tt.
  • σI(t)\sigma_I(t) represents the internal state at time tt.

Here, σ\sigma represents the entire state at any given time. We denote the set of all possible states by Σ\Sigma.

External state σE\sigma_E

The external state at time tt, σE(t)\sigma_E(t), is defined as an abstract representation of the system’s observable output, represented as a set or vector. This could be the current view of the conversation, the messages exchanged, or any other output that is visible to the user.

Integrity of the external state σE\sigma_E

There is one very important constraint for how the external state should evolve. Anything that the user has already seen should not change. This means that the external state can only increase over time, and not decrease. Formally, this can be expressed as:

σE(t)σE(t+1)\sigma_E(t) \subseteq \sigma_E(t+1)

The external state (observable output) at time tt is a subset of the external state at time t+1t+1, ensuring that the observable output can only be appended with new information or remain the same over time, but not altered or removed.

This constraint ensures that the integrity of the external conversation history is always preserved, while the internal state can change flexibly. In other words,

Anything about the conversation can change except what the system has already shown to the user.

This is one of the most important parts of the formulation.

Why use an abstract model?

At a basic level, this post describes a chatbot history that can track alternative user and assistant responses. Future systems can look different from conversations serialized as JSON objects. An abstract model removes infrastructure assumptions and keeps the one necessary rule: the integrity principle.

Integrity Principle

The record of actions that an agent has taken in the past cannot be changed.

I believe that the “why” is self-evident, without going into philosophy. As for the case of conversations, I represent the external state as a tree:

External state as a tree

The external state at time tt, σE(t)\sigma_E(t), is defined as a tree:

σE(t)=(V(t),E(t))\sigma_E(t) = (V(t), E(t))

where:

  • V(t)V(t) is a set of nodes at time tt.
  • E(t)E(t) is a set of directed edges at time tt.

As for the integrity requirement, only new nodes can be appended at any existing node, including the root node. The part of the tree from the previous step should remain the same:

  • V(t)V(t+1)V(t) \subseteq V(t+1): The set of nodes can only increase or remain the same over time.
  • E(t)E(t+1)E(t) \subseteq E(t+1): The set of edges can only increase or remain the same over time.
  • Nodes and edges from previous times remain unchanged, ensuring that the conversation history is preserved.

As an example, consider the following evolution of state of a user’s interaction:

flowchart TD
    0{V0} --> 1((V1))
    1 -.-> 2(V2)
    2 --> 3((V3))
    3 -.-> 4(V4)
    0 --> 5((V5))
    5 -.-> 6(V6)
    2 --> 7((V7))
    7 -.-> 8(V8)

The conversation evolved like this:

  1. The user started with an empty state σE(0)=(V0,)\sigma_E(0)=({V_0}, \emptyset), that is consisting only of the root node and no edges. Think of this like landing on the chat page for the first time. (The root node is not something that the user can visibly see, but it is sent by the API.)
  2. Then, they sent a message, which created a new node V1V_1.
  3. The system did some processing, called the LLM, and responded with a message, resulting in a new node V2V_2.
  4. The user sent another message, which resulted in a new node V3V_3, and the system responded with a message, resulting in a new node V4V_4.
  5. The user then went to the beginning of the conversation and edited their first message, resulting in the creation of a branch. The original message is preserved, and a new node V5V_5 is created.
  6. The system processes the edited message, which results in a new node V6V_6, serving as a response to the edited initial message V5V_5.
  7. Meanwhile, the user also revisits their second message (originally resulting in V3V_3) and edits it, creating another branch in the conversation with a new node V7V_7.
  8. The system processes this second edited message, leading to a new system response and the creation of node V8V_8.

The circular nodes represent user messages, and the rectangular nodes represent system messages. The full edges represent the edits to the state by the user, and the dashed edges represent the edits to the state by the assistant.

As an exercise, let’s write down and draw the external state at each step. We already described the initial state above, with only the root node:

σE(0)=({V0},)\sigma_E(0) = (\{V_0\}, \emptyset)
flowchart TD
    0{V0}

The rest follow as:

σE(1)=({V0,V1},{(V0,V1)})\sigma_E(1) = (\{V_0, V_1\}, \{(V_0, V_1)\})
flowchart TD
    0{V0} --> 1((V1))
σE(2)=({V0,V1,V2},{(V0,V1),(V1,V2)})\sigma_E(2) = (\{V_0, V_1, V_2\}, \{(V_0, V_1), (V_1, V_2)\})
flowchart TD
    0{V0} --> 1((V1))
    1 -.-> 2(V2)
σE(3)=({V0,V1,V2,V3},{(V0,V1),(V1,V2),(V2,V3)})\sigma_E(3) = (\{V_0, V_1, V_2, V_3\}, \{(V_0, V_1), (V_1, V_2), (V_2, V_3)\})
flowchart TD
    0{V0} --> 1((V1))
    1 -.-> 2(V2)
    2 --> 3((V3))
σE(4)=({V0,V1,V2,V3,V4},{(V0,V1),(V1,V2),(V2,V3),(V3,V4)})\sigma_E(4) = (\{V_0, V_1, V_2, V_3, V_4\}, \{(V_0, V_1), (V_1, V_2), (V_2, V_3), (V_3, V_4)\})
flowchart TD
    0{V0} --> 1((V1))
    1 -.-> 2(V2)
    2 --> 3((V3))
    3 -.-> 4(V4)
σE(5)=({V0,V1,V2,V3,V4,V5},{(V0,V1),(V1,V2),(V2,V3),(V3,V4),(V0,V5)})\sigma_E(5) = (\{V_0, V_1, V_2, V_3, V_4, V_5\}, \{(V_0, V_1), (V_1, V_2), (V_2, V_3), (V_3, V_4), (V_0, V_5)\})
flowchart TD
    0{V0} --> 1((V1))
    1 -.-> 2(V2)
    2 --> 3((V3))
    3 -.-> 4(V4)
    0 --> 5((V5))
σE(6)=({V0,V1,V2,V3,V4,V5,V6},{(V0,V1),(V1,V2),(V2,V3),(V3,V4),(V0,V5),(V5,V6)})\sigma_E(6) = (\{V_0, V_1, V_2, V_3, V_4, V_5, V_6\}, \{(V_0, V_1), (V_1, V_2), (V_2, V_3), (V_3, V_4), (V_0, V_5), (V_5, V_6)\})
flowchart TD
    0{V0} --> 1((V1))
    1 -.-> 2(V2)
    2 --> 3((V3))
    3 -.-> 4(V4)
    0 --> 5((V5))
    5 -.-> 6(V6)
σE(7)=({V0,V1,V2,V3,V4,V5,V6,V7},{(V0,V1),(V1,V2),(V2,V3),(V3,V4),(V0,V5),(V5,V6),(V2,V7)})\sigma_E(7) = (\{V_0, V_1, V_2, V_3, V_4, V_5, V_6, V_7\}, \{(V_0, V_1), (V_1, V_2), (V_2, V_3), (V_3, V_4), (V_0, V_5), (V_5, V_6), (V_2, V_7)\})
flowchart TD
    0{V0} --> 1((V1))
    1 -.-> 2(V2)
    2 --> 3((V3))
    3 -.-> 4(V4)
    0 --> 5((V5))
    5 -.-> 6(V6)
    2 --> 7((V7))
σE(8)=({V0,V1,V2,V3,V4,V5,V6,V7,V8},{(V0,V1),(V1,V2),(V2,V3),(V3,V4),(V0,V5),(V5,V6),(V2,V7),(V7,V8)})\sigma_E(8) = (\{V_0, V_1, V_2, V_3, V_4, V_5, V_6, V_7, V_8\}, \{(V_0, V_1), (V_1, V_2), (V_2, V_3), (V_3, V_4), (V_0, V_5), (V_5, V_6), (V_2, V_7), (V_7, V_8)\})
flowchart TD
    0{V0} --> 1((V1))
    1 -.-> 2(V2)
    2 --> 3((V3))
    3 -.-> 4(V4)
    0 --> 5((V5))
    5 -.-> 6(V6)
    2 --> 7((V7))
    7 -.-> 8(V8)

In each step, the external state σE(t)\sigma_E(t) is expanded to include new nodes and edges, adhering to the principle of “Integrity of the External State,” where previous nodes and edges remain unchanged.

An implementation should:

  • Keep track of the external state reliably.
  • Switch between branches efficiently.
  • Limit the amount of data stored and sent to the client.
  • Handle multiple users and conversations at scale.
  • Handle conversations that can contain hundreds of messages.

Internal state σI\sigma_I

Similar to the external state, we represent the internal state at time tt, σI(t)\sigma_I(t), as a set or vector capturing the system’s internal parameters, context, or any other hidden variables. It evolves over time based on system logic, user interactions, prompt engineering, tool calls and potentially other factors.

where:

  • H(t)H(t) represents hidden variables or parameters at time tt.

–>

We don’t define a specific constraint on how σI(t)\sigma_I(t) should evolve. It can change based on the system’s internal machinery and requirements.

State transition

A transition function defines how a system moves from one state to another based on its inputs. Here, it defines how the visible conversation tree and internal state evolve over time.

Given σ(t)\sigma(t), the next conversation state σ(t+1)\sigma(t+1) is a function of both the current external and internal states:

σ(t+1)=Δ(σ(t))=Δ(σE(t),σI(t))\sigma(t+1) = \Delta(\sigma(t)) = \Delta(\sigma_E(t), \sigma_I(t))

The transition function Δ\Delta must obey the constraints we defined earlier:

  • The external state σE\sigma_E transitions by only appending nodes and edges, and not modifying previous nodes or edges.
  • The internal state σI\sigma_I transitions according to the system’s requirements and logic.

Here, Δ\Delta is a general transition function that does not distinguish between changes made by the user and changes made by the assistant. The next section makes that distinction.

User and assistant transitions

The system combines a user and an assistant. The user provides an input, and the assistant works on it until a halting condition is fulfilled. For example, the assistant can stop after it completes the user’s task.

To this end, we introduce a user transition function u:ΣEΣEu:\Sigma_E\to\Sigma_E and an assistant transition function δ:ΣΣ\delta: \Sigma\to\Sigma.

The user transition function formalizes changes that the user can make to the conversation state, such as sending or editing a message. An edit creates a new branch in the conversation tree. By definition, the user cannot see or modify the internal state, so the function only operates on the external state:

u(σE(t))=σE(t+1)u(\sigma_E(t)) = \sigma_E(t+1)

The assistant can modify both the external and internal state:

δ(σE(t),σI(t))=(σE(t+1),σI(t+1))=:σ(t+1)\delta(\sigma_E(t), \sigma_I(t)) = (\sigma_E(t+1), \sigma_I(t+1)) =: \sigma(t+1)

For example, the assistant could query a knowledge base, save the results to the internal state, generate a response with an LLM, and send a message to the user. The assistant transition function δ\delta would then update both the external and internal state.

The user and assistant transition the state in rounds. In this model, the user can only send a message when the assistant is done processing the previous message. As a mental model, you can think that there is a human behind the user function uu, and there is something resembling a Turing machine behind the assistant function δ\delta, that does arbitrary computation on the state until it decides to stop. Whether the assistant can stop is formalized by the halting condition HH:

H:Σ{True,False}H: \Sigma \to \{True, False\}

For example, at the end of each state transition, the assistant could feed the LLM its entire output in the current round and ask

Is the user’s request fulfilled?

If the answer is yes, the halting condition is fulfilled and the assistant stops. If the answer is no, the assistant continues to work on the user’s request. One can also imagine that the assistant should stop after a maximum number of steps, preventing the assistant from going into an infinite loop.

We can now put all of our definitions together to define the main transition function Δ\Delta:

Δ(σ(t)):={(u(σE(t)),σI(t))if H(σ(t))=Trueδ(σE(t),σI(t))if H(σ(t))=Falseandσ(t+1)=  Δ(σ(t))\boxed{ \begin{aligned} \Delta(\sigma(t)) :=& \begin{cases} (u(\sigma_E(t)),\sigma_I(t)) & \text{if } H(\sigma(t)) = True \\ \delta(\sigma_E(t), \sigma_I(t)) & \text{if } H(\sigma(t)) = False \end{cases} \\ \text{and}\\ \sigma(t+1) =&\; \Delta(\sigma(t)) \end{aligned} }

Together with the integrity constraint, this box summarizes how the conversation state evolves over time. For example, call_assistant can be a recursive function whose input is the current state and whose output is the first state that meets the halting condition:

def call_assistant(state):
    """
    Assistant transitions as a recursive function
    """
    new_state = delta(state)

    if halting_condition(new_state):
        return new_state
    else:
        return call_assistant(new_state)

At the end of each round, this function checks whether the halting condition is fulfilled. If it is not, the function calls itself again with the next state.

We could alternatively use a while loop:

def call_assistant(state):
    """
    Assistant transitions as a while loop
    """
    while True:
        new_state = delta(state)

        if halting_condition(new_state):
            return new_state

        state = new_state