Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
f7edc8a
fix(verify): distinguish appended exchanges from archive refreshes
HengYangDS Sep 26, 2026
2e44c81
fix(summarizer): keep Codex summaries on Codex route
HengYangDS Sep 26, 2026
9c9173a
feat(admission): enforce record exclusions at ingestion and recall
HengYangDS Sep 26, 2026
cf21003
fix(search): rank admitted vectors by computed distance
HengYangDS Sep 26, 2026
adda5d6
fix(verify): keep verification read-only and signal lock contention
HengYangDS Sep 26, 2026
5f277c2
fix(admission): keep sessionless Codex summaries on Codex
HengYangDS Sep 26, 2026
0c7c541
fix(verify): classify record screening and stop unsafe repair
HengYangDS Sep 26, 2026
5e5d6b0
fix(admission): refresh same-mtime appended archives
HengYangDS Sep 26, 2026
c8c1900
test(verify): derive repair fixture from verifier result
HengYangDS Sep 26, 2026
b9f880a
fix(codex): keep sessionless summaries on Codex
HengYangDS Sep 26, 2026
1dfaf61
docs(redaction): Phase 0 findings and choke-point decision
claude Oct 4, 2026
9b16810
feat(redaction): redact secrets at the archive write, before index/em…
claude Oct 4, 2026
0c57890
fix(redaction): use key context so .NET/Azure secrets without a shape…
claude Oct 4, 2026
a6a4797
docs(verify): explain why repair keys resume off the redaction setting
darthmolen Oct 5, 2026
6a49ea1
fix(redaction): create redacted copies with the source file's mode
darthmolen Oct 5, 2026
fff4326
fix(indexer): force the archive refresh for already-indexed transcripts
darthmolen Oct 5, 2026
c931a14
fix(redaction): match an Entra client secret that ends a sentence
darthmolen Oct 5, 2026
3b7edc2
fix(redaction): match connection-string keywords in any case
darthmolen Oct 5, 2026
70592f5
fix(redaction): redact the part of a match outside an existing token
darthmolen Oct 5, 2026
64e0059
fix(redaction): redact a secret field whole even if it holds a token
darthmolen Oct 5, 2026
1cb16af
fix(verify): load the redaction rules before index repair
darthmolen Oct 5, 2026
505996f
feat(redact): read-only dry run and --report to review hits
darthmolen Oct 5, 2026
c46a8ed
fix(redact): pair report hits with their tokens exactly
darthmolen Oct 5, 2026
87e806a
fix(redaction): require a letter or digit in a connection-string value
darthmolen Oct 5, 2026
46c6a8a
docs(redaction): fold the design note into one summary-level guide
darthmolen Oct 5, 2026
a8eadb5
fix(codex): bound isolated summary process shutdown
HengYangDS Oct 10, 2026
ab29746
chore: keep unchanged MCP bundle in verifier fix
HengYangDS Oct 11, 2026
8e6d75b
chore: keep unchanged MCP bundle in Codex routing fix
HengYangDS Oct 11, 2026
e0792eb
build: retain native MCP scope without dependency churn
HengYangDS Oct 11, 2026
c7cca0f
fix(admission): withhold removed transcripts instead of failing search
HengYangDS Oct 15, 2026
5071114
Merge verifier classification into record admission
HengYangDS Nov 8, 2026
e0698c8
fix(summary): preserve provider when source resume is disabled
HengYangDS Nov 8, 2026
615c3d4
Merge Codex provider invariant into record admission
HengYangDS Nov 8, 2026
68cf887
Merge secret redaction into record admission
HengYangDS Nov 9, 2026
55c61f0
fix(summarizer): retain native authentication under settings isolation
HengYangDS Nov 9, 2026
aa29032
test: clean isolated configuration after each suite
HengYangDS Nov 9, 2026
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
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,23 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Security

- Recognized secrets are replaced with typed tokens before admitted records enter the archive, index, embeddings or summaries. The rules cover common provider keys, private keys, JWTs, connection strings and secret-named configuration fields. Record exclusions apply first, source transcripts stay unchanged, and safe neighboring records retain their original line numbers. Redaction is on by default and stops ingestion if its rules cannot load. See `docs/REDACTION.md` for coverage and limits.
- With redaction on, summaries use the admitted transcript instead of resuming the original session. Codex conversations stay on Codex; failures do not switch to Claude. Claude conversations keep their transcript-text route.

### Added

- `episodic-memory redact --rewrite` cleans data you indexed before upgrading. It redacts the archive and the search index in place, re-embeds only the messages that changed, and deletes summaries built from unredacted text so they regenerate. Use `--dry-run` to preview (it writes nothing, not even a schema migration), and add `--report` to list each value it would redact, by location, rule and shape, without printing the value.
- Custom redaction rules via `~/.config/superpowers/redaction-rules.json`, which extends the bundled defaults. Try rules with `episodic-memory redact --stdin`. See `docs/REDACTION.md`.
- New settings: `EPISODIC_MEMORY_REDACTION` (`on`/`off`), `EPISODIC_MEMORY_REDACTION_RULES`, and `EPISODIC_MEMORY_REDACTION_STRICT`.

### Changed

- `episodic-memory index` now parses the archived copy instead of the source transcript, and refreshes that copy when the source has grown. This is the same behavior `sync` already had.

## [1.6.0] - 2026-09-08

Adds a fifth conversation source, an off switch for automatic syncing, and two fixes for real-world resource problems.
Expand Down
185 changes: 176 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,21 @@

Semantic search for Claude Code, Codex, Cursor, opencode, and Oh My Pi (OMP) conversations. Remember past discussions, decisions, and patterns.

## Recall admission

Search rechecks the current record-exclusion policy and configured credential scanner,
including rows indexed before the policy changed. Excluded hits do not consume the
requested result limit. A stale whole-conversation summary is omitted if any of its
source records or tool calls is excluded; safe neighboring exchanges remain searchable.

Conversation opt-out markers are read from decoded user text, including JSON escapes.
Assistant quotations and tool outputs do not opt out the conversation. Tool-result
blocks inside a user envelope are still tool output. Malformed legacy text retains
conservative marker handling, and unreadable or oversized records fail closed.
Ranged reads retain physical line coordinates without materializing unrelated message bodies.
Original transcripts are never rewritten. Source and policy changes during a search
abort the result rather than returning a partially validated view.

## Testimonial

From an AI coding assistant's perspective:
Expand Down Expand Up @@ -214,15 +229,17 @@ In Codex and opencode, the skill guides the agent to use the episodic-memory MCP

## API Configuration

By default, episodic-memory uses your Claude Code authentication for Claude Code summarization. Codex-indexed sessions with a session ID are summarized through `codex app-server` by creating an ephemeral `thread/fork`, so the summary can use Codex session context and reasoning summaries without appending to the original rollout.
By default, episodic-memory uses your Claude Code authentication for Claude Code summarization. Codex-indexed sessions with a session ID are summarized through `codex app-server` by creating an ephemeral `thread/fork`, so the summary can use Codex session context and reasoning summaries without appending to the original rollout. A Codex transcript without a session ID is summarized from its admitted text in an isolated ephemeral `thread/start`; it is never sent to Claude.

Claude summaries preserve the user-level `apiKeyHelper` and `env.ANTHROPIC_BASE_URL` from the selected Claude configuration directory. The helper remains with Claude Code; episodic-memory does not retrieve or store its credential. Explicit environment and episodic endpoint overrides take precedence. Hooks, plugins, project settings and MCP servers remain excluded from the summarizer. Missing settings leave native environment or OAuth authentication available; unreadable or malformed settings stop the call with a message that omits their contents.

To route summarization through a custom Anthropic-compatible endpoint or override the model:

