/docs / agents

Agents

An Agent is a job description with a schedule. It bundles workflows and skills into a complete job profile — and it defines when each workflow runs. The Agent is the only artifact type that owns event bindings.

Only AGENT.md (persona) is required. agent.json (operational wiring — workflows, triggers, pricing) is optional, and manifest.json (marketplace identity) is optional too — it's only required for apps.

For packaging format and manifest.json, see Packaging.

agent.json — The Job Definition

The agent.json carries the operational structure: which workflows to run, when to run them, and what events to listen for. This is the file that makes an Agent more than a folder of workflows — it's what makes it an employee who already knows the job.

{
  "workflows": {
    "morning-briefing": {
      "trigger": {
        "type": "schedule",
        "cron": "0 7 * * *"
      },
      "description": "Daily morning briefing before the user wakes up",
      "activities": [
        { "id": "gather", "prompt": "Gather today's calendar, unread emails, and open tasks" },
        { "id": "write", "prompt": "Write a concise morning briefing from the gathered data" }
      ]
    },
    "day-monitor": {
      "trigger": {
        "type": "heartbeat",
        "interval": "30m",
        "window": "08:00-18:00"
      },
      "description": "Monitors for changes and interrupts only when something matters"
    },
    "evening-wrap": {
      "trigger": {
        "type": "schedule",
        "cron": "0 18 * * *"
      },
      "description": "End of day summary — what happened, what's unresolved, what's tomorrow"
    },
    "interrupt": {
      "trigger": {
        "type": "event",
        "sources": ["calendar.changed", "email.urgent"]
      },
      "description": "Fires when something urgent surfaces that needs immediate attention"
    }
  },
  "requires": {
    "plugins": ["PLUG-PJ3Z-ECFV"]
  },
  "skills": [
    "@nebo/skills/briefing-writer@^1.0.0"
  ],
  "pricing": {
    "model": "monthly_fixed",
    "cost": 47.0
  },
  "inputs": [
    {
      "key": "timezone",
      "label": "Your Timezone",
      "type": "select",
      "required": true,
      "default": "US/Eastern",
      "options": [
        { "value": "US/Eastern", "label": "Eastern" },
        { "value": "US/Pacific", "label": "Pacific" }
      ]
    },
    {
      "key": "briefing_focus",
      "label": "What should briefings focus on?",
      "type": "textarea",
      "placeholder": "e.g., sales pipeline, client deadlines, market news"
    }
  ],
  "defaults": {
    "timezone": "user_local",
    "configurable": [
      "workflows.morning-briefing.trigger.cron",
      "workflows.evening-wrap.trigger.cron",
      "workflows.day-monitor.trigger.interval"
    ]
  }
}

agent.json Fields

Top-Level

FieldTypeRequiredDefaultDescription
workflowsmapno{}Workflow bindings with triggers (keyed by binding name)
requiresobjectno{}Hard dependencies. requires.plugins is an array of plugin install codes (e.g., ["PLUG-PJ3Z-ECFV"]) that are auto-installed before skills during agent install.
skillsstring[]no[]Additional skill qualified names (beyond what workflows declare)
inputsarrayno[]Input field definitions for the agent's Configure tab (see Input Fields)
pricingobjectno--Pricing configuration (see below)
defaultsobjectno{}Default settings and user-configurable fields (see below)
memoryobjectno{}Memory scoping configuration (see Memory)
toolsAgentToolDef[]no[]Sidecar HTTP endpoints exposed as native LLM tools (see Sidecar Tool Definitions)
scopesmapno{}Named tool restriction sets (see Tool Scoping)
soulstringno--Agent personality/voice (DB-only via agents.soul column; see Agent Soul). Not parsed from agent.json — set via Settings UI or API.

Pricing

FieldTypeRequiredDescription
modelstringyesPricing model: monthly_fixed or per_run
costfloatyesPrice in USD. For monthly_fixed, the monthly subscription price. For per_run, the cost per workflow execution.

Defaults

FieldTypeDescription
timezonestringTimezone for schedule triggers. user_local resolves to the user's system timezone at install time. Also accepts IANA timezone names (e.g., America/New_York).
configurablestring[]JSON paths within agent.json that the user can override after installation.

