EXTENSION for TreeOS
prestige
Nodes do not just accumulate forever. At some point the current phase of work is done. The budget was met. The sprint shipped. The chapter was written. Prestige closes the book on the current version and opens a new one. When you prestige a node, the extension snapshots everything: status, values, goals, schedule, reeffect time. That snapshot goes into the prestige history array. Then it resets. Values go to zero. Status returns to active. The schedule advances by the reeffect interval. The version counter increments. The node starts fresh with a clean slate, but the full record of every previous version is preserved in metadata.prestige.history. Every note written to a prestiged node is tagged with the version number at time of writing via the beforeNote hook. Every contribution is stamped with the current version via beforeContribution. This means you can query the full history of a node and see exactly which version each piece of content and each action belonged to. Notes from version 2 are distinguishable from notes in version 5. Cross-extension coordination happens through the extension loader. When prestige resets values, it calls the values extension's setValueForNode export, not a direct metadata write. When it advances the schedule, it calls the schedules extension's updateSchedule export. If those extensions are not installed, those resets simply do not happen. The prestige itself still works. enrichContext injects the current version number and total version count so the AI always knows what generation it is working in.
v1.0.1 by TreeOS Site 0 downloads 5 files 443 lines 15.2 KB published 38d ago
treeos ext install prestige
View changelog

Manifest

Provides

  • routes
  • tools
  • 1 CLI commands
  • 1 energy actions

Requires

  • services: contributions, hooks
  • models: Node

Optional

  • services: energy
  • extensions: values, schedules, treeos-base
SHA256: 37176e1eebad6f108b6778d55e92d807bd6e4730164e553eac19c5a6881173e9

CLI Commands

CommandMethodDescription
prestigePOSTAdd new version to current node

Hooks

Listens To

  • beforeNote
  • beforeContribution
  • enrichContext

Source Code

