dsh-plugin-dbgraph: Indexes the database directory locally so the agent knows every table and field before writing queries.

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

  1. Indexes the remote database’s catalog, covering every table, column, comment, primary key, index, and bounded samples of enum literals.
  2. Writes the result to a local SQLite index at ~/.dsh/dbgraph.
  3. The model reads this index, plus a summary directly injected into the system prompt, rather than repeatedly sending SHOW TABLES and DESCRIBE.
  4. 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':

  • js always uses the JavaScript writer and does not touch the addon.
  • rust only 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 (including UNINDEXED ones) returns NULL, only rowid and rank survive. Therefore, the index uses a contentful table, where object_id UNINDEXED can be read back character by character. Additionally, FTS5’s column syntax is column-name [UNINDEXED], so prepending a type name results in a direct parse error.
  • unicode61 treats a segment of Chinese text as a single token: storing 广告点击 as-is means MATCH '广告' finds nothing. Therefore, text stores the pre-tokenized stream—ASCII split by camel/number boundaries, CJK split by bigrams—and the query uses the same tokenizer, connected by OR (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:

  1. The plugin runs with the permissions of the current DSH process; read the source code before mounting to the profile.
  2. The license is MIT, and the repository is currently public.
  3. 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_index yourself and verify the digest in status.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.

羽毛球分组比赛记分
小程序二维码

欢迎使用《羽毛球分组比赛记分》微信小程序

Xiaoye