test(core): git-backed memory repo integration (real object store) (#9298)

---------

Co-authored-by: Letta <noreply@letta.com>
This commit is contained in:
Sarah Wooders
2026-02-05 14:53:28 -08:00
committed by Caren Thomas
parent 0bdedb3c0f
commit 93249b96f5
3 changed files with 38 additions and 1 deletions

View File

@@ -41,7 +41,8 @@ jobs:
"integration_test_batch_api_cron_jobs.py",
"integration_test_builtin_tools.py",
"integration_test_turbopuffer.py",
"integration_test_human_in_the_loop.py"
"integration_test_human_in_the_loop.py",
"integration_test_git_memory_repo_http.py"
]
}
}

View File

@@ -381,6 +381,10 @@ jobs:
GOOGLE_CLOUD_PROJECT: ${{ secrets.GOOGLE_CLOUD_PROJECT }}
GOOGLE_CLOUD_LOCATION: ${{ secrets.GOOGLE_CLOUD_LOCATION }}
# Real object store (required for git-backed memory integration test)
# Use DEV bucket/prefix variable to avoid prod resources.
LETTA_OBJECT_STORE_URI: ${{ vars.LETTA_OBJECT_STORE_URI_DEV }}
# Feature flags (shared across all test types)
LETTA_ENABLE_BATCH_JOB_POLLING: true

View File

@@ -465,12 +465,14 @@ async def _sync_after_push(actor_id: str, agent_id: str) -> None:
logger.exception("Failed to read repo files from storage for post-push block sync (agent=%s)", agent_id)
files = {}
expected_labels = set()
synced = 0
for file_path, content in files.items():
if not file_path.startswith("blocks/") or not file_path.endswith(".md"):
continue
label = file_path[len("blocks/") : -3]
expected_labels.add(label)
await _server_instance.block_manager._sync_block_to_postgres(
agent_id=agent_id,
label=label,
@@ -482,6 +484,36 @@ async def _sync_after_push(actor_id: str, agent_id: str) -> None:
if synced == 0:
logger.warning("No blocks/*.md files found in repo HEAD during post-push sync (agent=%s)", agent_id)
else:
# Detach blocks that were removed in git.
#
# We treat git as the source of truth for which blocks are attached to
# this agent. If a blocks/*.md file disappears from HEAD, detach the
# corresponding block from the agent in Postgres.
try:
existing_blocks = await _server_instance.agent_manager.list_agent_blocks_async(
agent_id=agent_id,
actor=actor,
before=None,
after=None,
limit=1000,
ascending=True,
)
existing_by_label = {b.label: b for b in existing_blocks}
removed_labels = set(existing_by_label.keys()) - expected_labels
for label in sorted(removed_labels):
block = existing_by_label.get(label)
if not block:
continue
await _server_instance.agent_manager.detach_block_async(
agent_id=agent_id,
block_id=block.id,
actor=actor,
)
logger.info("Detached block %s from agent (removed from git)", label)
except Exception:
logger.exception("Failed detaching removed blocks during post-push sync (agent=%s)", agent_id)
# Cleanup local cache
_repo_cache.pop(cache_key, None)