Skip to content

Runner._compute_artifact_delta_for_rewind saves artifacts with "file_data", which GcsArtifactService and FileArtifactService don't supportΒ #4932

@lucasbarzotto-axonify

Description

@lucasbarzotto-axonify

πŸ”΄ Required Information

Describe the Bug:

Runner._compute_artifact_delta_for_rewind (runners.py:755–765) restores historical artifact versions by constructing a types.Part(file_data=types.FileData(file_uri=...)) and passing it to artifact_service.save_artifact. However, GcsArtifactService._save_artifact explicitly raises NotImplementedError when it receives a file_data part (gcs_artifact_service.py:232–236), and FileArtifactService has no file_data handling path at all.

This means rewind_async crashes at runtime for any application using GcsArtifactService or FileArtifactService when the session contains artifacts that changed after the rewind point. Only InMemoryArtifactService works, since it stores the Part object as-is without attempting to extract bytes.

Steps to Reproduce:

  1. Create an ADK application using GcsArtifactService (or FileArtifactService)
  2. Run agent invocations that produce artifacts (e.g. save an image via tool_context.save_artifact)
  3. Run additional invocations that modify or create new versions of the same artifact
  4. Call runner.rewind_async(user_id=..., session_id=..., rewind_before_invocation_id="<invocation_before_artifact_change>")
  5. Observe NotImplementedError from GcsArtifactService
NotImplementedError: Saving artifact with file_data is not supported yet in GcsArtifactService.

Expected Behavior:

rewind_async should successfully restore artifacts to their historical version regardless of which ArtifactService implementation is used. Since load_artifact already returns artifact content as inline_data (it reads the actual bytes from storage), the rewind code should use load_artifact to read the historical version and then save_artifact with inline_data β€” which all artifact service implementations support.

Observed Behavior:

rewind_async raises NotImplementedError when any artifact needs restoration and the artifact service is GcsArtifactService:

NotImplementedError: Saving artifact with file_data is not supported yet in GcsArtifactService.

The crash occurs at runners.py:766 when calling self.artifact_service.save_artifact(... artifact=artifact ...) with the file_data part constructed at line 765.

Root Cause:

In runners.py:755–765, _compute_artifact_delta_for_rewind constructs an internal artifact:// URI and wraps it in file_data:

artifact_uri = artifact_util.get_artifact_uri(
    app_name=self.app_name,
    user_id=session.user_id,
    session_id=session.id,
    filename=filename,
    version=vt,
)
artifact = types.Part(file_data=types.FileData(file_uri=artifact_uri))
await self.artifact_service.save_artifact(
    app_name=self.app_name,
    user_id=session.user_id,
    session_id=session.id,
    filename=filename,
    artifact=artifact,
)

But GcsArtifactService._save_artifact (gcs_artifact_service.py:232–236) rejects file_data:

elif artifact.file_data:
    raise NotImplementedError(
        "Saving artifact with file_data is not supported yet in"
        " GcsArtifactService."
    )

And FileArtifactService._save_artifact only handles inline_data and text β€” a file_data part falls through to the "Artifact must have either inline_data or text" validation error.

Suggested Fix:

Instead of constructing a file_data reference, the rewind code should load_artifact at the target version (which returns the actual bytes as inline_data) and then save_artifact with that inline_data part. This works with all artifact service implementations:

# Instead of constructing a file_data reference:
loaded = await self.artifact_service.load_artifact(
    app_name=self.app_name,
    user_id=session.user_id,
    session_id=session.id,
    filename=filename,
    version=vt,
)
if loaded and loaded.inline_data:
    artifact = types.Part(
        inline_data=types.Blob(
            mime_type=loaded.inline_data.mime_type,
            data=loaded.inline_data.data,
        )
    )
else:
    # Fallback: artifact couldn't be loaded, mark as empty
    artifact = types.Part(
        inline_data=types.Blob(mime_type="application/octet-stream", data=b"")
    )

Note: the code already uses inline_data for the "artifact did not exist at rewind point" case (runners.py:750–753) β€” the fix just extends this approach to the "restore existing version" case.

Environment Details:

  • ADK Library Version: 1.27.1
  • Desktop OS: macOS (darwin 24.6.0)
  • Python Version: 3.13

Model Information:

  • Are you using LiteLLM: No
  • Which model is being used: gemini-2.5-pro

🟑 Optional Information

How often has this issue occurred?:

  • Always (100%) β€” occurs whenever rewind_async needs to restore an artifact and the artifact service is GcsArtifactService or FileArtifactService

Metadata

Metadata

Assignees

No one assigned

    Labels

    services[Component] This issue is related to runtime services, e.g. sessions, memory, artifacts, etc

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions