sqlite-graphrag 1.0.90

Persistent GraphRAG memory for Claude Code, Codex, Cursor, and 24+ AI agents in a single 14.6 MiB Rust binary. LLM-only and one-shot in v1.0.78: every `remember` / `ingest` spawns a headless claude code or codex subprocess (OAuth, no MCP, no hooks). No daemon. No ONNX runtime. No model download. Graph-native retrieval with FTS5 + cosine + multi-hop traversal. OAuth-only enforcement: API keys ABORT the spawn.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
---
name: sqlite-graphrag
description:For persistent memory, GraphRAG, or long-term context in Claude Code, Codex, OpenCode, Cursor, Windsurf, AI agents. On: remember, save, recall, hybrid search, entity graph, SQLite memory, local RAG, LLM-only embedding, OAuth, BLOB-backed, migrate, embedding-dim, llm-parallelism, re-embed, force-reembed, OAuth-only, codex claude opencode hardening, mock LLM CI, ADR-0041 ADR-0051, OpenRouter Bedrock, --dry-run-backend, backend_invoked, preflight exit 16, --llm-backend codex claude opencode none auto, --llm-model gpt-5.4-mini claude-sonnet-4-6 opencode/big-pickle, headless fallback chain, v1.0.86-v1.0.90. KW: memory RAG GraphRAG SQLite one-shot OAuth offline persistent graph entity opencode codex claude.
---


## Current Version (v1.0.90)
- Current binary version: v1.0.90 (released 2026-06-22)
- Current schema version: v15 (after init or migrate on fresh database)
- This skill documents v1.0.86 through v1.0.90 features
- Earlier versions (v1.0.85.2 and below) are out of scope
- For older versions, CONSULT git history of this skill


## When This Skill Activates
- ACTIVATE when the user asks to remember, save, recall, retrieve, search, or persist anything across sessions
- ACTIVATE for long-term context, knowledge graph, GraphRAG, RAG, entity linking, memory management
- ACTIVATE when sqlite, sqlite-graphrag, embedding, FTS5, hybrid-search, or LLM memory is mentioned
- NEVER ACTIVATE for one-off ephemeral data, simple file I/O, or tasks unrelated to persistent context


## LLM Prompt Instruction Rules
- WHEN user says "remember this", "save this", "persist this" → EXECUTE `remember --force-merge` with `--graph-stdin` including curated entities and canonical relations
- WHEN user asks "what do you know about X", "recall X", "search for X" → EXECUTE `hybrid-search "X" --k 10 --json` FIRST, then EXPAND top results with `read --name <name> --json`
- WHEN user asks "how is X related to Y" → EXECUTE `graph traverse --from X --depth 2 --json` or `related X --hops 2 --json`
- WHEN user asks "deep research on X" → EXECUTE `deep-research "X" --k 20 --max-hops 3 --json`
- BEFORE creating ANY memory → EXECUTE `hybrid-search "<name or description>" --k 5 --json` to CHECK for duplicates; if found, USE `--force-merge` to UPDATE instead of creating new
- AFTER creating or updating a memory → VERIFY with `read --name <name> --json | jaq '{name, description, body_length}'`
- AFTER EVERY turn with new findings → EVALUATE whether to persist via `remember --force-merge`; if nothing new, DECLARE "No new findings to persist this turn"
- WHEN exit code is non-zero → READ JSON error envelope from stdout (`jaq '{code, message, error_class}'`), REPORT to user with remediation steps
- WHEN exit code 9 (duplicate) → RETRY with `--force-merge` flag
- WHEN exit code 19 (SHUTDOWN) → RETRY MANDATORY; partial work was discarded
- WHEN exit code 75 (singleton locked) → WAIT and retry; do NOT increase concurrency
- WHEN exit code 16 (preflight) → FIX the MCP config issue; do NOT bypass with `SKIP_PREFLIGHT`
- ALWAYS parse JSON output with `jaq` (NEVER `jq`)
- ALWAYS pass `--json` flag on every `sqlite-graphrag` invocation
- ALWAYS use `--llm-backend codex --llm-model gpt-5.4-mini` OR `--llm-backend claude --llm-model claude-sonnet-4-6` OR `--llm-backend opencode --llm-model opencode/big-pickle` for embedding commands
- ALWAYS use canonical relations ONLY: `applies-to`, `uses`, `depends-on`, `causes`, `fixes`, `contradicts`, `supports`, `follows`, `related`, `mentions`, `replaces`, `tracked-in`
- ALWAYS map non-canonical relations BEFORE persisting: `adds|creates → causes`, `implements → supports`, `blocks → contradicts`, `tested-by → related`, `part-of → applies-to`
- ALWAYS normalize entity names to kebab-case ASCII lowercase BEFORE passing to CLI
- NEVER use MCP Serena or `.md` memory files for persistence
- NEVER write MEMORY.md or any file-based memory
- NEVER start or reference daemon (removed in v1.0.76)
- NEVER pass `ANTHROPIC_API_KEY` or `OPENAI_API_KEY` in environment
- PREFER `remember --force-merge` over `edit` for updates to ensure re-indexation
- PREFER `--graph-stdin` with curated entities over `--enable-ner` for quality extraction
- LIMIT graph entities to domain-specific concepts: projects, tools, people, decisions, files, incidents
- REJECT generic words, pronouns, UUIDs, hashes, timestamps as entity names
```bash
# RULE: ALWAYS check duplicates BEFORE creating memory
sqlite-graphrag --llm-backend codex --llm-model gpt-5.4-mini \
  hybrid-search "auth JWT design" --k 5 --json | jaq '.results[].name'
# If found: UPDATE with --force-merge
sqlite-graphrag --llm-backend codex --llm-model gpt-5.4-mini \
  remember --name auth-design --type decision --force-merge \
  --description "JWT rotation strategy updated" --body-file auth.md

# RULE: ALWAYS use --graph-stdin with curated entities and canonical relations
echo '{"body":"JWT with 15-min expiry","entities":[{"name":"jwt","entity_type":"concept"},{"name":"auth-service","entity_type":"tool"}],"relationships":[{"source":"auth-service","target":"jwt","relation":"uses","strength":0.9}]}' \
  | sqlite-graphrag --llm-backend codex --llm-model gpt-5.4-mini \
    remember --name auth-design --type decision --description "JWT rotation" --graph-stdin --force-merge

# RULE: ALWAYS verify after write
sqlite-graphrag read --name auth-design --json | jaq '{name, description, body_length}'

# RULE: WHEN exit 9 (duplicate) → retry with --force-merge
sqlite-graphrag --llm-backend codex --llm-model gpt-5.4-mini \
  remember --name existing-mem --type note --description "x" --body "y" --force-merge
```


## Fundamental Principles
- INVOKE always as subprocess via `std::process::Command`
- READ stdout for JSON or NDJSON structured data
- READ stderr for tracing logs and human messages
- CHECK exit code BEFORE parsing stdout
- TRUST JSON contracts as SemVer-versioned API
- KNOW BUILD is LLM-only and one-shot; binary is 14.6 MiB stripped ELF (NOT 6 MB as in older docs)
- KNOW BUILD has NO daemon, NO ONNX runtime, NO model cache
- ENFORCE OAUTH-ONLY: spawn ABORTS exit 1 if `ANTHROPIC_API_KEY` is set
- ENFORCE OAUTH-ONLY: spawn ABORTS exit 1 if `OPENAI_API_KEY` is set
- ISOLATE NAMESPACE per project via `--namespace <ns>` or env
- KNOW NAMESPACE default is `global` when omitted
- NEVER expose the binary as MCP server or HTTP service
- NEVER write `.sqlite` file in parallel to the binary
- NEVER edit the `.sqlite` file from another tool

```bash
# INVOKE as subprocess, CHECK exit code, then PARSE stdout
sqlite-graphrag health --json
echo "exit=$?"

# READ structured stdout with jaq, NEVER parse stderr as data
sqlite-graphrag health --json | jaq '.integrity_ok'
```


## Quick Reference Card
- INIT first time: `sqlite-graphrag init --namespace <ns>`
- VERIFY health: `sqlite-graphrag health --json | jaq '.integrity_ok'`
- STORE memory (codex): `sqlite-graphrag --llm-backend codex --llm-model gpt-5.4-mini remember --name <kebab> --type note --description "x" --body "y"`
- STORE memory (claude): `sqlite-graphrag --llm-backend claude --llm-model claude-sonnet-4-6 remember --name <kebab> --type note --description "x" --body "y"`
- BATCH STORE (codex): `sqlite-graphrag --llm-backend codex --llm-model gpt-5.4-mini remember-batch --json < batch.ndjson`
- BATCH STORE (claude): `sqlite-graphrag --llm-backend claude --llm-model claude-sonnet-4-6 remember-batch --json < batch.ndjson`
- STORE memory (opencode): `sqlite-graphrag --llm-backend opencode --llm-model opencode/big-pickle remember --name <kebab> --type note --description "x" --body "y"`
- BATCH STORE (opencode): `sqlite-graphrag --llm-backend opencode --llm-model opencode/big-pickle remember-batch --json < batch.ndjson`
- EDIT memory (codex): `sqlite-graphrag --llm-backend codex --llm-model gpt-5.4-mini edit --name <n> --body-file <path>`
- EDIT memory (claude): `sqlite-graphrag --llm-backend claude --llm-model claude-sonnet-4-6 edit --name <n> --body-file <path>`
- EDIT memory (opencode): `sqlite-graphrag --llm-backend opencode --llm-model opencode/big-pickle edit --name <n> --body-file <path>`
- INGEST folder (codex): `sqlite-graphrag --llm-backend codex --llm-model gpt-5.4-mini ingest ./docs --mode codex --recursive --pattern "*.md" --json`
- INGEST folder (claude): `sqlite-graphrag --llm-backend claude --llm-model claude-sonnet-4-6 ingest ./docs --mode claude-code --recursive --pattern "*.md" --json`
- INGEST folder (opencode): `sqlite-graphrag --llm-backend opencode --llm-model opencode/big-pickle ingest ./docs --mode opencode --recursive --pattern "*.md" --json`
- SEARCH semantic (codex): `sqlite-graphrag --llm-backend codex --llm-model gpt-5.4-mini recall "query" --k 5 --json`
- SEARCH semantic (claude): `sqlite-graphrag --llm-backend claude --llm-model claude-sonnet-4-6 recall "query" --k 5 --json`
- SEARCH semantic (opencode): `sqlite-graphrag --llm-backend opencode --llm-model opencode/big-pickle recall "query" --k 5 --json`
- SEARCH hybrid (codex): `sqlite-graphrag --llm-backend codex --llm-model gpt-5.4-mini hybrid-search "query" --k 10 --rrf-k 60 --json`
- SEARCH hybrid (claude): `sqlite-graphrag --llm-backend claude --llm-model claude-sonnet-4-6 hybrid-search "query" --k 10 --rrf-k 60 --json`
- SEARCH hybrid (opencode): `sqlite-graphrag --llm-backend opencode --llm-model opencode/big-pickle hybrid-search "query" --k 10 --rrf-k 60 --json`
- DEEP RESEARCH (codex): `sqlite-graphrag --llm-backend codex --llm-model gpt-5.4-mini deep-research "question" --k 20 --max-hops 3 --json`
- DEEP RESEARCH (claude): `sqlite-graphrag --llm-backend claude --llm-model claude-sonnet-4-6 deep-research "question" --k 20 --max-hops 3 --json`
- DEEP RESEARCH (opencode): `sqlite-graphrag --llm-backend opencode --llm-model opencode/big-pickle deep-research "question" --k 20 --max-hops 3 --json`
- ENRICH graph (codex): `sqlite-graphrag --llm-backend codex --llm-model gpt-5.4-mini enrich --operation re-embed --limit 100 --resume --json`
- ENRICH graph (claude): `sqlite-graphrag --llm-backend claude --llm-model claude-sonnet-4-6 enrich --operation memory-bindings --mode claude-code --json`
- ENRICH graph (opencode): `sqlite-graphrag --llm-backend opencode --llm-model opencode/big-pickle enrich --operation re-embed --limit 100 --resume --json`
- READ memory (no backend): `sqlite-graphrag read --name <n> --json`
- LIST memories (no backend): `sqlite-graphrag list --type decision --limit 50 --json`
- GRAPH traversal (no backend): `sqlite-graphrag graph traverse --from <entity> --depth 2 --json`
- GRAPH stats (no backend): `sqlite-graphrag graph stats --json`
- LINK entities (no backend): `sqlite-graphrag link --from <a> --to <b> --relation uses --create-missing --json`
- HARD DELETE (no backend): `sqlite-graphrag forget --name <n>` then `sqlite-graphrag purge --retention-days 30 --yes`


