memuron 0.1.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. memuron/__init__.py +3 -0
  2. memuron/actions/__init__.py +12 -0
  3. memuron/actions/context.py +63 -0
  4. memuron/actions/helpers.py +88 -0
  5. memuron/actions/memory.py +340 -0
  6. memuron/actions/memory_write.py +290 -0
  7. memuron/actions/nodes.py +340 -0
  8. memuron/actions/registry.py +5 -0
  9. memuron/actions/runtime.py +37 -0
  10. memuron/actions/spaces_documents.py +720 -0
  11. memuron/actions/sync.py +155 -0
  12. memuron/application/__init__.py +1 -0
  13. memuron/application/api.py +206 -0
  14. memuron/application/app.py +103 -0
  15. memuron/application/capabilities.py +82 -0
  16. memuron/application/cli.py +35 -0
  17. memuron/application/config.py +176 -0
  18. memuron/application/mcp.py +44 -0
  19. memuron/application/mcp_oauth.py +290 -0
  20. memuron/application/registry.py +52 -0
  21. memuron/context.py +532 -0
  22. memuron/documents/__init__.py +1 -0
  23. memuron/documents/link_guardian.py +192 -0
  24. memuron/documents/linking.py +292 -0
  25. memuron/documents/parser.py +1152 -0
  26. memuron/documents/storage.py +151 -0
  27. memuron/documents/url_ingest.py +375 -0
  28. memuron/domain/__init__.py +1 -0
  29. memuron/domain/decoders.py +1 -0
  30. memuron/domain/encoders.py +185 -0
  31. memuron/domain/lifecycles.py +8 -0
  32. memuron/domain/limits.py +6 -0
  33. memuron/domain/representations.py +56 -0
  34. memuron/domain/schemas.py +581 -0
  35. memuron/domain/scope_filter.py +104 -0
  36. memuron/graphfs/__init__.py +1 -0
  37. memuron/graphfs/manual.py +635 -0
  38. memuron/graphfs/projection.py +578 -0
  39. memuron/graphfs/query.py +1782 -0
  40. memuron/graphfs/read_model.py +574 -0
  41. memuron/ingest/__init__.py +1 -0
  42. memuron/ingest/guardian.py +213 -0
  43. memuron/ingest/jobs.py +424 -0
  44. memuron/ingest/prompts.py +147 -0
  45. memuron/memory/__init__.py +1 -0
  46. memuron/memory/engine.py +35 -0
  47. memuron/memory/projections.py +452 -0
  48. memuron/memory/recipes.py +3247 -0
  49. memuron/persistence/__init__.py +1 -0
  50. memuron/persistence/db_pool.py +57 -0
  51. memuron/persistence/identity_store.py +918 -0
  52. memuron/persistence/store_helpers.py +16 -0
  53. memuron/search/__init__.py +1 -0
  54. memuron/search/fulltext.py +110 -0
  55. memuron/search/hybrid.py +284 -0
  56. memuron/search/pgvector.py +252 -0
  57. memuron/security/__init__.py +1 -0
  58. memuron/security/auth.py +143 -0
  59. memuron/security/auth_provider.py +119 -0
  60. memuron/security/authorization.py +53 -0
  61. memuron/security/clerk_scopes.py +94 -0
  62. memuron/security/clerk_webhooks.py +61 -0
  63. memuron/security/jwt_tokens.py +53 -0
  64. memuron/security/passwords.py +38 -0
  65. memuron/security/tenant.py +58 -0
  66. memuron/spaces/__init__.py +1 -0
  67. memuron/spaces/model.py +35 -0
  68. memuron/spaces/service.py +155 -0
  69. memuron/sync/__init__.py +25 -0
  70. memuron/sync/folder.py +828 -0
  71. memuron-0.1.1.dist-info/METADATA +242 -0
  72. memuron-0.1.1.dist-info/RECORD +74 -0
  73. memuron-0.1.1.dist-info/WHEEL +4 -0
  74. memuron-0.1.1.dist-info/entry_points.txt +4 -0
@@ -0,0 +1,635 @@
1
+ """Self-describing manual for the Memuron graph filesystem query language."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from copy import deepcopy
6
+ from typing import Any
7
+
8
+ COMMAND_GROUPS: dict[str, list[str]] = {
9
+ "discover": ["help", "ls", "pwd", "cd", "tree"],
10
+ "search": ["find", "rg", "grep", "semantic", "related"],
11
+ "graph": [
12
+ "graph",
13
+ "neighbors",
14
+ "neighborhood",
15
+ "links",
16
+ "parents",
17
+ "children",
18
+ "hubs",
19
+ "path",
20
+ "traverse",
21
+ "why",
22
+ ],
23
+ "transform": ["where", "sort", "head", "limit", "select", "wc"],
24
+ "inspect": ["cat", "stat", "explain"],
25
+ }
26
+
27
+ COMMANDS: dict[str, dict[str, Any]] = {
28
+ "ls": {
29
+ "summary": "List spaces, root entries, or collection members at the current cwd.",
30
+ "syntax": "ls [--collections-only] [--floating-only]",
31
+ "input": "Starts a pipeline. Ignores no prior stream.",
32
+ "output": "Spaces at /spaces; otherwise node entries visible at cwd.",
33
+ "flags": {
34
+ "--collections-only": "Return collections only.",
35
+ "--floating-only": "Return unplaced root nodes only.",
36
+ },
37
+ "examples": ["ls", "ls --collections-only", "ls | sort degree desc | limit 20"],
38
+ },
39
+ "find": {
40
+ "summary": "Structurally select nodes using indexed fields.",
41
+ "syntax": "find [--type TYPE] [--encoding ENCODING] [--id ID] [--name TEXT] [--floating]",
42
+ "input": "Starts a pipeline or filters its incoming node stream.",
43
+ "output": "Matching nodes.",
44
+ "flags": {
45
+ "--type": "Canonical structural type: text, document, image, or collection.",
46
+ "--encoding": "Exact encoding such as memory or document_chunk.",
47
+ "--id": "Exact node id.",
48
+ "--name": "Case-insensitive display-name substring; * is accepted.",
49
+ "--floating": "Only nodes not placed in a collection.",
50
+ },
51
+ "examples": [
52
+ "find --type text",
53
+ "find --encoding document_chunk | limit 10",
54
+ 'find --name "architecture"',
55
+ ],
56
+ },
57
+ "grep": {
58
+ "summary": "Search node content with a case-insensitive regex, or fixed text with --literal.",
59
+ "syntax": 'grep [--literal] "PATTERN" [--limit N]',
60
+ "input": "Starts a pipeline or searches only nodes from the incoming stream.",
61
+ "output": "Matching nodes ordered by the projection search.",
62
+ "flags": {
63
+ "--literal": "Treat PATTERN as fixed text instead of a regular expression.",
64
+ "--limit": "Maximum search hits, from 1 to 100. Default: 100.",
65
+ },
66
+ "notes": [
67
+ "Regex matching is case-insensitive by default.",
68
+ "`disco*` matches `disc` followed by zero or more `o` characters; use `disc.*` for any suffix.",
69
+ ],
70
+ "examples": [
71
+ 'grep "authentication"',
72
+ 'grep "new" | grep "disco*"',
73
+ 'grep "disc.*" --limit 20',
74
+ 'grep --literal "data[0]"',
75
+ 'find --type text | grep "authorization"',
76
+ ],
77
+ },
78
+ "rg": {
79
+ "summary": "Ripgrep-style regex search with smart-case defaults and familiar flags.",
80
+ "syntax": 'rg [-i|-s|-S] [-F] [-v] [-m N] "PATTERN"',
81
+ "input": "Starts a pipeline or searches only nodes from the incoming stream.",
82
+ "output": "Matching nodes with score=1.0.",
83
+ "flags": {
84
+ "-i, --ignore-case": "Always case-insensitive.",
85
+ "-s, --case-sensitive": "Always case-sensitive.",
86
+ "-S, --smart-case": "Case-sensitive only when PATTERN contains uppercase; this is the default.",
87
+ "-F, --fixed-strings": "Treat PATTERN as literal text.",
88
+ "-v, --invert-match": "Return nodes that do not match.",
89
+ "-m, --max-count": "Maximum results from 1 to 100.",
90
+ },
91
+ "examples": [
92
+ 'rg "discov.*"',
93
+ 'rg -i "Discovery" -m 20',
94
+ 'rg -F "data[0]"',
95
+ 'find --type text | rg -v "deprecated"',
96
+ ],
97
+ },
98
+ "semantic": {
99
+ "summary": "Retrieve memories by semantic meaning in the current space.",
100
+ "syntax": 'semantic "NATURAL LANGUAGE QUERY" [--limit N] [--links]',
101
+ "input": "Starts a pipeline, or reranks only incoming nodes.",
102
+ "output": "Memory nodes with semantic score and match=semantic.",
103
+ "flags": {
104
+ "--limit": "Maximum memory results from 1 to 100. Default: 10.",
105
+ "--links": "Allow relationship descriptions to participate in retrieval.",
106
+ },
107
+ "cost": "Embedding-backed; usually slower than rg.",
108
+ "examples": [
109
+ 'semantic "decisions about deployment reliability"',
110
+ 'find --type text | semantic "authentication risks" --limit 5',
111
+ ],
112
+ },
113
+ "related": {
114
+ "summary": "Find memories semantically related to incoming seed nodes.",
115
+ "syntax": "related [--limit N]",
116
+ "input": "Requires one or more incoming memory nodes.",
117
+ "output": "Semantically related nodes, excluding the seeds.",
118
+ "cost": "Embedding-backed.",
119
+ "examples": [
120
+ 'rg "Guardian" | head 1 | related --limit 10',
121
+ "find --id NODE_ID | related",
122
+ ],
123
+ },
124
+ "neighbors": {
125
+ "summary": "Expand incoming nodes through semantic and placement edges.",
126
+ "syntax": "neighbors [--depth N] [--direction both|inbound|outbound] [--include-self]",
127
+ "input": "Uses incoming nodes; without input it uses the current ls entries.",
128
+ "output": "Neighbor nodes. Depth is clamped to 1..5.",
129
+ "flags": {
130
+ "--depth": "Traversal depth from 1 to 5. Default: 2.",
131
+ "--direction": "both, inbound, or outbound.",
132
+ "--inbound": "Shortcut for --direction inbound.",
133
+ "--outbound": "Shortcut for --direction outbound.",
134
+ "--include-self": "Keep seed nodes in the result.",
135
+ "--placements": "Include collection placement edges; already enabled for neighbors.",
136
+ },
137
+ "examples": [
138
+ 'grep "authentication" | neighbors --depth 1',
139
+ "find --id artha_123 | neighbors --direction outbound --include-self",
140
+ ],
141
+ },
142
+ "neighborhood": {
143
+ "summary": "Alias for neighbors, matching the graph API terminology.",
144
+ "syntax": "neighborhood [--depth N] [--direction both|inbound|outbound] [--include-self]",
145
+ "input": "Uses incoming nodes; without input it uses current ls entries.",
146
+ "output": "The bounded N-hop graph neighborhood.",
147
+ "flags": {
148
+ "--depth": "Traversal depth from 1 to 5. Default: 2.",
149
+ "--direction": "both, inbound, or outbound.",
150
+ "--include-self": "Keep seed nodes in the result.",
151
+ },
152
+ "examples": [
153
+ 'semantic "authentication" | head 1 | neighborhood --depth 2',
154
+ "find --id NODE_ID | neighborhood --include-self",
155
+ ],
156
+ },
157
+ "links": {
158
+ "summary": "Follow graph links from incoming or current nodes.",
159
+ "syntax": "links [--depth N] [--direction both|inbound|outbound] [--placements] [--include-self]",
160
+ "input": "Uses incoming nodes; without input it uses the current ls entries.",
161
+ "output": "Linked nodes.",
162
+ "flags": {
163
+ "--depth": "Traversal depth from 1 to 5. Default: 1.",
164
+ "--direction": "both, inbound, or outbound.",
165
+ "--inbound": "Shortcut for --direction inbound.",
166
+ "--outbound": "Shortcut for --direction outbound.",
167
+ "--placements": "Also traverse collection placement edges.",
168
+ "--include-self": "Keep seed nodes in the result.",
169
+ },
170
+ "examples": ["find --id artha_123 | links", "links --placements --depth 2"],
171
+ },
172
+ "parents": {
173
+ "summary": "Return structural parents of incoming nodes.",
174
+ "syntax": "parents [--all]",
175
+ "input": "Uses incoming nodes; without input it uses current ls entries.",
176
+ "output": "Parent collection nodes. --all also considers semantic inbound edges.",
177
+ "examples": ["find --id NODE_ID | parents", 'rg "chunk text" | parents'],
178
+ },
179
+ "children": {
180
+ "summary": "Return structural children of incoming collection nodes.",
181
+ "syntax": "children [--all]",
182
+ "input": "Uses incoming nodes; without input it uses current ls entries.",
183
+ "output": "Placed child nodes. --all also considers semantic outbound edges.",
184
+ "examples": ["find --id COLLECTION_ID | children", "ls --collections-only | children"],
185
+ },
186
+ "hubs": {
187
+ "summary": "Return the highest-degree nodes in the current or incoming stream.",
188
+ "syntax": "hubs [--limit N]",
189
+ "input": "Starts from all nodes in the space or ranks incoming nodes.",
190
+ "output": "Nodes ordered by projected graph degree.",
191
+ "examples": ["hubs", "find --type text | hubs --limit 20"],
192
+ },
193
+ "path": {
194
+ "summary": "Find the shortest projected graph path between two nodes.",
195
+ "syntax": "path FROM_ID TO_ID | INPUT_ONE_NODE | path TO_ID",
196
+ "input": "Two ids, or exactly one incoming node plus a destination id.",
197
+ "output": "Ordered path nodes with path_index, path_length, and edge reason in via.",
198
+ "examples": ["path NODE_A NODE_B", "find --id NODE_A | path NODE_B"],
199
+ },
200
+ "traverse": {
201
+ "summary": "Follow graph edges whose descriptions semantically match a query.",
202
+ "syntax": 'traverse "EDGE QUERY" [--depth N] [--threshold 0..1]',
203
+ "input": "Requires exactly one incoming seed node.",
204
+ "output": "Visited nodes with hop_distance and edge similarity score.",
205
+ "cost": "Embedding-backed graph traversal.",
206
+ "examples": [
207
+ 'find --id NODE_ID | traverse "deployment consequences"',
208
+ 'semantic "Guardian" | head 1 | traverse "design decisions" --depth 2',
209
+ ],
210
+ },
211
+ "why": {
212
+ "summary": "Explain the direct projected relationship between two nodes.",
213
+ "syntax": "why OTHER_NODE_ID",
214
+ "input": "Requires exactly one incoming source node.",
215
+ "output": "Placement or semantic edge descriptions; suggests path when no direct edge exists.",
216
+ "examples": ["find --id NODE_A | why NODE_B", "path NODE_A NODE_B | head 1 | why NODE_B"],
217
+ },
218
+ "tree": {
219
+ "summary": "Show the bounded collection and placement hierarchy.",
220
+ "syntax": "tree [--depth N]",
221
+ "input": "Starts a pipeline using cwd as the root.",
222
+ "output": "Flattened tree nodes with tree_depth and tree_prefix.",
223
+ "examples": ["tree", "tree --depth 5 | select tree_depth,type,display,id"],
224
+ },
225
+ "graph": {
226
+ "summary": "Export a bounded node-and-edge graph payload from the current space.",
227
+ "syntax": "graph [--limit N]",
228
+ "input": "Starts a pipeline and uses the current space.",
229
+ "output": "One graph item containing nodes, internal edges, and counts.",
230
+ "notes": [
231
+ "Use select/tree/neighborhood for smaller agent-facing responses when full topology is unnecessary.",
232
+ "The related graph commands below cover every graph API operation.",
233
+ ],
234
+ "commands": [
235
+ "graph",
236
+ "neighborhood",
237
+ "neighbors",
238
+ "links",
239
+ "parents",
240
+ "children",
241
+ "hubs",
242
+ "path",
243
+ "why",
244
+ "traverse",
245
+ "tree",
246
+ ],
247
+ "examples": ["graph --limit 100", "graph --limit 25 | select node_count,edge_count"],
248
+ },
249
+ "where": {
250
+ "summary": "Filter an incoming stream using a field comparison.",
251
+ "syntax": "where FIELD OPERATOR VALUE",
252
+ "input": "Uses incoming nodes; without input it uses the current ls entries.",
253
+ "output": "Nodes satisfying the comparison.",
254
+ "fields": ["degree", "display", "encoding", "type"],
255
+ "operators": ["=", "==", "!=", ">", ">=", "<", "<=", "~"],
256
+ "notes": ["Use ~ for case-insensitive text containment.", "Numeric operators are intended for degree."],
257
+ "examples": ["ls | where degree > 5", 'find --type text | where display ~ "policy"'],
258
+ },
259
+ "sort": {
260
+ "summary": "Sort a node stream by a supported field.",
261
+ "syntax": "sort FIELD [asc|desc]",
262
+ "input": "Uses incoming nodes; without input it uses the current ls entries.",
263
+ "output": "The same nodes in sorted order.",
264
+ "fields": ["degree", "display", "encoding", "id", "type", "score"],
265
+ "examples": ["ls | sort degree desc", "find --type text | sort display asc"],
266
+ },
267
+ "limit": {
268
+ "summary": "Keep the first N items from a stream.",
269
+ "syntax": "limit N",
270
+ "input": "Uses incoming nodes; without input it uses the current ls entries.",
271
+ "output": "At most N items. N must be from 0 to 1000.",
272
+ "examples": ["ls | limit 20", 'grep "policy" | limit 5'],
273
+ },
274
+ "head": {
275
+ "summary": "Keep the first N items; familiar alias for limit.",
276
+ "syntax": "head N",
277
+ "input": "Uses incoming nodes; without input it uses current ls entries.",
278
+ "output": "At most N items.",
279
+ "examples": ["ls | head 20", 'semantic "policy" | head 5'],
280
+ },
281
+ "select": {
282
+ "summary": "Project only selected fields to reduce agent context usage.",
283
+ "syntax": "select FIELD[,FIELD...]",
284
+ "input": "Uses incoming items; without input it uses current ls entries.",
285
+ "output": "Items containing exactly the selected fields.",
286
+ "examples": [
287
+ "ls | select id,type,display",
288
+ 'semantic "policy" | select id,preview,score',
289
+ ],
290
+ },
291
+ "wc": {
292
+ "summary": "Summarize stream size and content volume.",
293
+ "syntax": "wc [--tokens]",
294
+ "input": "Uses incoming items; without input it uses current ls entries.",
295
+ "output": "One aggregate with items, nodes, characters, words, and bytes.",
296
+ "flags": {"--tokens": "Also include a rough character-based token estimate."},
297
+ "examples": ['rg "policy" | wc --tokens', "find --encoding document_chunk | wc"],
298
+ },
299
+ "explain": {
300
+ "summary": "Inspect a pipeline execution plan without running it.",
301
+ "syntax": 'explain "PIPELINE"',
302
+ "input": "Standalone only. Quote the whole pipeline.",
303
+ "output": "Stages, execution backend, and relative estimated cost.",
304
+ "examples": [
305
+ 'explain "rg policy | neighbors --depth 2 | head 10"',
306
+ 'explain "semantic deployment | related --limit 5"',
307
+ ],
308
+ },
309
+ "cat": {
310
+ "summary": "Return complete node content.",
311
+ "syntax": "cat NODE_ID",
312
+ "input": "A node id, or incoming nodes when NODE_ID is omitted.",
313
+ "output": "Nodes with view=content.",
314
+ "examples": ["cat artha_123", 'grep "decision" | limit 1 | cat'],
315
+ },
316
+ "stat": {
317
+ "summary": "Return node metadata, placements, and semantic links.",
318
+ "syntax": "stat NODE_ID",
319
+ "input": "A node id, or incoming nodes when NODE_ID is omitted.",
320
+ "output": "Nodes with view=stat, placements, and semantic_links.",
321
+ "examples": ["stat artha_123", "find --id artha_123 | stat"],
322
+ },
323
+ "cd": {
324
+ "summary": "Resolve a new cwd. The client must use navigation.cwd on its next request.",
325
+ "syntax": "cd DESTINATION",
326
+ "input": "Standalone only.",
327
+ "output": "A navigation object; it does not mutate server-side session state.",
328
+ "destinations": [
329
+ "/spaces",
330
+ "space.personal",
331
+ "/spaces/space.personal/collections/COLLECTION_ID",
332
+ "COLLECTION_ID",
333
+ "@NODE_ID",
334
+ "..",
335
+ ],
336
+ "examples": ["cd space.personal", "cd artha_collection_id", "cd @artha_node_id", "cd .."],
337
+ },
338
+ "pwd": {
339
+ "summary": "Return the cwd supplied with this request.",
340
+ "syntax": "pwd",
341
+ "input": "Standalone only.",
342
+ "output": "One cwd item.",
343
+ "examples": ["pwd"],
344
+ },
345
+ "help": {
346
+ "summary": "Read this manual or one focused topic.",
347
+ "syntax": "help [overview|commands|search|graph|compact|pipelines|paths|errors|examples|COMMAND]",
348
+ "input": "Standalone only.",
349
+ "output": "Structured help items.",
350
+ "examples": ["help", "help grep", "help pipelines", "help errors"],
351
+ },
352
+ }
353
+
354
+ TOPICS: dict[str, dict[str, Any]] = {
355
+ "overview": {
356
+ "title": "How to use Memuron graph filesystem queries",
357
+ "instructions": [
358
+ "POST one query string and one cwd to /memuron/spaces/query.",
359
+ "Begin at cwd=/spaces with query=ls to discover spaces.",
360
+ "Use the returned space path as cwd, then run ls, find, or grep.",
361
+ "Chain commands with |. Each stage receives the previous stage's node stream.",
362
+ "Use ids returned by commands with cat, stat, cd, links, and find --id.",
363
+ "cd is declarative: copy navigation.cwd into the next request's cwd.",
364
+ "Use `type` for the structural node category. node_type is deprecated.",
365
+ "Use `encoding` for subtype or origin, for example memory or document_chunk.",
366
+ "When a query fails, follow detail.hint and detail.examples; use detail.help_query for more.",
367
+ "Use `help search` for retrieval, `help graph` for graph operations, and `help compact` to reduce output tokens.",
368
+ ],
369
+ "field_glossary": {
370
+ "type": "Canonical structural category: text, image, document, or collection.",
371
+ "node_type": "Deprecated alias for type; present only for compatibility.",
372
+ "encoding": "Subtype/origin such as memory, document_chunk, document_collection, pdf_source, image_vlm.",
373
+ "degree": "Total projected graph connectivity in the current space, including placement and semantic edges.",
374
+ "preview": "Short content preview. Use cat for full content.",
375
+ "content_length": "Full content length in characters.",
376
+ "truncated": "Whether preview/display omits some content.",
377
+ },
378
+ "first_requests": [
379
+ {"cwd": "/spaces", "query": "ls"},
380
+ {"cwd": "/spaces/space.personal", "query": "ls"},
381
+ {"cwd": "/spaces/space.personal", "query": 'grep "topic" | limit 10'},
382
+ ],
383
+ },
384
+ "search": {
385
+ "title": "Lexical and semantic retrieval",
386
+ "instructions": [
387
+ "Use rg for regex or fixed-text content search with ripgrep-style smart case.",
388
+ "Use semantic when wording may differ but meaning is similar.",
389
+ "Use related after obtaining seed nodes to retrieve conceptually similar memories.",
390
+ "Use grep when you specifically want always-case-insensitive regex behavior.",
391
+ "Filter structurally with find before searching when type or encoding is known.",
392
+ ],
393
+ "decision_guide": {
394
+ "exact_or_pattern": "rg",
395
+ "literal_special_characters": "rg -F",
396
+ "meaning_or_concept": "semantic",
397
+ "similar_to_known_memory": "related",
398
+ },
399
+ "examples": [
400
+ 'rg "deploy.*fail" | select id,preview',
401
+ 'semantic "why production deployments failed" --limit 10',
402
+ 'semantic "Guardian architecture" | head 1 | related --limit 10',
403
+ ],
404
+ },
405
+ "graph": {
406
+ "title": "Graph operations",
407
+ "instructions": [
408
+ "Use neighbors for bounded N-hop expansion across semantic and placement edges.",
409
+ "Use links for semantic links only unless --placements is supplied.",
410
+ "Use parents and children for collection placement direction.",
411
+ "Use hubs to rank highly connected nodes.",
412
+ "Use path for shortest paths and why for direct edge explanations.",
413
+ "Use traverse when edge descriptions should be selected by semantic meaning.",
414
+ "Use tree for the collection hierarchy.",
415
+ ],
416
+ "commands": [
417
+ "neighbors",
418
+ "neighborhood",
419
+ "links",
420
+ "parents",
421
+ "children",
422
+ "hubs",
423
+ "path",
424
+ "why",
425
+ "traverse",
426
+ "tree",
427
+ "graph",
428
+ ],
429
+ "examples": [
430
+ 'semantic "authentication" | head 1 | neighbors --depth 2',
431
+ "path NODE_A NODE_B",
432
+ "find --id NODE_A | why NODE_B",
433
+ 'find --id NODE_A | traverse "decisions caused by this" --depth 2',
434
+ ],
435
+ },
436
+ "compact": {
437
+ "title": "Reducing response size and measuring context",
438
+ "instructions": [
439
+ "Use head or limit to bound item count.",
440
+ "Use select to return only fields the agent needs.",
441
+ "Use wc --tokens before cat when a stream may contain large content.",
442
+ "Prefer preview over content until full text is required.",
443
+ "Use explain to inspect relative pipeline cost without execution.",
444
+ ],
445
+ "examples": [
446
+ 'semantic "deployment" | head 5 | select id,preview,score',
447
+ 'find --encoding document_chunk | wc --tokens',
448
+ 'explain "semantic deployment | neighbors --depth 2 | head 10"',
449
+ ],
450
+ },
451
+ "pipelines": {
452
+ "title": "Pipeline model",
453
+ "instructions": [
454
+ "A pipeline is left-to-right: source | filter | traversal | ordering | limit | view.",
455
+ "Typical sources are ls, find, rg, grep, semantic, tree, and hubs.",
456
+ "where, sort, head, limit, select, links, neighbors, parents, children, related, and wc transform streams.",
457
+ "cat and stat can consume a stream, so `grep \"x\" | limit 1 | cat` is valid.",
458
+ "cd, pwd, and help must be standalone.",
459
+ "Quote multi-word values so the parser keeps them together.",
460
+ ],
461
+ "examples": [
462
+ 'find --type text | grep "access policy" | neighbors --depth 1 | limit 20',
463
+ "ls | where degree > 2 | sort degree desc | limit 10",
464
+ 'grep "decision" | limit 1 | stat',
465
+ ],
466
+ },
467
+ "documents": {
468
+ "title": "Document representation",
469
+ "instructions": [
470
+ "Documents are represented as a collection-backed subgraph.",
471
+ "The root document container has type=collection and encoding=document_collection.",
472
+ "The original uploaded file is a child node with type=document or type=image and an encoding like pdf_source.",
473
+ "Chunks are child text nodes with encoding=document_chunk.",
474
+ "Document containers include payload.collection_kind=document and payload.document with source metadata.",
475
+ ],
476
+ },
477
+ "paths": {
478
+ "title": "cwd and navigation",
479
+ "valid_cwds": [
480
+ "/spaces",
481
+ "/spaces/SPACE_TOKEN",
482
+ "/spaces/SPACE_TOKEN/collections/COLLECTION_ID",
483
+ ],
484
+ "instructions": [
485
+ "cwd is explicit request state, not a server-side shell session.",
486
+ "At /spaces, ls returns available space tokens and paths.",
487
+ "At a space root, ls returns collections plus floating nodes.",
488
+ "At a collection cwd, ls returns placed members.",
489
+ "After cd, use response.navigation.cwd as the next request's cwd.",
490
+ ],
491
+ },
492
+ "errors": {
493
+ "title": "Error recovery contract",
494
+ "fields": {
495
+ "code": "Stable machine-readable category.",
496
+ "message": "What was wrong.",
497
+ "hint": "The most likely correction.",
498
+ "examples": "Known-good replacement queries.",
499
+ "help_query": "A query that returns the relevant manual section.",
500
+ "stage": "One-based failing pipeline stage when available.",
501
+ "command": "Failing command when available.",
502
+ },
503
+ "instructions": [
504
+ "Do not blindly retry the same invalid query.",
505
+ "Apply hint, preserve the current cwd, and retry a corrected query.",
506
+ "If uncertain, execute help_query and inspect the structured result.",
507
+ ],
508
+ },
509
+ }
510
+
511
+ WORKED_EXAMPLES = [
512
+ {
513
+ "goal": "Discover the system",
514
+ "steps": [
515
+ {"cwd": "/spaces", "query": "ls"},
516
+ {"cwd": "/spaces/SPACE_TOKEN_FROM_RESULT", "query": "ls"},
517
+ ],
518
+ },
519
+ {
520
+ "goal": "Find relevant memories and inspect context",
521
+ "steps": [
522
+ {"cwd": "/spaces/space.personal", "query": 'grep "authentication" | limit 10'},
523
+ {
524
+ "cwd": "/spaces/space.personal",
525
+ "query": 'grep "authentication" | neighbors --depth 1 | limit 20',
526
+ },
527
+ ],
528
+ },
529
+ {
530
+ "goal": "Inspect one exact node",
531
+ "steps": [
532
+ {"cwd": "/spaces/space.personal", "query": "find --id NODE_ID | stat"},
533
+ {"cwd": "/spaces/space.personal", "query": "cat NODE_ID"},
534
+ ],
535
+ },
536
+ ]
537
+
538
+
539
+ def filesystem_manual(topic: str | None = None) -> dict[str, Any]:
540
+ normalized = (topic or "overview").strip().lower()
541
+ if normalized == "commands":
542
+ return {
543
+ "topic": "commands",
544
+ "title": "Command index",
545
+ "start_here": [
546
+ {"topic": "search", "purpose": "Choose lexical, regex, semantic, or related retrieval."},
547
+ {"topic": "graph", "purpose": "Navigate, export, traverse, and explain graph structure."},
548
+ {"topic": "compact", "purpose": "Bound output and reduce agent context usage."},
549
+ ],
550
+ "groups": deepcopy(COMMAND_GROUPS),
551
+ "commands": {
552
+ name: {"summary": spec["summary"], "syntax": spec["syntax"]}
553
+ for name, spec in COMMANDS.items()
554
+ },
555
+ }
556
+ if normalized == "examples":
557
+ return {"topic": "examples", "title": "Worked examples", "examples": deepcopy(WORKED_EXAMPLES)}
558
+ if normalized == "graph":
559
+ return {
560
+ "topic": "graph",
561
+ **deepcopy(TOPICS["graph"]),
562
+ "export_command": deepcopy(COMMANDS["graph"]),
563
+ }
564
+ if normalized in COMMANDS:
565
+ return {
566
+ "topic": normalized,
567
+ "title": f"{normalized} command",
568
+ "command": normalized,
569
+ **deepcopy(COMMANDS[normalized]),
570
+ }
571
+ if normalized in TOPICS:
572
+ return {"topic": normalized, **deepcopy(TOPICS[normalized])}
573
+ raise KeyError(normalized)
574
+
575
+
576
+ def manual_topics() -> list[str]:
577
+ return [
578
+ "overview",
579
+ "commands",
580
+ "search",
581
+ "graph",
582
+ "compact",
583
+ "pipelines",
584
+ "paths",
585
+ "documents",
586
+ "errors",
587
+ "examples",
588
+ *COMMANDS,
589
+ ]
590
+
591
+
592
+ def help_items(topic: str | None = None) -> list[dict[str, Any]]:
593
+ manual = filesystem_manual(topic)
594
+ if manual["topic"] == "commands":
595
+ index = {
596
+ "kind": "help",
597
+ "display": "Memuron command index",
598
+ "description": "Start with the search, graph, or compact guide; then inspect focused command help.",
599
+ "topic": "commands",
600
+ "manual": manual,
601
+ }
602
+ guides = [
603
+ {
604
+ "kind": "help",
605
+ "display": f"help {guide['topic']}",
606
+ "description": guide["purpose"],
607
+ "topic": guide["topic"],
608
+ "manual": filesystem_manual(guide["topic"]),
609
+ }
610
+ for guide in manual["start_here"]
611
+ ]
612
+ commands = [
613
+ {
614
+ "kind": "help",
615
+ "display": spec["syntax"],
616
+ "description": spec["summary"],
617
+ "topic": name,
618
+ "group": next(
619
+ (group for group, names in COMMAND_GROUPS.items() if name in names),
620
+ "other",
621
+ ),
622
+ "manual": spec,
623
+ }
624
+ for name, spec in manual["commands"].items()
625
+ ]
626
+ return [index, *guides, *commands]
627
+ return [
628
+ {
629
+ "kind": "help",
630
+ "display": str(manual["title"]),
631
+ "description": str(manual.get("summary") or "Structured Memuron query manual."),
632
+ "topic": manual["topic"],
633
+ "manual": manual,
634
+ }
635
+ ]