dsh-plugin-sessiongraph
Full-text search of all local sessions: sessiongraph finds conversations in any workspace, Chinese two-character terms included; sessiongraph_index builds it.
Install
dsh plugin --profile web add dsh-plugin-sessiongraph
Sandbox test results
| Stage | Result |
|---|---|
| 1 install | ✓ |
| 2 list | ✓ |
| 3 mount | ✓ |
| 4 boot | — |
Four stages run in an isolated sandbox: install (network on) → list → mount → boot (last three with no network).
README
dsh-plugin-sessiongraph
English | [中文](README.zh.md)
Every DSH session on this machine, indexed as one searchable corpus — the model looks up "where did I say or do this" in milliseconds, instead of you digging through the sidebar or grepping compressed logs.
Architecture
Six packages behind one strict seam: adding a store or an indexer never touches the model tools.
Package · Role
service · Defines the ctx.sessiongraph seam: query vocabulary, store/indexer registries, schema v1, the Chinese bigram tokenizer
sqlite · Read provider: answers the seven queries from index.db, routing between the native and TypeScript engines
sessions · Write provider: scan session logs, decode frame by frame, extract entries, write the database (TypeScript and Rust engines); raw.ts reads unabridged events back
tool · Consumer: one sessiongraph tool (eight operations), the /sessiongraph command, the system-prompt section, the settings HTTP route, and tool visibility
native · Rust kernel: index writes (indexSessions) and queries (query); returns undefined on any failure, never throws
panel · Browser half only: the settings "会话检索" tab (dsh.client; the host entry is an empty apply)
bundle · Installable meta-package: cordis.patch.yml
Routing is "exactly one claimant", never first match. Every registered store is asked concurrently on each query; zero claimants and several claimants both throw (SESSIONGRAPH_UNAVAILABLE / SESSIONGRAPH_CONFLICT). Registration order and hot reload therefore never change the result. status is the one operation that answers with no store at all — someone who has never built an index needs to learn that fact, not receive the same failure every other operation raises.
Rust kernel
The indexing path is a napi-rs native module (Rayon for parallelism, rusqlite for the database), not WASM: the kernel must read the filesystem directly, write SQLite directly, and move gigabyte-scale data. WASM's boundary copies and absent fs/sqlite are disqualifying.
· Full rebuild · Incremental · Query · Index size
Rust kernel · 6.76 s · 59–107 ms · 0.1–6.9 ms · ~170 MB
TypeScript (fallback) · 19.22 s · 230 ms · same · ~170 MB
Both databases hold identical content, so this is not "a different index": sqlite_master DDL is byte-for-byte equal, every unchanged session compares field by field with zero differences, entry rows / session rows / FTS token multisets hash to the same SHA-256, and the TypeScript store reads a Rust-built database directly.
Engine selection is engine: 'auto' | 'native' | 'typescript', defaulting to auto: use the native kernel when present, fall back silently otherwise — a missing binary is never an error.
How it relates to memory and to the built-in session search
The same question has three homes, and picking the wrong one means finding nothing:
· Memory (OpenViking / MEMORY.md) · Built-in DSH session_search · sessiongraph
Stores · Extracted conclusions: preferences, decisions, entities · Semantic text projected from session events, per event type · Raw entries: message bodies, tool-call names and arguments
Form · Markdown entries under viking:// · SQLite FTS5 in an in-memory database (openAt: first-search, rebuilt every start) · SQLite on disk, maintained incrementally
Scope · Machine-wide · The caller's workspace · Machine-wide, across workspaces
Chinese · Semantic (embeddings), recalls by meaning · No — the unicode61 tokenizer does no CJK handling at all: 我今天在改广告SDK is one token, so 广告 matches nothing · Yes — purpose-built bigram tokenizer, two-character terms match
How you search · By meaning · By token or phrase (English keywords, whole words) · By characters, full text (Chinese and English)
Can it find the exact words · No — only what survived extraction · Partly: returns an excerpt · Yes — the whole thing, including commands and arguments
Good for · "What do I prefer", "what did we decide" · English keywords, exact phrases, recent sessions · "When did I say this", "what was that command"
In one line: memory answers "what is the conclusion", the built-in search answers "which session mentioned it", sessiongraph answers "what was actually said".
Since 0.2.0 the model no longer has to choose: the settings page hides the host's five session_* tools by default and leaves only sessiongraph (see "Settings: which session tools the model sees"). Switch back any time.
Three differences that matter in practice:
- Chinese: the built-in search is effectively unusable for Chinese (its tokenizer has no CJK handling); sessiongraph is built for two-character Chinese terms
- Scope: the built-in search only sees the caller's workspace; sessiongraph spans every workspace
- Persistence: the built-in index lives in memory, opens on first search, and is gone after a restart; sessiongraph's index is on disk, usable immediately, kept fresh incrementally
Memory does not overlap with either: a conversation may never enter memory (short, casual, below any threshold) yet it is always in sessiongraph's raw index. Conversely, a memory entry has usually lost its context, and checking it means going back to the original.
Usage
Three entry points
Entry · How
The model calls it · Just ask "which session discussed the splash screen"; it reaches for sessiongraph
Slash command · /sessiongraph status / index / search <term> / workspaces
Command results flow back (opt-in, off by default) · With injectCommandResults: true the command's result is injected into the conversation as a plugin/notice message, so the model's next turn knows what you looked up — the same mechanism /plan uses. Off by default because commands run on the host's main thread, and calling
…(truncated)
Rendered from the README published on npm. Formatted only, not edited.
Versions
Published on npm::12,最后更新 2026-11-06