## Initialization, Health, and Global Config
- RUN `sqlite-graphrag init --namespace <ns>` on first use
- RUN `health --json` to verify `integrity_ok` and `schema_ok`
- VERIFY `schema_version >= 15` after `init` or `migrate`
- RUN `migrate --json` after each binary upgrade
- USE `migrate --to-llm-only --drop-vec-tables --json` for v1.0.74 or v1.0.75 databases
- USE `migrate --rehash --json` to repair V002 SipHasher13 checksum drift
- USE `migrate --dry-run --json` to PREVIEW pending migrations without applying
- TREAT exit code 10 as database error; RUN `vacuum` and `health`
- TREAT exit code 15 as busy; WIDEN `--wait-lock`
- TREAT exit code 16 as preflight failure (v1.0.87+); FIX MCP config or SET `SQLITE_GRAPHRAG_SKIP_PREFLIGHT=1`
- ABORT pipeline when `integrity_ok` returns `false`
- RUN `optimize --json` to refresh planner stats; response includes `fts_rebuilt`
- USE `optimize --skip-fts --json` when FTS5 was recently rebuilt
- RUN `fts rebuild --json` when `health.fts_degraded` is true
- INSPECT `wal_size_mb` in `health` for fragmentation
- VERIFY `journal_mode` equals `wal` in production
- USE `debug-schema --json` for troubleshooting schema drift
- PASS `--db <PATH>` to override database location (now accepted on `embedding status/list/abandon`, `pending list/show` since v1.0.89, ADR-0049)
- PASS `--namespace <NS>` on `health` since v1.0.89 to filter counts to one namespace
- SET `SQLITE_GRAPHRAG_DB_PATH` env for persistent config
- SET `SQLITE_GRAPHRAG_NAMESPACE` env for persistent namespace
- PASS `--lang en` or `--lang pt` to force stderr language
- PASS `--tz America/Sao_Paulo` to localize timestamps
- SET `SQLITE_GRAPHRAG_DISPLAY_TZ` env for persistent timezone
- SET `SQLITE_GRAPHRAG_LOG_FORMAT=json` for log aggregators
- USE `-v` for info, `-vv` for debug, `-vvv` for trace
- ENABLE `SQLITE_GRAPHRAG_LOW_MEMORY=1` in constrained containers
- SET `SQLITE_GRAPHRAG_EMBEDDING_DIM` env in range `[8, 4096]` (default 64 MRL)
- SET `SQLITE_GRAPHRAG_STRICT_ENV_CLEAR=1` for compliance mode (ADR-0041)
- SET `SQLITE_GRAPHRAG_IGNORE_SHUTDOWN=1` ONLY for CI test harnesses
- KNOW VALID `--type` values: `user`, `feedback`, `project`, `reference`, `decision`, `incident`, `skill`, `document`, `note`
- KNOW GLOBAL flags: `--db`, `--namespace`, `--lang`, `--tz`, `--json`, `--low-memory`, `--max-concurrency N`, `--wait-lock SECS`, `--llm-parallelism N`, `--llm-backend claude|codex|opencode|none|auto[,fallback...]`, `--llm-model <MODEL>`, `--dry-run-backend`, `--llm-fallback-mode <claude|codex|opencode>`, `--graceful-shutdown-secs N`, `--claude-binary <PATH>`, `--codex-binary <PATH>`, `--opencode-binary <PATH>`, `--opencode-model <MODEL>`, `--opencode-timeout <SECS>`, `--skip-embedding-on-failure`

```bash
# INIT a project namespace
sqlite-graphrag init --namespace myproject

# VERIFY integrity and schema version
sqlite-graphrag health --json | jaq '{integrity_ok, schema_ok, schema_version}'

# MIGRATE after binary upgrade, then PREVIEW first
sqlite-graphrag migrate --dry-run --json | jaq '.would_apply[]? | {name, version}'
sqlite-graphrag migrate --json

# OPTIMIZE planner stats and rebuild FTS5 if degraded
sqlite-graphrag optimize --json | jaq '{fts_rebuilt}'
sqlite-graphrag fts rebuild --json

# OVERRIDE database location and namespace per invocation
sqlite-graphrag --db /data/prod.sqlite --namespace prod health --json | jaq '.counts'
```


## Architecture Contract (OAuth/LLM/One-Shot)
- KNOW BUILD is LLM-only; default build has NO `fastembed`, `ort`, `ndarray`, `tokenizers`, `huggingface-hub`, `sqlite-vec`, `GLiNER`
- KNOW BUILD removed `daemon` subcommand entirely (ADR-0021)
- KNOW COSINE similarity is pure Rust in `src/similarity.rs`
- KNOW COSINE runs over BLOB-backed `memory_embeddings`, `entity_embeddings`, `chunk_embeddings`
- KNOW SCHEMA v15 after `init` or `migrate` on fresh database
- KNOW MIGRATION V013 drops `vec_memories`, `vec_entities`, `vec_chunks` virtual tables
- KNOW MIGRATION V014 creates `pending_memories` checkpoint table
- KNOW MIGRATION V015 creates `pending_embeddings` retry table
- ENFORCE OAUTH-ONLY: `ANTHROPIC_API_KEY` ABORTS spawn with `AppError::Validation` (ADR-0011)
- ENFORCE OAUTH-ONLY: `OPENAI_API_KEY` ABORTS spawn with `AppError::Validation` (ADR-0011)
- KNOW OAUTH-ONLY: both API keys EXCLUDED from env-clear whitelist
- KNOW OAUTH-ONLY: `--bare` flag REMOVED from all executable paths
- KNOW OAUTH-ONLY: 7 hardening flags ALWAYS passed to `claude -p`
- KNOW HARDENING flags for claude: `--model claude-sonnet-4-6 --strict-mcp-config --mcp-config '{}' --settings '{"hooks":{}}' --dangerously-skip-permissions --output-schema`
- KNOW HARDENING flags for codex: `--model gpt-5.5 --json --output-schema --ephemeral --skip-git-repo-check --sandbox read-only --ignore-user-config --ignore-rules -c mcp_servers='{}' --ask-for-approval never`
- KNOW ADR-0041 v1.0.83: `ANTHROPIC_AUTH_TOKEN` PRESERVED for Anthropic-compatible providers
- KNOW ADR-0041 v1.0.83: `ANTHROPIC_BASE_URL` PRESERVED for custom endpoints
- KNOW ADR-0041 v1.0.83: `OPENAI_BASE_URL` PRESERVED for OpenAI-compatible endpoints
- KNOW ADR-0041 v1.0.83: `CLAUDE_CODE_ENTRYPOINT`, `DISABLE_TELEMETRY`, `OTEL_EXPORTER_OTLP_ENDPOINT` PRESERVED
- KNOW ADR-0041 v1.0.83: supported providers include OpenRouter, AWS Bedrock, corporate gateways
- KNOW EMBEDDING DIM precedence: `SQLITE_GRAPHRAG_EMBEDDING_DIM` env then `schema_meta.dim` then default 64 MRL
- KNOW EMBEDDING DIM adapts batch size: base 8 chunks / 25 entity names at dim 64
- USE MOCK LLM CLI for CI: prepend `tests/mock-llm` to PATH
- USE SHUTDOWN bypass recipe: `PATH=tests/mock-llm:$PATH SQLITE_GRAPHRAG_IGNORE_SHUTDOWN=1 setsid -w timeout 120 sqlite-graphrag …`
- NEVER install with `--features embedding-legacy` or `--features ner-legacy`
- NEVER depend on daemon or `--bare` flag (REMOVED in v1.0.76 and v1.0.79)
- NEVER mix `vec_memories` queries (REMOVED in v1.0.76)
- NEVER call `migrate --to-llm-only` without `--drop-vec-tables` safety guard

```bash
# CONFIRM OAuth-only enforcement: setting an API key ABORTS the spawn
ANTHROPIC_API_KEY=sk-test sqlite-graphrag init 2>&1 || echo "exit=$?"

# CONFIRM the build is LLM-only (no vec tables, BLOB-backed embeddings)
sqlite-graphrag debug-schema --json | jaq '.tables'

# DROP vec tables ONLY with the safety guard present
sqlite-graphrag migrate --to-llm-only --drop-vec-tables --json
```


## Backend LLM Selection — Codex and Claude Headless
- NOTE: for OpenCode backend examples and env vars, SEE the dedicated section "Backend LLM Selection — OpenCode Headless (v1.0.90)" below
- MANDATE selecting the embedding backend explicitly via `--llm-backend` for every embedding command
- PASS `--llm-backend codex` to spawn OpenAI Codex CLI headless for embedding and extraction
- PASS `--llm-backend claude` to spawn Claude Code CLI headless via `embed_via_claude_local` (zero-token, OAuth-compatible)
- PASS `--llm-backend codex,claude` for codex-first with claude fallback (ADR-0038)
- PASS `--llm-backend codex,claude,none` to fall back to null embedding when both backends fail
- PASS `--llm-backend none` ONLY when you DEMAND zero embedding and accept a non-searchable memory
- KNOW DEFAULT `--llm-backend` is `codex`
- PASS `--llm-model <MODEL>` to select the model for the active backend (v1.0.89, ADR-0050)
- KNOW DEFAULT model for codex backend is `gpt-5.5`; for claude backend is `claude-sonnet-4-6`
- USE `--llm-model gpt-5.4-mini` for fast, cheap codex embedding; `--llm-model gpt-5.5` for highest codex quality
- USE `--llm-model claude-sonnet-4-6` for balanced claude embedding
- PASS `--llm-fallback-mode <claude|codex>` to swap backend mid-job on rate-limit
- PASS `--dry-run-backend` to plan backend operation without executing it (idempotent preview)
- PARSE `backend_invoked` field in every embedding envelope to CONFIRM which backend actually ran
- PASS `--codex-binary <PATH>` to override codex binary location (v1.0.89, ADR-0050)
- PASS `--claude-binary <PATH>` to override claude binary location (propagated via set_var since v1.0.89)
- SET env `SQLITE_GRAPHRAG_LLM_BACKEND` for persistent backend selection
- SET env `SQLITE_GRAPHRAG_LLM_MODEL` for persistent model selection
- SET env `SQLITE_GRAPHRAG_CODEX_BINARY` for persistent codex binary path
- SET env `SQLITE_GRAPHRAG_CODEX_EMBED_MODEL` for persistent codex embedding model
- USE `LlmEmbeddingBuilder` to compose embedding pipeline: `with_backend(Codex).or_fallback(Claude).or_skip()`
- RUN `codex login` after upgrade to refresh OAuth refresh token (2026-06-14 incident)
- RUN `claude` OAuth refresh when claude backend reports stale OAuth
- NEVER pass `ANTHROPIC_API_KEY` or `OPENAI_API_KEY` with either backend; spawn ABORTS exit 1

