/docs / apps

Apps Overview

An App is an agent with its own UI. It bundles a persona, a complete frontend application, and an optional native sidecar binary into a standalone application that opens in its own window. Apps are the right choice when chat output isn't enough — dashboards, contact managers, journals, deal trackers.

Apps are complete frontend applications — not just HTML pages. Build with any framework: React, Vue, Solid.js, SvelteKit, HTMX, or vanilla JavaScript. The Nebo App SDK provides storage, identity, agent invocation, and CORS-free HTTP proxying regardless of your framework choice.

When to Build an App vs. a Skill

NeedBuildWhy
Teach the agent a new skill via instructionsSkillNo UI needed -- chat output suffices
Visual dashboard with charts and tablesAppNeeds rendered UI
Contact list with search and editAppInteractive CRUD UI
Journal with custom layoutAppPersistent visual state
Email drafting with templatesSkillChat + tool calls handle it
Rule of thumb: If the user needs to see and interact with a dedicated interface, build an app. If chat bubbles and tool calls are enough, write a skill.

App Directory Structure

my-app/
├── AGENT.md              # Required -- persona and instructions
├── manifest.json         # Required -- identity, permissions, window config
├── agent.json            # Optional -- workflows, skills, user inputs
├── ui/                   # Required -- static frontend files
│   ├── index.html        #   Entry point
│   ├── style.css
│   └── app.js
├── skills/               # Optional -- skill docs that teach the agent how to use tools
│   ├── workspace-mgmt/
│   │   └── SKILL.md
│   └── document-analysis/
│       └── SKILL.md
├── sidecar/              # Optional -- native backend (Rust recommended)
│   ├── Cargo.toml
│   ├── src/main.rs
│   └── target/release/
│       └── my-app-sidecar
└── <data_dir>/appdata/agents/{{id}}/ # Auto-created at runtime -- persistent data

What Each File Does

FilePurpose
AGENT.mdThe agent's persona. Written in markdown. Determines how the agent responds when invoked from the app.
manifest.jsonIdentity, version, permissions, window dimensions. An optional "type": "app" field marks the agent as an app.
agent.jsonOperational wiring -- workflows, skill references, event bindings, user inputs. Same format as any agent.
skills/SKILL.md files that teach the agent how and when to use the sidecar tools. Loaded automatically at launch.
ui/index.htmlEntry point for the app's frontend. Served at /apps/{{agent_id}}/ ui/index.html.
sidecar/Optional native binary. Runs as a gRPC service on a Unix socket. Nebo proxies requests to it.

manifest.json

{
  "id": "deal-tracker",
  "name": "@acme/agents/deal-tracker",
  "version": "1.0.0",
  "description": "Track real estate deals with AI-powered analysis.",
  "type": "app",
  "permissions": [
    "storage:readwrite",
    "subagent:invoke",
    "network:outbound"
  ],
  "window": {
    "title": "Deal Tracker",
    "width": 1024,
    "height": 768,
    "resizable": true
  }
}

Required Fields

FieldDescription
idUnique identifier. Must match the directory name.
nameQualified name (@org/agents/name) or display name.
versionSemantic version (1.0.0).
typeOptional. Set to "app" to mark this agent as an app (drives UI window handling).

Optional Fields

FieldDescription
descriptionOne-line description shown in the marketplace and apps page.
permissionsArray of permission strings (see below).
windowDefault window dimensions and title.

Permissions

Permissions use prefix:scope format. Declare only what your app needs.

PermissionWhat It Grants
storage:readwriteRead and write to the app's scoped KV store
subagent:invokeInvoke other agents via the SDK
network:outboundMake HTTP requests through the proxy
filesystem:readRead files from the user's system
shell:executeRun shell commands
memory:readRead agent memories
oauth:googleOAuth flow for Google services

Window Config

FieldDefaultDescription
titleapp nameWindow title bar
width1024Default width (pixels)
height768Default height (pixels)
resizabletrueAllow user resize

Nebo remembers window position and size per app -- the user's last arrangement is restored on reopen.

AGENT.md -- The Persona

The AGENT.md defines how the agent behaves when invoked from the app. Same format as any agent persona.

# Deal Tracker

You are a real estate deal analyst. When the user asks you to analyze a deal,
examine the financials, compare with market data, and provide a recommendation.

## Capabilities
- Analyze deal financials (cap rate, cash-on-cash, IRR)
- Compare properties against market comps
- Track deal pipeline stages
- Generate investment memos

