sensemaking 0.28.0 → 0.28.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/package.json +1 -2
- package/skills/sense/EXAMPLES.md +0 -99
- package/skills/sense/SKILL.md +0 -145
- package/skills/sense/references/search.md +0 -63
- package/skills/sense/references/sql.md +0 -120
- package/skills/sense/references/stores/duckdb.md +0 -35
- package/skills/sense/references/stores/sqlite.md +0 -66
- package/skills/sense/references/stores/turso.md +0 -36
- package/skills/sense/references/terminology.md +0 -32
- package/skills/sense-bases/EXAMPLES.md +0 -103
- package/skills/sense-bases/SKILL.md +0 -147
- package/skills/sense-setup/EXAMPLES.md +0 -129
- package/skills/sense-setup/SKILL.md +0 -112
- package/skills/sense-setup/references/embeddings.md +0 -51
- package/skills/sense-setup/references/store-benchmarks.md +0 -16
- package/skills/sense-setup/references/stores/duckdb.md +0 -28
- package/skills/sense-setup/references/stores/sqlite.md +0 -21
- package/skills/sense-setup/references/stores/turso.md +0 -25
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
# sense-bases: worked translations
|
|
2
|
-
|
|
3
|
-
Two real `.base` shapes translated end to end. Field names are illustrative; your tree
|
|
4
|
-
defines its own.
|
|
5
|
-
|
|
6
|
-
## A. Filtered, sorted table with formulas
|
|
7
|
-
|
|
8
|
-
The base: notes categorized as Movies, excluding templates, with a "To-watch" view of unseen
|
|
9
|
-
entries sorted by score.
|
|
10
|
-
|
|
11
|
-
```yaml
|
|
12
|
-
filters:
|
|
13
|
-
and:
|
|
14
|
-
- categories.contains(link("Movies"))
|
|
15
|
-
- '!file.name.contains("Template")'
|
|
16
|
-
views:
|
|
17
|
-
- type: table
|
|
18
|
-
name: To-watch
|
|
19
|
-
filters:
|
|
20
|
-
and:
|
|
21
|
-
- last.isEmpty()
|
|
22
|
-
- rating.isEmpty()
|
|
23
|
-
order: [file.name, year, scoreImdb, runtime, director]
|
|
24
|
-
sort:
|
|
25
|
-
- { property: scoreImdb, direction: DESC }
|
|
26
|
-
- { property: file.name, direction: ASC }
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
One view, one query. Base filters AND view filters; `order` is the SELECT list; `sort` the
|
|
30
|
-
ORDER BY. The link comparison is the literal written text (`[[Movies]]`), the name test uses
|
|
31
|
-
`basename` so a folder named Templates does not false-positive:
|
|
32
|
-
|
|
33
|
-
```sql
|
|
34
|
-
SELECT basename(f.path, '.md') AS name, f.year, f.scoreImdb, f.runtime, f.director
|
|
35
|
-
FROM frontmatter f
|
|
36
|
-
WHERE EXISTS (SELECT 1 FROM json_each(f.categories) WHERE value = '[[Movies]]')
|
|
37
|
-
AND instr(basename(f.path), 'Template') = 0
|
|
38
|
-
AND (f.last IS NULL OR f.last IN ('', '[]', '[null]'))
|
|
39
|
-
AND (f.rating IS NULL OR f.rating IN ('', '[]', '[null]'))
|
|
40
|
-
ORDER BY f.scoreImdb DESC, name ASC
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
Saved in `sense.config.json` under `queries` as e.g. `to-watch`, it runs as `sense to-watch`.
|
|
44
|
-
|
|
45
|
-
## B. `this`-relative: the Related pattern
|
|
46
|
-
|
|
47
|
-
The base: for the current note, every other note ranked by shared outgoing links, including
|
|
48
|
-
anything it links or that links to it.
|
|
49
|
-
|
|
50
|
-
```yaml
|
|
51
|
-
filters:
|
|
52
|
-
and:
|
|
53
|
-
- file.path != this.file.path
|
|
54
|
-
formulas:
|
|
55
|
-
Related: list(this.file.links).filter(list(file.links).containsAny(value)).unique()
|
|
56
|
-
LinksOverlap: formula.Related.length
|
|
57
|
-
views:
|
|
58
|
-
- type: table
|
|
59
|
-
name: Related
|
|
60
|
-
filters:
|
|
61
|
-
or:
|
|
62
|
-
- formula.LinksOverlap > 2
|
|
63
|
-
- file.hasLink(this)
|
|
64
|
-
- this.file.hasLink(file)
|
|
65
|
-
sort:
|
|
66
|
-
- { property: formula.LinksOverlap, direction: DESC }
|
|
67
|
-
limit: 20
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
`this` becomes one bound path. The chained list formula (`filter(...containsAny).unique()`)
|
|
71
|
-
is set intersection, which SQL states as an IN-filtered count; the `Related` list itself is
|
|
72
|
-
the same rows through `GROUP_CONCAT`:
|
|
73
|
-
|
|
74
|
-
```sql
|
|
75
|
-
WITH me AS (SELECT ? AS p),
|
|
76
|
-
mine AS (SELECT DISTINCT dst FROM links, me WHERE src = me.p AND dst IS NOT NULL),
|
|
77
|
-
scored AS (
|
|
78
|
-
SELECT f.path,
|
|
79
|
-
(SELECT COUNT(DISTINCT l.dst) FROM links l
|
|
80
|
-
WHERE l.src = f.path AND l.dst IN (SELECT dst FROM mine)) AS overlap,
|
|
81
|
-
EXISTS (SELECT 1 FROM links, me WHERE src = f.path AND dst = me.p) AS links_to_me,
|
|
82
|
-
EXISTS (SELECT 1 FROM links, me WHERE src = me.p AND dst = f.path) AS linked_by_me,
|
|
83
|
-
(SELECT GROUP_CONCAT(DISTINCT l.dst) FROM links l
|
|
84
|
-
WHERE l.src = f.path AND l.dst IN (SELECT dst FROM mine)) AS shared_links
|
|
85
|
-
FROM frontmatter f, me WHERE f.path != me.p)
|
|
86
|
-
SELECT path, overlap, shared_links FROM scored
|
|
87
|
-
WHERE overlap > 2 OR links_to_me OR linked_by_me
|
|
88
|
-
ORDER BY overlap DESC
|
|
89
|
-
LIMIT 20
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
Run as `sense sql "..." current-note.md`, or saved with the `?` in place and the path passed
|
|
93
|
-
as the parameter. The `or:` block reproduces Obsidian's semantics exactly: rows qualify by
|
|
94
|
-
overlap, by linking to the note, or by being linked from it, so leaf notes the current note
|
|
95
|
-
links to appear even at zero overlap, as they do in Obsidian.
|
|
96
|
-
|
|
97
|
-
## Notes that recur across translations
|
|
98
|
-
|
|
99
|
-
- Multiple views over the same base share the base-level filter; write it once per query
|
|
100
|
-
rather than factoring it out. Saved queries are self-contained by design.
|
|
101
|
-
- `sort` keys referencing formulas sort by the SELECT alias.
|
|
102
|
-
- A `groupBy` view keeps its rows; see the window-function shape in SKILL.md. Only a
|
|
103
|
-
deliberately collapsed report wants `GROUP BY`.
|
|
@@ -1,147 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: sense-bases
|
|
3
|
-
description: "Translate an Obsidian Bases .base file into sense SQL that returns the same rows. Use when asked to convert, import, or reproduce a .base file (or a Bases view, filter, or formula) with sense, or when a markdown tree contains .base files whose views someone wants queryable outside Obsidian."
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# sense-bases
|
|
7
|
-
|
|
8
|
-
A `.base` file is YAML: filters selecting notes, formulas computing values, and views ordering
|
|
9
|
-
and grouping them. Obsidian evaluates it against its own metadata cache; sense holds the same
|
|
10
|
-
data in SQL tables. Every Bases construct that selects or computes rows has a SQL equivalent.
|
|
11
|
-
What has none is presentation (`columnSize`, card layout), those change pixels, not rows, so
|
|
12
|
-
a translation loses nothing by ignoring them.
|
|
13
|
-
|
|
14
|
-
Translate one view to one query: base-level `filters` AND the view's `filters`, the view's
|
|
15
|
-
`order`/`sort`/`limit` as the SELECT list, ORDER BY, and LIMIT. A base with four views becomes
|
|
16
|
-
four queries; saving them under the base's name in `sense.config.json`'s `queries` block keeps
|
|
17
|
-
them runnable as `sense <name>`. The general query surface is the `sense` skill; this one only
|
|
18
|
-
maps Bases constructs onto it.
|
|
19
|
-
|
|
20
|
-
## Properties
|
|
21
|
-
|
|
22
|
-
| Bases | sense | notes |
|
|
23
|
-
|---|---|---|
|
|
24
|
-
| `note.field` / bare `field` | `f.<field>` | frontmatter column, same name |
|
|
25
|
-
| `file.name` | `basename(f.path, '.md')` | the Name column: no directory, no extension |
|
|
26
|
-
| a name with extension | `basename(f.path)` | the suffix arg is Unix basename's: present, stripped |
|
|
27
|
-
| `file.folder` | `f.path LIKE 'Folder/%'` | for `inFolder("Folder")` |
|
|
28
|
-
| `file.ext` | `'md'` | sense indexes only markdown |
|
|
29
|
-
| `file.size` | `f._size` | bytes |
|
|
30
|
-
| `file.mtime` | `datetime(f._mtime / 1000, 'unixepoch')` | stored as ms |
|
|
31
|
-
| `file.ctime` | `datetime(f._ctime / 1000, 'unixepoch')` | see the caveat below |
|
|
32
|
-
| `file.tags` | the `tags` table | frontmatter + inline `#tags`, deduplicated, Obsidian's grain |
|
|
33
|
-
| `file.links` | `links WHERE embed = 0` | one row per distinct written target |
|
|
34
|
-
| `file.embeds` | `links WHERE embed = 1` | |
|
|
35
|
-
| `file.backlinks` | `links WHERE dst = f.path` | |
|
|
36
|
-
| `this` | a bound `?` | see below |
|
|
37
|
-
|
|
38
|
-
`file.ctime` is the filesystem's creation time. A `git clone` or a copy resets it, so on a
|
|
39
|
-
tree that arrived as a checkout, every ctime is the checkout date and age formulas are wrong
|
|
40
|
-
about authorship without being wrong about the filesystem. `map` warns when modification
|
|
41
|
-
times have this shape; creation times share it.
|
|
42
|
-
|
|
43
|
-
## Predicates
|
|
44
|
-
|
|
45
|
-
| Bases | sense |
|
|
46
|
-
|---|---|
|
|
47
|
-
| `==` `!=` `>` `<` `>=` `<=` | same |
|
|
48
|
-
| `&&` `\|\|` `!` | `AND` `OR` `NOT` |
|
|
49
|
-
| `and:` / `or:` / `not:` filter blocks | parenthesized `AND` / `OR` / `NOT` |
|
|
50
|
-
| `field.isEmpty()` | `(f.field IS NULL OR f.field IN ('', '[]', '[null]'))` |
|
|
51
|
-
| `file.hasTag("book")` | `EXISTS (SELECT 1 FROM tags WHERE tags.path = f.path AND (tag = 'book' OR tag LIKE 'book/%'))` |
|
|
52
|
-
| `file.hasLink("Note")` | `EXISTS (SELECT 1 FROM links WHERE src = f.path AND dst = 'Note.md')` |
|
|
53
|
-
| `list.contains(x)` | `EXISTS (SELECT 1 FROM json_each(f.list) WHERE value = x)` |
|
|
54
|
-
| `string.contains(x)` | `instr(f.string, x) > 0` |
|
|
55
|
-
| `list.containsAny(...)` | the `json_each` EXISTS with `value IN (...)` |
|
|
56
|
-
| `/regex/.matches(x)` | no SQLite regex; `LIKE`/`GLOB` cover anchored and wildcard shapes |
|
|
57
|
-
| `value.isType("object")` | `json_each`'s own `type` column: `... FROM json_each(f.field) j WHERE j.type = 'object'` |
|
|
58
|
-
|
|
59
|
-
A field no note in the tree declares has no column at all, so a filter naming it errors with
|
|
60
|
-
`no such column` instead of treating every row as empty. Obsidian's evaluator returns empty
|
|
61
|
-
for unknown properties; SQL does not. Dropping the clause states the same thing the error
|
|
62
|
-
did, and `SELECT name FROM pragma_table_info('frontmatter')` lists what exists.
|
|
63
|
-
|
|
64
|
-
`isEmpty()` has four true cases because empty is stored three ways: NULL (key absent), `''`
|
|
65
|
-
(empty string value), `'[]'` (a list written `[]`), and `'[null]'` (a list key above a bare
|
|
66
|
-
`-`). Obsidian's `isEmpty()` is true for all of them; `IS NULL` alone finds only the first.
|
|
67
|
-
The IN list is the whole test. `json_array_length()` is not: it reads `'[null]'` as length
|
|
68
|
-
1 and throws on plain strings. The same trap inside a list: `json_each` hands a string member
|
|
69
|
-
to `value` as plain text, so `json_type(value)` throws `malformed JSON` on it; the scan's own
|
|
70
|
-
`type` column is the discriminator.
|
|
71
|
-
|
|
72
|
-
`contains(link("Movies"))` compares against a link value. In frontmatter, a list of links
|
|
73
|
-
holds the written text, so the `json_each` comparison value is the literal `[[Movies]]`.
|
|
74
|
-
`hasLink` compares resolved paths: `links.dst` is the resolved target (`NULL` for dead
|
|
75
|
-
links), so the comparison value is the target's path, not its display name.
|
|
76
|
-
|
|
77
|
-
sense's own `has(field, x)` is looser than both `contains` variants: exact membership on JSON
|
|
78
|
-
arrays but substring on strings, so `has(f.status, 'active')` also matches `inactive`. It
|
|
79
|
-
reads shorter when the field is known to be a list; the `json_each` form is the exact
|
|
80
|
-
translation.
|
|
81
|
-
|
|
82
|
-
## Formulas
|
|
83
|
-
|
|
84
|
-
Formulas are SELECT expressions. The Bases functions map onto SQLite's:
|
|
85
|
-
|
|
86
|
-
| Bases | sense |
|
|
87
|
-
|---|---|
|
|
88
|
-
| `if(c, a, b)` | `iif(c, a, b)` or `CASE WHEN` |
|
|
89
|
-
| `now()` / `today()` | `datetime('now')` / `date('now')` |
|
|
90
|
-
| `date(x)` | `datetime(x)` |
|
|
91
|
-
| `(date1 - date2).days` | `julianday(date1) - julianday(date2)` |
|
|
92
|
-
| `(now() - acquired).months` | `(julianday('now') - julianday(f.acquired)) / 30.44` |
|
|
93
|
-
| `x.round(n)` / `x.toFixed(n)` | `round(x, n)` / `printf('%.2f', x)` |
|
|
94
|
-
| `d.format("YYYY-MM-DD")` | `strftime('%Y-%m-%d', d)` |
|
|
95
|
-
| `x.toString()` | `CAST(x AS TEXT)` |
|
|
96
|
-
| `list.length` | `json_array_length(f.list)` on a JSON column, a COUNT subquery on a table |
|
|
97
|
-
| `list(a).filter(list(b).containsAny(value)).unique()` | an EXISTS-joined subquery; worked example in EXAMPLES.md |
|
|
98
|
-
|
|
99
|
-
Bases durations are typed; SQL date arithmetic is julianday day-fractions. A formula chaining
|
|
100
|
-
duration fields (`.days.round()`) flattens to arithmetic on the julianday difference.
|
|
101
|
-
|
|
102
|
-
A formula referencing another formula becomes a CTE layer: SQL cannot read a SELECT alias in
|
|
103
|
-
the same SELECT list, so each dependency level computes its formulas as columns and the next
|
|
104
|
-
level reads them (`WITH t AS (SELECT ..., <level-1 formulas> FROM frontmatter) SELECT ...,
|
|
105
|
-
<level-2 formulas> FROM t`). A five-formula chain is however many *levels* it has, not five
|
|
106
|
-
CTEs. Formulas that only read base columns share one layer.
|
|
107
|
-
|
|
108
|
-
## Views
|
|
109
|
-
|
|
110
|
-
- `sort:` (multi-key, each with direction) -> `ORDER BY a DESC, b ASC`. `limit:` -> `LIMIT`.
|
|
111
|
-
- `groupBy` does not collapse rows. Obsidian shows every row bucketed under headers with
|
|
112
|
-
per-group summaries. The SQL producing the same rows and numbers is ordering plus window
|
|
113
|
-
functions, not GROUP BY:
|
|
114
|
-
|
|
115
|
-
```sql
|
|
116
|
-
SELECT f.path, f.status, f.days,
|
|
117
|
-
AVG(f.days) OVER (PARTITION BY f.status) AS group_avg,
|
|
118
|
-
COUNT(*) OVER (PARTITION BY f.status) AS group_n
|
|
119
|
-
FROM frontmatter f WHERE f.days IS NOT NULL
|
|
120
|
-
ORDER BY f.status, f.days DESC
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
A collapsed one-row-per-group report is plain `GROUP BY`. That is a different result than
|
|
124
|
-
the Bases view shows.
|
|
125
|
-
- View `summaries` (Sum, Average, Median via ordering, Unique, Filled, Checked) are the
|
|
126
|
-
matching aggregates, windowed as above to keep the rows, or a separate aggregate query.
|
|
127
|
-
|
|
128
|
-
## `this`
|
|
129
|
-
|
|
130
|
-
`this` is the note the base is evaluated against: the embedding note, or Obsidian's active
|
|
131
|
-
pane. sense has no pane, so the caller supplies the path as a bound parameter: `sense sql
|
|
132
|
-
"..." <path>`, or a saved query run as `sense <name> <path>`. A CTE keeps it single-bind:
|
|
133
|
-
|
|
134
|
-
```sql
|
|
135
|
-
WITH me AS (SELECT ? AS p)
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
and every `this.file.*` expression joins `me`. The full Related.base translation in
|
|
139
|
-
EXAMPLES.md is the worked case: link overlap, mutual-link filters, and the shared-target list
|
|
140
|
-
via `GROUP_CONCAT`, all from one bound path.
|
|
141
|
-
|
|
142
|
-
## Not translatable, and why it costs nothing
|
|
143
|
-
|
|
144
|
-
- `columnSize`, card/table/list/map chrome: pixel layout over the same rows.
|
|
145
|
-
- Obsidian choosing `this` from the active pane: there is no pane; the path parameter is the
|
|
146
|
-
resolution.
|
|
147
|
-
- Live re-evaluation as files change: default CLI queries incrementally prepare the needed capabilities; `--no-build` reads the last completed generation and requires its requested readiness without scanning live files. Nothing re-renders on its own.
|
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
# sense-setup: worked configurations
|
|
2
|
-
|
|
3
|
-
Four tree shapes, each with its config and the commands an agent actually runs. Field names and folder names are illustrative; your tree defines its own.
|
|
4
|
-
|
|
5
|
-
## A. Compiled wiki over immutable sources (the llm-wiki pattern)
|
|
6
|
-
|
|
7
|
-
`raw/` holds ingested sources: big, never hand-edited. `wiki/` holds agent-compiled pages: linked, curated. The human drops sources and asks questions; the agent compiles and cites.
|
|
8
|
-
|
|
9
|
-
```json
|
|
10
|
-
{
|
|
11
|
-
"version": 5,
|
|
12
|
-
"presets": {
|
|
13
|
-
"default": { "include": ["wiki/**/*.md"], "k": 10 },
|
|
14
|
-
"raw": { "include": ["raw/**/*.md"], "k": 5 }
|
|
15
|
-
},
|
|
16
|
-
"embed": { "model": "minishlab/potion-retrieval-32M", "provider": "static" },
|
|
17
|
-
"queries": {
|
|
18
|
-
"uncompiled": { "sql": "SELECT path, _mtime FROM frontmatter WHERE path LIKE 'raw/%' AND path NOT IN (SELECT dst FROM links WHERE dst IS NOT NULL) ORDER BY _mtime DESC" },
|
|
19
|
-
"stubs": { "sql": "SELECT path, _size FROM frontmatter WHERE path LIKE 'wiki/%' AND _size < 500 ORDER BY _size" },
|
|
20
|
-
"dead-links": { "sql": "SELECT src, target FROM links WHERE dst IS NULL ORDER BY src" }
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
```
|
|
26
|
-
sense uncompiled # compile queue: raw files nothing cites yet
|
|
27
|
-
sense search "how does attention scale" # wiki only (default preset)
|
|
28
|
-
sense search "rotary embeddings" --preset raw # cite from sources, k=5
|
|
29
|
-
sense dead-links # rows are broken citations to fix
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
What the shape buys: bare search never ranks raw noise above compiled pages; the compile queue, stub list, and citation integrity are one saved query each. The maintenance loop is `uncompiled` → write the wiki page citing its sources → `dead-links` stays empty.
|
|
33
|
-
|
|
34
|
-
## B. Nightly agent memory, consolidated (the dreaming pattern)
|
|
35
|
-
|
|
36
|
-
`memory/` accumulates small notes written at session end, each with `project`, `created`, and `kind` (observation / steer / decision) frontmatter. A consolidation agent runs periodically: prune, merge, surface contradictions for the human. Retired notes move to `archive/`: still queryable under their own preset, out of the default scope.
|
|
37
|
-
|
|
38
|
-
```json
|
|
39
|
-
{
|
|
40
|
-
"version": 5,
|
|
41
|
-
"presets": {
|
|
42
|
-
"default": { "include": ["memory/**/*.md"], "k": 10 },
|
|
43
|
-
"archive": { "include": ["archive/**/*.md"], "k": 10 }
|
|
44
|
-
},
|
|
45
|
-
"embed": { "model": "minishlab/potion-retrieval-32M", "provider": "static" },
|
|
46
|
-
"queries": {
|
|
47
|
-
"project": { "sql": "SELECT path, kind, created, title FROM frontmatter WHERE project = ? ORDER BY created DESC" },
|
|
48
|
-
"steers": { "sql": "SELECT path, created, title FROM frontmatter WHERE kind = 'steer' AND project = ? ORDER BY created" },
|
|
49
|
-
"retirement": { "sql": "SELECT path, project, created FROM frontmatter WHERE datetime(created) < datetime('now','-90 day') AND path NOT IN (SELECT dst FROM links WHERE dst IS NOT NULL)" },
|
|
50
|
-
"today": { "sql": "SELECT path, title FROM frontmatter WHERE date(created) = date('now','localtime') ORDER BY created" },
|
|
51
|
-
"unfiled": { "sql": "SELECT path FROM frontmatter WHERE project IS NULL" }
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
```
|
|
57
|
-
sense project acme-app # one project's notes, newest first
|
|
58
|
-
sense today # `now` is UTC: a query whose boundary is a day needs 'localtime'
|
|
59
|
-
# `retirement` above is a 90-day window, so it does not
|
|
60
|
-
sense search "prefers terse commit messages" --k 5
|
|
61
|
-
→ memory/acme-app/2026-08-02-commits.md via: match
|
|
62
|
-
→ memory/acme-app/2026-06-11-style.md via: vector similarity: 0.71 # near-duplicate → merge candidate
|
|
63
|
-
sense steers acme-app # oldest first: does a new steer override an old one?
|
|
64
|
-
sense retirement # old + uncited → move to archive/
|
|
65
|
-
sense unfiled # rows are notes missing a project. File them
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
Presets are structural (live vs archived); per-project filtering is metadata (`project = ?`). One tree serves every project. Semantic search over the memory preset is the near-duplicate detector: search a new note's own summary and read `similarity` within the results. Whether an old steer was overridden is a question for the human, found by search, never decided by it.
|
|
69
|
-
|
|
70
|
-
## C. Evidence corpus: claims trace to sources
|
|
71
|
-
|
|
72
|
-
`sources/` (immutable imports), `notes/` (one reading note per source), `reviews/` (synthesis whose claims must cite notes). The same shape fits incident reports and postmortems, user research and findings, due diligence and memos.
|
|
73
|
-
|
|
74
|
-
```json
|
|
75
|
-
{
|
|
76
|
-
"version": 5,
|
|
77
|
-
"presets": {
|
|
78
|
-
"default": { "include": ["reviews/**/*.md", "notes/**/*.md"], "k": 10 },
|
|
79
|
-
"source": { "include": ["sources/**/*.md"], "k": 5 }
|
|
80
|
-
},
|
|
81
|
-
"embed": { "model": "minishlab/potion-retrieval-32M", "provider": "static" },
|
|
82
|
-
"queries": {
|
|
83
|
-
"unsupported": { "sql": "SELECT path, title FROM frontmatter WHERE path LIKE 'reviews/%' AND path NOT IN (SELECT src FROM links WHERE dst LIKE 'notes/%')" },
|
|
84
|
-
"unread": { "sql": "SELECT path FROM frontmatter WHERE path LIKE 'sources/%' AND path NOT IN (SELECT dst FROM links WHERE dst IS NOT NULL)" },
|
|
85
|
-
"by-topic": { "sql": "SELECT path, title FROM frontmatter WHERE path NOT LIKE 'sources/%' AND has(topics, ?) ORDER BY path" }
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
```
|
|
91
|
-
sense search "replication failures in priming studies" # reviews + notes; sources never dilute
|
|
92
|
-
sense search "the claim's exact phrasing" --preset source # citation pull on demand
|
|
93
|
-
sense unsupported # rows are synthesis claims with no note behind them
|
|
94
|
-
sense unread # the reading queue: sources no note cites
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
## D. A plain vault: zero configuration
|
|
98
|
-
|
|
99
|
-
Someone else's Obsidian vault, heterogeneous with no structure worth declaring. The `sense init` starter is left untouched. The workflow is discovery:
|
|
100
|
-
|
|
101
|
-
```
|
|
102
|
-
sense map # fields in use, hub notes, recent changes
|
|
103
|
-
sense search "dataview queries" # words + links + meaning, one ranked list
|
|
104
|
-
sense sql "SELECT j.value AS tag, COUNT(*) n FROM frontmatter, json_each(frontmatter.tags) j GROUP BY j.value ORDER BY n DESC LIMIT 20"
|
|
105
|
-
sense peek "Plugins/dataview.md" # outline + links before reading
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
Presets earn their place only when a tree has parts deserving different treatment; a tree that is one kind of thing needs none of the vocabulary above. On a big vault, raise `default`'s `k` and read `lines` ranges instead of whole files, or start from the starter's `large` preset.
|
|
109
|
-
|
|
110
|
-
## E. One vault, separate consumers
|
|
111
|
-
|
|
112
|
-
Keep each consumer's configuration and cache under its own configuration directory while indexing one shared vault. `root` is relative to this config file, so saved SQL continues to use stable vault-relative paths.
|
|
113
|
-
|
|
114
|
-
`~/.config/obsidian/tasks/sense.config.json`:
|
|
115
|
-
|
|
116
|
-
```json
|
|
117
|
-
{
|
|
118
|
-
"version": 5,
|
|
119
|
-
"root": "../../../.obsidian",
|
|
120
|
-
"presets": {
|
|
121
|
-
"default": { "include": ["vault/tasks/**/*.md"], "k": 20 }
|
|
122
|
-
},
|
|
123
|
-
"queries": {
|
|
124
|
-
"active": { "sql": "SELECT path, title FROM frontmatter WHERE path LIKE 'vault/tasks/%' AND status = 'active' ORDER BY path" }
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
The database is `~/.config/obsidian/tasks/.sense/cache.db`; the indexed row remains `vault/tasks/...`, never a path back out through the configuration directory. A `general/` sibling can point at the same root with different presets and a separate `.sense/` cache.
|
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: sense-setup
|
|
3
|
-
description: "Set up the sense CLI on a markdown tree and make the decisions that shape its index: run sense init, choose sqlite, duckdb, or turso, define presets and search signals, configure embeddings, and design queryable note conventions. Use when creating or restructuring a markdown knowledge base, editing sense.config.json, choosing a store or embedding model, or deciding how agents should write notes for later retrieval."
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# sense setup and tree design
|
|
7
|
-
|
|
8
|
-
Use this skill to create or change a sense configuration. Querying an existing tree belongs to the `sense` skill. Worked configurations for common tree shapes are in [EXAMPLES.md](EXAMPLES.md).
|
|
9
|
-
|
|
10
|
-
## Set up the tree
|
|
11
|
-
|
|
12
|
-
Install the CLI and initialize it at the markdown root:
|
|
13
|
-
|
|
14
|
-
```sh
|
|
15
|
-
npm install -g sensemaking
|
|
16
|
-
cd path/to/notes
|
|
17
|
-
sense init
|
|
18
|
-
sense status
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
`sense init` writes `sense.config.json`. Config discovery walks upward from the current directory. `--config <path>` selects another file.
|
|
22
|
-
|
|
23
|
-
By default, the config indexes its own directory. A top-level `root` can point at another markdown tree. Relative roots resolve from the config directory, and `.sense/` remains beside the config. Use this when separate consumers need their own presets, saved queries, or caches over one tree.
|
|
24
|
-
|
|
25
|
-
Globs and indexed paths are relative to the selected root. Run `sense status` and `sense map` after changing the config to confirm preset coverage and the selected store. A change that affects indexed content rebuilds the relevant cache and reports the reason. A build-enabled command replaces an incompatible derived cache format and repairs incomplete feature state; config `"build": false` or `--no-build` reports the required build without rebuilding or repairing the Sense index.
|
|
26
|
-
|
|
27
|
-
## Choose the store
|
|
28
|
-
|
|
29
|
-
Choose from the intended use of the cache. Start with `sqlite` when no surrounding workflow favors
|
|
30
|
-
another engine, then consider interoperability, SQL, connection behavior, and representative Sense
|
|
31
|
-
measurements. A current timing result does not define an engine's long-term suitability.
|
|
32
|
-
|
|
33
|
-
| Store | Choose it when | Main trade-off |
|
|
34
|
-
|---|---|---|
|
|
35
|
-
| `sqlite` | The tree needs the smallest setup, advanced FTS5 search syntax, or concurrent Sense commands | Included with Node; raw SQL is SQLite |
|
|
36
|
-
| `duckdb` | The cache belongs in DuckDB analytical work over large datasets or in a local/cloud workflow, or needs DuckDB types and SQL | Experimental Sense adapter, large native install, one open connection at a time |
|
|
37
|
-
| `turso` | The tree benefits from the embedded Rust engine or Tantivy index, or is evaluating the engine as its adapter evolves | Experimental Sense adapter, restricted search grammar, one open connection at a time |
|
|
38
|
-
|
|
39
|
-
Read the matching selection guide before recommending or configuring a non-default store:
|
|
40
|
-
|
|
41
|
-
- [SQLite selection guide](references/stores/sqlite.md)
|
|
42
|
-
- [DuckDB selection guide](references/stores/duckdb.md)
|
|
43
|
-
- [Turso selection guide](references/stores/turso.md)
|
|
44
|
-
|
|
45
|
-
Read the [current store benchmark summary](references/store-benchmarks.md) only when performance could change the choice. The release assessment generates that shipped summary from the latest accepted all-store run.
|
|
46
|
-
|
|
47
|
-
Each store uses a separate cache file. Changing `store` rebuilds the index instead of migrating the old cache. Commands and public tables are shared, while raw SQL and advanced word-search syntax follow the selected engine.
|
|
48
|
-
|
|
49
|
-
## Design presets
|
|
50
|
-
|
|
51
|
-
A preset is a named, self-contained scope. The required `default` preset serves bare commands. Other presets are selected by name.
|
|
52
|
-
|
|
53
|
-
| Field | Meaning | Default |
|
|
54
|
-
|---|---|---|
|
|
55
|
-
| `include` and `exclude` | Files covered by the preset | `include` is required |
|
|
56
|
-
| `k` | Search result count | 10 |
|
|
57
|
-
| `signals` | Enabled search signals and their reciprocal-rank weights | Every available signal at weight 1 |
|
|
58
|
-
| `where` | Standing frontmatter filter | None |
|
|
59
|
-
|
|
60
|
-
A file is indexed when any preset includes it. Presets can overlap. Files outside every preset do not enter the index.
|
|
61
|
-
|
|
62
|
-
Use paths for stable layers such as `raw/`, `notes/`, and `archive/`. Use frontmatter filters for changing state such as `status`, `project`, and dates. A preset controls indexing, so its coverage must be computable from paths before frontmatter queries run.
|
|
63
|
-
|
|
64
|
-
Use separate presets when parts of the tree need different search signals or result counts. A single-purpose tree does not need extra preset vocabulary.
|
|
65
|
-
|
|
66
|
-
## Configure vectors
|
|
67
|
-
|
|
68
|
-
Vectors require two choices. The top-level `embed` block names the model and provider. Each preset's `signals` decides whether that scope uses vectors.
|
|
69
|
-
|
|
70
|
-
```json
|
|
71
|
-
{
|
|
72
|
-
"embed": {
|
|
73
|
-
"model": "minishlab/potion-retrieval-32M",
|
|
74
|
-
"provider": "static"
|
|
75
|
-
},
|
|
76
|
-
"presets": {
|
|
77
|
-
"default": {
|
|
78
|
-
"include": ["notes/**/*.md"],
|
|
79
|
-
"signals": { "words": 1, "links": 1, "vectors": 1 }
|
|
80
|
-
},
|
|
81
|
-
"raw": {
|
|
82
|
-
"include": ["raw/**/*.md"],
|
|
83
|
-
"signals": { "words": 1, "links": 1 }
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
The config's `"build": true` default lets CLI queries prepare their needed capabilities. Set `"build": false` for manual-build or watch workflows. The first build-enabled vector search downloads a named static model and embeds its eligible scope. `sense build` and `sense watch` prepare all configured capabilities regardless of this setting; `sense download` fetches the model earlier when CI, offline work, or timing makes that useful. A config change that alters the provider, model, effective endpoint, vector coverage, or chunking can invalidate or rebuild vectors. Config `"build": false` or `--no-build` requires the requested vector scope to be ready and does not read newer live files.
|
|
90
|
-
|
|
91
|
-
Read [embedding setup](references/embeddings.md) when choosing a provider or model, supporting a non-English tree, changing chunk size, or tuning signal weights.
|
|
92
|
-
|
|
93
|
-
## Design queryable notes
|
|
94
|
-
|
|
95
|
-
Sense accepts heterogeneous markdown. These choices decide which queries will be reliable:
|
|
96
|
-
|
|
97
|
-
- Consistent frontmatter fields make exact filters and saved reports possible. Inspect actual coverage and types with `sense map`.
|
|
98
|
-
- ISO 8601 dates can be compared by the selected store's date functions. Mixed date formats can still be stored, but do not make a dependable time range.
|
|
99
|
-
- A one-line `summary` appears in results and receives more lexical weight than body text. Its cost is keeping it current.
|
|
100
|
-
- Folders are the natural unit for preset coverage. Frontmatter is the natural unit for status and ownership.
|
|
101
|
-
- Small notes give precise hits and cheap whole-file reads. Large notes still work because `peek`, `sections`, and search `lines` point at ranges, and vector chunking splits long sections.
|
|
102
|
-
- Save recurring questions under `queries`. Use `{ "sql": "..." }` for deterministic filters and reports, and `{ "search": "..." }` for ranked retrieval.
|
|
103
|
-
|
|
104
|
-
Field names in examples are illustrative. The tree defines its own schema. Reserved frontmatter keys are `path`, `_mtime`, `_ctime`, `_size`, `_rank`, `_parse_error`, `content`, `links`, and `sections`; sense drops them with a warning.
|
|
105
|
-
|
|
106
|
-
## Validate the result
|
|
107
|
-
|
|
108
|
-
Run `sense status` to confirm the store, cache, document count, embedding state, watcher state, and preset coverage. Run `sense map` to confirm the discovered fields and their observed types.
|
|
109
|
-
|
|
110
|
-
Run every saved query after editing it. A parameterized SQL query can use any value because preparing the statement validates its columns and syntax before the parameter changes the result.
|
|
111
|
-
|
|
112
|
-
Record choices that should outlive the setup conversation in the tree's maintained agent guidance or in an authored note. The sense config should contain executable settings and reusable queries, not prose policy.
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
# Embedding setup
|
|
2
|
-
|
|
3
|
-
Read this guide when choosing an embedding provider or model, supporting a non-English tree, changing chunk size, or tuning signal weights.
|
|
4
|
-
|
|
5
|
-
## Provider choice
|
|
6
|
-
|
|
7
|
-
`static` loads a local Model2Vec model in JavaScript. The default model is `minishlab/potion-retrieval-32M`. A Hugging Face model id downloads into `~/.sense/models` on first vector use. A filesystem path points at a directory that already contains `model.safetensors` and `tokenizer.json`.
|
|
8
|
-
|
|
9
|
-
`openai` calls an OpenAI-compatible `/embeddings` endpoint. Local Ollama and LM Studio servers use this protocol. A hosted endpoint sends note text to that service.
|
|
10
|
-
|
|
11
|
-
`cohere` calls Cohere's native embedding endpoint. The `key` setting names the environment variable that contains the credential. Note text leaves the machine.
|
|
12
|
-
|
|
13
|
-
## Model choice
|
|
14
|
-
|
|
15
|
-
The default static model is English-only. For another language, choose a model whose published card declares that language. A static Model2Vec model must have compatible safetensors and tokenizer files with supported floating-point weights.
|
|
16
|
-
|
|
17
|
-
Sense checks declared model languages against the indexed tree. A clear mismatch raises `EMBED_MODEL_MISMATCH`. A model card with no language declaration cannot be checked, so confirm it before indexing.
|
|
18
|
-
|
|
19
|
-
For a static model, inspect current Model2Vec models and their cards rather than relying on a fixed list in this skill. For a local HTTP provider, inspect the model catalog exposed by the chosen runtime. Model availability and model cards change independently of sense releases.
|
|
20
|
-
|
|
21
|
-
## Download and rebuild behavior
|
|
22
|
-
|
|
23
|
-
Naming a remote static model in the config authorizes its download. `sense download` performs that fetch before a query. A default vector query embeds its eligible scope; `sense build` and `sense watch` prepare all configured vector capabilities, while `--no-build` requires the requested scope to be ready.
|
|
24
|
-
|
|
25
|
-
Changing the provider, model, or effective endpoint invalidates stored vector values while preserving the indexed chunks. Changing `embed.chunkTokens` rebuilds affected embedding rows, and changing preset coverage can add, remove, or rebuild them. A build-enabled command performs this work; config `"build": false` or `--no-build` reports incompatible or unprepared state without rebuilding or repairing the Sense index. Settle the broad scope before embedding a large tree.
|
|
26
|
-
|
|
27
|
-
## Chunk size
|
|
28
|
-
|
|
29
|
-
`embed.chunkTokens` sets the estimated token ceiling for chunks. The default is 500. Sense splits at headings and then applies the ceiling to long sections. Lower it for an embedding model with a smaller useful context window. Raising it trades fewer vectors for less precise line ranges and more text per vector.
|
|
30
|
-
|
|
31
|
-
## Signal weights
|
|
32
|
-
|
|
33
|
-
A preset includes a signal by naming it:
|
|
34
|
-
|
|
35
|
-
```json
|
|
36
|
-
"signals": { "words": 1, "links": 1, "vectors": 1 }
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
The number is the signal's reciprocal-rank weight. It is not a boolean. Weight 1 gives equal contribution. A higher vector weight can help a strong encoder on some corpora and hurt another tree whose exact vocabulary carries the answer.
|
|
40
|
-
|
|
41
|
-
Test representative questions from the actual tree before changing weights. Compare returned paths and evidence labels, not only the fused score.
|
|
42
|
-
|
|
43
|
-
## Layers without vectors
|
|
44
|
-
|
|
45
|
-
An `embed` block makes vectors available to the tree. A preset can still omit the vector signal:
|
|
46
|
-
|
|
47
|
-
```json
|
|
48
|
-
"signals": { "words": 1, "links": 1 }
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
Use that for layers where exact wording matters more than paraphrase, such as raw sources, archives, generated logs, or citation corpora. Those files can remain indexed without paying the embedding cost for that preset's scope.
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
<!-- sense-store-benchmark release=0.27.0 -->
|
|
2
|
-
# Current store benchmark summary
|
|
3
|
-
|
|
4
|
-
The release assessment generated this file for store selection. Release `0.27.0` passed on 2026-10-03, measured on Apple M1 Pro with Node v26.10.0.
|
|
5
|
-
|
|
6
|
-
The timing rows ran on the same 6,566-note tree. They include CLI startup and each store's complete selected path. Ranked candidates and downstream work can differ by store, so these are current operating measurements rather than an isolated database-engine contest.
|
|
7
|
-
|
|
8
|
-
| Store | Cold index | Warm count | Lexical search | Semantic search | Portable semantic nDCG@10 |
|
|
9
|
-
|---|---|---|---|---|---|
|
|
10
|
-
| sqlite | 2,254 ms | 260 ms | 350 ms | 576 ms | 0.3306 |
|
|
11
|
-
| duckdb | 3,026 ms | 319 ms | 510 ms | 695 ms | 0.3282 |
|
|
12
|
-
| turso | 3,273 ms | 279 ms | 412 ms | 1,081 ms | 0.3299 |
|
|
13
|
-
|
|
14
|
-
Cold index measures core index creation and a count query, excluding document embeddings. Warm count is a no-change `COUNT(*)` query. Lexical and semantic search are steady-state `sense search` commands. Lower timing is faster. Higher nDCG@10 is better; that quality column uses the same NFCorpus queries, judgments, result count, and model on every store.
|
|
15
|
-
|
|
16
|
-
Choose from the intended workflow, capabilities, and SQL compatibility. These numbers describe the current Sense implementations, not a permanent ranking of the engines. Treat small timing or relevance differences as diagnostic unless a representative workload for the target tree reproduces them.
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
# Choose DuckDB
|
|
2
|
-
|
|
3
|
-
Choose DuckDB when the cache file is an analytical artifact that another DuckDB tool will query, when saved SQL needs DuckDB's types and functions, or when the cache belongs beside large local or cloud datasets.
|
|
4
|
-
|
|
5
|
-
DuckDB is designed for analytical SQL and supports larger-than-memory work by spilling to disk. Its SQL closely follows PostgreSQL while keeping documented differences and extensions. See the official [DuckDB performance guide](https://duckdb.org/docs/current/guides/performance/how_to_tune_workloads) and [SQL introduction](https://duckdb.org/docs/current/sql/introduction).
|
|
6
|
-
|
|
7
|
-
Sense's [store benchmark summary](../store-benchmarks.md) measures the current adapter paths on a named markdown tree. It does not measure DuckDB's general analytical ceiling. Use it when current Sense latency matters to the choice.
|
|
8
|
-
|
|
9
|
-
## What the choice gives you
|
|
10
|
-
|
|
11
|
-
- A standard `.sense/cache.duckdb` database that DuckDB tools can open.
|
|
12
|
-
- Frontmatter in `VARIANT` columns, so lists, maps, booleans, numbers, and strings retain useful native shapes.
|
|
13
|
-
- Embeddings in native fixed-width float arrays.
|
|
14
|
-
- DuckDB SQL for downstream analysis.
|
|
15
|
-
- Compatibility with external DuckDB workflows. MotherDuck supports hybrid queries over local and cloud data, although Sense does not connect, upload, or replicate the cache for you. See [MotherDuck's hybrid execution overview](https://motherduck.com/research/motherduck-duckdb-in-the-cloud-and-in-the-client/).
|
|
16
|
-
|
|
17
|
-
## Current Sense adapter boundaries
|
|
18
|
-
|
|
19
|
-
- DuckDB support is experimental.
|
|
20
|
-
- The first open installs the newest `@duckdb/node-api` in the range sensemaking's `peerDependencies` declares, a native download of about 110 MB. An already-installed version is used, and one outside that range is reported. A build after the version changes rebuilds the cache.
|
|
21
|
-
- Under DuckDB's [concurrency model](https://duckdb.org/docs/current/connect/concurrency), Sense opens an existing cache for no-build observation in read-only mode, so several independent reader processes can coexist. A build-enabled command or watcher opens it read-write and excludes every other process until it closes. Contenders wait within Sense's bounded lock budget, then fail with `STORE_BUSY`. This cross-process rule does not promise writer/reader coexistence or unrestricted sharing by independently configured DuckDB instances in one process.
|
|
22
|
-
- `sense search` accepts bare words and quoted phrases. It rejects FTS5 boolean, prefix, `NEAR`, initial-token, grouping, and column-filter syntax.
|
|
23
|
-
- SQLite's raw `MATCH`, `snippet()`, and `bm25()` queries do not port.
|
|
24
|
-
- Mixed-type `VARIANT` fields can require explicit casts in predicates.
|
|
25
|
-
|
|
26
|
-
Commands, public tables, `has()`, `basename()`, `segment()`, vectors, and `sense watch` remain available. Read the [DuckDB query guide](../../../sense/references/stores/duckdb.md) before translating saved SQL or advanced searches.
|
|
27
|
-
|
|
28
|
-
Changing to DuckDB builds its separate cache. It does not migrate the SQLite or Turso cache.
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
# Choose SQLite
|
|
2
|
-
|
|
3
|
-
SQLite is the default recommendation for a sense tree.
|
|
4
|
-
|
|
5
|
-
Choose it when the tree has no engine-specific requirement, when saved searches use FTS5 operators, or when people and agents may run sense commands at the same time.
|
|
6
|
-
|
|
7
|
-
## What the choice gives you
|
|
8
|
-
|
|
9
|
-
- No optional store package. Sense uses Node's built-in SQLite.
|
|
10
|
-
- The full documented word-search grammar, including boolean, prefix, `NEAR`, initial-token, grouping, and column-filter expressions.
|
|
11
|
-
- Raw FTS5 SQL through `MATCH`, `bm25()`, and `snippet()`.
|
|
12
|
-
- The sense SQL functions `has()`, `basename()`, and `segment()`.
|
|
13
|
-
- WAL-backed concurrent sense commands.
|
|
14
|
-
|
|
15
|
-
The cache is `.sense/cache.db`. It is derived data and can be rebuilt from the markdown files. It records the bundled SQLite version that built it, so a Node upgrade that changes that version rebuilds it on the next build.
|
|
16
|
-
|
|
17
|
-
## When another store fits the workflow
|
|
18
|
-
|
|
19
|
-
Use DuckDB when downstream DuckDB tools need to query the cache, the tree needs DuckDB's analytical SQL and native types, or the cache belongs beside large datasets or in a local/cloud workflow. Use Turso when its embedded Rust engine or Tantivy index fits the intended integration. Compare current measurements in [the store benchmark summary](../store-benchmarks.md) when Sense performance affects the decision.
|
|
20
|
-
|
|
21
|
-
If existing queries use FTS5 syntax, moving away from SQLite requires rewriting those queries. Read the selected engine's query guide under the `sense` skill before changing the config.
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
# Choose Turso
|
|
2
|
-
|
|
3
|
-
Choose Turso when the tree benefits from the embedded Turso Database engine or its Tantivy text index, or when evaluating the engine as the Sense adapter evolves. The Sense adapter is experimental; the engine is a fast-moving Rust rewrite of SQLite whose trade-offs can change between releases.
|
|
4
|
-
|
|
5
|
-
Upstream Turso offers async I/O and concurrent writes in its local engine, plus sync through a separate SDK. Sense currently opens one local cache through `@tursodatabase/database` and serializes Sense commands against that file. It does not configure sync, remote access, or encryption. Read [Turso's current SDK guide](https://docs.turso.tech/sdk/introduction) to distinguish engine capabilities from the adapter features Sense exposes.
|
|
6
|
-
|
|
7
|
-
## What the choice gives you
|
|
8
|
-
|
|
9
|
-
- A `.sense/cache.turso.db` file using the embedded Turso engine.
|
|
10
|
-
- Tantivy-backed lexical ranking with shared stemming and n-gram support for unspaced scripts.
|
|
11
|
-
- The asynchronous JavaScript database client used by sense's store adapter.
|
|
12
|
-
- Native vector storage and search.
|
|
13
|
-
|
|
14
|
-
## Current Sense adapter boundaries
|
|
15
|
-
|
|
16
|
-
- Turso support is experimental.
|
|
17
|
-
- The first open installs the optional native package.
|
|
18
|
-
- Turso holds the cache file for the life of a connection. Concurrent sense commands wait for the current command or watcher cycle to close it.
|
|
19
|
-
- `sense search` accepts bare words and quoted phrases. It rejects FTS5 boolean, prefix, `NEAR`, initial-token, grouping, and column-filter syntax.
|
|
20
|
-
- SQLite's raw `MATCH`, `snippet()`, and `bm25()` queries do not port.
|
|
21
|
-
- `has()` and `basename()` work through SQL rewriting. `segment()` is unavailable.
|
|
22
|
-
|
|
23
|
-
Commands, public tables, vectors, and `sense watch` remain available. Read the [Turso query guide](../../../sense/references/stores/turso.md) before translating saved SQL or advanced searches.
|
|
24
|
-
|
|
25
|
-
Changing to Turso builds its separate cache. It does not migrate the SQLite or DuckDB cache. Review the shipped [store benchmark summary](../store-benchmarks.md) if performance affects the choice.
|