EXTENSION for treeos-intelligence
reflect-inner
Layer 2 of the inner monologue. Every 24 hours, compresses the tree's raw thoughts into 5 themes. 200 scattered observations become a concise summary. Other extensions (intent, purpose, evolve, rings) read themes instead of parsing individual notes. The tree notices patterns in its own thinking.
v1.0.2 by TreeOS Site 0 downloads 2 files 192 lines 5.9 KB published 38d ago
treeos ext install reflect-inner
View changelog

Manifest

Requires

  • services: hooks, llm
  • models: Node, Note

Optional

  • extensions: breath, inner
SHA256: 51d57d44f56536183194342bae26b0525e0ff317ef6ece96065318d17554a7c6

Dependents

1 package depend on this

PackageTypeRelationship
treeos-intelligence v1.0.2bundleincludes

Hooks

Listens To

  • breath:exhale

Source Code

1/**
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}
155
1export 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

Versions

Version Published Downloads
1.0.2 38d ago 0
1.0.1 46d ago 0
1.0.0 47d ago 0
0 stars
0 flags
React from the CLI: treeos ext star reflect-inner

Comments

Loading comments...

Post comments from the CLI: treeos ext comment reflect-inner "your comment"
Max 3 comments per extension. One star and one flag per user.