Introduction¶
In DSH (DeepSeek Harness, “Everything is a plugin”), connecting an agent to a database is most commonly done by handing over the connection information and letting it probe the structure itself using SHOW TABLES and DESCRIBE. This path comes with two costs: first, every probe of the structure and every guess consumes a round of model turns, where a round takes a few seconds; second, there is no boundary to the structure information, and hundreds of lines of SHOW/DESCRIBE results can easily burn up the context.
dbgraph approaches this differently: the plugin first writes a bounded sampling of the remote database’s catalog—tables, columns, comments, primary keys, indexes, and enum literals—into a local SQLite index. The agent reads this index (plus a summary directly injected into the system prompt) to know the structure, and only makes one network call to actually retrieve data.
Below is an introduction to CC19990113/dsh-plugin-dbgraph.
What is this¶
- Name:
CC19990113/dsh-plugin-dbgraph - Maintainer: CC19990113
- Category: Memory
- GitHub Stars: 1
- License: MIT
- One-liner: Indexes database structures for DeepSeek Harness, allowing agents to know every table and column before writing queries, and then retrieving the answer with a single tool call.
Structure Local, Data Online¶
| Where it goes | Reason | |
|---|---|---|
| Tables, columns, comments, primary keys, indexes, enum sampling, row count estimates | Local index | Zero network, offline, no credentials needed to ask “what does this column mean” |
| Actual row values | Always on the server | Caching a password is not a fast password, it’s a wrong password |
Things that actually come from business data will not be persisted. The only field with data shape is enum_values_json, which is a sample from a single SELECT DISTINCT … LIMIT n used to tell the model whether status stores 'paid' or 1.
Core Features¶
- Indexes the remote database’s catalog, covering every table, column, comment, primary key, index, and bounded samples of enum literals.
- Writes the result to a local SQLite index at
~/.dsh/dbgraph. - The model reads this index, plus a summary directly injected into the system prompt, rather than repeatedly sending
SHOW TABLESandDESCRIBE. - When actually retrieving values, only one network call is made.
status.json and the index are kept together, and the digest inside is read synchronously by the system-prompt section. The reason is practical: every model request concatenates this section; opening SQLite here would be a cost to pay at every step, and the plugin does not accept this cost.
Index Format¶
One SQLite file per connection, replaced entirely on each run (write to a temporary file <index>.<pid>-<nonce>.tmp first, then rename). The nonce is different for every run, so temporary files are not shared between runs. This single decision eliminates problems with incremental diff, deletion, and partial updates.
meta key/value: engine, database, schemaVersion, l1IndexedAt, l2IndexedAt
objects one row per table / view / collection / key pattern
columns ordinal, type, type_class, nullability, default, comment, pk/unique, enum sample
edges fk | index | view_ref | code_ref
terms normalized words + Chinese bigrams + alias expansion, for semantic lookup
search FTS5 (contentless) over object and column names and comments
Two Levels¶
| Level | Content | Location |
|---|---|---|
| L1 | Object and column names, declared types | Inline, within seconds, immediately available |
| L2 | Comments, foreign keys, indexes, row count estimates, enum samples | Background task |
Failure to populate L1 does not discard the structure snapshot: the index that can answer “which columns exist” is already worth the vast majority of the cost.
Tools¶
| Tool | Timeout | What it does |
|---|---|---|
dbgraph |
30 s | connections schema objects columns find sql impact refs |
dbgraph_index |
300 s | Build or refresh a connection (l1 / l2 / full); large runs are handed off to background tasks |
find is the most critical one: it performs offline word resolution to tables and columns, then runs a parameterized SELECT. When encountering ambiguity, it returns ambiguous and provides a sorted list of candidates rather than guessing; when encountering a phrase like “this user” without a predicate, it returns missing_predicate rather than scanning a table.
Sensitive Column Masking¶
Sensitive columns (e.g., password, passwd, pwd, secret, token, salt, id_card) default to returning [REDACTED]. Without this layer of protection, a careless question could send a password into the transcript, and this harness persists the transcript and indexes it. Setting sensitiveColumns: 'show' per connection can turn this off.
Credentials are not written into connections.json: a connection stores a credential reference, while the actual credential does not persist.
Why Rust for the Write Path¶
node:sqlite is a synchronous API. Writing ten thousand columns through it would block the event loop until finished. crates/dbgraph-indexer puts normalization, term generation, and SQLite writing onto the libuv thread pool, so rebuilding the index does not block the host.
The README provides measurements under the same machine, same directory, and fixed clock (500 tables / 10,000 columns):
| Writer | Write Duration | Index File | Max Event Loop Block |
|---|---|---|---|
| JavaScript | 1992 ms | 8.46 MiB | 1992 ms (entire write) |
Rust (AsyncTask) |
218 ms | 8.41 MiB | 33.6 ms |
The two writers cross-validate each other rather than trusting only one: given the same directory and the same fixed now, objects, columns, edges, terms, and search (including rowid) must be line-by-line identical, with only meta.writer allowed to differ. The fixture expectations are generated by running the JavaScript writer, not hand-written, so if a change on one side isn’t mirrored on the other, the build fails.
The JavaScript writer is therefore not an optimization fallback, but a consistency reference and a degradation path (it is used when there is no Rust toolchain, no addon, a call throws an error, or the report cannot be read, and it only degrades once). The known difference is a single point: the JavaScript writer includes dangling edges (pointing to tables not in the snapshot) in report.warnings, while the native writer silently drops them; the stored data is identical, only this one warning differs.
Selection and Persistence Details¶
dbgraph_index accepts writer: 'auto' | 'js' | 'rust':
jsalways uses the JavaScript writer and does not touch the addon.rustonly uses the native writer; missing artifacts or a call throwing an error will fail the entire run, rather than silently producing a slower index.auto(default) uses the native when available; on missing artifacts, rejected calls, or unreadable reports, it logs only once per process, then falls back to JavaScript.
DSH_DBGRAPH_RUST_ADDON explicitly points to a specific artifact: it serves as a test hook for the degradation path and also an escape hatch for deployments where the addon is placed outside of crates/. By default, it points to crates/dbgraph-indexer/artifacts/dbgraph-indexer.<platform>-<arch>.node.
Two empirical conclusions are written into the README, covering encountered pitfalls:
node:sqlite’s contentless FTS5 returns nothing readable: every column (includingUNINDEXEDones) returnsNULL, onlyrowidandranksurvive. Therefore, the index uses a contentful table, whereobject_id UNINDEXEDcan be read back character by character. Additionally, FTS5’s column syntax iscolumn-name [UNINDEXED], so prepending a type name results in a direct parse error.unicode61treats a segment of Chinese text as a single token: storing广告点击as-is meansMATCH '广告'finds nothing. Therefore,textstores the pre-tokenized stream—ASCII split by camel/number boundaries, CJK split by bigrams—and the query uses the same tokenizer, connected byOR(space denotes a phrase, not “any”).
Typical Usage¶
The comparison in the README is very straightforward. Without an index, answering “What is Zhang San’s password?” takes five steps: four model rounds, four network rounds, two guesses, and also inserts 2,000 lines into the transcript. With dbgraph:
dbgraph find {entity:'用户', fields:['密码'], where:{name:'张三'}}
One line to get the result, and the answer will explain which table and column it used.
The command entry point for structural queries is dbgraph, and for building or refreshing, splitting into l1 / l2 / full, with large tasks going to the background, is dbgraph_index.
Installation and Enablement¶
The release form of this project is a bundle: packages/bundle/cordis.patch.yml mounts the various modules as a profile layer, packages/service provides the vocabulary, dialect registry, and index protocol for ctx.dbgraph, and packages/catalog provides a DatabaseDialect for each engine (SQLite and MySQL are implemented; PostgreSQL/MongoDB/Redis are planned).
The README does not provide a standalone one-line install command, nor does it write usage like dsh plugin add github:.... To enable it, you need to mount it as a bundle/patch into your DSH profile. In the repository root’s package.json, dsh.bundle.patch points to ./packages/bundle/cordis.patch.yml, which is the mounting entry point. Please verify the specific enabling method based on the current repository content and do not guess commands based on the plugin name.
Suitable Scenarios and Notes¶
Suitable for DSH agents that need to read database structures: structure exploration is zero network and offline, and value retrieval only sends one call; Chinese column names and Chinese comments rely on pre-tokenization + bigrams for semantic retrieval; sensitive columns are masked by default.
Three notes:
- The plugin runs with the permissions of the current DSH process; read the source code before mounting to the profile.
- The license is MIT, and the repository is currently public.
- The repository is still new (created 2026-11-05, 1 star at time of writing); before large-scale use, it is recommended to run
dbgraph_indexyourself and verify the digest instatus.json, confirming that the index matches the real catalog before releasing.
Closing Thoughts¶
What dbgraph aims to solve is not “the database is slow,” but “model turns are expensive”: extracting the structural consultation from every request, keeping only the necessary value retrieval once. The directory page is at https://www.skillhub.cn/plugins/CC19990113/dsh-plugin-dbgraph, and the code is at https://github.com/CC19990113/dsh-plugin-dbgraph.