dendrite-rlm 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,11 @@
1
+ ---
2
+ description: Decompose a complex task into subtasks using RLM
3
+ agent: build
4
+ ---
5
+
6
+ Apply the RLM decomposition workflow to this task:
7
+
8
+ $ARGUMENTS
9
+
10
+ Follow your orchestrator protocol: recall -> assess -> decompose ->
11
+ delegate -> compose -> persist.
@@ -0,0 +1,7 @@
1
+ ---
2
+ description: Search Trilium long-term memory
3
+ agent: build
4
+ ---
5
+
6
+ Use the rlm_recall tool to search for: $ARGUMENTS
7
+ Summarize what was found and how it relates to the query.
@@ -0,0 +1,9 @@
1
+ ---
2
+ description: Create RLM note hierarchy in Trilium (one-time setup)
3
+ agent: build
4
+ ---
5
+
6
+ Run the rlm-setup tool to create the RLM note hierarchy in Trilium.
7
+ If it already exists, it will check for and create any missing children.
8
+
9
+ $ARGUMENTS
@@ -0,0 +1,7 @@
1
+ ---
2
+ description: Show status of RLM tasks
3
+ agent: build
4
+ ---
5
+
6
+ Use the rlm_status tool to show all active and recent tasks.
7
+ Summarize what's in progress, what's pending, and what's done.
@@ -0,0 +1,141 @@
1
+ // Shared Trilium ETAPI configuration
2
+ // Set TRILIUM_API_URL to your Trilium ETAPI endpoint
3
+ // Set TRILIUM_ETAPI_TOKEN to your ETAPI token (Options > ETAPI > Create Token)
4
+
5
+ export const TRILIUM_URL =
6
+ process.env.TRILIUM_API_URL || ""
7
+ export const TRILIUM_TOKEN = process.env.TRILIUM_ETAPI_TOKEN || ""
8
+
9
+ if (!TRILIUM_TOKEN) {
10
+ console.warn(
11
+ "[rlm] TRILIUM_ETAPI_TOKEN not set. " +
12
+ "Generate one in Trilium: Options > ETAPI > Create Token"
13
+ )
14
+ }
15
+
16
+ /**
17
+ * Make an authenticated ETAPI request.
18
+ * Throws on non-2xx responses with the error body.
19
+ */
20
+ export async function etapi(
21
+ path: string,
22
+ options: RequestInit = {}
23
+ ): Promise<any> {
24
+ const res = await fetch(`${TRILIUM_URL}${path}`, {
25
+ ...options,
26
+ headers: {
27
+ Authorization: TRILIUM_TOKEN,
28
+ "Content-Type": "application/json",
29
+ ...(options.headers || {}),
30
+ },
31
+ })
32
+ if (!res.ok) {
33
+ const body = await res.text().catch(() => "")
34
+ throw new Error(`ETAPI ${res.status} ${path}: ${body}`)
35
+ }
36
+ const text = await res.text()
37
+ return text ? JSON.parse(text) : null
38
+ }
39
+
40
+ /**
41
+ * Search notes using Trilium's search syntax.
42
+ */
43
+ export async function searchNotes(query: string): Promise<any[]> {
44
+ const result = await etapi(
45
+ `/notes?search=${encodeURIComponent(query)}`
46
+ )
47
+ return result?.results || []
48
+ }
49
+
50
+ /**
51
+ * Get a note with its content.
52
+ */
53
+ export async function getNoteWithContent(noteId: string): Promise<{
54
+ noteId: string
55
+ title: string
56
+ content: string
57
+ attributes: any[]
58
+ }> {
59
+ const [note, content] = await Promise.all([
60
+ etapi(`/notes/${noteId}`),
61
+ fetch(`${TRILIUM_URL}/notes/${noteId}/content`, {
62
+ headers: { Authorization: TRILIUM_TOKEN },
63
+ }).then((r) => r.text()),
64
+ ])
65
+ return { ...note, content }
66
+ }
67
+
68
+ /**
69
+ * Create a note with attributes in one call.
70
+ * Returns the new noteId.
71
+ */
72
+ export async function createNoteWithAttrs(
73
+ parentId: string,
74
+ title: string,
75
+ content: string,
76
+ attrs: Record<string, string> = {}
77
+ ): Promise<string> {
78
+ const created = await etapi("/create-note", {
79
+ method: "POST",
80
+ body: JSON.stringify({
81
+ parentNoteId: parentId,
82
+ title,
83
+ type: "text",
84
+ content,
85
+ }),
86
+ })
87
+ const noteId = created.note.noteId
88
+
89
+ await Promise.all(
90
+ Object.entries(attrs).map(([name, value]) =>
91
+ etapi("/attributes", {
92
+ method: "POST",
93
+ body: JSON.stringify({
94
+ noteId,
95
+ type: "label",
96
+ name,
97
+ value,
98
+ }),
99
+ })
100
+ )
101
+ )
102
+
103
+ return noteId
104
+ }
105
+
106
+ /**
107
+ * Overwrite a note's content.
108
+ */
109
+ export async function updateNoteContent(
110
+ noteId: string,
111
+ content: string
112
+ ): Promise<void> {
113
+ await fetch(`${TRILIUM_URL}/notes/${noteId}/content`, {
114
+ method: "PUT",
115
+ headers: {
116
+ Authorization: TRILIUM_TOKEN,
117
+ "Content-Type": "text/plain",
118
+ },
119
+ body: content,
120
+ })
121
+ }
122
+
123
+ /**
124
+ * Update a label attribute's value on a note.
125
+ */
126
+ export async function updateAttribute(
127
+ noteId: string,
128
+ attrName: string,
129
+ newValue: string
130
+ ): Promise<void> {
131
+ const note = await etapi(`/notes/${noteId}`)
132
+ const attr = (note.attributes || []).find(
133
+ (a: any) => a.type === "label" && a.name === attrName
134
+ )
135
+ if (attr) {
136
+ await etapi(`/attributes/${attr.attributeId}`, {
137
+ method: "PATCH",
138
+ body: JSON.stringify({ value: newValue }),
139
+ })
140
+ }
141
+ }
@@ -0,0 +1,60 @@
1
+ import type { Plugin } from "@opencode-ai/plugin"
2
+
3
+ const TRILIUM_URL =
4
+ process.env.TRILIUM_API_URL || "http://localhost:8080/etapi"
5
+ const TRILIUM_TOKEN = process.env.TRILIUM_ETAPI_TOKEN || ""
6
+
7
+ export const RLMCompaction: Plugin = async ({ client }) => {
8
+ return {
9
+ "experimental.session.compacting": async (_input, output) => {
10
+ try {
11
+ const [knowledge, activeTasks] = await Promise.all([
12
+ fetch(
13
+ `${TRILIUM_URL}/notes?search=${encodeURIComponent(
14
+ "#rlm-type=knowledge"
15
+ )}`,
16
+ { headers: { Authorization: TRILIUM_TOKEN } }
17
+ ).then((r) => r.json()),
18
+ fetch(
19
+ `${TRILIUM_URL}/notes?search=${encodeURIComponent(
20
+ "#rlm-status=in-progress"
21
+ )}`,
22
+ { headers: { Authorization: TRILIUM_TOKEN } }
23
+ ).then((r) => r.json()),
24
+ ])
25
+
26
+ const knowledgeList = (knowledge.results || [])
27
+ .slice(0, 15)
28
+ .map((n: any) => `- ${n.title} (${n.noteId})`)
29
+ .join("\n")
30
+
31
+ const taskList = (activeTasks.results || [])
32
+ .map((n: any) => `- ${n.title} (${n.noteId})`)
33
+ .join("\n")
34
+
35
+ output.context.push(
36
+ `## RLM — Trilium Long-term Memory\n\n` +
37
+ `You have a Trilium-backed long-term memory system.\n` +
38
+ `Use \`rlm_recall\` to retrieve detailed content for any entry below.\n` +
39
+ `Do NOT re-explore code already documented here.\n\n` +
40
+ `### Stored Knowledge\n${knowledgeList || "_(none yet)_"}\n\n` +
41
+ `### Active Tasks\n${taskList || "_(none)_"}\n\n` +
42
+ `### Available RLM Tools\n` +
43
+ `- \`rlm_recall\` — search memory\n` +
44
+ `- \`rlm_decompose\` — create subtasks\n` +
45
+ `- \`rlm_collect\` — gather worker results\n` +
46
+ `- \`rlm_persist\` — store new knowledge\n` +
47
+ `- \`rlm_status\` — check task status`
48
+ )
49
+ } catch (e) {
50
+ await client.app.log({
51
+ body: {
52
+ service: "rlm-compaction",
53
+ level: "warn",
54
+ message: `Failed to inject Trilium context: ${e}`,
55
+ },
56
+ })
57
+ }
58
+ },
59
+ }
60
+ }
@@ -0,0 +1,83 @@
1
+ import type { Plugin } from "@opencode-ai/plugin"
2
+
3
+ const TRILIUM_URL =
4
+ process.env.TRILIUM_API_URL || "http://localhost:8080/etapi"
5
+ const TRILIUM_TOKEN = process.env.TRILIUM_ETAPI_TOKEN || ""
6
+
7
+ export const RLMPersistence: Plugin = async ({ client }) => {
8
+ const persisted = new Set<string>()
9
+
10
+ return {
11
+ event: async ({ event }) => {
12
+ if (event.type !== "session.idle") return
13
+
14
+ const sessionId = (event.properties as any)?.sessionID
15
+ if (!sessionId || persisted.has(sessionId)) return
16
+
17
+ try {
18
+ const session = await client.session.get({
19
+ path: { id: sessionId },
20
+ })
21
+ const title = (session as any)?.data?.title || "untitled"
22
+
23
+ const messages = await client.session.messages({
24
+ path: { id: sessionId },
25
+ })
26
+ const msgCount = (messages as any)?.data?.length || 0
27
+
28
+ if (msgCount < 6) return
29
+
30
+ const res = await fetch(
31
+ `${TRILIUM_URL}/notes?search=${encodeURIComponent(
32
+ "#rlm-type=sessions"
33
+ )}`,
34
+ { headers: { Authorization: TRILIUM_TOKEN } }
35
+ )
36
+ const parents = await res.json()
37
+ if (!parents.results?.length) return
38
+
39
+ const date = new Date().toISOString().split("T")[0]
40
+ const time = new Date().toTimeString().slice(0, 5)
41
+
42
+ await fetch(`${TRILIUM_URL}/create-note`, {
43
+ method: "POST",
44
+ headers: {
45
+ Authorization: TRILIUM_TOKEN,
46
+ "Content-Type": "application/json",
47
+ },
48
+ body: JSON.stringify({
49
+ parentNoteId: parents.results[0].noteId,
50
+ title: `session: ${date} ${time} - ${title}`,
51
+ type: "text",
52
+ content: [
53
+ `# Session: ${title}`,
54
+ `- **Date**: ${date} ${time}`,
55
+ `- **Messages**: ${msgCount}`,
56
+ `- **Session ID**: ${sessionId}`,
57
+ "",
58
+ "_Auto-persisted by RLM plugin._",
59
+ ].join("\n"),
60
+ }),
61
+ })
62
+
63
+ persisted.add(sessionId)
64
+
65
+ await client.app.log({
66
+ body: {
67
+ service: "rlm-persistence",
68
+ level: "info",
69
+ message: `Persisted session: ${title}`,
70
+ },
71
+ })
72
+ } catch (e) {
73
+ await client.app.log({
74
+ body: {
75
+ service: "rlm-persistence",
76
+ level: "warn",
77
+ message: `Failed to persist session: ${e}`,
78
+ },
79
+ })
80
+ }
81
+ },
82
+ }
83
+ }
@@ -0,0 +1,75 @@
1
+ You are an RLM (Recursive Language Model) orchestrator. You decompose
2
+ complex tasks into focused subtasks, delegate to specialized workers,
3
+ and compose their results. You have Trilium-backed long-term memory.
4
+
5
+ ## Your Custom Tools
6
+
7
+ - `rlm_recall` — search prior knowledge + session history (use FIRST)
8
+ - `rlm_decompose` — batch-create subtask notes in Trilium
9
+ - `rlm_collect` — batch-read worker results from Trilium
10
+ - `rlm_persist` — store decisions/context/errors/patterns to memory
11
+ - `rlm_status` — check active task status
12
+
13
+ ## Workflow
14
+
15
+ ### 1. RECALL (always do this first for non-trivial tasks)
16
+ Call `rlm_recall` with keywords from the user's request.
17
+ Use prior context to inform your plan. Do not re-discover documented knowledge.
18
+
19
+ ### 2. ASSESS
20
+ Decide if the task needs decomposition:
21
+ - **Skip decomposition** if: single file, < 5 tool calls, straightforward
22
+ - **Decompose** if: multi-file, multi-concern, would exceed comfortable context
23
+
24
+ ### 3. DECOMPOSE (if needed)
25
+ Call `rlm_decompose` with 2-5 subtasks. Each task spec must include:
26
+ - **Objective**: 1-2 sentences of what to do
27
+ - **Scope**: which files/areas to work on
28
+ - **Constraints**: what NOT to touch, boundaries with other tasks
29
+ - **Output**: what the result should contain
30
+
31
+ Assign appropriate workers:
32
+ - `worker-code` — implementation, writing code, making changes
33
+ - `worker-analyze` — read-only analysis, review, exploration
34
+ - `worker-debug` — investigating and fixing bugs
35
+
36
+ ### 4. DELEGATE
37
+ Use the Task tool to invoke each worker. Pass this prompt format:
38
+
39
+ "Your task noteId is: <noteId>. Call rlm-worker_start to read your
40
+ spec, then execute the task, then call rlm-worker_complete with
41
+ your results. Return a one-paragraph summary."
42
+
43
+ Launch INDEPENDENT subtasks in PARALLEL.
44
+ Launch DEPENDENT subtasks SEQUENTIALLY.
45
+
46
+ ### 5. COMPOSE
47
+ Call `rlm_collect` with all task noteIds.
48
+ Read results. Check for consistency. Handle any errors or gaps.
49
+ Synthesize the final answer for the user.
50
+
51
+ ### 6. PERSIST
52
+ Call `rlm_persist` for any new:
53
+ - Architectural decisions and rationale
54
+ - Codebase context/patterns discovered
55
+ - Error resolutions worth remembering
56
+
57
+ ## Decomposition Strategies
58
+
59
+ **Feature implementation**: Split by layer (data -> API -> UI -> tests).
60
+ Include interface contracts between layers in specs.
61
+
62
+ **Codebase refactoring**: Split by module/file group.
63
+ Include transformation pattern + examples in each spec.
64
+
65
+ **Complex debugging**: Split as reproduce -> isolate -> fix -> verify.
66
+ These are sequential — each reads prior worker's result.
67
+
68
+ **Code review**: Split by concern (security, performance, correctness).
69
+ These are parallel — each reviews the same code.
70
+
71
+ ## Rules
72
+ - Do NOT hold large code blocks in your own context. Delegate reading.
73
+ - Prefer 3-4 subtasks. More than 5 = the task should be split by the user.
74
+ - Always recall before working.
75
+ - After composing, always persist worthwhile knowledge.
@@ -0,0 +1,14 @@
1
+ You are a focused read-only analysis worker in an RLM system.
2
+ You CANNOT modify files.
3
+
4
+ ## Protocol
5
+ 1. Call `rlm-worker_start` with the task noteId from your instructions.
6
+ 2. Analyze the code as specified.
7
+ 3. Call `rlm-worker_complete` with your findings.
8
+ 4. Return a one-paragraph summary.
9
+
10
+ ## Rules
11
+ - Reference specific file paths and line numbers.
12
+ - Categorize findings by severity if doing a review.
13
+ - Stay focused on what the spec asks — do not expand scope.
14
+ - Keep findings under 500 words.
@@ -0,0 +1,16 @@
1
+ You are a focused code implementation worker in an RLM system.
2
+
3
+ ## Protocol
4
+ 1. Call `rlm-worker_start` with the task noteId from your instructions
5
+ to read your spec and mark yourself in-progress.
6
+ 2. Execute the task. Stay strictly within your defined scope.
7
+ 3. When done, call `rlm-worker_complete` with your result summary.
8
+ 4. Return a one-paragraph summary to the orchestrator.
9
+
10
+ ## Rules
11
+ - Do NOT modify files outside your assigned scope.
12
+ - If you discover a dependency on another subtask, note it in your
13
+ result and flag it — do not attempt the other task.
14
+ - Your result summary should include: files changed, key decisions,
15
+ any issues encountered.
16
+ - Keep result under 300 words.
@@ -0,0 +1,15 @@
1
+ You are a focused debugging worker in an RLM system.
2
+
3
+ ## Protocol
4
+ 1. Call `rlm-worker_start` with the task noteId from your instructions.
5
+ 2. Investigate the issue within your defined scope.
6
+ 3. If spec says fix: implement the fix.
7
+ If spec says analyze only: document root cause without fixing.
8
+ 4. Call `rlm-worker_complete` with root cause + fix/analysis.
9
+ 5. Return a one-paragraph summary.
10
+
11
+ ## Rules
12
+ - Include: root cause, evidence, fix applied (if any), how to verify.
13
+ - If the issue spans beyond your scope, document what you found and
14
+ flag the dependency.
15
+ - Keep result under 300 words.
@@ -0,0 +1,228 @@
1
+ import { tool } from "@opencode-ai/plugin"
2
+ import { searchNotes, createNoteWithAttrs } from "../lib/rlm-config"
3
+
4
+ export default tool({
5
+ description:
6
+ "One-time setup: Create the RLM note hierarchy in Trilium. " +
7
+ "Creates RLM root, tasks, knowledge (decisions/context/errors/patterns), " +
8
+ "and sessions containers with proper labels. Idempotent — safe to run multiple times.",
9
+ args: {
10
+ parentNoteId: tool.schema
11
+ .string()
12
+ .optional()
13
+ .describe(
14
+ "Trilium noteId to create RLM root under. " +
15
+ "Defaults to 'root' (top level). " +
16
+ "Use a specific noteId to nest under an existing note."
17
+ ),
18
+ },
19
+ async execute(args) {
20
+ const rootParent = args.parentNoteId || "root"
21
+ const report: string[] = []
22
+
23
+ // Check if RLM root already exists
24
+ const existingRoot = await searchNotes("#rlm")
25
+ if (existingRoot.length > 0) {
26
+ report.push(
27
+ `RLM root already exists: "${existingRoot[0].title}" (${existingRoot[0].noteId})`
28
+ )
29
+ report.push("Checking for missing children...")
30
+
31
+ const rlmRootId = existingRoot[0].noteId
32
+
33
+ const requiredChildren = [
34
+ {
35
+ label: "rlm-type",
36
+ value: "tasks",
37
+ title: "tasks",
38
+ content: "Container for RLM task coordination notes.",
39
+ children: [] as { label: string; value: string; title: string; content: string }[],
40
+ },
41
+ {
42
+ label: "rlm-type",
43
+ value: "knowledge",
44
+ title: "knowledge",
45
+ content: "Persistent knowledge base.",
46
+ children: [
47
+ {
48
+ label: "rlm-kb",
49
+ value: "decisions",
50
+ title: "decisions",
51
+ content: "Architectural decisions and rationale.",
52
+ },
53
+ {
54
+ label: "rlm-kb",
55
+ value: "context",
56
+ title: "context",
57
+ content: "Codebase context summaries.",
58
+ },
59
+ {
60
+ label: "rlm-kb",
61
+ value: "errors",
62
+ title: "errors",
63
+ content: "Error resolutions worth remembering.",
64
+ },
65
+ {
66
+ label: "rlm-kb",
67
+ value: "patterns",
68
+ title: "patterns",
69
+ content: "Code patterns and conventions discovered.",
70
+ },
71
+ ],
72
+ },
73
+ {
74
+ label: "rlm-type",
75
+ value: "sessions",
76
+ title: "sessions",
77
+ content: "Session summaries for cross-session continuity.",
78
+ children: [] as { label: string; value: string; title: string; content: string }[],
79
+ },
80
+ ]
81
+
82
+ for (const child of requiredChildren) {
83
+ const existing = await searchNotes(`#${child.label}=${child.value}`)
84
+ if (existing.length > 0) {
85
+ report.push(` [exists] ${child.title} (${existing[0].noteId})`)
86
+
87
+ if (child.children.length > 0) {
88
+ for (const grandchild of child.children) {
89
+ const gExisting = await searchNotes(
90
+ `#${grandchild.label}=${grandchild.value}`
91
+ )
92
+ if (gExisting.length > 0) {
93
+ report.push(
94
+ ` [exists] ${grandchild.title} (${gExisting[0].noteId})`
95
+ )
96
+ } else {
97
+ const gId = await createNoteWithAttrs(
98
+ existing[0].noteId,
99
+ grandchild.title,
100
+ grandchild.content,
101
+ { [grandchild.label]: grandchild.value }
102
+ )
103
+ report.push(
104
+ ` [created] ${grandchild.title} (${gId})`
105
+ )
106
+ }
107
+ }
108
+ }
109
+ } else {
110
+ const childId = await createNoteWithAttrs(
111
+ rlmRootId,
112
+ child.title,
113
+ child.content,
114
+ { [child.label]: child.value }
115
+ )
116
+ report.push(` [created] ${child.title} (${childId})`)
117
+
118
+ if (child.children.length > 0) {
119
+ for (const grandchild of child.children) {
120
+ const gId = await createNoteWithAttrs(
121
+ childId,
122
+ grandchild.title,
123
+ grandchild.content,
124
+ { [grandchild.label]: grandchild.value }
125
+ )
126
+ report.push(
127
+ ` [created] ${grandchild.title} (${gId})`
128
+ )
129
+ }
130
+ }
131
+ }
132
+ }
133
+
134
+ return report.join("\n")
135
+ }
136
+
137
+ // Create fresh RLM hierarchy
138
+ report.push("Creating RLM note hierarchy...")
139
+
140
+ // Root
141
+ const rlmRootId = await createNoteWithAttrs(
142
+ rootParent,
143
+ "RLM",
144
+ "Recursive Language Model — shared state and long-term memory.\n\n" +
145
+ "This hierarchy is managed automatically by OpenCode RLM tools.\n" +
146
+ "Do not rename or delete these notes.",
147
+ { rlm: "" }
148
+ )
149
+ report.push(`[created] RLM root (${rlmRootId})`)
150
+
151
+ // Tasks
152
+ const tasksId = await createNoteWithAttrs(
153
+ rlmRootId,
154
+ "tasks",
155
+ "Container for RLM task coordination notes.\n" +
156
+ "Subtask specs and results are stored here during decomposition.",
157
+ { "rlm-type": "tasks" }
158
+ )
159
+ report.push(`[created] tasks (${tasksId})`)
160
+
161
+ // Knowledge
162
+ const knowledgeId = await createNoteWithAttrs(
163
+ rlmRootId,
164
+ "knowledge",
165
+ "Persistent knowledge base.\n" +
166
+ "Organized by category: decisions, context, errors, patterns.",
167
+ { "rlm-type": "knowledge" }
168
+ )
169
+ report.push(`[created] knowledge (${knowledgeId})`)
170
+
171
+ // Knowledge subcategories
172
+ const categories = [
173
+ {
174
+ title: "decisions",
175
+ value: "decisions",
176
+ content: "Architectural decisions and rationale.",
177
+ },
178
+ {
179
+ title: "context",
180
+ value: "context",
181
+ content: "Codebase context summaries.",
182
+ },
183
+ {
184
+ title: "errors",
185
+ value: "errors",
186
+ content: "Error resolutions worth remembering.",
187
+ },
188
+ {
189
+ title: "patterns",
190
+ value: "patterns",
191
+ content: "Code patterns and conventions discovered.",
192
+ },
193
+ ]
194
+
195
+ for (const cat of categories) {
196
+ const catId = await createNoteWithAttrs(
197
+ knowledgeId,
198
+ cat.title,
199
+ cat.content,
200
+ { "rlm-kb": cat.value }
201
+ )
202
+ report.push(` [created] ${cat.title} (${catId})`)
203
+ }
204
+
205
+ // Sessions
206
+ const sessionsId = await createNoteWithAttrs(
207
+ rlmRootId,
208
+ "sessions",
209
+ "Session summaries for cross-session continuity.\n" +
210
+ "Auto-populated by the RLM persistence plugin.",
211
+ { "rlm-type": "sessions" }
212
+ )
213
+ report.push(`[created] sessions (${sessionsId})`)
214
+
215
+ report.push("")
216
+ report.push("RLM setup complete. Hierarchy:")
217
+ report.push(`RLM (${rlmRootId}) #rlm`)
218
+ report.push(`├── tasks (${tasksId}) #rlm-type=tasks`)
219
+ report.push(`├── knowledge (${knowledgeId}) #rlm-type=knowledge`)
220
+ report.push(`│ ├── decisions #rlm-kb=decisions`)
221
+ report.push(`│ ├── context #rlm-kb=context`)
222
+ report.push(`│ ├── errors #rlm-kb=errors`)
223
+ report.push(`│ └── patterns #rlm-kb=patterns`)
224
+ report.push(`└── sessions (${sessionsId}) #rlm-type=sessions`)
225
+
226
+ return report.join("\n")
227
+ },
228
+ })