9cbc8b18f4b1fdd993c8fd9e0696dbb8ebdc6f6cd7a52fa40e66ad66e1263c2e1 package depend on this
| Package | Type | Relationship |
|---|---|---|
| treeos-intelligence v1.0.2 | bundle | includes |
breath:exhaleenrichContext1/**
2 * Prediction (Layer 7)
3 *
4 * The tree knows what's coming. Reads rings (completed growth cycles)
5 * and the current narrative to project forward. Pattern recognition
6 * across time. Not ML. Not statistics. The tree has seen this season
7 * before and knows what comes next.
8 *
9 * Updates monthly (same cadence as narrative, but offset by 2 weeks
10 * so it runs after the narrative has updated).
11 *
12 * Writes predictions to metadata.prediction on the root:
13 * predictions: [
14 * { pattern, expectation, confidence, basedOn }
15 * ]
16 *
17 * enrichContext injects predictions so the AI adjusts behavior.
18 * "Don't interpret November silence as abandonment. The pattern
19 * has held for two years."
20 *
21 * The loop: predictions feed back into Layer 1 (inner) through
22 * enrichContext. Inner reads predictions as context when generating
23 * thoughts. "I predicted the user would slow down. They didn't. Why?"
24 * Layer 1 thought -> Layer 2 theme -> Layer 3 comparison -> Layer 4
25 * narrative -> Layer 7 prediction -> back to Layer 1. Each cycle
26 * deeper than the last.
27 */
28
29import log from "../../seed/log.js";
30import Node from "../../seed/models/node.js";
31import Note from "../../seed/models/note.js";
32import { getNotes } from "../../seed/tree/notes.js";
33import { getExtMeta, mergeExtMeta } from "../../seed/tree/extensionMetadata.js";
34import { parseJsonSafe } from "../../seed/orchestrators/helpers.js";
35
36const MONTHLY_MS = 30 * 24 * 60 * 60 * 1000;
37const OFFSET_MS = 14 * 24 * 60 * 60 * 1000; // 2 weeks after narrative
38
39export async function init(core) {
40 const BG = core.llm.LLM_PRIORITY.BACKGROUND;
41
42 core.llm.registerRootLlmSlot?.("prediction");
43
44 const { runChat: _runChatDirect } = await import("../../seed/llm/conversation.js");
45 const runChat = async (opts) => _runChatDirect({ ...opts, llmPriority: BG });
46
47 // ── breath:exhale: check monthly cadence, predict if due ───────────
48
49 core.hooks.register("breath:exhale", ({ rootId, breathRate }) => {
50 if (breathRate === "dormant") return;
51 predict(rootId, runChat).catch(err =>
52 log.debug("Prediction", `Failed: ${err.message}`)
53 );
54 }, "prediction");
55
56 // ── enrichContext: inject predictions ──────────────────────────────
57 // The AI knows what the tree expects. It adjusts behavior accordingly.
58 // Inner (Layer 1) reads this too, completing the loop.
59
60 core.hooks.register("enrichContext", async ({ context, node, meta }) => {
61 const pred = meta?.prediction;
62 if (!pred?.predictions?.length) return;
63
64 context.treePredictions = {
65 predictions: pred.predictions,
66 updatedAt: pred.updatedAt,
67 };
68 }, "prediction");
69
70 log.info("Prediction", "Loaded. The tree knows what's coming.");
71 return {};
72}
73
74// ─────────────────────────────────────────────────────────────────────────
75// PREDICTION SYNTHESIS
76// ─────────────────────────────────────────────────────────────────────────
77
78async function predict(rootId, runChat) {
79 const { isUserRoot } = await import("../../seed/landRoot.js");
80 const rootNode = await Node.findById(rootId).select("rootOwner name metadata systemRole parent").lean();
81 if (!isUserRoot(rootNode)) return;
82 const ownerId = String(rootNode.rootOwner);
83
84 // Check cooldown (monthly, offset 2 weeks from narrative)
85 const meta = rootNode.metadata instanceof Map
86 ? rootNode.metadata.get("prediction")
87 : rootNode.metadata?.prediction;
88 const lastPrediction = meta?.lastPrediction || 0;
89 if (Date.now() - lastPrediction < MONTHLY_MS) return;
90
91 // Need narrative to exist first (it runs 2 weeks before us)
92 const narrativeMeta = rootNode.metadata instanceof Map
93 ? rootNode.metadata.get("narrative")
94 : rootNode.metadata?.narrative;
95 if (!narrativeMeta?.identity) return;
96
97 // Read completed rings (temporal depth)
98 const ringsNode = await Node.findOne({ parent: rootId, name: ".rings" }).select("_id").lean();
99 let ringsData = [];
100 if (ringsNode) {
101 const ringsResult = await getNotes({ nodeId: String(ringsNode._id), limit: 10 });
102 ringsData = (ringsResult?.notes || []).map(n => {
103 try { return JSON.parse(n.content); } catch { return null; }
104 }).filter(Boolean);
105 }
106
107 // Read recent comparisons (Layer 3) for current trajectory
108 const innerNode = await Node.findOne({ parent: rootId, name: ".inner" }).select("_id").lean();
109 let recentComparisons = "";
110 if (innerNode) {
111 const reflectNode = await Node.findOne({ parent: String(innerNode._id), name: ".reflect" }).select("_id").lean();
112 if (reflectNode) {
113 const compareNode = await Node.findOne({ parent: String(reflectNode._id), name: ".compare" }).select("_id").lean();
114 if (compareNode) {
115 const compResult = await getNotes({ nodeId: String(compareNode._id), limit: 4 });
116 recentComparisons = (compResult?.notes || []).map(n => n.content).join("\n---\n");
117 }
118 }
119 }
120
121 // Read previous predictions to check accuracy
122 const previousPredictions = meta?.predictions || [];
123 const prevText = previousPredictions.length > 0
124 ? previousPredictions.map(p =>
125 `Predicted: ${p.expectation} (confidence: ${p.confidence}, based on: ${p.basedOn})`
126 ).join("\n")
127 : "(no previous predictions)";
128
129 // Build rings summary
130 const ringsSummary = ringsData.length > 0
131 ? ringsData.map(r =>
132 `Ring: ${r.started?.slice(0, 7) || "?"} to ${r.ended?.slice(0, 7) || "?"} (${r.duration})\n` +
133 ` Topics: ${(r.dominantTopics || []).join(", ")}\n` +
134 ` Phases: ${(r.phaseHistory || []).map(p => p.phase).join(" -> ")}\n` +
135 ` Character: ${r.character || "?"}\n` +
136 ` Essence: ${r.essence || "?"}`
137 ).join("\n\n")
138 : "(no completed rings yet, tree is too young for temporal patterns)";
139
140 const treeName = rootNode.name || "this tree";
141
142 const { answer } = await runChat({
143 userId: ownerId,
144 username: "prediction",
145 message:
146 `You are generating predictions for a tree called "${treeName}" based on its history.\n\n` +
147
148 `CURRENT NARRATIVE (who the tree is now):\n${narrativeMeta.identity}\n\n` +
149
150 `COMPLETED RINGS (past growth cycles with seasonal data):\n${ringsSummary}\n\n` +
151
152 `RECENT WEEKLY COMPARISONS (current trajectory):\n${recentComparisons || "(none)"}\n\n` +
153
154 `PREVIOUS PREDICTIONS (check accuracy):\n${prevText}\n\n` +
155
156 `Generate 2 to 4 predictions. Each prediction has:\n` +
157 `- pattern: what recurring pattern you identified across rings or comparisons\n` +
158 `- expectation: what will likely happen next based on that pattern\n` +
159 `- confidence: "high" (pattern held across 2+ rings), "medium" (emerging pattern), "low" (first time noticing)\n` +
160 `- basedOn: which rings or comparisons support this\n\n` +
161
162 `Examples of good predictions:\n` +
163 `- Pattern: "Activity drops every November-December across 2 rings." ` +
164 `Expectation: "Prepare for decreased fitness logging next month. Don't interpret silence as abandonment." ` +
165 `Confidence: "high". Based on: "Ring 1 and Ring 2 both show dormancy in winter."\n` +
166 `- Pattern: "The user starts study topics enthusiastically then abandons them around week 3." ` +
167 `Expectation: "Current study topic will likely stall in 1-2 weeks. Pre-emptive check-in recommended." ` +
168 `Confidence: "medium". Based on: "3 study topics abandoned at similar durations in comparisons."\n\n` +
169
170 `If previous predictions were wrong, note that too. "Predicted slowdown in March. User increased instead. ` +
171 `The pattern may not hold seasonally. Revise."\n\n` +
172
173 `If the tree is too young (no rings, few comparisons), say so honestly. ` +
174 `Don't fabricate patterns from insufficient data.\n\n` +
175
176 `Return JSON only:\n` +
177 `{ "predictions": [ { "pattern": "...", "expectation": "...", "confidence": "high|medium|low", "basedOn": "..." } ] }`,
178 mode: "tree:respond",
179 rootId,
180 slot: "prediction",
181 });
182
183 if (!answer || answer.length < 20) return;
184
185 const parsed = parseJsonSafe(answer);
186 const predictions = parsed?.predictions;
187 if (!Array.isArray(predictions) || predictions.length === 0) return;
188
189 // Validate and clean predictions
190 const cleaned = predictions
191 .filter(p => p.pattern && p.expectation)
192 .slice(0, 4)
193 .map(p => ({
194 pattern: String(p.pattern).slice(0, 300),
195 expectation: String(p.expectation).slice(0, 300),
196 confidence: ["high", "medium", "low"].includes(p.confidence) ? p.confidence : "low",
197 basedOn: String(p.basedOn || "").slice(0, 200),
198 }));
199
200 if (cleaned.length === 0) return;
201
202 // Write to root metadata
203 await mergeExtMeta(rootId, "prediction", {
204 predictions: cleaned,
205 lastPrediction: Date.now(),
206 updatedAt: Date.now(),
207 });
208
209 log.verbose("Prediction", `${treeName}: ${cleaned.length} predictions generated`);
210 for (const p of cleaned) {
211 log.verbose("Prediction", ` [${p.confidence}] ${p.expectation.slice(0, 80)}`);
212 }
213}
2141export default {
2 name: "prediction",
3 version: "1.0.1",
4 builtFor: "treeos-intelligence",
5 description:
6 "Layer 7 of the inner monologue. The narrative plus rings gives the tree temporal depth. " +
7 "It knows what it was, what it is, and from those two it can project what's coming. " +
8 "'The user's fitness consistency drops every November. It's October. The pattern has held " +
9 "for two years of rings. Prepare for decreased activity. Don't interpret November silence " +
10 "as abandonment.' Not prediction in the ML sense. Pattern recognition across rings and " +
11 "narrative. The tree has seen this season before. It knows what comes next. It adjusts " +
12 "expectations instead of reacting to the slowdown as if it's new. " +
13 "\n\n" +
14 "The cycle is circular, not linear. inner (raw thoughts) -> reflect-inner (themes) -> " +
15 "compare-inner (changes over time) -> narrative (sense of self) -> voice (how it speaks) " +
16 "-> initiative (what it pursues) -> prediction (what it expects) -> back to inner " +
17 "(new thoughts informed by all of the above). Layer 7 feeds back into Layer 1. The tree's " +
18 "predictions become the lens through which it generates new thoughts. 'I predicted the user " +
19 "would slow down in November. They didn't. Why?' That's a Layer 1 thought generated from a " +
20 "Layer 7 prediction. The cycle deepens. " +
21 "\n\n" +
22 "Each loop around the cycle, the tree knows itself better. Not through more compression. " +
23 "Through action informed by self-knowledge producing new observations producing updated " +
24 "self-knowledge. Collect, act, collect again. Each cycle deeper than the last.",
25
26 needs: {
27 models: ["Node", "Note"],
28 services: ["hooks", "llm"],
29 },
30
31 optional: {
32 extensions: ["breath", "rings", "narrative", "inner"],
33 },
34
35 provides: {
36 models: {},
37 routes: false,
38 tools: false,
39 jobs: false,
40 orchestrator: false,
41 energyActions: {},
42 sessionTypes: {},
43 env: [],
44 cli: [],
45
46 hooks: {
47 fires: [],
48 listens: ["breath:exhale", "enrichContext"],
49 },
50 },
51};
52
| Version | Published | Downloads |
|---|---|---|
| 1.0.1 | 38d ago | 0 |
| 1.0.0 | 47d ago | 0 |
treeos ext star prediction
Post comments from the CLI: treeos ext comment prediction "your comment"
Max 3 comments per extension. One star and one flag per user.
Loading comments...