GoodMem
How-To Guides

Work Through Metadata Filters

Upload a sample memory and practice GoodMem filter expressions with real retrieval calls.

Work Through Metadata Filters

This guide walks an experienced GoodMem platform developer through a hands-on metadata filter drill:

  1. Load a realistic escalation runbook with rich JSON metadata.
  2. Run retrieval calls that isolate different business scenarios by changing only the filter expression.

Vector similarity is fantastic at finding semantically related memories, but it cannot enforce the business guardrails that operators rely on day to day. Metadata filters fill that gap: “Only show Manila escalation playbooks,” “Hide anything past its review date,” or “Return procedures that leadership can read” are all crisp boolean predicates layered on top of fuzzy retrieval.

GoodMem models metadata as an arbitrary JSON document stored at the memory (document) level. Every filter expression in this guide uses JSONPath extraction—val('$.field'), exists('$.array[*]'), and friends—to coerce pieces of that JSON into SQL comparisons. Today, metadata is not stored per chunk, so plan your schema with document-level attributes in mind.

Modern LLM-powered assistants often synthesize these predicates for you. A prompt like “show me documents published last year” (assuming the current year is 2025) might yield:

EXTRACT(YEAR FROM CAST(val('$.effectiveDate') AS DATE)) = 2024

While “show me documents published in the last year” becomes a rolling window expression:

CAST(val('$.effectiveDate') AS DATE) >= (CURRENT_DATE - 365)

Both filters operate on the same metadata, yet a single word changes the shape of the predicate—one clamps to a specific calendar year, the other to a sliding freshness window. Working through the scenarios below will help you spot those nuances and design metadata that LLMs (or humans) can filter reliably.

Keep the Filter Expressions Reference open in another tab while you work—every filter used below maps directly to the constructs documented there.

Before You Start

  • GoodMem server running locally (defaults assumed in this guide):
    • REST bridge: http://localhost:8080
    • gRPC endpoint: https://localhost:9090
  • goodmem CLI installed and authenticated (GOODMEM_API_KEY available).
  • At least one space that already has an embedder attached. Capture its UUID (for example, using goodmem space list --format json).
  • curl available; jq is optional but handy for inspecting JSON output.

Export a few helper variables so the commands stay short:

export GOODMEM_API_KEY="<your api key>"
export SPACE_ID="<uuid of the space you want to use>"
export GOODMEM_SERVER="http://localhost:8080"
export GOODMEM_RETRIEVE="$GOODMEM_SERVER/v1/memories:retrieve"

If you are connecting to a remote server, adjust the host and scheme in GOODMEM_SERVER accordingly.

1. Prepare the Sample Memory

Work from a scratch directory for the files that follow:

mkdir -p ~/goodmem-filters
cd ~/goodmem-filters

1.1 Create the source document

cat <<'EOF' > manila-escalation.txt
Escalation Procedures – Manila Support Center
===========================================

Use this playbook when handling after-hours incidents in the Manila contact hub.

1. Log and acknowledge P1 tickets within 5 minutes.
2. Notify the on-call Team Lead and join the bridge.
3. If the incident persists beyond 15 minutes, page the Shift Manager.
4. Escalate to the Regional Director after 30 minutes or whenever customer impact crosses the MOC threshold.
5. Record timeline updates in ServiceNow and circulate the handover note before shift turnover.

Keep the Microsoft Teams War Room open until the Major Incident Team declares recovery.
EOF

1.2 Create the metadata sidecar

