Skip to content

InMemoryArtifactService ignores the "user:" namespace convention — user-scoped artifacts are wrongly session-scoped #1460

Description

@Ashfaqbs

Bug

InMemoryArtifactService does not implement the user:-prefixed filename convention that the rest of the artifact-service contract relies on: a filename starting with user: is meant to be stored once per (appName, userId) and be visible from every session for that user, not just the session that saved it.

Both other implementations in this repo already do this correctly:

  • GcsArtifactService.getBlobPrefix() stores user:-prefixed filenames under appName/userId/user/filename/..., independent of sessionId.
  • adk-python's InMemoryArtifactService._artifact_path() does the same ({app_name}/{user_id}/user/{filename}), and list_artifact_keys unions the session-scoped and user-namespaced prefixes.

InMemoryArtifactService.java, however, keys everything by (appName, userId, sessionId, filename) unconditionally:

private Map<String, List<Part>> getArtifactsMap(String appName, String userId, String sessionId) {
  return artifacts
      .computeIfAbsent(appName, unused -> new HashMap<>())
      .computeIfAbsent(userId, unused -> new HashMap<>())
      .computeIfAbsent(sessionId, unused -> new HashMap<>());
}

Repro

InMemoryArtifactService service = new InMemoryArtifactService();
service.saveArtifact("app", "user1", "session-a", "user:profile.txt", artifact).blockingGet();

// Same user, different session — should see the same user-namespaced artifact.
Optional<Part> result = service.loadArtifact("app", "user1", "session-b", "user:profile.txt")
    .map(Optional::of).defaultIfEmpty(Optional.empty()).blockingGet();

// Actual: result is empty. Expected: result contains the artifact saved from session-a.

The same gap applies to listArtifactKeys (a user:-namespaced artifact saved elsewhere never appears) and deleteArtifact/listVersions (they silently no-op / return empty against the wrong session-scoped key instead of touching the real user-scoped entry).

Impact

Any application relying on InMemoryArtifactService (the default in tests, samples, and quick local runs) for cross-session, per-user artifacts (e.g. a user profile or preference file meant to persist across conversations) silently loses that data the moment a new session starts — with no error, since the tool call still succeeds, it just operates on an empty/wrong scope.

Fix

I have a fix + regression tests ready (keys the in-memory map the same way GcsArtifactService keys GCS blobs, and unions the session/user prefixes in listArtifactKeys) and will open a PR shortly.

Activity

  1. self-assigned this
    on Aug 27, 2026
  2. added theissue type on Aug 27, 2026
  3. hemasekhar-p commented on Aug 27, 2026

    @hemasekhar-p
    Contributor

    Hi @Ashfaqbs,Thank you for flagging this issue and putting together a PR to address it. I am able to reproduce this issue from my end and currently this Issue and PR are under review by our team. We will reach out if we need any more information. Thank you.

  4. sherryfox commented on Aug 27, 2026

    @sherryfox
    Contributor

    Thanks @Ashfaqbs — real bug, and #1460 nails the diagnosis.

    This issue was recently addressed and merged via #1449, which had been open since Aug 23. Both solutions are slightly different: yours mirrors adk-python's flat _artifact_path() → {app}/{user}/user/{filename}, while #1449 mirrors adk-go's sessionID = userScopedArtifactKey substitution (artifact/inmemory.go:54). Both are defensible ports, and we appreciate the effort you put into this.

    Since the fix has landed, I am going to close this issue. Could you pull main and check that the merged fix actually covers your cases? You found this, so you're the right person to confirm it's genuinely resolved. If it doesn't cover your case, please let me know or reopen the issue and we will investigate further.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions