-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmcp-tools.texi
More file actions
1231 lines (966 loc) · 34.2 KB
/
Copy pathmcp-tools.texi
File metadata and controls
1231 lines (966 loc) · 34.2 KB
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
@node MCP Tools
@chapter MCP Tools
@cindex MCP tools
@cindex tools
OwnSona exposes its functionality as a set of MCP tools. Each one is
a JSON-RPC method on the @code{/mcp} endpoint, callable by any MCP
client that presents a valid OAuth 2.1 access token. This chapter
is the reference for every tool's inputs, outputs, errors, and
example calls.
@menu
* General tool conventions:: Inputs, outputs, errors, IDs, timestamps.
* The remember tool:: Store a new fact.
* The remember_batch tool:: Bulk-load up to 200 facts in one call.
* The recall tool:: Vector-similarity search.
* The search_memory tool:: Alias of recall for clients that prefer verbs.
* The build_context_prompt tool:: Pack relevant facts into a prompt envelope.
* The list_memories tool:: Paginated listing with cleanup filters.
* The update_memory tool:: Edit an existing memory.
* The update_memory_batch tool:: Edit up to 200 memories in one call.
* The confirm tool:: Refresh last_confirmed_at.
* The reinforce tool:: Feedback that tunes learned ranking.
* The forget tool:: Soft-delete or hard-delete a memory.
* The forget_batch tool:: Soft-delete up to 200 memories in one call.
* The find_near_duplicates tool:: Cluster active memories by cosine similarity.
* The find_conflicts tool:: Surface same-topic memories that may contradict.
* The query_relations tool:: Multi-hop traversal of the relation graph.
* The text_search tool:: Plain-substring search.
* The get_memory tool:: Fetch a single memory by id.
* The count_memories tool:: Cheap COUNT with optional filters.
* The memory_stats tool:: Aggregate counts and top tags.
* The list_tags tool:: Distinct tags with their counts.
* The export_memories tool:: Full JSON dump for backup.
* Error codes:: The full error vocabulary.
@end menu
@node General tool conventions
@section General tool conventions
@cindex JSON-RPC
@cindex tool envelope
All tool calls are JSON-RPC 2.0 over HTTPS POST to @code{/mcp}:
@example
POST /mcp HTTP/1.1
Authorization: Bearer <OAuth-2.1-access-token>
Content-Type: application/json
@{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": @{
"name": "<tool-name>",
"arguments": @{ ... @}
@}
@}
@end example
The server returns a JSON-RPC response whose @code{result.content[0].text}
field holds the tool's structured payload as a JSON string. Every
successful payload begins with @code{"ok": true}; every error payload
begins with @code{"ok": false, "error": @{...@}}.
@subsection Common types
@table @asis
@item Memory ID
A 64-bit integer (Postgres @code{BIGSERIAL}). Stable for the row's
lifetime; never reused after a hard-delete.
@item Timestamps
ISO 8601 with timezone, e.g.@: @code{"2026-05-17T14:00:00Z"}.
@item Tags
Lowercase short strings, alphanumeric plus hyphen. See
@ref{Tag normalization}.
@item Vectors
Never returned in tool output; an implementation detail of the
similarity search.
@end table
@subsection Authentication
Every request must carry @code{Authorization: Bearer @var{JWT}},
where @var{JWT} is an OAuth 2.1 access token issued by OwnSona's
embedded authorization server. The resource-server side validates
the token's signature against the AS's JWKS, plus the @code{iss},
@code{aud}, @code{exp}, and (if configured) required scopes.
Missing, malformed, expired, or wrong-signature tokens return HTTP
401 with an RFC 6750 / RFC 9728 @code{WWW-Authenticate} header
naming the resource-metadata document at
@code{/.well-known/oauth-protected-resource}. MCP clients use that
header to (re-)discover the AS and refresh their token.
MCP clients normally obtain a token by the auth-code + PKCE flow:
the client discovers the AS via the well-known metadata documents,
registers itself dynamically at @code{/oauth/register} (RFC 7591),
sends the user to @code{/oauth/authorize} (where they log in with
@code{OWNSONA_LOGIN_USERNAME} / @code{OWNSONA_LOGIN_PASSWORD} and
click @b{Allow}), and exchanges the resulting code at
@code{/oauth/token} for an access token and refresh token. Refresh
tokens are rotated on each use, with stolen-token detection per
RFC 6749bis.
@node The remember tool
@section The @code{remember} tool
@cindex remember
Stores a single durable fact.
@subsection Inputs
@table @code
@item text
@b{Required string.} The fact to remember. Up to @code{MAX_TEXT_CHARS}
characters (default 16000). Texts shaped like secrets (API keys, JWTs,
PEM private-key markers) are rejected with @code{SECRET_REJECTED}.
@item tags
Array of strings. Optional. Normalized to lowercase canonical forms
(see @ref{Tag normalization}).
@item source_provider
String. Optional. Name of the LLM or client that produced this
memory. Useful for auditing.
@item importance
Number, 0--1. Optional, default 0.5. Reserved for future ranking
work; currently stored but not used in recall scoring.
@item capture_mode
String. Optional. Either @code{"explicit"} (user explicitly asked to
remember) or @code{"inferred"} (LLM decided on its own). Recorded as
provenance.
@item session_id
String. Optional. Opaque conversation identifier. Stored as-is.
@item dedup_policy
String. Optional. One of @code{"insert"} (skip the semantic-dedup
check), @code{"skip_if_near"} (return the existing id if a
near-duplicate is found), or @code{"ask"} (default: insert anyway, but
include the near-duplicates in the response).
@item expires_at
ISO 8601 timestamp. Optional. Recall excludes this memory after the
date passes. Useful for time-limited facts.
@item last_confirmed_at
ISO 8601 timestamp. Optional. When this fact was last verified as
still true. Defaults to @code{now()} on insert.
@end table
@subsection Output
@example
@{
"ok": true,
"memory_id": 123,
"message": "Ok",
"near_duplicates": [...], // present if dedup_policy = "ask"
"previously_corrected": [...] // present if any tombstones nearby
@}
@end example
If the request was rejected for being a near-duplicate
(@code{dedup_policy = "skip_if_near"}), the existing memory's id is
returned:
@example
@{
"ok": true,
"memory_id": 99,
"message": "Already remembered",
"near_duplicates": [...]
@}
@end example
@subsection Errors
@code{INVALID_INPUT}, @code{SECRET_REJECTED}, @code{EMBEDDING_ERROR},
@code{DATABASE_ERROR}.
@subsection Example
@example
@{
"name": "remember",
"arguments": @{
"text": "My son Colby lives in Los Angeles.",
"tags": ["family"],
"capture_mode": "explicit"
@}
@}
@end example
@node The remember_batch tool
@section The @code{remember_batch} tool
@cindex remember_batch
@cindex bulk import
Stores up to @code{MAX_BATCH_SIZE} (default 200) memories in a single
call. The embedding provider is invoked @b{once} for the whole batch,
making this dramatically faster than calling @code{remember} 200
times.
@subsection Inputs
@table @code
@item items
@b{Required array.} Each item has the same shape as a @code{remember}
argument: @code{text} required, plus optional @code{tags},
@code{source_provider}, @code{importance}, @code{capture_mode},
@code{session_id}, @code{dedup_policy}, @code{expires_at},
@code{last_confirmed_at}.
@item source_provider
String. Optional default applied to items that don't specify their
own.
@item dedup_policy
String. Optional default policy applied to items that don't specify
their own. Default @code{"ask"}.
@end table
@subsection Output
@example
@{
"ok": true,
"results": [
@{ "ok": true, "memory_id": 200, "message": "Ok" @},
@{ "ok": true, "memory_id": 201, "message": "Ok" @},
@{ "ok": false, "error": @{ "code": "SECRET_REJECTED", ... @} @}
],
"summary": @{
"total": 3,
"succeeded": 2,
"failed": 1
@}
@}
@end example
Per-item failures do @b{not} fail the batch. Each item's outcome
lands in its own slot in @code{results}.
@subsection Errors
Batch-level: @code{INVALID_INPUT} (e.g.@: @code{items} not an array,
or exceeds @code{MAX_BATCH_SIZE}), @code{EMBEDDING_ERROR}. Per-item
errors appear inside @code{results}.
@node The recall tool
@section The @code{recall} tool
@cindex recall
@cindex vector search
Finds memories semantically similar to a query, ordered by cosine
similarity.
@subsection Inputs
@table @code
@item query
@b{Required string.} The natural-language question or topic to search
for.
@item limit
Integer. Optional, default @code{DEFAULT_RECALL_LIMIT} (8), capped at
@code{MAX_RECALL_LIMIT} (50).
@item min_score
Number, 0--1. Optional. Filter out matches below this score.
@item tags
Array of strings. Optional. Filter to memories with at least one
matching tag.
@end table
@subsection Output
@example
@{
"ok": true,
"matches": [
@{
"id": 123,
"text": "The user's son Colby lives in Los Angeles.",
"score": 0.8421,
"created_at": "2026-05-05T12:00:00Z",
"updated_at": "2026-05-05T12:00:00Z",
"last_confirmed_at": "2026-05-05T12:00:00Z",
"tags": ["family"],
"source_provider": "openai",
"capture_mode": "explicit"
@},
...
]
@}
@end example
The tool description instructs the calling LLM to @b{prefer the most
recently confirmed match} when multiple results look contradictory:
compare @code{last_confirmed_at} first, then @code{updated_at}, then
@code{created_at}.
@cindex salience
@cindex ranking
Each match also carries the learned @code{salience} (a double) and
@code{use_count} (and @code{context_count} when it has learned context).
Results are @b{ordered} by a blend of cosine similarity, salience, and
learned per-query context --- @code{cosine * (1 + w*salience +
w*context_match)} --- with recency as a tiebreaker. Crucially the
reported @code{score} stays the @b{raw cosine similarity}, so
@code{min_score} and the contradiction heuristic above keep their original
meaning; the learning affects order, not the reported score. See
@ref{The reinforce tool} for how salience and context are taught, and
@ref{Background intelligence} for the bigger picture.
@subsection Errors
@code{INVALID_INPUT}, @code{EMBEDDING_ERROR}, @code{DATABASE_ERROR}.
@subsection Example
@example
@{
"name": "recall",
"arguments": @{
"query": "where does my son live",
"limit": 5,
"tags": ["family"]
@}
@}
@end example
@node The search_memory tool
@section The @code{search_memory} tool
@cindex search_memory
@cindex recall alias
Alias of @code{recall} with identical inputs, outputs, and errors.
Exists because some MCP-client harnesses (notably Claude's
deferred-tool-loading keyword ranker) fail to surface a tool named
@code{recall} from a generic search query and pick the wrong
fallback. Registering the same tool under a more verb-led name
gives those harnesses a discoverable handle. Both names dispatch to
the same handler.
@node The build_context_prompt tool
@section The @code{build_context_prompt} tool
@cindex build_context_prompt
@cindex prompt envelope
Returns a fully assembled prompt with relevant facts in a fixed
envelope, suitable for piping into a non-MCP LLM.
@subsection Inputs
@table @code
@item user_prompt
@b{Required string.} The user's actual question.
@item limit
Integer. Optional. Number of facts to include. Default 8.
@item max_chars
Integer. Optional. Hard cap on the total prompt size in characters.
The tool greedy-packs the highest-scoring facts that fit, stopping
when adding the next would exceed @code{max_chars}. At least one
fact is always included if any matched.
@item min_score
Number, 0--1. Optional. Lower bound on similarity for included
facts.
@item tags
Array of strings. Optional. Tag filter, same semantics as
@code{recall}.
@end table
@subsection Output
@example
@{
"ok": true,
"prompt": "The following are previously known facts:\\n\\n[fact 1]\\n\\n[fact 2]\\n\\n-----------------\\n\\nThe following is the user's prompt:\\n\\n[user prompt]"
@}
@end example
If no relevant facts were found, the body says @code{"No relevant
previously known facts were found."} instead of listing facts.
@subsection Errors
@code{INVALID_INPUT}, @code{EMBEDDING_ERROR}, @code{DATABASE_ERROR}.
@node The list_memories tool
@section The @code{list_memories} tool
@cindex list_memories
Paginated chronological listing for review, audit, and cleanup
workflows. Optional filters narrow the result to untagged rows,
very-short or very-long rows, or rows that haven't been confirmed
since a given timestamp --- the same filters are accepted by
@code{count_memories} so a client can preview the size of a cleanup
slice before listing it.
@subsection Inputs
@table @code
@item limit
Integer. Optional, default 20, capped at @code{MAX_RECALL_LIMIT}.
@item offset
Integer. Optional, default 0.
@item include_deleted
Boolean. Optional, default false. When true, soft-deleted memories
(tombstones) and expired memories are also returned.
@item untagged_only
Boolean. Optional, default false. When true, restrict to rows with
no tags (empty tag array).
@item min_chars
Integer. Optional. Return only rows whose @code{text} length is at
least this many characters.
@item max_chars
Integer. Optional. Return only rows whose @code{text} length is at
most this many characters. Useful for finding fragments
(e.g.@: @code{max_chars=20}).
@item not_confirmed_since
ISO 8601 timestamp. Optional. Return only rows that have @b{not}
been confirmed since this instant --- a row qualifies if
@code{last_confirmed_at IS NULL} (never confirmed) or
@code{last_confirmed_at < this}. Useful for finding stale memories
during cleanup.
@end table
When multiple filters are supplied they combine with AND.
@subsection Output
Same shape as @code{recall}'s @code{matches}, but with no @code{score}
field, and ordered by @code{created_at DESC}.
@subsection Errors
@code{INVALID_INPUT} (negative @code{min_chars} or @code{max_chars},
@code{min_chars > max_chars}, @code{not_confirmed_since} in the
future), @code{DATABASE_ERROR}.
@node The update_memory tool
@section The @code{update_memory} tool
@cindex update_memory
Updates an existing memory. Each field except @code{id} is optional
and follows ``omit to leave unchanged'' semantics, so a caller can
correct only the tags, only the importance, only the freshness
timestamps, etc. When @code{text} is supplied the embedding is
regenerated; when it is omitted the embedding (and the
@code{embedding_provider} / @code{embedding_model}) are left alone.
At least one of @code{text}, @code{tags}, @code{importance},
@code{expires_at}, or @code{last_confirmed_at} must be supplied.
@subsection Inputs
@table @code
@item id
@b{Required integer.} The memory's id.
@item text
String. Optional. Replacement text; same length and secret-shape
constraints as @code{remember}. Omit to leave text and embedding
unchanged.
@item tags
Array of strings. Optional. Replaces the tag list entirely if
provided.
@item importance
Number, 0--1. Optional.
@item expires_at
ISO 8601 timestamp. Optional.
@item last_confirmed_at
ISO 8601 timestamp. Optional. To refresh @b{just} this field
without otherwise touching the row, prefer the @code{confirm} tool.
@item dry_run
Boolean. Optional, default false. When true, validate the request
and report which fields would change, but make no writes.
@end table
@subsection Output
@example
@{
"ok": true,
"memory_id": 123,
"dry_run": false,
"changed_fields": ["tags", "importance"],
"message": "Ok"
@}
@end example
@code{changed_fields} lists the field names the caller asked to
change. On a dry-run, @code{message} reads @code{"Would update"}
instead. Updating a soft-deleted memory fails with
@code{NOT_FOUND} --- forget and re-add instead.
@subsection Errors
@code{INVALID_INPUT} (no fields supplied, or an out-of-range value),
@code{NOT_FOUND}, @code{SECRET_REJECTED}, @code{EMBEDDING_ERROR},
@code{DATABASE_ERROR}.
@node The update_memory_batch tool
@section The @code{update_memory_batch} tool
@cindex update_memory_batch
@cindex bulk update
Updates up to @code{MAX_BATCH_SIZE} (default 200) memories in a
single call. The embedding provider is invoked once per batch for
the items that supply new @code{text}; items that change only
metadata (tags, importance, freshness) don't call the embedder at
all, so a bulk tag-normalization pass is cheap.
Strongly prefer this over calling @code{update_memory} repeatedly
when correcting several memories at once.
@subsection Inputs
@table @code
@item items
@b{Required array.} Each item is a partial @code{update_memory}
payload: @code{id} required, plus at least one of @code{text},
@code{tags}, @code{importance}, @code{expires_at},
@code{last_confirmed_at}.
@item dry_run
Boolean. Optional, default false. When true, validate every item
and report which fields would change, but make no writes.
@end table
@subsection Output
@example
@{
"ok": true,
"dry_run": false,
"results": [
@{ "input_index": 0, "id": 41, "ok": true,
"changed_fields": ["tags"], "message": "Ok" @},
@{ "input_index": 1, "id": 42, "ok": false,
"error": @{ "code": "NOT_FOUND",
"message": "Memory 42 not found." @} @}
],
"summary": @{ "total": 2, "updated": 1, "errors": 1 @}
@}
@end example
Per-item failures (null id, unknown id, secret rejected, embedding
failure on a single text) do @b{not} fail the rest of the batch.
@subsection Errors
Batch-level: @code{INVALID_INPUT} (empty list, over 200 items),
@code{LIMIT_EXCEEDED}. Per-item errors appear inside @code{results}.
@node The confirm tool
@section The @code{confirm} tool
@cindex confirm
Refreshes a memory's @code{last_confirmed_at} to @code{now()} without
rebuilding the embedding. Cheap operation.
@subsection Inputs
@table @code
@item id
@b{Required integer.} The memory's id.
@end table
@subsection Output
@example
@{
"ok": true,
"memory_id": 123,
"last_confirmed_at": "2026-05-17T14:00:00Z"
@}
@end example
@subsection Errors
@code{INVALID_INPUT}, @code{NOT_FOUND}.
@node The reinforce tool
@section The @code{reinforce} tool
@cindex reinforce
@cindex salience
@cindex learning
Records feedback on which recalled memories were useful, so the store
learns to rank genuinely helpful facts higher over time. This is the
crux of OwnSona's learning behaviour: it adjusts a per-memory
@dfn{salience} weight that recall multiplies into its ranking. It never
edits a memory's text and never deletes anything, so it is exempt from
the @code{keep='Y'} lock (like @code{confirm}).
There is @b{no time-based decay}: a memory is never demoted merely for
being old. Salience moves only on an explicit @code{reinforce} (or a
@code{confirm}, which counts as positive feedback).
@subsection Inputs
@table @code
@item memory_ids
@b{Required array of integers.} The ids that helped (or hurt) ---
typically ids returned by @code{recall} / @code{search_memory}.
@item delta
Number, @minus{}1.0--1.0. Optional, default @code{+1}. Positive means
the memory was helpful (rank it higher); negative means it was unhelpful
or wrong (rank it lower).
@item query
Optional string. The question or topic these memories helped answer ---
ideally the same query you passed to @code{recall}. With positive
feedback, OwnSona moves each memory's learned @dfn{context centroid}
toward this query's embedding, so similar future queries surface these
memories first --- @dfn{contextual ranking}, beyond the single global
salience weight. Ignored for negative feedback.
@end table
@subsection Output
@example
@{
"ok": true,
"reinforced": [
@{ "id": 17, "salience": 0.775, "use_count": 3, "context_count": 1 @}
],
"skipped": [ 99 ],
"message": "Ok"
@}
@end example
@code{reinforced} lists the active rows that were updated; @code{skipped}
(present only when non-empty) lists requested ids that were unknown or
already soft-deleted. Duplicate ids are collapsed.
@subsection Errors
@code{INVALID_INPUT} (empty @code{memory_ids}, out-of-range @code{delta}),
@code{LIMIT_EXCEEDED}, @code{DATABASE_ERROR}.
@node The forget tool
@section The @code{forget} tool
@cindex forget
@cindex soft delete
@cindex tombstone
Deletes a memory. Soft-delete by default (the row stays but is
hidden from @code{recall}/@code{list_memories}/@code{text_search} and
becomes a tombstone that the dedup-on-write check consults). Pass
@code{hard_delete=true} to drop the row entirely.
@subsection Inputs
@table @code
@item id
@b{Required integer.} The memory's id.
@item hard_delete
Boolean. Optional, default false. When true, the row is removed from
the database completely.
@item reason
String. Optional. Free-text reason recorded as @code{forget_reason}.
Rejected with @code{INVALID_INPUT} if @code{hard_delete=true} (a hard
delete has no row to attach the reason to).
@item replaced_by_id
Integer. Optional. Id of the memory that supersedes this one. Same
restriction as @code{reason}: not allowed with @code{hard_delete}.
@item dry_run
Boolean. Optional, default false. When true, validate the request
and report what would happen, but make no changes. The response
still reports @code{already_deleted} so the caller can preview the
outcome.
@end table
@subsection Output
@example
@{
"ok": true,
"memory_id": 123,
"dry_run": false,
"already_deleted": false,
"message": "Forgotten"
@}
@end example
@code{already_deleted} is true when the row was already
soft-deleted before this call (the tombstone metadata is updated in
that case). On a dry-run @code{message} reads @code{"Would
soft-delete"} / @code{"Would hard-delete"} / @code{"Would update
tombstone (already soft-deleted)"}.
@subsection Errors
@code{INVALID_INPUT}, @code{NOT_FOUND}, @code{DATABASE_ERROR}.
@node The forget_batch tool
@section The @code{forget_batch} tool
@cindex forget_batch
@cindex bulk delete
Soft-deletes up to @code{MAX_BATCH_SIZE} (default 200) memories in a
single call. Soft-delete only by design --- a bulk hard delete has
no tombstone trail and is intentionally not exposed here. Use
@code{forget} with @code{hard_delete=true} for individual rows that
need to be erased completely.
Strongly prefer this over calling @code{forget} repeatedly during
cleanup. A batch payload carries only a list of integer ids, which
is also less likely to be misclassified by an LLM client's safety
filter than repeated single-id forgets whose context contains the
per-row memory text.
@subsection Inputs
@table @code
@item ids
@b{Required array of integers.} The memory ids to forget. Maximum
200 per call.
@item reason
String. Optional. Shared explanation recorded as
@code{forget_reason} on every soft-deleted row in the batch.
@item dry_run
Boolean. Optional, default false. When true, validate every id
and report what would happen, but make no changes. Each result
still reports @code{already_deleted} reflecting current state.
@end table
@subsection Output
@example
@{
"ok": true,
"dry_run": false,
"results": [
@{ "input_index": 0, "id": 144, "ok": true,
"already_deleted": false, "message": "Forgotten" @},
@{ "input_index": 1, "id": 999999, "ok": false,
"error": @{ "code": "NOT_FOUND",
"message": "Memory 999999 not found." @} @}
],
"summary": @{ "total": 2, "deleted": 1,
"already_deleted": 0, "errors": 1 @}
@}
@end example
If a row was already soft-deleted, the tombstone metadata is updated
(reason rewritten if supplied) and @code{already_deleted} is reported
true.
@subsection Errors
Batch-level: @code{INVALID_INPUT} (empty list, invalid reason),
@code{LIMIT_EXCEEDED} (over 200 items). Per-item errors appear
inside @code{results}.
@node The find_near_duplicates tool
@section The @code{find_near_duplicates} tool
@cindex find_near_duplicates
@cindex duplicates
@cindex cleanup
Read-only diagnostic for memory cleanup. Returns clusters of active
memories whose embeddings are at least @code{threshold} similar to
each other. Clusters are formed by union-find over qualifying pairs
(so transitively similar rows a~b~c land in the same group) and
sorted strongest-first by the max pair-similarity within each
cluster. Soft-deleted and expired rows are excluded.
Pair candidates are looked up via pgvector's HNSW index using a
fixed top-10 per row, which is more than enough at the cutoffs
typical for cleanup (@code{threshold >= 0.85}).
@subsection Inputs
@table @code
@item threshold
Number, 0.5--1.0. Optional, default 0.92. Cosine-similarity cutoff
for pair candidates. 0.95+ is typical for ``effectively identical''
rows.
@item max_groups
Integer. Optional, default 50. Hard cap 500.
@end table
@subsection Output
@example
@{
"ok": true,
"threshold": 0.92,
"groups": [
@{
"ids": [4, 5],
"max_similarity": 0.984,
"pair_count": 1,
"memories": [
@{ "id": 4, "text": "...", "tags": ["family"], ... @},
@{ "id": 5, "text": "...", "tags": ["family"], ... @}
]
@}
],
"summary": @{ "groups": 5, "pairs": 8 @}
@}
@end example
@code{pair_count} is the number of qualifying pairs inside that
cluster (always at least 1; higher means the cluster is denser).
@code{pairs} in the summary is the total raw pair count across all
returned clusters.
@subsection Errors
@code{INVALID_INPUT} (out-of-range threshold or max_groups),
@code{DATABASE_ERROR}.
@node The find_conflicts tool
@section The @code{find_conflicts} tool
@cindex find_conflicts
@cindex conflicts
@cindex contradictions
Read-only diagnostic that surfaces memories which may @emph{contradict}
each other: clusters of active memories that are both semantically close
@b{and} share at least one tag (so they describe the same labelled topic
but might disagree). Same clustering and output shape as
@code{find_near_duplicates}; the differences are the lower default
threshold and the tag gate.
The server does not decide whether the memories truly conflict --- that
is a judgement for the calling LLM (or you). Resolve a conflict by
confirming the right memory, or by storing the corrected fact with
@code{remember} using its @code{supersedes} field (the old fact is now
wrong) or @code{downweights} (it is merely outdated).
@subsection Inputs
@table @code
@item threshold
Number, 0.5--1.0. Optional, default 0.80 (lower than
@code{find_near_duplicates}, since a conflict is ``same topic, possibly
different answer'').
@item max_groups
Integer. Optional, default 50. Hard cap 500.
@end table
@subsection Output
Identical shape to @code{find_near_duplicates} (@code{groups} of
@code{ids} / @code{max_similarity} / @code{pair_count} / @code{memories},
plus a @code{summary}).
@subsection Errors
@code{INVALID_INPUT}, @code{DATABASE_ERROR}.
@node The query_relations tool
@section The @code{query_relations} tool
@cindex query_relations
@cindex relations
@cindex knowledge graph
@cindex multi-hop
Traverses the relationship graph extracted from your memories to answer
connected, @dfn{multi-hop} questions --- e.g.@: ``who is my manager's
spouse?'' --- that a flat similarity search cannot. Read-only; pure
breadth-first search over the @code{memory_relations} graph, with no LLM
call at query time.
The graph is populated by the relation-extraction background job (see
@ref{Background intelligence}); if this tool returns nothing, extraction
may not be enabled yet --- fall back to @code{recall}.
@subsection Inputs
@table @code
@item entity
@b{Required string.} The entity to start from (a person, place,
organization, thing, or ``the user''). Matched case-insensitively
against relation subjects and objects.
@item max_hops
Integer. Optional, default 2, hard cap 5. Use 1 for direct relations
only; higher values follow longer chains.
@end table
@subsection Output
@example
@{
"ok": true,
"entity": "my manager",
"relations": [
@{ "subject": "Dana", "predicate": "manages", "object": "the user",
"source_memory_id": 12, "source_text": "Dana is my manager." @}
],
"summary": @{ "count": 1 @}
@}
@end example
Edges are treated as undirected for reachability. Soft-deleted source
memories are excluded, and each edge carries its source memory's current
text. Results are capped at @code{GRAPH_MAX_RELATIONS}.
@subsection Errors
@code{INVALID_INPUT} (missing entity, @code{max_hops} < 1),
@code{DATABASE_ERROR}.
@node The text_search tool
@section The @code{text_search} tool
@cindex text_search
Plain substring search (case-insensitive). Useful for direct lookup
when you already know roughly what to find. Indexed via
@code{pg_trgm} so partial matches are fast.
@subsection Inputs
@table @code
@item text
@b{Required string.} The substring to find.
@item limit
Integer. Optional, default 20.
@end table
@subsection Output
Same shape as @code{list_memories}.
@subsection Errors
@code{INVALID_INPUT}, @code{DATABASE_ERROR}.
@node The get_memory tool
@section The @code{get_memory} tool
@cindex get_memory
Fetches a single memory by id, including soft-deleted rows. Useful
for inspecting one row surfaced by @code{recall},
@code{list_memories}, or as a near-duplicate candidate from
@code{remember}, and for examining tombstone metadata (@code{deleted_at},
@code{forget_reason}, @code{replaced_by_id}) of a soft-deleted row.
@subsection Inputs
@table @code
@item id
@b{Required integer.} The memory's id.
@end table
@subsection Output
@example
@{
"ok": true,
"memory": @{
"id": 123,
"text": "...",
"tags": ["family"],
"importance": 0.5,
"created_at": "2026-05-05T12:00:00Z",
"updated_at": "2026-05-05T12:00:00Z",
"deleted_at": null,
"forget_reason": null,
"replaced_by_id": null,
"last_confirmed_at": "2026-05-05T12:00:00Z",