## Guidelines
- Always show your math
- Flag deals with cap rates below 5% as high-risk
- Use conservative assumptions for projections

agent.json

The agent.json file wires together workflows, skill references, event bindings, and user inputs. It uses the same format as any Nebo agent. See the Agents guide for the full specification.

{
  "skills": [
    "skills/workspace-management",
    "skills/document-analysis",
    "skills/data-export"
  ]
}

Frontend (ui/)

The ui/ directory contains your app's static frontend. Any framework works -- React, Vue, Svelte, Solid, HTMX, or vanilla HTML/JS. Nebo serves these files as-is.

Entry Point

ui/index.html is the entry point. In the Tauri desktop app, each app opens in its own window using the neboapp:// custom protocol:

neboapp://{{agent_id}}/ 

This gives each app its own origin with / as the root URL. Your app's assets load from their natural paths (/style.css, /app.js, etc.) -- no base URL configuration needed. Any framework works out of the box.

In the browser fallback, apps are served at /apps/{{agent_id}}/ ui/index.html.

Using the SDK

Install the SDK as an ES module for React, Vue, Svelte, or Solid:

npm install @neboai/app-sdk

Or include the global SDK for HTMX and vanilla HTML:

<script src="/sdk/nebo.global.js"></script>

See the App SDK page for the full API reference.

Framework Notes

The neboapp:// custom protocol gives each app its own origin at /, so all frameworks work without special base URL configuration. For SvelteKit, use adapter-static with output to ../ui. HTMX apps work natively. React, Vue, Solid, and vanilla HTML need no special configuration -- build your app normally and place the output in ui/.

Skills Bundled with Apps

Apps can bundle skills in a skills/ directory alongside the sidecar. Each skill is a directory containing a SKILL.md file with YAML frontmatter and markdown instructions that teach the agent how and when to use each tool.

---
name: workspace-management
description: Manage projects, documents, and folders
triggers:
  - create a project
  - list projects
  - upload document
  - workspace stats
---
# Workspace Management

Tools for managing projects, documents, and folders.

## list_projects
List all projects for the current user.
- **Method:** GET /projects
- Returns: Array of project objects with id, name, description, created_at

## create_project
Create a new project.
- **Method:** POST /projects
- **name** (string, required): Project name
- **description** (string, optional): Project description
- Returns: Created project object

Skills are loaded automatically when the app's sidecar launches and unloaded when the sidecar shuts down. Reference them in agent.json using the path prefix (e.g. "skills/workspace-management"). Nebo matches the last path segment against the name: field in the SKILL.md frontmatter.

Complete Example: Journal App

A minimal app with no sidecar. The agent provides AI reflection, storage persists the last entry, the embedded chat gives the user a full conversational interface, and identity lets the UI adapt to the agent's configuration.

manifest.json

{
  "id": "journal",
  "name": "@nebo/agents/journal",
  "version": "1.0.0",
  "description": "AI-powered journal with reflection prompts.",
  "type": "app",
  "permissions": ["storage:readwrite", "subagent:invoke"],
  "window": {
    "title": "Journal",
    "width": 700,
    "height": 800,
    "resizable": true
  }
}

AGENT.md

# Journal

You are a thoughtful journaling companion. When the user writes an entry,
read it carefully and offer a brief, insightful reflection. Don't
summarize -- add depth. Ask one follow-up question that helps them
think more deeply about what they wrote.

ui/index.html

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
  <meta name="nebo-app-id" content="journal" />
  <script src="/sdk/nebo.global.js"></script>
</head>
<body>
  <h1>Journal</h1>
  <textarea id="entry" placeholder="What's on your mind?"></textarea>
  <button onclick="reflect()">Reflect</button>
  <div id="reflection"></div>
  <div id="chat"></div>

  <script>
    // Mount the embedded chat
    nebo.chat.mount(document.getElementById('chat'), {
      placeholder: 'Reflect on your day...',
      height: '300px'
    });

    // Use identity to personalize the UI
    nebo.identity.get().then(agent => {
      document.querySelector('h1').textContent = agent.displayName;
    });

    async function reflect() {
      const entry = document.getElementById('entry').value;
      const { text } = await nebo.agents.invoke(entry);
      document.getElementById('reflection').textContent = text;
      await nebo.storage.setItem('last-entry', entry);
    }
  </script>
</body>
</html>