```bash
# CODEX HEADLESS — explicit backend and fast model
sqlite-graphrag --llm-backend codex --llm-model gpt-5.4-mini \
  remember --name auth-design --type decision \
  --description "JWT rotation strategy" --body "15-min expiry with refresh"

# CLAUDE CODE HEADLESS — explicit backend and balanced model
sqlite-graphrag --llm-backend claude --llm-model claude-sonnet-4-6 \
  remember --name auth-design --type decision \
  --description "JWT rotation strategy" --body "15-min expiry with refresh"

# CODEX EXCLUSIVE — fail on error, no fallback
sqlite-graphrag --llm-backend codex --llm-model gpt-5.4-mini \
  remember --name auth-design --type decision \
  --description "JWT rotation strategy" --body-file auth.md

# CODEX-FIRST WITH CLAUDE FALLBACK
sqlite-graphrag --llm-backend codex,claude --llm-model gpt-5.5 \
  remember --name auth-design --type decision \
  --description "JWT rotation strategy" --body-file auth.md

# CODEX-FIRST, CLAUDE FALLBACK, THEN NULL EMBEDDING (last resort)
sqlite-graphrag --llm-backend codex,claude,none --skip-embedding-on-failure \
  remember --name auth-design --type decision \
  --description "JWT rotation strategy" --body-file auth.md

# DRY-RUN BACKEND — plan without executing, confirm backend choice
sqlite-graphrag --llm-backend codex --llm-model gpt-5.4-mini --dry-run-backend \
  remember --name preview --type note --description x --body y | jaq '.backend_invoked'

# SWAP BACKEND MID-JOB ON RATE LIMIT
sqlite-graphrag --llm-backend codex --llm-fallback-mode claude \
  enrich --operation re-embed --limit 200 --resume --json

# CONFIRM EFFECTIVE BACKEND from the envelope
sqlite-graphrag --llm-backend codex --llm-model gpt-5.4-mini \
  recall "auth" --k 3 --json | jaq '.backend_invoked'
```

```bash
# PERSISTENT codex backend via env vars
export SQLITE_GRAPHRAG_LLM_BACKEND=codex
export SQLITE_GRAPHRAG_LLM_MODEL=gpt-5.4-mini
export SQLITE_GRAPHRAG_CODEX_EMBED_MODEL=gpt-5.4-mini

# PERSISTENT claude backend via env vars
export SQLITE_GRAPHRAG_LLM_BACKEND=claude
export SQLITE_GRAPHRAG_LLM_MODEL=claude-sonnet-4-6

# OVERRIDE codex binary path persistently
export SQLITE_GRAPHRAG_CODEX_BINARY=/usr/local/bin/codex

# OVERRIDE claude binary path per invocation
sqlite-graphrag --claude-binary /usr/local/bin/claude --llm-backend claude \
  remember --name x --type note --description "x" --body "y"

# OVERRIDE codex binary path per invocation
sqlite-graphrag --codex-binary /usr/local/bin/codex --llm-backend codex \
  remember --name x --type note --description "x" --body "y"
```


## Backend LLM Selection — OpenCode Headless (v1.0.90)
- PASS `--llm-backend opencode` to spawn OpenCode CLI headless for embedding and extraction
- PASS `--llm-backend codex,claude,opencode,none` for full fallback chain
- KNOW opencode is the THIRD priority in auto-detect: codex > claude > opencode > none
- KNOW opencode uses its OWN auth system (NOT OAuth); `ANTHROPIC_API_KEY` and `OPENAI_API_KEY` are NOT required
- KNOW opencode has NO `--output-schema` or `--json-schema` flag; structured output relies on role-setting prompts + JSON parsing
- KNOW opencode NDJSON output has 3 event types: `step_start`, `text` (`.part.text`), `step_finish`
- SET env `SQLITE_GRAPHRAG_OPENCODE_BINARY` for persistent binary path override
- SET env `SQLITE_GRAPHRAG_OPENCODE_MODEL` for persistent model selection (default: `opencode/big-pickle`)
- SET env `SQLITE_GRAPHRAG_OPENCODE_EMBED_MODEL` for persistent embedding model
- SET env `SQLITE_GRAPHRAG_OPENCODE_TIMEOUT` for persistent timeout (default: 300s)
- PASS `--opencode-binary <PATH>` to override binary location per invocation
- PASS `--opencode-model <MODEL>` for ingest/enrich model selection
- PASS `--opencode-timeout <SECONDS>` for ingest/enrich timeout
- PASS `--mode opencode` for ingest and enrich pipelines
- KNOW opencode embedding uses role-setting prompt "You are an embedding function" to produce real numeric vectors
- KNOW `SQLITE_GRAPHRAG_OPENCODE_MODEL` does NOT fall back to `SQLITE_GRAPHRAG_LLM_MODEL` (cross-contamination fix v1.0.90 audit)
- KNOW `propagate_opencode_env()` forwards OPENCODE_*, OPENROUTER_*, XDG_*, LANG, TERM, USER, LOGNAME, TMPDIR to subprocess
- KNOW opencode free models: `opencode/big-pickle`, `opencode/deepseek-v4-flash-free`, `opencode/mimo-v2.5-free`, `opencode/nemotron-3-ultra-free`, `opencode/north-mini-code-free`
- KNOW minimum opencode version: 1.17.0

```bash
# OPENCODE HEADLESS — explicit backend and free model
sqlite-graphrag --llm-backend opencode \
  remember --name auth-design --type decision \
  --description "JWT rotation strategy" --body "15-min expiry with refresh"

# OPENCODE WITH SPECIFIC MODEL
sqlite-graphrag --llm-backend opencode --llm-model opencode/deepseek-v4-flash-free \
  remember --name auth-design --type decision \
  --description "JWT rotation strategy" --body-file auth.md

# FULL FALLBACK CHAIN: codex first, then claude, then opencode, then none
sqlite-graphrag --llm-backend codex,claude,opencode,none --skip-embedding-on-failure \
  remember --name auth-design --type decision \
  --description "JWT rotation strategy" --body-file auth.md

# INGEST WITH OPENCODE EXTRACTION
sqlite-graphrag ingest ./docs --mode opencode --recursive --json

# INGEST WITH SPECIFIC OPENCODE MODEL AND TIMEOUT
sqlite-graphrag ingest ./docs --mode opencode --opencode-model opencode/mimo-v2.5-free \
  --opencode-timeout 600 --recursive --json

# ENRICH WITH OPENCODE
sqlite-graphrag enrich --operation memory-bindings --mode opencode --json

# DRY-RUN WITH OPENCODE BACKEND
sqlite-graphrag --llm-backend opencode --dry-run-backend \
  remember --name preview --type note --description x --body y | jaq '.backend_invoked'
```

```bash
# PERSISTENT opencode backend via env vars
export SQLITE_GRAPHRAG_LLM_BACKEND=opencode
export SQLITE_GRAPHRAG_OPENCODE_MODEL=opencode/big-pickle
export SQLITE_GRAPHRAG_OPENCODE_EMBED_MODEL=opencode/big-pickle

# OVERRIDE opencode binary path
export SQLITE_GRAPHRAG_OPENCODE_BINARY=~/.opencode/bin/opencode
```


## CRUD — Write Path (remember, remember-batch, ingest)
- INVOKE `remember --name <kebab> --type <kind> --description <text> --body-stdin` for long bodies
- INVOKE `remember --name <kebab> --body-file <path>` to avoid shell escaping
- INVOKE `remember --name <kebab> --body <text>` for short bodies
- PASS `--force-merge` for idempotent updates and soft-deleted restoration
- PASS `--clear-body` to wipe body during `--force-merge` update
- PASS `--dry-run` to validate inputs without persisting
- PASS `--max-rss-mb <MiB>` to abort when RSS exceeds threshold (default 8192)
- RESPECT 512000 bytes and 512 chunks limit per body
- INVOKE `remember --graph-stdin` to attach `{body, entities, relationships}` in single JSON
- PASS entities as `[{name, entity_type}]` with kebab-case ASCII
- PASS relationships as `[{source, target, relation, strength}]` where `strength ∈ [0.0, 1.0]`
- USE `--enable-ner` for URL-regex entity extraction (URL-regex ONLY since v1.0.79)
- NEVER send both `entity_type` and `type` in same JSON object
- NEVER use `--gliner-variant` (no-op since v1.0.79)
- INVOKE `remember-batch` for 10+ memories via NDJSON stdin
- EXPECT per-item event: `name`, `status ∈ {created, updated, skipped, failed}`, `memory_id?`, `error?`, `elapsed_ms`
- EXPECT summary line: `total`, `created`, `updated`, `skipped`, `failed`, `elapsed_ms`
- INVOKE `ingest <DIR> --recursive --pattern "*.md"` to import directory
- PASS `--type <kind>` to apply same type to all ingested files
- RESPECT `--max-files 10000` cap as all-or-nothing validation
- USE `--fail-fast` to stop at first per-file failure
- USE `--max-name-length N` to override 60-char name truncation
- EXPECT NDJSON per-file line: `file`, `name`, `status`, `truncated`, `original_name?`, `memory_id?`, `action?`, `error?`
- EXPECT summary line: `files_total`, `files_succeeded`, `files_failed`, `files_skipped`, `elapsed_ms`
- USE `--llm-parallelism N` on `ingest` (default 2, clamp [1, 32])
- DISTINGUISH `--max-concurrency N` (CLI fan-out) from `--ingest-parallelism N` (per-file extract+embed)
- USE `--auto-describe` (default true since v1.0.89) to extract description from first significant body line; OPT OUT via `--no-auto-describe`
- INVOKE `ingest --mode claude-code` for LLM-curated entity extraction
- INVOKE `ingest --mode codex` for OpenAI Codex-curated extraction
- EXPECT claude-code events: `entities` count, `rels` count, `cost_usd` (Omit cost for OAuth)
- USE `--resume` to continue from queue DB after interruption
- USE `--retry-failed` to retry only failed files
- NEVER use `fd | xargs remember`; INVOKE `ingest` instead
- NEVER mix `--body`, `--body-file`, `--body-stdin`, `--graph-stdin` in single invocation
- NEVER pass empty body with no entities via `--graph-stdin` (exit 1 since v1.0.54)
- NEVER use `--force-merge` in `ingest` (exclusive to `remember`)
- NEVER mix different memory types in same `ingest` invocation

```bash
# REMEMBER via stdin (codex backend) — long body
sqlite-graphrag --llm-backend codex --llm-model gpt-5.4-mini \
  remember --name design-auth --type decision \
  --description "auth JWT" --body-stdin < doc.md

# REMEMBER via stdin (claude backend) — long body
sqlite-graphrag --llm-backend claude --llm-model claude-sonnet-4-6 \
  remember --name design-auth --type decision \
  --description "auth JWT" --body-stdin < doc.md

# REMEMBER via stdin (opencode backend) — long body
sqlite-graphrag --llm-backend opencode --llm-model opencode/big-pickle \
  remember --name design-auth --type decision \
  --description "auth JWT" --body-stdin < doc.md

# REMEMBER from file with idempotent merge (codex)
sqlite-graphrag --llm-backend codex --llm-model gpt-5.4-mini \
  remember --name doc-readme --type document \
  --description "import" --body-file README.md --force-merge

# REMEMBER from file with idempotent merge (claude)
sqlite-graphrag --llm-backend claude --llm-model claude-sonnet-4-6 \
  remember --name doc-readme --type document \
  --description "import" --body-file README.md --force-merge

# REMEMBER from file with idempotent merge (opencode)
sqlite-graphrag --llm-backend opencode --llm-model opencode/big-pickle \
  remember --name doc-readme --type document \
  --description "import" --body-file README.md --force-merge

# ATTACH curated graph in a single JSON via --graph-stdin (codex)
echo '{"body":"JWT rotation","entities":[{"name":"jwt","entity_type":"concept"}],"relationships":[{"source":"jwt","target":"auth-service","relation":"uses","strength":0.8}]}' \
  | sqlite-graphrag --llm-backend codex --llm-model gpt-5.4-mini \
    remember --name spec-x --type reference --description "spec" --graph-stdin

# DRY-RUN to validate inputs without persisting
sqlite-graphrag remember --name spec-x --type reference --description "spec" --body "x" --dry-run

# REMEMBER-BATCH 10+ memories via NDJSON (codex backend)
sqlite-graphrag --llm-backend codex --llm-model gpt-5.4-mini \
  remember-batch --force-merge --json < batch.ndjson | jaq -c 'select(.summary != true)'

# REMEMBER-BATCH 10+ memories via NDJSON (claude backend)
sqlite-graphrag --llm-backend claude --llm-model claude-sonnet-4-6 \
  remember-batch --force-merge --json < batch.ndjson | jaq -c 'select(.summary != true)'

# REMEMBER-BATCH 10+ memories via NDJSON (opencode backend)
sqlite-graphrag --llm-backend opencode --llm-model opencode/big-pickle \
  remember-batch --force-merge --json < batch.ndjson | jaq -c 'select(.summary != true)'

# INGEST a directory with Codex extraction
sqlite-graphrag --llm-backend codex --llm-model gpt-5.4-mini \
  ingest ./docs --mode codex --recursive --pattern "*.md" --json

# INGEST a directory with Claude Code extraction
sqlite-graphrag --llm-backend claude --llm-model claude-sonnet-4-6 \
  ingest ./docs --mode claude-code --recursive --pattern "*.md" --json

# INGEST a directory with OpenCode extraction
sqlite-graphrag --llm-backend opencode --llm-model opencode/big-pickle \
  ingest ./docs --mode opencode --recursive --pattern "*.md" --json

# INGEST with auto-describe and parallelism, RESUME after interruption (codex)
sqlite-graphrag --llm-backend codex --llm-model gpt-5.4-mini \
  ingest ./corpus --mode codex --recursive --auto-describe \
  --llm-parallelism 2 --max-files 1000 --resume --json | jaq -c 'select(.status == "done")'
```


## CRUD — Read, History, Update
- INVOKE `read --name <kebab>` for O(1) fetch by name
- INVOKE `read --id <N>` for direct lookup by memory_id
- INVOKE `read --with-graph` to include linked entities and relationships
- PARSE fields `body`, `description`, `created_at_iso`, `updated_at_iso`
- TREAT exit code 4 as memory not found in namespace
- EXPECT v1.0.85 G55 bilingual message: `--lang en` emits `Memory not found`, `--lang pt` emits `Memória não encontrada`
- INVOKE `list --type <kind> --limit N` to filter by memory type
- USE `--offset N` to paginate large datasets
- USE `--include-deleted` to include soft-deleted memories
- EXPECT `list` response: `items[]`, `total_count`, `truncated`, `body_length`, `elapsed_ms`
- INVOKE `history --name <n>` to list versions in reverse chronological order
- USE `--diff` to include character diff stats between versions
- EXPECT `versions[]`: `version`, `created_at_iso`, `body_length`, `deleted?`, `changes?`
- INVOKE `edit --name <n> --body-file <path>` to update body from file
- USE `--description <text>` to update description only
- USE `--type <kind>` to change memory type without recreating (v1.0.66)
- USE `--force-reembed` to regenerate embedding without body change (v1.0.79)
- USE `--llm-parallelism N` on `edit` (default 4, clamp [1, 32])
- USE `--expected-updated-at <ts>` for optimistic locking
- TREAT exit code 3 as optimistic lock conflict; RELOAD `read --json` and RETRY
- INVOKE `rename --from <old> --to <new>` to rename preserving history
- TREAT exit 1 when new name equals old name (v1.0.64)
- INVOKE `restore --name <n> --version <N>` to restore old version
- OMIT `--version` to select last non-restore version automatically
- EXPECT each `edit` or `restore` to create new immutable version
- EXPECT FTS5 desync fix applied (v1.0.56) so edited memories are immediately findable
- NEVER skip optimistic locking in concurrent pipelines

```bash
# READ memory by name (no backend needed for read)
sqlite-graphrag read --name design-auth --json | jaq '{description, body_length}'

# READ memory by id, with linked graph
sqlite-graphrag read --id 42 --with-graph --json | jaq '{name, entities, relationships}'

# LIST decisions, paginated
sqlite-graphrag list --type decision --limit 50 --offset 0 --json | jaq '.items[].name'

# INSPECT version history with character diffs
sqlite-graphrag history --name design-auth --diff --json | jaq '.versions[] | {version, changes}'

# EDIT body from file (codex backend re-embeds)
sqlite-graphrag --llm-backend codex --llm-model gpt-5.4-mini \
  edit --name design-auth --body-file ./revised.md \
  --expected-updated-at "2026-04-19T12:00:00Z"

# EDIT body from file (claude backend re-embeds)
sqlite-graphrag --llm-backend claude --llm-model claude-sonnet-4-6 \
  edit --name design-auth --body-file ./revised.md

# EDIT body from file (opencode backend re-embeds)
sqlite-graphrag --llm-backend opencode --llm-model opencode/big-pickle \
  edit --name design-auth --body-file ./revised.md

# FORCE-REEMBED without body change (codex)
sqlite-graphrag --llm-backend codex --llm-model gpt-5.4-mini \
  edit --name design-auth --force-reembed --json | jaq '.backend_invoked'

# FORCE-REEMBED without body change (opencode)
sqlite-graphrag --llm-backend opencode --llm-model opencode/big-pickle \
  edit --name design-auth --force-reembed --json | jaq '.backend_invoked'

# RENAME preserving history (no backend needed)
sqlite-graphrag rename --from old-name --to new-name --json

# RESTORE an old version (no backend needed; auto re-embed runs)
sqlite-graphrag restore --name design-auth --version 2 --json
```


## CRUD — Delete (forget, purge, unlink, prune, cleanup)
- INVOKE `forget --name <n>` for reversible soft-delete
- EXPECT `forget` to disappear from `recall` and `list` outputs
- TREAT exit 4 as memory absent (since v1.0.52)
- INVOKE `restore` to reverse soft-delete before any purge
- INVOKE `purge --retention-days <N> --yes` for hard delete
- USE `--dry-run` first to audit count
- EXPECT default retention 90 days for soft-deleted memories
- INVOKE `unlink --from <a> --to <b> --relation <type>` for targeted edge removal
- OMIT `--relation` to remove all edges between `--from` and `--to`
- USE `--entity <name> --all` to bulk-remove all relationships for entity
- TREAT exit code 4 as nonexistent edge
- INVOKE `prune-relations --relation <type> --yes` for bulk relationship deletion
- USE `--show-entities` with `--dry-run` to list affected entity names
- INVOKE `cleanup-orphans --dry-run` to audit orphaned entities
- APPLY `--yes` in automated pipelines for `cleanup-orphans`
- INVOKE `prune-ner --entity <n>` to remove NER bindings for specific entity
- INVOKE `prune-ner --all --yes` to remove all NER bindings in namespace
- USE standard pipeline: bulk `forget` then `cleanup-orphans --yes` then `vacuum --json`
- NEVER delete manually via `sqlite3` shell; INVOKE binary commands only

```bash
# FORGET (soft-delete, reversible)
sqlite-graphrag forget --name old-design --json

# PURGE physically after auditing count first
sqlite-graphrag purge --retention-days 30 --yes --dry-run
sqlite-graphrag purge --retention-days 30 --yes

# UNLINK a specific edge
sqlite-graphrag unlink --from jwt --to auth-service --relation uses --json

# UNLINK ALL edges between two entities
sqlite-graphrag unlink --from jwt --to auth-service --json

# BULK-REMOVE all relationships for one entity
sqlite-graphrag unlink --entity jwt --all --json

# PRUNE noisy relations in bulk, preview affected entities first
sqlite-graphrag prune-relations --relation mentions --show-entities --dry-run --json
sqlite-graphrag prune-relations --relation mentions --yes --json

# CLEANUP orphaned entities, then vacuum
sqlite-graphrag cleanup-orphans --yes --json
sqlite-graphrag vacuum --json
```


## Entity Graph (link, graph, memory-entities, rename, delete, merge, reclassify, normalize)
- INVOKE `link --from <a> --to <b> --relation <type>` to create edge
- PASS `--create-missing` to auto-create nonexistent entities during link
- PASS `--entity-type <kind>` for auto-created entities (default `concept`)
- PASS `--weight <float>` for edge weight (default 0.5)
- USE `--strict-relations` to fail on non-canonical relation types
- USE `--max-entity-degree N` to warn when entity exceeds N connections
- INVOKE `graph entities --json` to list all entities
- ACCESS via `.entities[]` (field is `entities` NOT `items`)
- FILTER via `--entity-type <kind>`
- SORT via `--sort-by degree|name|created_at` (default `name`)
- SET direction via `--order asc|desc` (default `asc`)
- PAGINATE via `--limit N --offset N`
- INVOKE `graph stats --json` to inspect `node_count`, `edge_count`, `avg_degree`, `max_degree`
- INVOKE `graph traverse --from <root> --depth <N>` for subgraph traversal
- EXPECT `hops[]`: `entity`, `relation`, `direction`, `weight`, `depth`
- TREAT exit 4 as nonexistent root entity
- USE `--format json|dot|mermaid` with `--output <path>` to export graph
- INVOKE `memory-entities --name <memory>` for forward entity lookup
- INVOKE `memory-entities --entity <name>` for reverse memory lookup
- INVOKE `rename-entity --name <old> --new-name <new>` to rename entity
- TREAT exit 4 as entity not found
- TREAT exit 1 if new name fails validation
- INVOKE `delete-entity --name <n> --cascade` to remove entity and all bindings
- KNOW `--cascade` is REQUIRED when entity has relationships (else exit 1)
- INVOKE `merge-entities --names "a,b,c" --into <target>` to merge entities
- INVOKE `reclassify --name <n> --new-type <kind>` for single entity reclassification
- INVOKE `reclassify --from-type <old> --to-type <new> --batch` for bulk reclassification
- INVOKE `reclassify-relation --from-relation <old> --to-relation <new> --batch`
- INVOKE `normalize-entities --yes` to normalize all names to kebab-case ASCII
- VALIDATE names: minimum 2 chars, no newlines, no short ALL_CAPS (4 chars or less rejected since v1.0.88 BUG-13 fix)
- NORMALIZE names via NFKD then ASCII then lowercase then hyphens
- KNOW CANONICAL relations: `applies-to`, `uses`, `depends-on`, `causes`, `fixes`, `contradicts`, `supports`, `follows`, `related`, `mentions`, `replaces`, `tracked-in`
- KNOW NON-CANONICAL mapping: `adds|creates → causes`, `implements → supports`, `blocks → contradicts`, `tested-by → related`, `part-of → applies-to`
- KNOW CANONICAL entity types: `project`, `tool`, `person`, `file`, `concept`, `incident`, `decision`, `memory`, `dashboard`, `issue_tracker`, `organization`, `location`, `date`
- NEVER use `mentions` as default relation (adds noise)
- NEVER persist ephemeral state in entities

```bash
# LINK two entities, auto-creating them if absent (no backend needed)
sqlite-graphrag link --from jwt --to auth-service --relation uses \
  --weight 0.8 --create-missing --entity-type concept --json

# LIST entities sorted by degree, descending
sqlite-graphrag graph entities --sort-by degree --order desc --limit 20 --json \
  | jaq -r '.entities[] | "\(.name) \(.degree)"'

# INSPECT graph stats before deciding traversal depth
sqlite-graphrag graph stats --json | jaq '{node_count, edge_count, avg_degree, max_degree}'

# TRAVERSE a 2-hop subgraph from a root entity
sqlite-graphrag graph traverse --from jwt --depth 2 --json \
  | jaq -r '.hops[] | "\(.entity) \(.relation) (depth \(.depth))"'

# EXPORT the graph as mermaid to a file
sqlite-graphrag graph --format mermaid --output graph.mmd --json

# FORWARD lookup: which entities a memory links to
sqlite-graphrag memory-entities --name design-auth --json | jaq '.entities[].name'

# REVERSE lookup: which memories link to an entity
sqlite-graphrag memory-entities --entity jwt --json | jaq '.memories[].name'

# RENAME an entity preserving relationships
sqlite-graphrag rename-entity --name auth --new-name authentication --json

# DELETE an entity and cascade its relationships
sqlite-graphrag delete-entity --name stale-entity --cascade --json

# MERGE near-duplicate entities into one
sqlite-graphrag merge-entities --names "auth,authn,authentication" --into authentication --json

# NORMALIZE all entity names to kebab-case ASCII
sqlite-graphrag normalize-entities --yes --json | jaq '{normalized_count, merged_count}'
```


## GraphRAG Search (recall, hybrid-search, related, deep-research, enrich)
- USE canonical three-layer pattern: `hybrid-search` then `read --name` then `related|graph traverse`
- INVOKE `recall <query> --k N` for pure semantic KNN search
- PASS `--no-graph` to disable automatic graph expansion
- INTERPRET `distance` increasing as similarity decreasing
- INTERPRET `score` as `1.0 - distance` clamped to `[0.0, 1.0]`
- EXPECT `source ∈ {direct, graph}` and `graph_depth` for graph results
- EXPECT response: `direct_matches[]`, `graph_matches[]`, `results[]`, `elapsed_ms`
- INVOKE `hybrid-search <query> --k N` for FTS5 and KNN fusion via RRF
- PASS `--rrf-k 60` for standard RRF fusion constant
- PASS `--weight-vec 1.0` and `--weight-fts 1.0` for balanced fusion
- USE `--with-graph --max-hops 2 --min-weight 0.3` for graph expansion
- EXPECT `hybrid-search` response: `results[]`, `graph_matches[]`, `fts_degraded`, `vec_degraded_reason?`, `backend_invoked`, `elapsed_ms`
- READ BOTH `results[]` AND `graph_matches[]` when `--with-graph` active
- INVOKE `related <name> --hops N` for multi-hop traversal from memory
- PASS `--relation <type>` to filter traversal by relation
- EXPECT `hop_distance` explicit per hop
- INVOKE `deep-research "<query>" --k 20` for parallel multi-hop research
- PASS `--max-sub-queries 7` to cap query decomposition
- PASS `--max-hops 3 --min-weight 0.3 --max-results 50` for graph traversal
- PASS `--with-bodies` to include full memory bodies in results
- EXPECT response: `sub_queries[]`, `results[]`, `evidence_chains[]`, `graph_context?`, `stats`
- INVOKE `enrich --operation <op> --mode claude-code` for LLM graph quality
- KNOW OPERATIONS: `memory-bindings`, `entity-descriptions`, `body-enrich` (Jaccard >=0.7), `re-embed --limit N --resume`
- PASS `--llm-parallelism N` to control concurrent LLM subprocesses
- PASS `--max-cost-usd N` to cap cumulative spend (ignored for OAuth users)
- PASS `--resume` and `--retry-failed` for crash resilience
- USE `--dry-run` to preview without spawning LLM
- USE BROAD query for `recall --k 5`
- USE MIXED token query for `hybrid-search --k 10`
- USE MIXED with graph for `hybrid-search --with-graph --max-hops 2`
- USE EXPLORATORY from memory for `related --hops 2`
- USE EXPLORATORY from entity for `graph traverse --depth 2`
- NEVER confuse `distance` with `combined_score` in ranking
- NEVER increase `--hops` without inspecting `graph stats` first
- NEVER skip layer 2 when snippet is insufficient
- NEVER read only `.results[]` when `--with-graph` is active

```bash
# RECALL with Codex embedding — pure semantic KNN
sqlite-graphrag --llm-backend codex --llm-model gpt-5.4-mini \
  recall "JWT authentication" --k 5 --json | jaq '.results[] | {name, score}'

# RECALL with Claude embedding — pure semantic KNN
sqlite-graphrag --llm-backend claude --llm-model claude-sonnet-4-6 \
  recall "JWT authentication" --k 5 --json | jaq '.results[] | {name, score}'

# RECALL with OpenCode embedding — pure semantic KNN
sqlite-graphrag --llm-backend opencode --llm-model opencode/big-pickle \
  recall "JWT authentication" --k 5 --json | jaq '.results[] | {name, score}'

# HYBRID SEARCH with Codex embedding and graph expansion
sqlite-graphrag --llm-backend codex --llm-model gpt-5.4-mini \
  hybrid-search "auth flow" --k 10 --rrf-k 60 --with-graph --max-hops 2 --json \
  | jaq -r '(.results[] | .name), (.graph_matches[] | .name)' | sort -u

# HYBRID SEARCH with Claude embedding and graph expansion
sqlite-graphrag --llm-backend claude --llm-model claude-sonnet-4-6 \
  hybrid-search "auth flow" --k 10 --with-graph --json \
  | jaq -r '(.results[] | .name), (.graph_matches[] | .name)' | sort -u

# HYBRID SEARCH with OpenCode embedding and graph expansion
sqlite-graphrag --llm-backend opencode --llm-model opencode/big-pickle \
  hybrid-search "auth flow" --k 10 --with-graph --json \
  | jaq -r '(.results[] | .name), (.graph_matches[] | .name)' | sort -u

# RELATED traversal from a known memory (no embedding needed)
sqlite-graphrag related design-auth --hops 2 --json | jaq '.results[] | {name, hop_distance}'

# DEEP RESEARCH with Codex embedding
sqlite-graphrag --llm-backend codex --llm-model gpt-5.4-mini \
  deep-research "How does the binary authenticate to OAuth providers?" \
  --k 20 --max-hops 3 --max-sub-queries 5 --json | jaq '.stats'

# DEEP RESEARCH with Claude embedding
sqlite-graphrag --llm-backend claude --llm-model claude-sonnet-4-6 \
  deep-research "How does the binary authenticate to OAuth providers?" \
  --k 20 --max-hops 3 --json | jaq '.stats'

# DEEP RESEARCH with OpenCode embedding
sqlite-graphrag --llm-backend opencode --llm-model opencode/big-pickle \
  deep-research "How does the binary authenticate to OAuth providers?" \
  --k 20 --max-hops 3 --json | jaq '.stats'

# ENRICH with Codex backend — rebuild missing embeddings
sqlite-graphrag --llm-backend codex --llm-model gpt-5.4-mini \
  enrich --operation re-embed --limit 100 --resume --json

# ENRICH with Claude backend — extract entity bindings
sqlite-graphrag --llm-backend claude --llm-model claude-sonnet-4-6 \
  enrich --operation memory-bindings --mode claude-code --json

# ENRICH with OpenCode backend — extract entity bindings
sqlite-graphrag --llm-backend opencode --llm-model opencode/big-pickle \
  enrich --operation memory-bindings --mode opencode --json
```


## v1.0.86+ Surface (pending, slots, embedding, llm-backend, shutdown)
- INVOKE `pending list --filter-status queued` to inspect three-stage remember checkpoint queue
- INVOKE `pending show <id>` to inspect single checkpoint row
- INVOKE `pending cleanup --yes` to remove terminal-state rows
- KNOW BACKED by `pending_memories` table created by migration V014 (ADR-0036)
- PASS `--db <PATH>` on `pending list`/`pending show` (v1.0.89, ADR-0049)
- INVOKE `pending-embeddings list` to inspect retry queue for failed embeddings
- INVOKE `pending-embeddings process` to reprocess with next backend
- KNOW BACKED by `pending_embeddings` table created by migration V015 (ADR-0040)
- INVOKE `slots status` to inspect host-wide slot semaphore
- INVOKE `slots release --slot-id <N> --yes` to reap orphan slots
- KNOW LOCK via `fs4 = "0.9"` with `fcntl(F_SETLK)` on Unix and `LockFileEx` on Windows (ADR-0039)
- INVOKE `embedding status` for aggregate per-status counts
- INVOKE `embedding list` for per-entry inspection
- PASS `--db <PATH>` on `embedding status`/`embedding list`/`embedding abandon` (v1.0.89, ADR-0049)
- PASS `--llm-backend codex,claude` for codex-first with claude fallback (ADR-0038)
- PASS `--llm-backend codex,claude,none` for null embedding fallback
- KNOW DEFAULT `--llm-backend` is `codex`
- PASS `--llm-fallback-mode <claude|codex>` to swap backend mid-job on rate-limit
- PASS `--max-concurrency N` global flag to limit concurrent heavy CLI invocations
- PASS `--wait-lock SECS` global flag to widen lock acquisition window
- PASS `--llm-parallelism N` global flag to cap embedding subprocess fan-out (default 4, clamp [1, 32])
- PASS `--ingest-parallelism N` to control per-file extract+embed parallelism in `ingest`
- PASS `--graceful-shutdown-secs N` to reserve cleanup budget before SIGKILL
- PASS `--skip-embedding-on-failure` only when `--llm-backend …,none`
- PASS ADR-0041 `--strict-env-clear` to drop custom-provider credentials in subprocess
- PASS `--dry-run-backend` to plan backend operation without executing it (idempotent preview)
- PARSE `backend_invoked` field in recall, hybrid-search, remember, edit, ingest, enrich, read envelopes to confirm effective backend
- READ `vec_degraded_reason` in recall/hybrid-search envelopes when vec path is degraded
- KNOW claude backend splits into local embedder via `embed_via_claude_local` (zero-token, OAuth-compatible)
- USE `LlmEmbeddingBuilder` to compose embedding pipeline: `with_backend(Codex).or_fallback(Claude).or_skip()`
- INVOKE `codex-models --json` since v1.0.89 to emit JSON envelope `{"action":"codex_models","count":N,"default":"...","models":[...]}` (no-op alias)
- RUN `codex login` after upgrade to refresh OAuth refresh token (2026-06-14 incident)
- KNOW OPERATOR action for stale OAuth: `codex login` then retry

```bash
# INSPECT the three-stage remember checkpoint queue
sqlite-graphrag pending list --filter-status queued --json | jaq '.[] | {id, name, status}'
sqlite-graphrag pending show 7 --json
sqlite-graphrag pending cleanup --yes --json

# INSPECT and reprocess the failed-embedding retry queue (codex-first, claude fallback)
sqlite-graphrag pending-embeddings list --json | jaq '.[] | {id, status}'
sqlite-graphrag --llm-backend codex,claude pending-embeddings process --json

# INSPECT host-wide slot semaphore and reap an orphan slot
sqlite-graphrag slots status --json | jaq '{max_concurrency, acquired, waiting}'
sqlite-graphrag slots release --slot-id 3 --yes --json

# INSPECT embedding queue status with explicit --db path
sqlite-graphrag --db /data/prod.sqlite embedding status --json | jaq '{pending, done, failed}'

# CONFIRM backend whitelist before a large embed job
sqlite-graphrag codex-models --json | jaq '{count, default, models: .models[:3]}'
```


## v1.0.87+ Pre-Flight Validation Layer (ADR-0045, GAP-META-005)
- KNOW that `src/spawn/preflight.rs` ports every LLM subprocess spawn through 7 guards BEFORE fork
- KNOW exit code 16 (`EX_CONFIG`) is the universal preflight failure exit code (added v1.0.87)
- KNOW 7 guards run in order: `check_argv_size`, `check_binary_exists`, `check_mcp_config_inline`, `check_mcp_config_path`, `check_walkup_mcp_json`, `check_output_buffer`, `check_claude_config_dir`
- KNOW `check_argv_size` rejects argv exceeding `ARG_MAX - 4096` bytes (margin for kernel env vars)
- KNOW `check_binary_exists` aborts when `claude` or `codex` is not in PATH
- KNOW `check_mcp_config_inline` rewrites `--mcp-config '{}'` literal to a tempfile with `{"mcpServers":{}}` (Claude Code 2.1.177 rejects the literal form)
- KNOW `check_mcp_config_path` validates JSON content of `--mcp-config <PATH>` files
- KNOW `check_walkup_mcp_json` rejects invalid `.mcp.json` in CWD ancestor chain (up to 16 levels via `Path::ancestors()`)
- KNOW `check_output_buffer` doubles parser buffer above 64 KB to handle large model outputs
- KNOW `check_claude_config_dir` avoids MCP leak from user-level `~/.claude/`
- SET `SQLITE_GRAPHRAG_SKIP_PREFLIGHT=1` ONLY in emergencies; bypass reverts to direct `Command::spawn()` and inherits all 5 GAP-META-005 bug classes
- READ `AppError::PreFlightFailed(PreFlightError)` envelope JSON for variant-specific remediation
- KNOW v1.0.88 BUG-11 fix ensures preflight failure propagates via `embed_via_backend_strict`; NEVER expect silent success when preflight fails
- NEVER proceed past exit code 16 without addressing the specific variant reported

```bash
# REPRODUCE a preflight failure (bad MCP config dir) — expect exit 16
CLAUDE_CONFIG_DIR=/tmp/bad-mcp sqlite-graphrag --llm-backend claude \
  remember --name test --type note --description x --body y 2>&1 || echo "exit=$?"

# READ the variant-specific remediation from the error envelope
CLAUDE_CONFIG_DIR=/tmp/bad-mcp sqlite-graphrag --llm-backend claude \
  remember --name test --type note --description x --body y --json 2>/dev/null \
  | jaq '{error, code, message}'

# BYPASS preflight ONLY in emergencies (inherits all bug classes)
SQLITE_GRAPHRAG_SKIP_PREFLIGHT=1 sqlite-graphrag --llm-backend codex \
  remember --name test --type note --description x --body y
```


## v1.0.88+ Hotfixes (BUG-11, BUG-12, BUG-13)
- KNOW BUG-11 (CRITICAL) FIXED: preflight failure in `extract/llm_embedding.rs:563-565` now propagates to `remember` via `embed_via_backend_strict` instead of silent persist with `backend_invoked: "none"` and zero chunks
- REPRODUCE BUG-11 fix: `CLAUDE_CONFIG_DIR=/tmp/bad-config-with-mcp sqlite-graphrag remember --name X --type note --description x --body y` returns exit 11 with JSON error envelope
- KNOW BUG-12 (MEDIUM) FIXED: OAuth-only enforcement emits exactly 1 stderr line (was 2 — duplicate `eprintln!` removed in `src/output.rs`)
- VERIFY BUG-12 fix: `ANTHROPIC_API_KEY=sk-test sqlite-graphrag init` emits 1 stderr line
- KNOW BUG-13 (MEDIUM) FIXED: `link --create-missing` validates entity names BEFORE normalizing (was bypassing validation; ALL_CAPS 3-4 char abbreviations like `API`, `WAL`, `RUST` now correctly rejected via CLI matching the `remember --graph-stdin` path)
- VERIFY BUG-13 fix: `sqlite-graphrag link --from api --to service --create-missing --relation uses` returns exit 1 with validation error
- INVOKE `AppError::PreFlightFailed(PreFlightError)` variant in error handling; exit code 16, `is_permanent() == true`

```bash
# VERIFY BUG-11 fix — preflight failure propagates instead of silent persist
CLAUDE_CONFIG_DIR=/tmp/bad-config-with-mcp sqlite-graphrag --llm-backend claude \
  remember --name X --type note --description x --body y 2>&1 || echo "exit=$?"

# VERIFY BUG-12 fix — OAuth enforcement emits exactly 1 stderr line
ANTHROPIC_API_KEY=sk-test sqlite-graphrag init 2>&1 1>/dev/null | wc -l

# VERIFY BUG-13 fix — ALL_CAPS short abbreviation rejected on link
sqlite-graphrag link --from api --to service --create-missing --relation uses 2>&1 || echo "exit=$?"
```


## v1.0.89+ Embedding Deadlock Remediation (ADR-0050)
- PASS `--llm-model <MODEL>` global flag to select embedding model for ALL backends (v1.0.89, ADR-0050)
- KNOW DEFAULT model for codex backend: `gpt-5.5`; for claude backend: `claude-sonnet-4-6`
- SET env `SQLITE_GRAPHRAG_LLM_MODEL` as persistent override for `--llm-model`
- PASS `--codex-binary <PATH>` to override codex binary location (v1.0.89, ADR-0050)
- SET env `SQLITE_GRAPHRAG_CODEX_BINARY` as persistent override for `--codex-binary`
- PASS `--claude-binary <PATH>` to override claude binary location (propagated via set_var since v1.0.89)
- PASS `--skip-embedding-on-failure` to exit 0 when LLM embedding fails (wired end-to-end since v1.0.89, ADR-0050)
- KNOW 7 dead CLI flags were fixed in v1.0.89 via `set_var` propagation in `main.rs`: `--llm-model`, `--llm-fallback`, `--skip-embedding-on-failure`, `--claude-binary`, `--codex-binary`, `--llm-max-host-concurrency`, `--llm-slot-wait-secs`
- KNOW `deep-research` and `remember-batch` now receive `llm_backend` from main.rs (v1.0.89, ADR-0050)
- KNOW adaptive timeout scales with batch size: `base + 15s × (batch_size - 1)` (v1.0.89, ADR-0050)
- KNOW OAuth expiry errors now include actionable hint: "run codex login" or "refresh claude OAuth" (v1.0.89)
- KNOW `BoolishValueParser` accepts `1/yes/on/true` and `0/no/off/false` for boolean env vars (v1.0.89, ADR-0050)
- KNOW `--yes` flag on `slots release`, `purge`, `cleanup-orphans` was wired end-to-end (v1.0.89, BUG-YES-FLAG-IGNORED)

```bash
# SELECT a specific embedding model per backend
sqlite-graphrag --llm-backend codex --llm-model gpt-5.4-mini \
  remember --name x --type note --description x --body y
sqlite-graphrag --llm-backend claude --llm-model claude-sonnet-4-6 \
  remember --name x --type note --description x --body y

# OVERRIDE binary paths for both backends
sqlite-graphrag --llm-backend codex --codex-binary /usr/local/bin/codex \
  --llm-model gpt-5.5 remember --name x --type note --description x --body y
sqlite-graphrag --llm-backend claude --claude-binary /usr/local/bin/claude \
  --llm-model claude-sonnet-4-6 remember --name x --type note --description x --body y

# EXIT 0 on embedding failure (degraded, non-searchable memory)
sqlite-graphrag --llm-backend codex,claude,none --skip-embedding-on-failure \
  remember --name x --type note --description x --body-file big.md

# DEEP-RESEARCH and REMEMBER-BATCH now honor the global backend
sqlite-graphrag --llm-backend codex --llm-model gpt-5.4-mini \
  deep-research "OAuth flow" --k 20 --json | jaq '.stats'
sqlite-graphrag --llm-backend codex --llm-model gpt-5.4-mini \
  remember-batch --json < batch.ndjson | jaq -c 'select(.summary == true)'
```


## v1.0.89+ Schema Drift and Flag Parity (ADR-0048, ADR-0049)
- KNOW `health.schema.json` regenerated via `schemars` derive macro (ADR-0048); `additionalProperties: true` per Must-Ignore policy (RFC 7493 I-JSON)
- KNOW 17 new fields added to `health` envelope since v1.0.88: `fts_query_ok`, `vec_memories_missing`, `vec_memories_orphaned`, `sqlite_version`, `mentions_ratio`, `mentions_warning`, `top_relation`, `top_relation_ratio`, `applies_to_ratio`, `relation_concentration_warning`, `super_hub_count`, `super_hub_warning`, `top_hub_entity`, `top_hub_degree`, `hub_warning`, `non_normalized_count`, `normalization_warning`
- REGENERATE schemas via `cargo run --bin dump-schema` (idempotent BTreeMap ordering)
- PASS `--namespace <NS>` on `health` to filter counts to one namespace
- USE `migrate --dry-run --json` to PREVIEW pending migrations without applying; lists names+versions, validates checksums, checks preconditions
- USE `codex-models --json` as no-op alias returning JSON envelope
- USE `--auto-describe` (default true) on `ingest` to extract description from first significant body line; OPT OUT via `--no-auto-describe`
- PASS `--db <PATH>` on `embedding status`/`embedding list`/`embedding abandon`/`pending list`/`pending show` (ADR-0049)
- KNOW `--db <PATH>` is NOT global; each subcommand accepts it independently (clap `Arg::global = true` was REJECTED as invasive)
- TREAT binary size as 14.6 MiB stripped ELF (NOT 6 MB as in older docs); see `Cargo.toml:6` description

```bash
# INSPECT the 17 new health fields
sqlite-graphrag health --json \
  | jaq '{fts_query_ok, sqlite_version, top_relation, super_hub_count, normalization_warning}'

# REGENERATE schemas idempotently and CHECK they are in sync
cargo run --bin dump-schema -- --check
cargo run --bin dump-schema

# HEALTH scoped to a namespace
sqlite-graphrag health --namespace prod --json | jaq '.counts'

# PREVIEW pending migrations without applying
sqlite-graphrag migrate --dry-run --json | jaq '.would_apply[]? | {name, version}'
```


## JSON Contracts (Top-5 Fields per Command)
- KNOW `recall` top fields: `results[].name`, `snippet`, `distance`, `score`, `source`
- KNOW `hybrid-search` top fields: `results[].name`, `combined_score`, `vec_rank`, `fts_rank`, `source`
- KNOW `health` top fields: `integrity_ok`, `schema_ok`, `counts`, `wal_size_mb`, `schema_version`
- KNOW `list` top fields: `items[].name`, `type`, `description`, `updated_at_iso`, `deleted_at_iso?`
- KNOW `edit` top fields: `memory_id`, `name`, `action`, `version`, `elapsed_ms`
- KNOW `read` top fields: `name`, `body`, `description`, `created_at_iso`, `updated_at_iso`
- KNOW `forget` top fields: `action`, `forgotten`, `name`, `namespace`, `elapsed_ms`
- KNOW `link` top fields: `action`, `from`, `to`, `relation`, `weight`
- KNOW `graph entities` top fields: `entities[].id`, `name`, `entity_type`, `degree`, `description?`
- KNOW `deep-research` top fields: `sub_queries[]`, `results[]`, `evidence_chains[]`, `graph_context`, `stats`
- KNOW `enrich` NDJSON events: `phase`, `name`, `status`, `entities?`, `rels?`, `cost_usd?`, `elapsed_ms?`
- KNOW `pending list` top fields: `id`, `name`, `status`, `created_at`, `namespace`
- KNOW `slots status` top fields: `max_concurrency`, `acquired`, `waiting`, `held_by_pid[]`
- KNOW `embedding status` top fields: `pending`, `processing`, `done`, `failed`, `skipped`
- KNOW `remember`/`edit`/`ingest`/`enrich`/`read` envelopes: include `backend_invoked` and `vec_degraded_reason?`
- KNOW `health.schema.json` uses `"additionalProperties": true` per Must-Ignore policy (RFC 7493 I-JSON) since v1.0.89 (ADR-0048); the other 49 schemas in `docs/schemas/` still use `"additionalProperties": false` (Must-Validate) pending regeneration in v1.0.90+
- READ FULL schemas in `docs/schemas/*.schema.json` (never inline full schema in skill)

```bash
# PARSE recall top fields
sqlite-graphrag --llm-backend codex recall "auth" --k 3 --json \
  | jaq '.results[] | {name, snippet, distance, score, source}'

# PARSE hybrid-search top fields plus backend confirmation
sqlite-graphrag --llm-backend claude hybrid-search "auth" --k 5 --json \
  | jaq '{backend_invoked, results: [.results[] | {name, combined_score, vec_rank, fts_rank}]}'

# PARSE deep-research top fields
sqlite-graphrag --llm-backend codex deep-research "OAuth flow" --k 20 --json \
  | jaq '{sub_queries: (.sub_queries | length), evidence_chains: (.evidence_chains | length), stats}'
```


## Exit Codes and Retry
- KNOW EXIT 0 means success; parse stdout
- KNOW EXIT 1 means validation error (invalid weight, self-link, max-files exceeded, link ALL_CAPS bypass)
- KNOW EXIT 2 means Clap argument parsing error
- KNOW EXIT 3 means optimistic lock conflict; reload `read --json` and retry
- KNOW EXIT 4 means entity, memory, or version not found
- KNOW EXIT 5 means namespace error
- KNOW EXIT 6 means payload above size limit
- KNOW EXIT 9 means duplicate memory (use `--force-merge` to update or restore)
- KNOW EXIT 10 means database error; run `vacuum` and `health`
- KNOW EXIT 11 means embedding failure (LLM subprocess error, including preflight fail since BUG-11 fix)
- KNOW EXIT 13 means partial batch failure; reprocess only failed
- KNOW EXIT 14 means I/O error (permission, disk full)
- KNOW EXIT 15 means database busy; widen `--wait-lock`
- KNOW EXIT 16 means preflight validation failure (v1.0.87+, ADR-0045); check JSON envelope for variant
- KNOW EXIT 19 means SHUTDOWN_EXIT_CODE (ADR-0037); partial work discarded; RETRY MANDATORY
- KNOW EXIT 19 envelope: `{error:true, code:19, signal, graceful, message}`
- KNOW EXIT 20 means internal error or JSON serialization failure
- KNOW EXIT 75 means slots exhausted OR `JobSingletonLocked`
- KNOW EXIT 75 from `enrich`/`ingest --mode claude-code|codex|opencode`: parse `job '(\w+)'.*namespace '(\w+)'`
- KNOW EXIT 75 circuit breaker: respect per-namespace cooldown window; do NOT retry immediately
- KNOW EXIT 77 means RAM pressure; wait for free memory
- NEVER ignore non-zero exit code as success
- NEVER reprocess entire batch after exit 13
- NEVER increase concurrency after exit 75 or 77
- NEVER confuse exit 1 (validation) with exit 9 (duplicate)
- NEVER treat exit 16 as transient; fix the underlying preflight issue

```bash
# CHECK exit code first, then branch on it
sqlite-graphrag --llm-backend codex recall "auth" --k 3 --json
case $? in
  0) echo "success" ;;
  11) echo "embedding failure — check backend and OAuth" ;;
  16) echo "preflight failure — fix MCP config" ;;
  19) echo "shutdown — RETRY MANDATORY" ;;
  75) echo "slots exhausted — wait for cooldown, do NOT retry immediately" ;;
  *) echo "other failure: $?" ;;
esac
```


## Concurrency, RAM, Parallelism, Slots
- RESPECT hard ceiling `2 × nCPUs` for heavy commands
- TREAT as heavy: `init`, `remember`, `ingest`, `recall`, `hybrid-search`
- DISTINGUISH `--max-concurrency` (CLI fan-out) from `--ingest-parallelism` (per-file)
- SET `--llm-parallelism N` default 4 on `remember`/`edit`, default 2 on `ingest`
- CLAMP `--llm-parallelism` in range `[1, 32]`
- USE `--llm-max-host-concurrency N` to cap cross-process LLM subprocesses
- USE `--llm-slot-wait-secs N` to wait for slot or `--llm-slot-no-wait` to abort
- WIDEN `--wait-lock SECS` when contention is expected
- ENABLE `SQLITE_GRAPHRAG_LOW_MEMORY=1` for unitary parallelism (3-4x slower)
- USE `--strict-env-clear` (ADR-0041) to preserve only `PATH` for compliance
- USE SHUTDOWN bypass recipe: prepend `tests/mock-llm` to PATH then set `SQLITE_GRAPHRAG_IGNORE_SHUTDOWN=1` then wrap with `setsid -w timeout`
- KNOW JOB SINGLETON: `enrich`, `ingest --mode claude-code`, `ingest --mode codex`, `ingest --mode opencode` acquire per-namespace singleton
- USE `--wait-job-singleton SECS` to wait for lock or `--force-job-singleton` to break stale lock
- LIMIT parallel ingestion in CI to avoid LLM rate limits
- NEVER run `enrich` in parallel against same database

```bash
# CAP embedding subprocess fan-out and cross-process concurrency
sqlite-graphrag --llm-backend codex --llm-parallelism 4 --llm-max-host-concurrency 8 \
  ingest ./docs --mode codex --recursive --json

# WAIT for a slot instead of aborting
sqlite-graphrag --llm-backend codex --llm-slot-wait-secs 30 \
  recall "auth" --k 5 --json

# RUN unitary, low-memory mode in a constrained container
SQLITE_GRAPHRAG_LOW_MEMORY=1 sqlite-graphrag --llm-backend codex \
  ingest ./docs --mode codex --recursive --json

# WAIT for the per-namespace job singleton on enrich
sqlite-graphrag --llm-backend codex enrich --operation re-embed --limit 100 \
  --wait-job-singleton 60 --resume --json
```


## Maintenance (fts, backup, vacuum, optimize, migrate, export, debug-schema, vec, completions)
- INVOKE `fts rebuild --json` to fully rebuild FTS5 full-text index
- INVOKE `fts check --json` to run FTS5 integrity check
- INVOKE `fts stats --json` to inspect FTS5 health (`total_rows`, `fts_functional`)
- INVOKE `optimize --fts-dry-run` to preview FTS5 rebuild
- INVOKE `optimize --fts-progress N` to print progress every N seconds
- PASS `--no-fts-skip-when-functional` to force FTS5 rebuild even when healthy
- INVOKE `backup --output <path> --json` for safe online backup via SQLite API
- INVOKE `sync-safe-copy --dest <path>` for atomic snapshot before critical operations
- INVOKE `export --namespace <ns> --type <kind> --json` to export memories as NDJSON
- INVOKE `vacuum --json` after large purge to reclaim space
- INVOKE `migrate --rehash --json` to repair V002 checksum drift
- INVOKE `migrate --to-llm-only --drop-vec-tables --json` for v1.0.74/75 upgrades
- INVOKE `migrate --dry-run --json` to preview migrations (v1.0.89)
- INVOKE `debug-schema --json` (hidden from `--help`) to inspect schema state
- INVOKE `completions <bash|zsh|fish|elvish|powershell>` to generate shell completions
- INVOKE `vec orphan-list --json` to list orphaned memory vectors
- INVOKE `vec purge-orphan --yes --dry-run` to PREVIEW purge
- INVOKE `vec purge-orphan --yes` to PERMANENTLY purge orphans
- INVOKE `vec stats --json` to inspect vec table health
- REGENERATE schemas via `cargo run --bin dump-schema` (v1.0.89, ADR-0048)
- SCHEDULE weekly: `purge --retention-days 30 --yes` then `cleanup-orphans --yes` then `prune-relations --relation mentions --yes` then `vacuum --json` then `optimize --json` then `sync-safe-copy --dest ~/backups/`
- KNOW SINCE v1.0.53 every write runs `PRAGMA wal_checkpoint(TRUNCATE)` after commit
- KNOW IF corruption occurs despite checkpoint: `sqlite3 broken.sqlite ".recover" | sqlite3 repaired.sqlite`

```bash
# REBUILD and CHECK the FTS5 index
sqlite-graphrag fts rebuild --json
sqlite-graphrag fts check --json | jaq '.integrity_ok'
sqlite-graphrag fts stats --json | jaq '{total_rows, fts_functional}'

# BACKUP online and SNAPSHOT atomically
sqlite-graphrag backup --output ~/backups/graphrag.sqlite --json
sqlite-graphrag sync-safe-copy --dest ~/backups/graphrag-$(date +%Y%m%d).sqlite

# EXPORT a namespace as NDJSON
sqlite-graphrag export --namespace prod --type decision --json > decisions.ndjson

# LIST and PURGE orphaned vectors safely
sqlite-graphrag vec orphan-list --json | jaq 'length'
sqlite-graphrag vec purge-orphan --yes --dry-run --json
sqlite-graphrag vec purge-orphan --yes --json

# GENERATE shell completions
sqlite-graphrag completions bash > ~/.local/share/bash-completion/completions/sqlite-graphrag
```


## Ready-Made Examples

### Example 1 — Bootstrap a project namespace
```bash
sqlite-graphrag init --namespace myproject
sqlite-graphrag health --json | jaq '.integrity_ok'
sqlite-graphrag health --json | jaq '{schema_version, counts}'
```
- EXPECT: exit 0, `integrity_ok: true`, `schema_version >= 15`

### Example 2 — Store and retrieve a memory (codex backend)
```bash
sqlite-graphrag --llm-backend codex --llm-model gpt-5.4-mini \
  remember --name auth-decision --type decision \
  --description "JWT 15-min expiry with refresh flow" \
  --body-stdin <<'EOF'
We chose JWT with 15-minute expiry because:
- Refresh tokens are HTTP-only cookies
- 15min limit reduces blast radius of XSS
- Refresh flow reissues tokens on user activity
EOF

sqlite-graphrag read --name auth-decision --json | jaq '{description, body_length}'
```
- EXPECT: memory persisted, body contains full text, `body_length` > 100

### Example 3 — Store the same memory (claude backend)
```bash
sqlite-graphrag --llm-backend claude --llm-model claude-sonnet-4-6 \
  remember --name auth-decision --type decision \
  --description "JWT 15-min expiry with refresh flow" \
  --body "15-min expiry, HTTP-only refresh cookies, reissue on activity" --force-merge

sqlite-graphrag read --name auth-decision --json | jaq '.backend_invoked'
```
- EXPECT: idempotent merge; `backend_invoked` confirms the claude path ran

