Skip to content

AdvancedSQLiteSession.delete_branch() leaves branch-only messages in the base table #3346

@Aphroq

Description

@Aphroq

Please read this first

  • I checked the related implementation and existing tests.
  • I searched for related issues/PRs and did not find a direct duplicate: AdvancedSQLiteSession delete_branch orphan messages, delete_branch message_structure agent_messages.

Describe the bug

AdvancedSQLiteSession.delete_branch() removes turn_usage and message_structure rows for the deleted branch, but it leaves rows in the underlying agent_messages table when those messages were only referenced by that branch. After deletion, those messages are invisible to advanced branch queries such as get_items(), but they remain in the database.

Impact: branch-heavy sessions can accumulate invisible orphaned rows, causing database growth and audit noise.

Debug information

  • Agents SDK version: main @ 4c3de2df (latest release boundary: v0.17.0)
  • Python version: Python 3.12.1

Repro steps

Run this minimal script:

import asyncio
import json

from agents.extensions.memory import AdvancedSQLiteSession


async def main():
    session = AdvancedSQLiteSession(
        session_id="delete_branch_orphan_repro",
        create_tables=True,
    )

    await session.add_items(
        [
            {"role": "user", "content": "main question"},
            {"role": "assistant", "content": "main answer"},
        ]
    )

    await session.create_branch_from_turn(1, "branch_only")
    await session.add_items(
        [
            {"role": "user", "content": "branch-only question"},
            {"role": "assistant", "content": "branch-only answer"},
        ]
    )

    await session.delete_branch("branch_only", force=True)

    with session._locked_connection() as conn:
        message_rows = conn.execute(
            """
            SELECT id, message_data
            FROM agent_messages
            WHERE session_id = ?
            ORDER BY id
            """,
            (session.session_id,),
        ).fetchall()
        structure_rows = conn.execute(
            """
            SELECT branch_id, message_id
            FROM message_structure
            WHERE session_id = ?
            ORDER BY message_id
            """,
            (session.session_id,),
        ).fetchall()

    print(
        "agent_messages_after_delete:",
        [(row[0], json.loads(row[1]).get("content")) for row in message_rows],
    )
    print("message_structure_after_delete:", structure_rows)
    session.close()


asyncio.run(main())

Actual result:

agent_messages_after_delete: [(1, 'main question'), (2, 'main answer'), (3, 'branch-only question'), (4, 'branch-only answer')]
message_structure_after_delete: [('main', 1), ('main', 2)]

message_structure only has main branch references, but agent_messages still contains the two branch-only messages.

Related paths and boundary cases checked:

  • create_branch_from_turn() / _copy_messages_to_new_branch(): branch creation reuses existing main message IDs, so cleanup must not delete shared messages that are still referenced by other branches.
  • delete_branch(..., force=True): deleting the current branch switches back to main before deletion, and cleanup must not affect the active main branch.
  • _cleanup_orphaned_messages_sync(): the existing helper already scopes cleanup to the current session_id and removes messages with no message_structure reference, so it can be reused without cross-session deletion.
  • custom table names: cleanup should keep using self.messages_table instead of hard-coding agent_messages.

Expected behavior

Branch deletion should remove message rows for the current session that are no longer referenced by any message_structure row, while keeping messages still shared by main or other branches. Branch-heavy sessions should not accumulate invisible orphaned message rows.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions