datanotes 2.0.1 → 3.1.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.
Files changed (185) hide show
  1. package/NOTICE +9 -0
  2. package/README.md +203 -28
  3. package/SECURITY.md +92 -45
  4. package/dist/THIRD-PARTY-NOTICES.txt +682 -0
  5. package/dist/api/catalog.d.cts +69 -0
  6. package/dist/api/catalog.d.ts +50 -2
  7. package/dist/api/cli-options.d.cts +39 -0
  8. package/dist/api/cli-options.d.ts +39 -0
  9. package/dist/api/client.d.cts +122 -0
  10. package/dist/api/client.d.ts +56 -34
  11. package/dist/api/docs.d.cts +211 -0
  12. package/dist/api/docs.d.ts +211 -0
  13. package/dist/api/guidance.d.cts +81 -0
  14. package/dist/api/guidance.d.ts +81 -0
  15. package/dist/api/index.cjs +7470 -2771
  16. package/dist/api/index.d.cts +14 -0
  17. package/dist/api/index.d.ts +11 -7
  18. package/dist/api/index.js +7470 -2771
  19. package/dist/api/local.cjs +118 -6
  20. package/dist/api/local.d.cts +95 -0
  21. package/dist/api/local.d.ts +70 -6
  22. package/dist/api/local.js +104 -6
  23. package/dist/api/mcp.cjs +17387 -0
  24. package/dist/api/mcp.d.cts +108 -0
  25. package/dist/api/mcp.d.ts +108 -0
  26. package/dist/api/mcp.js +17337 -0
  27. package/dist/api/otlp.d.cts +113 -0
  28. package/dist/api/otlp.d.ts +63 -15
  29. package/dist/api/redact.d.cts +42 -0
  30. package/dist/api/redact.d.ts +42 -0
  31. package/dist/api/requests.d.cts +26 -0
  32. package/dist/api/requests.d.ts +14 -16
  33. package/dist/api/results.d.cts +16 -0
  34. package/dist/api/results.d.ts +2 -1
  35. package/dist/api/tokens.d.cts +12 -0
  36. package/dist/api/tokens.d.ts +5 -0
  37. package/dist/api/tracing.d.cts +107 -0
  38. package/dist/api/tracing.d.ts +107 -0
  39. package/dist/datanotes.cjs +69597 -28329
  40. package/dist/docs/administration/configuration.md +59 -0
  41. package/dist/docs/administration/docker.md +80 -0
  42. package/dist/docs/administration/git-sync.md +71 -0
  43. package/dist/docs/administration/limits.md +153 -0
  44. package/dist/docs/administration/run-the-service.md +237 -0
  45. package/dist/docs/administration/status.md +61 -0
  46. package/dist/docs/administration/use-with-obsidian.md +75 -0
  47. package/dist/docs/api/operations/add_column.md +49 -0
  48. package/dist/docs/api/operations/add_constraint.md +55 -0
  49. package/dist/docs/api/operations/adopt_rows.md +50 -0
  50. package/dist/docs/api/operations/align_relations.md +41 -0
  51. package/dist/docs/api/operations/alter_column.md +46 -0
  52. package/dist/docs/api/operations/alter_table.md +67 -0
  53. package/dist/docs/api/operations/batch.md +48 -0
  54. package/dist/docs/api/operations/changes_stream.md +28 -0
  55. package/dist/docs/api/operations/check_table.md +29 -0
  56. package/dist/docs/api/operations/clone_table.md +48 -0
  57. package/dist/docs/api/operations/comment_on.md +42 -0
  58. package/dist/docs/api/operations/configure_database.md +52 -0
  59. package/dist/docs/api/operations/create_table.md +78 -0
  60. package/dist/docs/api/operations/create_view.md +54 -0
  61. package/dist/docs/api/operations/create_webhook.md +37 -0
  62. package/dist/docs/api/operations/define_base.md +52 -0
  63. package/dist/docs/api/operations/delete_rows.md +43 -0
  64. package/dist/docs/api/operations/delete_webhook.md +31 -0
  65. package/dist/docs/api/operations/describe_database.md +30 -0
  66. package/dist/docs/api/operations/describe_history.md +40 -0
  67. package/dist/docs/api/operations/describe_semantic_index.md +24 -0
  68. package/dist/docs/api/operations/describe_table.md +38 -0
  69. package/dist/docs/api/operations/docs.md +13 -0
  70. package/dist/docs/api/operations/drop_column.md +41 -0
  71. package/dist/docs/api/operations/drop_constraint.md +39 -0
  72. package/dist/docs/api/operations/drop_table.md +43 -0
  73. package/dist/docs/api/operations/drop_view.md +38 -0
  74. package/dist/docs/api/operations/engine_status.md +23 -0
  75. package/dist/docs/api/operations/graph_neighbors.md +34 -0
  76. package/dist/docs/api/operations/graph_path.md +32 -0
  77. package/dist/docs/api/operations/graph_traverse.md +43 -0
  78. package/dist/docs/api/operations/identify_session.md +24 -0
  79. package/dist/docs/api/operations/infer_table.md +43 -0
  80. package/dist/docs/api/operations/insert_row.md +48 -0
  81. package/dist/docs/api/operations/list_changes.md +35 -0
  82. package/dist/docs/api/operations/list_folder.md +28 -0
  83. package/dist/docs/api/operations/list_link_targets.md +44 -0
  84. package/dist/docs/api/operations/list_migrations.md +35 -0
  85. package/dist/docs/api/operations/list_tables.md +32 -0
  86. package/dist/docs/api/operations/list_webhooks.md +23 -0
  87. package/dist/docs/api/operations/manual_index.md +14 -0
  88. package/dist/docs/api/operations/manual_page.md +22 -0
  89. package/dist/docs/api/operations/merge_rows.md +44 -0
  90. package/dist/docs/api/operations/move_rows.md +52 -0
  91. package/dist/docs/api/operations/openapi.md +13 -0
  92. package/dist/docs/api/operations/openapi_post.md +20 -0
  93. package/dist/docs/api/operations/read_note.md +29 -0
  94. package/dist/docs/api/operations/recompute_columns.md +44 -0
  95. package/dist/docs/api/operations/record_tool_call.md +37 -0
  96. package/dist/docs/api/operations/rename_column.md +44 -0
  97. package/dist/docs/api/operations/rename_value.md +45 -0
  98. package/dist/docs/api/operations/restore_database.md +51 -0
  99. package/dist/docs/api/operations/restore_table.md +46 -0
  100. package/dist/docs/api/operations/search_notes.md +28 -0
  101. package/dist/docs/api/operations/select_rows.md +61 -0
  102. package/dist/docs/api/operations/semantic_search.md +34 -0
  103. package/dist/docs/api/operations/semantic_search_docs.md +26 -0
  104. package/dist/docs/api/operations/set_table_bases.md +44 -0
  105. package/dist/docs/api/operations/status.md +13 -0
  106. package/dist/docs/api/operations/stop_engine.md +23 -0
  107. package/dist/docs/api/operations/sync_semantic_index.md +33 -0
  108. package/dist/docs/api/operations/table_changes.md +31 -0
  109. package/dist/docs/api/operations/tags/auxiliary.md +14 -0
  110. package/dist/docs/api/operations/tags/change-feed.md +8 -0
  111. package/dist/docs/api/operations/tags/history.md +17 -0
  112. package/dist/docs/api/operations/tags/queries.md +15 -0
  113. package/dist/docs/api/operations/tags/rows-dml.md +16 -0
  114. package/dist/docs/api/operations/tags/schema-ddl.md +22 -0
  115. package/dist/docs/api/operations/tags/service.md +15 -0
  116. package/dist/docs/api/operations/tags/tokens.md +13 -0
  117. package/dist/docs/api/operations/tokens_create.md +34 -0
  118. package/dist/docs/api/operations/tokens_delete.md +29 -0
  119. package/dist/docs/api/operations/tokens_list.md +21 -0
  120. package/dist/docs/api/operations/tokens_me.md +20 -0
  121. package/dist/docs/api/operations/tokens_rotate.md +29 -0
  122. package/dist/docs/api/operations/tokens_update.md +34 -0
  123. package/dist/docs/api/operations/undo_migration.md +46 -0
  124. package/dist/docs/api/operations/update_row.md +60 -0
  125. package/dist/docs/api/operations/update_rows.md +46 -0
  126. package/dist/docs/api/operations/vacuum_table.md +37 -0
  127. package/dist/docs/api.md +135 -0
  128. package/dist/docs/develop/build-an-app.md +320 -0
  129. package/dist/docs/develop/build-on-datanotes.md +138 -0
  130. package/dist/docs/develop/change-feed-and-webhooks.md +70 -0
  131. package/dist/docs/develop/client.md +116 -0
  132. package/dist/docs/develop/concurrency.md +188 -0
  133. package/dist/docs/develop/http-api.md +89 -0
  134. package/dist/docs/develop/telemetry.md +285 -0
  135. package/dist/docs/guides/agents/docs-offline.md +99 -0
  136. package/dist/docs/guides/agents/how-agents-use-datanotes.md +124 -0
  137. package/dist/docs/guides/agents/prompts.md +495 -0
  138. package/dist/docs/guides/agents/reading-errors.md +112 -0
  139. package/dist/docs/guides/data-quality/check-constraints.md +379 -0
  140. package/dist/docs/guides/data-quality/check-table.md +63 -0
  141. package/dist/docs/guides/data-quality/hand-edits.md +507 -0
  142. package/dist/docs/guides/data-quality/sync-between-devices.md +51 -0
  143. package/dist/docs/guides/data-quality/validation.md +90 -0
  144. package/dist/docs/guides/database-objects/bases.md +480 -0
  145. package/dist/docs/guides/database-objects/catalogs-and-schemas.md +897 -0
  146. package/dist/docs/guides/database-objects/columns.md +1332 -0
  147. package/dist/docs/guides/database-objects/relations.md +1454 -0
  148. package/dist/docs/guides/database-objects/tables.md +1340 -0
  149. package/dist/docs/guides/database-objects/views.md +1003 -0
  150. package/dist/docs/guides/history/back-up.md +203 -0
  151. package/dist/docs/guides/history/change-data-feed.md +247 -0
  152. package/dist/docs/guides/history/journal-and-undo.md +262 -0
  153. package/dist/docs/guides/history/restore.md +341 -0
  154. package/dist/docs/guides/history/table-history.md +657 -0
  155. package/dist/docs/guides/history/vacuum.md +26 -0
  156. package/dist/docs/guides/read-and-write/automatic-links.md +768 -0
  157. package/dist/docs/guides/read-and-write/batch.md +139 -0
  158. package/dist/docs/guides/read-and-write/import-notes.md +896 -0
  159. package/dist/docs/guides/read-and-write/joins-and-aggregates.md +251 -0
  160. package/dist/docs/guides/read-and-write/read-and-filter.md +536 -0
  161. package/dist/docs/guides/read-and-write/write-rows.md +573 -0
  162. package/dist/docs/guides/search/link-graph.md +241 -0
  163. package/dist/docs/guides/search/semantic.md +133 -0
  164. package/dist/docs/guides/search/text.md +142 -0
  165. package/dist/docs/guides/security/security-model.md +110 -0
  166. package/dist/docs/guides/security/tokens.md +60 -0
  167. package/dist/docs/guides/security/who-did-what.md +44 -0
  168. package/dist/docs/index.md +113 -0
  169. package/dist/docs/llms.txt +33 -0
  170. package/dist/docs/reference/cli.md +143 -0
  171. package/dist/docs/reference/compatibility.md +83 -0
  172. package/dist/docs/reference/errors.md +72 -0
  173. package/dist/docs/reference/glossary.md +161 -0
  174. package/dist/docs/reference/operations/auxiliary.md +116 -0
  175. package/dist/docs/reference/operations/history.md +168 -0
  176. package/dist/docs/reference/operations/queries.md +169 -0
  177. package/dist/docs/reference/operations/rows.md +185 -0
  178. package/dist/docs/reference/operations/schema.md +316 -0
  179. package/dist/docs/reference/operations.md +96 -0
  180. package/dist/docs/reference/settings.md +43 -0
  181. package/dist/docs/release-notes.md +188 -0
  182. package/dist/docs/start/connect-agents.md +204 -0
  183. package/dist/docs/start/how-it-works.md +349 -0
  184. package/dist/docs/start/quick-start.md +128 -0
  185. package/package.json +60 -15
