51d57d44f56536183194342bae26b0525e0ff317ef6ece96065318d17554a7c61 package depend on this
| Package | Type | Relationship |
|---|---|---|
| treeos-intelligence v1.0.2 | bundle | includes |
breath:exhale1/**
2 * Reflect-Inner (Layer 2)
3 *
4 * Compresses raw inner thoughts into themes every 24 hours.
5 * 200 scattered observations become 5 specific themes.
6 */
7
8import { v4 as uuidv4 } from "uuid";
9import log from "../../seed/log.js";
10import Node from "../../seed/models/node.js";
11import Note from "../../seed/models/note.js";
12import { getNotes } from "../../seed/tree/notes.js";
13import { createNote } from "../../seed/tree/notes.js";
14import { getExtMeta, mergeExtMeta } from "../../seed/tree/extensionMetadata.js";
15
16const DAILY_MS = 24 * 60 * 60 * 1000;
17const MIN_THOUGHTS = 10;
18const MAX_REFLECTIONS = 30;
19const _reflecting = new Set();
20
21export async function init(core) {
22 const BG = core.llm.LLM_PRIORITY.BACKGROUND;
23
24 core.llm.registerRootLlmSlot?.("reflectInner");
25
26 const { runChat: _runChatDirect } = await import("../../seed/llm/conversation.js");
27 const runChat = async (opts) => _runChatDirect({ ...opts, llmPriority: BG });
28
29 core.hooks.register("breath:exhale", ({ rootId, breathRate }) => {
30 if (breathRate === "dormant") return;
31 reflect(rootId, runChat).catch(err => log.debug("ReflectInner", `Failed: ${err.message}`));
32 }, "reflect-inner");
33
34 log.info("ReflectInner", "Loaded. The tree reflects on its own thoughts.");
35 return {};
36}
37
38async function reflect(rootId, runChat) {
39 const rid = String(rootId);
40 if (_reflecting.has(rid)) return;
41 _reflecting.add(rid);
42 try { await _reflect(rootId, runChat); } finally { _reflecting.delete(rid); }
43}
44
45async function _reflect(rootId, runChat) {
46 // Find .inner node
47 const innerNode = await Node.findOne({ parent: rootId, name: ".inner" }).select("_id metadata").lean();
48 if (!innerNode) return;
49
50 // Check cooldown
51 const meta = getExtMeta(innerNode, "reflect-inner");
52 const lastReflection = meta?.lastReflection || 0;
53 if (Date.now() - lastReflection < DAILY_MS) return;
54
55 // Get tree owner for LLM access
56 const { isUserRoot } = await import("../../seed/landRoot.js");
57 const rootNode = await Node.findById(rootId).select("rootOwner systemRole parent").lean();
58 if (!isUserRoot(rootNode)) return;
59 const ownerId = String(rootNode.rootOwner);
60
61 // Read thoughts
62 const result = await getNotes({ nodeId: String(innerNode._id), limit: 200 });
63 const thoughts = result?.notes || [];
64 if (thoughts.length < MIN_THOUGHTS) return;
65
66 // Build prompt
67 const thoughtList = thoughts
68 .map(n => n.content)
69 .filter(Boolean)
70 .join("\n");
71
72 const { answer } = await runChat({
73 userId: ownerId,
74 username: "reflect-inner",
75 message:
76 `You are summarizing a tree's internal observations from the past 24 hours.\n\n` +
77 `Here are the raw thoughts:\n${thoughtList}\n\n` +
78 `Compress into exactly 5 themes. Each theme is one specific sentence.\n` +
79 `Not "the user is active" but "the user logs chicken 3x more than any other protein source."\n` +
80 `Not "the tree has gaps" but "the study queue has 4 items untouched for 2 weeks."\n\n` +
81 `Return as a numbered list. Nothing else.`,
82 mode: "tree:respond",
83 rootId,
84 slot: "reflectInner",
85 });
86
87 if (!answer || answer.length < 10) return;
88
89 // Find or create .inner.reflect node
90 const reflectNode = await getOrCreateReflectNode(String(innerNode._id));
91 if (!reflectNode) return;
92
93 // Write themes as a note
94 await createNote({
95 contentType: "text",
96 content: answer.trim(),
97 userId: ownerId,
98 nodeId: String(reflectNode._id),
99 wasAi: true,
100 });
101
102 // Update last reflection time. Re-fetch node document for mergeExtMeta (needs full doc, not ID).
103 const innerNodeFull = await Node.findById(innerNode._id).select("_id metadata").lean();
104 if (innerNodeFull) await mergeExtMeta(innerNodeFull, "reflect-inner", { lastReflection: Date.now() });
105
106 // Cap reflections
107 const noteCount = await Note.countDocuments({ nodeId: String(reflectNode._id) });
108 if (noteCount > MAX_REFLECTIONS) {
109 const oldest = await Note.find({ nodeId: String(reflectNode._id) })
110 .sort({ createdAt: 1 })
111 .limit(noteCount - MAX_REFLECTIONS)
112 .select("_id")
113 .lean();
114 if (oldest.length > 0) {
115 await Note.deleteMany({ _id: { $in: oldest.map(n => n._id) } });
116 }
117 }
118
119 log.verbose("ReflectInner", `Reflected on ${thoughts.length} thoughts: "${answer.trim().slice(0, 80)}"`);
120}
121
122async function getOrCreateReflectNode(innerNodeId) {
123 try {
124 let node = await Node.findOne({ parent: innerNodeId, name: ".reflect" }).select("_id").lean();
125 if (node) return node;
126
127 node = await Node.findOneAndUpdate(
128 { parent: innerNodeId, name: ".reflect" },
129 {
130 $setOnInsert: {
131 _id: uuidv4(),
132 name: ".reflect",
133 parent: innerNodeId,
134 status: "active",
135 children: [],
136 contributors: [],
137 metadata: {},
138 },
139 },
140 { upsert: true, new: true, lean: true },
141 );
142
143 await Node.updateOne(
144 { _id: innerNodeId },
145 { $addToSet: { children: node._id } },
146 );
147
148 log.verbose("ReflectInner", `Created .reflect node under .inner`);
149 return node;
150 } catch (err) {
151 log.debug("ReflectInner", `Failed to create .reflect node: ${err.message}`);
152 return null;
153 }
154}
1551export default {
2 name: "reflect-inner",
3 version: "1.0.2",
4 builtFor: "treeos-intelligence",
5 description:
6 "Layer 2 of the inner monologue. Every 24 hours, compresses the tree's raw thoughts " +
7 "into 5 themes. 200 scattered observations become a concise summary. Other extensions " +
8 "(intent, purpose, evolve, rings) read themes instead of parsing individual notes. " +
9 "The tree notices patterns in its own thinking.",
10
11 needs: {
12 models: ["Node", "Note"],
13 services: ["hooks", "llm"],
14 },
15
16 optional: {
17 extensions: ["breath", "inner"],
18 },
19
20 provides: {
21 models: {},
22 routes: false,
23 tools: false,
24 jobs: false,
25 orchestrator: false,
26 energyActions: {},
27 sessionTypes: {},
28 env: [],
29 cli: [],
30
31 hooks: {
32 fires: [],
33 listens: ["breath:exhale"],
34 },
35 },
36};
37
treeos ext star reflect-inner
Post comments from the CLI: treeos ext comment reflect-inner "your comment"
Max 3 comments per extension. One star and one flag per user.
Loading comments...