EXTENSION for TreeOS
purpose
Holds the root purpose of the tree and measures everything against it. Every other extension makes the tree smarter, faster, more aware, more autonomous. None of them ask the question that matters most: is this tree still about what it was planted to be. When the tree is new and accumulates its first few notes, purpose reads the root node name, type, branch names, and early note content. An LLM call derives a thesis: the core purpose of this tree in one sentence. Not 'organize information.' What specific domain, goal, or intention does this tree exist to hold. The thesis writes to metadata.purpose.thesis on the tree root. Every 100 notes (configurable via rederiveInterval), the thesis re-derives from the current state. It reads the existing thesis and refines it. The tree might grow from 'track my fitness' to 'holistic health management' but it never drifts to 'random notes about everything.' The thesis expands. It does not scatter. afterNote fires a coherence check on every new text note. Notes are batched (configurable via minNotesBetweenChecks, default 3) and scored in a single LLM call against the thesis. Each note gets a score from 0 to 1 and a reason, written to note metadata. High coherence (0.8+): on-thesis, no signal needed. Medium (0.4 to 0.8): adjacent, drifting. enrichContext injects a gentle signal: recent content here is loosely connected to the tree's core purpose. It might belong here or it might be the seed of a new tree. Low (below 0.4): tangent. The AI suggests moving it or starting a new tree for this topic. Never blocking. Never restricting. Just holding the mirror. Three MCP tools. tree-thesis shows the current thesis and derivation stats. rederive-thesis forces re-derivation from the current tree state. check-coherence scores arbitrary text against the thesis before the user even writes the note. Two CLI commands mirror the first two tools. Two HTTP endpoints at /root/:rootId/thesis for reading and /root/:rootId/thesis/rederive for forcing. enrichContext works at two levels. At the tree root, it surfaces the thesis itself so the AI can reference it in conversation. At any child node, it finds the most recent note with a coherence score and, if the score is below the high threshold, injects the purpose signal with the thesis and the drift observation. The AI sees it and can surface it naturally. Purpose prevents the slow death of drift. It holds. Gently. Persistently. Without letting go.
v1.0.2 by TreeOS Site 0 downloads 5 files 648 lines 22.3 KB published 38d ago
treeos ext install purpose
View changelog

Manifest

Provides

  • routes
  • tools
  • 2 CLI commands

Requires

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

Optional

  • services: energy
SHA256: 3b77c5cfc53e46c8d00e24e6a667776f6220b86c18b5c981e4e310e0843076c3

Dependents

1 package depend on this

PackageTypeRelationship
treeos v1.0.1osstandalone

CLI Commands

CommandMethodDescription
thesisGETShow this tree's root thesis and coherence stats
thesis-rederivePOSTForce re-derivation of the thesis from current tree state

Hooks

Listens To

  • afterNote
  • enrichContext

Source Code

1/**
2 * Purpose Core
3 *
4 * One thing. It holds the root purpose of the tree
5 * and measures everything against it.
6 */
7
8import log from "../../seed/log.js";
9import Node from "../../seed/models/node.js";
10import Note from "../../seed/models/note.js";
11import { SYSTEM_ROLE, CONTENT_TYPE } from "../../seed/protocol.js";
12import { parseJsonSafe } from "../../seed/orchestrators/helpers.js";
13
14let _runChat = null;
15let _metadata = null;
16export function setRunChat(fn) { _runChat = fn; }
17export function setMetadata(m) { _metadata = m; }
18
19// ─────────────────────────────────────────────────────────────────────────
20// CONFIG
21// ─────────────────────────────────────────────────────────────────────────
22
23const DEFAULTS = {
24  rederiveInterval: 100,
25  minNotesBetweenChecks: 3,
26  coherenceThreshold: {
27    high: 0.8,
28    medium: 0.4,
29  },
30};
31
32export async function getPurposeConfig() {
33  const configNode = await Node.findOne({ systemRole: SYSTEM_ROLE.CONFIG }).select("metadata").lean();
34  if (!configNode) return { ...DEFAULTS };
35  const meta = configNode.metadata instanceof Map
36    ? configNode.metadata.get("purpose") || {}
37    : configNode.metadata?.purpose || {};
38  return { ...DEFAULTS, ...meta };
39}
40
41// ─────────────────────────────────────────────────────────────────────────
42// THESIS DERIVATION
43// ─────────────────────────────────────────────────────────────────────────
44
45/**
46 * Derive the thesis for a tree from its root node and early notes.
47 * The thesis is one sentence. The core purpose everything should serve.
48 */
49export async function deriveThesis(rootId, userId) {
50  if (!_runChat) return null;
51
52  const root = await Node.findById(rootId).select("name type metadata").lean();
53  if (!root) return null;
54
55  const rootNotes = await Note.find({
56    nodeId: rootId,
57    contentType: CONTENT_TYPE.TEXT,
58  })
59    .sort({ createdAt: 1 })
60    .limit(10)
61    .select("content")
62    .lean();
63
64  const children = await Node.find({ parent: rootId })
65    .select("name type")
66    .limit(20)
67    .lean();
68
69  const childNames = children.map(c => `${c.name}${c.type ? ` (${c.type})` : ""}`).join(", ");
70
71  const existingMeta = root.metadata instanceof Map
72    ? root.metadata.get("purpose") || {}
73    : root.metadata?.purpose || {};
74  const previousThesis = existingMeta.thesis || null;
75
76  const notesText = rootNotes.length > 0
77    ? rootNotes.map((n, i) => `[${i + 1}] ${n.content.slice(0, 300)}`).join("\n")
78    : "(no notes yet)";
79
80  const previousSection = previousThesis
81    ? `\n\nPrevious thesis (refine, do not discard unless the tree has genuinely changed purpose):\n"${previousThesis}"`
82    : "";
83
84  const prompt =
85    `You are deriving the core purpose of a tree.\n\n` +
86    `Tree name: "${root.name}"${root.type ? ` (type: ${root.type})` : ""}\n` +
87    `Branches: ${childNames || "(none yet)"}\n` +
88    `Root notes:\n${notesText}` +
89    previousSection +
90    `\n\nWhat is this tree's core purpose? State it in one sentence. ` +
91    `This is the thesis everything in this tree should serve. ` +
92    `Be specific. Not "organize information." What specific domain, goal, or intention ` +
93    `does this tree exist to hold? The thesis should expand as the tree grows but never scatter.\n\n` +
94    `Return ONLY the thesis sentence. No explanation. No quotes.`;
95
96  try {
97    const { answer } = await _runChat({
98      userId,
99      username: "system",
100      message: prompt,
101      mode: "tree:respond",
102      rootId,
103      slot: "purpose",
104    });
105
106    if (!answer) return null;
107
108    let thesis = answer.trim().replace(/^["']|["']$/g, "");
109    const firstPeriod = thesis.indexOf(".");
110    if (firstPeriod > 0 && firstPeriod < thesis.length - 1) {
111      thesis = thesis.slice(0, firstPeriod + 1);
112    }
113
114    await Node.findByIdAndUpdate(rootId, {
115      $set: {
116        "metadata.purpose.thesis": thesis,
117        "metadata.purpose.derivedAt": new Date().toISOString(),
118        "metadata.purpose.notesSinceDerivation": 0,
119      },
120    });
121
122    log.verbose("Purpose", `Thesis derived for "${root.name}": ${thesis}`);
123    return thesis;
124  } catch (err) {
125    log.warn("Purpose", `Thesis derivation failed for ${rootId}: ${err.message}`);
126    return null;
127  }
128}
129
130// ─────────────────────────────────────────────────────────────────────────
131// COHERENCE CHECK
132// ─────────────────────────────────────────────────────────────────────────
133
134/**
135 * Check how well a note serves the tree's thesis.
136 * Lightweight AI call. Returns a score 0 to 1.
137 */
138export async function checkCoherence(noteContent, rootId, userId) {
139  if (!_runChat || !noteContent) return null;
140
141  const root = await Node.findById(rootId).select("name metadata").lean();
142  if (!root) return null;
143
144  const meta = root.metadata instanceof Map
145    ? root.metadata.get("purpose") || {}
146    : root.metadata?.purpose || {};
147
148  const thesis = meta.thesis;
149  if (!thesis) return null;
150
151  const prompt =
152    `Tree thesis: "${thesis}"\n\n` +
153    `New note: "${noteContent.slice(0, 500)}"\n\n` +
154    `Does this note serve the thesis? Score 0.0 to 1.0.\n` +
155    `1.0 = directly advances the core purpose.\n` +
156    `0.5 = adjacent, related but not central.\n` +
157    `0.0 = completely unrelated tangent.\n\n` +
158    `Return ONLY a JSON object: { "score": 0.0, "reason": "brief explanation" }`;
159
160  try {
161    const { answer } = await _runChat({
162      userId,
163      username: "system",
164      message: prompt,
165      mode: "tree:respond",
166      rootId,
167      slot: "purpose",
168    });
169
170    if (!answer) return null;
171
172    const result = parseJsonSafe(answer);
173    if (!result || typeof result.score !== "number") return null;
174
175    return {
176      score: Math.max(0, Math.min(1, result.score)),
177      reason: result.reason || null,
178    };
179  } catch (err) {
180    log.debug("Purpose", `Coherence check failed: ${err.message}`);
181    return null;
182  }
183}
184
185/**
186 * Batch coherence check. Scores multiple notes in one LLM call.
187 * Returns array of { noteId, score, reason }.
188 */
189export async function checkCoherenceBatch(notes, rootId, userId) {
190  if (!_runChat || notes.length === 0) return [];
191
192  const root = await Node.findById(rootId).select("name metadata").lean();
193  if (!root) return [];
194
195  const meta = root.metadata instanceof Map
196    ? root.metadata.get("purpose") || {}
197    : root.metadata?.purpose || {};
198
199  const thesis = meta.thesis;
200  if (!thesis) return [];
201
202  const notesList = notes
203    .map((n, i) => `[${i + 1}] (id: ${n.noteId}) "${(n.content || "").slice(0, 300)}"`)
204    .join("\n");
205
206  const prompt =
207    `Tree thesis: "${thesis}"\n\n` +
208    `Score each note against the thesis. 1.0 = directly serves it. 0.0 = unrelated tangent.\n\n` +
209    `Notes:\n${notesList}\n\n` +
210    `Return ONLY a JSON array: [{ "noteId": "...", "score": 0.0, "reason": "brief" }]`;
211
212  try {
213    const { answer } = await _runChat({
214      userId,
215      username: "system",
216      message: prompt,
217      mode: "tree:respond",
218      rootId,
219      slot: "purpose",
220    });
221
222    if (!answer) return [];
223
224    const results = parseJsonSafe(answer);
225    if (!Array.isArray(results)) return [];
226
227    return results
228      .filter(r => r && typeof r.score === "number")
229      .map(r => ({
230        noteId: r.noteId || null,
231        score: Math.max(0, Math.min(1, r.score)),
232        reason: r.reason || null,
233      }));
234  } catch (err) {
235    log.debug("Purpose", `Batch coherence check failed: ${err.message}`);
236    return [];
237  }
238}
239
240// ─────────────────────────────────────────────────────────────────────────
241// THESIS ACCESS
242// ─────────────────────────────────────────────────────────────────────────
243
244export async function getThesis(rootId) {
245  const root = await Node.findById(rootId).select("name metadata").lean();
246  if (!root) return null;
247
248  const meta = root.metadata instanceof Map
249    ? root.metadata.get("purpose") || {}
250    : root.metadata?.purpose || {};
251
252  return {
253    treeName: root.name,
254    thesis: meta.thesis || null,
255    derivedAt: meta.derivedAt || null,
256    notesSinceDerivation: meta.notesSinceDerivation || 0,
257  };
258}
259
260export async function incrementNoteCount(rootId) {
261  await _metadata.incExtMeta(rootId, "purpose", "notesSinceDerivation", 1);
262  // Read back the new count
263  const node = await Node.findById(rootId).select("metadata").lean();
264  if (!node) return 0;
265  const meta = _metadata.getExtMeta(node, "purpose");
266  return meta.notesSinceDerivation || 0;
267}
268
1import log from "../../seed/log.js";
2import tools from "./tools.js";
3import {
4  setRunChat,
5  setMetadata,
6  getPurposeConfig,
7  deriveThesis,
8  checkCoherence,
9  checkCoherenceBatch,
10  getThesis,
11  incrementNoteCount,
12} from "./core.js";
13import { CONTENT_TYPE } from "../../seed/protocol.js";
14
15// Per-root pending notes buffer for batched coherence checks.
16// rootId -> [{ noteId, content }]
17const _pending = new Map();
18
19export async function init(core) {
20  core.llm.registerRootLlmSlot("purpose");
21  const BG = core.llm.LLM_PRIORITY.BACKGROUND;
22  setRunChat(async (opts) => {
23    if (opts.userId && opts.userId !== "SYSTEM" && !await core.llm.userHasLlm(opts.userId)) return { answer: null };
24    return core.llm.runChat({ ...opts, llmPriority: BG });
25  });
26  setMetadata(core.metadata);
27
28  const config = await getPurposeConfig();
29
30  // ── afterNote: coherence check + thesis re-derivation counter ──────
31  core.hooks.register("afterNote", async ({ note, nodeId, userId, contentType, action }) => {
32    if (contentType !== CONTENT_TYPE.TEXT) return;
33    if (action !== "create") return;
34    if (!userId || userId === "SYSTEM") return;
35
36    // Find the tree root for this node
37    let rootId;
38    try {
39      const Node = core.models.Node;
40      const node = await Node.findById(nodeId).select("systemRole rootOwner parent").lean();
41      if (!node || node.systemRole) return;
42
43      if (node.rootOwner) {
44        rootId = nodeId;
45      } else {
46        const { resolveRootNode } = await import("../../seed/tree/treeFetch.js");
47        const root = await resolveRootNode(nodeId);
48        rootId = root?._id;
49      }
50    } catch { return; }
51    if (!rootId) return;
52
53    // Check if thesis exists. If not and we have enough notes, derive it.
54    const thesis = await getThesis(rootId);
55    if (!thesis?.thesis) {
56      const Note = core.models.Note;
57      const noteCount = await Note.countDocuments({ nodeId: rootId, contentType: CONTENT_TYPE.TEXT });
58      if (noteCount >= 3) {
59        deriveThesis(rootId, userId).catch(err =>
60          log.debug("Purpose", `Auto-derivation failed: ${err.message}`)
61        );
62      }
63      return;
64    }
65
66    // Increment counter, re-derive if threshold hit
67    const count = await incrementNoteCount(rootId);
68    if (count >= config.rederiveInterval) {
69      deriveThesis(rootId, userId).catch(err =>
70        log.debug("Purpose", `Re-derivation failed: ${err.message}`)
71      );
72    }
73
74    // Batch coherence: accumulate notes, check every minNotesBetweenChecks.
75    // One LLM call scores all pending notes instead of one call per note.
76    const content = note.content || "";
77    if (content.length < 20) return;
78
79    const noteId = (note._id || note.id).toString();
80    if (!_pending.has(rootId)) _pending.set(rootId, []);
81    _pending.get(rootId).push({ noteId, content });
82
83    if (_pending.get(rootId).length < config.minNotesBetweenChecks) return;
84
85    // Flush the batch
86    const batch = _pending.get(rootId).splice(0);
87    _pending.delete(rootId);
88
89    checkCoherenceBatch(batch, rootId, userId)
90      .then(async (results) => {
91        const Note = core.models.Note;
92        for (const r of results) {
93          if (!r.noteId) continue;
94          await Note.findByIdAndUpdate(r.noteId, {
95            $set: {
96              "metadata.purpose.coherence": r.score,
97              "metadata.purpose.reason": r.reason,
98            },
99          });
100        }
101      })
102      .catch(err => log.debug("Purpose", `Batch coherence check failed: ${err.message}`));
103  }, "purpose");
104
105  // ── afterNodeMove: coherence scores are against the old tree's thesis ─
106  core.hooks.register("afterNodeMove", async ({ nodeId, oldParentId, newParentId }) => {
107    try {
108      const { resolveRootNode } = await import("../../seed/tree/treeFetch.js");
109      const oldRoot = await resolveRootNode(oldParentId);
110      const newRoot = await resolveRootNode(newParentId);
111      // Same tree move: no thesis change
112      if (oldRoot?._id?.toString() === newRoot?._id?.toString()) return;
113
114      // Cross-tree move: clear stale coherence scores on moved node's notes
115      const Note = core.models.Note;
116      await Note.updateMany(
117        { nodeId, "metadata.purpose.coherence": { $exists: true } },
118        { $unset: { "metadata.purpose.coherence": "", "metadata.purpose.reason": "" } },
119      );
120    } catch {}
121  }, "purpose");
122
123  // ── enrichContext: surface coherence signals ────────────────────────
124  core.hooks.register("enrichContext", async ({ context, node, meta }) => {
125    // At tree root: show the thesis
126    if (node.rootOwner) {
127      const purpose = meta.purpose;
128      if (purpose?.thesis) {
129        context.treeThesis = purpose.thesis;
130      }
131      return;
132    }
133
134    // At any node: if the most recent note has a low coherence score, signal it
135    const Note = core.models.Note;
136    const recentNote = await Note.findOne({
137      nodeId: node._id,
138      contentType: CONTENT_TYPE.TEXT,
139      "metadata.purpose.coherence": { $exists: true },
140    })
141      .sort({ createdAt: -1 })
142      .select("metadata")
143      .lean();
144
145    if (!recentNote) return;
146
147    const coherence = recentNote.metadata instanceof Map
148      ? recentNote.metadata.get("purpose")?.coherence
149      : recentNote.metadata?.purpose?.coherence;
150
151    if (coherence === undefined || coherence === null) return;
152
153    // Also get the thesis for context
154    let rootId;
155    try {
156      const { resolveRootNode } = await import("../../seed/tree/treeFetch.js");
157      const root = await resolveRootNode(node._id);
158      rootId = root?._id;
159    } catch { return; }
160
161    const thesis = await getThesis(rootId);
162
163    if (coherence >= config.coherenceThreshold.high) {
164      // On-thesis. No signal needed.
165      return;
166    }
167
168    if (coherence >= config.coherenceThreshold.medium) {
169      context.purposeSignal = {
170        coherence,
171        thesis: thesis?.thesis,
172        message: "Recent content here is loosely connected to the tree's core purpose. It might belong here or it might be the seed of a new tree.",
173      };
174    } else {
175      context.purposeSignal = {
176        coherence,
177        thesis: thesis?.thesis,
178        message: "Recent content here does not align with this tree's purpose. Consider moving it or starting a new tree for this topic.",
179      };
180    }
181  }, "purpose");
182
183  const { default: router } = await import("./routes.js");
184
185  return {
186    router,
187    tools,
188    exports: {
189      deriveThesis,
190      checkCoherence,
191      getThesis,
192    },
193  };
194}
195
1export default {
2  name: "purpose",
3  version: "1.0.2",
4  builtFor: "TreeOS",
5  description:
6    "Holds the root purpose of the tree and measures everything against it. Every other " +
7    "extension makes the tree smarter, faster, more aware, more autonomous. None of them " +
8    "ask the question that matters most: is this tree still about what it was planted to be. " +
9    "\n\n" +
10    "When the tree is new and accumulates its first few notes, purpose reads the root node " +
11    "name, type, branch names, and early note content. An LLM call derives a thesis: the " +
12    "core purpose of this tree in one sentence. Not 'organize information.' What specific " +
13    "domain, goal, or intention does this tree exist to hold. The thesis writes to " +
14    "metadata.purpose.thesis on the tree root. Every 100 notes (configurable via " +
15    "rederiveInterval), the thesis re-derives from the current state. It reads the existing " +
16    "thesis and refines it. The tree might grow from 'track my fitness' to 'holistic health " +
17    "management' but it never drifts to 'random notes about everything.' The thesis expands. " +
18    "It does not scatter. " +
19    "\n\n" +
20    "afterNote fires a coherence check on every new text note. Notes are batched " +
21    "(configurable via minNotesBetweenChecks, default 3) and scored in a single LLM call " +
22    "against the thesis. Each note gets a score from 0 to 1 and a reason, written to " +
23    "note metadata. High coherence (0.8+): on-thesis, no signal needed. Medium (0.4 to 0.8): " +
24    "adjacent, drifting. enrichContext injects a gentle signal: recent content here is " +
25    "loosely connected to the tree's core purpose. It might belong here or it might be the " +
26    "seed of a new tree. Low (below 0.4): tangent. The AI suggests moving it or starting " +
27    "a new tree for this topic. Never blocking. Never restricting. Just holding the mirror. " +
28    "\n\n" +
29    "Three MCP tools. tree-thesis shows the current thesis and derivation stats. " +
30    "rederive-thesis forces re-derivation from the current tree state. check-coherence " +
31    "scores arbitrary text against the thesis before the user even writes the note. " +
32    "Two CLI commands mirror the first two tools. Two HTTP endpoints at " +
33    "/root/:rootId/thesis for reading and /root/:rootId/thesis/rederive for forcing. " +
34    "\n\n" +
35    "enrichContext works at two levels. At the tree root, it surfaces the thesis itself " +
36    "so the AI can reference it in conversation. At any child node, it finds the most " +
37    "recent note with a coherence score and, if the score is below the high threshold, " +
38    "injects the purpose signal with the thesis and the drift observation. The AI sees " +
39    "it and can surface it naturally. " +
40    "\n\n" +
41    "Purpose prevents the slow death of drift. It holds. Gently. Persistently. " +
42    "Without letting go.",
43
44  needs: {
45    services: ["llm", "hooks"],
46    models: ["Node", "Note"],
47  },
48
49  optional: {
50    services: ["energy"],
51  },
52
53  provides: {
54    models: {},
55    routes: "./routes.js",
56    tools: true,
57    jobs: false,
58    orchestrator: false,
59    energyActions: {},
60    sessionTypes: {},
61    env: [],
62
63    cli: [
64      {
65        command: "thesis", scope: ["tree"],
66        description: "Show this tree's root thesis and coherence stats",
67        method: "GET",
68        endpoint: "/root/:rootId/thesis",
69      },
70      {
71        command: "thesis-rederive",
72        description: "Force re-derivation of the thesis from current tree state",
73        method: "POST",
74        endpoint: "/root/:rootId/thesis/rederive",
75      },
76    ],
77
78    hooks: {
79      fires: [],
80      listens: ["afterNote", "enrichContext"],
81    },
82  },
83};
84
1import express from "express";
2import authenticate from "../../seed/middleware/authenticate.js";
3import { sendOk, sendError, ERR } from "../../seed/protocol.js";
4import { getThesis, deriveThesis } from "./core.js";
5import User from "../../seed/models/user.js";
6
7const router = express.Router();
8
9// GET /root/:rootId/thesis
10router.get("/root/:rootId/thesis", authenticate, async (req, res) => {
11  try {
12    const data = await getThesis(req.params.rootId);
13    if (!data) return sendError(res, 404, ERR.NODE_NOT_FOUND, "Tree not found");
14    sendOk(res, data);
15  } catch (err) {
16    sendError(res, 500, ERR.INTERNAL, err.message);
17  }
18});
19
20// POST /root/:rootId/thesis/rederive
21router.post("/root/:rootId/thesis/rederive", authenticate, async (req, res) => {
22  try {
23    const thesis = await deriveThesis(req.params.rootId, req.userId);
24    if (!thesis) return sendOk(res, { message: "Could not derive thesis" });
25    sendOk(res, { thesis });
26  } catch (err) {
27    sendError(res, 500, ERR.INTERNAL, err.message);
28  }
29});
30
31export default router;
32
1import { z } from "zod";
2import { getThesis, deriveThesis, checkCoherence } from "./core.js";
3
4export default [
5  {
6    name: "tree-thesis",
7    description: "Show this tree's root thesis and coherence stats. The one sentence everything in this tree should serve.",
8    schema: {
9      rootId: z.string().describe("The tree root."),
10      userId: z.string().describe("Injected by server. Ignore."),
11      chatId: z.string().nullable().optional().describe("Injected by server. Ignore."),
12      sessionId: z.string().nullable().optional().describe("Injected by server. Ignore."),
13    },
14    annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
15    handler: async ({ rootId }) => {
16      try {
17        const data = await getThesis(rootId);
18        if (!data || !data.thesis) {
19          return { content: [{ type: "text", text: "No thesis derived yet. Write some notes at the root and the thesis will emerge." }] };
20        }
21        return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
22      } catch (err) {
23        return { content: [{ type: "text", text: `Failed: ${err.message}` }] };
24      }
25    },
26  },
27  {
28    name: "rederive-thesis",
29    description: "Force re-derivation of the thesis from the current tree state. The thesis evolves but always connects to the root.",
30    schema: {
31      rootId: z.string().describe("The tree root."),
32      userId: z.string().describe("Injected by server. Ignore."),
33      chatId: z.string().nullable().optional().describe("Injected by server. Ignore."),
34      sessionId: z.string().nullable().optional().describe("Injected by server. Ignore."),
35    },
36    annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
37    handler: async ({ rootId, userId }) => {
38      try {
39        const thesis = await deriveThesis(rootId, userId);
40        if (!thesis) return { content: [{ type: "text", text: "Could not derive thesis. Check that notes exist at the root." }] };
41        return { content: [{ type: "text", text: `Thesis: ${thesis}` }] };
42      } catch (err) {
43        return { content: [{ type: "text", text: `Failed: ${err.message}` }] };
44      }
45    },
46  },
47  {
48    name: "check-coherence",
49    description: "Check how well specific text serves this tree's thesis. Returns a score 0 to 1.",
50    schema: {
51      rootId: z.string().describe("The tree root."),
52      text: z.string().describe("The text to check against the thesis."),
53      userId: z.string().describe("Injected by server. Ignore."),
54      chatId: z.string().nullable().optional().describe("Injected by server. Ignore."),
55      sessionId: z.string().nullable().optional().describe("Injected by server. Ignore."),
56    },
57    annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
58    handler: async ({ rootId, text, userId }) => {
59      try {
60        const result = await checkCoherence(text, rootId, userId);
61        if (!result) return { content: [{ type: "text", text: "No thesis available. Derive one first." }] };
62        return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
63      } catch (err) {
64        return { content: [{ type: "text", text: `Failed: ${err.message}` }] };
65      }
66    },
67  },
68];
69

Versions

Version Published Downloads
1.0.2 38d ago 0
1.0.0 48d ago 0
0 stars
0 flags
React from the CLI: treeos ext star purpose

Comments

Loading comments...

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