cat <<'EOF' > manila-escalation.metadata.json
{
  "documentId": "DOC-OPS-ESCALATION-2025-03",
  "title": "Escalation Procedures – Manila Support Center",
  "summary": "Workflow for handling priority incident tickets and escalation paths within the Manila call center.",
  "status": "approved",
  "category": "operations",
  "businessUnit": "Customer Support",
  "department": "Global Support Operations",
  "subDepartment": "APAC Contact Centers",
  "serviceLine": "Voice Support",
  "primaryLocation": {
    "city": "Manila",
    "country": "Philippines",
    "timezone": "Asia/Manila"
  },
  "escalationLevels": [
    {
      "level": 1,
      "responseTimeSlaMinutes": 15,
      "requiredRoles": ["Team Lead"]
    },
    {
      "level": 2,
      "responseTimeSlaMinutes": 30,
      "requiredRoles": ["Shift Manager", "Incident Manager"]
    },
    {
      "level": 3,
      "responseTimeSlaMinutes": 60,
      "requiredRoles": ["Regional Director", "Major Incident Team"]
    }
  ],
  "tags": [
    "incident-management",
    "manila",
    "priority-handling",
    "call-center"
  ],
  "keywords": [
    "P1 ticket",
    "escalation",
    "SLA",
    "MOC",
    "after-hours",
    "handover"
  ],
  "compliance": {
    "requiresSOXReview": false,
    "requiresISO27001Review": true,
    "gdprImpact": "low"
  },
  "language": "en-US",
  "effectiveDate": "2025-03-01",
  "nextReviewDate": "2026-03-01",
  "version": {
    "major": 3,
    "minor": 1,
    "lastApprovedBy": "8c8a06da-3f97-4d4a-9974-b3017071c3e3",
    "changeLog": [
      {
        "date": "2024-11-15",
        "summary": "Updated level-2 escalation timelines for the revised regional policy."
      },
      {
        "date": "2025-02-20",
        "summary": "Added after-hours contact tree and on-call rotation details."
      }
    ]
  },
  "owners": [
    {
      "role": "Process Owner",
      "userId": "6f0f2e30-97a4-4d5c-9b46-04c1690f33f1",
      "email": "[email protected]"
    },
    {
      "role": "Regional Director",
      "userId": "11b2fdc5-94aa-41b5-a2ef-ef505bb38574",
      "email": "[email protected]"
    }
  ],
  "stakeholders": [
    "service-delivery",
    "incident-response",
    "continuous-improvement"
  ],
  "relatedDocuments": [
    {
      "documentId": "DOC-OPS-ONCALL-APAC-2025",
      "relationship": "referenced-by"
    },
    {
      "documentId": "DOC-SECURITY-MAJOR-INCIDENT",
      "relationship": "requires"
    }
  ],
  "retentionPolicy": {
    "policyId": "RET-OPERATIONS-GRADUAL-ARCHIVE",
    "retainForMonths": 36,
    "archiveAfterMonths": 24,
    "disposalAction": "review"
  },
  "accessControl": {
    "defaultVisibility": "internal",
    "allowedGroups": [
      "OPS-MNL-Escalation",
      "IncidentManagers",
      "SupportLeadership"
    ],
    "restrictions": [
      "no-external-sharing",
      "requires-mfa"
    ]
  },
  "operationalContext": {
    "ticketingPlatform": "ServiceNow",
    "priorityLevels": ["P1", "P2"],
    "businessHours": {
      "local": "08:00-20:00",
      "afterHoursHandledBy": "Follow-the-Sun APAC On-Call"
    },
    "escalationChannels": [
      "ServiceNow Workflow",
      "On-Call Bridge",
      "Microsoft Teams War Room"
    ]
  },
  "lastReviewed": {
    "timestamp": "2025-02-20T09:18:42Z",
    "reviewerId": "b5a10a5b-0450-4cdb-82f9-5d2cc17f363c"
  }
}
EOF

2. Upload the Sample Memory

Use the CLI to push both files into your target space. The CLI accepts @file syntax to load JSON metadata directly:

goodmem memory create \
  --space-id "$SPACE_ID" \
  --content-type "text/plain" \
  --file manila-escalation.txt \
  --metadata @manila-escalation.metadata.json

Confirm the memory landed:

goodmem memory list --space-id "$SPACE_ID" --format table --no-trunc

You should see the new record in COMPLETED status once background processing finishes.

3. Build a Reusable Retrieval Call

Each scenario below follows the same pattern: assign a filter to FILTER_EXPR, then run your preferred retrieval command.

goodmem memory retrieve \
  --space-id "$SPACE_ID" \
  --filter "$FILTER_EXPR" \
  --max-results 5 \
  --fetch-content \
  --format json \
  "metadata filter walkthrough"
