Skip to content
Data
Skill

/read-memories

Search past Claude Code session logs to recover context from previous conversations. Finds past decisions, data paths, CRS info, model configurations, and unresolved work. Works across all projects or scoped to the current one.

From plugin
geoai-skills
368 skills

Claude Code

$ npx -y skills add opengeos/geoai-skills --skill read-memories --agent claude-code

SKILL.md

read-memories.SKILL.md
name: read-memories
description: >
  Search past Claude Code session logs to recover context from previous
  conversations. Finds past decisions, data paths, CRS info, model
  configurations, and unresolved work. Works across all projects or
  scoped to the current one.
argument-hint: <keyword> [--here] [--geo]
allowed-tools: Bash, Read

Use this to recover your own context from past sessions. Do NOT narrate the process to the user -- silently run the query, absorb the results, and continue with enriched context.

`$0` is the keyword to search for. Pass `--here` as an argument to scope the search to the current project only. Pass `--geo` as an argument to additionally extract geospatial-specific context (EPSG codes, bounding boxes, CRS info, spatial file paths, model names).

Step 1 -- Set the search path

ALL_PROJECTS="$HOME/.claude/projects/*/*.jsonl"
CURRENT_PROJECT="$HOME/.claude/projects/$(echo "$PWD" | sed 's|[/_]|-|g')/*.jsonl"

Use `$CURRENT_PROJECT` if any argument is `--here`, otherwise use `$ALL_PROJECTS`. Store the chosen glob in `SEARCH_PATH`.

Check whether the `--geo` flag is present.

Step 2 -- Query with Python

Run the following Python script via `python3 -c "..."`, substituting `<SEARCH_PATH>` and `<KEYWORD>` with the resolved values. Escape any single quotes in `<KEYWORD>` before embedding it.

python3 -c "
import json, glob, os

SEARCH_PATH = '<SEARCH_PATH>'
KEYWORD = '<KEYWORD>'.lower()
LIMIT = 40

files = sorted(glob.glob(os.path.expanduser(SEARCH_PATH)))
results = []

for fpath in files:
    parts = fpath.split('/')
    try:
        proj_idx = parts.index('projects') + 1
        project = parts[proj_idx] if proj_idx < len(parts) else 'unknown'
    except ValueError:
        project = 'unknown'

    with open(fpath, 'r', errors='replace') as f:
        for line in f:
            try:
                obj = json.loads(line)
            except (json.JSONDecodeError, ValueError):
                continue

            msg = obj.get('message')
            if not isinstance(msg, dict):
                continue
            role = msg.get('role')
            if role not in ('user', 'assistant'):
                continue

            content = msg.get('content', '')
            if isinstance(content, list):
                text = ' '.join(
                    c.get('text', '')
                    for c in content
                    if isinstance(c, dict) and 'text' in c
                )
            elif isinstance(content, str):
                text = content
            else:
                continue

            if KEYWORD not in text.lower():
                continue

            ts = obj.get('timestamp', '')
            snippet = text[:1500]
            results.append({
                'project': project,
                'ts': ts[:16].replace('T', ' ') if ts else '',
                'role': role,
                'content': snippet,
            })

            if len(results) >= LIMIT:
                break
    if len(results) >= LIMIT:
        break

print(f'Found {len(results)} results (limit {LIMIT})')
print('---')
for i, r in enumerate(results):
    print(f'[{i+1}] project={r[\"project\"]} ts={r[\"ts\"]} role={r[\"role\"]}')
    print(r['content'][:800])
    print('---')
"

Step 3 -- Handle large result sets

If Step 2 reports exactly 40 results (limit hit), the keyword is common. Run a counting pass to understand the scope:

python3 -c "
import json, glob, os

SEARCH_PATH = '<SEARCH_PATH>'
KEYWORD = '<KEYWORD>'.lower()

files = sorted(glob.glob(os.path.expanduser(SEARCH_PATH)))
total = 0
by_project = {}