@@ -0,0 +1,285 @@
1
+ # Send logs and traces to your observability backend
2
+
3
+ > Send the logs and traces of the engine, the MCP server and the apps to any OpenTelemetry backend, and find slow operations.
4
+
5
+ Every part of Datanotes sends logs and traces with OpenTelemetry, the vendor-neutral standard. Any backend with an OpenTelemetry Protocol (OTLP) endpoint can receive them. This page shows you how to:
6
+
7
+ * Point the engine, the Model Context Protocol (MCP) server and the apps at your backend
8
+ * Read the spans the engine sends, and what each one measures
9
+ * Follow one agent call across the MCP server and the engine
10
+ * Find the operations that are slow, and why
11
+
12
+ Datanotes has no vendor-specific code.
13
+
14
+ ## What Datanotes sends and where
15
+
16
+ Datanotes sends two signals over HTTP:
17
+
18
+ * **Logs** to `your_endpoint/v1/logs`
19
+ * **Traces** to `your_endpoint/v1/traces`
20
+
21
+ It encodes them as `http/protobuf`, the OpenTelemetry default, or as `http/json`. Any OpenTelemetry Collector receives them, and so do these backends, among others:
22
+
23
+ * SigNoz
24
+ * Grafana: Alloy, Tempo, Loki or Grafana Cloud
25
+ * Elastic, Datadog, Honeycomb, New Relic and Dynatrace
26
+
27
+ Datanotes sends no metrics: your backend computes rates and latency percentiles from the spans. For a backend that accepts only gRPC, put an OpenTelemetry Collector in front of it. When the collector doesn’t answer, Datanotes counts the records and spans as lost and keeps working.
28
+
29
+ ## Configure the export with environment variables
30
+
31
+ The engine, the MCP server and the apps all read the standard OpenTelemetry variables:
32
+
33
+ | Variable | Meaning |
34
+ | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
35
+ | `OTEL_EXPORTER_OTLP_ENDPOINT` | The collector’s base address, for example `http://127.0.0.1:4318` |
36
+ | `OTEL_EXPORTER_OTLP_LOGS_ENDPOINT` | Or the full `…/v1/logs` address |
37
+ | `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT` | Or the full `…/v1/traces` address |
38
+ | `OTEL_EXPORTER_OTLP_HEADERS` | `key=value,key2=value2`, with URL-encoded values: the API key or `Authorization` your backend asks for |
39
+ | `OTEL_EXPORTER_OTLP_PROTOCOL` | `http/protobuf` (default) or `http/json` |
40
+ | `OTEL_SERVICE_NAME` | The service name; defaults to `datanotes`, `datanotes-mcp` (for `datanotes mcp`) or the app’s name |
41
+ | `OTEL_RESOURCE_ATTRIBUTES` | Extra resource attributes, for example `team=home` |
42
+ | `OTEL_TRACES_SAMPLER` and `OTEL_TRACES_SAMPLER_ARG` | `traceidratio` with a share from 0 to 1, `always_on` or `always_off`, plain or with the `parentbased_` prefix |
43
+ | `OTEL_SDK_DISABLED=true` | Turns all the export off |
44
+ | `OTEL_LOGS_EXPORTER=none` or `OTEL_TRACES_EXPORTER=none` | Turns off the logs or the traces only |
45
+
46
+ Datanotes also reads the `…_LOGS_…` and `…_TRACES_…` variants of the headers and protocol variables. Without an endpoint, it sends nothing.
47
+
48
+ ## Configure the engine in its settings file
49
+
50
+ Instead of the variables, you can configure the engine in its settings file, `settings.json` in its store. `datanotes store path` shows where. A variable you set overrides the matching setting:
51
+
52
+ ```json
53
+ {
54
+ "telemetryEnabled": true,
55
+ "telemetryEndpoint": "http://127.0.0.1:4318",
56
+ "telemetryServiceName": "datanotes",
57
+ "telemetryEnvironment": "home",
58
+ "telemetryProtocol": "http/protobuf",
59
+ "telemetryHeaders": {},
60
+ "telemetryTraces": true,
61
+ "telemetryTraceSampleRatio": 1,
62
+ "telemetryMaxSpansPerTrace": 1000,
63
+ "telemetrySlowSpanMs": 0
64
+ }
65
+ ```
66
+
67
+ Each setting does one thing:
68
+
69
+ * **`telemetryEnabled`** turns the export of logs and traces on.
70
+ * **`telemetryEnvironment`** names the environment, such as `dev` or `home`. The engine sends it as `deployment.environment.name` and as the older `deployment.environment`, which some backends group by.
71
+ * **`telemetryHeaders`** holds what your backend asks for, such as an API key. To keep a key out of the file, set `OTEL_EXPORTER_OTLP_HEADERS` instead.
72
+ * **`telemetryTraces`** sends the traces too. It is on by default: turning telemetry on gives you both signals.
73
+ * **`telemetryTraceSampleRatio`** keeps that share of new traces, from 0 to 1. The default, 1, keeps every trace. A trace that a caller started follows the caller’s choice.
74
+ * **`telemetryMaxSpansPerTrace`** caps the spans of one trace. The engine drops the rest and counts them in `datanotes.spans_dropped` on the trace’s first span.
75
+ * **`telemetrySlowSpanMs`** logs a request, an operation or a background job as a WARN when it lasts at least that many milliseconds. 0 turns it off.
76
+
77
+ The engine reads the settings again while it runs, so a change applies within a few seconds. With traces off, tracing costs the engine one check per instrumented call.
78
+
79
+ ## What the engine logs
80
+
81
+ The engine logs these events:
82
+
83
+ * Every HTTP request and operation, with the route, duration, outcome and actor
84
+ * The tool calls MCP servers report, with the tool, duration, result size and client
85
+ * Refused requests
86
+ * The semantic index and git sync
87
+ * Errors
88
+
89
+ A record made during a traced request names its trace. The OTLP record carries the trace and span IDs, so your backend links the log to the trace. The log files carry them as `trace_id` and `span_id`. While a request’s trace is sent, its INFO records become DEBUG records: the span already says what they say.
90
+
91
+ Whatever the collector does, the engine also writes its records to files in `logs/`, in its store outside the vault. The `fileLogEnabled` setting turns these files on or off, and `logRetentionDays` sets how long the engine keeps them.
92
+
93
+ ## The spans the engine sends
94
+
95
+ A trace is a tree of spans. Each span measures one piece of work and names its parent. A request to the engine gives this tree:
96
+
97
+ ```text
98
+ POST /datanotes/operations/insert_row/ SERVER: the HTTP request
99
+ └── engine.op insert_row the operation
100
+ ├── engine.schedule.wait a read waits for writes (only then)
101
+ ├── engine.settle wait for the vault to settle
102
+ └── engine.insert_row the engine's work
103
+ ├── engine.autolink links found in the text
104
+ ├── engine.checks CHECKs and link rules
105
+ ├── engine.lock.acquire wait for the write lock
106
+ └── engine.lock.hold work under the lock
107
+ ├── engine.transaction_log hand edits logged first
108
+ ├── engine.apply files written
109
+ │ └── engine.journal.append the undo entry
110
+ └── engine.transaction_log the operation's commit
111
+ ```
112
+
113
+ A read gives a similar tree under `engine.query.execute`:
114
+
115
+ * **`engine.query.view`** runs the stored query of a view.
116
+ * **`engine.query.plan`** resolves dates and `within` and compiles the filter.
117
+ * **`engine.query.scan`** reads every row of the table. Its `datanotes.query.plan` is `full_scan`: Datanotes has no indexes, and a file cache spares the reads of unchanged notes.
118
+ * **`engine.query.filter`**, **`engine.query.join`**, **`engine.query.group`**, **`engine.query.sort`** and **`engine.query.project`** follow, as the query needs them.
119
+
120
+ Other spans cover the rest of the engine’s time:
121
+
122
+ * **`engine.metastore.parse`** when `metastore.yaml` changed and the engine parses it again
123
+
124
+ * **`engine.history.read`** for a read as of a version or a time
125
+
126
+ * **`engine.graph.build`** when a graph operation rebuilds the link graph after a change. Calls that need it at the same time wait for that one build and count it in `datanotes.graph.build_waits`.
127
+
128
+ * **`engine.change_feed.record`** for the change feed’s events of an operation
129
+
130
+ * **`POST`** (CLIENT) for each webhook delivery, with `url.full`, `server.address` and `http.response.status_code`
131
+
132
+ * **`job <name>`** for background work. The jobs are:
133
+
134
+ * `job hand_edits_log`
135
+ * `job adopt_note`
136
+ * `job schema_reload`
137
+ * `job semantic_index`
138
+ * `job store_maintenance`
139
+ * `job change_feed_prune`
140
+ * `job change_feed_hand_edits`
141
+ * `job transaction_log_warmup`
142
+ * `job auto_recompute`
143
+ * `job inverse_links`
144
+ * `job datanotes_start`, the engine’s start
145
+
146
+ Background work is always a trace of its own, even when a request saw the change behind it. After a start, `job transaction_log_warmup` reads each table’s transaction log once, one table at a time, so a write waits for one table at most.
147
+
148
+ ### Attributes of the operation span
149
+
150
+ The `engine.op <operation>` span follows the conventions of database spans. Most backends list these spans as database calls:
151
+
152
+ | Attribute | Meaning |
153
+ | ---------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
154
+ | `db.system.name`, `db.system` | `datanotes` |
155
+ | `db.operation.name` | The operation, such as `select_rows` |
156
+ | `db.collection.name` | The table or view the call names |
157
+ | `db.namespace` | The catalog and schema, when the call gives a full name |
158
+ | `db.query.summary` | The operation and its table, such as `select_rows task` |
159
+ | `db.response.returned_rows` | The rows the call returned |
160
+ | `datanotes.rows_scanned` | The rows the engine read to answer |
161
+ | `datanotes.notes_changed`, `datanotes.files_created` | What a write changed |
162
+ | `datanotes.migration_id` | The write’s journal entry, for `undo_migration` |
163
+ | `datanotes.dry_run` | Whether the call was a dry run |
164
+ | `datanotes.long_poll` | `true` for a `list_changes` call that waits for changes |
165
+ | `datanotes.schedule.class` | `read`, `write` or `free`: how [write priority](https://datanotes.dev/develop/concurrency/#reads-wait-for-writes) treats the call |
166
+ | `datanotes.schedule.wait_ms` | How long the call waited for writes before it started |
167
+ | `datanotes.schedule.released` | Why a waiting read started: `writes_done`, `max_wait` or `max_writes` |
168
+ | `datanotes.read_cache` | For a read: `hit` (answered from memory), `miss` or `not_kept` ([read cache](https://datanotes.dev/develop/concurrency/#repeated-reads-come-from-memory)) |
169
+ | `error.type` | Why the call failed or was refused |
170
+
171
+ The SERVER span adds `http.request.method`, `http.route`, `http.response.status_code`, `client.address` and `user_agent.original`. It also names the caller: `datanotes.token.name` holds the token’s name, never the token. `datanotes.client.harness` and `datanotes.client.model` hold what the agent says of itself.
172
+
173
+ ### Calls to the vault
174
+
175
+ Reading a table of thousands of notes means thousands of file reads. The engine doesn’t make a span of each one. It adds them up on the span that made them and on its operation span:
176
+
177
+ * `datanotes.io.read_count`, `datanotes.io.read_chars` and `datanotes.io.read_ms`
178
+ * The same for `write`, `create`, `list`, `exists`, `rename` and the other calls
179
+ * `datanotes.cache.hits` and `datanotes.cache.misses` of the file cache
180
+ * `datanotes.cache.text_reads` and `datanotes.cache.text_source`: note texts the file cache read again, or took from the [vault index](https://datanotes.dev/administration/limits/#start-faster-with-the-vault-index)
181
+ * `datanotes.validate.rows` and `datanotes.validate.ms`, `datanotes.checks.rows` and `datanotes.checks.ms` for the checks run once per row
182
+
183
+ `_chars` counts characters, not bytes: the length of the text as JavaScript counts it, in UTF-16 code units. In English Markdown, characters and bytes are about the same. Accents, other alphabets and emoji take more bytes than characters. For `list`, `_chars` counts the files and folders listed. The calls that move no text count 0.
184
+
185
+ A single call that takes 20 ms or more gets a span of its own, `vault.<call>`, with its `file.path` and `datanotes.io.chars`.
186
+
187
+ ### Errors and refusals
188
+
189
+ The engine tells failures and refusals apart:
190
+
191
+ * **A failure** is a bug, a disk error, a write that failed partway or a 5xx answer. Its span has status ERROR and an `exception` event with the type, message and stack.
192
+ * **A refusal** is a call the caller can fix: bad arguments, an unknown table, a broken CHECK. Its span keeps the status unset and sets `error.type`, such as `violation` or `TableNotFoundError`.
193
+
194
+ An empty answer to a `list_changes` poll sends no trace and no log line: services poll every few seconds.
195
+
196
+ ## Follow one agent call across services
197
+
198
+ One agent call is one trace across the MCP server and the engine:
199
+
200
+ 1. The MCP server makes a SERVER span, `tools/call <tool>`. It continues the client’s trace when the client sends `traceparent` in the call’s `_meta`.
201
+ 2. Each request it sends to the engine is a CLIENT span, child of the tool’s span. The request carries that span as its `traceparent` header.
202
+ 3. The engine’s SERVER span continues that trace, and its operation spans follow.
203
+
204
+ The tool span carries `mcp.method.name`, `gen_ai.operation.name` (`execute_tool`), `gen_ai.tool.name` and `mcp.session.id`. Find the agent call by its tool or session, open the trace, and you see where the time went in both processes.
205
+
206
+ The engine, the MCP server and the apps also log the same `session` and `tool` for each agent call. `session` is the first 16 characters of the MCP session ID.
207
+
208
+ ## Find the slow operations
209
+
210
+ Your backend computes the latency of each span name from the traces. To find where the engine spends its time:
211
+
212
+ 1. List the `engine.op <operation>` spans by duration, or open your backend’s database calls view. Leave out the spans with `datanotes.long_poll`: they wait on purpose.
213
+ 2. Open a slow trace and compare its children. A long `engine.lock.acquire` means the call waited for another write. A long `engine.lock.hold` means the write itself was slow. A long `engine.schedule.wait` means a read waited for writes to end.
214
+ 3. In a read, compare `engine.query.scan` with the rest. A slow scan with many `datanotes.cache.misses` reads many notes from disk. A slow scan with few misses works on a large table: narrow it with `where`, or split the table.
215
+ 4. Look at the `datanotes.io.*_ms` attributes of the operation span. They show how much of its time went to the disk.
216
+ 5. Set `telemetrySlowSpanMs` to log the slow calls as WARN records, and alert on them.
217
+
218
+ ## Configure the MCP server and the apps
219
+
220
+ The MCP server and the apps read the same variables, and each has its own alternative:
221
+
222
+ * **`datanotes mcp`**: the `--otlp-endpoint your_endpoint` option, for its logs and traces
223
+ * **Apps**: an `otlp` block in the app’s configuration, `{"endpoint": "http://127.0.0.1:4318", "headers": {}, "serviceName": "…"}`
224
+
225
+ ## Continue a trace from your own program
226
+
227
+ Your program continues its trace in the engine when it sends the W3C `traceparent` header, and `tracestate` when it has one. `datanotes/client` does it for you with two options of `DatanotesClient`:
228
+
229
+ * **`trace`** is the trace to continue: a `traceparent` value, a context, or a function that returns the current one.
230
+ * **`tracer`** makes a CLIENT span for each request, child of `trace`, and sends that span as the engine’s parent.
231
+
232
+ ```ts
233
+ import {
234
+ DatanotesClient,
235
+ OtlpTraceExporter,
236
+ Tracer,
237
+ otlpTracesFromEnv,
238
+ } from "datanotes/client";
239
+
240
+
241
+ const otlp = otlpTracesFromEnv(process.env, { serviceName: "my-app" });
242
+ const exporter = otlp ? new OtlpTraceExporter(otlp) : null;
243
+ const tracer = new Tracer({ export: (s) => exporter?.export(s) });
244
+
245
+
246
+ const run = tracer.startSpan("import films", { kind: "server" });
247
+ const client = new DatanotesClient({
248
+ url: "http://127.0.0.1:27150",
249
+ token: process.env.DATANOTES_TOKEN ?? "",
250
+ tracer,
251
+ trace: run.context,
252
+ });
253
+ await client.call("insert_row", {
254
+ table: "film",
255
+ values: { title: "Arrival" },
256
+ });
257
+ run.end();
258
+ await exporter?.close();
259
+ ```
260
+
261
+ Without a tracer, pass the incoming header as it is. A call can also name its own trace:
262
+
263
+ ```ts
264
+ await client.call("select_rows", { table: "film" }, undefined, {
265
+ trace: request.headers.traceparent,
266
+ });
267
+ ```
268
+
269
+ Any HTTP client works too: send `traceparent: 00-<trace id>-<span id>-01` with the request.
270
+
271
+ ## Send logs from your own program
272
+
273
+ `datanotes/client` exports the log exporter that Datanotes uses, so your own programs can log the same way:
274
+
275
+ ```ts
276
+ import { OtlpLogExporter, otlpFromEnv } from "datanotes/client";
277
+
278
+
279
+ const otlp = otlpFromEnv(process.env, { serviceName: "my-app" });
280
+ const log = otlp ? new OtlpLogExporter(otlp) : null;
281
+ log?.log("INFO", "imported films", { count: 8 });
282
+ await log?.close();
283
+ ```
284
+
285
+ `otlpFromEnv` reads the variables above and returns `null` when you set no endpoint or turn the export off. The program then logs nothing. Pass a span’s context as the fourth argument of `log` to link the record to that span. Call `close()` before the program exits to send the remaining records.
@@ -0,0 +1,99 @@
1
+ # Read the docs offline
2
+
3
+ > The datanotes package ships these docs as Markdown for its version: agents read and search them with list_docs, read_docs and search_docs, programs through the engine, people from the package.
4
+
5
+ The `datanotes` package ships this documentation as Markdown: one file per page of the site in `dist/docs/`, with `llms.txt`. The pages match the installed version, and reading them needs no internet access.
6
+
7
+ ## The agent reads the docs of its version
8
+
9
+ `datanotes mcp` serves these pages to agents. The agent looks up an operation or an example without internet access, and the pages always match the version it talks to. Three tools read them:
10
+
11
+ * **`list_docs`**: lists the pages, each with its path, title and description. `prefix` keeps one folder, such as `guides/`.
12
+ * **`read_docs`**: returns a page as Markdown, or one section of it by the anchor of its heading, such as `guides/read-and-write/read-and-filter#compare-values-with-operators`. It returns at most 20,000 characters at a time, and a longer text ends with how to read the rest.
13
+ * **`search_docs`**: finds the sections that match a few words, best first. Each hit has the path to pass to `read_docs` and a short excerpt.
14
+
15
+ The `Docs:` line of an error names the `read_docs` call for its section, so the agent reads it offline at once:
16
+
17
+ ```text
18
+ Docs: read_docs("guides/read-and-write/read-and-filter#compare-values-with-operators")
19
+ ```
20
+
21
+ The server leaves out the web address, since its agents always have the docs tools. A program that uses `DatanotesClient` gets both: the address, then the `read_docs` call in parentheses. Your own MCP server with the docs tools drops the address with `offlineDocs` from `datanotes/mcp`.
22
+
23
+ An agent that asks for a page or section that doesn’t exist gets the closest ones in the hint. Reading a section instead of a whole page keeps the agent’s context small: `search_docs` finds the section, and `read_docs` returns only that.
24
+
25
+ ## Search with required and excluded words
26
+
27
+ `search_docs` ranks the sections by the words of the query. A word matches in any form, so `rows` finds `row`. It also matches its synonyms, so `revert` finds `undo`. Words in a heading count most.
28
+
29
+ Three marks narrow the search:
30
+
31
+ * **`"a phrase"`**: the section must hold these words in a row, in any form, such as `"time travel"`
32
+ * **`+word`**: the section must hold the word, such as `+view`
33
+ * **`-word`**: the section must not hold the word, such as `-sql`; `-"a phrase"` leaves out a phrase
34
+
35
+ The other words only rank the sections. This query finds the sections on time travel that mention views and never SQL. Here, `db` is a [client](https://datanotes.dev/develop/client/) connected to the engine, as in [Build your own MCP server](https://datanotes.dev/develop/build-on-datanotes/#build-your-own-mcp-server):
36
+
37
+ ```ts
38
+ import { callDocsTool } from "datanotes/mcp";
39
+
40
+
41
+ const hits = await callDocsTool(
42
+ "search_docs",
43
+ { query: '"time travel" +view -sql' },
44
+ { client: db },
45
+ );
46
+ ```
47
+
48
+ The marks filter the results, and never rank them. A section with every required word still ranks by all the words of the query. A word that starts with two dashes, such as `--vault`, is a plain word.
49
+
50
+ ## Search by words and by meaning
51
+
52
+ When the engine has [semantic search](https://datanotes.dev/guides/search/semantic/) on, `search_docs` becomes a hybrid search. It ranks the sections twice:
53
+
54
+ * **By words**: the ranking above, made by the MCP server from its own copy of the docs
55
+ * **By meaning**: the engine embeds the query and ranks the sections by the similarity of their vectors
56
+
57
+ Reciprocal Rank Fusion merges the two rankings. Each section scores the sum of 1 / (60 + its rank) over the rankings that hold it. Only the first 50 sections of each ranking count. A section that both rankings place high comes first, ahead of one that a single ranking finds. The required and excluded words filter both rankings.
58
+
59
+ Each hit then ends with the rankings that found it: `(keyword, semantic)`, `(keyword)` or `(semantic)`. The last line of the answer says the search was hybrid.
60
+
61
+ This is the hybrid search of vector databases such as ChromaDB, with no service to add. The engine already has the embedding model of semantic search. It keeps the vectors of the docs in its store, in `semantic/docs.json`, apart from the index of your notes.
62
+
63
+ The engine builds that index in the background:
64
+
65
+ * **When it starts**: after it updates the index of your notes
66
+ * **When the docs change**: after an upgrade or a new docs build, the engine embeds again only the sections whose text changed
67
+ * **When the model changes**: another provider, address or model starts a new index
68
+
69
+ The version of the docs is a hash of their sections, so any change to a section counts. The docs are public: embedding them sends nothing from your vault. With a paid provider, the docs cost about as much to index as 700 short notes.
70
+
71
+ `search_docs` ranks by words alone, as without semantic search, in these cases:
72
+
73
+ * **No semantic search**: it’s off, or the engine is older than hybrid search
74
+ * **The index is being built**: the first search after a change starts it
75
+ * **The engine runs another version**: its docs differ from those of the MCP server
76
+ * **The engine doesn’t answer**: `search_docs` asks it again a minute later
77
+
78
+ ## Read the docs from programs and editors
79
+
80
+ Programs and people read the same pages:
81
+
82
+ * **Programs**: the engine serves them, with a token like its other routes. `GET /datanotes/manual/` lists them, and `GET /datanotes/manual/guides/read-and-write/read-and-filter.md` returns one page. Add `?section=compare-values-with-operators` for one section.
83
+ * **Search by meaning**: `POST /datanotes/manual/search/` takes a `query` and returns the sections closest in meaning, with `ready: false` while the engine builds the index. Programs that use `datanotes/mcp` pass a `client` to `callDocsTool` to search like `datanotes mcp`.
84
+ * **People and editors**: the files themselves, in `node_modules/datanotes/dist/docs/`.
85
+
86
+ ```bash
87
+ curl -H "Authorization: Bearer your_token" \
88
+ http://127.0.0.1:27150/datanotes/manual/start/quick-start.md
89
+ ```
90
+
91
+ A program ranks the sections by meaning with the same [client](https://datanotes.dev/develop/client/):
92
+
93
+ ```ts
94
+ const ranked = await db.post("/manual/search/", {
95
+ query: "undo what an agent did",
96
+ });
97
+ ```
98
+
99
+ In a checkout of the repository, build the docs first with `cd docs && npm ci && npm run build`, which copies the pages to `dist/docs/`. Until then, the tools answer with that command.
@@ -0,0 +1,124 @@
1
+ # How agents work with your vault
2
+
3
+ > What an agent reads first, how it finds tables and views, why the engine validates its writes, and how the journal records and undoes what each agent did.
4
+
5
+ An agent connected through `datanotes mcp`, the Model Context Protocol (MCP) server of Datanotes, works with your vault the way a careful colleague works with a shared database. It reads the descriptions before it writes, writes only through operations the engine checks, and signs every change with its name in the journal. This page follows that path, from the instructions the agent receives to undoing what it did; the [prompts](https://datanotes.dev/guides/agents/prompts/) show it on concrete requests.
6
+
7
+ ## The agent starts with the server’s instructions
8
+
9
+ When an MCP client connects, `datanotes mcp` sends instructions with its list of tools. The engine’s part is short:
10
+
11
+ ```text
12
+ Database of Markdown notes: tables defined in metastore.yaml, a row is a note whose front matter key `table` names its table, and each front matter key is a column. Use list_tables / describe_table first, select_rows to read, insert_row / update_row / update_rows / batch to write (validated, journaled, undo with undo_migration). Change the schema only with its operations (create_table, add_column, alter_column, create_view, ...); never edit metastore.yaml. Every table and column needs a `description` of what it holds, or it is refused: descriptions are how agents and people understand the schema. Fill those describe_table lists in `missing_descriptions` with alter_table / alter_column. A body you write links the notes it mentions by name (a one-word name only with a capital, inside a sentence): when a write returns ambiguous_mentions, read the candidate notes and link the right one with update_row and autolink: false, or leave the text. Read the docs of this version with search_docs and read_docs.
13
+ ```
14
+
15
+ The tools are every [operation](https://datanotes.dev/reference/operations/) of the engine, plus three that [read the docs of the installed version](#the-agent-reads-the-docs-of-its-version). To give agents rules and tools of your own, serve [your own MCP server](https://datanotes.dev/develop/build-on-datanotes/#build-your-own-mcp-server) next to it. Each tool’s description says when to call it: `list_tables` asks the agent to call it first, and `add_column` asks the agent to read `describe_table` before it adds a column.
16
+
17
+ ## The agent finds tables by their descriptions
18
+
19
+ Before it reads or writes, the agent matches your words against what the tables say about themselves:
20
+
21
+ * **`list_tables`**: every table and view, with its `description`, `aliases` and `tags`, so “my watchlist” leads to `film`, whose aliases include `watchlist`
22
+ * **`describe_table`**: the columns, each with its type, `description`, `aliases`, limits and, for an enum, the meaning of each value, plus the table’s row count
23
+
24
+ The meanings come from `value_descriptions`, which you give when you create the column. On the film diary, `describe_table` answers this for `status`:
25
+
26
+ ```json
27
+ {
28
+ "name": "status",
29
+ "type": "enum",
30
+ "values": ["to watch", "watched"],
31
+ "value_descriptions": {
32
+ "to watch": "on my watchlist, not seen yet",
33
+ "watched": "I saw it at least once"
34
+ },
35
+ "ordered": true,
36
+ "default": "to watch"
37
+ }
38
+ ```
39
+
40
+ Every table and every column has a description, because the engine refuses one without. Agents and people understand the schema through them, so write each for a reader who never saw your notes. Good aliases help too: the better both are, the less the agent guesses. You set them with `create_table`, `add_column`, `alter_table`, `alter_column` and, for catalogs and schemas, `comment_on`.
41
+
42
+ A table created before descriptions were required may lack some. `describe_table` lists them in `missing_descriptions`, and the agent fills them with `alter_table` and `alter_column`. [Describe every table and column](https://datanotes.dev/guides/database-objects/tables/#describe-every-table-and-column) shows the rule in use.
43
+
44
+ ## The agent reads views first
45
+
46
+ A view is a stored query with a name, a description and aliases, such as `vw_to_watch`: the films whose status is `to watch`, shortest first. `list_tables` returns views next to tables, and `select_rows` reads a view like a table and can narrow it further. When a view answers the question, the agent reads it instead of rebuilding the filter, so the answer follows your definition of “to watch” and not the agent’s. [Views](https://datanotes.dev/guides/database-objects/views/) shows views with joins, groups and filters on the note they’re shown for.
47
+
48
+ ## The agent writes only through checked operations
49
+
50
+ Rows change through `insert_row`, `update_row`, `update_rows`, `delete_rows` and `batch`; the schema changes through operations such as `create_table`, `add_column` and `add_constraint`. The engine checks every one against the table’s column types, limits, links and CHECK constraints, and writes nothing when a check fails. Three arguments make an agent’s writes safer:
51
+
52
+ * **`dry_run`**: reports what would change, without writing
53
+ * **`batch`**: a group of writes that succeed or fail together, and that you undo as one
54
+ * **`expect`** on `update_row`: writes only if the row still has the values the agent read, so it doesn’t overwrite an edit you made in the meantime
55
+
56
+ The agent never edits `metastore.yaml`: a hand edit skips validation and has no undo. [Concurrent writes](https://datanotes.dev/develop/concurrency/) explains what happens when agents, programs and people write at the same time.
57
+
58
+ ## A refusal tells the agent what to fix
59
+
60
+ A refused write names the note, the column or rule, and the values the engine accepts. It ends with a hint on how to fix it and a link to the page that shows the fix. For a film with the status `seen`, the engine answers:
61
+
62
+ ```text
63
+ insert_row refused — nothing was written:
64
+ invalid datanotes/main/public/film/Sunrise.md: status must be one of: to watch (on my watchlist, not seen yet); watched (I saw it at least once)
65
+ Hint: Use one of the allowed values listed in the message, written exactly; describe_table shows what each one means. To add a value, alter_column the enum. Then call insert_row again (dry_run: true checks without writing).
66
+ Docs: read_docs("guides/database-objects/columns#describe-what-each-enum-value-means")
67
+ ```
68
+
69
+ The agent can correct the value and try again, or ask you. Every other error follows the same pattern: a misspelled table or column gets the closest real name, a wrong filter operator gets the one meant, and a tool argument the tool doesn’t take gets the tool’s arguments. [Read the engine’s errors](https://datanotes.dev/guides/agents/reading-errors/) shows them all. Before it writes a link, the agent can call `list_link_targets`, which returns the rows the link column accepts, such as the genres a film may link to. Over HTTP, a refused write comes back with status 422 and the reasons in the body.
70
+
71
+ ## The agent reads the docs of its version
72
+
73
+ `datanotes mcp` also serves this documentation, read from the installed `datanotes` package, with three tools: `list_docs`, `read_docs` and `search_docs`. The agent looks up an operation or an example without internet access, and the pages always match the version it talks to. [Read the docs offline](https://datanotes.dev/guides/agents/docs-offline/) shows the tools.
74
+
75
+ ## Every write names the agent that made it
76
+
77
+ The engine records an actor with every change. Through `datanotes mcp`, the actor comes from the MCP connection:
78
+
79
+ * **`harness`** and **`harness_version`**: the client’s name and version from the MCP handshake, such as `claude-code`
80
+ * **`session`**: the MCP session, shortened to 16 characters
81
+ * **`tool`**: the tool the agent called
82
+ * **`auth`**: `owner`, or the token’s name for a [named token](https://datanotes.dev/start/connect-agents/#give-each-agent-its-own-token)
83
+ * **`model`**: the model, when an MCP server reports it with `POST /datanotes/sessions/identify/`
84
+
85
+ `describe_history` shows the actor of each commit:
86
+
87
+ ```json
88
+ {
89
+ "table": "main.public.film",
90
+ "version": 10,
91
+ "operation": "UPDATE",
92
+ "migration_id": "20261009T215306-update_rows-update-film",
93
+ "actor": {
94
+ "session": "5d8c0456-3b15-40",
95
+ "harness": "claude-code",
96
+ "harness_version": "2.5.0",
97
+ "tool": "batch",
98
+ "auth": "owner"
99
+ },
100
+ "batch": "20261009T215306-batch-log-the-screening-of-metropoli"
101
+ }
102
+ ```
103
+
104
+ Three operations read this record:
105
+
106
+ * **`list_migrations`**: the journal, newest first, each operation with `by`, the agent’s name
107
+ * **`describe_history`**: the commits of one table or of every table, with the full actor; it needs history on
108
+ * **`list_changes`**: every row change with its actor, and with `kinds: ["tool_call"]` every tool call the MCP server reported, reads included
109
+
110
+ A script records its own actor with the `X-Datanotes-Actor` header or the client’s `actor` option, as the [HTTP API guide](https://datanotes.dev/develop/http-api/#record-who-makes-each-write) shows.
111
+
112
+ ## Undo what an agent did
113
+
114
+ Because the journal records every write, you can take back one operation or a whole run:
115
+
116
+ * **One operation**: `undo_migration` with its `migration_id`; for a `batch`, it reverts every write in it
117
+ * **A whole run**: `restore_database` with `before_migration` set to the agent’s first operation or batch; every table with history goes back to its state before that operation (run it with `dry_run` first)
118
+ * **One table**: `restore_table` to a version or a time
119
+
120
+ The journal records an undo too, so you can undo the undo. The engine refuses an undo that would overwrite a note you edited since, and lists the conflicts. [Restore a table or the database](https://datanotes.dev/guides/history/restore/) shows each operation with the engine’s answers, and the [prompts](https://datanotes.dev/guides/agents/prompts/#safety-and-history-prompts) show how to ask for them.
121
+
122
+ ## Give each agent only what it needs
123
+
124
+ The owner token can do everything. A [named token](https://datanotes.dev/guides/security/tokens/) limits an agent to the tables in `write`, to schema changes only with `ddl`, and to configuration, undo and restore only with `maintain`. The journal then names the token with each of its writes. [Connect agents](https://datanotes.dev/start/connect-agents/) shows how to create one and pass it to an MCP client.