Why
Every AI coding session leaves a transcript behind. Claude Code buries them in ~/.claude/projects/, Codex and Pi have their own layouts, OpenCode keeps everything in a SQLite database — and everything in them (what you tried, what you decided, what broke) is effectively write-only.
sessions builds a full-text search index over all of it and makes that history useful in three ways:
- Search & resume (CLI) — fuzzy-find any past session across all four tools, ranked by relevance, and jump back in.
- Agent memory (MCP) — agents search your history, pull a repo-scoped context primer when you return to a codebase, and answer "what did I do last week?" via bundled skills.
- Usage reports — a token/cost dashboard across tools, models, and projects.
Install
Homebrew
brew install nicknisi/formulae/sessions
Or, equivalently:
brew tap nicknisi/formulae
brew install sessions
From source
git clone https://github.com/nicknisi/sessions && cd sessions
bun install && bun run build
The compiled binary is at dist/sessions. Requires Bun when building from source. The Homebrew install is a standalone binary — no runtime needed.
Dependencies
- fzf (optional but recommended) — used for fuzzy selection. If fzf is not installed, a built-in numbered list selector is used as a fallback. Install with
brew install fzf.
Quick Setup
After installing, run:
sessions setup
This automatically:
- Copies the plugin and skills to
~/.local/share/sessions/plugin/ - Detects which AI tools you have installed (Claude Code, Cursor, Codex)
- Adds the MCP server config to each tool
- Registers the plugin so skills are discoverable
❯ sessions setup
sessions setup
✓ Plugin installed to ~/.local/share/sessions/plugin/
✓ MCP server added to Claude Code
✓ Plugin registered with Claude Code
✓ MCP server added to Cursor
✓ Plugin registered with Cursor
Skills available:
/context Context primer for the current repo
/weekly-summary Summarize your past week's AI sessions
/standup Yesterday + today activity for standups
/recall What did I do on a specific project?
/session-metrics Usage dashboard with tool breakdown
/memory Triage durable facts mined from past sessions
Run `sessions setup` again after upgrading to update skills.
After upgrading sessions (e.g., brew upgrade sessions), run sessions setup again to update the skills to the latest version.
To remove the plugin, the MCP config, and the SessionStart hook: sessions uninstall. It removes only what the installer created — durable data in ~/.local/share/sessions (memory triage decisions) is left alone.
CLI: search & resume
sessions # Browse all sessions with fzf
sessions <query> # Full-text search across session content
sessions --here # Scope to current git repo only
sessions --tool claude # Filter to Claude Code sessions only
sessions --errored # Only sessions that hit an error
sessions --file src/auth.ts # Only sessions that touched this file
sessions context # Print a context primer for the current repo
sessions digest <session> # Print one session's arc as compact markdown
sessions report # Usage report (HTML dashboard, opens in browser)
sessions memory mine # Mine past sessions for durable facts (JSON on stdout)
sessions memory mine --since-last # ...only transcripts changed since the last mine
sessions memory pending # Count + preview the candidates awaiting triage
sessions memory approve <id> # Keep a mined candidate as a durable memory
sessions memory approve <id> --always-on # ...and exempt it from topic filtering
sessions memory approve <id> --no-always-on # ...or explicitly revoke that exemption
sessions memory approve <id> --scope group:authkit # ...and scope it to a project group
sessions memory approve <id> --scope repo:. # ...or bind it to this repo (imports arrive unbound)
sessions memory reject <id> # Dismiss a candidate; it stops being emitted
sessions memory snooze <id> # Hide a candidate without rejecting it
sessions memory merge <id> <id>... # Fold paraphrases into the first id
sessions memory export # Write approved memories as a portable bundle (JSON)
sessions memory import <p> # Merge another author's bundle in as candidates
sessions memory import --from pi-hermes # ...or import another agent's memory store
Options
| Flag / Command | Description |
|---|---|
context |
Print a markdown context primer for the current repo (see Context primer) |
digest <session> |
Print one session's arc as compact markdown (~8k chars): each genuine user turn with its exchange's final assistant reply. Accepts a JSONL file path or a session id |
report |
Generate a usage report (see Usage reports) |
memory mine |
Mine past sessions for durable facts worth remembering and print the candidate batch as JSON. --repo <path> scopes to one repo container (default: the current repo); --all mines every repo; --since-last mines only transcripts whose mtime or size changed since the previous mine, so a repeat run over an unchanged corpus emits an empty batch |
memory pending |
Print the untriaged backlog as JSON: the true candidate count plus the first five texts. Reads the store only — it never mines, which is what makes it cheap enough for /weekly-summary to call |
memory <action> |
Record a triage decision by the id from the mine's batch: approve <id>, reject <id> (never emitted again), or snooze <id> (hidden until the snooze expires and a later merge adds a phrasing that was not there before). approve also takes --always-on (budgeted — see Topic filtering and standing constraints), --no-always-on to explicitly revoke it, --scope group:<name> — see Project groups — and --scope repo:<path>, which binds a memory to one repo (the path is resolved to its repo container, so any worktree or subdirectory of it works) |
memory export |
Write approved memories as a portable JSON bundle on stdout; --out <path> writes a file. Approved records only, with session paths and repo paths stripped — no local paths leave the machine |
memory import <path> |
Merge a bundle from another author in as candidates to triage. Never lands as approved and never overwrites your own approve/reject decisions. A repo-scoped memory arrives with no repo key — export strips local paths — so bind it to one of your repos when you approve it: --scope repo:.. Import says how many need it |
memory import --from <source> |
Import the durable facts another agent on this machine has stored, as candidates to triage: pi-hermes (its structured store, or MEMORY.md/USER.md/failures.md), claude (global and repo CLAUDE.md/AGENTS.md plus the per-project memory store), or all. --repo <path> sets the repo context (default: cwd). Long consolidated entries split at their own sentence boundaries to fit the memory band; entries already in the store count as known, not duplicates. codex stores hold command permissions, not facts, so it imports nothing. See Other agents' memory stores |
setup |
Install plugin and configure MCP for detected tools (--hooks opts into auto-injection) |
uninstall |
Remove plugin, MCP config, and the SessionStart hook from all tools. Triage decisions in ~/.local/share/sessions are preserved |
cleanup |
Full reset: uninstall plugin + clear search index |
--here |
Scope to the current git repo (default: all projects) |
--tool <name> |
Filter by tool: claude, codex, pi, or opencode |
--errored |
Only show sessions that hit an error |
--file <path> |
Only sessions that touched or read this path (substring match; repeatable — every path must match). Newest first when no query is given |
--mcp |
Start as an MCP server (stdio transport) |
--clear-cache |
Remove the search index (rebuilds on next use) |
--no-color |
Disable colored output |
-h, --help |
Show help |
Browsing
With no arguments, sessions scans all session directories, extracts the first user prompt from each conversation, and pipes the results into fzf for fuzzy selection:
● my-project claude today Refactor the auth middleware to use JWT
● my-project pi 2d Help me debug the flaky integration test
● api-server codex 1w Add rate limiting to the /api/v2 endpoints
○ old-project claude 2025-03 Set up the initial project structure
- ● (green) — the project directory still exists
- ○ (red) — the project directory has been deleted
Searching
Pass a query to run a full-text search across all sessions:
sessions "rate limit"
The CLI uses the same search index as the MCP server: results are ranked by relevance (BM25) rather than recency, matching uses porter stemming (refactor matches refactoring), and multi-word queries match sessions containing any of the terms with the closest matches first. Search covers both your messages and the assistant's replies; system-injected content (<system-reminder>, etc.) is stripped from your messages so you only match what you actually typed. Search is message-granular: each matching session is shown with the best-matching message's snippet and its msg#N index, then piped into fzf.
After selection
When you pick a session, sessions displays the resume command and copies it to your clipboard:
my-project (claude)
Refactor the auth middleware to use JWT
cd /Users/you/Developer/my-project && claude --resume abc123
(copied to clipboard)
For Claude Code sessions, the command includes --resume <session-id>; for OpenCode, opencode --session <session-id>. For Pi and Codex sessions, it navigates to the project directory (these tools don't support direct session resume).
Agent memory
sessions includes an MCP server that gives AI agents searchable access to your past conversations — across every tool, not just the one they're running in. sessions setup configures it automatically; for manual setup, add to your MCP configuration (e.g., ~/.claude/.mcp.json):
{
"mcpServers": {
"sessions": {
"command": "sessions",
"args": ["--mcp"]
}
}
}
MCP tools
The MCP server exposes ten tools:
| Tool | Description |
|---|---|
search_sessions |
Ranked, top-k search across sessions by keyword; each result includes snippets, files/commands, an errored flag, a resume command, and messageHits — the specific matching messages (index, role, snippet). files and commands are capped at 10 and 5, with fileCount/commandCount reporting the true totals. A files filter answers "which sessions touched this file?" (newest first) |
grep_sessions |
Exhaustive literal-or-regex match over every indexed message — for "every time I said X", counts, or exact patterns where ranked search would miss some. Returns totalHits/totalSessions and hit snippets; each hit's msgIndex feeds get_session_messages directly |
get_session_messages |
Retrieve messages from a specific session, paginated by offset and limit — pass a messageHits[].index from search (or a grep_sessions hit's msgIndex, or an exchanges[].index from the digest) as the offset. include_tools annotates each turn with the assistant's tool calls |
get_session_digest |
The arc of one session in a single bounded call (~2k tokens): every genuine user turn paired with its exchange's final assistant reply. Long sessions elide middle exchanges, never the ends |
get_activity_digest |
Compact digest of sessions in a date range, grouped by day and project — for weekly summaries |
get_session_metrics |
Usage metrics for a date range: tool/project breakdown, daily activity, active hours |
get_context_primer |
Repo-scoped primer (recent sessions in detail + older headlines) for re-injecting prior work |
get_memory |
Approved standing instructions and durable facts for this repo, its project groups, and cross-repo workflow rules — a bounded set of short sentences to read before starting a task. An optional topic narrows the result to what is relevant to the task at hand; memory approved with --always-on are returned regardless and come first |
get_memory_sources |
Inventory every memory store the agents on this machine keep — pi-hermes-memory, Claude Code's global/per-project memory and CLAUDE.md/AGENTS.md files, Codex's rules and goals — with entry counts, durable-fact counts, and last-updated dates. Pure discovery for "what does each agent know?" |
review_agent_memories |
Read the CONTENTS of those stores with provenance: source agent and store, scope, a durable flag (importable fact vs audit-only material), and a similarTo flag when an entry substantially overlaps a memory sessions already holds. Secret/injection text is withheld with a count. Filter by agent, narrow by topic; capped at 50 entries with total/truncated reporting the rest |
Together these support the recall flow the bundled skills teach: search_sessions (ranked) or grep_sessions (exhaustive) localizes the hit to a message, the digest gives a session's whole arc in one call, and targeted message reads expand only the exchanges that matter — no paging full transcripts.
The get_activity_digest tool supports a detail parameter: "compact" (default) returns topics and file paths only, "highlights" adds first and last user messages for substantive sessions (best for summaries), and "full" includes all user messages per session.
Skills
The plugin ships six skills that compose the MCP tools (and, for /memory, the CLI) into repeatable workflows:
| Skill | Trigger | What it does |
|---|---|---|
/context |
"what was I doing here", "catch me up" | Repo-scoped primer: prior decisions, dead ends, the open thread |
/recall |
"what did I do on [project]" | Searches by topic or file, digests the best candidates, expands only the matched exchanges |
/standup |
"standup", "what did I do yesterday" | Yesterday + today in compact format, terse bullets for Slack |
/weekly-summary |
"summarize my week", "weekly recap" | Fetches full digest for the past 7 days, writes structured report, then nudges toward any new memory candidates |
/session-metrics |
"session stats", "which tool do I use most" | Tool/project breakdown, daily activity, active hours heatmap |
/memory |
"triage memory", "review memory" | Runs the mine, clusters paraphrases, merges them, and walks approve / reject / snooze |
Skills work with Claude Code, Cursor, Codex, and any agent that supports the skills.sh format.
Project groups
A memory's scope is normally derived: a fact seen in one repo container is repo-scoped, one seen across unrelated containers is workflow-scoped. A project group is the tier in between — a fact true of several related repos, but not of everything.
Group membership cannot be derived from your session history, so it comes from a config file at ~/.local/share/sessions/groups.json (or $SESSIONS_DATA_DIR/groups.json). Nothing writes it for you; create it yourself:
{
"groups": {
"authkit": ["~/Developer/authkit-*"],
"workos-cli": ["~/Developer/cli/*"]
}
}
Each group maps a name to a list of path globs matched against the resolved repo container. ~ expands to your home directory, and a subdirectory of a match counts as a member — working in ~/Developer/authkit-session/packages/core puts you in authkit.
Then assign a group at triage time:
sessions memory approve <id> --scope group:authkit
The file is never read from inside a repo, and it is never required. If it is missing, malformed, or does not mention a memory's group, retrieval quietly degrades to repo-plus-workflow rather than failing — but that also means a group memory with no matching config is silently never returned, so check the file if a group memory is not showing up.
Topic filtering and standing constraints
get_memory takes an optional topic. When present, the returned set is narrowed to memory whose text overlaps the topic, so an agent about to work on authentication does not spend context on your build conventions. Omitting topic returns everything, exactly as before.
Some facts must never be filtered out — "canary is the mainline branch" has to reach the agent whether or not the task description mentions branching. Approve those with --always-on:
sessions memory approve <id> --always-on
An always-on memory is returned for every topic and sorts first, so an agent that truncates drops the conditional tail rather than the invariants. It still respects state and scope: rejected, snoozed, and out-of-scope memories are never returned. Approving again without the flag does not clear it — omission is not a decision; clearing takes an explicit --no-always-on.
The set is budgeted: at most 20 always-on memories totalling 2,000 characters. The cap is what keeps the flag's promise credible — standing constraints only cut through if there are few enough of them to always be read — so a grant past it is refused with instructions to free a slot (--no-always-on). A store already over budget (hand-edited, or written before the cap existed) is still served in full, and get_memory says so: truncating a standing constraint would be exactly the silent suppression the flag exists to prevent.
Content scanning
Memory text is scanned at every boundary it can cross. Secret material (API keys, tokens, private-key blocks), prompt-injection phrasing ("ignore previous instructions…"), and invisible Unicode characters never enter the store or reach an agent: the mine drops the turn, import withholds the record and says so on stderr, approve refuses with the finding named, and get_memory withholds any pre-existing approved row while reporting its id — so you can reject it or re-approve a clean rephrasing with --as. The withheld text itself is never served, not even in the warning.
Facts about secret handling are deliberately not flagged — "set DATABASE_URL through doppler, never in .env" and "never pipe $GITHUB_TOKEN through curl" are exactly the memories worth keeping. The scanner matches secret material and hijack phrasing, not secret vocabulary: a prohibition legitimately names the thing it prohibits.
Other agents' memory stores
Sessions are only half of what an agent remembers. Each harness also keeps its own store beside the conversation, and if you hop between agents those stores scatter: pi-hermes-memory writes a categorized SQLite store plus MEMORY.md/USER.md/failures.md; Claude Code injects a global ~/.claude/CLAUDE.md, per-repo CLAUDE.md/AGENTS.md files, and a per-project memory/ directory; Codex records command-permission rules. Three surfaces make them visible and portable, all read-only against the source stores:
get_memory_sources(MCP) inventories every store it can see for the current repo: what it is, how many entries it holds, how many of those are durable facts, and when it was last updated. This is the "what does each agent know about me?" answer.review_agent_memories(MCP) reads their contents with provenance. Every entry carries its source agent and store, a sessions-style scope, and adurableflag —truefor standing facts (importable),falsefor audit-only material like Codex's allow/deny rules or Claude's agent research notes. Entries that substantially overlap a memory sessions already stores are flagged insimilarTo, so redundancy and near-conflicts between agents are visible. The content scanner gates this surface too: flagged text is withheld with a count, never served. These entries are untriaged source material, not binding instructions —get_memoryremains the binding surface.sessions memory import --from <pi-hermes|claude|all>(CLI) lands another store's durable facts in your own store as candidates, through the same gates as a bundle import: the text band, the content scan, and candidate-until-approved. Long consolidated entries (pi-hermes averages ~1,100 characters per row) split at their own enumeration and sentence boundaries to fit the 240-character band; fragments that can't be reshaped are counted, never silently dropped. Entries already in the store are reported as known, and a pi-hermes fact recorded against a bare project name arrives unbound — bind it withapprove <id> --scope repo:.like any import.
The workflow this enables: switch agents for a project, run get_memory_sources to see what the old harness learned, review_agent_memories to audit it, and memory import --from to carry the facts worth keeping — then triage with /memory as usual. Codex keeps permission decisions rather than facts, so --from codex reports that and imports nothing.
Context primer
When you return to a codebase, the primer answers "where did I leave off?" — recent sessions in detail, older ones as headlines, including the branch you were on and the last exchange of each session. It's available three ways:
- On demand from an agent — the
/contextskill or theget_context_primerMCP tool - On demand from the shell —
sessions contextprints it as markdown (--fullwidens detail;--limit/--days/--toolfilter;--worktreenarrows to the current worktree;--out <path>writes to a file) - Automatically at session start — opt-in, below
Auto-injecting context at session start (opt-in)
You can have a small primer injected automatically at the start of every Claude Code session via a SessionStart hook:
sessions setup --hooks # enable auto-injection (Claude Code)
Run without --hooks and setup will ask interactively (when on a TTY); it is off by default because it costs a small number of tokens on every session. The hook runs sessions context --hook — a tiny primer (the 3 most recent sessions for the current repo). In a fresh repo with no history, or outside a git repo, it injects nothing and never blocks session start.
To turn it off, run sessions uninstall (which also removes the plugin and MCP config). The hook lives in ~/.claude/settings.json under hooks.SessionStart; enabling and disabling preserve any other hooks you have configured.
Codex and Cursor are not yet supported — their session-start hook contracts are still being confirmed. The hook also requires
sessionsto be on yourPATHat session start.
Usage reports
sessions report reads your local Claude Code, Codex, Pi, and OpenCode logs and produces a token/cost usage report. By default it renders a self-contained HTML dashboard and opens it in your browser; JSON is available for piping and --format text prints a summary straight to the terminal.
sessions report # HTML dashboard, opens in your browser
sessions report --format text --today # quick terminal summary
sessions report --out ./report.html # save the dashboard instead of opening it
sessions report --format json --stdout # print JSON to stdout (for piping)
sessions report --days 30 --tool claude # last 30 days, Claude Code only
sessions report --this-month # current month to date
sessions report --month 2026-05 # a specific calendar month
sessions report --here # current project only
Parsed transcripts are cached against their mtime and size, so only what changed is re-read; a bounded period additionally skips files last written before the window. Pass --no-cache to force a full read.
The selected period is shown prominently at the top of both outputs (and in the JSON period).
Report options
| Flag | Description |
|---|---|
--format json|html|both|text |
What to emit. Default html. text prints to stdout and writes no file. |
--out <path> |
Save output instead of opening in the browser. For both, a directory → usage-report.json + report.html; for a single format, a file path. |
--here |
Restrict to the current project. |
--from YYYY-MM-DD / --to YYYY-MM-DD |
I |
No comments yet
Be the first to share your take.