Extensions

TreeOS is modular and extensible. The core protocol defines nodes, notes, types, status, and AI interaction modes. These are the kernel. Everything else, values, schedules, versioning, scripts, understanding, is an extension that can be installed, disabled, removed, or replaced independently.

⚙️ How It Works
Every extension lives in its own directory with a manifest that declares what it needs and what it provides. The loader scans these manifests on boot, resolves dependencies in topological order, validates version constraints, and wires routes, tools, jobs, hooks, and models into the land automatically.
Extensions only receive the services they declare. If an extension says it needs the User model and the energy service, that's all it gets. Extensions communicate with each other through the getExtension() API and declared exports, never through direct file imports. If you uninstall one, everything that depends on it gracefully degrades.
📂 Think of It Like an OS
In a traditional OS, you install programs that extend what the system can do. A fresh OS has a file manager and a terminal. You install a browser, a text editor, a music player. Each program registers its file types, adds menu entries, and integrates with the system.
TreeOS works the same way. A fresh land has nodes, notes, types, and AI chat. You install extensions for values, scripts, understanding runs, billing, Solana wallets, blog posts. Each extension registers its routes, models, hooks, energy costs, and CLI commands. Uninstall one and the rest keep running.
📋 The Manifest
Every extension has a manifest.js that declares its contract:
export default { name: "understanding", version: "1.0.0", description: "Bottom-up tree compression with LLM summarization", // Required: won't load without these needs: { services: ["llm", "session", "chat", "orchestrator"], models: ["Node", "Contribution"], extensions: ["other-ext@^1.0.0"], // semver constraints supported }, // Optional: works without these (no-op stubs injected) optional: { services: ["energy"], extensions: ["gateway"], // uses if available, skips if not }, provides: { models: { UnderstandingRun: "./models/run.js" }, routes: "./routes.js", tools: "./tools.js", energyActions: { understanding: { cost: 1, unit: "per-node" } }, sessionTypes: { UNDERSTANDING: "understanding-orchestrate" }, cli: [ { command: "understand", description: "Start understanding run" }, ], }, };
The needs field lists required dependencies. If they're missing, the extension won't load. Version constraints like @^1.0.0 are checked against installed versions. The optional field lists services and extensions that enhance functionality but aren't required. If the host land doesn't have energy, calls become silent no-ops.
🔗 Inter-Extension Communication
Extensions never import each other's files directly. They communicate through declared exports and the getExtension() API.
// Expose functions from your extension export async function init(core) { return { router, exports: { myFunction, myOtherFunction, }, }; } // Use another extension's exports import { getExtension } from "../loader.js"; const other = getExtension("other-ext"); if (other?.exports?.myFunction) { await other.exports.myFunction(data); }
This keeps extensions fully decoupled. If the other extension isn't installed, getExtension() returns null and your code skips the call. No crashes, no broken imports.
🔄 Lifecycle
Search
Find extensions in the registry
treeos ext search blog
Install
Downloads files, resolves deps, verifies checksum
treeos ext install blog
Active
Loaded on boot, routes and tools available
treeos ext list
Disable
Skip on next boot, files stay
treeos ext disable blog
Enable
Load again on next boot
treeos ext enable blog
Uninstall
Delete directory, data stays in database
treeos ext uninstall blog
Dependencies are resolved automatically. Installing an extension that needs others will install them first. Uninstalling checks for dependents and warns before removing. Installs are verified with SHA256 checksums.
📦 Built-in Extensions
TreeOS ships with these extensions. They're all optional. A minimal land runs with just the core protocol.
fitness
Personal fitness coaching, workout programming, and exercise tracking
food
Calorie and macro tracking, meal planning, and nutritional coaching
tree-orchestrator
Chat/place/query conversation AI with planning and multi-step execution
html-rendering
Server-rendered HTML pages, share token auth, and page registration API for other extensions
understanding
Bottom-up tree compression with LLM summarization
dreams
Daily background maintenance: cleanup, drain, understand, notify
raw-ideas
Unstructured capture with auto-placement pipeline
gateway
External channel integration for Telegram, Discord, and web push
values
Numeric values and goals on nodes with tree-wide accumulation
scripts
Sandboxed JavaScript on nodes with value/goal mutation
prestige
Node versioning system with archived history
schedules
Date scheduling and calendar views for nodes
energy
Daily energy budget with tier-based limits and metering
billing
Stripe subscription tiers and energy purchases
transactions
Value transactions between nodes with approval policies
blog
Land-level blog for posts and updates
book
Export tree notes as shareable documents
solana
On-chain wallets and token operations per node
shell
Execute shell commands from AI (admin only)
land-manager
Autonomous land management agent for system operations
api-keys
User-generated API keys for programmatic access
llm-failover
Failover stacks for user and tree LLM connections
user-queries
Notes, tags, contributions, chats, notifications
deleted-revive
Soft delete with branch recovery
email
Email verification for registration and password reset
console
Colored log formatter with runtime log level control
🚀 Publishing
Anyone running a land can publish extensions to the registry. The registry is decentralized: your land authenticates via Canopy protocol (Ed25519 signed tokens). Published extensions include SHA256 checksums for integrity verification. No npm account needed, no build step. Just write a manifest.js and index.js, and publish.
treeos ext publish my-extension
The publishing land owns the extension. Other lands on the maintainers list can also push updates. Only the author land can change maintainers.
Extensions can also be installed directly from a git repository if the registry entry includes a repoUrl. Large extensions use this instead of inline file storage.
🔧 Building an Extension
An extension is a directory with at minimum two files:
manifest.js . declares needs, provides, version
index.js . exports init(core) function
routes.js . optional, Express router for HTTP endpoints
core.js . optional, business logic
model.js . optional, Mongoose schema
tools.js . optional, MCP tools for AI
The init(core) function receives a scoped services bundle and returns what the loader needs to wire up:
export async function init(core) { // Wire optional services if (core.energy) setEnergyService(core.energy); // Register hooks core.hooks.register("enrichContext", async ({ context, node, meta }) => { if (meta.myData) context.myData = meta.myData; }, "my-extension"); return { router, tools, exports: { myFunction, myOtherFunction }, jobs: [{ name: "my-job", start: () => {}, stop: () => {} }], }; }
🪝 Hooks
Extensions integrate deeply through an open hook system. The kernel fires events when things happen (note created, status changed, node deleted). Extensions listen and react without the kernel knowing they exist. Extensions can also fire their own hooks for other extensions to listen to.
This is how energy tracks usage without being hardcoded into the kernel. How prestige tags version numbers without the kernel knowing about versions. How understanding flags dirty nodes without the kernel knowing about compression. Each extension hooks into the events it cares about and ignores everything else.
🌍 Spatial Extension Scoping
Any node can control which extensions are active at that position. Block an extension at a tree root and it disappears from the entire tree. Block it on a branch and only that subtree is affected. The extension stays installed on the land. Other trees still use it. Navigate somewhere and the capabilities change.
Three access levels per extension per node:
active . full access. All tools, hooks, modes, metadata. The default.
restricted "read" . read-only tools pass. Write tools filtered out. Hooks still fire.
blocked . nothing. No tools, no hooks, no modes, no metadata writes.
This is how a Health tree has a Fitness branch and a Food branch where each extension can see the other's data (restricted to read) but can't write to it. The fitness coach can reference your nutrition while planning workouts, but can't create food nodes on the fitness branch.
treeos cd Health/Fitness treeos ext-restrict food read # food can see but not write here treeos ext-block solana # no wallets on this branch treeos cd Health/Food treeos ext-restrict fitness read # fitness can see but not write here
Tool filtering uses the MCP readOnlyHint annotation that every tool already declares. When an extension is restricted to read, only its read-only tools pass through. No manual tool lists needed. The kernel handles it.
💬 Two AI Entry Points
Every extension uses one of two core functions for AI. No manual MCP connections, no session management, no Chat tracking. One call handles everything.
runChat . Single message, persistent session. For user-facing chat.
const { answer } = await core.llm.runChat({ userId, username, message: "show me land status", mode: "land:manager", rootId: null, // for tree modes signal: null, // AbortController for cancellation });
OrchestratorRuntime . Multi-step chain with managed lifecycle. For background pipelines.
const rt = new OrchestratorRuntime({ rootId, userId, username, visitorId, sessionType: "my-pipeline", description: "Background job", modeKeyForLlm: "tree:analyze", }); await rt.init("Starting pipeline"); const { parsed } = await rt.runStep("tree:analyze", { prompt: "Analyze this tree", }); rt.setResult("Done", "my-pipeline:complete"); await rt.cleanup();
🧠 Custom Orchestrator
Extensions can replace the entire conversation orchestrator. The orchestrator controls how chat, place, and query messages are classified, planned, and executed. If no extension registers one, the built-in orchestrator runs.
return { orchestrator: { bigMode: "tree", async handle({ visitorId, message, socket, userId, ...ctx }) { // Full control over the AI conversation flow }, }, };
🖥️ Adding HTML Pages
If the html-rendering extension is installed, any extension can register its own server-rendered pages and use shared render functions.
import { getExtension } from "../loader.js"; export async function init(core) { const html = getExtension("html-rendering"); // Register a page route if (html?.exports?.registerPage) { html.exports.registerPage("get", "/my-dashboard", (req, res) => { res.send("<h1>My Dashboard</h1>"); }); } // Use shared render functions // html?.exports?.renderValues({ ... }) // html?.exports?.renderEnergy({ ... }) }
If html-rendering is not installed, all routes fall back to JSON responses automatically. No crashes, no conditional imports needed.
🌐 API Endpoints
GET/api/v1/land/extensionsList all loaded extensions with status
GET/api/v1/land/extensions/:nameGet manifest details for an extension
POST/api/v1/land/extensions/installInstall extension from registry (checksum verified)
POST/api/v1/land/extensions/:name/publishPublish local extension to registry (author + maintainers)
POST/api/v1/land/extensions/:name/disableDisable extension (restart to apply)
POST/api/v1/land/extensions/:name/enableRe-enable disabled extension
POST/api/v1/land/extensions/:name/uninstallRemove extension directory (checks dependents, data kept)
🔒 Security
Extensions run in the same Node.js process as the kernel. There is no sandbox. Manifests declare dependencies for documentation and scoped injection, but do not enforce access boundaries. Review all third-party extension code before installing.
Published extensions include SHA256 checksums computed from file contents. The installer verifies integrity before writing files. Publishing requires Canopy authentication (Ed25519 signed tokens). Only the author land or declared maintainer lands can update a published extension.