Nekyia
The rite for questioning the dead, pointed at the session you lost.
Nekyia (Νέκυια) is the rite in Book XI of the Odyssey. Odysseus digs a pit at the edge of the world and the dead rise to be questioned. He is not there to mourn them: Tiresias knows the way home and Odysseus does not.
- TypeScript
- Bun
- SQLite
- Ink
GitHub (opens in a new tab)README (opens in a new tab)Changelog (opens in a new tab)
What it does
The context you need is usually in a session you already had. You worked the problem out three weeks ago, in a different directory, possibly in a different client, and the only way back to it is to remember enough of it to search for it. So you describe it to a fresh agent instead and pay for the reasoning twice.
Nekyia searches the transcript stores your agent CLIs already keep on disk, ranks what comes back, and launches the client. Where the exact resume command was tested, Nekyia attaches to that session by ID. Where the command was not tested, it starts a fresh session with a handover built from the indexed prompts, the touched files and the branch, and says plainly that it is a handover rather than a recovered state. Each message in it is carried whole and numbered, so a prompt written across several lines reads as one instruction rather than three.
That distinction is the whole design. A tool that claims to resume everything and quietly starts something new is worse than one that does less and tells you which it did.
Quick start
Nekyia needs Bun 1.1 or newer. It is pre-release and not on npm yet, so you install it from the repository. Both names work: nekyia and the shorter nek.
bun install -g github:AraneaDev/Nekyia
nekyia indexThe first run shows what it intends to read and waits for consent before it opens a transcript store or creates an index. --yes skips that prompt once you have looked at the boundary.
The picker
The picker opens on the project you are standing in. Start it from your home directory, from a filesystem root, or from somewhere nothing has been indexed under, and it opens on the whole index instead, because a list scoped to that directory would be empty.
Typing filters the list as you go, and the matching span is lit in every title, so the list answers each keystroke instead of only getting shorter.
tab widens to every directory, and pressing it again narrows to the project of the row you are on, so you can start anywhere and end up somewhere specific. The count beside the search line always names what is being searched, and when that is a single project, its name stands beside it.
A query that matches nothing says what to try rather than leaving you on an empty screen.
ctrl+f cycles the clients your index actually holds, so you do not page past empty filters to reach the one you use. ctrl+p and ctrl+y copy the opening prompt and the command that would run, for when you want either of them somewhere else. A prompt written across several lines is copied whole, not cut off at its first line.
The status line always names how old the index is, green while it is fresh, then yellow, then red once it is old enough that a search can be answered from something the disk no longer says. Once it has gone stale, ctrl+r appears beside the other keys and reindexes: the picker steps aside, the refresh runs where you can watch it, and the picker comes back on the result. Set autoReindexAfterHours in the config to have that happen on its own past a given age, or 0 to refresh on every open. It is unset by default, so nothing reindexes behind your back.
Reading a session before you resume it
ctrl+o opens the session under the cursor and gives it the screen: what you asked and what came back, in the order it was said, and which files it touched. A long reply wraps rather than running off the right edge. The arrow keys scroll a line, page up and page down scroll a screen, and esc closes it again.
The picker lays itself out against the terminal it is drawn in, so a narrow window gets the same interface at a smaller size rather than a broken one.
From the command line
Really run on this machine, abridged with '…'.
What it found:
$ nekyia doctor
clients
agy 99 sessions (built-in)
claude 105 sessions (built-in)
codebuff 56 sessions (built-in) [partial]
codex 6 sessions (built-in)
copilot 0 sessions (built-in)
kilo 1 sessions (built-in)
opencode 2 sessions (built-in)
index
267 sessions, 16 size-capped, 0 partly unreadable, 6 missing from disk
size-capped: codebuff:18843754-fafe-4461-bf1a-6b9b878dc4a8
…Seven clients, one index. Four of them resume by ID; the other three get a briefed session, and doctor is where you find out which is which before it matters. A session that ran into the size cap is counted apart from one it could not read, because raising the cap and re-indexing recovers the first and does nothing for the second.
Searching without the picker:
$ nekyia search reconnect race
$ nekyia search reconnect --json # machine-readable, with the transcript paths
$ nekyia search reconnect --ids # just the ids, one per line, for show and forget
$ nekyia blame src/sse.ts # sessions that touched this exact file
$ nekyia timeline --dir . # every file operation recorded under a directory
$ nekyia last # newest session under this directory
$ nekyia show <uid> # the handover, as Markdown, before you send itA session you picked out by eye still has to be named before you can act on it, which is what --ids is for: it prints the ids alone, so nekyia search "sse reconnect" --ids | head -1 | xargs nekyia show gets you from a search to a handover without going through JSON to recover an identifier.
Search is scoped to the current directory by default. --all widens it, --client <id> narrows to one client, and --file <path> finds the sessions that touched a file. blame reverses that default: it resolves the path from where you are standing and then searches everywhere, newest first. "Touched" means the path appeared in indexed tool input, not that the session changed the file. With --json, every row carries the transcript paths, so an agent can read the original instead of trusting the indexed summary.
What happened to your files
blame answers for one file. timeline answers for a directory: every file operation recorded under it, grouped by the session that did it.
$ nekyia timeline --dir . --since 7d --limit 2
/home/dev/work/api-gateway · 2 sessions · 12 events · git was not consulted
exact order inside a session, end-time order between them
* claude 23m api-gateway the retry budget is shared across tenants, it should be per tenant
2 read src/gateway/retry-budget.ts
3 edit src/gateway/retry-budget.ts
8 read src/gateway/tenant.ts
9 edit src/gateway/tenant.ts
14 read test/retry-budget.test.ts
15 write test/retry-budget.test.ts
o codex 6h api-gateway add structured logging around the upstream timeout path
2 read src/gateway/logging.ts
3 edit src/gateway/logging.tsThe number in front of an operation is the turn it happened on, so every line points back into the session's own history.
Inside a session the order is exact. Between sessions the index only knows end times, so the operations stay grouped per session rather than merged into one stream that would claim more precision than there is. Where the directory is a git repository, a path git does not track is marked untracked, and where git could not be asked at all the header says so outright, because a missing marker would otherwise read as "git tracks this". Recovering what a session did means reading both kinds of path: tracked only means git knows the path, not that it holds the latest edits. A session that ran from somewhere else and named these files by relative path is not found here; blame has the same limit.
One thing to know before pointing it at an older index: file operations are recorded from the next time a session is read in, so sessions that were indexed before this existed list their files with no operations against them, and a plain nekyia index does not fill them in: it only re-reads sessions whose transcript changed, and an old transcript has not. nekyia index --rebuild does. The output names the sessions in that state instead of showing them as though nothing happened.
How it indexes
Indexing runs in two phases. Discovery reads bounded metadata and a stable fingerprint per session; hydration runs only for what changed, and commits metadata and search facets to SQLite atomically. Search is FTS5, weighting titles, your prompts and selected assistant prose differently, then weighing relevance against a recency half-life. Fork chains collapse to one row.
Tool output is not indexed. Command results and file dumps are large and noisy, and nowhere is private material more likely to be sitting. None of that is what you search a session by.
Configuration
The index lives at ~/.local/share/nekyia/index.db and the config at ~/.config/nekyia/config.json, both honouring XDG_DATA_HOME and XDG_CONFIG_HOME.
There is no network service, no API key and no telemetry, and the handover is deterministic, so building one makes no model call. What the index does hold is a copy of text that was already on your disk, and that copy outlives the transcript it came from. Three commands control that:
nekyia forget <uid> # one session, and every facet of it
nekyia prune --missing # sessions whose source files are gone
nekyia exclude '/work/private' # then: nekyia indexAn exclusion covers the directory and everything under it, and resolves a relative path against the directory you ran it in. The next nekyia index then deletes what was already indexed there, including sessions whose transcripts have since been deleted.
exclude and hiddenClients are instructions rather than preferences, and the values they fall back to are the permissive ones. A config file Nekyia cannot read would therefore quietly widen what it holds. That is why indexing refuses outright on a config whose exclusions could not be honoured, before it writes anything, and names the file. Searching still answers, but it says on stderr that the config was not honoured: a typo should not cost you your history.
It does not promise secret redaction or an encrypted index, so read what show, doctor and --json print before you paste any of it somewhere public.
A client it does not know yet can be described locally: user manifests live in ~/.config/nekyia/clients/*.json, and nekyia doctor --sniff inspects likely transcript stores and scaffolds a draft rather than guessing at a path.