latticesql 3.1.0 → 3.2.0
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 +4 -0
- package/dist/cli.js +1996 -878
- package/dist/index.cjs +352 -187
- package/dist/index.d.cts +15 -20
- package/dist/index.d.ts +15 -20
- package/dist/index.js +352 -187
- package/docs/api-reference.md +1370 -0
- package/docs/architecture.md +331 -0
- package/docs/assistant.md +138 -0
- package/docs/cli.md +515 -0
- package/docs/cloud.md +675 -0
- package/docs/collaboration.md +85 -0
- package/docs/configuration.md +416 -0
- package/docs/entity-context.md +510 -0
- package/docs/examples/agent-system.md +313 -0
- package/docs/examples/cms.md +366 -0
- package/docs/examples/ticket-tracker.md +313 -0
- package/docs/migrations.md +272 -0
- package/docs/templates.md +338 -0
- package/docs/workspaces.md +81 -0
- package/package.json +3 -2
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
# Architecture Overview
|
|
2
|
+
|
|
3
|
+
How `latticesql` is structured internally and the design decisions behind it.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Table of Contents
|
|
8
|
+
|
|
9
|
+
- [High-level picture](#high-level-picture)
|
|
10
|
+
- [Module breakdown](#module-breakdown)
|
|
11
|
+
- [Data flow](#data-flow)
|
|
12
|
+
- [Design decisions](#design-decisions)
|
|
13
|
+
- [Package structure](#package-structure)
|
|
14
|
+
|
|
15
|
+
> **v0.5 additions** are called out inline below. They cover entity context directories, lifecycle management, the manifest, and the `reconcile()` method.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## High-level picture
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
┌─────────────────────────────────────┐
|
|
23
|
+
│ Lattice │
|
|
24
|
+
│ (public facade) │
|
|
25
|
+
│ │
|
|
26
|
+
│ defineEntityContext() reconcile() │ ← v0.5
|
|
27
|
+
└──────────────────┬──────────────────┘
|
|
28
|
+
│
|
|
29
|
+
┌────────────────────────────┼──────────────────────────┐
|
|
30
|
+
│ │ │
|
|
31
|
+
┌─────────▼──────────┐ ┌────────────▼──────────┐ ┌───────────▼──────────┐
|
|
32
|
+
│ SchemaManager │ │ SQLiteAdapter │ │ WritebackPipeline │
|
|
33
|
+
│ │ │ │ │ │
|
|
34
|
+
│ • define(table) │ │ • open() / close() │ │ • define(def) │
|
|
35
|
+
│ • defineEntityCtx() │ │ • run() / all() / get()│ │ • process() │
|
|
36
|
+
│ • getPrimaryKey() │ │ • WAL mode │ │ • file watching │
|
|
37
|
+
│ • getRelations() │ │ • busy timeout │ │ • dedup │
|
|
38
|
+
│ • applySchema() │ └────────────────────────┘ └──────────────────────┘
|
|
39
|
+
│ • applyMigrations() │
|
|
40
|
+
└─────────────────────┘
|
|
41
|
+
│
|
|
42
|
+
┌─────────▼──────────┐ ┌────────────────────────┐
|
|
43
|
+
│ RenderEngine │ │ SyncLoop │
|
|
44
|
+
│ │ │ │
|
|
45
|
+
│ • render(outputDir) │◄──│ • watch(outputDir) │
|
|
46
|
+
│ • resolveRelations()│ │ • setInterval polling │
|
|
47
|
+
│ • writeFiles() │ │ • cleanup on each tick │ ← v0.5
|
|
48
|
+
│ • _renderEntityCtxs │ │ • StopFn returned │
|
|
49
|
+
│ • writeManifest() │ └─────────────────────────┘
|
|
50
|
+
│ • cleanup() │ ← v0.5
|
|
51
|
+
└─────────────────────┘
|
|
52
|
+
│
|
|
53
|
+
┌─────────▼──────────┐ ┌────────────────────────┐ ┌──────────────────────┐
|
|
54
|
+
│ RenderTemplates │ │ Sanitizer │ │ Lifecycle (v0.5) │
|
|
55
|
+
│ │ │ │ │ │
|
|
56
|
+
│ • compileRender() │ │ • sanitizeRow() │ │ • readManifest() │
|
|
57
|
+
│ • default-list │ │ • null-byte strip │ │ • writeManifest() │
|
|
58
|
+
│ • default-table │ │ • field length limits │ │ • manifestPath() │
|
|
59
|
+
│ • default-detail │ │ • audit event emission │ │ • cleanupEntityCtxs()│
|
|
60
|
+
│ • default-json │ └─────────────────────────┘ └──────────────────────┘
|
|
61
|
+
│ • interpolate() │
|
|
62
|
+
└─────────────────────┘
|
|
63
|
+
│
|
|
64
|
+
┌─────────▼──────────┐
|
|
65
|
+
│ EntityQuery (v0.5) │
|
|
66
|
+
│ │
|
|
67
|
+
│ • resolveSource() │
|
|
68
|
+
│ • self / hasMany │
|
|
69
|
+
│ • manyToMany │
|
|
70
|
+
│ • belongsTo │
|
|
71
|
+
│ • custom │
|
|
72
|
+
│ • truncateContent() │
|
|
73
|
+
└─────────────────────┘
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## Module breakdown
|
|
79
|
+
|
|
80
|
+
### `Lattice` — public facade (`src/lattice.ts`)
|
|
81
|
+
|
|
82
|
+
The single public class. It wires together all internal modules and exposes the public API. Key responsibilities:
|
|
83
|
+
|
|
84
|
+
- Accepts `string | LatticeConfigInput` in the constructor, normalising both forms to a `dbPath` + table definitions
|
|
85
|
+
- Enforces the `define → init → CRUD/sync` lifecycle (throws if called out of order)
|
|
86
|
+
- Owns the event handler arrays and dispatches to them
|
|
87
|
+
- Delegates every operation to an internal module
|
|
88
|
+
|
|
89
|
+
### `SQLiteAdapter` — database layer (`src/db/sqlite.ts`)
|
|
90
|
+
|
|
91
|
+
A thin wrapper around `better-sqlite3`. Responsibilities:
|
|
92
|
+
|
|
93
|
+
- `open()` — opens the connection, optionally enabling WAL mode and setting busy timeout
|
|
94
|
+
- `close()` — closes the connection
|
|
95
|
+
- `run(sql, params)` — executes a DML statement (INSERT, UPDATE, DELETE)
|
|
96
|
+
- `all(sql, params)` — returns `Row[]`
|
|
97
|
+
- `get(sql, params)` — returns one `Row | undefined`
|
|
98
|
+
- Exposes `.db` for the escape hatch
|
|
99
|
+
|
|
100
|
+
The adapter is synchronous — `better-sqlite3` is a synchronous binding. All Lattice methods return Promises for a consistent async API surface, but they resolve synchronously.
|
|
101
|
+
|
|
102
|
+
### `SchemaManager` — schema registry (`src/schema/manager.ts`)
|
|
103
|
+
|
|
104
|
+
Holds all registered table and multi-table definitions. Responsibilities:
|
|
105
|
+
|
|
106
|
+
- `define(table, compiledDef)` — stores a `CompiledTableDef` (render is always a function)
|
|
107
|
+
- `defineMulti(name, def)` — stores a multi-table view definition
|
|
108
|
+
- `getPrimaryKey(table)` — returns the PK column array for a table
|
|
109
|
+
- `getRelations(table)` — returns the relations map for a table
|
|
110
|
+
- `applySchema(adapter)` — emits `CREATE TABLE IF NOT EXISTS` for every registered table
|
|
111
|
+
- `applyMigrations(adapter, migrations)` — creates `_lattice_migrations` and runs pending versions
|
|
112
|
+
|
|
113
|
+
`CompiledTableDef` differs from `TableDefinition` in that the `render` field is always a compiled `(rows: Row[]) => string` function. The compilation happens once in `Lattice.define()` via `compileRender()`.
|
|
114
|
+
|
|
115
|
+
### `RenderEngine` — sync to files (`src/render/engine.ts`)
|
|
116
|
+
|
|
117
|
+
Executes the render cycle. Responsibilities:
|
|
118
|
+
|
|
119
|
+
- `render(outputDir)` — iterates all registered tables, multi-table views, and entity context definitions; renders each to a string; writes to the appropriate output file (skipping unchanged content)
|
|
120
|
+
- `resolveRelations(table, rows)` — for `belongsTo` relations referenced in template strings, joins to the related table in-process
|
|
121
|
+
- `_renderEntityContexts(outputDir)` — (v0.5) renders all `defineEntityContext()` definitions; returns `Record<string, EntityContextManifestEntry>` and writes the manifest
|
|
122
|
+
- `cleanup(outputDir, prevManifest, options, newManifest?)` — (v0.5) builds current slug sets from the DB and calls `cleanupEntityContexts()`
|
|
123
|
+
- Returns `RenderResult` with file paths and timing
|
|
124
|
+
|
|
125
|
+
File writes are skipped when the new content equals the existing file content — important for keeping LLM context file mtimes stable.
|
|
126
|
+
|
|
127
|
+
### `EntityQuery` — entity source resolver (`src/render/entity-query.ts`) _(v0.5)_
|
|
128
|
+
|
|
129
|
+
Contains the synchronous row-resolution logic for entity context directories. Responsibilities:
|
|
130
|
+
|
|
131
|
+
- `resolveEntitySource(source, entityRow, entityPk, adapter)` — dispatches to the correct SQL query based on the source type (`self`, `hasMany`, `manyToMany`, `belongsTo`, `custom`)
|
|
132
|
+
- `truncateContent(content, budget?)` — applies the per-file character budget and appends the truncation notice
|
|
133
|
+
|
|
134
|
+
### `Lifecycle` — manifest and cleanup (`src/lifecycle/`) _(v0.5)_
|
|
135
|
+
|
|
136
|
+
Two modules:
|
|
137
|
+
|
|
138
|
+
- `manifest.ts` — `readManifest()`, `writeManifest()`, `manifestPath()` and the `LatticeManifest` / `EntityContextManifestEntry` types
|
|
139
|
+
- `cleanup.ts` — `cleanupEntityContexts()` and the `CleanupOptions` / `CleanupResult` types
|
|
140
|
+
|
|
141
|
+
The manifest (`{outputDir}/.lattice/manifest.json`) is the single source of truth for what Lattice generated. It is written atomically after every render cycle that includes entity contexts and read by the cleanup step to determine orphans.
|
|
142
|
+
|
|
143
|
+
### `SyncLoop` — polling loop (`src/sync/loop.ts`)
|
|
144
|
+
|
|
145
|
+
Wraps `RenderEngine` in a `setInterval` polling loop. Responsibilities:
|
|
146
|
+
|
|
147
|
+
- `watch(outputDir, opts)` — starts the loop, returns a `StopFn`
|
|
148
|
+
- Reads the previous manifest before each render cycle when `opts.cleanup` is set (v0.5)
|
|
149
|
+
- Calls `engine.cleanup()` after each render cycle when `opts.cleanup` is set (v0.5)
|
|
150
|
+
- Calls `onRender`, `onError`, and `onCleanup` callbacks per cycle
|
|
151
|
+
|
|
152
|
+
### `WritebackPipeline` — agent-to-db writes (`src/writeback/pipeline.ts`)
|
|
153
|
+
|
|
154
|
+
Monitors agent-written files and ingests new entries into the database. Responsibilities:
|
|
155
|
+
|
|
156
|
+
- `define(def)` — registers a writeback definition
|
|
157
|
+
- `process()` — reads registered files from their last-read offset, calls `parse()`, deduplicates, and calls `persist()` for each new entry
|
|
158
|
+
|
|
159
|
+
### `RenderTemplates` — built-in templates (`src/render/templates.ts`)
|
|
160
|
+
|
|
161
|
+
Contains the four built-in template implementations and the template compilation logic. Responsibilities:
|
|
162
|
+
|
|
163
|
+
- `compileRender(def, table, schema, adapter)` — converts a `RenderSpec` into a `(rows: Row[]) => string` function. Called once at `define()` time
|
|
164
|
+
- `interpolate(template, row, relations)` — replaces `{{field}}` and `{{rel.field}}` tokens
|
|
165
|
+
- `renderList`, `renderTable`, `renderDetail`, `renderJson` — the four built-in renderers
|
|
166
|
+
|
|
167
|
+
### `Sanitizer` — input safety (`src/security/sanitize.ts`)
|
|
168
|
+
|
|
169
|
+
Applied to every row before it reaches the database. Responsibilities:
|
|
170
|
+
|
|
171
|
+
- Strip null bytes from string values
|
|
172
|
+
- Apply field length limits (`SecurityOptions.fieldLimits`)
|
|
173
|
+
- Emit `AuditEvent` on each write operation
|
|
174
|
+
|
|
175
|
+
### Config layer (`src/config/`)
|
|
176
|
+
|
|
177
|
+
Two modules:
|
|
178
|
+
|
|
179
|
+
- `types.ts` — TypeScript types for `LatticeConfig`, `LatticeEntityDef`, `LatticeFieldDef`, `LatticeEntityRenderSpec`
|
|
180
|
+
- `parser.ts` — `parseConfigFile()` and `parseConfigString()`: read YAML, validate, convert to `ParsedConfig` (array of `{ name, TableDefinition }`)
|
|
181
|
+
|
|
182
|
+
### Codegen layer (`src/codegen/`)
|
|
183
|
+
|
|
184
|
+
- `generate.ts` — `generateTypes()`, `generateMigration()`, `generateAll()`: string generators for `types.ts` and `migration.sql`
|
|
185
|
+
|
|
186
|
+
### CLI (`src/cli.ts`)
|
|
187
|
+
|
|
188
|
+
Standalone entry point compiled to `dist/cli.js` with a `#!/usr/bin/env node` shebang. Uses no external CLI framework — just manual `process.argv` parsing. Calls `generateAll()` and logs results.
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
## Data flow
|
|
193
|
+
|
|
194
|
+
### Insert flow
|
|
195
|
+
|
|
196
|
+
```
|
|
197
|
+
db.insert(table, row)
|
|
198
|
+
→ Sanitizer.sanitizeRow(row)
|
|
199
|
+
→ resolve PK (auto-UUID if default 'id' and absent)
|
|
200
|
+
→ SQLiteAdapter.run(INSERT INTO ...)
|
|
201
|
+
→ Sanitizer.emitAudit(table, 'insert', id)
|
|
202
|
+
→ emit 'audit' event to handlers
|
|
203
|
+
→ return Promise.resolve(pkValue)
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### Render flow
|
|
207
|
+
|
|
208
|
+
```
|
|
209
|
+
db.render(outputDir) / SyncLoop (watch)
|
|
210
|
+
→ RenderEngine.render(outputDir)
|
|
211
|
+
→ for each registered table:
|
|
212
|
+
→ SQLiteAdapter.all(SELECT * FROM table)
|
|
213
|
+
→ apply table.filter (if defined)
|
|
214
|
+
→ apply hooks.beforeRender (if defined)
|
|
215
|
+
→ resolve belongsTo relations (for template interpolation)
|
|
216
|
+
→ call compiled render function(rows)
|
|
217
|
+
→ compare output to existing file content
|
|
218
|
+
→ write file if changed
|
|
219
|
+
→ for each multi-table view:
|
|
220
|
+
→ call def.keys() for anchor rows
|
|
221
|
+
→ for each anchor: query tables, call def.render(key, tables)
|
|
222
|
+
→ write files
|
|
223
|
+
→ _renderEntityContexts(outputDir): ← v0.5
|
|
224
|
+
→ for each entity context definition:
|
|
225
|
+
→ render index file (if defined)
|
|
226
|
+
→ for each entity row:
|
|
227
|
+
→ derive slug → entity subdirectory
|
|
228
|
+
→ for each EntityFileSpec:
|
|
229
|
+
→ resolveEntitySource(source, row, pk, adapter)
|
|
230
|
+
→ call spec.render(rows)
|
|
231
|
+
→ apply budget truncation (if defined)
|
|
232
|
+
→ skip write if omitIfEmpty and rows empty
|
|
233
|
+
→ write file if content changed
|
|
234
|
+
→ write combined file (if defined)
|
|
235
|
+
→ writeManifest(outputDir, manifest)
|
|
236
|
+
→ return RenderResult
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### Reconcile flow _(v0.5)_
|
|
240
|
+
|
|
241
|
+
```
|
|
242
|
+
db.reconcile(outputDir, options)
|
|
243
|
+
→ prevManifest = readManifest(outputDir) ← read BEFORE render
|
|
244
|
+
→ RenderEngine.render(outputDir) ← writes new manifest
|
|
245
|
+
→ newManifest = readManifest(outputDir)
|
|
246
|
+
→ cleanupEntityContexts(
|
|
247
|
+
outputDir,
|
|
248
|
+
entityContexts,
|
|
249
|
+
currentSlugsByTable, ← fresh DB query
|
|
250
|
+
prevManifest,
|
|
251
|
+
options,
|
|
252
|
+
newManifest ← used to detect omitIfEmpty-skipped files
|
|
253
|
+
)
|
|
254
|
+
→ return ReconcileResult { ...renderResult, cleanup: CleanupResult }
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
### Sync flow
|
|
258
|
+
|
|
259
|
+
```
|
|
260
|
+
db.sync(outputDir)
|
|
261
|
+
→ RenderEngine.render(outputDir) ← same as render()
|
|
262
|
+
→ WritebackPipeline.process() ← read agent files, ingest entries
|
|
263
|
+
→ return SyncResult
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
---
|
|
267
|
+
|
|
268
|
+
## Design decisions
|
|
269
|
+
|
|
270
|
+
**Synchronous SQLite, async API surface.** `better-sqlite3` is synchronous. All Lattice methods still return `Promise<T>` — this allows callers to use `await` and keeps the API contract stable if an async adapter is ever added. The promises resolve in the same tick.
|
|
271
|
+
|
|
272
|
+
**Compile at define-time, not render-time.** `compileRender()` converts a `RenderSpec` (which can be a string, object, or function) into a single `(rows: Row[]) => string` function when `define()` is called. This ensures zero per-cycle overhead for template dispatch.
|
|
273
|
+
|
|
274
|
+
**No ORM, no query builder.** Lattice does not attempt to abstract SQL. The `columns` spec is a raw SQLite type string. Advanced queries use `db.db` (escape hatch). This keeps the library small and avoids the impedance mismatch that plagues ORMs.
|
|
275
|
+
|
|
276
|
+
**Config form is a thin wrapper over `define()`.** `new Lattice({ config })` calls `parseConfigFile()` then loops over the result calling `this.define()`. The config form is not a separate code path — it just automates the manual `define()` calls.
|
|
277
|
+
|
|
278
|
+
**Files are skipped when content is unchanged.** `RenderEngine` compares the rendered string to the file's current content before writing. This prevents unnecessary filesystem writes and keeps file modification times stable (important for LLM context systems that watch mtimes).
|
|
279
|
+
|
|
280
|
+
**Relation resolution happens in-process.** When a `belongsTo` relation is referenced in a `{{rel.field}}` token, Lattice issues a `SELECT` for each row via the adapter. This is intentionally simple — N+1 for N rows. For tables with thousands of rows this could be slow, but Lattice is designed for small-to-medium context tables (dozens to hundreds of rows), not analytics workloads.
|
|
281
|
+
|
|
282
|
+
**Manifest-driven cleanup.** Lattice never scans the output directory for files it does not recognise. Instead it only removes files and directories it previously recorded in the manifest. This means files created by agents or other tools in entity directories are never touched — unless they happen to match a filename Lattice manages, which is why `protectedFiles` exists as an escape valve.
|
|
283
|
+
|
|
284
|
+
**Render before cleanup.** `reconcile()` always runs the render cycle first, writing a new manifest, before running cleanup. This ensures the cleanup step has both the previous state (what to remove) and the current state (what is still being written) and can correctly detect `omitIfEmpty` files that were skipped this cycle but existed before.
|
|
285
|
+
|
|
286
|
+
---
|
|
287
|
+
|
|
288
|
+
## Package structure
|
|
289
|
+
|
|
290
|
+
```
|
|
291
|
+
src/
|
|
292
|
+
├── index.ts # Public exports
|
|
293
|
+
├── lattice.ts # Lattice class (public facade)
|
|
294
|
+
├── types.ts # All public TypeScript types
|
|
295
|
+
├── cli.ts # CLI entry point
|
|
296
|
+
├── config/
|
|
297
|
+
│ ├── types.ts # YAML config schema types
|
|
298
|
+
│ └── parser.ts # parseConfigFile / parseConfigString
|
|
299
|
+
├── codegen/
|
|
300
|
+
│ └── generate.ts # generateTypes / generateMigration / generateAll
|
|
301
|
+
├── db/
|
|
302
|
+
│ └── sqlite.ts # SQLiteAdapter
|
|
303
|
+
├── schema/
|
|
304
|
+
│ └── manager.ts # SchemaManager
|
|
305
|
+
├── render/
|
|
306
|
+
│ ├── engine.ts # RenderEngine (+ entity context rendering, v0.5)
|
|
307
|
+
│ ├── templates.ts # Built-in templates + compileRender + interpolate
|
|
308
|
+
│ └── entity-query.ts # resolveEntitySource + truncateContent (v0.5)
|
|
309
|
+
├── lifecycle/ # v0.5
|
|
310
|
+
│ ├── index.ts # Barrel export
|
|
311
|
+
│ ├── manifest.ts # readManifest / writeManifest / manifestPath
|
|
312
|
+
│ └── cleanup.ts # cleanupEntityContexts + CleanupOptions/Result
|
|
313
|
+
├── sync/
|
|
314
|
+
│ └── loop.ts # SyncLoop (+ cleanup integration, v0.5)
|
|
315
|
+
├── writeback/
|
|
316
|
+
│ └── pipeline.ts # WritebackPipeline
|
|
317
|
+
└── security/
|
|
318
|
+
└── sanitize.ts # Sanitizer
|
|
319
|
+
|
|
320
|
+
tests/
|
|
321
|
+
├── unit/
|
|
322
|
+
│ ├── config.test.ts # parseConfigFile / parseConfigString
|
|
323
|
+
│ ├── codegen.test.ts # generateTypes / generateMigration + integration
|
|
324
|
+
│ ├── lattice.test.ts # Core CRUD / query / render tests
|
|
325
|
+
│ └── entity-query.test.ts # resolveEntitySource unit tests (v0.5)
|
|
326
|
+
├── integration/
|
|
327
|
+
│ ├── entity-context.test.ts # defineEntityContext() flow (v0.5)
|
|
328
|
+
│ └── lifecycle.test.ts # reconcile() + cleanup (v0.5)
|
|
329
|
+
└── fixtures/
|
|
330
|
+
└── lattice.config.yml
|
|
331
|
+
```
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# The AI assistant & Context Constructor (2.0+)
|
|
2
|
+
|
|
3
|
+
`lattice gui` ships an optional assistant rail. It is **GUI-only** and inert
|
|
4
|
+
until you configure a credential — the `latticesql` library API is unchanged.
|
|
5
|
+
|
|
6
|
+
## Connect Claude
|
|
7
|
+
|
|
8
|
+
Open **Settings → User → Assistant** and paste an Anthropic API key, or set
|
|
9
|
+
`ANTHROPIC_API_KEY` in the environment. Keys are stored encrypted in the native
|
|
10
|
+
`secrets` entity; the env var is a fallback. That's all the chat and the
|
|
11
|
+
Context Constructor need.
|
|
12
|
+
|
|
13
|
+
A **"Connect your Claude subscription"** link (Authorization-Code + PKCE) appears
|
|
14
|
+
only when all four `ANTHROPIC_OAUTH_*` values are configured (see
|
|
15
|
+
[`.env.example`](../.env.example)); otherwise the panel shows a dormant hint.
|
|
16
|
+
Use a fixed GUI port so the redirect URI is stable: `lattice gui --port 4317`.
|
|
17
|
+
|
|
18
|
+
## Chat
|
|
19
|
+
|
|
20
|
+
The rail runs a Claude tool-calling loop streamed over SSE. The model can list,
|
|
21
|
+
read, **full-text search**, create, update, link, delete tables, and revert in
|
|
22
|
+
the active database. **Every edit goes through the same audited, undoable
|
|
23
|
+
mutation path as a manual edit** — it appears in the activity feed and the
|
|
24
|
+
version history and can be reverted.
|
|
25
|
+
|
|
26
|
+
The **top search box hands your query to the assistant**: type and press Enter
|
|
27
|
+
and the query is submitted as a chat turn, which the assistant answers using its
|
|
28
|
+
`search` (and read) tools rather than a plain text match. The assistant never
|
|
29
|
+
sees the conversation-storage or `secrets` tables (search and `list_entities`
|
|
30
|
+
both exclude them).
|
|
31
|
+
|
|
32
|
+
When the assistant points you at a specific record — ask it to "link me to" or
|
|
33
|
+
"open" one — it renders a **clickable object pill** inline in its answer
|
|
34
|
+
(emitted as `[label](lattice://<table>/<id>)`). Clicking the pill opens that row
|
|
35
|
+
in the GUI via the same mode-aware navigator the activity feed uses; it links the
|
|
36
|
+
user-facing record (the contract/person/etc.) rather than an internal `files` id,
|
|
37
|
+
and only ids it actually retrieved.
|
|
38
|
+
|
|
39
|
+
**Deleting a table is guarded + reversible.** The `delete_entity` tool refuses
|
|
40
|
+
built-in tables, tables another table links to, and tables you don't own. An
|
|
41
|
+
**empty** table is soft-deleted immediately; a **non-empty** one is **not**
|
|
42
|
+
deleted until you decide what happens to the data — the tool reports the row
|
|
43
|
+
count and the assistant asks, then you choose `delete_data` (soft-delete the rows
|
|
44
|
+
too) or `move_to` another table. The physical table + rows are kept (no hard
|
|
45
|
+
drop), so the whole thing is revertible from version history.
|
|
46
|
+
|
|
47
|
+
Conversations persist in the native `chat_threads` / `chat_messages` entities;
|
|
48
|
+
use the thread switcher to revisit them. A new thread is **named from a short AI
|
|
49
|
+
summary** of its first exchange (e.g. "Adding New Notes About Cheese"). The
|
|
50
|
+
assistant's **data changes are saved with each turn and replayed as activity
|
|
51
|
+
cards** when you reopen the conversation — collapsed by type (e.g. "Deleted 19
|
|
52
|
+
tables", "Removed 49 rows across 9 tables"), with the operation's icon. Reads
|
|
53
|
+
(list / get / search) change nothing, so they produce no card; only data changes
|
|
54
|
+
appear. The activity feed is scoped to the open conversation rather than a global
|
|
55
|
+
workspace log.
|
|
56
|
+
|
|
57
|
+
The assistant **remembers what it read across turns.** Earlier tool calls and
|
|
58
|
+
their results (including row ids) are replayed into the model's context, so a
|
|
59
|
+
follow-up like "now update that row" reuses the id it just listed instead of
|
|
60
|
+
guessing one. Replay is bounded to the recent turns within a size budget and is
|
|
61
|
+
secret-redacted; set `LATTICE_CHAT_REHYDRATE=false` to disable it. Reads are also
|
|
62
|
+
deterministically ordered, so listing the same table twice returns the same rows.
|
|
63
|
+
|
|
64
|
+
The assistant **knows the record you're looking at.** When a file or row detail is
|
|
65
|
+
open, the chat passes that record (table + id) as context, so "delete this file",
|
|
66
|
+
"summarize this", or "share this row" act on it directly instead of asking which
|
|
67
|
+
one. It's a hint only — every action still goes through the same permission-gated
|
|
68
|
+
tools, so it can't reach a record you couldn't otherwise touch.
|
|
69
|
+
|
|
70
|
+
The assistant can also **answer questions about Lattice itself.** Ask "what is
|
|
71
|
+
private mode?" or "how do I invite a member?" and it calls the `lattice_help` tool,
|
|
72
|
+
which searches Lattice's own documentation (these `docs/*.md` files — the single
|
|
73
|
+
canonical source, shipped in the npm package) and answers from it rather than
|
|
74
|
+
guessing or searching your data.
|
|
75
|
+
|
|
76
|
+
## The Context Constructor (file & text ingest)
|
|
77
|
+
|
|
78
|
+
Drag files onto the rail, click the paperclip, or paste text (or a URL). For each
|
|
79
|
+
source:
|
|
80
|
+
|
|
81
|
+
1. **Referenced, not copied.** The source becomes a native `files` row that
|
|
82
|
+
points at the original; bytes are not moved into Lattice.
|
|
83
|
+
2. **Extracted.** Plain text/markdown/code is read directly; documents
|
|
84
|
+
(PDF, Word `.docx`/`.doc`, PowerPoint `.pptx`, Excel `.xlsx`, OpenDocument
|
|
85
|
+
`.odt`/`.ods`/`.odp`, EPUB, RTF) are parsed **natively in-process** — no
|
|
86
|
+
external CLI; **images are described by Claude vision**; **scanned/image-only
|
|
87
|
+
PDFs** with no text layer fall back to Claude's native PDF read; a pasted
|
|
88
|
+
**bare URL is crawled** for readable text (and the URL preserved on the row as
|
|
89
|
+
a `cloud_ref`). Legacy binary `.xls`/`.ppt` (pre-2007) and any other binary
|
|
90
|
+
are still referenced and marked `extraction_status='skipped'`. The parsers
|
|
91
|
+
ship as optional dependencies, so a document just skips (rather than failing)
|
|
92
|
+
if its parser isn't installed.
|
|
93
|
+
3. **Summarized** with Claude Haiku (the description fills in).
|
|
94
|
+
4. **Organized.** The text is classified against your existing records, and for
|
|
95
|
+
each match the file is **linked** — **auto-creating the `files_<entity>` junction
|
|
96
|
+
table when none exists yet**. When a source fits **nothing** (and aggressiveness
|
|
97
|
+
is high), a new native `notes` object is **created** for it, linked back via
|
|
98
|
+
`source_file_id`. New objects, enrichment, links, and junctions are all
|
|
99
|
+
reversible via the version history.
|
|
100
|
+
|
|
101
|
+
### Library API
|
|
102
|
+
|
|
103
|
+
The same intelligence is a first-class, GUI-independent API (inert without an LLM
|
|
104
|
+
client): `organizeSource`, `describeImage`, `crawlUrl`, `enrichKnowledge`, and the
|
|
105
|
+
`summarizeText` / `classifyLinks` primitives — all importable from `latticesql`.
|
|
106
|
+
`sharp` + `file-type` are optional, lazily-loaded deps; the crawler uses `jsdom` +
|
|
107
|
+
`@mozilla/readability`.
|
|
108
|
+
|
|
109
|
+
A transient **"Analyzing…"** row shows while ingest runs; the add/enrich/link
|
|
110
|
+
events stream into the feed as the server materializes them.
|
|
111
|
+
|
|
112
|
+
## Inference Aggressiveness
|
|
113
|
+
|
|
114
|
+
A single **Conservative ↔ Aggressive** slider (Settings → Assistant) tunes how
|
|
115
|
+
much the assistant extrapolates. It maps to the model sampling temperature, how
|
|
116
|
+
liberally the ingest classifier proposes links, and whether ingest auto-creates a
|
|
117
|
+
missing junction (gated at ≥ 0.25) versus only suggesting it. Default: balanced
|
|
118
|
+
(0.5). Settable via `PUT /api/assistant/aggressiveness { "value": 0..1 }`. This
|
|
119
|
+
is a **user preference** (machine-local `~/.lattice/preferences.json`), not a
|
|
120
|
+
workspace secret — it persists across workspaces and never appears in a
|
|
121
|
+
workspace's Secrets object.
|
|
122
|
+
|
|
123
|
+
## Voice (optional)
|
|
124
|
+
|
|
125
|
+
Set `OPENAI_API_KEY` (Whisper) or `ELEVENLABS_API_KEY` to enable the composer
|
|
126
|
+
mic; choose the provider in the Assistant settings (also a machine-local user
|
|
127
|
+
preference, not a workspace secret). When no microphone is available the mic
|
|
128
|
+
button is shown disabled with a tooltip rather than erroring. **While a note is
|
|
129
|
+
recording or transcribing, the composer is read-only** — it shows a
|
|
130
|
+
"Listening… / Transcribing…" placeholder and the Send button is disabled — and
|
|
131
|
+
the transcript is inserted when you stop.
|
|
132
|
+
|
|
133
|
+
## Cloud
|
|
134
|
+
|
|
135
|
+
The assistant runs against local SQLite and any `postgres://` connection, including
|
|
136
|
+
a Lattice cloud. On a cloud it connects as your own scoped role, so its reads and
|
|
137
|
+
writes are confined by Postgres Row-Level Security to the rows you may see — see
|
|
138
|
+
[cloud.md](cloud.md).
|