Build & ship
From first line to other people's devices
The tools for the whole developer lifecycle live inside NAOMS itself, so building, testing and sharing your package happen in the same place your users already are — no separate hosting, no outside CI service, no app store to apply to.
The lifecycle
Build
Write your package against the building blocks — identity, storage, groups, trust, agents. No backend, no database to run, no login screen to design.
Connect by asking
Your app registers with a running NAOMS daemon and asks for exactly the scope it needs, such as read-only access to someone's graph. The owner approves the request on any of their devices — a phone, a browser, a CLI — and your app receives its own limited key, scoped to what was actually granted, never the owner's real identity key.
Package
A single typed manifest declares what your package needs and provides, so it moves as one signed unit rather than a folder of loose files.
Test
Continuous integration is built into the platform. Your tests run before anyone installs your package, with no outside CI service to wire up.
Distribute
Upload to your own Hive and share it with friends, or list it on a hub where people look for packages. A NAOMS-run hub adds extra validation on top; anyone is free to run their own hub.
Work together
Your Hive doubles as a workspace: chat with other developers, let coding agents join the same channels, and pool resources for CI runs and agent work.
Build your first package
Packages, not standalone client apps, are the main thing people build on NAOMS. A package is a small, self-contained app that lives inside the platform: it declares what it needs in one typed file, and the daemon finds it, runs it and enforces its limits automatically.
What a package is
A package is a folder with a manifest and, usually, its own screen. Drop it in the right place and the daemon discovers it at startup on its own; there is no central list of installed packages to edit by hand.
The manifest
Every package exports one typed manifest object: an id, a name, a version, and a category. This is the single source of truth the daemon checks before it lets your code touch anything.
Give it a screen
Point the manifest at a small script and the shell loads it as your package's own tab: it gets a scoped area of the page to draw into, and lifecycle calls for when someone opens it, switches away, or removes it.
Ask for capabilities
The manifest also lists exactly which data types your package may read or write, and the minimum trust level a person needs before they can install it. Anything not listed is refused automatically, not by convention.
Add a handler
A handler is a small function that does the work: reading or writing data, or reacting when new data of a type your package cares about shows up. It only ever sees the slice of the graph and chain your manifest declared.
Run and test it locally
Your package's tests live next to its code and run through the platform's own test runner, which enforces the same consent and audit rules a live daemon would. Restart the daemon and it picks up your new package on its own.
A trimmed manifest looks like this. The field names are real; only the package id is a placeholder.
// A package manifest is one typed constant. The daemon reads it to decide // what your package is allowed to do before any of its code runs. export const MANIFEST: NaomsFeatureManifest = { id: "hello-package", name: "Hello Package", version: "0.1.0", category: "productivity", // Points the shell at your package's own screen. uiModule: "hello-package/hello-package.js", // What this package is allowed to read or write, and who may install it. capabilities: [{ name: "hello.read", trustLevel: "connected" }], permissions: { minTrustLevel: "connected", domainScopable: false }, // One operation here becomes a typed method AND a WebSocket message // type AND an MCP tool for agents AND a CLI command -- one declaration, // four surfaces. operations: [{ name: "lookup", description: "Look something up.", version: 1, method: "ws_message", messageType: "hello_package.lookup", pattern: "send", returns: "HelloResult", }], chainTypes: [], graphTypes: [], };
A handler for that operation only ever sees a scoped context, never the raw database: ctx.api for your own declared operations, ctx.graph gated to the graph types you declared, ctx.chain gated to the chain events you declared, and ctx.send for the message types you declared. If a package reaches for something it never asked for in its manifest, the request is refused before it does anything, not caught afterwards.
Reacting automatically to new data works the same way: a small typed helper checks your declaration once at build time (that a graph type is not reserved, that an edge type is not protected, and so on), then wires your handler to fire only on the data types you named.
When it works, package it and hand it off. One command signs it, runs its tests, builds it and publishes it to a repository chain on your Hive; from there you can share it directly with friends, or list it on a hub where people go looking for packages. A NAOMS-run hub adds its own extra validation on top of the signature; anyone is free to run their own hub with different rules.
Connecting an app that runs outside NAOMS. If you would rather build a separate app that talks to a running daemon from outside it, the same approval model still applies: your app registers with the daemon and asks for a specific, narrow scope, a person approves that request on a device they already trust, and your app gets back its own key, good for exactly what was granted and nothing more.
Developer tools
Package manager
Discovery, install, approval gates and dependency resolution for your packages.
Marketplace & hubs
Publish through Hive-run repositories with attestation attached; anyone can run a hub.
CI
Continuous integration and job scheduling built into the platform itself.
Coding agents
Agents that follow a staged workflow suited to the kind of work they're doing, and can share a Hive's pooled resources.