diff --git a/samples/AgentStudioMemoryStores.java b/samples/AgentStudioMemoryStores.java
new file mode 100644
index 00000000..caf2ea63
--- /dev/null
+++ b/samples/AgentStudioMemoryStores.java
@@ -0,0 +1,94 @@
+import com.alibaba.dashscope.agentstudio.AgentStudioClient;
+import com.alibaba.dashscope.agentstudio.model.AgentStudioDeletionStatus;
+import com.alibaba.dashscope.agentstudio.model.Memory;
+import com.alibaba.dashscope.agentstudio.model.MemoryStore;
+import com.alibaba.dashscope.agentstudio.model.MemoryVersion;
+import com.alibaba.dashscope.agentstudio.param.MemoryCreateParam;
+import com.alibaba.dashscope.agentstudio.param.MemoryListParam;
+import com.alibaba.dashscope.agentstudio.param.MemoryStoreCreateParam;
+import com.alibaba.dashscope.agentstudio.param.MemoryStoreListParam;
+import com.alibaba.dashscope.agentstudio.param.MemoryUpdateParam;
+import com.alibaba.dashscope.agentstudio.param.MemoryVersionListParam;
+
+/**
+ * Memory store example: seed a store, read it back, audit versions, cleanup.
+ *
+ *
Set DASHSCOPE_API_KEY and DASHSCOPE_WORKSPACE before running.
+ */
+public class AgentStudioMemoryStores {
+
+ public static void main(String[] args) {
+ try (AgentStudioClient client = new AgentStudioClient()) {
+ // 1. Create a store; description is read by the agent - write it for
+ // the model, not for humans.
+ MemoryStore store =
+ client
+ .memoryStores()
+ .create(
+ MemoryStoreCreateParam.builder()
+ .name("demo-preferences")
+ .description("Per-user preferences and project context.")
+ .build());
+ System.out.printf("store: %s%n", store.getId());
+
+ // 2. Seed reference material before any session runs.
+ Memory memory =
+ client
+ .memoryStores()
+ .memories()
+ .create(
+ store.getId(),
+ MemoryCreateParam.builder()
+ .path("/formatting_standards.md")
+ .content("All reports use GAAP formatting.")
+ .build());
+ System.out.printf(
+ "memory: %s %s (%d bytes)%n",
+ memory.getId(), memory.getPath(), memory.getContentSizeBytes());
+
+ // 3. Read back with content (view=full; the default basic is metadata
+ // only). Print prefixes before iterating: the loop auto-paginates
+ // and prefixes are per page.
+ com.alibaba.dashscope.agentstudio.pagination.CursorPage page =
+ client
+ .memoryStores()
+ .memories()
+ .list(store.getId(), MemoryListParam.builder().view("full").build());
+ System.out.printf("prefixes: %s%n", page.getPrefixes());
+ for (Memory item : page) {
+ System.out.printf("%s -> %s%n", item.getPath(), item.getContent());
+ }
+
+ // 4. Update with the optimistic lock (contentSha256 from step 2).
+ Memory updated =
+ client
+ .memoryStores()
+ .memories()
+ .update(
+ store.getId(),
+ memory.getId(),
+ MemoryUpdateParam.builder()
+ .content("All reports use GAAP formatting. ISO-8601 dates.")
+ .contentSha256(memory.getContentSha256())
+ .build());
+ System.out.printf("updated sha: %s%n", updated.getContentSha256());
+
+ // 5. Audit history: every mutation produced a memver_ snapshot.
+ for (MemoryVersion version :
+ client
+ .memoryStores()
+ .memoryVersions()
+ .list(store.getId(), MemoryVersionListParam.builder().build())) {
+ System.out.printf("version: %s %s%n", version.getId(), version.getOperation());
+ }
+
+ // 6. List stores and clean up.
+ for (MemoryStore item :
+ client.memoryStores().list(MemoryStoreListParam.builder().limit(20).build())) {
+ System.out.printf("store: %s %s%n", item.getId(), item.getName());
+ }
+ AgentStudioDeletionStatus deleted = client.memoryStores().delete(store.getId());
+ System.out.printf("deleted: %s%n", deleted.getType());
+ }
+ }
+}
diff --git a/src/main/java/com/alibaba/dashscope/agentstudio/AgentStudioClient.java b/src/main/java/com/alibaba/dashscope/agentstudio/AgentStudioClient.java
index 89e72ab5..2a1f84dc 100644
--- a/src/main/java/com/alibaba/dashscope/agentstudio/AgentStudioClient.java
+++ b/src/main/java/com/alibaba/dashscope/agentstudio/AgentStudioClient.java
@@ -6,6 +6,7 @@
import com.alibaba.dashscope.agentstudio.resource.Deployments;
import com.alibaba.dashscope.agentstudio.resource.Environments;
import com.alibaba.dashscope.agentstudio.resource.Files;
+import com.alibaba.dashscope.agentstudio.resource.MemoryStores;
import com.alibaba.dashscope.agentstudio.resource.Security;
import com.alibaba.dashscope.agentstudio.resource.Sessions;
import com.alibaba.dashscope.agentstudio.resource.Skills;
@@ -24,6 +25,7 @@ public class AgentStudioClient implements Closeable {
private final Environments environments;
private final Skills skills;
private final Vaults vaults;
+ private final MemoryStores memoryStores;
private final Files files;
private final WebhookEndpoints webhookEndpoints;
private final Security security;
@@ -56,6 +58,7 @@ public AgentStudioClient(
this.files = new Files(this.baseUrl, connectionOptions, apiKey);
this.skills = new Skills(this.baseUrl, connectionOptions, apiKey, this.files);
this.vaults = new Vaults(this.baseUrl, connectionOptions, apiKey);
+ this.memoryStores = new MemoryStores(this.baseUrl, connectionOptions, apiKey);
this.webhookEndpoints = new WebhookEndpoints(this.baseUrl, connectionOptions, apiKey);
this.security = new Security(this.baseUrl, connectionOptions, apiKey);
}
@@ -129,6 +132,10 @@ public Vaults vaults() {
return vaults;
}
+ public MemoryStores memoryStores() {
+ return memoryStores;
+ }
+
public Files files() {
return files;
}
diff --git a/src/main/java/com/alibaba/dashscope/agentstudio/model/Memory.java b/src/main/java/com/alibaba/dashscope/agentstudio/model/Memory.java
new file mode 100644
index 00000000..f5dd32db
--- /dev/null
+++ b/src/main/java/com/alibaba/dashscope/agentstudio/model/Memory.java
@@ -0,0 +1,49 @@
+// Copyright (c) Alibaba, Inc. and its affiliates.
+package com.alibaba.dashscope.agentstudio.model;
+
+import com.alibaba.dashscope.common.FlattenResultBase;
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * One text document inside a memory store, addressed by {@code path}.
+ *
+ * {@code content} appears only in retrieve responses and in list responses with {@code
+ * view="full"}; create and update never return it. {@code contentSha256} identifies the content
+ * revision — pass it back to {@code memories().update()} for optimistic concurrency (a mismatch
+ * returns 409 {@code memory_precondition_failed_error}).
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class Memory extends FlattenResultBase {
+ @SerializedName("id")
+ private String id;
+
+ @SerializedName("type")
+ private String type;
+
+ @SerializedName("memory_store_id")
+ private String memoryStoreId;
+
+ @SerializedName("path")
+ private String path;
+
+ @SerializedName("content")
+ private String content;
+
+ @SerializedName("content_sha256")
+ private String contentSha256;
+
+ @SerializedName("content_size_bytes")
+ private Long contentSizeBytes;
+
+ @SerializedName("memory_version_id")
+ private String memoryVersionId;
+
+ @SerializedName("created_at")
+ private String createdAt;
+
+ @SerializedName("updated_at")
+ private String updatedAt;
+}
diff --git a/src/main/java/com/alibaba/dashscope/agentstudio/model/MemoryStore.java b/src/main/java/com/alibaba/dashscope/agentstudio/model/MemoryStore.java
new file mode 100644
index 00000000..195737af
--- /dev/null
+++ b/src/main/java/com/alibaba/dashscope/agentstudio/model/MemoryStore.java
@@ -0,0 +1,45 @@
+// Copyright (c) Alibaba, Inc. and its affiliates.
+package com.alibaba.dashscope.agentstudio.model;
+
+import com.alibaba.dashscope.common.FlattenResultBase;
+import com.google.gson.annotations.SerializedName;
+import java.util.Map;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * A workspace-scoped memory store: a collection of small text documents that persists across
+ * sessions.
+ *
+ *
Stores are attached to sessions via the session {@code resources[]} array and mounted into the
+ * session container at {@code /mnt/memory//}. {@code archivedAt} is set once the store is
+ * archived: read-only afterwards, existing session attachments keep working, but new sessions
+ * cannot reference it.
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class MemoryStore extends FlattenResultBase {
+ @SerializedName("id")
+ private String id;
+
+ @SerializedName("type")
+ private String type;
+
+ @SerializedName("name")
+ private String name;
+
+ @SerializedName("description")
+ private String description;
+
+ @SerializedName("metadata")
+ private Map metadata;
+
+ @SerializedName("archived_at")
+ private String archivedAt;
+
+ @SerializedName("created_at")
+ private String createdAt;
+
+ @SerializedName("updated_at")
+ private String updatedAt;
+}
diff --git a/src/main/java/com/alibaba/dashscope/agentstudio/model/MemoryVersion.java b/src/main/java/com/alibaba/dashscope/agentstudio/model/MemoryVersion.java
new file mode 100644
index 00000000..f9df13d3
--- /dev/null
+++ b/src/main/java/com/alibaba/dashscope/agentstudio/model/MemoryVersion.java
@@ -0,0 +1,51 @@
+// Copyright (c) Alibaba, Inc. and its affiliates.
+package com.alibaba.dashscope.agentstudio.model;
+
+import com.alibaba.dashscope.common.FlattenResultBase;
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * Immutable snapshot produced by every memory mutation; versions are returned newest-first.
+ *
+ * {@code operation} is {@code created}, {@code modified} or {@code deleted}. After {@code
+ * redact()}, {@code path}, {@code content}, {@code contentSha256} and {@code contentSizeBytes} are
+ * cleared; the audit trail is preserved.
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class MemoryVersion extends FlattenResultBase {
+ @SerializedName("id")
+ private String id;
+
+ @SerializedName("type")
+ private String type;
+
+ @SerializedName("memory_store_id")
+ private String memoryStoreId;
+
+ @SerializedName("memory_id")
+ private String memoryId;
+
+ @SerializedName("operation")
+ private String operation;
+
+ @SerializedName("path")
+ private String path;
+
+ @SerializedName("content")
+ private String content;
+
+ @SerializedName("content_sha256")
+ private String contentSha256;
+
+ @SerializedName("content_size_bytes")
+ private Long contentSizeBytes;
+
+ @SerializedName("created_at")
+ private String createdAt;
+
+ @SerializedName("redacted_at")
+ private String redactedAt;
+}
diff --git a/src/main/java/com/alibaba/dashscope/agentstudio/pagination/CursorPage.java b/src/main/java/com/alibaba/dashscope/agentstudio/pagination/CursorPage.java
index 4e875ad7..818e6c75 100644
--- a/src/main/java/com/alibaba/dashscope/agentstudio/pagination/CursorPage.java
+++ b/src/main/java/com/alibaba/dashscope/agentstudio/pagination/CursorPage.java
@@ -20,6 +20,10 @@ public class CursorPage extends FlattenResultBase implements Iterable {
@SerializedName("next_page")
private String nextPage;
+ /** Directory prefixes of a tree-style listing (memory listings only, null elsewhere). */
+ @SerializedName("prefixes")
+ private List prefixes;
+
@EqualsAndHashCode.Exclude
private transient Function>> fetchNext;
diff --git a/src/main/java/com/alibaba/dashscope/agentstudio/param/MemoryCreateParam.java b/src/main/java/com/alibaba/dashscope/agentstudio/param/MemoryCreateParam.java
new file mode 100644
index 00000000..6b02f222
--- /dev/null
+++ b/src/main/java/com/alibaba/dashscope/agentstudio/param/MemoryCreateParam.java
@@ -0,0 +1,35 @@
+// Copyright (c) Alibaba, Inc. and its affiliates.
+package com.alibaba.dashscope.agentstudio.param;
+
+import com.alibaba.dashscope.base.FlattenHalfDuplexParamBase;
+import com.google.gson.JsonObject;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NonNull;
+import lombok.experimental.SuperBuilder;
+
+/**
+ * Request body for {@code POST /memory_stores/{store_id}/memories}.
+ *
+ * {@code path} rules (enforced server-side, case-sensitive): POSIX style with a leading {@code
+ * /} and no trailing {@code /}; 2..1024 UTF-8 bytes; NFC-normalized; every segment non-empty and
+ * not {@code .} or {@code ..}; no control / format / line- or paragraph-separator code points.
+ * {@code content} is UTF-8 text, at most 102400 bytes. A memory already existing at {@code path}
+ * returns 409 {@code memory_path_conflict_error}.
+ */
+@Data
+@SuperBuilder
+@EqualsAndHashCode(callSuper = true)
+public class MemoryCreateParam extends FlattenHalfDuplexParamBase {
+ @NonNull private String path;
+ @NonNull private String content;
+
+ @Override
+ public JsonObject getHttpBody() {
+ JsonObject body = new JsonObject();
+ body.addProperty("path", path);
+ body.addProperty("content", content);
+ addExtraBody(body);
+ return body;
+ }
+}
diff --git a/src/main/java/com/alibaba/dashscope/agentstudio/param/MemoryListParam.java b/src/main/java/com/alibaba/dashscope/agentstudio/param/MemoryListParam.java
new file mode 100644
index 00000000..e5fc6fb2
--- /dev/null
+++ b/src/main/java/com/alibaba/dashscope/agentstudio/param/MemoryListParam.java
@@ -0,0 +1,48 @@
+// Copyright (c) Alibaba, Inc. and its affiliates.
+package com.alibaba.dashscope.agentstudio.param;
+
+import com.alibaba.dashscope.agentstudio.AgentStudioConstants;
+import com.alibaba.dashscope.base.FlattenHalfDuplexParamBase;
+import com.google.gson.JsonObject;
+import lombok.Builder.Default;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.experimental.SuperBuilder;
+
+/**
+ * Query params for {@code GET /memory_stores/{store_id}/memories}.
+ *
+ *
{@code pathPrefix} scopes the listing (server-validated: must start and end with {@code /} —
+ * {@code "/notes/"} matches {@code /notes/a.md} but not {@code /notes_backup/old.md}; a bare {@code
+ * "/"} lists the whole store). {@code view} is {@code basic} (metadata only, the default; {@code
+ * limit} up to 100) or {@code full} (include {@code content}; {@code limit} is capped at 20 and
+ * higher values are rejected with 400 — CMA silently clamps instead). {@code depth} is 0/absent
+ * (flat) or 1 (single-level tree) on the wire; larger values are derived client-side from a flat
+ * listing (no {@code depth} is sent). With {@code view=full}, the wire still transfers the content
+ * of files the derivation cuts — prefer {@code basic} for deep walks.
+ */
+@Data
+@SuperBuilder
+@EqualsAndHashCode(callSuper = true)
+public class MemoryListParam extends FlattenHalfDuplexParamBase {
+ @Default private String pathPrefix = null;
+ @Default private Integer depth = null;
+ @Default private String view = null;
+ @Default private Integer limit = null;
+ @Default private String page = null;
+
+ @Override
+ public JsonObject getHttpBody() {
+ return new JsonObject();
+ }
+
+ public String toQueryString() {
+ StringBuilder sb = new StringBuilder();
+ AgentStudioConstants.appendParam(sb, "path_prefix", pathPrefix);
+ AgentStudioConstants.appendParam(sb, "depth", depth);
+ AgentStudioConstants.appendParam(sb, "view", view);
+ AgentStudioConstants.appendParam(sb, "limit", limit);
+ AgentStudioConstants.appendParam(sb, "page", page);
+ return sb.toString();
+ }
+}
diff --git a/src/main/java/com/alibaba/dashscope/agentstudio/param/MemoryStoreCreateParam.java b/src/main/java/com/alibaba/dashscope/agentstudio/param/MemoryStoreCreateParam.java
new file mode 100644
index 00000000..7f01eb07
--- /dev/null
+++ b/src/main/java/com/alibaba/dashscope/agentstudio/param/MemoryStoreCreateParam.java
@@ -0,0 +1,42 @@
+// Copyright (c) Alibaba, Inc. and its affiliates.
+package com.alibaba.dashscope.agentstudio.param;
+
+import com.alibaba.dashscope.base.FlattenHalfDuplexParamBase;
+import com.alibaba.dashscope.utils.JsonUtils;
+import com.google.gson.JsonObject;
+import java.util.Map;
+import lombok.Builder.Default;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NonNull;
+import lombok.experimental.SuperBuilder;
+
+/**
+ * Request body for {@code POST /memory_stores}.
+ *
+ *
{@code name} must contain 1..255 characters; names may repeat within a workspace, and the
+ * session mount slug {@code /mnt/memory//} is derived from it. {@code description} is passed
+ * to the agent — write it for the model, not for humans.
+ */
+@Data
+@SuperBuilder
+@EqualsAndHashCode(callSuper = true)
+public class MemoryStoreCreateParam extends FlattenHalfDuplexParamBase {
+ @NonNull private String name;
+ @Default private String description = null;
+ @Default private Map metadata = null;
+
+ @Override
+ public JsonObject getHttpBody() {
+ JsonObject body = new JsonObject();
+ body.addProperty("name", name);
+ if (description != null) {
+ body.addProperty("description", description);
+ }
+ if (metadata != null) {
+ body.add("metadata", JsonUtils.toJsonElement(metadata));
+ }
+ addExtraBody(body);
+ return body;
+ }
+}
diff --git a/src/main/java/com/alibaba/dashscope/agentstudio/param/MemoryStoreListParam.java b/src/main/java/com/alibaba/dashscope/agentstudio/param/MemoryStoreListParam.java
new file mode 100644
index 00000000..3c670d53
--- /dev/null
+++ b/src/main/java/com/alibaba/dashscope/agentstudio/param/MemoryStoreListParam.java
@@ -0,0 +1,37 @@
+// Copyright (c) Alibaba, Inc. and its affiliates.
+package com.alibaba.dashscope.agentstudio.param;
+
+import com.alibaba.dashscope.agentstudio.AgentStudioConstants;
+import com.alibaba.dashscope.base.FlattenHalfDuplexParamBase;
+import com.google.gson.JsonObject;
+import lombok.Builder.Default;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.experimental.SuperBuilder;
+
+/**
+ * Query params for {@code GET /memory_stores}.
+ *
+ * The public API has no {@code keyword} filter (that is a console-only capability).
+ */
+@Data
+@SuperBuilder
+@EqualsAndHashCode(callSuper = true)
+public class MemoryStoreListParam extends FlattenHalfDuplexParamBase {
+ @Default private Integer limit = null;
+ @Default private String page = null;
+ @Default private Boolean includeArchived = null;
+
+ @Override
+ public JsonObject getHttpBody() {
+ return new JsonObject();
+ }
+
+ public String toQueryString() {
+ StringBuilder sb = new StringBuilder();
+ AgentStudioConstants.appendParam(sb, "limit", limit);
+ AgentStudioConstants.appendParam(sb, "page", page);
+ AgentStudioConstants.appendParam(sb, "include_archived", includeArchived);
+ return sb.toString();
+ }
+}
diff --git a/src/main/java/com/alibaba/dashscope/agentstudio/param/MemoryStoreUpdateParam.java b/src/main/java/com/alibaba/dashscope/agentstudio/param/MemoryStoreUpdateParam.java
new file mode 100644
index 00000000..71fe4795
--- /dev/null
+++ b/src/main/java/com/alibaba/dashscope/agentstudio/param/MemoryStoreUpdateParam.java
@@ -0,0 +1,42 @@
+// Copyright (c) Alibaba, Inc. and its affiliates.
+package com.alibaba.dashscope.agentstudio.param;
+
+import com.alibaba.dashscope.base.FlattenHalfDuplexParamBase;
+import com.alibaba.dashscope.utils.JsonUtils;
+import com.google.gson.JsonObject;
+import java.util.Map;
+import lombok.Builder.Default;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.experimental.SuperBuilder;
+
+/**
+ * Request body for {@code POST /memory_stores/{store_id}}.
+ *
+ *
{@code metadata} is replaced wholesale (unlike CMA's merge-patch): keys not included here are
+ * dropped; a key set to {@code null} deletes it. To change one key, re-send the whole mapping.
+ */
+@Data
+@SuperBuilder
+@EqualsAndHashCode(callSuper = true)
+public class MemoryStoreUpdateParam extends FlattenHalfDuplexParamBase {
+ @Default private String name = null;
+ @Default private String description = null;
+ @Default private Map metadata = null;
+
+ @Override
+ public JsonObject getHttpBody() {
+ JsonObject body = new JsonObject();
+ if (name != null) {
+ body.addProperty("name", name);
+ }
+ if (description != null) {
+ body.addProperty("description", description);
+ }
+ if (metadata != null) {
+ body.add("metadata", JsonUtils.toJsonElement(metadata));
+ }
+ addExtraBody(body);
+ return body;
+ }
+}
diff --git a/src/main/java/com/alibaba/dashscope/agentstudio/param/MemoryUpdateParam.java b/src/main/java/com/alibaba/dashscope/agentstudio/param/MemoryUpdateParam.java
new file mode 100644
index 00000000..1967f8cc
--- /dev/null
+++ b/src/main/java/com/alibaba/dashscope/agentstudio/param/MemoryUpdateParam.java
@@ -0,0 +1,41 @@
+// Copyright (c) Alibaba, Inc. and its affiliates.
+package com.alibaba.dashscope.agentstudio.param;
+
+import com.alibaba.dashscope.base.FlattenHalfDuplexParamBase;
+import com.google.gson.JsonObject;
+import lombok.Builder.Default;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.experimental.SuperBuilder;
+
+/**
+ * Request body for {@code POST /memory_stores/{store_id}/memories/{memory_id}}.
+ *
+ * Mutate {@code content}, rename via {@code path}, or both (at least one is required
+ * server-side). Passing the current {@code contentSha256} (from the retrieved memory) enables
+ * optimistic concurrency — a mismatch returns 409 {@code memory_precondition_failed_error}.
+ */
+@Data
+@SuperBuilder
+@EqualsAndHashCode(callSuper = true)
+public class MemoryUpdateParam extends FlattenHalfDuplexParamBase {
+ @Default private String path = null;
+ @Default private String content = null;
+ @Default private String contentSha256 = null;
+
+ @Override
+ public JsonObject getHttpBody() {
+ JsonObject body = new JsonObject();
+ if (path != null) {
+ body.addProperty("path", path);
+ }
+ if (content != null) {
+ body.addProperty("content", content);
+ }
+ if (contentSha256 != null) {
+ body.addProperty("content_sha256", contentSha256);
+ }
+ addExtraBody(body);
+ return body;
+ }
+}
diff --git a/src/main/java/com/alibaba/dashscope/agentstudio/param/MemoryVersionListParam.java b/src/main/java/com/alibaba/dashscope/agentstudio/param/MemoryVersionListParam.java
new file mode 100644
index 00000000..67aecf52
--- /dev/null
+++ b/src/main/java/com/alibaba/dashscope/agentstudio/param/MemoryVersionListParam.java
@@ -0,0 +1,48 @@
+// Copyright (c) Alibaba, Inc. and its affiliates.
+package com.alibaba.dashscope.agentstudio.param;
+
+import com.alibaba.dashscope.agentstudio.AgentStudioConstants;
+import com.alibaba.dashscope.base.FlattenHalfDuplexParamBase;
+import com.google.gson.JsonObject;
+import lombok.Builder.Default;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.experimental.SuperBuilder;
+
+/**
+ * Query params for {@code GET /memory_stores/{store_id}/memory_versions}.
+ *
+ *
Filters: {@code memoryId}, {@code operation} ({@code created} / {@code modified} / {@code
+ * deleted}), {@code createdAtGte} / {@code createdAtLte} (ISO-8601 timestamps; sent on the wire as
+ * {@code created_at.gte} / {@code created_at.lte}), and {@code view} ({@code basic} / {@code
+ * full}). Versions are returned newest-first.
+ */
+@Data
+@SuperBuilder
+@EqualsAndHashCode(callSuper = true)
+public class MemoryVersionListParam extends FlattenHalfDuplexParamBase {
+ @Default private String memoryId = null;
+ @Default private String operation = null;
+ @Default private String createdAtGte = null;
+ @Default private String createdAtLte = null;
+ @Default private String view = null;
+ @Default private Integer limit = null;
+ @Default private String page = null;
+
+ @Override
+ public JsonObject getHttpBody() {
+ return new JsonObject();
+ }
+
+ public String toQueryString() {
+ StringBuilder sb = new StringBuilder();
+ AgentStudioConstants.appendParam(sb, "memory_id", memoryId);
+ AgentStudioConstants.appendParam(sb, "operation", operation);
+ AgentStudioConstants.appendParam(sb, "created_at.gte", createdAtGte);
+ AgentStudioConstants.appendParam(sb, "created_at.lte", createdAtLte);
+ AgentStudioConstants.appendParam(sb, "view", view);
+ AgentStudioConstants.appendParam(sb, "limit", limit);
+ AgentStudioConstants.appendParam(sb, "page", page);
+ return sb.toString();
+ }
+}
diff --git a/src/main/java/com/alibaba/dashscope/agentstudio/resource/MemoryStores.java b/src/main/java/com/alibaba/dashscope/agentstudio/resource/MemoryStores.java
new file mode 100644
index 00000000..bf7fd838
--- /dev/null
+++ b/src/main/java/com/alibaba/dashscope/agentstudio/resource/MemoryStores.java
@@ -0,0 +1,536 @@
+// Copyright (c) Alibaba, Inc. and its affiliates.
+package com.alibaba.dashscope.agentstudio.resource;
+
+import com.alibaba.dashscope.agentstudio.AgentStudioConstants;
+import com.alibaba.dashscope.agentstudio.model.AgentStudioDeletionStatus;
+import com.alibaba.dashscope.agentstudio.model.Memory;
+import com.alibaba.dashscope.agentstudio.model.MemoryStore;
+import com.alibaba.dashscope.agentstudio.model.MemoryVersion;
+import com.alibaba.dashscope.agentstudio.pagination.CursorPage;
+import com.alibaba.dashscope.agentstudio.param.MemoryCreateParam;
+import com.alibaba.dashscope.agentstudio.param.MemoryListParam;
+import com.alibaba.dashscope.agentstudio.param.MemoryStoreCreateParam;
+import com.alibaba.dashscope.agentstudio.param.MemoryStoreListParam;
+import com.alibaba.dashscope.agentstudio.param.MemoryStoreUpdateParam;
+import com.alibaba.dashscope.agentstudio.param.MemoryUpdateParam;
+import com.alibaba.dashscope.agentstudio.param.MemoryVersionListParam;
+import com.alibaba.dashscope.api.GeneralApi;
+import com.alibaba.dashscope.base.HalfDuplexParamBase;
+import com.alibaba.dashscope.common.FlattenResultBase;
+import com.alibaba.dashscope.common.GeneralGetParam;
+import com.alibaba.dashscope.exception.InputRequiredException;
+import com.alibaba.dashscope.protocol.ConnectionOptions;
+import com.alibaba.dashscope.protocol.GeneralServiceOption;
+import com.alibaba.dashscope.protocol.HttpMethod;
+import com.alibaba.dashscope.utils.StringUtils;
+import com.google.gson.reflect.TypeToken;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeSet;
+import java.util.concurrent.CompletableFuture;
+
+/**
+ * Memory stores: workspace-scoped collections of small text documents that persist across sessions.
+ * Stores hold {@code memories} ({@code mem_...}, one text file per path) and their {@code
+ * memory_versions} ({@code memver_...}, immutable snapshots per mutation).
+ *
+ *
Follows the Claude Managed Agents {@code client.beta.memoryStores()} surface, with these
+ * verified divergences (the backend is bailian-app-control {@code MemoryStoreController} over
+ * bailian-app-context OTS storage):
+ *
+ *
+ * - updates use POST; the optimistic-lock hash rides the top-level {@code content_sha256} body
+ * field (CMA uses PATCH with a {@code precondition} object);
+ *
- store {@code metadata} on update is replaced wholesale; CMA merge-patches it;
+ *
- {@code view="full"} caps {@code limit} at 20 and rejects higher values with 400; CMA
+ * silently clamps;
+ *
- directory nodes come back as a separate {@code prefixes} page field ({@code
+ * page.getPrefixes()}) instead of inline {@code memory_prefix} items in {@code data};
+ *
- depth accepts 0/1 natively; larger values are derived client-side (see {@link
+ * #MAX_WIRE_DEPTH});
+ *
- create and update responses never include {@code content};
+ *
- no store-list time filters, no conditional memory delete, no version attribution fields or
+ * filters — CMA has all of them.
+ *
+ */
+public final class MemoryStores {
+ // The backend accepts only depth null/0 (flat) or 1 (single-level tree);
+ // anything else returns 400 (CMA likewise defines only 0/1). depth >= 2 is
+ // this SDK's own generalization over the flat listing.
+ static final int MAX_WIRE_DEPTH = 1;
+
+ private final GeneralApi api;
+ private final String baseUrl;
+ private final String apiKey;
+ private final Memories memories;
+ private final MemoryVersions memoryVersions;
+
+ public MemoryStores(String baseUrl, ConnectionOptions connectionOptions, String apiKey) {
+ this.baseUrl = baseUrl;
+ this.apiKey = apiKey;
+ this.api = connectionOptions != null ? new GeneralApi<>(connectionOptions) : new GeneralApi<>();
+ this.memories = new Memories();
+ this.memoryVersions = new MemoryVersions();
+ }
+
+ public Memories memories() {
+ return memories;
+ }
+
+ public MemoryVersions memoryVersions() {
+ return memoryVersions;
+ }
+
+ public MemoryStore create(MemoryStoreCreateParam param) {
+ return AsyncHelper.joinAndUnwrap(createAsync(param));
+ }
+
+ public MemoryStore retrieve(String storeId) {
+ return retrieve(storeId, null, null);
+ }
+
+ public MemoryStore retrieve(String storeId, String apiKey, Map headers) {
+ return AsyncHelper.joinAndUnwrap(retrieveAsync(storeId, apiKey, headers));
+ }
+
+ public MemoryStore update(String storeId, MemoryStoreUpdateParam param) {
+ return AsyncHelper.joinAndUnwrap(updateAsync(storeId, param));
+ }
+
+ public CursorPage list(MemoryStoreListParam param) {
+ return AsyncHelper.joinAndUnwrap(listAsync(param));
+ }
+
+ public MemoryStore archive(String storeId) {
+ return AsyncHelper.joinAndUnwrap(archiveAsync(storeId));
+ }
+
+ public AgentStudioDeletionStatus delete(String storeId) {
+ return delete(storeId, null, null);
+ }
+
+ public AgentStudioDeletionStatus delete(
+ String storeId, String apiKey, Map headers) {
+ return AsyncHelper.joinAndUnwrap(deleteAsync(storeId, apiKey, headers));
+ }
+
+ public CompletableFuture createAsync(MemoryStoreCreateParam param) {
+ if (param == null) {
+ return AsyncHelper.failedFuture(new InputRequiredException("param is required!"));
+ }
+ GeneralServiceOption opt =
+ AgentStudioConstants.newServiceOption(HttpMethod.POST, "memory_stores", baseUrl);
+ return AsyncHelper.callAsync(api, AgentStudioConstants.withApiKey(apiKey, param), opt)
+ .thenApply(r -> FlattenResultBase.fromDashScopeResult(r, MemoryStore.class));
+ }
+
+ public CompletableFuture retrieveAsync(String storeId) {
+ return retrieveAsync(storeId, null, null);
+ }
+
+ public CompletableFuture retrieveAsync(
+ String storeId, String apiKey, Map headers) {
+ if (storeId == null || storeId.isEmpty()) {
+ return AsyncHelper.failedFuture(new InputRequiredException("storeId is required!"));
+ }
+ GeneralServiceOption opt =
+ AgentStudioConstants.newServiceOption(
+ HttpMethod.GET, StringUtils.format("memory_stores/%s", storeId), baseUrl);
+ String resolvedKey = apiKey != null ? apiKey : this.apiKey;
+ return AsyncHelper.callAsync(
+ api,
+ GeneralGetParam.builder()
+ .apiKey(resolvedKey)
+ .headers(headers != null ? headers : new HashMap<>())
+ .build(),
+ opt)
+ .thenApply(r -> FlattenResultBase.fromDashScopeResult(r, MemoryStore.class));
+ }
+
+ public CompletableFuture updateAsync(String storeId, MemoryStoreUpdateParam param) {
+ if (storeId == null || storeId.isEmpty()) {
+ return AsyncHelper.failedFuture(new InputRequiredException("storeId is required!"));
+ }
+ if (param == null) {
+ return AsyncHelper.failedFuture(new InputRequiredException("param is required!"));
+ }
+ GeneralServiceOption opt =
+ AgentStudioConstants.newServiceOption(
+ HttpMethod.POST, StringUtils.format("memory_stores/%s", storeId), baseUrl);
+ return AsyncHelper.callAsync(api, AgentStudioConstants.withApiKey(apiKey, param), opt)
+ .thenApply(r -> FlattenResultBase.fromDashScopeResult(r, MemoryStore.class));
+ }
+
+ public CompletableFuture> listAsync(MemoryStoreListParam param) {
+ if (param == null) {
+ return AsyncHelper.failedFuture(new InputRequiredException("param is required!"));
+ }
+ String query = param.toQueryString();
+ String path = query.isEmpty() ? "memory_stores" : "memory_stores?" + query;
+ GeneralServiceOption opt = AgentStudioConstants.newServiceOption(HttpMethod.GET, path, baseUrl);
+ return AsyncHelper.callAsync(
+ api, GeneralGetParam.builder().apiKey(apiKey).headers(new HashMap<>()).build(), opt)
+ .thenApply(
+ r -> {
+ Type type = new TypeToken>() {}.getType();
+ CursorPage page = FlattenResultBase.fromDashScopeResult(r, type);
+ page.setFetchNext(
+ cursor ->
+ listAsync(
+ MemoryStoreListParam.builder()
+ .limit(param.getLimit())
+ .includeArchived(param.getIncludeArchived())
+ .page(cursor)
+ .build()));
+ return page;
+ });
+ }
+
+ public CompletableFuture archiveAsync(String storeId) {
+ if (storeId == null || storeId.isEmpty()) {
+ return AsyncHelper.failedFuture(new InputRequiredException("storeId is required!"));
+ }
+ GeneralServiceOption opt =
+ AgentStudioConstants.newServiceOption(
+ HttpMethod.POST, StringUtils.format("memory_stores/%s/archive", storeId), baseUrl);
+ return AsyncHelper.callAsync(
+ api,
+ AgentStudioConstants.withApiKey(apiKey, MemoryStoreUpdateParam.builder().build()),
+ opt)
+ .thenApply(r -> FlattenResultBase.fromDashScopeResult(r, MemoryStore.class));
+ }
+
+ public CompletableFuture deleteAsync(String storeId) {
+ return deleteAsync(storeId, null, null);
+ }
+
+ public CompletableFuture deleteAsync(
+ String storeId, String apiKey, Map headers) {
+ if (storeId == null || storeId.isEmpty()) {
+ return AsyncHelper.failedFuture(new InputRequiredException("storeId is required!"));
+ }
+ GeneralServiceOption opt =
+ AgentStudioConstants.newServiceOption(
+ HttpMethod.DELETE, StringUtils.format("memory_stores/%s", storeId), baseUrl);
+ String resolvedKey = apiKey != null ? apiKey : this.apiKey;
+ return AsyncHelper.callAsync(
+ api,
+ GeneralGetParam.builder()
+ .apiKey(resolvedKey)
+ .headers(headers != null ? headers : new HashMap<>())
+ .build(),
+ opt)
+ .thenApply(r -> FlattenResultBase.fromDashScopeResult(r, AgentStudioDeletionStatus.class));
+ }
+
+ /**
+ * Depth emulation: files more than {@code depth} segments below {@code pathPrefix} are dropped
+ * and directory prefixes are synthesized — files cut by the bound still contribute their frontier
+ * directory, matching the native {@code depth=1} behavior. Kept files and prefixes are both per
+ * page.
+ */
+ private static void filterByDepth(CursorPage page, int depth, String pathPrefix) {
+ String base = pathPrefix == null || pathPrefix.isEmpty() ? "/" : pathPrefix;
+ if (!base.endsWith("/")) {
+ base = base + "/";
+ }
+ List kept = new ArrayList<>();
+ TreeSet dirs = new TreeSet<>();
+ List data = page.getData() != null ? page.getData() : Collections.emptyList();
+ for (Memory memory : data) {
+ String path = memory.getPath() == null ? "" : memory.getPath();
+ String rel = path.startsWith(base) ? path.substring(base.length()) : path;
+ List segs = new ArrayList<>();
+ for (String segment : rel.split("/")) {
+ if (!segment.isEmpty()) {
+ segs.add(segment);
+ }
+ }
+ if (segs.size() <= depth) {
+ kept.add(memory);
+ }
+ for (int level = 1; level <= Math.min(segs.size() - 1, depth); level++) {
+ dirs.add(base + String.join("/", segs.subList(0, level)) + "/");
+ }
+ }
+ page.setData(kept);
+ page.setPrefixes(new ArrayList<>(dirs));
+ }
+
+ /** Memories (files) inside a memory store. */
+ public final class Memories {
+
+ public Memory create(String storeId, MemoryCreateParam param) {
+ return AsyncHelper.joinAndUnwrap(createAsync(storeId, param));
+ }
+
+ public Memory retrieve(String storeId, String memoryId) {
+ return retrieve(storeId, memoryId, null, null);
+ }
+
+ public Memory retrieve(
+ String storeId, String memoryId, String apiKey, Map headers) {
+ return AsyncHelper.joinAndUnwrap(retrieveAsync(storeId, memoryId, apiKey, headers));
+ }
+
+ public Memory update(String storeId, String memoryId, MemoryUpdateParam param) {
+ return AsyncHelper.joinAndUnwrap(updateAsync(storeId, memoryId, param));
+ }
+
+ public CursorPage list(String storeId, MemoryListParam param) {
+ return AsyncHelper.joinAndUnwrap(listAsync(storeId, param));
+ }
+
+ public AgentStudioDeletionStatus delete(String storeId, String memoryId) {
+ return delete(storeId, memoryId, null, null);
+ }
+
+ public AgentStudioDeletionStatus delete(
+ String storeId, String memoryId, String apiKey, Map headers) {
+ return AsyncHelper.joinAndUnwrap(deleteAsync(storeId, memoryId, apiKey, headers));
+ }
+
+ public CompletableFuture createAsync(String storeId, MemoryCreateParam param) {
+ if (storeId == null || storeId.isEmpty()) {
+ return AsyncHelper.failedFuture(new InputRequiredException("storeId is required!"));
+ }
+ if (param == null) {
+ return AsyncHelper.failedFuture(new InputRequiredException("param is required!"));
+ }
+ GeneralServiceOption opt =
+ AgentStudioConstants.newServiceOption(
+ HttpMethod.POST, StringUtils.format("memory_stores/%s/memories", storeId), baseUrl);
+ return AsyncHelper.callAsync(api, AgentStudioConstants.withApiKey(apiKey, param), opt)
+ .thenApply(r -> FlattenResultBase.fromDashScopeResult(r, Memory.class));
+ }
+
+ public CompletableFuture retrieveAsync(String storeId, String memoryId) {
+ return retrieveAsync(storeId, memoryId, null, null);
+ }
+
+ public CompletableFuture retrieveAsync(
+ String storeId, String memoryId, String apiKey, Map headers) {
+ if (storeId == null || storeId.isEmpty() || memoryId == null || memoryId.isEmpty()) {
+ return AsyncHelper.failedFuture(
+ new InputRequiredException("storeId and memoryId are required!"));
+ }
+ GeneralServiceOption opt =
+ AgentStudioConstants.newServiceOption(
+ HttpMethod.GET,
+ StringUtils.format("memory_stores/%s/memories/%s", storeId, memoryId),
+ baseUrl);
+ String resolvedKey = apiKey != null ? apiKey : MemoryStores.this.apiKey;
+ return AsyncHelper.callAsync(
+ api,
+ GeneralGetParam.builder()
+ .apiKey(resolvedKey)
+ .headers(headers != null ? headers : new HashMap<>())
+ .build(),
+ opt)
+ .thenApply(r -> FlattenResultBase.fromDashScopeResult(r, Memory.class));
+ }
+
+ public CompletableFuture updateAsync(
+ String storeId, String memoryId, MemoryUpdateParam param) {
+ if (storeId == null || storeId.isEmpty() || memoryId == null || memoryId.isEmpty()) {
+ return AsyncHelper.failedFuture(
+ new InputRequiredException("storeId and memoryId are required!"));
+ }
+ if (param == null) {
+ return AsyncHelper.failedFuture(new InputRequiredException("param is required!"));
+ }
+ GeneralServiceOption opt =
+ AgentStudioConstants.newServiceOption(
+ HttpMethod.POST,
+ StringUtils.format("memory_stores/%s/memories/%s", storeId, memoryId),
+ baseUrl);
+ return AsyncHelper.callAsync(api, AgentStudioConstants.withApiKey(apiKey, param), opt)
+ .thenApply(r -> FlattenResultBase.fromDashScopeResult(r, Memory.class));
+ }
+
+ public CompletableFuture> listAsync(String storeId, MemoryListParam param) {
+ if (storeId == null || storeId.isEmpty()) {
+ return AsyncHelper.failedFuture(new InputRequiredException("storeId is required!"));
+ }
+ if (param == null) {
+ return AsyncHelper.failedFuture(new InputRequiredException("param is required!"));
+ }
+ boolean depthEmulated = param.getDepth() != null && param.getDepth() > MAX_WIRE_DEPTH;
+ // depth >= 2 must not reach the wire; everything else passes through.
+ MemoryListParam wireParam = param;
+ if (depthEmulated) {
+ wireParam =
+ MemoryListParam.builder()
+ .pathPrefix(param.getPathPrefix())
+ .view(param.getView())
+ .limit(param.getLimit())
+ .page(param.getPage())
+ .build();
+ }
+ String query = wireParam.toQueryString();
+ String path = StringUtils.format("memory_stores/%s/memories", storeId);
+ if (!query.isEmpty()) {
+ path = path + "?" + query;
+ }
+ GeneralServiceOption opt =
+ AgentStudioConstants.newServiceOption(HttpMethod.GET, path, baseUrl);
+ return AsyncHelper.callAsync(
+ api, GeneralGetParam.builder().apiKey(apiKey).headers(new HashMap<>()).build(), opt)
+ .thenApply(
+ r -> {
+ Type type = new TypeToken>() {}.getType();
+ CursorPage page = FlattenResultBase.fromDashScopeResult(r, type);
+ page.setFetchNext(
+ cursor ->
+ listAsync(
+ storeId,
+ MemoryListParam.builder()
+ .pathPrefix(param.getPathPrefix())
+ .depth(param.getDepth())
+ .view(param.getView())
+ .limit(param.getLimit())
+ .page(cursor)
+ .build()));
+ if (depthEmulated) {
+ filterByDepth(page, param.getDepth(), param.getPathPrefix());
+ }
+ return page;
+ });
+ }
+
+ public CompletableFuture deleteAsync(
+ String storeId, String memoryId) {
+ return deleteAsync(storeId, memoryId, null, null);
+ }
+
+ public CompletableFuture deleteAsync(
+ String storeId, String memoryId, String apiKey, Map headers) {
+ if (storeId == null || storeId.isEmpty() || memoryId == null || memoryId.isEmpty()) {
+ return AsyncHelper.failedFuture(
+ new InputRequiredException("storeId and memoryId are required!"));
+ }
+ GeneralServiceOption opt =
+ AgentStudioConstants.newServiceOption(
+ HttpMethod.DELETE,
+ StringUtils.format("memory_stores/%s/memories/%s", storeId, memoryId),
+ baseUrl);
+ String resolvedKey = apiKey != null ? apiKey : MemoryStores.this.apiKey;
+ return AsyncHelper.callAsync(
+ api,
+ GeneralGetParam.builder()
+ .apiKey(resolvedKey)
+ .headers(headers != null ? headers : new HashMap<>())
+ .build(),
+ opt)
+ .thenApply(
+ r -> FlattenResultBase.fromDashScopeResult(r, AgentStudioDeletionStatus.class));
+ }
+ }
+
+ /** Immutable version snapshots of the memories in a store. */
+ public final class MemoryVersions {
+
+ public CursorPage list(String storeId, MemoryVersionListParam param) {
+ return AsyncHelper.joinAndUnwrap(listAsync(storeId, param));
+ }
+
+ public MemoryVersion retrieve(String storeId, String versionId) {
+ return retrieve(storeId, versionId, null, null);
+ }
+
+ public MemoryVersion retrieve(
+ String storeId, String versionId, String apiKey, Map headers) {
+ return AsyncHelper.joinAndUnwrap(retrieveAsync(storeId, versionId, apiKey, headers));
+ }
+
+ public MemoryVersion redact(String storeId, String versionId) {
+ return AsyncHelper.joinAndUnwrap(redactAsync(storeId, versionId));
+ }
+
+ public CompletableFuture> listAsync(
+ String storeId, MemoryVersionListParam param) {
+ if (storeId == null || storeId.isEmpty()) {
+ return AsyncHelper.failedFuture(new InputRequiredException("storeId is required!"));
+ }
+ if (param == null) {
+ return AsyncHelper.failedFuture(new InputRequiredException("param is required!"));
+ }
+ String query = param.toQueryString();
+ String path = StringUtils.format("memory_stores/%s/memory_versions", storeId);
+ if (!query.isEmpty()) {
+ path = path + "?" + query;
+ }
+ GeneralServiceOption opt =
+ AgentStudioConstants.newServiceOption(HttpMethod.GET, path, baseUrl);
+ return AsyncHelper.callAsync(
+ api, GeneralGetParam.builder().apiKey(apiKey).headers(new HashMap<>()).build(), opt)
+ .thenApply(
+ r -> {
+ Type type = new TypeToken>() {}.getType();
+ CursorPage page = FlattenResultBase.fromDashScopeResult(r, type);
+ page.setFetchNext(
+ cursor ->
+ listAsync(
+ storeId,
+ MemoryVersionListParam.builder()
+ .memoryId(param.getMemoryId())
+ .operation(param.getOperation())
+ .createdAtGte(param.getCreatedAtGte())
+ .createdAtLte(param.getCreatedAtLte())
+ .view(param.getView())
+ .limit(param.getLimit())
+ .page(cursor)
+ .build()));
+ return page;
+ });
+ }
+
+ public CompletableFuture retrieveAsync(String storeId, String versionId) {
+ return retrieveAsync(storeId, versionId, null, null);
+ }
+
+ public CompletableFuture retrieveAsync(
+ String storeId, String versionId, String apiKey, Map headers) {
+ if (storeId == null || storeId.isEmpty() || versionId == null || versionId.isEmpty()) {
+ return AsyncHelper.failedFuture(
+ new InputRequiredException("storeId and versionId are required!"));
+ }
+ GeneralServiceOption opt =
+ AgentStudioConstants.newServiceOption(
+ HttpMethod.GET,
+ StringUtils.format("memory_stores/%s/memory_versions/%s", storeId, versionId),
+ baseUrl);
+ String resolvedKey = apiKey != null ? apiKey : MemoryStores.this.apiKey;
+ return AsyncHelper.callAsync(
+ api,
+ GeneralGetParam.builder()
+ .apiKey(resolvedKey)
+ .headers(headers != null ? headers : new HashMap<>())
+ .build(),
+ opt)
+ .thenApply(r -> FlattenResultBase.fromDashScopeResult(r, MemoryVersion.class));
+ }
+
+ /** Redact a version: idempotent and irreversible. */
+ public CompletableFuture redactAsync(String storeId, String versionId) {
+ if (storeId == null || storeId.isEmpty() || versionId == null || versionId.isEmpty()) {
+ return AsyncHelper.failedFuture(
+ new InputRequiredException("storeId and versionId are required!"));
+ }
+ GeneralServiceOption opt =
+ AgentStudioConstants.newServiceOption(
+ HttpMethod.POST,
+ StringUtils.format("memory_stores/%s/memory_versions/%s/redact", storeId, versionId),
+ baseUrl);
+ return AsyncHelper.callAsync(
+ api,
+ AgentStudioConstants.withApiKey(apiKey, MemoryUpdateParam.builder().build()),
+ opt)
+ .thenApply(r -> FlattenResultBase.fromDashScopeResult(r, MemoryVersion.class));
+ }
+ }
+}
diff --git a/src/test/java/com/alibaba/dashscope/TestAgentStudioMemoryStores.java b/src/test/java/com/alibaba/dashscope/TestAgentStudioMemoryStores.java
new file mode 100644
index 00000000..4ca91a65
--- /dev/null
+++ b/src/test/java/com/alibaba/dashscope/TestAgentStudioMemoryStores.java
@@ -0,0 +1,361 @@
+package com.alibaba.dashscope;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import com.alibaba.dashscope.agentstudio.AgentStudioClient;
+import com.alibaba.dashscope.agentstudio.model.AgentStudioDeletionStatus;
+import com.alibaba.dashscope.agentstudio.model.Memory;
+import com.alibaba.dashscope.agentstudio.model.MemoryStore;
+import com.alibaba.dashscope.agentstudio.model.MemoryVersion;
+import com.alibaba.dashscope.agentstudio.pagination.CursorPage;
+import com.alibaba.dashscope.agentstudio.param.MemoryCreateParam;
+import com.alibaba.dashscope.agentstudio.param.MemoryListParam;
+import com.alibaba.dashscope.agentstudio.param.MemoryStoreCreateParam;
+import com.alibaba.dashscope.agentstudio.param.MemoryStoreListParam;
+import com.alibaba.dashscope.agentstudio.param.MemoryStoreUpdateParam;
+import com.alibaba.dashscope.agentstudio.param.MemoryUpdateParam;
+import com.alibaba.dashscope.agentstudio.param.MemoryVersionListParam;
+import com.alibaba.dashscope.exception.ApiException;
+import com.alibaba.dashscope.utils.JsonUtils;
+import com.google.gson.JsonObject;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.Collections;
+import okhttp3.mockwebserver.MockWebServer;
+import okhttp3.mockwebserver.RecordedRequest;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.parallel.Execution;
+import org.junit.jupiter.api.parallel.ExecutionMode;
+
+@Execution(ExecutionMode.SAME_THREAD)
+public class TestAgentStudioMemoryStores {
+ private static MockWebServer mockServer;
+ private static JsonObject fixtures;
+ private static String baseUrl;
+ private static AgentStudioClient client;
+
+ @BeforeAll
+ public static void before() throws IOException {
+ mockServer = new MockWebServer();
+ mockServer.start();
+ baseUrl = mockServer.url("/api/v1/agentstudio/").toString();
+ byte[] content = Files.readAllBytes(Paths.get("./src/test/resources/agentstudio.json"));
+ fixtures = JsonUtils.parse(new String(content, StandardCharsets.UTF_8));
+ client = new AgentStudioClient("test-key", baseUrl, "ws_01", null, null);
+ }
+
+ @AfterAll
+ public static void after() throws IOException {
+ mockServer.close();
+ }
+
+ private void enqueue(String fixtureKey) {
+ JsonObject response = fixtures.get(fixtureKey).getAsJsonObject();
+ mockServer.enqueue(TestUtils.createMockResponse(JsonUtils.toJson(response), 200));
+ }
+
+ @Test
+ public void testStoreCreatePostsBody() throws Exception {
+ enqueue("memory_store_response");
+ MemoryStore store =
+ client
+ .memoryStores()
+ .create(
+ MemoryStoreCreateParam.builder()
+ .name("User Preferences")
+ .description("Per-user preferences.")
+ .metadata(Collections.singletonMap("owner", "demo"))
+ .build());
+ RecordedRequest request = mockServer.takeRequest();
+ JsonObject body = JsonUtils.parse(request.getBody().readUtf8());
+ assertEquals("POST", request.getMethod());
+ assertTrue(request.getPath().endsWith("/memory_stores"));
+ assertEquals("User Preferences", body.get("name").getAsString());
+ assertEquals("Per-user preferences.", body.get("description").getAsString());
+ assertEquals("demo", body.getAsJsonObject("metadata").get("owner").getAsString());
+ assertEquals("memstore_01Hx", store.getId());
+ assertEquals("memory_store", store.getType());
+ assertNull(store.getArchivedAt());
+ }
+
+ @Test
+ public void testStoreUpdatePartialBody() throws Exception {
+ enqueue("memory_store_response");
+ client
+ .memoryStores()
+ .update("memstore_01Hx", MemoryStoreUpdateParam.builder().name("Renamed").build());
+ RecordedRequest request = mockServer.takeRequest();
+ JsonObject body = JsonUtils.parse(request.getBody().readUtf8());
+ assertEquals("POST", request.getMethod());
+ assertTrue(request.getPath().endsWith("/memory_stores/memstore_01Hx"));
+ assertEquals(1, body.size());
+ assertEquals("Renamed", body.get("name").getAsString());
+ }
+
+ @Test
+ public void testStoreUpdateEmptyMetadataClears() throws Exception {
+ // metadata is replaced wholesale: an empty map must reach the wire
+ // (an isEmpty() guard would silently turn "clear" into a no-op).
+ enqueue("memory_store_response");
+ client
+ .memoryStores()
+ .update(
+ "memstore_01Hx",
+ MemoryStoreUpdateParam.builder().metadata(Collections.emptyMap()).build());
+ RecordedRequest request = mockServer.takeRequest();
+ JsonObject body = JsonUtils.parse(request.getBody().readUtf8());
+ assertEquals("POST", request.getMethod());
+ assertTrue(body.has("metadata"));
+ assertEquals(0, body.getAsJsonObject("metadata").size());
+ }
+
+ @Test
+ public void testStoreListQueryParams() throws Exception {
+ enqueue("memory_store_list_response");
+ client
+ .memoryStores()
+ .list(MemoryStoreListParam.builder().includeArchived(true).limit(10).page("tok_1").build());
+ RecordedRequest request = mockServer.takeRequest();
+ String path = request.getPath();
+ assertEquals("GET", request.getMethod());
+ assertTrue(path.split("\\?")[0].endsWith("/memory_stores"));
+ assertTrue(path.contains("include_archived=true"));
+ assertTrue(path.contains("limit=10"));
+ assertTrue(path.contains("page=tok_1"));
+ }
+
+ @Test
+ public void testStoreArchiveAndDelete() throws Exception {
+ enqueue("memory_store_response");
+ MemoryStore archived = client.memoryStores().archive("memstore_01Hx");
+ RecordedRequest archiveRequest = mockServer.takeRequest();
+ assertEquals("POST", archiveRequest.getMethod());
+ assertTrue(archiveRequest.getPath().endsWith("/memory_stores/memstore_01Hx/archive"));
+ assertNotNull(archived);
+
+ enqueue("delete_response");
+ AgentStudioDeletionStatus deleted = client.memoryStores().delete("memstore_01Hx");
+ RecordedRequest deleteRequest = mockServer.takeRequest();
+ assertEquals("DELETE", deleteRequest.getMethod());
+ assertTrue(deleteRequest.getPath().endsWith("/memory_stores/memstore_01Hx"));
+ assertTrue(deleted.isDeleted());
+ }
+
+ @Test
+ public void testMemoryCreateAndRetrieve() throws Exception {
+ enqueue("memory_created_response");
+ Memory memory =
+ client
+ .memoryStores()
+ .memories()
+ .create(
+ "memstore_01Hx",
+ MemoryCreateParam.builder()
+ .path("/formatting.md")
+ .content("Use GAAP formatting.")
+ .build());
+ RecordedRequest request = mockServer.takeRequest();
+ JsonObject body = JsonUtils.parse(request.getBody().readUtf8());
+ assertEquals("POST", request.getMethod());
+ assertTrue(request.getPath().endsWith("/memory_stores/memstore_01Hx/memories"));
+ assertEquals("/formatting.md", body.get("path").getAsString());
+ assertEquals("Use GAAP formatting.", body.get("content").getAsString());
+ assertEquals("mem_01Hy", memory.getId());
+ assertEquals("memstore_01Hx", memory.getMemoryStoreId());
+ // Create responses never include content (probe-verified wire shape).
+ assertNull(memory.getContent());
+
+ enqueue("memory_response");
+ Memory retrieved = client.memoryStores().memories().retrieve("memstore_01Hx", "mem_01Hy");
+ RecordedRequest retrieveRequest = mockServer.takeRequest();
+ assertEquals("GET", retrieveRequest.getMethod());
+ assertTrue(
+ retrieveRequest.getPath().endsWith("/memory_stores/memstore_01Hx/memories/mem_01Hy"));
+ assertEquals("Use GAAP formatting.", retrieved.getContent());
+ }
+
+ @Test
+ public void testMemoryUpdateFlattenedPrecondition() throws Exception {
+ enqueue("memory_created_response");
+ client
+ .memoryStores()
+ .memories()
+ .update(
+ "memstore_01Hx",
+ "mem_01Hy",
+ MemoryUpdateParam.builder().content("CORRECTED").contentSha256("abc123").build());
+ RecordedRequest request = mockServer.takeRequest();
+ JsonObject body = JsonUtils.parse(request.getBody().readUtf8());
+ assertEquals("POST", request.getMethod());
+ assertTrue(request.getPath().endsWith("/memory_stores/memstore_01Hx/memories/mem_01Hy"));
+ assertEquals("CORRECTED", body.get("content").getAsString());
+ assertEquals("abc123", body.get("content_sha256").getAsString());
+ }
+
+ @Test
+ public void testMemoryListAutoPaginates() throws Exception {
+ enqueue("memory_list_response");
+ enqueue("memory_list_page2_response");
+ CursorPage page =
+ client
+ .memoryStores()
+ .memories()
+ .list(
+ "memstore_01Hx",
+ MemoryListParam.builder().pathPrefix("/").depth(1).view("basic").build());
+ RecordedRequest request = mockServer.takeRequest();
+ String path = request.getPath();
+ assertEquals("GET", request.getMethod());
+ assertTrue(path.split("\\?")[0].endsWith("/memory_stores/memstore_01Hx/memories"));
+ assertTrue(path.contains("path_prefix=%2F"));
+ assertTrue(path.contains("depth=1"));
+ assertTrue(path.contains("view=basic"));
+ // Native depth=1: gson fills prefixes from the envelope.
+ assertEquals(Collections.singletonList("/notes/"), page.getPrefixes());
+
+ // Iterating drains the queued second page; the fetchNext closure must
+ // rebuild the full filter set with the cursor.
+ java.util.List ids = new java.util.ArrayList<>();
+ for (Memory item : page) {
+ ids.add(item.getId());
+ }
+ assertEquals(java.util.Arrays.asList("mem_01Hy", "mem_02"), ids);
+ RecordedRequest second = mockServer.takeRequest();
+ assertTrue(second.getPath().contains("page=tok_2"));
+ assertTrue(second.getPath().contains("path_prefix=%2F"));
+ assertTrue(second.getPath().contains("depth=1"));
+ }
+
+ @Test
+ public void testMemoryListDepthTwoEmulatedAcrossPages() throws Exception {
+ enqueue("memory_flat_list_response");
+ enqueue("memory_flat_list_page2_response");
+ CursorPage page =
+ client
+ .memoryStores()
+ .memories()
+ .list("memstore_01Hx", MemoryListParam.builder().depth(2).build());
+ RecordedRequest request = mockServer.takeRequest();
+ // depth >= 2 must not reach the wire; the view is derived locally.
+ assertFalse(request.getPath().contains("depth="));
+
+ // Page 1: the 3-segment file is cut. Page 2: adds one 1-segment file,
+ // drops a 3-segment one; the second request still carries no depth
+ // (fetchNext re-enters the emulation with the original depth).
+ java.util.List paths = new java.util.ArrayList<>();
+ for (Memory item : page) {
+ paths.add(item.getPath());
+ }
+ assertEquals(java.util.Arrays.asList("/root.md", "/notes/top.md", "/flat2.md"), paths);
+ // Per-page synthesized prefixes: the cut 3-segment file leaves its
+ // frontier directory.
+ assertEquals(java.util.Arrays.asList("/notes/", "/notes/deep/"), page.getPrefixes());
+ RecordedRequest second = mockServer.takeRequest();
+ assertFalse(second.getPath().contains("depth="));
+ }
+
+ @Test
+ public void testMemoryListDepthOnePassesThrough() throws Exception {
+ enqueue("memory_flat_list_response");
+ client
+ .memoryStores()
+ .memories()
+ .list("memstore_01Hx", MemoryListParam.builder().depth(1).build());
+ RecordedRequest request = mockServer.takeRequest();
+ assertTrue(request.getPath().contains("depth=1"));
+ }
+
+ @Test
+ public void testMemoryErrorPathParsesNestedErrorBody() throws Exception {
+ // Live-wire shape: nested error envelope on a 409. OkHttpHttpClient's
+ // parseFailedJson fallback must surface code/message to the user.
+ String body =
+ "{\"type\":\"error\",\"error\":{\"code\":\"memory_path_conflict_error\","
+ + "\"message\":\"Memory path already exists.\"},\"request_id\":\"req-e1\"}";
+ mockServer.enqueue(TestUtils.createMockResponse(body, 409));
+ ApiException ex =
+ assertThrows(
+ ApiException.class,
+ () ->
+ client
+ .memoryStores()
+ .memories()
+ .create(
+ "memstore_01Hx",
+ MemoryCreateParam.builder()
+ .path("/e2e/notes.md")
+ .content("duplicate")
+ .build()));
+ assertEquals(409, ex.getStatus().getStatusCode());
+ assertEquals("memory_path_conflict_error", ex.getStatus().getCode());
+ // Drain the recorded request: a test that never takeRequest()s leaves
+ // the shared server's request queue off by one and misaligns every
+ // later test's assertions.
+ assertEquals("POST", mockServer.takeRequest().getMethod());
+ }
+
+ @Test
+ public void testMemoryDelete() throws Exception {
+ enqueue("memory_delete_response");
+ AgentStudioDeletionStatus deleted =
+ client.memoryStores().memories().delete("memstore_01Hx", "mem_01Hy");
+ RecordedRequest request = mockServer.takeRequest();
+ assertEquals("DELETE", request.getMethod());
+ assertTrue(request.getPath().endsWith("/memory_stores/memstore_01Hx/memories/mem_01Hy"));
+ assertEquals("memory_deleted", deleted.getType());
+ }
+
+ @Test
+ public void testVersionListDottedTimeKeysAndRedact() throws Exception {
+ enqueue("memory_version_list_response");
+ client
+ .memoryStores()
+ .memoryVersions()
+ .list(
+ "memstore_01Hx",
+ MemoryVersionListParam.builder()
+ .memoryId("mem_01Hy")
+ .operation("modified")
+ .createdAtGte("2026-10-01T00:00:00Z")
+ .createdAtLte("2026-10-31T00:00:00Z")
+ .view("full")
+ .build());
+ RecordedRequest request = mockServer.takeRequest();
+ String path = request.getPath();
+ assertEquals("GET", request.getMethod());
+ assertTrue(path.split("\\?")[0].endsWith("/memory_stores/memstore_01Hx/memory_versions"));
+ assertTrue(path.contains("memory_id=mem_01Hy"));
+ assertTrue(path.contains("operation=modified"));
+ assertTrue(path.contains("created_at.gte=2026-10-01T00%3A00%3A00Z"));
+ assertTrue(path.contains("created_at.lte=2026-10-31T00%3A00%3A00Z"));
+ assertTrue(path.contains("view=full"));
+
+ enqueue("memory_version_response");
+ MemoryVersion version =
+ client.memoryStores().memoryVersions().retrieve("memstore_01Hx", "memver_01Hz");
+ RecordedRequest retrieveRequest = mockServer.takeRequest();
+ assertEquals(
+ "/api/v1/agentstudio/memory_stores/memstore_01Hx/memory_versions/memver_01Hz",
+ retrieveRequest.getPath());
+
+ enqueue("memory_version_response");
+ MemoryVersion redacted =
+ client.memoryStores().memoryVersions().redact("memstore_01Hx", "memver_01Hz");
+ RecordedRequest redactRequest = mockServer.takeRequest();
+ assertEquals("POST", redactRequest.getMethod());
+ assertTrue(
+ redactRequest
+ .getPath()
+ .endsWith("/memory_stores/memstore_01Hx/memory_versions/memver_01Hz/redact"));
+ assertEquals("memver_01Hz", redacted.getId());
+ }
+}
diff --git a/src/test/resources/agentstudio.json b/src/test/resources/agentstudio.json
index 495a6d6b..a8babaae 100644
--- a/src/test/resources/agentstudio.json
+++ b/src/test/resources/agentstudio.json
@@ -4,13 +4,21 @@
"type": "agent",
"name": "test-agent",
"description": "A test agent",
- "model": {"id": "qwen-max"},
+ "model": {
+ "id": "qwen-max"
+ },
"system": "You are a helpful assistant.",
- "tools": [{"type": "bash_tool"}],
+ "tools": [
+ {
+ "type": "bash_tool"
+ }
+ ],
"skills": [],
"mcp_servers": [],
"version": 2,
- "metadata": {"env": "test"},
+ "metadata": {
+ "env": "test"
+ },
"workspace_id": "ws_001",
"archived_at": null,
"created_at": "2025-01-01T00:00:00Z",
@@ -42,13 +50,23 @@
{
"agent_id": "agent_xyz",
"version": 1,
- "config": {"name": "test-agent", "model": {"id": "qwen-max"}},
+ "config": {
+ "name": "test-agent",
+ "model": {
+ "id": "qwen-max"
+ }
+ },
"created_at": "2025-01-01T00:00:00Z"
},
{
"agent_id": "agent_xyz",
"version": 2,
- "config": {"name": "test-agent-v2", "model": {"id": "qwen-plus"}},
+ "config": {
+ "name": "test-agent-v2",
+ "model": {
+ "id": "qwen-plus"
+ }
+ },
"created_at": "2025-01-02T00:00:00Z"
}
],
@@ -65,7 +83,9 @@
"version": 1,
"name": "test-agent",
"description": null,
- "model": {"id": "qwen-max"},
+ "model": {
+ "id": "qwen-max"
+ },
"system": "You are a test agent.",
"tools": []
},
@@ -82,7 +102,9 @@
"speed": 42.5
},
"vault_ids": [],
- "metadata": {"env": "test"},
+ "metadata": {
+ "env": "test"
+ },
"created_at": "2025-01-01T00:00:00Z",
"updated_at": "2025-01-01T01:00:00Z",
"request_id": "req-001"
@@ -92,13 +114,21 @@
{
"id": "sess_1",
"status": "idle",
- "agent": {"id": "agent_xyz", "type": "agent", "version": 1},
+ "agent": {
+ "id": "agent_xyz",
+ "type": "agent",
+ "version": 1
+ },
"created_at": "2025-01-01T00:00:00Z"
},
{
"id": "sess_2",
"status": "running",
- "agent": {"id": "agent_xyz", "type": "agent", "version": 1},
+ "agent": {
+ "id": "agent_xyz",
+ "type": "agent",
+ "version": 1
+ },
"created_at": "2025-01-02T00:00:00Z"
}
],
@@ -113,11 +143,20 @@
"status": "active",
"config": {
"type": "sandbox",
- "networking": {"type": "public"},
- "packages": {"pip": ["requests", "numpy"]}
+ "networking": {
+ "type": "public"
+ },
+ "packages": {
+ "pip": [
+ "requests",
+ "numpy"
+ ]
+ }
},
"scope": "organization",
- "metadata": {"team": "infra"},
+ "metadata": {
+ "team": "infra"
+ },
"created_at": "2025-01-01T00:00:00Z",
"updated_at": "2025-01-01T01:00:00Z",
"request_id": "req-003"
@@ -184,7 +223,9 @@
"id": "vault_001",
"type": "vault",
"display_name": "Test Vault",
- "metadata": {"team": "security"},
+ "metadata": {
+ "team": "security"
+ },
"archived_at": null,
"created_at": "2025-01-01T00:00:00Z",
"updated_at": "2025-01-01T01:00:00Z",
@@ -213,8 +254,13 @@
"type": "vault_credential",
"vault_id": "vault_001",
"display_name": "Test Credential",
- "auth": {"type": "environment_variable", "secret_name": "DASHSCOPE_API_KEY"},
- "metadata": {"service": "openai"},
+ "auth": {
+ "type": "environment_variable",
+ "secret_name": "DASHSCOPE_API_KEY"
+ },
+ "metadata": {
+ "service": "openai"
+ },
"archived_at": null,
"created_at": "2025-01-01T00:00:00Z",
"updated_at": "2025-01-01T01:00:00Z",
@@ -288,7 +334,12 @@
"type": "message",
"status": "completed",
"role": "user",
- "content": [{"type": "text", "text": "hello"}],
+ "content": [
+ {
+ "type": "text",
+ "text": "hello"
+ }
+ ],
"sequence_number": 1,
"created_at": "2025-01-01T00:00:00Z"
},
@@ -298,7 +349,12 @@
"type": "message",
"status": "completed",
"role": "assistant",
- "content": [{"type": "text", "text": "hi there"}],
+ "content": [
+ {
+ "type": "text",
+ "text": "hi there"
+ }
+ ],
"sequence_number": 2,
"created_at": "2025-01-01T00:00:01Z"
}
@@ -316,7 +372,9 @@
"type": "agent",
"version": 12,
"name": "data-analyst",
- "model": {"id": "qwen3-max"},
+ "model": {
+ "id": "qwen3-max"
+ },
"system": "Analyze data."
},
"environment_id": "env_01",
@@ -331,19 +389,37 @@
{
"type": "message",
"role": "user",
- "content": [{"type": "text", "text": "Summarize yesterday's orders"}]
+ "content": [
+ {
+ "type": "text",
+ "text": "Summarize yesterday's orders"
+ }
+ ]
}
],
"resources": [
- {"type": "file", "file_id": "file_01", "mount_path": "/mnt/data"}
+ {
+ "type": "file",
+ "file_id": "file_01",
+ "mount_path": "/mnt/data"
+ }
+ ],
+ "vault_ids": [
+ "vault_01"
],
- "vault_ids": ["vault_01"],
- "environment_variables": {"API_BASE_URL": "https://example.test"},
- "metadata": {"biz": "summary"},
+ "environment_variables": {
+ "API_BASE_URL": "https://example.test"
+ },
+ "metadata": {
+ "biz": "summary"
+ },
"status": "paused",
"paused_reason": {
"type": "error",
- "error": {"code": "RUN_FAILED", "message": "failed"}
+ "error": {
+ "code": "RUN_FAILED",
+ "message": "failed"
+ }
},
"archived_at": null,
"created_at": "2026-07-27T01:00:00Z",
@@ -361,7 +437,9 @@
"type": "agent",
"version": 12,
"name": "data-analyst",
- "model": {"id": "qwen3-max"}
+ "model": {
+ "id": "qwen3-max"
+ }
},
"status": "active",
"created_at": "2026-07-27T01:00:00Z",
@@ -375,11 +453,17 @@
"id": "drun_01",
"type": "deployment_run",
"deployment_id": "depl_01",
- "agent": {"id": "agent_01", "version": 12},
+ "agent": {
+ "id": "agent_01",
+ "version": 12
+ },
"session_id": "session_01",
"trigger_source": "manual",
"status": "failed",
- "error": {"code": "RUN_FAILED", "message": "failed"},
+ "error": {
+ "code": "RUN_FAILED",
+ "message": "failed"
+ },
"started_at": "2026-07-27T01:00:00.123Z",
"finished_at": "2026-07-27T01:01:00.456Z",
"requestId": "req-run-01"
@@ -390,7 +474,10 @@
"id": "drun_01",
"type": "deployment_run",
"deployment_id": "depl_01",
- "agent": {"id": "agent_01", "version": 12},
+ "agent": {
+ "id": "agent_01",
+ "version": 12
+ },
"session_id": "session_01",
"trigger_source": "manual",
"status": "succeeded",
@@ -401,5 +488,149 @@
],
"next_page": "next-run",
"requestId": "req-run-list"
+ },
+ "memory_store_response": {
+ "id": "memstore_01Hx",
+ "type": "memory_store",
+ "name": "User Preferences",
+ "description": "Per-user preferences.",
+ "metadata": {
+ "owner": "demo"
+ },
+ "archived_at": null,
+ "created_at": "2026-10-01T00:00:00Z",
+ "updated_at": "2026-10-01T00:00:00Z",
+ "request_id": "req-m01"
+ },
+ "memory_store_list_response": {
+ "data": [
+ {
+ "id": "memstore_01Hx",
+ "type": "memory_store",
+ "name": "User Preferences"
+ }
+ ],
+ "next_page": null,
+ "request_id": "req-m02"
+ },
+ "memory_response": {
+ "id": "mem_01Hy",
+ "type": "memory",
+ "memory_store_id": "memstore_01Hx",
+ "path": "/formatting.md",
+ "content": "Use GAAP formatting.",
+ "content_sha256": "abc123",
+ "content_size_bytes": 21,
+ "memory_version_id": "memver_01Hz",
+ "created_at": "2026-10-01T00:00:00Z",
+ "updated_at": "2026-10-02T00:00:00Z",
+ "request_id": "req-m03"
+ },
+ "memory_list_response": {
+ "data": [
+ {
+ "id": "mem_01Hy",
+ "type": "memory",
+ "memory_store_id": "memstore_01Hx",
+ "path": "/e2e/notes.md",
+ "content_sha256": "abc123"
+ }
+ ],
+ "prefixes": [
+ "/notes/"
+ ],
+ "next_page": "tok_2",
+ "request_id": "req-m04"
+ },
+ "memory_flat_list_response": {
+ "data": [
+ {
+ "id": "mem_1",
+ "type": "memory",
+ "path": "/root.md"
+ },
+ {
+ "id": "mem_2",
+ "type": "memory",
+ "path": "/notes/top.md"
+ },
+ {
+ "id": "mem_3",
+ "type": "memory",
+ "path": "/notes/deep/file.md"
+ }
+ ],
+ "next_page": "tok_3",
+ "request_id": "req-m05"
+ },
+ "memory_version_response": {
+ "id": "memver_01Hz",
+ "type": "memory_version",
+ "memory_store_id": "memstore_01Hx",
+ "memory_id": "mem_01Hy",
+ "operation": "modified",
+ "path": "/formatting.md",
+ "content": "Use GAAP formatting.",
+ "content_sha256": "abc123",
+ "content_size_bytes": 21,
+ "created_at": "2026-10-02T00:00:00Z",
+ "request_id": "req-m06"
+ },
+ "memory_version_list_response": {
+ "data": [
+ {
+ "id": "memver_01Hz",
+ "type": "memory_version",
+ "operation": "modified"
+ }
+ ],
+ "next_page": null,
+ "request_id": "req-m07"
+ },
+ "memory_delete_response": {
+ "id": "mem_01Hy",
+ "type": "memory_deleted",
+ "request_id": "req-m08"
+ },
+ "memory_list_page2_response": {
+ "data": [
+ {
+ "id": "mem_02",
+ "type": "memory",
+ "memory_store_id": "memstore_01Hx",
+ "path": "/e2e/other.md"
+ }
+ ],
+ "prefixes": [],
+ "next_page": null,
+ "request_id": "req-m09"
+ },
+ "memory_created_response": {
+ "id": "mem_01Hy",
+ "type": "memory",
+ "memory_store_id": "memstore_01Hx",
+ "path": "/formatting.md",
+ "content_sha256": "abc123",
+ "content_size_bytes": 21,
+ "memory_version_id": "memver_01Hz",
+ "created_at": "2026-10-01T00:00:00Z",
+ "updated_at": "2026-10-02T00:00:00Z",
+ "request_id": "req-m10"
+ },
+ "memory_flat_list_page2_response": {
+ "data": [
+ {
+ "id": "mem_4",
+ "type": "memory",
+ "path": "/flat2.md"
+ },
+ {
+ "id": "mem_5",
+ "type": "memory",
+ "path": "/notes/x/deep.md"
+ }
+ ],
+ "next_page": null,
+ "request_id": "req-m11"
}
-}
+}
\ No newline at end of file