From ac9a2e40cbe5d1af567d3cf1c4346b63f3ac234f Mon Sep 17 00:00:00 2001 From: Jayson Rawlins Date: Mon, 13 Apr 2026 11:20:29 -0400 Subject: [PATCH] docs: surface soft-delete and demote patterns via update_episode Agents can already expire episodes via update_episode but the current description doesn't mention this cleanup use case. Documents the two agent-safe patterns: soft-delete via expired_at (reversible) and demote via deprecated tag (visible but filterable). No behavior changes, only description text. --- README.md | 24 ++++++++++++++++++++++++ internal/api/openapi.go | 2 +- internal/mcp/server.go | 6 +++--- 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index ac0bb9b..e31dcfd 100644 --- a/README.md +++ b/README.md @@ -150,6 +150,30 @@ just docker-up-linux For detailed deployment instructions including Docker Compose, Kubernetes, and production configurations, see [docs/deployment.md](docs/deployment.md). +## Cleanup Patterns + +Agents clean up stale memories via `update_episode` — engram intentionally does not expose a `delete_episode` MCP tool because permanent deletion is a deliberate human action, not something agents should do autonomously. + +### Soft-delete (reversible) + +Set `expired_at` to a past timestamp. The episode is hidden from default search but remains in the store — recover it later by clearing `expired_at`. + +```json +{"tool": "update_episode", "id": "...", "expired_at": "2020-01-01T00:00:00Z"} +``` + +### Demote (visible but filtered) + +Replace the episode's tags to include a marker like `deprecated` or `low-confidence`. The episode stays in search results so nothing is lost, but callers can filter at query time. + +```json +{"tool": "update_episode", "id": "...", "tags": ["deprecated", "original-topic"]} +``` + +### Scheduled expiration + +Set `expired_at` to a future timestamp — the episode disappears from default search after that time with no further action. + ## Architecture ```text diff --git a/internal/api/openapi.go b/internal/api/openapi.go index e1c44a5..0b28889 100644 --- a/internal/api/openapi.go +++ b/internal/api/openapi.go @@ -287,7 +287,7 @@ func (s *Server) handleOpenAPISpec(w http.ResponseWriter, r *http.Request) { "/api/v1/memory/episodes/{id}": map[string]interface{}{ "put": map[string]interface{}{ "summary": "Update episode", - "description": "Update metadata, tags, or expiration of an episode", + "description": "Update metadata, tags, or expiration of an episode. Set expired_at to a past timestamp for soft-delete (reversible, hidden from default search). Use tags (e.g. 'deprecated') to demote content that should be filtered at query time.", "operationId": "updateEpisode", "parameters": []map[string]interface{}{ { diff --git a/internal/mcp/server.go b/internal/mcp/server.go index 6e0c607..0d4920a 100644 --- a/internal/mcp/server.go +++ b/internal/mcp/server.go @@ -193,7 +193,7 @@ func (s *Server) registerTools() { // update_episode tool s.mcpServer.AddTool(mcp.Tool{ Name: "update_episode", - Description: "Update metadata, tags, or expiration of an episode", + Description: "Update metadata, tags, or expiration of an episode. Setting expired_at to a past timestamp performs a soft-delete — the episode is hidden from default search but remains recoverable by setting expired_at back to null. Use tags to demote (e.g. add 'deprecated') so callers can filter stale content at query time.", InputSchema: mcp.ToolInputSchema{ Type: "object", Properties: map[string]interface{}{ @@ -206,11 +206,11 @@ func (s *Server) registerTools() { "items": map[string]interface{}{ "type": "string", }, - "description": "New tags array", + "description": "New tags array (replaces existing tags). Add tags like 'deprecated' to demote episodes that should be filtered out at query time.", }, "expired_at": map[string]interface{}{ "type": "string", - "description": "Expiration time (ISO 8601)", + "description": "Expiration time (ISO 8601). Set to a past timestamp to soft-delete (hidden from default search, recoverable). Set to a future timestamp to schedule expiration. Pass null to un-expire.", }, "metadata": map[string]interface{}{ "type": "string",