```bash
# Override model (default: haiku)
export EPISODIC_MEMORY_API_MODEL=opus

# Override fallback model on error (default: sonnet)
# Override the Claude fallback model on error (default: sonnet; not used for Codex)
export EPISODIC_MEMORY_API_MODEL_FALLBACK=sonnet

# Route through custom endpoint
Expand Down Expand Up @@ -294,7 +311,7 @@ These pass through unchanged to episodic-memory's summarizer subprocess. Because

These settings only affect episodic-memory's summarization calls, not your interactive Claude Code or Codex sessions.

Codex summarization requires `codex-cli 0.130.0` or newer. If Codex app-server summarization is unavailable, sync logs the reason and falls back to transcript-text summarization.
Codex summarization requires `codex-cli 0.130.0` or newer; isolated transcript-only summaries require `0.154.0` or newer. If the applicable Codex route is unavailable, sync records a retryable summary failure rather than switching to Claude or a metered Anthropic API. Exchanges can still be indexed and searched without a generated summary.

### What's Affected

Expand Down Expand Up @@ -327,6 +344,18 @@ Add to `.claude/hooks/session-end`:
episodic-memory sync
```

### `episodic-memory redact`

```bash
episodic-memory redact --rewrite --dry-run # what would change
episodic-memory redact --rewrite --dry-run --report # each hit: location, rule, value shape
episodic-memory redact --rewrite # redact the existing archive + index in place
episodic-memory redact --stdin < file.jsonl # try the rules on some text
episodic-memory redact --print-default-rules
```

See [docs/REDACTION.md](docs/REDACTION.md).

### `episodic-memory stats`

Display index statistics including conversation counts, date ranges, and project breakdown.
Expand Down Expand Up @@ -372,9 +401,14 @@ This test uses your normal Claude Code auth and writes small test transcripts to
Manual indexing tools for bulk operations and maintenance. See `episodic-memory index --help` for full options.

Common operations:
- `--cleanup` - Index all unprocessed conversations
- `--verify` - Check index health
- `--repair` - Fix detected issues
- `--cleanup` - Index unprocessed exchanges; summaries may call an AI provider unless disabled
- `--verify` - Distinguish missing summaries, unindexed archives, appended exchanges, and archive mtime refreshes
- `--repair` - Re-embed and regenerate summaries for repair candidates; review the scope and provider cost first

An archive mtime refresh without new exchanges is reported separately and is
not an outdated-index repair target. An unindexed archive is reported, but
that status alone does not select it for `--repair`; a separate overlapping
repair category may still cause it to be processed.

### `episodic-memory search`

Expand Down Expand Up @@ -415,16 +449,39 @@ open output.html
4. **Index** - Stores in SQLite with sqlite-vec for fast similarity search
5. **Search** - Semantic search using vector similarity or exact text matching

## Secret Redaction

Recognized secrets in your conversations (Azure client secrets, storage keys, SAS
signatures, connection-string passwords, private keys, JWTs, provider API keys,
`password: …` assignments) are replaced with typed tokens such as
`[REDACTED:azure-storage-key]` **before** anything is archived, indexed,
embedded, or sent to the summarizer. Shape rules preserve Git SHAs and GUIDs;
a value in a secret-named field is still redacted. The tokens are searchable too.

Redaction is on by default and fails closed: if the rules can't be loaded, sync
won't run. After upgrading, run `episodic-memory redact --rewrite` once to clean
data you indexed before. Record exclusions and screening apply before redaction;
disabling redaction does not disable them. Summaries keep the original provider.

| Variable | Default | Meaning |
|---|---|---|
| `EPISODIC_MEMORY_REDACTION` | `on` | `off` disables redaction |
| `EPISODIC_MEMORY_REDACTION_RULES` | `<config dir>/redaction-rules.json` | Custom rules file (extends the defaults) |
| `EPISODIC_MEMORY_REDACTION_STRICT` | `1` | `0` continues unredacted (with a warning) when rules fail to load |

See [docs/REDACTION.md](docs/REDACTION.md) for the rule list, the custom-rules
format, and what is and isn't covered.

## Excluding Conversations

Conversations containing this marker anywhere in their content will be archived but not indexed:
Conversations with this marker in a decoded user instruction are excluded before new archive copying, parsing, and indexing:

```
<INSTRUCTIONS-TO-EPISODIC-MEMORY>DO NOT INDEX THIS CHAT</INSTRUCTIONS-TO-EPISODIC-MEMORY>
```

**Automatic exclusions:**
- Conversations where Claude generates summaries (marker in system prompt)
- Summarizer-generated conversations carrying the marker in their prompt
- Meta-conversations about conversation processing