1import log from "../../seed/log.js";
2import { getExtension } from "../loader.js";
3
4// Services wired from init() via setServices()
5let Node = null;
6let logContribution = async () => {};
7let useEnergy = async () => ({ energyUsed: 0 });
8let _metadata = null;
9
10export function setServices({ models, contributions, metadata }) {
11  Node = models.Node;
12  logContribution = contributions.logContribution;
13  if (metadata) _metadata = metadata;
14}
15export function setEnergyService(energy) { useEnergy = energy.useEnergy; }
16
17/**
18 * Resolve "latest" to the current prestige level for a node.
19 * Without prestige data, returns 0.
20 */
21export async function resolveVersion(nodeId, version) {
22  if (version === "latest") {
23    const node = await Node.findById(nodeId).select("metadata").lean();
24    if (!node) throw new Error("Node not found");
25    const prestige = _metadata.getExtMeta(node, "prestige");
26    return prestige?.current || 0;
27  }
28  return Number(version);
29}
30
31function calculateNextSchedule(scheduleData) {
32  if (scheduleData.schedule === null) return null;
33  const current = new Date(scheduleData.schedule);
34  return new Date(current.getTime() + scheduleData.reeffectTime * 60 * 60 * 1000).toISOString();
35}
36
37async function addPrestige({
38  nodeId,
39  userId,
40  wasAi,
41  chatId = null,
42  sessionId = null,
43}) {
44  const node = await Node.findById(nodeId).populate("children");
45  if (!node) throw new Error("Node not found");
46  if (node.systemRole) throw new Error("Cannot modify system nodes");
47
48  const { energyUsed } = await useEnergy({
49    userId,
50    action: "prestige",
51  });
52
53  const prestigeData = _metadata.getExtMeta(node, "prestige");
54  const currentLevel = prestigeData.current || 0;
55
56  await addPrestigeToNode(node);
57
58  await logContribution({
59    userId,
60    nodeId,
61    wasAi,
62    chatId,
63    sessionId,
64    action: "prestige",
65    nodeVersion: currentLevel,
66    energyUsed,
67  });
68
69  return { message: "Prestige added successfully." };
70}
71
72async function addPrestigeToNode(node) {
73  const meta = node.metadata instanceof Map ? Object.fromEntries(node.metadata) : (node.metadata || {});
74  const prestigeData = meta.prestige || { current: 0, history: [] };
75  const currentLevel = prestigeData.current || 0;
76
77  const snapshot = {
78    version: currentLevel,
79    status: node.status,
80    values: { ...(meta.values || {}) },
81    goals: { ...(meta.goals || {}) },
82    schedule: meta.schedules?.date || null,
83    reeffectTime: meta.schedules?.reeffectTime || 0,
84    archivedAt: new Date().toISOString(),
85  };
86
87  if (!prestigeData.history) prestigeData.history = [];
88  prestigeData.history.push(snapshot);
89
90  const values = meta.values || {};
91  const newValues = {};
92  for (const key of Object.keys(values)) {
93    newValues[key] = 0;
94  }
95
96  prestigeData.current = currentLevel + 1;
97
98  // Write prestige metadata (own namespace only)
99  await _metadata.setExtMeta(node, "prestige", prestigeData);
100
101  // Reset values via the values extension's export (not direct namespace write)
102  const valuesExt = getExtension("values");
103  if (valuesExt?.exports?.setValueForNode) {
104    for (const key of Object.keys(newValues)) {
105      try {
106        await valuesExt.exports.setValueForNode({
107          nodeId: node._id.toString(), key, value: 0,
108          userId: node.rootOwner?.toString() || "system",
109        });
110      } catch (err) {
111        log.debug("Prestige", "Value reset failed for key " + key + ":", err.message);
112      }
113    }
114  }
115
116  // Advance schedule via the schedules extension's export (not direct namespace write)
117  const schedulesExt = getExtension("schedules");
118  if (schedulesExt?.exports?.updateSchedule && meta.schedules?.date) {
119    const scheduleData = { schedule: new Date(meta.schedules.date), reeffectTime: meta.schedules?.reeffectTime || 0 };
120    const newSchedule = calculateNextSchedule(scheduleData);
121    if (newSchedule) {
122      try {
123        await schedulesExt.exports.updateSchedule(node._id.toString(), { schedule: newSchedule });
124      } catch (err) {
125        log.debug("Prestige", "Schedule advance failed:", err.message);
126      }
127    }
128  }
129
130  // Reset status to active via direct DB update.
131  // Note: this bypasses beforeStatusChange/afterStatusChange hooks intentionally.
132  // Prestige is a kernel-level reset, not a user status change.
133  await Node.updateOne({ _id: node._id }, { $set: { status: "active" } });
134}
135
136export { addPrestige, addPrestigeToNode };
137
1import tools from "./tools.js";
2import { setServices, setEnergyService, addPrestige, resolveVersion } from "./core.js";
3
4export async function init(core) {
5  setServices({ models: core.models, contributions: core.contributions, metadata: core.metadata });
6  if (core.energy) setEnergyService(core.energy);
7
8  const { default: router, setNodeModel } = await import("./routes.js");
9  setNodeModel(core.models.Node);
10
11  const Node = core.models.Node;
12
13  core.hooks.register("beforeNote", async (data) => {
14    const node = await Node.findById(data.nodeId).select("metadata").lean();
15    if (!node) return;
16    const prestige = core.metadata.getExtMeta(node, "prestige");
17    if (!data.metadata) data.metadata = {};
18    data.metadata.version = prestige?.current || 0;
19  }, "prestige");
20
21  core.hooks.register("beforeContribution", async (data) => {
22    const node = await Node.findById(data.nodeId).select("metadata").lean();
23    if (!node) return;
24    const prestige = core.metadata.getExtMeta(node, "prestige");
25    if (prestige?.current) {
26      data.nodeVersion = String(prestige.current);
27    }
28  }, "prestige");
29
30  core.hooks.register("enrichContext", async ({ context, node, meta }) => {
31    const prestige = meta.prestige;
32    if (prestige?.current) {
33      context.prestige = prestige.current;
34      context.totalVersions = (prestige.history?.length || 0) + 1;
35    }
36  }, "prestige");
37
38  // Register navigation for prestige tool (if treeos-base installed)
39  try {
40    const { getExtension } = await import("../loader.js");
41    const base = getExtension("treeos-base");
42    if (base?.exports?.registerToolNavigation) {
43      base.exports.registerToolNavigation("add-node-prestige", ({ args, withToken: t }) =>
44        t(`/api/v1/node/${args.nodeId}/${args.prestige || 0}?html`));
45    }
46  } catch {}
47
48  // Register UI slots
49  try {
50    const { getExtension } = await import("../loader.js");
51    const treeos = getExtension("treeos-base");
52    if (treeos?.exports?.registerSlot) {
53      // Versions list on node detail page
54      treeos.exports.registerSlot("node-detail-sections", "prestige", ({ node, nodeId, qs, isPublicAccess }) => {
55        const meta = node.metadata instanceof Map ? Object.fromEntries(node.metadata) : (node.metadata || {});
56        const prestige = meta.prestige || { current: 0, history: [] };
57        return `<div class="versions-section">
58          <h2>Versions</h2>
59          <ul class="versions-list">
60            ${[...Array(prestige.current + 1)].map((_, i) =>
61              `<li><a href="/api/v1/node/${nodeId}/${i}${qs}">Version ${i}${i === prestige.current ? " (current)" : ""}</a></li>`
62            ).reverse().join("")}
63          </ul>
64          ${!isPublicAccess ? `<form method="POST" action="/api/v1/node/${nodeId}/prestige${qs}"
65            onsubmit="return confirm('Complete current version and create new prestige level?')" style="margin-top:16px;">
66            <button type="submit" class="primary-button">Add New Version</button>
67          </form>` : ""}
68        </div>`;
69      }, { priority: 10 });
70
71      // Version badge on version detail page
72      treeos.exports.registerSlot("version-badge", "prestige", ({ version, data }) => {
73        return `<span class="version-badge version-status-${data?.status || "active"}">Version ${version}</span>`;
74      }, { priority: 10 });
75
76      // Version control on version detail page
77      treeos.exports.registerSlot("version-detail-sections", "prestige", ({ nodeId, version, qs, showPrestige }) => {
78        if (!showPrestige) return "";
79        return `<div class="actions-section">
80          <h3>Version Control</h3>
81          <form method="POST" action="/api/v1/node/${nodeId}/${version}/prestige${qs}"
82            onsubmit="return confirm('Complete current version and create new prestige level?')" class="action-form">
83            <button type="submit" class="primary-button">Add New Version</button>
84          </form>
85        </div>`;
86      }, { priority: 10 });
87    }
88  } catch {}
89
90  return {
91    router,
92    tools,
93    modeTools: [
94      { modeKey: "tree:edit", toolNames: ["add-node-prestige"] },
95    ],
96    exports: { addPrestige, resolveVersion },
97  };
98}
99
1export default {
2  name: "prestige",
3  version: "1.0.1",
4  builtFor: "TreeOS",
5  description:
6    "Nodes do not just accumulate forever. At some point the current phase of work is done. The " +
7    "budget was met. The sprint shipped. The chapter was written. Prestige closes the book on the " +
8    "current version and opens a new one. When you prestige a node, the extension snapshots " +
9    "everything: status, values, goals, schedule, reeffect time. That snapshot goes into the " +
10    "prestige history array. Then it resets. Values go to zero. Status returns to active. The " +
11    "schedule advances by the reeffect interval. The version counter increments. The node starts " +
12    "fresh with a clean slate, but the full record of every previous version is preserved in " +
13    "metadata.prestige.history." +
14    "\n\n" +
15    "Every note written to a prestiged node is tagged with the version number at time of writing " +
16    "via the beforeNote hook. Every contribution is stamped with the current version via " +
17    "beforeContribution. This means you can query the full history of a node and see exactly " +
18    "which version each piece of content and each action belonged to. Notes from version 2 are " +
19    "distinguishable from notes in version 5." +
20    "\n\n" +
21    "Cross-extension coordination happens through the extension loader. When prestige resets " +
22    "values, it calls the values extension's setValueForNode export, not a direct metadata " +
23    "write. When it advances the schedule, it calls the schedules extension's updateSchedule " +
24    "export. If those extensions are not installed, those resets simply do not happen. The " +
25    "prestige itself still works. enrichContext injects the current version number and total " +
26    "version count so the AI always knows what generation it is working in.",
27
28  needs: {
29    services: ["contributions", "hooks"],
30    models: ["Node"],
31  },
32
33  optional: {
34    services: ["energy"],
35    extensions: ["values", "schedules", "treeos-base"],
36  },
37
38  provides: {
39    models: {},
40    routes: "./routes.js",
41    tools: true,
42    jobs: false,
43    orchestrator: false,
44    energyActions: {
45      prestige: { cost: 1 },
46    },
47    sessionTypes: {},
48    cli: [
49      { command: "prestige", scope: ["tree"], description: "Add new version to current node", method: "POST", endpoint: "/node/:nodeId/prestige" },
50    ],
51    hooks: {
52      fires: [],
53      listens: ["beforeNote", "beforeContribution", "enrichContext"],
54    },
55  },
56};
57
1import log from "../../seed/log.js";
2import express from "express";
3import { sendOk, sendError, ERR } from "../../seed/protocol.js";
4import authenticate from "../../seed/middleware/authenticate.js";
5import { addPrestige } from "./core.js";
6
7let _Node = null;
8export function setNodeModel(Node) { _Node = Node; }
9
10const router = express.Router();
11
12async function useLatest(req, res, next) {
13  try {
14    const node = await _Node.findById(req.params.nodeId).select("metadata").lean();
15    if (!node) return sendError(res, 404, ERR.NODE_NOT_FOUND, "Node not found");
16    const meta = node.metadata instanceof Map ? node.metadata.get("prestige") : node.metadata?.prestige;
17    req.params.version = String(meta?.current || 0);
18    next();
19  } catch (err) {
20    sendError(res, 500, ERR.INTERNAL, err.message);
21  }
22}
23
24const prestigeHandler = async (req, res) => {
25  try {
26    const { nodeId, version } = req.params;
27    const userId = req.userId;
28
29    const nextVersion = Number(version) + 1;
30
31    if (Number.isNaN(nextVersion)) {
32      return sendError(res, 400, ERR.INVALID_INPUT, "Invalid version");
33    }
34
35    const result = await addPrestige({
36      nodeId,
37      userId,
38    });
39
40    if ("html" in req.query) {
41      return res.redirect(
42        `/api/v1/node/${nodeId}/${nextVersion}?token=${req.query.token ?? ""}&html`,
43      );
44    }
45
46    sendOk(res, result);
47  } catch (err) {
48    log.error("Prestige", "prestige error:", err);
49    sendError(res, 400, ERR.INVALID_INPUT, err.message);
50  }
51};
52
53router.post("/node/:nodeId/prestige", authenticate, useLatest, prestigeHandler);
54router.post("/node/:nodeId/:version/prestige", authenticate, prestigeHandler);
55
56export default router;
57
1import { z } from "zod";
2import { addPrestige, resolveVersion } from "./core.js";
3import { createNote } from "../../seed/tree/notes.js";
4
5export default [
6  {
7    name: "create-node-version-note",
8    description:
9      "Create a text note tagged with the node's current prestige version. Use this instead of create-node-note when version tracking matters.",
10    schema: {
11      content: z.string().describe("The text content of the note."),
12      nodeId: z.string().describe("The ID of the node the note belongs to."),
13      userId: z.string().describe("Injected by server. Ignore."),
14      chatId: z.string().nullable().optional().describe("Injected by server. Ignore."),
15      sessionId: z.string().nullable().optional().describe("Injected by server. Ignore."),
16    },
17    annotations: {
18      readOnlyHint: false,
19      destructiveHint: false,
20      idempotentHint: false,
21    },
22    handler: async ({ content, nodeId, userId, chatId, sessionId }) => {
23      try {
24        const version = await resolveVersion(nodeId, "latest");
25        const result = await createNote({
26          contentType: "text",
27          content,
28          userId,
29          nodeId,
30          wasAi: true,
31          chatId,
32          sessionId,
33          metadata: {
34            treeos: { isReflection: true },
35            prestige: { version: version || 0 },
36          },
37        });
38        return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
39      } catch (err) {
40        return { content: [{ type: "text", text: `Failed to create versioned note: ${err.message}` }] };
41      }
42    },
43  },
44  {
45    name: "add-node-prestige",
46    description:
47      "Calls addPrestige() to increment a node's prestige level and create a new version.",
48    schema: {
49      nodeId: z
50        .string()
51        .describe("The unique ID of the node to add prestige to."),
52      userId: z.string().describe("Injected by server. Ignore."),
53      chatId: z
54        .string()
55        .nullable()
56        .optional()
57        .describe("Injected by server. Ignore."),
58      sessionId: z
59        .string()
60        .nullable()
61        .optional()
62        .describe("Injected by server. Ignore."),
63    },
64    annotations: {
65      readOnlyHint: false,
66      destructiveHint: false,
67      idempotentHint: false,
68      openWorldHint: false,
69    },
70    handler: async ({ nodeId, userId, chatId, sessionId }) => {
71      try {
72        const result = await addPrestige({
73          nodeId,
74          userId,
75          wasAi: true,
76          chatId,
77          sessionId,
78        });
79
80        return {
81          content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
82        };
83      } catch (err) {
84        return {
85          content: [
86            { type: "text", text: `Failed to add prestige: ${err.message}` },
87          ],
88        };
89      }
90    },
91  },
92];
93

Versions

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

Comments

Loading comments...

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