Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion internal/api/openapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{}{
{
Expand Down
6 changes: 3 additions & 3 deletions internal/mcp/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{}{
Expand All @@ -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",
Expand Down
Loading