Persistent, searchable cross-session memory for AI coding agents.
Your agent learned something painful at 2 AM. Today it walks into the same wall. gz-sessions fixes that — with a JSONL file and zero ceremony.
Local-first: plain JSONL files on your machine. No server, no database, no account, no telemetry, zero runtime dependencies.
- Why
- Quick Start
- Install
- How Memory Works
- Architecture
- CLI Reference
- JSONL Schema
- Storage Format
- Storage Location
- Wiring into OpenCode
- Programmatic API
- Why Not a Database?
- Comparison
- Feature Highlights
- Development
- Related Projects
- Roadmap
- Contributing
- License
AI coding sessions are amnesiac. Every session:
- re-learns the same project quirks ("oh, the build needs
--compilebefore--outfile"), - re-litigates the same decisions ("should we use JSONL or SQLite?" — decided last week),
- re-hits the same walls (the flaky test, the proxy that eats PUT requests, the peak-hour pricing window).
Context files like AGENTS.md hold static knowledge you wrote by hand. They can't capture what the agent discovered while working — and that's exactly the knowledge you lose when the session dies.
gz-sessions gives agents a tiny, boring, durable place to write things down — and a fast way to recall them at the start of the next session.
Before:
Session 47: "TIL: vitest needs --pool=forks on this repo or workers hang."
Session 48: *hangs* … 20 minutes lost rediscovering why.After:
$ sessions add my-app "vitest needs --pool=forks here or workers hang" \
--type learning --tag vitest --tag ci
$ # next day
$ sessions search my-app "vitest hang"
2026-08-24T09:12:44.102Z [learning] vitest needs --pool=forks here or workers hang #vitest #cinpx @ground-zero-llc/gz-sessions add my-app "deploy script requires NODE_ENV=production" --type fact
npx @ground-zero-llc/gz-sessions search my-app "deploy"That's the whole loop. Everything else is detail.
Run directly (no install step):
npx @ground-zero-llc/gz-sessions --help
bunx @ground-zero-llc/gz-sessions --helpInstall globally:
bun add -g @ground-zero-llc/gz-sessions # or: npm i -g @ground-zero-llc/gz-sessions
sessions --helpUse as a library:
bun add @ground-zero-llc/gz-sessions # or: npm i @ground-zero-llc/gz-sessionsRequires Node ≥ 18 (or Bun ≥ 1.0). No other prerequisites.
The memory loop is intentionally simple:
-
Write — After a non-obvious discovery, persist it:
sessions add my-app "vitest needs --pool=forks here or workers hang" \ --type learning --tag vitest --tag ci -
Recall — At the start of the next session, search for relevant context:
sessions search my-app "vitest hang" -
List — Browse everything for a project:
sessions list my-app
Suggested rhythm: read at session start, write at session end — plus immediately after any non-obvious discovery. The CLI is fast enough (~ms) to call mid-session without friction.
Entries are typed (learning, decision, fact, preference) and tagged for organization. Types are cheap now and gold later — they let you filter by "what did I learn" vs "what did I decide."
┌─────────────────────────────────────────────────┐
│ Agent Session │
│ │
│ ┌───────────┐ ┌───────────┐ ┌─────────┐ │
│ │ recall │ │ capture │ │ browse │ │
│ │ search │ │ add │ │ list │ │
│ └─────┬─────┘ └─────┬─────┘ └────┬────┘ │
│ │ │ │ │
│ └────────┬───────┴───────┬───────┘ │
│ │ │ │
└─────────────────┼───────────────┼───────────────┘
│ │
┌──────▼───────────────▼──────┐
│ gz-sessions CLI │
│ (zero-dep arg parsing) │
└──────────────┬──────────────┘
│
┌────────▼────────┐
│ store.ts │
│ append/search │
│ list/count │
│ sanitize │
└────────┬────────┘
│
┌──────────────▼──────────────┐
│ ~/.gz-sessions/ │
│ ├── my-app.jsonl │
│ ├── another-project.jsonl │
│ └── ... │
└─────────────────────────────┘
Storage layer (store.ts): append-only JSONL I/O, UUIDv4 generation, case-insensitive substring search, newest-first ordering. All functions are async and resolve GZ_SESSIONS_HOME at call time.
CLI layer (cli.ts): zero-dependency argument parsing, human-readable output by default, --json for machine-readable output. No third-party arg parser — just a hand-rolled loop.
sessions add <project> <text> [--type <t>] [--tag <t>]...
sessions search <project> <query> [--limit <n>]
sessions list <project>
sessions count <project>| Command | Arguments | Flags | Behavior |
|---|---|---|---|
add |
<project> <text> |
--type, --tag (repeatable), --json |
Stores one entry; prints the stored record |
search |
<project> <query> |
--limit n (default 20), --json |
Case-insensitive substring match on text + tags; newest first |
list |
<project> |
--json |
All entries, newest first |
count |
<project> |
--json |
Entry count (0 if project unknown) |
Global flags: --json (machine-readable output on any command), -h / --help.
Use them consistently — they're cheap now and gold later:
| Type | Use for |
|---|---|
learning |
Lessons, wrong turns, "never do X again" |
decision |
Chosen approaches and why |
fact |
Environment/project facts (ports, flags, quirks) |
preference |
User conventions (style, tooling, workflow) |
- Project names are sanitized into safe filenames:
My Cool App!→My-Cool-App.jsonl. - Multi-word queries need no extra quoting:
sessions search my-app compile flag. addrejects empty text; tags are trimmed and deduplicated.- Corrupt JSONL lines are skipped on read — one bad line never takes down the file.
- Exit codes:
0success,1usage/runtime error.
One JSON object per line in $GZ_SESSIONS_HOME/<project>.jsonl:
{
"id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"ts": "2026-08-24T09:12:44.102Z",
"type": "learning",
"text": "Bun needs --compile before --outfile",
"tags": ["bun", "build"]
}| Field | Type | Notes |
|---|---|---|
id |
string |
UUIDv4 |
ts |
string |
ISO-8601 timestamp; ordering key (newest first) |
type |
enum | learning | decision | fact | preference |
text |
string |
The memory itself (trimmed, non-empty) |
tags? |
string[] |
Optional, deduplicated |
Each project gets its own append-only JSONL file. This design choice is deliberate:
Why JSONL (not SQLite, not a single JSON blob)?
| Concern | JSONL | SQLite | Single JSON |
|---|---|---|---|
| Append without locking | Yes | Yes (needs WAL) | Must rewrite whole file |
| Human readable | cat or grep |
Needs .sqlite3 CLI |
jq or python |
| Git-friendly diffs | Line-level | Binary blob | Whole-file diff |
| Corrupt line recovery | Skip bad line, rest intact | Needs PRAGMA integrity_check |
Entire file broken |
| Zero dependencies | Node fs built-ins |
better-sqlite3 or WASM |
Node fs built-ins |
The files are safe to cat, grep, diff, back up, version, and sync. Future features (edit/delete, date filters) won't require a migration.
$GZ_SESSIONS_HOME/<project>.jsonl # default: ~/.gz-sessions/Override the root for tests, dotfiles repos, synced folders, or per-machine setups:
GZ_SESSIONS_HOME=~/Dropbox/sessions sessions add my-app "remembered everywhere"Add this block to your project's AGENTS.md (or your global agent rules):
## Cross-session memory
You have persistent memory via the `sessions` CLI.
- At session start, recall relevant context:
`sessions search <project> "<current task topic>" --limit 10`
and `sessions list <project>` if the project is new to you.
- Before ending a session, persist durable knowledge:
`sessions add <project> "<insight>" --type learning --tag <area>`
- Use types consistently: `learning` = lessons/wrong turns,
`decision` = chosen approaches + why, `fact` = environment/project facts,
`preference` = user conventions.
- Never store secrets, tokens, or credentials.Works with any agent harness that can run shell commands — OpenCode, Claude Code, Aider, your own scripts. If it has a terminal, it has memory.
import {
append,
search,
list,
count,
ENTRY_TYPES,
isEntryType,
storageRoot,
sanitizeProject,
type SessionEntry,
type EntryType,
type NewEntry,
} from "@ground-zero-llc/gz-sessions";
// Store a memory
const entry = await append("my-app", {
type: "decision",
text: "Use JSONL over SQLite",
tags: ["storage"],
});
// => SessionEntry (with generated id + ts)
// Search (case-insensitive substring on text + tags)
const hits = await search("my-app", "sqlite", 10); // limit optional
// List all entries (newest first)
const all = await list("my-app");
// Count entries (0 when project unknown)
const n = await count("my-app");
// Utilities
ENTRY_TYPES; // ["learning","decision","fact","preference"]
isEntryType("fact"); // true — type guard
sanitizeProject("My App!"); // "My-App"
storageRoot(); // current GZ_SESSIONS_HOME (resolved)All functions are async and resolve against GZ_SESSIONS_HOME at call time, so tests can redirect storage freely.
type EntryType = "learning" | "decision" | "fact" | "preference";
interface SessionEntry {
id: string; // UUIDv4
ts: string; // ISO-8601
type: EntryType;
text: string;
tags?: string[];
}
type NewEntry = Omit<SessionEntry, "id" | "ts"> &
Partial<Pick<SessionEntry, "id" | "ts">>;Deliberate boring-tech choice:
- Inspectable —
cat ~/.gz-sessions/my-app.jsonlis the whole UI. - Diffable & backupable — it's just files; git/dropdir/rsync all work for free.
- Zero supply chain — Node built-ins only. Nothing to audit, nothing to break, installs in milliseconds.
- Fast enough — substring scan over thousands of short lines is sub-millisecond. If your memory outgrows grep, you have bigger problems than this tool's query planner.
Embeddings/vector DBs are the right tool for fuzzy recall over huge corpora. Agent session memory is small, high-signal, and keyword-shaped. Boring wins.
gz-sessions |
Static context files (AGENTS.md) |
Vector-DB memory services | |
|---|---|---|---|
| Captures dynamic agent learnings | ✅ | ❌ hand-written only | ✅ |
| Local-first, no account/server | ✅ | ✅ | often ❌ |
| Zero runtime dependencies | ✅ | ✅ | ❌ |
| Human-readable storage | ✅ JSONL | ✅ Markdown | ❌ embeddings |
| Works offline | ✅ | ✅ | varies |
| Recall method | keyword substring | none (always in context) | semantic similarity |
These complement each other: static conventions in AGENTS.md, lived experience in gz-sessions.
- Zero runtime dependencies — Node built-ins only.
fs/promises,os,path,crypto. Nothing else. - Append-only storage — No locking, no migrations, no corruption risk from partial writes.
- Corrupt-line tolerant — Bad JSONL lines are silently skipped on read. One stray line never takes down the file.
- Type-safe TypeScript — Strict
tsconfig, full type exports, CI-enforced typecheck. - Cross-platform — Works on Linux, macOS, and Windows (Node or Bun).
- Any agent harness — OpenCode, Claude Code, Aider, custom scripts — if it has a shell, it has memory.
Requires Bun for tests; TypeScript compiles the published CLI.
bun install
bun test # runs test/ against temp GZ_SESSIONS_HOME dirs — never touches real data
bun run build # tsc -> dist/
npx tsc --noEmit # strict typecheck (also enforced by CI)Project layout:
src/store.ts # storage layer: append/search/list/count, schema, sanitization
src/cli.ts # zero-dep arg parsing + output formatting
test/*.test.ts # store + CLI round-trips via bun testCI runs typecheck + tests on every push and PR (Node 20 + Bun).
| Project | What It Does |
|---|---|
| gz-sessions | Persistent cross-session memory for AI agents |
| gz-sessionrecall | AI code archaeology — search your session history |
| gz-codemap | Scan codebases → auto-generate project config |
| gz-modelrouter | Intelligent LLM cost router — save 40-70% on bills |
| gz-gateway | OpenAI-compatible AI gateway — rate limiting, caching, failover, cost tracking |
| gz-agent | Production-grade agent runtime — tool calling, state machines, multi-agent coordination |
| gz-eval | Evaluation framework — golden test sets, quality scoring, A/B comparison |
| gz-guardrails | AI safety middleware — PII detection, prompt injection defense, content moderation |
| gz-bench | Standardized benchmark harness for AI coding agents |
| gz-authmesh | Unified credential mesh for AI providers |
| gz-remote | Drive AI coding agents on remote machines over SSH |
| gz-context-engine | Production-grade RAG context engine |
Shipped in v0.1: append/search/list/count, tags, types, JSON output, GZ_SESSIONS_HOME override.
Candidates (in rough priority order — no promises, no migrations needed thanks to append-only format):
-
forget/ entry deletion by id (tombstone or rewrite) - Date-range and tag-only filters
- Cross-project search
- Near-duplicate detection on
add -
statscommand (entries per type/tag, activity over time)
Have an opinion? Open an issue.
PRs welcome! Keep the constraints in mind: zero runtime dependencies, Node built-ins only, strict TypeScript must pass, tests must pass. See CONTRIBUTING.md.
Need this customized for your infrastructure? We offer:
- Integration consulting — Wire gz-sessions into your agent runtime
- Custom configuration — Task-specific rules, models, and workflows for your team
- Managed deployment — We host and maintain your instance
- Training workshops — Hands-on sessions for your engineering team
Book a 30-min call · See pricing
MIT — Ground Zero LLC
Built by Ground Zero LLC — AI infrastructure for the agentic age.