Input Fields

Input fields define a dynamic form rendered in the agent's Configure tab. Users fill in values after installation; the agent uses them at runtime.

FieldTypeRequiredDescription
keystringyesUnique reference key (used in {{ key }} template substitution and system prompt injection)
labelstringyesDisplay label shown to the user
typestringyesField type: text, textarea, number, select, checkbox, radio
descriptionstringnoHelp text displayed below the field
requiredbooleannoWhether the field must be filled before the agent can activate
defaultanynoDefault value pre-filled in the form
placeholderstringnoPlaceholder text for text/textarea fields
optionsarraynoFor select/radio fields: [{ "value": "...", "label": "..." }]

How input values are used:

  • System prompt injection — All filled input values are appended to the agent's system prompt as a "Configured Inputs" section. The LLM sees them and uses them without asking the user again.
  • Watch trigger template substitution{{ key }} placeholders in watch trigger commands are replaced with the corresponding input value at runtime. Example: gmail +watch --project {{ gcp_project }}. The placeholder name must exactly match an input key — if the command uses {{ gcp_project }}, there must be an input with "key": "gcp_project". Unmatched placeholders are left as literal text, which will cause the watch command to fail or behave unexpectedly.
  • Stored separately from schema — The input field schema lives in agent.json. The user-supplied values are stored in the input_values DB column and updated via PUT /agents/{id}/inputs.

Memory

Controls how the agent's memories are scoped and inherited. By default, each agent gets its own isolated memory pool (user_id:agent:{agent_id}). These fields extend that behavior.

FieldTypeDefaultDescription
inherit_userbooleanfalseWhen true, the agent can read the user's main Nebo preferences (timezone, language, communication style). Read-only — the agent never writes to the user's memory scope.
context_isolatedbooleanfalseWhen true, memories are isolated per contextId from SDK embed sessions. Each document/project/record gets its own memory pool.

Example:

{
  "memory": {
    "inherit_user": true,
    "context_isolated": true
  }
}

When to use context_isolated: Use this when your agent handles multiple independent contexts — legal clients, project documents, patient records — where facts from one context must never leak into another. The contextId comes from the SDK embed:

nebo.chat.mount(container, { contextId: document.id });

Each context maintains its own memory pool. Agent-wide memories (stored without a contextId) are still visible to all contexts.

When to use inherit_user: Use this when your agent needs user preferences without asking for them — timezone for scheduling, language for communication, name for personalization. The agent reads from the main Nebo companion's tacit/preferences memories (read-only).

Three-Tier user_id Convention

Memory scoping follows a layered naming convention:

Layeruser_id formatDescription
Layer 1 (User)"user123"Nebo companion preferences (timezone, language, style)
Layer 2 (Agent)"user123:agent:brief"Agent-wide memories
Layer 3 (Context)"user123:agent:brief:ctx:doc-123"Per-document/project memories

How the config flags interact:

  • Default (both false) — reads/writes Layer 2 only
  • inherit_user: true — reads Layer 1 (read-only) + reads/writes Layer 2
  • context_isolated: true — reads/writes Layer 3 instead of Layer 2
  • Both enabled — reads all 3 layers, writes Layer 3

Workflows Overview

The workflows map pairs triggers (when to run) with activities (what to do). Each binding connects a trigger to either inline activities or an external workflow reference.

For the full reference — trigger types, activities, event system, watch triggers, budget math, and examples — see Workflows & Automation.

Trigger Types (Summary)

TypeDescription
scheduleFires on a cron schedule
heartbeatFires at a recurring interval (with optional time window)
eventFires when a matching event occurs
watchLong-running plugin process emitting NDJSON
folderFires when files change in a watched directory
manualOnly fires by explicit user request or API call

AGENT.md — The Persona

The AGENT.md is the agent's job description in prose. It defines who the agent is when operating as this Agent — capabilities, communication style, priorities, judgment calls. Think of it as the job description.

# Executive Assistant

You are an Executive Assistant. You have been up for two hours before the
principal opens their eyes. You already know what their day looks like,
what matters most, and what can wait.

