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.
Please read this first
AdvancedSQLiteSession delete_branch orphan messages,delete_branch message_structure agent_messages.Describe the bug
AdvancedSQLiteSession.delete_branch()removesturn_usageandmessage_structurerows for the deleted branch, but it leaves rows in the underlyingagent_messagestable when those messages were only referenced by that branch. After deletion, those messages are invisible to advanced branch queries such asget_items(), but they remain in the database.Impact: branch-heavy sessions can accumulate invisible orphaned rows, causing database growth and audit noise.
Debug information
main @ 4c3de2df(latest release boundary:v0.17.0)Repro steps
Run this minimal script:
Actual result:
message_structureonly has main branch references, butagent_messagesstill 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 currentsession_idand removes messages with nomessage_structurereference, so it can be reused without cross-session deletion.self.messages_tableinstead of hard-codingagent_messages.Expected behavior
Branch deletion should remove message rows for the current session that are no longer referenced by any
message_structurerow, while keeping messages still shared by main or other branches. Branch-heavy sessions should not accumulate invisible orphaned message rows.