for fpath in files:
    parts = fpath.split('/')
    try:
        proj_idx = parts.index('projects') + 1
        project = parts[proj_idx] if proj_idx < len(parts) else 'unknown'
    except ValueError:
        project = 'unknown'

    with open(fpath, 'r', errors='replace') as f:
        for line in f:
            try:
                obj = json.loads(line)
            except (json.JSONDecodeError, ValueError):
                continue
            msg = obj.get('message')
            if not isinstance(msg, dict):
                continue
            role = msg.get('role')
            if role not in ('user', 'assistant'):
                continue
            content = msg.get('content', '')
            if isinstance(content, list):
                text = ' '.join(
                    c.get('text', '')
                    for c in content
                    if isinstance(c, dict) and 'text' in c
                )
            elif isinstance(content, str):
                text = content
            else:
                continue
            if KEYWORD in text.lower():
                total += 1
                by_project[project] = by_project.get(project, 0) + 1

print(f'Total matches: {total}')
for proj, cnt in sorted(by_project.items(), key=lambda x: -x[1]):
    print(f'  {proj}: {cnt}')
"

Use this breakdown to decide whether to:

  • Narrow the keyword (combine with a second term)
  • Scope to `--here` if not already scoped
  • Retrieve only the most recent results (sort by timestamp descending)

Step 4 -- Extract geospatial context (when --geo is set)

If the `--geo` flag was provided, run an additional extraction pass:

python3 -c "
import json, glob, os, re

SEARCH_PATH = '<SEARCH_PATH>'
KEYWORD = '<KEYWORD>'.lower()

patterns = {
    'epsg_codes': re.compile(r'EPSG[:\s]*(\d{4,5})', re.IGNORECASE),
    'bbox': re.compile(r'(?:bbox|bounding.?box|bounds)\s*[=:]\s*\[([^\]]+)\]', re.IGNORECASE),
    'crs': re.compile(r'(?:CRS|SRS|projection)\s*[=:]\s*[\"\\']?([^\"\\'\\n,;]{3,60})', re.IGNORECASE),
    'spatial_files': re.compile(r'[\w/.-]+\.(?:shp|gpkg|geojson|tiff?|nc|hdf[45]?|gdb|fgb|kml|las|laz|parquet)', re.IGNORECASE),
    'coords': re.compile(r'(?:lat(?:itude)?|lon(?:gitude)?|lng)\s*[=:]\s*(-?\d+\.?\d*)', re.IGNORECASE),
    'models': re.compile(r'(?:sam2?|segment.?anything|yolo\w*|resnet\w*|u-?net|dee
Read more
@opengeos
Ships withgeoai-skills

A Claude Code plugin that adds GeoAI-powered skills for geospatial data exploration, satellite imagery download, AI-based object detection, and session memory. Built on the GeoAI Python library.

Stars on GitHub

Momentum

stars in total
36
Star history of opengeos/geoai-skills on GitHub, a chart by star-history.com

Other skills on geoai-skills.

detect-objects

detect-objects

Run pre-trained AI models on geospatial imagery. Detect buildings, cars, ships, solar panels, agriculture fields, or use…

download-data

download-data

Download NAIP aerial imagery for a bounding box. Specify coordinates as minx,miny,maxx,maxy in WGS84 and optionally a year.

inspect-geo

inspect-geo

Inspect any raster or vector geospatial file. Returns CRS, bounds, bands, resolution, dtype, attribute summaries, and band…

install-geoai

install-geoai

Verify that the geoai Python package is installed and functional. If not, provide installation instructions. Optionally check…

overture-data

overture-data

Download Overture Maps data (buildings, places, roads, land use, water, etc.) for a bounding box. Returns a GeoDataFrame saved as…

process-raster

process-raster

Process raster data: clip by bounding box, stack multiple bands, mosaic GeoTIFFs, or convert between raster and vector formats.

More skills from other plugins.

context-loader
Auto-invoked

context-loader

Search memories from earlier Pi sessions in this repository. Use it when earlier work may already explain the code, error,…

mem08d69k
@mem0ai@mem0aiInstall Skill
analyzing-ai-citations

analyzing-ai-citations

Answer AI citation and answer engine optimization (AEO) questions in PostHog: whether AI answer engines (ChatGPT, Claude, and an…

posthog2w41k
@posthog@posthogInstall Skill

Questions about read-memories.

Which coding agents can use read-memories?
read-memories installs in Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode and Windsurf with the skills CLI, using each agent's flag shown on this page. Flowy's auto-invocation, which fires a skill when your prompt matches, runs in Claude Code only.