Your job is to make sure the principal is never blindsided. You surface
what's important, suppress what isn't, and interrupt only when something
genuinely demands attention.

## Communication Style

- Lead with the one thing that matters most today
- Be direct. No preamble, no pleasantries in briefings
- When you interrupt during the day, say why in one sentence
- Evening wraps are reflective, not just recaps

## Judgment

- "Important" means: time-sensitive, high-stakes, or likely to be missed
- If two things compete for attention, pick the one with a deadline
- Never surface something just because it's new — surface it because
  it matters
- When in doubt, mention it briefly rather than omit it entirely

## What You Don't Do

- You don't make decisions for the principal — you inform them
- You don't send messages on their behalf unless explicitly told to
- You don't editorialize about their schedule — you present it clearly

Agent Soul

The soul field is separate from AGENT.md. Where AGENT.md defines capabilities, communication style, and the job description, soul captures voice, personality quirks, tone, ethical boundaries, and values — the character behind the role.

  • Stored in the agents.soul DB column
  • Injected into prompt assembly as agent_soul context
  • Editable in Settings > Soul section

Example:

# Core Truths
- Be genuinely helpful, not performatively helpful
- Have opinions and share them when relevant

# Vibe
- Conversational and warm, not corporate
- Direct and honest — skip filler words

# Boundaries
- Private things stay private. Period.
- When in doubt, ask before acting externally

When to use soul vs AGENT.md:

AGENT.mdsoul
PurposeJob descriptionPersonality
ContainsCapabilities, priorities, judgment rulesVoice, tone, quirks, values, ethical lines
AnalogyWhat the agent doesWho the agent is

Tool Scoping

Agents can declare named scopes that restrict which tools, skills, and plugins are available in a given context. This lets the same agent operate with different capabilities depending on where it runs.

Declaration in agent.json:

{
  "scopes": {
    "write": { "tools": ["file_write", "email_send"], "skills": [], "plugins": [] },
    "read": { "tools": ["file_read", "email_search"], "skills": [], "plugins": [] }
  }
}

Each scope is a ToolScope struct with three fields:

  • tools — tool names to allow
  • skills — skill qualified names to allow
  • plugins — plugin install codes to allow

SDK embeds pass a scope parameter, and the runner restricts tool access to that named scope's allowlist:

nebo.chat.mount(container, { scope: "read" });

Use case: A public-facing embed uses the read scope (search and view only), while the main Nebo UI uses the write scope (full access). Same agent, different capabilities per context.

Sidecar Tool Definitions

Tools can be declared directly in agent.json, turning sidecar HTTP endpoints into native LLM tools (not proxied through a wrapper):

{
  "tools": [
    {
      "name": "get_document",
      "description": "Fetch a document by ID",
      "method": "GET",
      "path": "/documents/{id}",
      "input_schema": {
        "type": "object",
        "properties": {
          "id": { "type": "string" }
        }
      }
    }
  ]
}

Behavior:

  • Each entry becomes a tool the LLM can call directly
  • Path parameters are resolved from input: /documents/{id} with {"id": "abc"} becomes /documents/abc
  • HTTP method determines body vs query handling (GET uses query params, POST/PUT/PATCH send a JSON body)
  • Discovery is also available via a GET /_tools endpoint on the sidecar, returning the same format

Multi-Agent Delegation

An agent can delegate tasks to other installed agents using the agents domain tool. The delegating agent pauses while the target agent runs with its full identity — persona, plugins, skills, and memory scoping.

Tool Call

agents(action: "delegate", name: "Deal Tracker", prompt: "List all open deals closing this month")
ParameterTypeRequiredDescription
namestringyes*Target agent's display name
idstringyes*Target agent's ID (alternative to name)
promptstringyesTask description for the delegated agent
waitbooleannoWait for result before continuing (default: true). Set false for background delegation.
max_iterationsintegernoMaximum agentic loop iterations for the delegated agent (0 = default)

* One of name or id is required.

Session Isolation

Each delegation creates a separate session keyed as subagent:<parent_id>:<child_id>. The delegated agent:

  • Loads its own AGENT.md persona and skills
  • Runs with its own plugin set (from requires.plugins in its agent.json)
  • Gets its own memory scope — it does not read the parent's conversation history
  • Returns a text result to the parent when complete