### Example 3b — Store the same memory (opencode backend)
```bash
sqlite-graphrag --llm-backend opencode --llm-model opencode/big-pickle \
  remember --name auth-decision --type decision \
  --description "JWT 15-min expiry with refresh flow" \
  --body "15-min expiry, HTTP-only refresh cookies, reissue on activity" --force-merge

sqlite-graphrag read --name auth-decision --json | jaq '.backend_invoked'
```
- EXPECT: idempotent merge; `backend_invoked` confirms the opencode path ran

### Example 4 — Search with hybrid ranking + graph expansion (both backends)
```bash
sqlite-graphrag --llm-backend codex --llm-model gpt-5.4-mini \
  hybrid-search "JWT authentication" --k 5 --with-graph --max-hops 2 --json \
  | jaq -r '(.results[] | .name), (.graph_matches[] | .name)' | sort -u

sqlite-graphrag --llm-backend claude --llm-model claude-sonnet-4-6 \
  hybrid-search "JWT authentication" --k 5 --with-graph --max-hops 2 --json \
  | jaq -r '(.results[] | .name), (.graph_matches[] | .name)' | sort -u
```
- EXPECT: top 5 KNN+FTS5 fused results plus 0-N multi-hop neighbors

### Example 5 — Bulk ingest a docs directory (codex extraction)
```bash
sqlite-graphrag --llm-backend codex --llm-model gpt-5.4-mini \
  ingest ./docs --mode codex --recursive --type document \
  --pattern "*.md" --max-files 1000 --auto-describe --json \
  | jaq -c 'select(.status)' | jaq -s 'group_by(.status) | map({status: .[0].status, count: length})'
```
- EXPECT: NDJSON progress; summary shows `files_total`, `files_succeeded`, `files_failed`

### Example 5b — Bulk ingest a docs directory (opencode extraction)
```bash
sqlite-graphrag --llm-backend opencode --llm-model opencode/big-pickle \
  ingest ./docs --mode opencode --recursive --type document \
  --pattern "*.md" --auto-describe --json \
  | jaq -c 'select(.status)' | jaq -s 'group_by(.status) | map({status: .[0].status, count: length})'
```
- EXPECT: NDJSON progress; opencode extracts entities and relations per file

### Example 6 — Graph traversal from a known entity
```bash
sqlite-graphrag graph entities --json | jaq -r '.entities[].name' | head -10
sqlite-graphrag graph traverse --from jwt --depth 2 --json | jaq -r '.hops[] | "\(.entity) \(.relation)"'
```
- EXPECT: list of entities; traversal shows 2-hop neighborhood via canonical relations

### Example 7 — Deep research question (claude backend)
```bash
sqlite-graphrag --llm-backend claude --llm-model claude-sonnet-4-6 \
  deep-research "How does the binary authenticate to OAuth providers?" \
  --k 20 --max-hops 3 --max-sub-queries 5 --json \
  | jaq '{stats, evidence_chains: (.evidence_chains | length)}'
```
- EXPECT: decomposed sub-queries, evidence chains linking seed to target, graph_context populated

### Example 8 — LLM-curated entity extraction from existing docs (claude-code mode)
```bash
sqlite-graphrag --llm-backend claude --llm-model claude-sonnet-4-6 \
  ingest ./corpus --mode claude-code --recursive --resume --json \
  | jaq -c 'select(.status == "done") | {file, entities, rels}'
```
- EXPECT: per-file NDJSON with `entities` count, `rels` count; `--resume` continues after interruption

### Example 9 — Diagnose a preflight failure (exit 16)
```bash
CLAUDE_CONFIG_DIR=/tmp/bad-mcp sqlite-graphrag --llm-backend claude \
  remember --name test --type note --description x --body y 2>&1
echo "exit=$?"
sqlite-graphrag --llm-backend claude \
  remember --name test --type note --description x --body y 2>&1 || echo "exit=$?"
```
- EXPECT: first invocation returns exit 16 with `AppError::PreFlightFailed` envelope
- EXPECT: second invocation without bad MCP dir returns exit 0

### Example 10 — Recovery from soft-delete
```bash
sqlite-graphrag forget --name auth-decision
sqlite-graphrag history --name auth-decision --json | jaq '.versions[0].deleted'
sqlite-graphrag restore --name auth-decision
sqlite-graphrag --llm-backend codex --llm-model gpt-5.4-mini recall "JWT" --k 3 --json | jaq '.results[].name'
```
- EXPECT: soft-delete hides from recall; restore brings it back; recall shows it again

### Example 11 — Codex-first with claude fallback on a flaky network
```bash
sqlite-graphrag --llm-backend codex,claude --llm-model gpt-5.5 \
  remember --name resilient-note --type note \
  --description "survives codex rate limit" --body-file note.md

sqlite-graphrag read --name resilient-note --json | jaq '.backend_invoked'
```
- EXPECT: codex attempted first; on rate-limit the claude backend completes; `backend_invoked` confirms which ran

### Example 11b — Full fallback chain: codex → claude → opencode → none
```bash
sqlite-graphrag --llm-backend codex,claude,opencode,none --skip-embedding-on-failure \
  remember --name max-resilience --type note \
  --description "survives all backend failures" --body-file note.md

sqlite-graphrag read --name max-resilience --json | jaq '.backend_invoked'
```
- EXPECT: tries codex, then claude, then opencode, then degrades to null embedding; `backend_invoked` confirms which ran

### Example 12 — Health check with namespace filter and vec tables
```bash
sqlite-graphrag health --namespace prod --json | jaq '{integrity_ok, schema_version, counts}'
sqlite-graphrag vec stats --json | jaq '.'
sqlite-graphrag embedding status --json | jaq '{pending, done, failed}'
```
- EXPECT: scoped counts for the `prod` namespace; vec table health; embedding queue status

### Example 13 — Regenerate JSON schemas after type changes
```bash
cargo run --bin dump-schema -- --check
git diff --stat docs/schemas/
cargo run --bin dump-schema  # if --check failed
```
- EXPECT: `--check` exits 0 when schemas are in sync; regeneration produces idempotent output

### Example 14 — Maintenance pipeline (weekly)
```bash
sqlite-graphrag purge --retention-days 30 --yes --dry-run
sqlite-graphrag cleanup-orphans --yes --dry-run
sqlite-graphrag prune-relations --relation mentions --yes --dry-run
sqlite-graphrag vacuum --json
sqlite-graphrag optimize --json
sqlite-graphrag sync-safe-copy --dest ~/backups/graphrag-$(date +%Y%m%d).sqlite
```
- EXPECT: each dry-run reports counts; full pipeline reclaims space and snapshots safely

### Example 15 — Inspect Codex models whitelist (v1.0.89, no-op alias, GAP-E2E-010a)
```bash
sqlite-graphrag codex-models --json | jaq '{count, default, models: .models[:3]}'
sqlite-graphrag codex-models  # text mode for humans
sqlite-graphrag codex-models --json | jaq '.models | length'
```
- EXPECT: JSON envelope `{"action":"codex_models","count":N,"default":"gpt-5.5","models":[...]}`
- EXPECT: text mode emits human-readable list of supported models
- USE when validating that current OAuth scope includes required codex model names

### Example 16 — Health check scoped to one namespace (v1.0.89, GAP-E2E-002)
```bash
sqlite-graphrag health --namespace prod --json | jaq '{integrity_ok, schema_version, counts}'
sqlite-graphrag health --namespace dev --json | jaq '.counts'  # different counts
sqlite-graphrag health --json | jaq '.counts'  # global counts
```
- EXPECT: counts filtered to the specified namespace; integrity and schema_version fields unchanged
- USE in multi-tenant environments to verify per-namespace isolation
- OMISSION RULE: when `--namespace` is omitted, counts aggregate across all namespaces (global view)

### Example 17 — Dry-run migration preview (v1.0.89, GAP-E2E-009)
```bash
sqlite-graphrag migrate --dry-run --json | jaq '.would_apply[]? | {name, version}'
sqlite-graphrag migrate --to-llm-only --drop-vec-tables --dry-run --json | jaq '.'
sqlite-graphrag migrate --dry-run --json  # always PREVIEW before destructive migrations
```
- EXPECT: list of pending migrations with name+version without applying them; database remains unchanged
- EXPECT: `--to-llm-only --dry-run` reports vec table drop plan without executing
- USE in CI pipelines and before any irreversible migration step

### Example 18 — Plan backend operation without executing (dry-run-backend)
```bash
sqlite-graphrag --llm-backend codex --llm-model gpt-5.4-mini --dry-run-backend \
  remember --name preview --type note --description x --body y --json | jaq '.backend_invoked'

sqlite-graphrag --llm-backend claude --llm-model claude-sonnet-4-6 --dry-run-backend \
  recall "auth" --k 5 --json | jaq '{backend_invoked, vec_degraded_reason}'
```
- EXPECT: idempotent preview; `backend_invoked` reports the planned backend; nothing persisted


## References to Extended Documentation

For details beyond this skill's daily-use scope, the following project documents extend coverage:

- READ `docs/HOW_TO_USE.md` — quickstart, installation, common workflows
- READ `docs/COOKBOOK.md` — 50+ recipes for advanced patterns (preflight diagnostics, schema drift recovery, etc.)
- READ `docs/MIGRATION.md` — version-to-version upgrade paths
- READ `docs/CROSS_PLATFORM.md` — behavior across Linux, macOS, Windows ARM64
- READ `docs/AGENTS.pt-BR.md` — extended PT-BR documentation for AI agents
- READ `docs/schemas/*.schema.json` — full JSON Schema contracts (versioned per SemVer)
- READ `docs/decisions/adr-*.md` — Architecture Decision Records (justifications for each design choice)
- READ `llms-full.txt` — complete LLM-context dump with all rules
- READ `gaps.md` — current open and closed gaps
- READ `CHANGELOG.md` — version-by-version release notes
- READ `Cargo.toml` — package metadata and binary size documentation (14.6 MiB)


## Active Rules and Anti-patterns Summary
- NEVER pass `ANTHROPIC_API_KEY` or `OPENAI_API_KEY` (OAuth-only, exit 1)
- NEVER depend on daemon or use `--bare` flag (REMOVED v1.0.76 and v1.0.79)
- NEVER install with `--features embedding-legacy` or `--features ner-legacy` (REMOVED)
- NEVER use `fastembed`, `tokenizers`, `sqlite-vec`, or `GLiNER` crates
- NEVER expect sqlite-vec KNN; cosine is pure Rust in `src/similarity.rs`
- NEVER run `enrich` in parallel against same database (job singleton via `lock::acquire_job_singleton`)
- NEVER write to `.sqlite` file outside the binary
- NEVER ignore exit 19 (SHUTDOWN_EXIT_CODE envelope); partial work discarded, RETRY MANDATORY
- NEVER ignore exit 16 (preflight failure); fix MCP config or `SQLITE_GRAPHRAG_SKIP_PREFLIGHT=1`
- NEVER duplicate content already in `CHANGELOG.md`
- NEVER use `mentions` as default graph relation
- NEVER pass empty body via `--graph-stdin` (exit 1 since v1.0.54)
- NEVER use `--gliner-variant` (no-op since v1.0.79)
- NEVER call `migrate --to-llm-only` without `--drop-vec-tables` safety guard
- NEVER ignore `--wait-lock` flag when contention is expected
- NEVER assume exit 1 equals exit 9 (validation vs duplicate)
- NEVER assume binary size is 6 MB; actual is 14.6 MiB stripped ELF
- ALWAYS pass `--llm-backend` and `--llm-model` explicitly for embedding commands
- ALWAYS parse `backend_invoked` to confirm which backend ran
- ALWAYS run `codex login` or refresh claude OAuth when a backend reports stale OAuth