diff --git a/bootstrap/sql/migrations/native/2.0.0/mysql/schemaChanges.sql b/bootstrap/sql/migrations/native/2.0.0/mysql/schemaChanges.sql
index 814df2c138bb..45f25fd8f6c4 100644
--- a/bootstrap/sql/migrations/native/2.0.0/mysql/schemaChanges.sql
+++ b/bootstrap/sql/migrations/native/2.0.0/mysql/schemaChanges.sql
@@ -340,3 +340,18 @@ CREATE TABLE IF NOT EXISTS search_index_retry_queue (
KEY idx_search_index_retry_queue_status (status),
KEY idx_search_index_retry_queue_claimed_at (claimedAt)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
+
+-- ContextMemory entity - reusable Context Center memory.
+CREATE TABLE IF NOT EXISTS context_memory (
+ id VARCHAR(36) GENERATED ALWAYS AS (json ->> '$.id') STORED NOT NULL,
+ name VARCHAR(256) GENERATED ALWAYS AS (json ->> '$.name') STORED NOT NULL,
+ nameHash VARCHAR(256) NOT NULL COLLATE ascii_bin,
+ json JSON NOT NULL,
+ updatedAt BIGINT UNSIGNED GENERATED ALWAYS AS (json ->> '$.updatedAt') STORED NOT NULL,
+ updatedBy VARCHAR(256) GENERATED ALWAYS AS (json ->> '$.updatedBy') STORED NOT NULL,
+ deleted BOOLEAN GENERATED ALWAYS AS (json -> '$.deleted') STORED,
+
+ PRIMARY KEY (id),
+ UNIQUE KEY unique_context_memory_name (nameHash),
+ INDEX idx_context_memory_updated_at (updatedAt)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
diff --git a/bootstrap/sql/migrations/native/2.0.0/postgres/schemaChanges.sql b/bootstrap/sql/migrations/native/2.0.0/postgres/schemaChanges.sql
index f544b9511cea..6ce3236f6daa 100644
--- a/bootstrap/sql/migrations/native/2.0.0/postgres/schemaChanges.sql
+++ b/bootstrap/sql/migrations/native/2.0.0/postgres/schemaChanges.sql
@@ -291,3 +291,18 @@ CREATE INDEX IF NOT EXISTS idx_search_index_retry_queue_status
ON search_index_retry_queue (status);
CREATE INDEX IF NOT EXISTS idx_search_index_retry_queue_claimed_at
ON search_index_retry_queue (claimedAt);
+
+-- ContextMemory entity - reusable Context Center memory.
+CREATE TABLE IF NOT EXISTS context_memory (
+ id VARCHAR(36) GENERATED ALWAYS AS (json ->> 'id') STORED NOT NULL,
+ name VARCHAR(256) GENERATED ALWAYS AS (json ->> 'name') STORED NOT NULL,
+ nameHash VARCHAR(256) NOT NULL,
+ json JSONB NOT NULL,
+ updatedAt BIGINT GENERATED ALWAYS AS ((json ->> 'updatedAt')::bigint) STORED NOT NULL,
+ updatedBy VARCHAR(256) GENERATED ALWAYS AS (json ->> 'updatedBy') STORED NOT NULL,
+ deleted BOOLEAN GENERATED ALWAYS AS ((json ->> 'deleted')::boolean) STORED,
+
+ PRIMARY KEY (id),
+ UNIQUE (nameHash)
+);
+CREATE INDEX IF NOT EXISTS idx_context_memory_updated_at ON context_memory (updatedAt);
diff --git a/openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/ContextMemoryIT.java b/openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/ContextMemoryIT.java
new file mode 100644
index 000000000000..15c0bf386488
--- /dev/null
+++ b/openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/ContextMemoryIT.java
@@ -0,0 +1,587 @@
+package org.openmetadata.it.tests;
+
+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.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.parallel.Execution;
+import org.junit.jupiter.api.parallel.ExecutionMode;
+import org.openmetadata.it.util.SdkClients;
+import org.openmetadata.it.util.TestNamespace;
+import org.openmetadata.schema.api.context.CreateContextMemory;
+import org.openmetadata.schema.entity.context.ContextMemory;
+import org.openmetadata.schema.entity.context.ContextMemoryScope;
+import org.openmetadata.schema.entity.context.ContextMemoryStatus;
+import org.openmetadata.schema.entity.context.ContextMemoryType;
+import org.openmetadata.schema.entity.context.MemoryShareConfig;
+import org.openmetadata.schema.entity.context.MemorySharedPrincipal;
+import org.openmetadata.schema.entity.context.MemoryVisibility;
+import org.openmetadata.schema.entity.teams.User;
+import org.openmetadata.schema.type.EntityHistory;
+import org.openmetadata.schema.type.EntityReference;
+import org.openmetadata.sdk.fluent.Users;
+import org.openmetadata.sdk.models.ListParams;
+import org.openmetadata.sdk.models.ListResponse;
+import org.openmetadata.sdk.services.context.ContextMemoryService;
+
+/**
+ * Integration tests for ContextMemory entity operations.
+ *
+ *
Tests ContextMemory CRUD operations, status lifecycle transitions, scope/visibility handling,
+ * and context-memory-specific validations.
+ *
+ *
Modeled on LearningResourceIT, the reference entity for the ContextMemory OSS implementation.
+ */
+@Execution(ExecutionMode.CONCURRENT)
+public class ContextMemoryIT extends BaseEntityIT {
+
+ public ContextMemoryIT() {
+ supportsPatch = true;
+ supportsFollowers = false;
+ supportsTags = true;
+ supportsOwners = true;
+ supportsDomains = true;
+ supportsDataProducts = false;
+ supportsCustomExtension = true;
+ supportsSearchIndex = false;
+ }
+
+ // ===================================================================
+ // ABSTRACT METHOD IMPLEMENTATIONS (Required by BaseEntityIT)
+ // ===================================================================
+
+ @Override
+ protected CreateContextMemory createMinimalRequest(TestNamespace ns) {
+ return new CreateContextMemory()
+ .withName(ns.prefix("context-memory"))
+ .withDescription("Test context memory")
+ .withQuestion("How do I find certified tables?")
+ .withAnswer("Filter the Explore page by the Certification tag.");
+ }
+
+ @Override
+ protected CreateContextMemory createRequest(String name, TestNamespace ns) {
+ return new CreateContextMemory()
+ .withName(name)
+ .withDescription("Test context memory")
+ .withQuestion("What is the data quality SLA?")
+ .withAnswer("Critical tables must pass tests every 24 hours.");
+ }
+
+ @Override
+ protected ContextMemory createEntity(CreateContextMemory createRequest) {
+ return getContextMemoryService().create(createRequest);
+ }
+
+ @Override
+ protected ContextMemory getEntity(String id) {
+ return getContextMemoryService().get(id);
+ }
+
+ @Override
+ protected ContextMemory getEntityByName(String fqn) {
+ return getContextMemoryService().getByName(fqn);
+ }
+
+ @Override
+ protected ContextMemory patchEntity(String id, ContextMemory entity) {
+ return getContextMemoryService().update(id, entity);
+ }
+
+ @Override
+ protected void deleteEntity(String id) {
+ getContextMemoryService().delete(id);
+ }
+
+ @Override
+ protected void restoreEntity(String id) {
+ getContextMemoryService().restore(id);
+ }
+
+ @Override
+ protected void hardDeleteEntity(String id) {
+ Map params = new HashMap<>();
+ params.put("hardDelete", "true");
+ getContextMemoryService().delete(id, params);
+ }
+
+ @Override
+ protected String getEntityType() {
+ return "contextMemory";
+ }
+
+ @Override
+ protected void validateCreatedEntity(ContextMemory entity, CreateContextMemory createRequest) {
+ assertEquals(createRequest.getName(), entity.getName());
+
+ if (createRequest.getDescription() != null) {
+ assertEquals(createRequest.getDescription(), entity.getDescription());
+ }
+
+ if (createRequest.getDisplayName() != null) {
+ assertEquals(createRequest.getDisplayName(), entity.getDisplayName());
+ }
+
+ assertEquals(createRequest.getQuestion(), entity.getQuestion());
+ assertEquals(createRequest.getAnswer(), entity.getAnswer());
+
+ assertTrue(
+ entity.getFullyQualifiedName().contains(entity.getName()),
+ "FQN should contain memory name");
+ }
+
+ @Override
+ protected ListResponse listEntities(ListParams params) {
+ return getContextMemoryService().list(params);
+ }
+
+ @Override
+ protected ContextMemory getEntityWithFields(String id, String fields) {
+ return getContextMemoryService().get(id, fields);
+ }
+
+ @Override
+ protected ContextMemory getEntityByNameWithFields(String fqn, String fields) {
+ return getContextMemoryService().getByName(fqn, fields);
+ }
+
+ @Override
+ protected ContextMemory getEntityIncludeDeleted(String id) {
+ return getContextMemoryService().get(id, null, "deleted");
+ }
+
+ @Override
+ protected EntityHistory getVersionHistory(UUID id) {
+ return getContextMemoryService().getVersionList(id);
+ }
+
+ @Override
+ protected ContextMemory getVersion(UUID id, Double version) {
+ return getContextMemoryService().getVersion(id.toString(), version);
+ }
+
+ // ===================================================================
+ // CRUD TESTS
+ // ===================================================================
+
+ @Test
+ void post_contextMemory_200_OK(TestNamespace ns) {
+ CreateContextMemory request =
+ new CreateContextMemory()
+ .withName(ns.prefix("crud-memory"))
+ .withDescription("CRUD happy path")
+ .withQuestion("Where are the gold datasets?")
+ .withAnswer("Under the Sales domain tagged Tier.Gold.");
+
+ ContextMemory memory = createEntity(request);
+ assertNotNull(memory.getId());
+ assertEquals(request.getName(), memory.getName());
+ assertEquals("Where are the gold datasets?", memory.getQuestion());
+ assertEquals("Under the Sales domain tagged Tier.Gold.", memory.getAnswer());
+ assertEquals(0.1, memory.getVersion(), 0.001);
+
+ ContextMemory fetched = getEntity(memory.getId().toString());
+ assertEquals(memory.getId(), fetched.getId());
+ assertEquals(memory.getName(), fetched.getName());
+ }
+
+ @Test
+ void post_contextMemoryWithQuestionAnswerSummaryTitle_200_OK(TestNamespace ns) {
+ CreateContextMemory request =
+ new CreateContextMemory()
+ .withName(ns.prefix("rich-memory"))
+ .withDisplayName("Certification Lookup")
+ .withDescription("Full content memory")
+ .withTitle("How to find certified data")
+ .withSummary("Use the Certification tag filter on Explore.")
+ .withQuestion("How do I find certified tables?")
+ .withAnswer("Filter the Explore page by Certification = Certified.");
+
+ ContextMemory memory = createEntity(request);
+ assertEquals("Certification Lookup", memory.getDisplayName());
+ assertEquals("How to find certified data", memory.getTitle());
+ assertEquals("Use the Certification tag filter on Explore.", memory.getSummary());
+ assertEquals("How do I find certified tables?", memory.getQuestion());
+ assertEquals("Filter the Explore page by Certification = Certified.", memory.getAnswer());
+ }
+
+ @Test
+ void post_contextMemoryWithoutRequiredFields_400(TestNamespace ns) {
+ assertThrows(
+ Exception.class,
+ () -> createEntity(new CreateContextMemory().withName(null)),
+ "Creating memory without name should fail");
+
+ assertThrows(
+ Exception.class,
+ () ->
+ createEntity(
+ new CreateContextMemory()
+ .withName(ns.prefix("no-question"))
+ .withAnswer("An answer without a question.")),
+ "Creating memory without question should fail");
+
+ assertThrows(
+ Exception.class,
+ () ->
+ createEntity(
+ new CreateContextMemory()
+ .withName(ns.prefix("no-answer"))
+ .withQuestion("A question without an answer?")),
+ "Creating memory without answer should fail");
+ }
+
+ @Test
+ void post_contextMemoryDuplicateName_409(TestNamespace ns) {
+ String memoryName = ns.prefix("duplicate-memory");
+
+ createEntity(
+ new CreateContextMemory()
+ .withName(memoryName)
+ .withDescription("First memory")
+ .withQuestion("First question?")
+ .withAnswer("First answer."));
+
+ CreateContextMemory duplicate =
+ new CreateContextMemory()
+ .withName(memoryName)
+ .withDescription("Duplicate memory")
+ .withQuestion("Duplicate question?")
+ .withAnswer("Duplicate answer.");
+
+ assertThrows(Exception.class, () -> createEntity(duplicate), "Duplicate name should fail");
+ }
+
+ @Test
+ void get_contextMemoryByFqn_200_OK(TestNamespace ns) {
+ CreateContextMemory request =
+ new CreateContextMemory()
+ .withName(ns.prefix("fqn-memory"))
+ .withDescription("FQN lookup test")
+ .withQuestion("What is the FQN of this memory?")
+ .withAnswer("The FQN equals the name for context memories.");
+
+ ContextMemory memory = createEntity(request);
+
+ // FQN == name for ContextMemory (ContextMemoryRepository.setFullyQualifiedName).
+ assertEquals(memory.getName(), memory.getFullyQualifiedName());
+
+ ContextMemory byFqn = getEntityByName(memory.getFullyQualifiedName());
+ assertEquals(memory.getId(), byFqn.getId());
+ assertEquals(memory.getName(), byFqn.getName());
+ }
+
+ // ===================================================================
+ // STATUS LIFECYCLE TESTS
+ // ===================================================================
+
+ @Test
+ void put_contextMemoryStatusTransitions_valid_200_OK(TestNamespace ns) {
+ CreateContextMemory request =
+ new CreateContextMemory()
+ .withName(ns.prefix("status-valid"))
+ .withDescription("Valid status transitions")
+ .withQuestion("What is the status flow?")
+ .withAnswer("Draft to Active to Archived and back to Active.")
+ .withStatus(ContextMemoryStatus.DRAFT);
+
+ ContextMemory memory = createEntity(request);
+ assertEquals(ContextMemoryStatus.DRAFT, memory.getStatus());
+
+ request.withStatus(ContextMemoryStatus.ACTIVE);
+ ContextMemory active = getContextMemoryService().put(request);
+ assertEquals(ContextMemoryStatus.ACTIVE, active.getStatus());
+
+ request.withStatus(ContextMemoryStatus.ARCHIVED);
+ ContextMemory archived = getContextMemoryService().put(request);
+ assertEquals(ContextMemoryStatus.ARCHIVED, archived.getStatus());
+
+ request.withStatus(ContextMemoryStatus.ACTIVE);
+ ContextMemory reactivated = getContextMemoryService().put(request);
+ assertEquals(ContextMemoryStatus.ACTIVE, reactivated.getStatus());
+ }
+
+ @Test
+ void put_contextMemoryStatusTransition_invalid_fails(TestNamespace ns) {
+ CreateContextMemory request =
+ new CreateContextMemory()
+ .withName(ns.prefix("status-invalid"))
+ .withDescription("Invalid status transition")
+ .withQuestion("Can Active go back to Draft?")
+ .withAnswer("No, Active cannot revert to Draft.")
+ .withStatus(ContextMemoryStatus.ACTIVE);
+
+ ContextMemory memory = createEntity(request);
+ assertEquals(ContextMemoryStatus.ACTIVE, memory.getStatus());
+
+ request.withStatus(ContextMemoryStatus.DRAFT);
+ assertThrows(
+ Exception.class,
+ () -> getContextMemoryService().put(request),
+ "Transition from Active to Draft should be rejected");
+ }
+
+ @Test
+ void put_statusOnlyChange_persistsAfterGet(TestNamespace ns) {
+ CreateContextMemory request =
+ new CreateContextMemory()
+ .withName(ns.prefix("status-persist"))
+ .withDescription("Status-only update persistence test")
+ .withQuestion("Does the status persist?")
+ .withAnswer("Yes, after a status-only PUT.")
+ .withStatus(ContextMemoryStatus.DRAFT);
+
+ ContextMemory memory = createEntity(request);
+ assertEquals(ContextMemoryStatus.DRAFT, memory.getStatus());
+
+ request.withStatus(ContextMemoryStatus.ACTIVE);
+ ContextMemory putResponse = getContextMemoryService().put(request);
+ assertEquals(ContextMemoryStatus.ACTIVE, putResponse.getStatus());
+
+ ContextMemory fetched = getEntity(memory.getId().toString());
+ assertEquals(
+ ContextMemoryStatus.ACTIVE,
+ fetched.getStatus(),
+ "Status should persist after a status-only PUT update");
+ assertTrue(
+ fetched.getVersion() > memory.getVersion(),
+ "Version should be incremented after status change");
+ }
+
+ @Test
+ void put_statusChanges_recordVersionHistory(TestNamespace ns) {
+ CreateContextMemory request =
+ new CreateContextMemory()
+ .withName(ns.prefix("status-history"))
+ .withDescription("Status change history test")
+ .withQuestion("Are status changes versioned?")
+ .withAnswer("Yes, each transition bumps the version.")
+ .withStatus(ContextMemoryStatus.DRAFT);
+
+ ContextMemory memory = createEntity(request);
+
+ request.withStatus(ContextMemoryStatus.ACTIVE);
+ getContextMemoryService().put(request);
+
+ request.withStatus(ContextMemoryStatus.ARCHIVED);
+ getContextMemoryService().put(request);
+
+ EntityHistory history = getVersionHistory(memory.getId());
+ assertTrue(
+ history.getVersions().size() >= 3,
+ "Should have at least 3 versions: create + 2 status updates");
+ }
+
+ // ===================================================================
+ // SCOPE / TYPE / VISIBILITY TESTS
+ // ===================================================================
+
+ @Test
+ void post_contextMemoryWithScopeAndType_200_OK(TestNamespace ns) {
+ CreateContextMemory request =
+ new CreateContextMemory()
+ .withName(ns.prefix("scope-type"))
+ .withDescription("Scope and type test")
+ .withQuestion("What is my reporting preference?")
+ .withAnswer("Always include row counts in summaries.")
+ .withMemoryScope(ContextMemoryScope.USER_GLOBAL)
+ .withMemoryType(ContextMemoryType.PREFERENCE);
+
+ ContextMemory memory = createEntity(request);
+ assertEquals(ContextMemoryScope.USER_GLOBAL, memory.getMemoryScope());
+ assertEquals(ContextMemoryType.PREFERENCE, memory.getMemoryType());
+ }
+
+ @Test
+ void post_contextMemoryWithShareConfigVisibility_200_OK(TestNamespace ns) {
+ CreateContextMemory request =
+ new CreateContextMemory()
+ .withName(ns.prefix("visibility"))
+ .withDescription("Visibility test")
+ .withQuestion("Who can see this memory?")
+ .withAnswer("Only the owner while visibility is Private.")
+ .withShareConfig(new MemoryShareConfig().withVisibility(MemoryVisibility.PRIVATE));
+
+ ContextMemory memory = createEntity(request);
+ assertNotNull(memory.getShareConfig());
+ assertEquals(MemoryVisibility.PRIVATE, memory.getShareConfig().getVisibility());
+ }
+
+ // ===================================================================
+ // VALIDATION TESTS
+ // ===================================================================
+
+ @Test
+ void patch_contextMemorySelfParentReference_4xx(TestNamespace ns) {
+ ContextMemory memory = createEntity(createMinimalRequest(ns));
+
+ ContextMemory selfParent = getEntity(memory.getId().toString());
+ selfParent.setParentMemory(memory.getEntityReference());
+
+ assertThrows(
+ Exception.class,
+ () -> patchEntity(memory.getId().toString(), selfParent),
+ "A memory must not reference itself as parentMemory");
+ }
+
+ @Test
+ void post_contextMemoryInvalidSharedPrincipalType_4xx(TestNamespace ns) {
+ ContextMemory principalMemory = createEntity(createMinimalRequest(ns));
+
+ CreateContextMemory request =
+ new CreateContextMemory()
+ .withName(ns.prefix("bad-principal"))
+ .withDescription("Invalid shared principal type")
+ .withQuestion("Can a memory be a shared principal?")
+ .withAnswer("No - only user, team, or domain principals are allowed.")
+ .withShareConfig(
+ new MemoryShareConfig()
+ .withVisibility(MemoryVisibility.SHARED)
+ .withSharedWith(
+ List.of(
+ new MemorySharedPrincipal()
+ .withPrincipal(principalMemory.getEntityReference()))));
+
+ assertThrows(
+ Exception.class,
+ () -> createEntity(request),
+ "Sharing with a non-user/team/domain principal must be rejected");
+ }
+
+ @Test
+ void post_contextMemoryAllTypes_200_OK(TestNamespace ns) {
+ for (ContextMemoryType type : ContextMemoryType.values()) {
+ CreateContextMemory request =
+ new CreateContextMemory()
+ .withName(ns.prefix("type-" + type.value().toLowerCase()))
+ .withDescription("Memory of type " + type.value())
+ .withQuestion("Question for " + type.value() + "?")
+ .withAnswer("Answer for " + type.value() + ".")
+ .withMemoryType(type);
+
+ ContextMemory memory = createEntity(request);
+ assertEquals(type, memory.getMemoryType());
+ }
+ }
+
+ // ===================================================================
+ // LIST TESTS
+ // ===================================================================
+
+ @Test
+ void test_listContextMemories(TestNamespace ns) {
+ CreateContextMemory request1 =
+ new CreateContextMemory()
+ .withName(ns.prefix("list-1"))
+ .withDescription("First memory")
+ .withQuestion("First list question?")
+ .withAnswer("First list answer.");
+
+ CreateContextMemory request2 =
+ new CreateContextMemory()
+ .withName(ns.prefix("list-2"))
+ .withDescription("Second memory")
+ .withQuestion("Second list question?")
+ .withAnswer("Second list answer.");
+
+ createEntity(request1);
+ createEntity(request2);
+
+ ListParams params = new ListParams();
+ params.setLimit(10);
+ ListResponse response = listEntities(params);
+
+ assertNotNull(response);
+ assertFalse(response.getData().isEmpty());
+ assertTrue(response.getData().size() >= 2);
+ }
+
+ // ===================================================================
+ // OWNERSHIP TEST OVERRIDES
+ // ===================================================================
+
+ /**
+ * ContextMemory auto-assigns the creating user as owner when the create request omits owners
+ * (see {@code ContextMemoryRepository#setCreatorAsDefaultOwner}), so it deliberately diverges
+ * from the generic BaseEntityIT precondition that a freshly created entity has no owner. The
+ * PATCH contract is unchanged: setting an explicit owner replaces the creator.
+ */
+ @Test
+ @Override
+ void patch_entityUpdateOwner_200(TestNamespace ns) {
+ ContextMemory created = createEntity(createMinimalRequest(ns));
+
+ ContextMemory fetched = getEntityWithFields(created.getId().toString(), "owners");
+ assertNotNull(fetched.getOwners(), "ContextMemory should be owned by its creator initially");
+ assertEquals(
+ 1, fetched.getOwners().size(), "ContextMemory creator should be the sole initial owner");
+
+ User botUser = Users.getByName("ingestion-bot");
+ EntityReference ownerRef =
+ new EntityReference()
+ .withId(botUser.getId())
+ .withType("user")
+ .withName(botUser.getName())
+ .withFullyQualifiedName(botUser.getFullyQualifiedName());
+
+ fetched.setOwners(List.of(ownerRef));
+ ContextMemory updated = patchEntity(fetched.getId().toString(), fetched);
+
+ ContextMemory updatedFetched = getEntityWithFields(updated.getId().toString(), "owners");
+ assertNotNull(updatedFetched.getOwners(), "Entity should have owners");
+ assertEquals(1, updatedFetched.getOwners().size(), "Entity should have 1 owner");
+ assertEquals(
+ botUser.getId(),
+ updatedFetched.getOwners().get(0).getId(),
+ "Owner should be ingestion-bot user");
+ }
+
+ /**
+ * ContextMemory already has the creating user as its sole owner before this PATCH (see {@code
+ * ContextMemoryRepository#setCreatorAsDefaultOwner}); the original "from null" precondition does
+ * not hold. Setting an explicit owners list still replaces it wholesale.
+ */
+ @Test
+ @Override
+ void patch_entityUpdateOwnerFromNull_200(TestNamespace ns) {
+ ContextMemory entity = createEntity(createMinimalRequest(ns));
+
+ ContextMemory fetched = getEntityWithFields(entity.getId().toString(), "owners");
+ assertNotNull(fetched.getOwners(), "ContextMemory should be owned by its creator initially");
+ assertEquals(
+ 1, fetched.getOwners().size(), "ContextMemory creator should be the sole initial owner");
+
+ EntityReference owner1 =
+ new EntityReference()
+ .withId(testUser1().getId())
+ .withType("user")
+ .withName(testUser1().getName());
+ EntityReference owner2 =
+ new EntityReference()
+ .withId(testUser2().getId())
+ .withType("user")
+ .withName(testUser2().getName());
+
+ fetched.setOwners(List.of(owner1, owner2));
+ ContextMemory updated = patchEntity(fetched.getId().toString(), fetched);
+
+ ContextMemory verify = getEntityWithFields(updated.getId().toString(), "owners");
+ assertNotNull(verify.getOwners(), "Entity should have owners");
+ assertEquals(2, verify.getOwners().size(), "Entity should have 2 owners");
+ }
+
+ // ===================================================================
+ // HELPER METHODS
+ // ===================================================================
+
+ private ContextMemoryService getContextMemoryService() {
+ return new ContextMemoryService(SdkClients.adminClient().getHttpClient());
+ }
+}
diff --git a/openmetadata-sdk/src/main/java/org/openmetadata/sdk/services/context/ContextMemoryService.java b/openmetadata-sdk/src/main/java/org/openmetadata/sdk/services/context/ContextMemoryService.java
new file mode 100644
index 000000000000..cbd55011973c
--- /dev/null
+++ b/openmetadata-sdk/src/main/java/org/openmetadata/sdk/services/context/ContextMemoryService.java
@@ -0,0 +1,27 @@
+package org.openmetadata.sdk.services.context;
+
+import org.openmetadata.schema.api.context.CreateContextMemory;
+import org.openmetadata.schema.entity.context.ContextMemory;
+import org.openmetadata.sdk.exceptions.OpenMetadataException;
+import org.openmetadata.sdk.network.HttpClient;
+import org.openmetadata.sdk.network.HttpMethod;
+import org.openmetadata.sdk.services.EntityServiceBase;
+
+public class ContextMemoryService extends EntityServiceBase {
+ public ContextMemoryService(HttpClient httpClient) {
+ super(httpClient, "/v1/contextCenter/memories");
+ }
+
+ @Override
+ protected Class getEntityClass() {
+ return ContextMemory.class;
+ }
+
+ public ContextMemory create(CreateContextMemory request) throws OpenMetadataException {
+ return httpClient.execute(HttpMethod.POST, basePath, request, ContextMemory.class);
+ }
+
+ public ContextMemory put(CreateContextMemory request) throws OpenMetadataException {
+ return httpClient.execute(HttpMethod.PUT, basePath, request, ContextMemory.class);
+ }
+}
diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/Entity.java b/openmetadata-service/src/main/java/org/openmetadata/service/Entity.java
index fbfdaae9202e..a190f2cdff49 100644
--- a/openmetadata-service/src/main/java/org/openmetadata/service/Entity.java
+++ b/openmetadata-service/src/main/java/org/openmetadata/service/Entity.java
@@ -310,6 +310,7 @@ public final class Entity {
public static final String DOCUMENT = "document";
public static final String LEARNING_RESOURCE = "learningResource";
+ public static final String CONTEXT_MEMORY = "contextMemory";
// ServiceType - Service Entity name map
static final Map SERVICE_TYPE_ENTITY_MAP = new EnumMap<>(ServiceType.class);
// entity type to service entity name map
diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/CollectionDAO.java b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/CollectionDAO.java
index 240936ff3275..0f08e3c21f99 100644
--- a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/CollectionDAO.java
+++ b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/CollectionDAO.java
@@ -99,6 +99,7 @@
import org.openmetadata.schema.entity.automations.Workflow;
import org.openmetadata.schema.entity.classification.Classification;
import org.openmetadata.schema.entity.classification.Tag;
+import org.openmetadata.schema.entity.context.ContextMemory;
import org.openmetadata.schema.entity.data.APICollection;
import org.openmetadata.schema.entity.data.APIEndpoint;
import org.openmetadata.schema.entity.data.Chart;
@@ -460,6 +461,9 @@ public interface CollectionDAO {
@CreateSqlObject
LearningResourceDAO learningResourceDAO();
+ @CreateSqlObject
+ ContextMemoryDAO contextMemoryDAO();
+
@CreateSqlObject
SuggestionDAO suggestionDAO();
@@ -10871,6 +10875,23 @@ default String getNameHashColumn() {
}
}
+ interface ContextMemoryDAO extends EntityDAO {
+ @Override
+ default String getTableName() {
+ return "context_memory";
+ }
+
+ @Override
+ default Class getEntityClass() {
+ return ContextMemory.class;
+ }
+
+ @Override
+ default String getNameHashColumn() {
+ return "nameHash";
+ }
+ }
+
interface SuggestionDAO {
default String getTableName() {
return "suggestions";
diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/ContextMemoryRepository.java b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/ContextMemoryRepository.java
new file mode 100644
index 000000000000..3af048a1c9a7
--- /dev/null
+++ b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/ContextMemoryRepository.java
@@ -0,0 +1,323 @@
+/*
+ * Copyright 2024 Collate
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openmetadata.service.jdbi3;
+
+import static org.openmetadata.common.utils.CommonUtil.listOrEmpty;
+import static org.openmetadata.common.utils.CommonUtil.nullOrEmpty;
+
+import jakarta.ws.rs.BadRequestException;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+import lombok.extern.slf4j.Slf4j;
+import org.openmetadata.schema.entity.context.ContextMemory;
+import org.openmetadata.schema.entity.context.ContextMemoryStatus;
+import org.openmetadata.schema.type.EntityReference;
+import org.openmetadata.schema.type.Include;
+import org.openmetadata.schema.type.Relationship;
+import org.openmetadata.schema.type.change.ChangeSource;
+import org.openmetadata.service.Entity;
+import org.openmetadata.service.resources.context.ContextMemoryResource;
+import org.openmetadata.service.util.EntityUtil;
+import org.openmetadata.service.util.EntityUtil.Fields;
+import org.openmetadata.service.util.EntityUtil.RelationIncludes;
+import org.openmetadata.service.util.FullyQualifiedName;
+
+@Slf4j
+@Repository(name = "ContextMemoryRepository")
+public class ContextMemoryRepository extends EntityRepository {
+
+ public ContextMemoryRepository() {
+ super(
+ ContextMemoryResource.COLLECTION_PATH,
+ Entity.CONTEXT_MEMORY,
+ ContextMemory.class,
+ Entity.getCollectionDAO().contextMemoryDAO(),
+ "",
+ "");
+ supportsSearch = false;
+ }
+
+ @Override
+ protected void setFields(ContextMemory entity, Fields fields, RelationIncludes relationIncludes) {
+ // ContextMemory stores its fields in the entity JSON for now.
+ }
+
+ @Override
+ protected void clearFields(ContextMemory entity, Fields fields) {
+ // ContextMemory stores its fields in the entity JSON for now.
+ }
+
+ @Override
+ public void setFullyQualifiedName(ContextMemory entity) {
+ if (!nullOrEmpty(entity.getFullyQualifiedName())) {
+ return;
+ }
+ // FQN is the (immutable) memory name. Deriving it from mutable fields such as
+ // primaryEntity or owners would change nameHash on update, risking unique-constraint
+ // collisions and orphaned references. The link to primaryEntity/owners is captured
+ // via the relationship table instead. FullyQualifiedName.build quotes reserved
+ // characters, matching the convention in every other top-level entity repository.
+ entity.setFullyQualifiedName(FullyQualifiedName.build(entity.getName()));
+ }
+
+ private static final Set ALLOWED_SHARED_PRINCIPAL_TYPES =
+ Set.of(Entity.USER, Entity.TEAM, Entity.DOMAIN);
+
+ @Override
+ public void prepare(ContextMemory entity, boolean update) {
+ if (entity.getPrimaryEntity() != null) {
+ EntityReference primaryEntity =
+ Entity.getEntityReference(entity.getPrimaryEntity(), Include.NON_DELETED);
+ entity.setPrimaryEntity(primaryEntity);
+ }
+ entity.setRelatedEntities(EntityUtil.populateEntityReferences(entity.getRelatedEntities()));
+
+ if (entity.getRootMemory() != null) {
+ ContextMemory rootMemory = Entity.getEntity(entity.getRootMemory(), "", Include.NON_DELETED);
+ validateNotSelfReference(entity, rootMemory.getId(), "rootMemory");
+ entity.setRootMemory(rootMemory.getEntityReference());
+ }
+ if (entity.getParentMemory() != null) {
+ ContextMemory parentMemory =
+ Entity.getEntity(entity.getParentMemory(), "", Include.NON_DELETED);
+ validateNotSelfReference(entity, parentMemory.getId(), "parentMemory");
+ entity.setParentMemory(parentMemory.getEntityReference());
+ }
+ validateSharedPrincipals(entity);
+ setCreatorAsDefaultOwner(entity, update);
+ }
+
+ private void validateNotSelfReference(ContextMemory entity, UUID referencedId, String field) {
+ if (entity.getId() != null && entity.getId().equals(referencedId)) {
+ throw new BadRequestException(
+ String.format("A context memory cannot reference itself as %s", field));
+ }
+ }
+
+ private void validateSharedPrincipals(ContextMemory entity) {
+ if (entity.getShareConfig() == null || entity.getShareConfig().getSharedWith() == null) {
+ return;
+ }
+ for (var sharedPrincipal : entity.getShareConfig().getSharedWith()) {
+ if (sharedPrincipal.getPrincipal() == null) {
+ continue;
+ }
+ EntityReference principal =
+ Entity.getEntityReference(sharedPrincipal.getPrincipal(), Include.NON_DELETED);
+ if (!ALLOWED_SHARED_PRINCIPAL_TYPES.contains(principal.getType())) {
+ throw new BadRequestException(
+ String.format(
+ "Invalid shared principal type '%s'. Supported types: %s",
+ principal.getType(), ALLOWED_SHARED_PRINCIPAL_TYPES));
+ }
+ sharedPrincipal.setPrincipal(principal);
+ }
+ }
+
+ /**
+ * The creator owns the memory only at creation time. On update/PUT the owners are managed by the
+ * standard framework path so omitting owners no longer silently replaces previously set owners.
+ */
+ private void setCreatorAsDefaultOwner(ContextMemory entity, boolean update) {
+ if (update || !nullOrEmpty(entity.getOwners())) {
+ return;
+ }
+ entity.setOwners(
+ List.of(
+ Entity.getEntityReferenceByName(
+ Entity.USER, entity.getUpdatedBy(), Include.NON_DELETED)));
+ }
+
+ @Override
+ public void storeEntity(ContextMemory entity, boolean update) {
+ store(entity, update);
+ }
+
+ @Override
+ public void storeRelationships(ContextMemory entity) {
+ // Add-only: addRelationship upserts, so re-running on update is idempotent. Stale-edge
+ // cleanup on update is handled in ContextMemoryUpdater via updateFromRelationship(s),
+ // which deletes only the specific changed refs. A blanket deleteTo here would also wipe
+ // the framework's domain --HAS--> memory edge (storeDomains runs before storeRelationships).
+ if (entity.getPrimaryEntity() != null) {
+ addRelationship(
+ entity.getPrimaryEntity().getId(),
+ entity.getId(),
+ entity.getPrimaryEntity().getType(),
+ Entity.CONTEXT_MEMORY,
+ Relationship.HAS);
+ }
+
+ for (var relatedEntity : listOrEmpty(entity.getRelatedEntities())) {
+ addRelationship(
+ relatedEntity.getId(),
+ entity.getId(),
+ relatedEntity.getType(),
+ Entity.CONTEXT_MEMORY,
+ Relationship.RELATED_TO);
+ }
+
+ // Distinct relationship types (CONTAINS for root-ancestor, PARENT_OF for direct parent)
+ // so the two hierarchies resolve independently and neither collides with the framework's
+ // HAS edges (domains).
+ if (entity.getRootMemory() != null) {
+ addRelationship(
+ entity.getRootMemory().getId(),
+ entity.getId(),
+ Entity.CONTEXT_MEMORY,
+ Entity.CONTEXT_MEMORY,
+ Relationship.CONTAINS);
+ }
+
+ if (entity.getParentMemory() != null) {
+ addRelationship(
+ entity.getParentMemory().getId(),
+ entity.getId(),
+ Entity.CONTEXT_MEMORY,
+ Entity.CONTEXT_MEMORY,
+ Relationship.PARENT_OF);
+ }
+ }
+
+ private static List asRefList(EntityReference ref) {
+ return ref == null ? List.of() : List.of(ref);
+ }
+
+ // ------------------------------------------------------------------
+ // Lifecycle enforcement
+ // ------------------------------------------------------------------
+
+ /**
+ * Valid status transitions:
+ * DRAFT → ACTIVE
+ * DRAFT → ARCHIVED
+ * ACTIVE → ARCHIVED
+ * ARCHIVED → ACTIVE (re-activate)
+ *
+ * Invalid:
+ * ARCHIVED → DRAFT (cannot revert to draft)
+ * ACTIVE → DRAFT (cannot revert to draft)
+ */
+ private static final Map> VALID_TRANSITIONS =
+ Map.of(
+ ContextMemoryStatus.DRAFT,
+ Set.of(ContextMemoryStatus.ACTIVE, ContextMemoryStatus.ARCHIVED),
+ ContextMemoryStatus.ACTIVE, Set.of(ContextMemoryStatus.ARCHIVED),
+ ContextMemoryStatus.ARCHIVED, Set.of(ContextMemoryStatus.ACTIVE));
+
+ /** Validate that a status transition is allowed. */
+ public static void validateStatusTransition(ContextMemoryStatus from, ContextMemoryStatus to) {
+ if (from == to) {
+ return; // No change
+ }
+ Set allowed = VALID_TRANSITIONS.get(from);
+ if (allowed == null) {
+ throw new BadRequestException(
+ String.format("No transitions defined for status %s", from.value()));
+ }
+ if (!allowed.contains(to)) {
+ throw new BadRequestException(
+ String.format(
+ "Invalid memory status transition from %s to %s. Allowed transitions from %s: %s",
+ from.value(), to.value(), from.value(), allowed));
+ }
+ }
+
+ @Override
+ public EntityUpdater getUpdater(
+ ContextMemory original, ContextMemory updated, Operation operation, ChangeSource source) {
+ return new ContextMemoryUpdater(original, updated, operation);
+ }
+
+ public class ContextMemoryUpdater extends EntityUpdater {
+ public ContextMemoryUpdater(
+ ContextMemory original, ContextMemory updated, Operation operation) {
+ super(original, updated, operation);
+ }
+
+ @Override
+ public void entitySpecificUpdate(boolean consolidatingChanges) {
+ recordChange("title", original.getTitle(), updated.getTitle());
+ recordChange("summary", original.getSummary(), updated.getSummary());
+ recordChange("question", original.getQuestion(), updated.getQuestion());
+ recordChange("answer", original.getAnswer(), updated.getAnswer());
+ recordChange("memoryType", original.getMemoryType(), updated.getMemoryType());
+ recordChange("memoryScope", original.getMemoryScope(), updated.getMemoryScope());
+ recordChange("sourceType", original.getSourceType(), updated.getSourceType());
+ recordChange(
+ "sourceConversation", original.getSourceConversation(), updated.getSourceConversation());
+ recordChange(
+ "sourceHumanMessage", original.getSourceHumanMessage(), updated.getSourceHumanMessage());
+ recordChange(
+ "sourceAssistantMessage",
+ original.getSourceAssistantMessage(),
+ updated.getSourceAssistantMessage());
+ recordChange(
+ "machineRepresentation",
+ original.getMachineRepresentation(),
+ updated.getMachineRepresentation());
+
+ // Validate lifecycle transition before recording status change
+ if (original.getStatus() != null
+ && updated.getStatus() != null
+ && original.getStatus() != updated.getStatus()) {
+ validateStatusTransition(original.getStatus(), updated.getStatus());
+ }
+ recordChange("status", original.getStatus(), updated.getStatus());
+
+ recordChange("shareConfig", original.getShareConfig(), updated.getShareConfig());
+
+ // Relationship-backed fields: these helpers record the version change and delete only
+ // the specific changed refs (never a blanket delete), so the framework's
+ // domain --HAS--> memory edge is left intact.
+ updateFromRelationships(
+ "primaryEntity",
+ Entity.CONTEXT_MEMORY,
+ asRefList(original.getPrimaryEntity()),
+ asRefList(updated.getPrimaryEntity()),
+ Relationship.HAS,
+ Entity.CONTEXT_MEMORY,
+ original.getId());
+ updateFromRelationships(
+ "relatedEntities",
+ Entity.CONTEXT_MEMORY,
+ listOrEmpty(original.getRelatedEntities()),
+ listOrEmpty(updated.getRelatedEntities()),
+ Relationship.RELATED_TO,
+ Entity.CONTEXT_MEMORY,
+ original.getId());
+ updateFromRelationship(
+ "rootMemory",
+ Entity.CONTEXT_MEMORY,
+ original.getRootMemory(),
+ updated.getRootMemory(),
+ Relationship.CONTAINS,
+ Entity.CONTEXT_MEMORY,
+ original.getId());
+ updateFromRelationship(
+ "parentMemory",
+ Entity.CONTEXT_MEMORY,
+ original.getParentMemory(),
+ updated.getParentMemory(),
+ Relationship.PARENT_OF,
+ Entity.CONTEXT_MEMORY,
+ original.getId());
+
+ // usageCount and lastUsedAt are AI-retrieval telemetry, intentionally excluded from
+ // version history so routine retrieval does not churn the entity version.
+ }
+ }
+}
diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/context/ContextMemoryMapper.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/context/ContextMemoryMapper.java
new file mode 100644
index 000000000000..0cb2dd27efd2
--- /dev/null
+++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/context/ContextMemoryMapper.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2024 Collate
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openmetadata.service.resources.context;
+
+import org.openmetadata.schema.api.context.CreateContextMemory;
+import org.openmetadata.schema.entity.context.ContextMemory;
+import org.openmetadata.service.mapper.EntityMapper;
+
+public class ContextMemoryMapper implements EntityMapper {
+ @Override
+ public ContextMemory createToEntity(CreateContextMemory create, String user) {
+ // copy() owns the common fields: it sanitizes description and validates owners,
+ // domains, and reviewers. Re-setting them here would reintroduce the raw
+ // (unsanitized/unvalidated) values, so only ContextMemory-specific fields are set.
+ return copy(new ContextMemory(), create, user)
+ .withTitle(create.getTitle())
+ .withSummary(create.getSummary())
+ .withQuestion(create.getQuestion())
+ .withAnswer(create.getAnswer())
+ .withMemoryType(create.getMemoryType())
+ .withMemoryScope(create.getMemoryScope())
+ .withStatus(create.getStatus())
+ .withShareConfig(create.getShareConfig())
+ .withPrimaryEntity(create.getPrimaryEntity())
+ .withRelatedEntities(create.getRelatedEntities())
+ .withSourceType(create.getSourceType())
+ .withSourceConversation(create.getSourceConversation())
+ .withSourceHumanMessage(create.getSourceHumanMessage())
+ .withSourceAssistantMessage(create.getSourceAssistantMessage())
+ .withRootMemory(create.getRootMemory())
+ .withParentMemory(create.getParentMemory())
+ .withMachineRepresentation(create.getMachineRepresentation());
+ }
+}
diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/context/ContextMemoryResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/context/ContextMemoryResource.java
new file mode 100644
index 000000000000..9ba868a9bc19
--- /dev/null
+++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/context/ContextMemoryResource.java
@@ -0,0 +1,414 @@
+/*
+ * Copyright 2024 Collate
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openmetadata.service.resources.context;
+
+import io.swagger.v3.oas.annotations.ExternalDocumentation;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.ExampleObject;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.parameters.RequestBody;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.json.JsonPatch;
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.Max;
+import jakarta.validation.constraints.Min;
+import jakarta.ws.rs.Consumes;
+import jakarta.ws.rs.DELETE;
+import jakarta.ws.rs.DefaultValue;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.PATCH;
+import jakarta.ws.rs.POST;
+import jakarta.ws.rs.PUT;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.QueryParam;
+import jakarta.ws.rs.core.Context;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.core.SecurityContext;
+import jakarta.ws.rs.core.UriInfo;
+import java.util.List;
+import java.util.UUID;
+import lombok.extern.slf4j.Slf4j;
+import org.openmetadata.schema.api.context.CreateContextMemory;
+import org.openmetadata.schema.api.data.RestoreEntity;
+import org.openmetadata.schema.entity.context.ContextMemory;
+import org.openmetadata.schema.type.EntityHistory;
+import org.openmetadata.schema.type.Include;
+import org.openmetadata.schema.type.MetadataOperation;
+import org.openmetadata.schema.utils.ResultList;
+import org.openmetadata.service.Entity;
+import org.openmetadata.service.jdbi3.ContextMemoryRepository;
+import org.openmetadata.service.jdbi3.ListFilter;
+import org.openmetadata.service.limits.Limits;
+import org.openmetadata.service.resources.Collection;
+import org.openmetadata.service.resources.EntityResource;
+import org.openmetadata.service.security.Authorizer;
+
+@Slf4j
+@Tag(name = "Context Memories", description = "APIs for managing reusable Context Center memories.")
+@Path("/v1/contextCenter/memories")
+@Produces(MediaType.APPLICATION_JSON)
+@Consumes(MediaType.APPLICATION_JSON)
+@Collection(name = "contextMemories")
+public class ContextMemoryResource extends EntityResource {
+ public static final String COLLECTION_PATH = "v1/contextCenter/memories/";
+ public static final String FIELDS = "owners,tags,domains";
+
+ private final ContextMemoryMapper mapper = new ContextMemoryMapper();
+
+ public ContextMemoryResource(Authorizer authorizer, Limits limits) {
+ super(Entity.CONTEXT_MEMORY, authorizer, limits);
+ }
+
+ public static class ContextMemoryList extends ResultList {
+ /* Required for serde */
+ }
+
+ @Override
+ protected List getEntitySpecificOperations() {
+ return null;
+ }
+
+ @Override
+ public ContextMemory addHref(UriInfo uriInfo, ContextMemory memory) {
+ super.addHref(uriInfo, memory);
+ Entity.withHref(uriInfo, memory.getPrimaryEntity());
+ Entity.withHref(uriInfo, memory.getRelatedEntities());
+ Entity.withHref(uriInfo, memory.getRootMemory());
+ Entity.withHref(uriInfo, memory.getParentMemory());
+ return memory;
+ }
+
+ @GET
+ @Operation(
+ operationId = "listContextMemories",
+ summary = "List context memories",
+ description = "Get a paginated list of context memories.",
+ responses = {
+ @ApiResponse(
+ responseCode = "200",
+ description = "List of context memories",
+ content =
+ @Content(
+ mediaType = "application/json",
+ schema = @Schema(implementation = ContextMemoryList.class)))
+ })
+ public ResultList list(
+ @Context UriInfo uriInfo,
+ @Context SecurityContext securityContext,
+ @Parameter(
+ description = "Fields requested in the returned resource",
+ schema = @Schema(type = "string", example = FIELDS))
+ @QueryParam("fields")
+ String fieldsParam,
+ @Parameter(description = "Limit the number of results returned. (1 to 1000000, default = 10)")
+ @DefaultValue("10")
+ @Min(0)
+ @Max(1000000)
+ @QueryParam("limit")
+ int limitParam,
+ @Parameter(description = "Returns list of context memories before this cursor")
+ @QueryParam("before")
+ String before,
+ @Parameter(description = "Returns list of context memories after this cursor")
+ @QueryParam("after")
+ String after,
+ @Parameter(
+ description = "Include all, deleted, or non-deleted entities",
+ schema = @Schema(implementation = Include.class))
+ @QueryParam("include")
+ @DefaultValue("non-deleted")
+ Include include) {
+ return addHref(
+ uriInfo,
+ listInternal(
+ uriInfo,
+ securityContext,
+ fieldsParam,
+ new ListFilter(include),
+ limitParam,
+ before,
+ after));
+ }
+
+ @GET
+ @Path("/{id}")
+ @Operation(
+ operationId = "getContextMemory",
+ summary = "Get a memory by id",
+ description = "Get a context memory by `id`.",
+ responses = {
+ @ApiResponse(
+ responseCode = "200",
+ description = "The context memory",
+ content =
+ @Content(
+ mediaType = "application/json",
+ schema = @Schema(implementation = ContextMemory.class))),
+ @ApiResponse(responseCode = "404", description = "Memory not found")
+ })
+ public ContextMemory get(
+ @Context UriInfo uriInfo,
+ @Context SecurityContext securityContext,
+ @Parameter(description = "Id of the context memory", schema = @Schema(type = "UUID"))
+ @PathParam("id")
+ UUID id,
+ @Parameter(description = "Fields requested in the returned resource") @QueryParam("fields")
+ String fieldsParam,
+ @Parameter(description = "Include all, deleted, or non-deleted entities")
+ @QueryParam("include")
+ @DefaultValue("non-deleted")
+ Include include) {
+ return getInternal(uriInfo, securityContext, id, fieldsParam, include);
+ }
+
+ @GET
+ @Path("/name/{fqn}")
+ @Operation(
+ operationId = "getContextMemoryByFqn",
+ summary = "Get a memory by fully qualified name",
+ description = "Get a context memory by fully qualified name.",
+ responses = {
+ @ApiResponse(
+ responseCode = "200",
+ description = "The context memory",
+ content =
+ @Content(
+ mediaType = "application/json",
+ schema = @Schema(implementation = ContextMemory.class))),
+ @ApiResponse(responseCode = "404", description = "Memory not found")
+ })
+ public ContextMemory getByName(
+ @Context UriInfo uriInfo,
+ @Context SecurityContext securityContext,
+ @Parameter(description = "Fully qualified name of the context memory") @PathParam("fqn")
+ String fqn,
+ @Parameter(description = "Fields requested in the returned resource") @QueryParam("fields")
+ String fieldsParam,
+ @Parameter(description = "Include deleted memories")
+ @QueryParam("include")
+ @DefaultValue("non-deleted")
+ Include include) {
+ return getByNameInternal(uriInfo, securityContext, fqn, fieldsParam, include);
+ }
+
+ @GET
+ @Path("/{id}/versions")
+ @Operation(
+ operationId = "listAllContextMemoryVersions",
+ summary = "List context memory versions",
+ description = "Get a list of all the versions of a context memory identified by `id`.",
+ responses = {
+ @ApiResponse(
+ responseCode = "200",
+ description = "List of versions",
+ content =
+ @Content(
+ mediaType = "application/json",
+ schema = @Schema(implementation = EntityHistory.class)))
+ })
+ public EntityHistory listVersions(
+ @Context SecurityContext securityContext,
+ @Parameter(description = "Id of the context memory", schema = @Schema(type = "UUID"))
+ @PathParam("id")
+ UUID id) {
+ return listVersionsInternal(securityContext, id);
+ }
+
+ @GET
+ @Path("/{id}/versions/{version}")
+ @Operation(
+ operationId = "getSpecificContextMemoryVersion",
+ summary = "Get a version of a context memory",
+ description = "Get a version of a context memory by given `id`.",
+ responses = {
+ @ApiResponse(
+ responseCode = "200",
+ description = "Context memory version details",
+ content =
+ @Content(
+ mediaType = "application/json",
+ schema = @Schema(implementation = ContextMemory.class)))
+ })
+ public ContextMemory getVersion(
+ @Context SecurityContext securityContext,
+ @Parameter(description = "Id of the context memory", schema = @Schema(type = "UUID"))
+ @PathParam("id")
+ UUID id,
+ @Parameter(description = "Context memory version", schema = @Schema(type = "string"))
+ @PathParam("version")
+ String version) {
+ return getVersionInternal(securityContext, id, version);
+ }
+
+ @POST
+ @Operation(
+ operationId = "createContextMemory",
+ summary = "Create a memory",
+ description = "Create a new context memory.",
+ responses = {
+ @ApiResponse(
+ responseCode = "200",
+ description = "The created memory",
+ content =
+ @Content(
+ mediaType = "application/json",
+ schema = @Schema(implementation = ContextMemory.class)))
+ })
+ public Response create(
+ @Context UriInfo uriInfo,
+ @Context SecurityContext securityContext,
+ @Valid CreateContextMemory create) {
+ ContextMemory memory =
+ mapper.createToEntity(create, securityContext.getUserPrincipal().getName());
+ return create(uriInfo, securityContext, memory);
+ }
+
+ @PUT
+ @Operation(
+ operationId = "createOrUpdateContextMemory",
+ summary = "Create or update a memory",
+ description = "Create a new context memory, or update an existing one if it already exists.",
+ responses = {
+ @ApiResponse(
+ responseCode = "200",
+ description = "The updated memory",
+ content =
+ @Content(
+ mediaType = "application/json",
+ schema = @Schema(implementation = ContextMemory.class)))
+ })
+ public Response createOrUpdate(
+ @Context UriInfo uriInfo,
+ @Context SecurityContext securityContext,
+ @Valid CreateContextMemory create) {
+ ContextMemory memory =
+ mapper.createToEntity(create, securityContext.getUserPrincipal().getName());
+ return createOrUpdate(uriInfo, securityContext, memory);
+ }
+
+ @PATCH
+ @Path("/{id}")
+ @Consumes(MediaType.APPLICATION_JSON_PATCH_JSON)
+ @Operation(
+ operationId = "patchContextMemory",
+ summary = "Update a memory",
+ description = "Apply a JSONPatch to a context memory.",
+ externalDocs =
+ @ExternalDocumentation(
+ description = "JsonPatch RFC",
+ url = "https://tools.ietf.org/html/rfc6902"))
+ public Response patch(
+ @Context UriInfo uriInfo,
+ @Context SecurityContext securityContext,
+ @Parameter(description = "Id of the context memory", schema = @Schema(type = "UUID"))
+ @PathParam("id")
+ UUID id,
+ @RequestBody(
+ description = "JsonPatch with array of operations",
+ content =
+ @Content(
+ mediaType = MediaType.APPLICATION_JSON_PATCH_JSON,
+ examples =
+ @ExampleObject("[{op:replace, path:/displayName, value: 'New name'}]")))
+ JsonPatch patch) {
+ return patchInternal(uriInfo, securityContext, id, patch);
+ }
+
+ @DELETE
+ @Path("/{id}")
+ @Operation(
+ operationId = "deleteContextMemory",
+ summary = "Delete a memory by id",
+ description = "Delete a context memory by `id`.",
+ responses = {
+ @ApiResponse(responseCode = "200", description = "OK"),
+ @ApiResponse(responseCode = "404", description = "Memory not found")
+ })
+ public Response delete(
+ @Context UriInfo uriInfo,
+ @Context SecurityContext securityContext,
+ @Parameter(description = "Recursively delete this entity and its children. (Default = false)")
+ @DefaultValue("false")
+ @QueryParam("recursive")
+ boolean recursive,
+ @Parameter(description = "Hard delete the entity. (Default = false)")
+ @DefaultValue("false")
+ @QueryParam("hardDelete")
+ boolean hardDelete,
+ @Parameter(description = "Id of the context memory", schema = @Schema(type = "UUID"))
+ @PathParam("id")
+ UUID id) {
+ return delete(uriInfo, securityContext, id, recursive, hardDelete);
+ }
+
+ @DELETE
+ @Path("/name/{fqn}")
+ @Operation(
+ operationId = "deleteContextMemoryByFqn",
+ summary = "Delete a memory by fully qualified name",
+ description = "Delete a context memory by `fullyQualifiedName`.",
+ responses = {
+ @ApiResponse(responseCode = "200", description = "OK"),
+ @ApiResponse(responseCode = "404", description = "Memory not found")
+ })
+ public Response deleteByFqn(
+ @Context UriInfo uriInfo,
+ @Context SecurityContext securityContext,
+ @Parameter(description = "Recursively delete this entity and its children. (Default = false)")
+ @DefaultValue("false")
+ @QueryParam("recursive")
+ boolean recursive,
+ @Parameter(description = "Hard delete the entity. (Default = false)")
+ @DefaultValue("false")
+ @QueryParam("hardDelete")
+ boolean hardDelete,
+ @Parameter(description = "Fully qualified name of the context memory") @PathParam("fqn")
+ String fqn) {
+ return deleteByName(uriInfo, securityContext, fqn, recursive, hardDelete);
+ }
+
+ @PUT
+ @Path("/restore")
+ @Operation(
+ operationId = "restoreContextMemory",
+ summary = "Restore a soft-deleted memory",
+ description = "Restore a previously soft-deleted context memory.",
+ responses = {
+ @ApiResponse(
+ responseCode = "200",
+ description = "The restored memory",
+ content =
+ @Content(
+ mediaType = "application/json",
+ schema = @Schema(implementation = ContextMemory.class)))
+ })
+ public Response restore(
+ @Context UriInfo uriInfo,
+ @Context SecurityContext securityContext,
+ @RequestBody(
+ description = "Id of the context memory to restore",
+ content =
+ @Content(
+ mediaType = "application/json",
+ schema = @Schema(type = "string", format = "uuid")))
+ RestoreEntity restore) {
+ return restoreEntity(uriInfo, securityContext, restore.getId());
+ }
+}
diff --git a/openmetadata-service/src/test/java/org/openmetadata/service/resources/context/ContextMemoryStatusTransitionTest.java b/openmetadata-service/src/test/java/org/openmetadata/service/resources/context/ContextMemoryStatusTransitionTest.java
new file mode 100644
index 000000000000..d8e693dbbda5
--- /dev/null
+++ b/openmetadata-service/src/test/java/org/openmetadata/service/resources/context/ContextMemoryStatusTransitionTest.java
@@ -0,0 +1,52 @@
+package org.openmetadata.service.resources.context;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import jakarta.ws.rs.BadRequestException;
+import org.junit.jupiter.api.Test;
+import org.openmetadata.schema.entity.context.ContextMemoryStatus;
+import org.openmetadata.service.jdbi3.ContextMemoryRepository;
+
+class ContextMemoryStatusTransitionTest {
+
+ @Test
+ void testValidStatusTransitionsAreAccepted() {
+ ContextMemoryRepository.validateStatusTransition(
+ ContextMemoryStatus.DRAFT, ContextMemoryStatus.ACTIVE);
+ ContextMemoryRepository.validateStatusTransition(
+ ContextMemoryStatus.DRAFT, ContextMemoryStatus.ARCHIVED);
+ ContextMemoryRepository.validateStatusTransition(
+ ContextMemoryStatus.ACTIVE, ContextMemoryStatus.ARCHIVED);
+ ContextMemoryRepository.validateStatusTransition(
+ ContextMemoryStatus.ARCHIVED, ContextMemoryStatus.ACTIVE);
+ }
+
+ @Test
+ void testNoOpStatusTransitionIsAccepted() {
+ ContextMemoryRepository.validateStatusTransition(
+ ContextMemoryStatus.ACTIVE, ContextMemoryStatus.ACTIVE);
+ ContextMemoryRepository.validateStatusTransition(
+ ContextMemoryStatus.DRAFT, ContextMemoryStatus.DRAFT);
+ }
+
+ @Test
+ void testActiveToDraftIsRejected() {
+ BadRequestException exception =
+ assertThrows(
+ BadRequestException.class,
+ () ->
+ ContextMemoryRepository.validateStatusTransition(
+ ContextMemoryStatus.ACTIVE, ContextMemoryStatus.DRAFT));
+ assertTrue(exception.getMessage().contains("Invalid memory status transition"));
+ }
+
+ @Test
+ void testArchivedToDraftIsRejected() {
+ assertThrows(
+ BadRequestException.class,
+ () ->
+ ContextMemoryRepository.validateStatusTransition(
+ ContextMemoryStatus.ARCHIVED, ContextMemoryStatus.DRAFT));
+ }
+}
diff --git a/openmetadata-spec/src/main/antlr4/org/openmetadata/schema/EntityLink.g4 b/openmetadata-spec/src/main/antlr4/org/openmetadata/schema/EntityLink.g4
index b07dff497711..847d580dc5f8 100644
--- a/openmetadata-spec/src/main/antlr4/org/openmetadata/schema/EntityLink.g4
+++ b/openmetadata-spec/src/main/antlr4/org/openmetadata/schema/EntityLink.g4
@@ -102,6 +102,7 @@ ENTITY_TYPE
| 'folder'
| 'contextFile'
| 'contextFileContent'
+ | 'contextMemory'
| 'type'
| 'aiApplication'
| 'llmModel'
diff --git a/openmetadata-spec/src/main/resources/json/schema/api/context/createContextMemory.json b/openmetadata-spec/src/main/resources/json/schema/api/context/createContextMemory.json
new file mode 100644
index 000000000000..4d87cea0cc07
--- /dev/null
+++ b/openmetadata-spec/src/main/resources/json/schema/api/context/createContextMemory.json
@@ -0,0 +1,100 @@
+{
+ "$id": "https://open-metadata.org/schema/api/context/createContextMemory.json",
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "title": "CreateContextMemory",
+ "description": "Request to create a reusable Context Center memory.",
+ "type": "object",
+ "javaType": "org.openmetadata.schema.api.context.CreateContextMemory",
+ "javaInterfaces": ["org.openmetadata.schema.CreateEntity"],
+ "properties": {
+ "name": {
+ "description": "Stable system name for the memory.",
+ "$ref": "../../type/basic.json#/definitions/entityName"
+ },
+ "displayName": {
+ "description": "Display name for the memory.",
+ "type": "string"
+ },
+ "description": {
+ "description": "Optional markdown description for the memory.",
+ "$ref": "../../type/basic.json#/definitions/markdown"
+ },
+ "title": {
+ "description": "Short title shown in Context Center.",
+ "type": "string"
+ },
+ "summary": {
+ "description": "Optional summary of the memory.",
+ "type": "string"
+ },
+ "question": {
+ "description": "Canonical question or instruction represented by this memory.",
+ "type": "string"
+ },
+ "answer": {
+ "description": "Canonical answer or retained guidance represented by this memory.",
+ "type": "string"
+ },
+ "memoryType": {
+ "$ref": "../../entity/context/contextMemory.json#/definitions/memoryType"
+ },
+ "memoryScope": {
+ "$ref": "../../entity/context/contextMemory.json#/definitions/memoryScope"
+ },
+ "status": {
+ "$ref": "../../entity/context/contextMemory.json#/definitions/memoryStatus"
+ },
+ "shareConfig": {
+ "$ref": "../../entity/context/contextMemory.json#/definitions/shareConfig"
+ },
+ "primaryEntity": {
+ "$ref": "../../type/entityReference.json"
+ },
+ "relatedEntities": {
+ "$ref": "../../type/entityReferenceList.json"
+ },
+ "sourceType": {
+ "$ref": "../../entity/context/contextMemory.json#/definitions/sourceType"
+ },
+ "sourceConversation": {
+ "$ref": "../../type/basic.json#/definitions/uuid"
+ },
+ "sourceHumanMessage": {
+ "$ref": "../../type/basic.json#/definitions/uuid"
+ },
+ "sourceAssistantMessage": {
+ "$ref": "../../type/basic.json#/definitions/uuid"
+ },
+ "rootMemory": {
+ "$ref": "../../type/entityReference.json"
+ },
+ "parentMemory": {
+ "$ref": "../../type/entityReference.json"
+ },
+ "machineRepresentation": {
+ "$ref": "../../entity/context/contextMemory.json#/definitions/machineRepresentation"
+ },
+ "owners": {
+ "description": "Owners of this memory.",
+ "$ref": "../../type/entityReferenceList.json",
+ "default": null
+ },
+ "tags": {
+ "description": "Tags associated with this memory.",
+ "type": "array",
+ "items": {
+ "$ref": "../../type/tagLabel.json"
+ },
+ "default": null
+ },
+ "domains": {
+ "description": "Fully qualified names of the domains this memory belongs to.",
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ },
+ "required": ["name", "question", "answer"],
+ "additionalProperties": false
+}
diff --git a/openmetadata-spec/src/main/resources/json/schema/entity/context/contextMemory.json b/openmetadata-spec/src/main/resources/json/schema/entity/context/contextMemory.json
new file mode 100644
index 000000000000..9418ca558475
--- /dev/null
+++ b/openmetadata-spec/src/main/resources/json/schema/entity/context/contextMemory.json
@@ -0,0 +1,304 @@
+{
+ "$id": "https://open-metadata.org/schema/entity/context/contextMemory.json",
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "title": "ContextMemory",
+ "$comment": "@om-entity-type",
+ "description": "Reusable context memory for Context Center and AI-assisted retrieval.",
+ "type": "object",
+ "javaType": "org.openmetadata.schema.entity.context.ContextMemory",
+ "javaInterfaces": ["org.openmetadata.schema.EntityInterface"],
+ "definitions": {
+ "memoryType": {
+ "javaType": "org.openmetadata.schema.entity.context.ContextMemoryType",
+ "description": "High-level type of reusable memory.",
+ "type": "string",
+ "enum": ["Preference", "UseCase", "Note", "Runbook", "Faq"],
+ "javaEnums": [
+ { "name": "PREFERENCE" },
+ { "name": "USE_CASE" },
+ { "name": "NOTE" },
+ { "name": "RUNBOOK" },
+ { "name": "FAQ" }
+ ],
+ "default": "Note"
+ },
+ "memoryScope": {
+ "javaType": "org.openmetadata.schema.entity.context.ContextMemoryScope",
+ "description": "Scope where the memory should be applied.",
+ "type": "string",
+ "enum": ["UserGlobal", "EntityScoped"],
+ "javaEnums": [
+ { "name": "USER_GLOBAL" },
+ { "name": "ENTITY_SCOPED" }
+ ],
+ "default": "EntityScoped"
+ },
+ "memoryStatus": {
+ "javaType": "org.openmetadata.schema.entity.context.ContextMemoryStatus",
+ "description": "Lifecycle state of the memory. Any status may be set at creation (e.g. importing an already-archived memory); the Draft -> Active -> Archived transition rules are only enforced on subsequent updates.",
+ "type": "string",
+ "enum": ["Draft", "Active", "Archived"],
+ "javaEnums": [
+ { "name": "DRAFT" },
+ { "name": "ACTIVE" },
+ { "name": "ARCHIVED" }
+ ],
+ "default": "Active"
+ },
+ "sourceType": {
+ "javaType": "org.openmetadata.schema.entity.context.ContextMemorySourceType",
+ "description": "How the memory was created.",
+ "type": "string",
+ "enum": ["Manual", "ChatPromotion", "RememberRequest"],
+ "javaEnums": [
+ { "name": "MANUAL" },
+ { "name": "CHAT_PROMOTION" },
+ { "name": "REMEMBER_REQUEST" }
+ ],
+ "default": "Manual"
+ },
+ "shareVisibility": {
+ "javaType": "org.openmetadata.schema.entity.context.MemoryVisibility",
+ "description": "Visibility level for the memory.",
+ "type": "string",
+ "enum": ["Private", "Entity", "Shared"],
+ "javaEnums": [
+ { "name": "PRIVATE" },
+ { "name": "ENTITY" },
+ { "name": "SHARED" }
+ ],
+ "default": "Private"
+ },
+ "shareRole": {
+ "javaType": "org.openmetadata.schema.entity.context.MemoryShareRole",
+ "description": "Role granted to a shared principal.",
+ "type": "string",
+ "enum": ["Viewer", "Editor"],
+ "javaEnums": [
+ { "name": "VIEWER" },
+ { "name": "EDITOR" }
+ ],
+ "default": "Viewer"
+ },
+ "sharedPrincipal": {
+ "javaType": "org.openmetadata.schema.entity.context.MemorySharedPrincipal",
+ "description": "A principal granted access to the memory.",
+ "type": "object",
+ "properties": {
+ "principal": {
+ "description": "Principal receiving access. Supported principal types are user, team, and domain.",
+ "$ref": "../../type/entityReference.json"
+ },
+ "role": {
+ "description": "Role granted to the principal.",
+ "$ref": "#/definitions/shareRole"
+ }
+ },
+ "additionalProperties": false
+ },
+ "shareConfig": {
+ "javaType": "org.openmetadata.schema.entity.context.MemoryShareConfig",
+ "description": "Visibility and sharing configuration for the memory.",
+ "type": "object",
+ "properties": {
+ "visibility": {
+ "$ref": "#/definitions/shareVisibility"
+ },
+ "sharedWith": {
+ "description": "Explicit principals the memory is shared with.",
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/sharedPrincipal"
+ },
+ "default": []
+ }
+ },
+ "additionalProperties": false
+ },
+ "machineRepresentationStatus": {
+ "javaType": "org.openmetadata.schema.entity.context.MachineRepresentationStatus",
+ "description": "Availability state of the machine-oriented representation.",
+ "type": "string",
+ "enum": ["Pending", "Ready", "Stale", "Failed"],
+ "javaEnums": [
+ { "name": "PENDING" },
+ { "name": "READY" },
+ { "name": "STALE" },
+ { "name": "FAILED" }
+ ],
+ "default": "Pending"
+ },
+ "machineRepresentation": {
+ "javaType": "org.openmetadata.schema.entity.context.ContextMemoryRepresentation",
+ "description": "Optional machine-oriented representation used for prompt packing.",
+ "type": "object",
+ "properties": {
+ "format": {
+ "description": "Representation format identifier.",
+ "type": "string"
+ },
+ "version": {
+ "description": "Version of the representation format.",
+ "type": "string"
+ },
+ "content": {
+ "description": "Compressed or transformed memory content.",
+ "type": "string"
+ },
+ "generatedFromHash": {
+ "description": "Hash of the canonical source content used to generate this representation.",
+ "type": "string"
+ },
+ "generatedAt": {
+ "description": "Timestamp when the representation was generated.",
+ "$ref": "../../type/basic.json#/definitions/timestamp"
+ },
+ "status": {
+ "$ref": "#/definitions/machineRepresentationStatus"
+ }
+ },
+ "additionalProperties": false
+ }
+ },
+ "properties": {
+ "id": {
+ "description": "Unique identifier of the memory.",
+ "$ref": "../../type/basic.json#/definitions/uuid"
+ },
+ "name": {
+ "description": "Stable system name for the memory.",
+ "$ref": "../../type/basic.json#/definitions/entityName"
+ },
+ "fullyQualifiedName": {
+ "description": "Fully qualified name of the memory.",
+ "$ref": "../../type/basic.json#/definitions/fullyQualifiedEntityName"
+ },
+ "displayName": {
+ "description": "Display name of the memory.",
+ "type": "string"
+ },
+ "description": {
+ "description": "Optional markdown description for the memory.",
+ "$ref": "../../type/basic.json#/definitions/markdown"
+ },
+ "title": {
+ "description": "Short title shown in Context Center.",
+ "type": "string"
+ },
+ "summary": {
+ "description": "Optional summary of the memory.",
+ "type": "string"
+ },
+ "question": {
+ "description": "Canonical question or instruction represented by this memory.",
+ "type": "string"
+ },
+ "answer": {
+ "description": "Canonical answer or retained guidance represented by this memory.",
+ "type": "string"
+ },
+ "memoryType": {
+ "$ref": "#/definitions/memoryType"
+ },
+ "memoryScope": {
+ "$ref": "#/definitions/memoryScope"
+ },
+ "status": {
+ "$ref": "#/definitions/memoryStatus"
+ },
+ "shareConfig": {
+ "$ref": "#/definitions/shareConfig"
+ },
+ "primaryEntity": {
+ "description": "Primary entity this memory should attach to for reuse.",
+ "$ref": "../../type/entityReference.json"
+ },
+ "relatedEntities": {
+ "description": "Additional related entities this memory applies to.",
+ "$ref": "../../type/entityReferenceList.json"
+ },
+ "sourceType": {
+ "$ref": "#/definitions/sourceType"
+ },
+ "sourceConversation": {
+ "description": "Conversation identifier that produced this memory.",
+ "$ref": "../../type/basic.json#/definitions/uuid"
+ },
+ "sourceHumanMessage": {
+ "description": "Human message identifier used to produce this memory.",
+ "$ref": "../../type/basic.json#/definitions/uuid"
+ },
+ "sourceAssistantMessage": {
+ "description": "Assistant message identifier used to produce this memory.",
+ "$ref": "../../type/basic.json#/definitions/uuid"
+ },
+ "rootMemory": {
+ "description": "Root memory in an append-style memory thread.",
+ "$ref": "../../type/entityReference.json"
+ },
+ "parentMemory": {
+ "description": "Immediate parent memory in an append-style thread.",
+ "$ref": "../../type/entityReference.json"
+ },
+ "machineRepresentation": {
+ "$ref": "#/definitions/machineRepresentation"
+ },
+ "usageCount": {
+ "description": "How many times this memory has been used in AI-assisted retrieval.",
+ "type": "integer",
+ "default": 0
+ },
+ "lastUsedAt": {
+ "description": "Last time the memory was used by AI-assisted retrieval.",
+ "$ref": "../../type/basic.json#/definitions/timestamp"
+ },
+ "owners": {
+ "description": "Owners of this memory.",
+ "$ref": "../../type/entityReferenceList.json",
+ "default": null
+ },
+ "tags": {
+ "description": "Tags associated with this memory.",
+ "type": "array",
+ "items": {
+ "$ref": "../../type/tagLabel.json"
+ },
+ "default": null
+ },
+ "domains": {
+ "description": "Domains this memory belongs to.",
+ "$ref": "../../type/entityReferenceList.json"
+ },
+ "version": {
+ "description": "Metadata version of the entity.",
+ "$ref": "../../type/entityHistory.json#/definitions/entityVersion"
+ },
+ "updatedAt": {
+ "description": "Last update time in Unix epoch time milliseconds.",
+ "$ref": "../../type/basic.json#/definitions/timestamp"
+ },
+ "updatedBy": {
+ "description": "User who made the update.",
+ "type": "string"
+ },
+ "href": {
+ "description": "Link to this resource.",
+ "$ref": "../../type/basic.json#/definitions/href"
+ },
+ "changeDescription": {
+ "description": "Change that led to this version.",
+ "$ref": "../../type/entityHistory.json#/definitions/changeDescription"
+ },
+ "incrementalChangeDescription": {
+ "description": "Incremental change that led to this version.",
+ "$ref": "../../type/entityHistory.json#/definitions/changeDescription"
+ },
+ "deleted": {
+ "description": "When true indicates the entity has been soft deleted.",
+ "type": "boolean",
+ "default": false
+ }
+ },
+ "required": ["id", "name"],
+ "additionalProperties": false
+}
diff --git a/openmetadata-ui/src/main/resources/ui/src/generated/api/context/createContextMemory.ts b/openmetadata-ui/src/main/resources/ui/src/generated/api/context/createContextMemory.ts
new file mode 100644
index 000000000000..86121ceca80f
--- /dev/null
+++ b/openmetadata-ui/src/main/resources/ui/src/generated/api/context/createContextMemory.ts
@@ -0,0 +1,448 @@
+/*
+ * Copyright 2026 Collate.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/**
+ * Request to create a reusable Context Center memory.
+ */
+export interface CreateContextMemory {
+ /**
+ * Canonical answer or retained guidance represented by this memory.
+ */
+ answer: string;
+ /**
+ * Optional markdown description for the memory.
+ */
+ description?: string;
+ /**
+ * Display name for the memory.
+ */
+ displayName?: string;
+ /**
+ * Fully qualified names of the domains this memory belongs to.
+ */
+ domains?: string[];
+ machineRepresentation?: MachineRepresentation;
+ memoryScope?: MemoryScope;
+ memoryType?: MemoryType;
+ /**
+ * Stable system name for the memory.
+ */
+ name: string;
+ /**
+ * Owners of this memory.
+ */
+ owners?: EntityReference[];
+ parentMemory?: EntityReference;
+ primaryEntity?: EntityReference;
+ /**
+ * Canonical question or instruction represented by this memory.
+ */
+ question: string;
+ relatedEntities?: EntityReference[];
+ rootMemory?: EntityReference;
+ shareConfig?: ShareConfig;
+ sourceAssistantMessage?: string;
+ sourceConversation?: string;
+ sourceHumanMessage?: string;
+ sourceType?: SourceType;
+ status?: MemoryStatus;
+ /**
+ * Optional summary of the memory.
+ */
+ summary?: string;
+ /**
+ * Tags associated with this memory.
+ */
+ tags?: TagLabel[];
+ /**
+ * Short title shown in Context Center.
+ */
+ title?: string;
+}
+
+/**
+ * Optional machine-oriented representation used for prompt packing.
+ */
+export interface MachineRepresentation {
+ /**
+ * Compressed or transformed memory content.
+ */
+ content?: string;
+ /**
+ * Representation format identifier.
+ */
+ format?: string;
+ /**
+ * Timestamp when the representation was generated.
+ */
+ generatedAt?: number;
+ /**
+ * Hash of the canonical source content used to generate this representation.
+ */
+ generatedFromHash?: string;
+ status?: MachineRepresentationStatus;
+ /**
+ * Version of the representation format.
+ */
+ version?: string;
+}
+
+/**
+ * Availability state of the machine-oriented representation.
+ */
+export enum MachineRepresentationStatus {
+ Failed = "Failed",
+ Pending = "Pending",
+ Ready = "Ready",
+ Stale = "Stale",
+}
+
+/**
+ * Scope where the memory should be applied.
+ */
+export enum MemoryScope {
+ EntityScoped = "EntityScoped",
+ UserGlobal = "UserGlobal",
+}
+
+/**
+ * High-level type of reusable memory.
+ */
+export enum MemoryType {
+ FAQ = "Faq",
+ Note = "Note",
+ Preference = "Preference",
+ Runbook = "Runbook",
+ UseCase = "UseCase",
+}
+
+/**
+ * Owners of this memory.
+ *
+ * This schema defines the EntityReferenceList type used for referencing an entity.
+ * EntityReference is used for capturing relationships from one entity to another. For
+ * example, a table has an attribute called database of type EntityReference that captures
+ * the relationship of a table `belongs to a` database.
+ *
+ * This schema defines the EntityReference type used for referencing an entity.
+ * EntityReference is used for capturing relationships from one entity to another. For
+ * example, a table has an attribute called database of type EntityReference that captures
+ * the relationship of a table `belongs to a` database.
+ *
+ * Principal receiving access. Supported principal types are user, team, and domain.
+ */
+export interface EntityReference {
+ /**
+ * If true the entity referred to has been soft-deleted.
+ */
+ deleted?: boolean;
+ /**
+ * Optional description of entity.
+ */
+ description?: string;
+ /**
+ * Display Name that identifies this entity.
+ */
+ displayName?: string;
+ /**
+ * Fully qualified name of the entity instance. For entities such as tables, databases
+ * fullyQualifiedName is returned in this field. For entities that don't have name hierarchy
+ * such as `user` and `team` this will be same as the `name` field.
+ */
+ fullyQualifiedName?: string;
+ /**
+ * Link to the entity resource.
+ */
+ href?: string;
+ /**
+ * Unique identifier that identifies an entity instance.
+ */
+ id: string;
+ /**
+ * If true the relationship indicated by this entity reference is inherited from the parent
+ * entity.
+ */
+ inherited?: boolean;
+ /**
+ * Name of the entity instance.
+ */
+ name?: string;
+ /**
+ * Entity type/class name - Examples: `database`, `table`, `metrics`, `databaseService`,
+ * `dashboardService`...
+ */
+ type: string;
+}
+
+/**
+ * Visibility and sharing configuration for the memory.
+ */
+export interface ShareConfig {
+ /**
+ * Explicit principals the memory is shared with.
+ */
+ sharedWith?: SharedPrincipal[];
+ visibility?: ShareVisibility;
+}
+
+/**
+ * A principal granted access to the memory.
+ */
+export interface SharedPrincipal {
+ /**
+ * Principal receiving access. Supported principal types are user, team, and domain.
+ */
+ principal?: EntityReference;
+ /**
+ * Role granted to the principal.
+ */
+ role?: ShareRole;
+}
+
+/**
+ * Role granted to the principal.
+ *
+ * Role granted to a shared principal.
+ */
+export enum ShareRole {
+ Editor = "Editor",
+ Viewer = "Viewer",
+}
+
+/**
+ * Visibility level for the memory.
+ */
+export enum ShareVisibility {
+ Entity = "Entity",
+ Private = "Private",
+ Shared = "Shared",
+}
+
+/**
+ * How the memory was created.
+ */
+export enum SourceType {
+ ChatPromotion = "ChatPromotion",
+ Manual = "Manual",
+ RememberRequest = "RememberRequest",
+}
+
+/**
+ * Lifecycle state of the memory. Any status may be set at creation (e.g. importing an
+ * already-archived memory); the Draft -> Active -> Archived transition rules are only
+ * enforced on subsequent updates.
+ */
+export enum MemoryStatus {
+ Active = "Active",
+ Archived = "Archived",
+ Draft = "Draft",
+}
+
+/**
+ * This schema defines the type for labeling an entity with a Tag.
+ */
+export interface TagLabel {
+ /**
+ * Timestamp when this tag was applied in ISO 8601 format
+ */
+ appliedAt?: Date;
+ /**
+ * Who it is that applied this tag (e.g: a bot, AI or a human)
+ */
+ appliedBy?: string;
+ /**
+ * Description for the tag label.
+ */
+ description?: string;
+ /**
+ * Display Name that identifies this tag.
+ */
+ displayName?: string;
+ /**
+ * Link to the tag resource.
+ */
+ href?: string;
+ /**
+ * Label type describes how a tag label was applied. 'Manual' indicates the tag label was
+ * applied by a person. 'Derived' indicates a tag label was derived using the associated tag
+ * relationship (see Classification.json for more details). 'Propagated` indicates a tag
+ * label was propagated from upstream based on lineage. 'Automated' is used when a tool was
+ * used to determine the tag label.
+ */
+ labelType: LabelType;
+ /**
+ * Additional metadata associated with this tag label, such as recognizer information for
+ * automatically applied tags.
+ */
+ metadata?: TagLabelMetadata;
+ /**
+ * Name of the tag or glossary term.
+ */
+ name?: string;
+ /**
+ * An explanation of why this tag was proposed, specially for autoclassification tags
+ */
+ reason?: string;
+ /**
+ * Label is from Tags or Glossary.
+ */
+ source: TagSource;
+ /**
+ * 'Suggested' state is used when a tag label is suggested by users or tools. Owner of the
+ * entity must confirm the suggested labels before it is marked as 'Confirmed'.
+ */
+ state: State;
+ style?: Style;
+ tagFQN: string;
+}
+
+/**
+ * Label type describes how a tag label was applied. 'Manual' indicates the tag label was
+ * applied by a person. 'Derived' indicates a tag label was derived using the associated tag
+ * relationship (see Classification.json for more details). 'Propagated` indicates a tag
+ * label was propagated from upstream based on lineage. 'Automated' is used when a tool was
+ * used to determine the tag label.
+ */
+export enum LabelType {
+ Automated = "Automated",
+ Derived = "Derived",
+ Generated = "Generated",
+ Manual = "Manual",
+ Propagated = "Propagated",
+}
+
+/**
+ * Additional metadata associated with this tag label, such as recognizer information for
+ * automatically applied tags.
+ *
+ * Additional metadata associated with a tag label, including information about how the tag
+ * was applied.
+ */
+export interface TagLabelMetadata {
+ /**
+ * Epoch time in milliseconds when the certification tag expires
+ */
+ expiryDate?: number;
+ /**
+ * Metadata about the recognizer that automatically applied this tag
+ */
+ recognizer?: TagLabelRecognizerMetadata;
+}
+
+/**
+ * Metadata about the recognizer that automatically applied this tag
+ *
+ * Metadata about the recognizer that applied a tag, including scoring and pattern
+ * information.
+ */
+export interface TagLabelRecognizerMetadata {
+ /**
+ * Details of patterns that matched during recognition
+ */
+ patterns?: PatternMatch[];
+ /**
+ * Unique identifier of the recognizer that applied this tag
+ */
+ recognizerId: string;
+ /**
+ * Human-readable name of the recognizer
+ */
+ recognizerName: string;
+ /**
+ * Confidence score assigned by the recognizer (0.0 to 1.0)
+ */
+ score: number;
+ /**
+ * What the recognizer analyzed to apply this tag
+ */
+ target?: Target;
+}
+
+/**
+ * Information about a pattern that matched during recognition
+ */
+export interface PatternMatch {
+ /**
+ * Name of the pattern that matched
+ */
+ name: string;
+ /**
+ * Regular expression or pattern definition
+ */
+ regex?: string;
+ /**
+ * Confidence score for this specific pattern match
+ */
+ score: number;
+}
+
+/**
+ * What the recognizer analyzed to apply this tag
+ */
+export enum Target {
+ ColumnName = "column_name",
+ Content = "content",
+}
+
+/**
+ * Label is from Tags or Glossary.
+ */
+export enum TagSource {
+ Classification = "Classification",
+ Glossary = "Glossary",
+}
+
+/**
+ * 'Suggested' state is used when a tag label is suggested by users or tools. Owner of the
+ * entity must confirm the suggested labels before it is marked as 'Confirmed'.
+ */
+export enum State {
+ Confirmed = "Confirmed",
+ Suggested = "Suggested",
+}
+
+/**
+ * UI Style is used to associate a color code and/or icon to entity to customize the look of
+ * that entity in UI.
+ */
+export interface Style {
+ /**
+ * Hex Color Code to mark an entity such as GlossaryTerm, Tag, Domain or Data Product.
+ */
+ color?: string;
+ /**
+ * Cover image configuration for the entity.
+ */
+ coverImage?: CoverImage;
+ /**
+ * An icon to associate with GlossaryTerm, Tag, Domain or Data Product.
+ */
+ iconURL?: string;
+}
+
+/**
+ * Cover image configuration for the entity.
+ *
+ * Cover image configuration for an entity. This is used to display a banner or header image
+ * for entities like Domain, Glossary, Data Product, etc.
+ */
+export interface CoverImage {
+ /**
+ * Position of the cover image in CSS background-position format. Supports keywords (top,
+ * center, bottom) or pixel values (e.g., '20px 30px').
+ */
+ position?: string;
+ /**
+ * URL of the cover image.
+ */
+ url?: string;
+}
diff --git a/openmetadata-ui/src/main/resources/ui/src/generated/entity/context/contextMemory.ts b/openmetadata-ui/src/main/resources/ui/src/generated/entity/context/contextMemory.ts
new file mode 100644
index 000000000000..e7670fee6e9e
--- /dev/null
+++ b/openmetadata-ui/src/main/resources/ui/src/generated/entity/context/contextMemory.ts
@@ -0,0 +1,586 @@
+/*
+ * Copyright 2026 Collate.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/**
+ * Reusable context memory for Context Center and AI-assisted retrieval.
+ */
+export interface ContextMemory {
+ /**
+ * Canonical answer or retained guidance represented by this memory.
+ */
+ answer?: string;
+ /**
+ * Change that led to this version.
+ */
+ changeDescription?: ChangeDescription;
+ /**
+ * When true indicates the entity has been soft deleted.
+ */
+ deleted?: boolean;
+ /**
+ * Optional markdown description for the memory.
+ */
+ description?: string;
+ /**
+ * Display name of the memory.
+ */
+ displayName?: string;
+ /**
+ * Domains this memory belongs to.
+ */
+ domains?: EntityReference[];
+ /**
+ * Fully qualified name of the memory.
+ */
+ fullyQualifiedName?: string;
+ /**
+ * Link to this resource.
+ */
+ href?: string;
+ /**
+ * Unique identifier of the memory.
+ */
+ id: string;
+ /**
+ * Incremental change that led to this version.
+ */
+ incrementalChangeDescription?: ChangeDescription;
+ /**
+ * Last time the memory was used by AI-assisted retrieval.
+ */
+ lastUsedAt?: number;
+ machineRepresentation?: MachineRepresentation;
+ memoryScope?: MemoryScope;
+ memoryType?: MemoryType;
+ /**
+ * Stable system name for the memory.
+ */
+ name: string;
+ /**
+ * Owners of this memory.
+ */
+ owners?: EntityReference[];
+ /**
+ * Immediate parent memory in an append-style thread.
+ */
+ parentMemory?: EntityReference;
+ /**
+ * Primary entity this memory should attach to for reuse.
+ */
+ primaryEntity?: EntityReference;
+ /**
+ * Canonical question or instruction represented by this memory.
+ */
+ question?: string;
+ /**
+ * Additional related entities this memory applies to.
+ */
+ relatedEntities?: EntityReference[];
+ /**
+ * Root memory in an append-style memory thread.
+ */
+ rootMemory?: EntityReference;
+ shareConfig?: ShareConfig;
+ /**
+ * Assistant message identifier used to produce this memory.
+ */
+ sourceAssistantMessage?: string;
+ /**
+ * Conversation identifier that produced this memory.
+ */
+ sourceConversation?: string;
+ /**
+ * Human message identifier used to produce this memory.
+ */
+ sourceHumanMessage?: string;
+ sourceType?: SourceType;
+ status?: MemoryStatus;
+ /**
+ * Optional summary of the memory.
+ */
+ summary?: string;
+ /**
+ * Tags associated with this memory.
+ */
+ tags?: TagLabel[];
+ /**
+ * Short title shown in Context Center.
+ */
+ title?: string;
+ /**
+ * Last update time in Unix epoch time milliseconds.
+ */
+ updatedAt?: number;
+ /**
+ * User who made the update.
+ */
+ updatedBy?: string;
+ /**
+ * How many times this memory has been used in AI-assisted retrieval.
+ */
+ usageCount?: number;
+ /**
+ * Metadata version of the entity.
+ */
+ version?: number;
+}
+
+/**
+ * Change that led to this version.
+ *
+ * Description of the change.
+ *
+ * Incremental change that led to this version.
+ */
+export interface ChangeDescription {
+ changeSummary?: { [key: string]: ChangeSummary };
+ /**
+ * Names of fields added during the version changes.
+ */
+ fieldsAdded?: FieldChange[];
+ /**
+ * Fields deleted during the version changes with old value before deleted.
+ */
+ fieldsDeleted?: FieldChange[];
+ /**
+ * Fields modified during the version changes with old and new values.
+ */
+ fieldsUpdated?: FieldChange[];
+ /**
+ * When a change did not result in change, this could be same as the current version.
+ */
+ previousVersion?: number;
+}
+
+export interface ChangeSummary {
+ changedAt?: number;
+ /**
+ * Name of the user or bot who made this change
+ */
+ changedBy?: string;
+ changeSource?: ChangeSource;
+ [property: string]: any;
+}
+
+/**
+ * The source of the change. This will change based on the context of the change (example:
+ * manual vs programmatic)
+ */
+export enum ChangeSource {
+ Automated = "Automated",
+ Derived = "Derived",
+ Ingested = "Ingested",
+ Manual = "Manual",
+ Propagated = "Propagated",
+ Suggested = "Suggested",
+}
+
+export interface FieldChange {
+ /**
+ * Name of the entity field that changed.
+ */
+ name?: string;
+ /**
+ * New value of the field. Note that this is a JSON string and use the corresponding field
+ * type to deserialize it.
+ */
+ newValue?: any;
+ /**
+ * Previous value of the field. Note that this is a JSON string and use the corresponding
+ * field type to deserialize it.
+ */
+ oldValue?: any;
+}
+
+/**
+ * Domains this memory belongs to.
+ *
+ * This schema defines the EntityReferenceList type used for referencing an entity.
+ * EntityReference is used for capturing relationships from one entity to another. For
+ * example, a table has an attribute called database of type EntityReference that captures
+ * the relationship of a table `belongs to a` database.
+ *
+ * This schema defines the EntityReference type used for referencing an entity.
+ * EntityReference is used for capturing relationships from one entity to another. For
+ * example, a table has an attribute called database of type EntityReference that captures
+ * the relationship of a table `belongs to a` database.
+ *
+ * Immediate parent memory in an append-style thread.
+ *
+ * Primary entity this memory should attach to for reuse.
+ *
+ * Root memory in an append-style memory thread.
+ *
+ * Principal receiving access. Supported principal types are user, team, and domain.
+ */
+export interface EntityReference {
+ /**
+ * If true the entity referred to has been soft-deleted.
+ */
+ deleted?: boolean;
+ /**
+ * Optional description of entity.
+ */
+ description?: string;
+ /**
+ * Display Name that identifies this entity.
+ */
+ displayName?: string;
+ /**
+ * Fully qualified name of the entity instance. For entities such as tables, databases
+ * fullyQualifiedName is returned in this field. For entities that don't have name hierarchy
+ * such as `user` and `team` this will be same as the `name` field.
+ */
+ fullyQualifiedName?: string;
+ /**
+ * Link to the entity resource.
+ */
+ href?: string;
+ /**
+ * Unique identifier that identifies an entity instance.
+ */
+ id: string;
+ /**
+ * If true the relationship indicated by this entity reference is inherited from the parent
+ * entity.
+ */
+ inherited?: boolean;
+ /**
+ * Name of the entity instance.
+ */
+ name?: string;
+ /**
+ * Entity type/class name - Examples: `database`, `table`, `metrics`, `databaseService`,
+ * `dashboardService`...
+ */
+ type: string;
+}
+
+/**
+ * Optional machine-oriented representation used for prompt packing.
+ */
+export interface MachineRepresentation {
+ /**
+ * Compressed or transformed memory content.
+ */
+ content?: string;
+ /**
+ * Representation format identifier.
+ */
+ format?: string;
+ /**
+ * Timestamp when the representation was generated.
+ */
+ generatedAt?: number;
+ /**
+ * Hash of the canonical source content used to generate this representation.
+ */
+ generatedFromHash?: string;
+ status?: MachineRepresentationStatus;
+ /**
+ * Version of the representation format.
+ */
+ version?: string;
+}
+
+/**
+ * Availability state of the machine-oriented representation.
+ */
+export enum MachineRepresentationStatus {
+ Failed = "Failed",
+ Pending = "Pending",
+ Ready = "Ready",
+ Stale = "Stale",
+}
+
+/**
+ * Scope where the memory should be applied.
+ */
+export enum MemoryScope {
+ EntityScoped = "EntityScoped",
+ UserGlobal = "UserGlobal",
+}
+
+/**
+ * High-level type of reusable memory.
+ */
+export enum MemoryType {
+ FAQ = "Faq",
+ Note = "Note",
+ Preference = "Preference",
+ Runbook = "Runbook",
+ UseCase = "UseCase",
+}
+
+/**
+ * Visibility and sharing configuration for the memory.
+ */
+export interface ShareConfig {
+ /**
+ * Explicit principals the memory is shared with.
+ */
+ sharedWith?: SharedPrincipal[];
+ visibility?: ShareVisibility;
+}
+
+/**
+ * A principal granted access to the memory.
+ */
+export interface SharedPrincipal {
+ /**
+ * Principal receiving access. Supported principal types are user, team, and domain.
+ */
+ principal?: EntityReference;
+ /**
+ * Role granted to the principal.
+ */
+ role?: ShareRole;
+}
+
+/**
+ * Role granted to the principal.
+ *
+ * Role granted to a shared principal.
+ */
+export enum ShareRole {
+ Editor = "Editor",
+ Viewer = "Viewer",
+}
+
+/**
+ * Visibility level for the memory.
+ */
+export enum ShareVisibility {
+ Entity = "Entity",
+ Private = "Private",
+ Shared = "Shared",
+}
+
+/**
+ * How the memory was created.
+ */
+export enum SourceType {
+ ChatPromotion = "ChatPromotion",
+ Manual = "Manual",
+ RememberRequest = "RememberRequest",
+}
+
+/**
+ * Lifecycle state of the memory. Any status may be set at creation (e.g. importing an
+ * already-archived memory); the Draft -> Active -> Archived transition rules are only
+ * enforced on subsequent updates.
+ */
+export enum MemoryStatus {
+ Active = "Active",
+ Archived = "Archived",
+ Draft = "Draft",
+}
+
+/**
+ * This schema defines the type for labeling an entity with a Tag.
+ */
+export interface TagLabel {
+ /**
+ * Timestamp when this tag was applied in ISO 8601 format
+ */
+ appliedAt?: Date;
+ /**
+ * Who it is that applied this tag (e.g: a bot, AI or a human)
+ */
+ appliedBy?: string;
+ /**
+ * Description for the tag label.
+ */
+ description?: string;
+ /**
+ * Display Name that identifies this tag.
+ */
+ displayName?: string;
+ /**
+ * Link to the tag resource.
+ */
+ href?: string;
+ /**
+ * Label type describes how a tag label was applied. 'Manual' indicates the tag label was
+ * applied by a person. 'Derived' indicates a tag label was derived using the associated tag
+ * relationship (see Classification.json for more details). 'Propagated` indicates a tag
+ * label was propagated from upstream based on lineage. 'Automated' is used when a tool was
+ * used to determine the tag label.
+ */
+ labelType: LabelType;
+ /**
+ * Additional metadata associated with this tag label, such as recognizer information for
+ * automatically applied tags.
+ */
+ metadata?: TagLabelMetadata;
+ /**
+ * Name of the tag or glossary term.
+ */
+ name?: string;
+ /**
+ * An explanation of why this tag was proposed, specially for autoclassification tags
+ */
+ reason?: string;
+ /**
+ * Label is from Tags or Glossary.
+ */
+ source: TagSource;
+ /**
+ * 'Suggested' state is used when a tag label is suggested by users or tools. Owner of the
+ * entity must confirm the suggested labels before it is marked as 'Confirmed'.
+ */
+ state: State;
+ style?: Style;
+ tagFQN: string;
+}
+
+/**
+ * Label type describes how a tag label was applied. 'Manual' indicates the tag label was
+ * applied by a person. 'Derived' indicates a tag label was derived using the associated tag
+ * relationship (see Classification.json for more details). 'Propagated` indicates a tag
+ * label was propagated from upstream based on lineage. 'Automated' is used when a tool was
+ * used to determine the tag label.
+ */
+export enum LabelType {
+ Automated = "Automated",
+ Derived = "Derived",
+ Generated = "Generated",
+ Manual = "Manual",
+ Propagated = "Propagated",
+}
+
+/**
+ * Additional metadata associated with this tag label, such as recognizer information for
+ * automatically applied tags.
+ *
+ * Additional metadata associated with a tag label, including information about how the tag
+ * was applied.
+ */
+export interface TagLabelMetadata {
+ /**
+ * Epoch time in milliseconds when the certification tag expires
+ */
+ expiryDate?: number;
+ /**
+ * Metadata about the recognizer that automatically applied this tag
+ */
+ recognizer?: TagLabelRecognizerMetadata;
+}
+
+/**
+ * Metadata about the recognizer that automatically applied this tag
+ *
+ * Metadata about the recognizer that applied a tag, including scoring and pattern
+ * information.
+ */
+export interface TagLabelRecognizerMetadata {
+ /**
+ * Details of patterns that matched during recognition
+ */
+ patterns?: PatternMatch[];
+ /**
+ * Unique identifier of the recognizer that applied this tag
+ */
+ recognizerId: string;
+ /**
+ * Human-readable name of the recognizer
+ */
+ recognizerName: string;
+ /**
+ * Confidence score assigned by the recognizer (0.0 to 1.0)
+ */
+ score: number;
+ /**
+ * What the recognizer analyzed to apply this tag
+ */
+ target?: Target;
+}
+
+/**
+ * Information about a pattern that matched during recognition
+ */
+export interface PatternMatch {
+ /**
+ * Name of the pattern that matched
+ */
+ name: string;
+ /**
+ * Regular expression or pattern definition
+ */
+ regex?: string;
+ /**
+ * Confidence score for this specific pattern match
+ */
+ score: number;
+}
+
+/**
+ * What the recognizer analyzed to apply this tag
+ */
+export enum Target {
+ ColumnName = "column_name",
+ Content = "content",
+}
+
+/**
+ * Label is from Tags or Glossary.
+ */
+export enum TagSource {
+ Classification = "Classification",
+ Glossary = "Glossary",
+}
+
+/**
+ * 'Suggested' state is used when a tag label is suggested by users or tools. Owner of the
+ * entity must confirm the suggested labels before it is marked as 'Confirmed'.
+ */
+export enum State {
+ Confirmed = "Confirmed",
+ Suggested = "Suggested",
+}
+
+/**
+ * UI Style is used to associate a color code and/or icon to entity to customize the look of
+ * that entity in UI.
+ */
+export interface Style {
+ /**
+ * Hex Color Code to mark an entity such as GlossaryTerm, Tag, Domain or Data Product.
+ */
+ color?: string;
+ /**
+ * Cover image configuration for the entity.
+ */
+ coverImage?: CoverImage;
+ /**
+ * An icon to associate with GlossaryTerm, Tag, Domain or Data Product.
+ */
+ iconURL?: string;
+}
+
+/**
+ * Cover image configuration for the entity.
+ *
+ * Cover image configuration for an entity. This is used to display a banner or header image
+ * for entities like Domain, Glossary, Data Product, etc.
+ */
+export interface CoverImage {
+ /**
+ * Position of the cover image in CSS background-position format. Supports keywords (top,
+ * center, bottom) or pixel values (e.g., '20px 30px').
+ */
+ position?: string;
+ /**
+ * URL of the cover image.
+ */
+ url?: string;
+}