When to Use Delegation

ScenarioApproach
Agent needs a capability it doesn't have (e.g., calendar access)Delegate to an agent that has the required plugins
Task requires a different persona or expertiseDelegate to a specialist agent
Background processing while continuing the conversationDelegate with wait: false
Sequential pipeline across multiple agentsChain delegations in workflow activities

Constraints

  • The target agent must be installed (appears in agents(action: "list"))
  • If not already active, delegation auto-activates the target agent
  • Delegation inherits the parent's cancellation token — cancelling the parent cancels the child
  • There is no built-in depth limit, but deep delegation chains consume more tokens and time

Followup Suggestions

After each chat turn, the agent generates 2-3 contextual follow-up suggestions displayed as clickable chips below the response. This helps users continue the conversation without typing.

Constraints:

  • 2-8 words each
  • Not phrased as questions
  • No "Tell me" / "Can you" patterns
  • Uses the cheapest available provider (avoids Janus credits for background operations)

Followup generation happens asynchronously after the main response completes. The chips are delivered to the frontend via WebSocket.

Auto-Install Cascade

When a user installs an Agent:

  • Parse agent.json for requires.plugins and skill references
  • Install required plugins from requires.plugins (install codes like PLUG-XXXX-XXXX)
  • For each workflow binding: install its declared skill dependencies
  • Install any additional skills listed in top-level skills array
  • Skills cascade to their own plugin dependencies (via plugins: in SKILL.md frontmatter)
  • Register all trigger bindings from agent.json
  • Load the AGENT.md persona into the agent's context

The user installs a job. Everything else cascades — plugins first, then skills.

Agent Lifecycle

Install, activation, and deletion all run through one canonical routine — there is no second pathway.

Install

  • Installing an agent first runs its dependency cascade — plugins, then skills (skills cascade to their own plugin dependencies).
  • A single canonical install routine then reloads the agent, materializes its workflows, seeds update-tracking (so future marketplace updates are detected), and refreshes the agent roster.
  • A single-agent install (an AGNT- code) auto-activates — the agent starts running immediately.
  • An agent installed as part of a collection (a COLL- code) arrives Paused — the user activates it when ready.

Delete

Deleting an agent stops its worker, unregisters its triggers, and cleans up the data it owns — its chats, sessions, memories, and workflow runs.

Filesystem Watcher (Development)

During development, agents placed in the Nebo data directory's user/agents/ folder are detected automatically by the filesystem watcher. The data directory is platform-native — ~/Library/Application Support/Nebo/ on macOS, %APPDATA%\Nebo\ on Windows, ~/.local/share/nebo/ on Linux (override with NEBO_HOME):

  • Added: New agent directory or symlink with AGENT.md — appears in sidebar and Apps page
  • Changed: Edits to AGENT.md, agent.json, or manifest.json — metadata updated in DB, worker restarted if active
  • Removed: Deleted directory — agent soft-deactivated (DB record preserved with is_enabled=0)

Changes are debounced at 1 second. No restart needed. Symlinks are fully supported — you can symlink an entire app directory from your source repo into the data directory's user/agents/ folder and changes take effect immediately.

Validation Rules

  • Each workflow binding must have a trigger
  • Trigger type must be one of: schedule, heartbeat, event, watch, folder, manual
  • Workflow bindings with invalid triggers are skipped with a warning — they do not prevent the agent from loading
  • Schedule triggers must have a valid cron expression
  • Heartbeat triggers must have a valid interval (e.g., "30m", "1h")
  • Event triggers should have at least one entry in sources — an empty array is accepted but the trigger will never fire
  • Skill refs must be qualified names (@org/skills/name)
  • Watch triggers must have a plugin and either event or command (both recommended — command as fallback for when event resolution fails)
  • Activity IDs must be unique within each binding
  • If budget.total_per_run > 0, the sum of all activity token_budget.max values must not exceed it
  • All {{ key }} placeholders in watch trigger commands must match an input key exactly
  • An Agent with no workflows is valid — it provides only a persona and skill declarations