**Use cases:**
Expand All @@ -433,7 +490,117 @@ Conversations containing this marker anywhere in their content will be archived
- Test or experimental sessions
- Any conversation you don't want searchable

The marker can appear in any message (user or assistant) and excludes the entire conversation from the search index.
The marker in user instructions prevents new derived copies and indexing; an assistant quotation or tool result does not opt out the conversation. It does not delete an existing archive or index entry.

### Persistent record exclusions

`record-exclusions.json` in the existing configuration directory defines exact
records that must not enter new derived archives, parser output, summaries, or
database insertions. It is independent of `CONVERSATION_SEARCH_EXCLUDE_PROJECTS`;
that environment variable cannot replace or disable record exclusions.

```json
{
"version": 1,
"exchanges": [
{
"session_id": "00000000-1111-4222-8333-444444444444",
"transcript": "rollout-00000000-1111-4222-8333-444444444444.jsonl",
"line_start": 4,
"line_end": 5
}
],
"tool_calls": [
{
"session_id": "00000000-1111-4222-8333-444444444444",
"transcript": "rollout-00000000-1111-4222-8333-444444444444.jsonl",
"call_id": "call-example"
}
]
}
```

Line ranges are inclusive physical JSONL coordinates in the original transcript.
Excluded records become empty lines in a derived copy, preserving later line
numbers. Session identity, exact transcript basename and native tool-call identity, not
a machine-specific absolute path, bind the policy. Sibling subagent transcripts
sharing a parent session are separate subjects. Safe text blocks remain when only a tool block
is excluded. Original transcripts are never changed.

Malformed, unreadable, symlinked or oversized policy files reject admission.
When a policy is present, unchanged source timestamps do not bypass a fresh
admission decision. Source/archive aliasing and changing input are rejected.
The marker scan and record stream use the same descriptor; source identity is
checked during and after streaming. Direct database insertion checks both the
transcript identity and historical session metadata, which may name an ancestor.
The foreground sync CLI exits nonzero for per-file admission errors and does not
claim successful synchronization or start embedding migration after those errors.
Full, session and incremental indexing, repair, MCP reads and the display CLI use
the same admission boundary. Display pagination retains physical line numbers.
An absent policy retains ordinary behavior; deleting the policy intentionally
removes its protection, so maintain it as private operator configuration.

Summarization with a record policy uses admitted transcript text, never a resumed
source session. Codex retains its selected provider through an ephemeral fresh
context and fails rather than silently falling back to another provider. Claude
receives the admitted text without session resume. Policy-enabled Codex summaries
require Codex 0.154.0 or newer. The isolated summary path disables inherited
tools, plugins, hooks, and skills, and refuses to proceed if the configured
MCP or skill boundary cannot be established. It uses a temporary empty working
directory; provider error payloads are suppressed and the directory is removed
after process termination.

### Optional local secret screening

Add an optional `screening` object to the same policy to reject newly detected
content before derived archive writes, reads, summaries and database insertion:

```json
"screening": {
"engine": "gitleaks",
"executable": "/absolute/resolved/path/to/gitleaks",
"sha256": "<64 lowercase hex characters from the trusted executable>",
"timeout_ms": 2000,
"max_record_bytes": 1048576
}
```

Keep the existing `version`, `exchanges` and `tool_calls` fields. Install Gitleaks
through a trusted package source, resolve its executable symlink and verify its provenance before pinning its
SHA-256. On migration or upgrade, explicitly replace the host-local path and
digest after verifying the new executable. The scanner must be a regular,
executable file owned by the current user or root and not group/world-writable.
Do not copy credential stores or an old host's executable-path assumption.

Exact exclusions run first. Each remaining physical record is screened locally
with the pinned Gitleaks built-in rules. Nested JSON strings are decoded while
retaining key/value assignment context; separate exchange messages are screened
separately. A finding rejects the operation rather than silently dropping a new
message and breaking conversation pairing. Missing/changed executables, byte or
nesting limits, timeouts and malformed or contradictory reports fail closed.
Verification reports scanner rejections and scanner unavailability separately
from transcript corruption, even when a summary is missing. A broad repair
refuses to start while either screening failure remains.
No findings, input, scanner diagnostics or secret fingerprints appear in errors.
The scanner receives stdin in a private empty working directory with no inherited
environment/configuration and with inline allow comments disabled. Its temporary
directory is removed after execution. Original transcripts and previously
published archives remain unchanged when admission fails.