curl -sS \
  --request POST "$GOODMEM_RETRIEVE" \
  --header "x-api-key: $GOODMEM_API_KEY" \
  --header "Content-Type: application/json" \
  --header "Accept: application/x-ndjson" \
  --data @- <<JSON
{
  "message": "metadata filter walkthrough",
  "requestedSize": 5,
  "fetchMemory": true,
  "fetchMemoryContent": true,
  "spaceKeys": [
    {
      "spaceId": "$SPACE_ID",
      "filter": "$FILTER_EXPR"
    }
  ]
}
JSON
wget --quiet \
  --method=POST \
  --header="x-api-key: $GOODMEM_API_KEY" \
  --header="Content-Type: application/json" \
  --header="Accept: application/x-ndjson" \
  --output-document - \
  --body-data="$(cat <<'JSON'
{
  \"message\": \"metadata filter walkthrough\",
  \"requestedSize\": 5,
  \"fetchMemory\": true,
  \"fetchMemoryContent\": true,
  \"spaceKeys\": [
    {
      \"spaceId\": \"$SPACE_ID\",
      \"filter\": \"$FILTER_EXPR\"
    }
  ]
}
JSON
)" \
  "$GOODMEM_RETRIEVE"
  • Responses stream as NDJSON; each line is a retrieval event. Pipe through jq for prettier output, or watch the raw stream to see timing details.
  • If you prefer to leave out the raw content, drop "fetchMemoryContent": true in the payload.

With the plumbing ready, walk through the scenarios.

Scenario Library

The next sections apply the filters to the Manila escalation sample you uploaded earlier. Each scenario follows the same structure:

  • Spot-check the relevant metadata with goodmem memory list … | jq so you know the fields exist.
  • Review the SQL-style filter and copy the single-line variant into FILTER_EXPR.
  • Run your preferred retrieval command to confirm the expected results.

We start with basic location/tag checks and layer on additional constraints—leadership access, regional scope, compliance, SLA coverage, and access controls—so you can see how one filter builds on the next.

Scenario 1 – Fresh Manila escalation guidance

Goal: Manila playbooks tagged incident-management with an effective date on or after 1 Oct 2024.

Start by verifying the metadata:

goodmem memory list \\
  --space-id "$SPACE_ID" \\
  --format json \\
  | jq '.memories
         | map(select(.metadata != null)
             | {
                 title: .metadata.title,
                 city: .metadata.primaryLocation.city,
                 tags: (.metadata.tags // []),
                 effectiveDate: .metadata.effectiveDate
               })'

Look for the Manila escalation record with city = "Manila", tags including "incident-management", and an effectiveDate of 2025-03-01.

CAST(val('$.primaryLocation.city') AS TEXT) ILIKE 'manila'
AND 'incident-management' IN val('$.tags')
AND CAST(val('$.effectiveDate') AS DATE) >= CAST('2024-10-01' AS DATE)
FILTER_EXPR="CAST(val('$.primaryLocation.city') AS TEXT) ILIKE 'manila' AND 'incident-management' IN val('$.tags') AND CAST(val('$.effectiveDate') AS DATE) >= CAST('2024-10-01' AS DATE)"

Run the retrieval command. A matching result confirms the runbook is categorized correctly and still within the active window. An empty response typically means the ingestion job hasn’t finished yet.

With location and freshness confirmed, the next scenario checks that leadership can still rely on the same runbook.

Scenario 2 – Leadership visibility with future review

Goal: Procedures the SupportLeadership group can read, that explicitly name a Regional Director in the escalation chain, and whose review date has not lapsed.

Before you run the filter, confirm the metadata exposes those cues:

goodmem memory list \
  --space-id "$SPACE_ID" \
  --format json \
  | jq '.memories
         | map(select(.metadata != null)
             | {
                 title: .metadata.title,
                 allowedGroups: (.metadata.accessControl.allowedGroups // []),
                 escalationRoles: (.metadata.escalationLevels // [] | map(.requiredRoles)),
                 nextReviewDate: .metadata.nextReviewDate
               })'

Look for "SupportLeadership" in the allowedGroups array, a nested set of escalation roles that includes "Regional Director", and a nextReviewDate that hasn’t expired.

'SupportLeadership' IN val('$.accessControl.allowedGroups')
AND exists('$.escalationLevels[*].requiredRoles[*]?(@ == "Regional Director")')
AND CAST(val('$.nextReviewDate') AS DATE) >= current_date
FILTER_EXPR="'SupportLeadership' IN val('$.accessControl.allowedGroups') AND exists('$.escalationLevels[*].requiredRoles[*]?(@ == \"Regional Director\")') AND CAST(val('$.nextReviewDate') AS DATE) >= current_date"

Run the retrieval command with the new FILTER_EXPR. A single Manila result confirms the metadata is still visible to leadership and not past due. Zero rows typically mean the review date is in the past or the allowed group list changed.

With leadership sign-off confirmed, expand the scope to the broader APAC operations posture. Scenario 3 reuses the same sample memory but filters on regional coverage, ticketing platform, and version retention.

Scenario 3 – APAC runbooks on ServiceNow

Goal: APAC-oriented runbooks that reference ServiceNow, are at major version ≥ 3, and have not yet hit their archive window.

The Manila escalation sample you uploaded in Step 1 already satisfies these constraints:

  • primaryLocation.country = "Philippines" (so the first predicate branch matches outright).
  • subDepartment = "APAC Contact Centers", giving the fallback substring check a positive hit.
  • operationalContext.ticketingPlatform = "ServiceNow".
  • version.major = 3 and retentionPolicy.archiveAfterMonths = 24, both persisted as integers so we can cast them in the filter.

You can spot-check the metadata before running the filter:

goodmem memory list \
  --space-id "$SPACE_ID" \
  --format json \
  | jq '.memories
         | map(select(.metadata != null)
             | {
                 title: .metadata.title,
                 country: .metadata.primaryLocation.country,
                 subDepartment: .metadata.subDepartment,
                 versionMajor: .metadata.version.major,
                 archiveAfterMonths: .metadata.retentionPolicy.archiveAfterMonths
               })'

Expect to see at least one entry titled Escalation Procedures – Manila Support Center with country = "Philippines", subDepartment = "APAC Contact Centers", versionMajor = 3, and archiveAfterMonths = 24. If you have re-run the upload a few times you may see duplicates; that is fine and the filter will return the newest memory.

(
  CAST(val('$.primaryLocation.country') AS TEXT) ILIKE 'philippines'
  OR CAST(val('$.subDepartment') AS TEXT) ILIKE '%apac%'
)
AND CAST(val('$.operationalContext.ticketingPlatform') AS TEXT) ILIKE 'servicenow'
AND CAST(val('$.version.major') AS INTEGER) >= 3
AND CAST(val('$.retentionPolicy.archiveAfterMonths') AS INTEGER) >= 0
FILTER_EXPR="(CAST(val('$.primaryLocation.country') AS TEXT) ILIKE 'philippines' OR CAST(val('$.subDepartment') AS TEXT) ILIKE '%apac%') AND CAST(val('$.operationalContext.ticketingPlatform') AS TEXT) ILIKE 'servicenow' AND CAST(val('$.version.major') AS INTEGER) >= 3 AND CAST(val('$.retentionPolicy.archiveAfterMonths') AS INTEGER) >= 0"
  • The initial parentheses cover either a direct Philippines match or any sub-department containing “APAC.”
  • Casting the numeric fields ensures comparisons work against the denormalized metadata stored for each chunk pointer.
  • Use whichever retrieval command you prefer. You should see two chunks from the Manila runbook; no results usually means processing hasn’t completed yet.

With the regional operations checks squared away, stay with the same Manila playbook but flip the lens from “is it current?” to “is it still compliant?” We already saw that the runbook is active; now we make sure the ISO27001 review timer hasn’t slipped and that the most recent change log actually calls out the on-call rotation update. The next filter adds those compliance guardrails without changing the underlying dataset.

Scenario 4 – ISO-sensitive procedures due for review

Goal: ISO27001-sensitive items whose next review falls before 1 May 2026 and whose change log explicitly mentions “on-call.” Adjust the literal date to match your maintenance window.

Before running the filter, glance at the seeded metadata to confirm the fields exist:

goodmem memory list \
  --space-id "$SPACE_ID" \
  --format json \
  | jq '.memories
         | map(select(.metadata != null)
             | {
                 title: .metadata.title,
                 requiresISO: .metadata.compliance.requiresISO27001Review,
                 nextReviewDate: .metadata.nextReviewDate,
                 changeLog: ((.metadata.version.changeLog // []) | map(.summary))
               })'

You should see the Manila escalation memory reporting requiresISO: true, a nextReviewDate around 2026-03-01, and a change-log entry that contains “on-call.” If the list is empty, re-run the upload step and wait for processing to complete.

CAST(val('$.compliance.requiresISO27001Review') AS BOOLEAN) = TRUE
AND CAST(val('$.nextReviewDate') AS DATE) <= CAST('2026-05-01' AS DATE)
AND exists('$.version.changeLog[*] ? (@.summary like_regex "on-call" flag "i")')
FILTER_EXPR="CAST(val('$.compliance.requiresISO27001Review') AS BOOLEAN) = TRUE AND CAST(val('$.nextReviewDate') AS DATE) <= CAST('2026-05-01' AS DATE) AND exists('$.version.changeLog[*] ? (@.summary like_regex \"on-call\" flag \"i\")')"

Run the retrieval command again. A successful pass returns the same Manila runbook, this time annotated as a compliance candidate. Zero results mean either the review date has been edited past the cut-off or the change log no longer includes “on-call.”

With compliance accounted for, you can widen the view to the SLA footprint. Scenario 5 keeps the Manila dataset in place but focuses on the teams that must react quickly: the presence of P1 response levels, the priority-handling tag, and an escalation sequence that commits to a 15-minute first response.

Scenario 5 – P1 focus with rapid SLA coverage

Goal: Documents tagged for priority handling that cover P1 tickets and promise at least one 15-minute escalation step.

Start by confirming the SLA metadata is present:

goodmem memory list \
  --space-id "$SPACE_ID" \
  --format json \
  | jq '.memories
         | map(select(.metadata != null)
             | {
                 title: .metadata.title,
                 tags: (.metadata.tags // []),
                 priorityLevels: (.metadata.operationalContext.priorityLevels // []),
                 escalationMinutes: (.metadata.escalationLevels // [] | map(.responseTimeSlaMinutes))
               })'

Look for "priority-handling" in the tags array, a priorityLevels list containing "P1", and at least one escalation level showing a response time of 15 minutes. The Manila escalation runbook seeded earlier satisfies all of these conditions.

'priority-handling' IN val('$.tags')
AND 'P1' IN val('$.operationalContext.priorityLevels')
AND exists('$.escalationLevels[*] ? (@.responseTimeSlaMinutes <= 15)')
FILTER_EXPR="'priority-handling' IN val('$.tags') AND 'P1' IN val('$.operationalContext.priorityLevels') AND exists('$.escalationLevels[*] ? (@.responseTimeSlaMinutes <= 15)')"

Run the retrieval command again. You should receive the Manila runbook, with chunks covering the step-by-step escalation rule set. If nothing returns, the metadata either lacks the priority-handling tag or every escalation level has an SLA above 15 minutes.

After checking responsiveness, shift the lens one last time to access controls. Scenario 6 keeps the same sample memory but verifies that it remains internal-only, MFA-gated, and newly effective in 2025.

Scenario 6 – Internal-only runbooks effective in 2025

Goal: Procedures that are internal-only, enforce MFA, and take effect in calendar year 2025.

Confirm those properties exist before running the filter:

goodmem memory list \
  --space-id "$SPACE_ID" \
  --format json \
  | jq '.memories
         | map(select(.metadata != null)
             | {
                 title: .metadata.title,
                 restrictions: (.metadata.accessControl.restrictions // []),
                 effectiveDate: .metadata.effectiveDate
               })'

You should see "no-external-sharing" and "requires-mfa" inside the restrictions array and an effectiveDate of 2025-03-01. If the output is empty, re-run the upload and wait for processing to finish.

'no-external-sharing' IN val('$.accessControl.restrictions')
AND 'requires-mfa' IN val('$.accessControl.restrictions')
AND date_trunc('year', CAST(val('$.effectiveDate') AS DATE)) = CAST('2025-01-01' AS DATE)
FILTER_EXPR="'no-external-sharing' IN val('$.accessControl.restrictions') AND 'requires-mfa' IN val('$.accessControl.restrictions') AND date_trunc('year', CAST(val('$.effectiveDate') AS DATE)) = CAST('2025-01-01' AS DATE)"

Execute the usual retrieval command. A matching result indicates the runbook still has the expected internal guardrails. No results typically mean one of the restrictions was removed or the effective date falls outside calendar year 2025.

Where to Go Next

  • Expand the metadata fixture with your own fields, regenerate the JSON sidecar, and repeat the drill.
  • Combine these expressions with additional retrieval parameters (context files, post-processors, multiple spaces) to mimic production workloads.
  • Cross-reference every construct with the Filter Expressions Reference to stay aligned with the supported grammar.
  • Brush up on JSONPath semantics directly from RFC 9535 and Stefan Goessner’s original JSONPath overview so you know which predicates map cleanly onto GoodMem’s functions.