Screening launches a bounded subprocess per record; it is not an unbounded bulk
collection service. Measure a finite representative batch before large imports.
No automatic sync or background job is enabled by this setting. Before enabling
it, replace all policy consumers with this version through their native lifecycle:
older strict-schema consumers reject the additional field. Installing a plugin
does not replace already-loaded code, so do not enable the field while protected
old consumers still require the previous schema.

Without `screening`, this policy remains an exact exclusion mechanism. Even with
screening, pattern matching does not prove universal unknown-secret absence,
purge preexisting derived data, rewrite original
sessions, or retroactively filter already-loaded code. Old randomly generated
tool-row IDs need verified native source-call mapping before policy migration;
do not broaden those entries to whole sessions or claim a complete migration.

## MCP Server

Expand Down
5 changes: 5 additions & 0 deletions cli/episodic-memory.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ COMMANDS:
stats Show index statistics
doctor Diagnose Claude Code or Codex integration issues
import-cursor-history Export legacy Cursor conversations from state.vscdb for indexing
redact Re-run secret redaction over the archive and index (--rewrite)

Run 'episodic-memory <command> --help' for command-specific help.

Expand Down Expand Up @@ -94,6 +95,10 @@ async function main() {
await runScript(join(distDir, 'cursor-import-cli.js'), args);
break;

case 'redact':
await runScript(join(distDir, 'redact-cli.js'), args);
break;

case '--help':
case '-h':
case undefined:
Expand Down
14 changes: 7 additions & 7 deletions cli/index-conversations.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,16 +50,16 @@ USAGE:

COMMANDS:
(default) Index all conversations
--cleanup Process only unindexed conversations (fast, cheap)
--cleanup Process unindexed exchanges (may generate AI summaries)
--session ID Index specific session (used by hook)
--verify Check index health
--repair Fix detected issues
--verify Classify index and summary state
--repair Re-embed and summarize repair candidates (review first)
--rebuild Delete DB and re-index everything (requires confirmation)

OPTIONS:
--concurrency N Parallel summarization (1-16, default: 1)
-c N Short form of --concurrency
--no-summaries Skip AI summary generation (free, but no summaries in results)
--no-summaries Skip AI summary provider calls
--help, -h Show this help

EXAMPLES:
Expand All @@ -69,13 +69,13 @@ EXAMPLES:
# Index with 8 parallel summarizations (8x faster)
index-conversations --cleanup --concurrency 8

# Index without AI summaries (free, fast)
# Index without AI summary provider calls
index-conversations --cleanup --no-summaries

# Check index health
index-conversations --verify

# Fix any issues found
# Review candidates and provider cost before broad repair
index-conversations --repair

# Nuclear option (deletes everything, re-indexes)
Expand All @@ -85,7 +85,7 @@ WORKFLOW:
1. Initial setup: index-conversations --cleanup
2. Ongoing: Auto-indexed by sessionEnd hook
3. Health check: index-conversations --verify (weekly)
4. Recovery: index-conversations --repair (if issues found)
4. Recovery: review exact verifier categories before repair

SEE ALSO:
INDEXING.md - Setup and maintenance guide
Expand Down
3 changes: 3 additions & 0 deletions dist/cursor-legacy.d.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { type Redactor } from './redaction.js';
export declare function getDefaultCursorVscdbPath(): string | undefined;
/**
* Collect composer IDs that already have live agent transcripts under
Expand All @@ -14,6 +15,8 @@ export interface CursorLegacyImportOptions {
force?: boolean;
/** Report what would be exported without writing files. */
dryRun?: boolean;
/** `undefined` loads from the environment (strict mode throws on bad rules); `null` disables. */
redactor?: Redactor | null;
}
export interface CursorLegacyImportResult {
exported: number;
Expand Down
Loading