Manifest
Every Egglet ships a manifest.json alongside its entry module. The manifest describes who the Egglet is, what it owns, and what runtime backings it uses. The host reads the manifest once on activation and enforces it for the life of the process.
Required fields
An Egglet manifest must include these fields. Everything else is optional.
| Field | Type | Purpose |
|---|---|---|
id | string | Stable, unique id. Convention: lowercase, single word, matches the folder name. |
version | string | SemVer. Visible in Settings. See Versioning. |
label | string | Human-readable name shown in menus and Settings. |
route | string | Path the Egglet mounts at, like /hello. The Egglet owns this route. |
entry | string | Relative path to the entry module, like ./index.ts. |
Recommended but optional:
| Field | Type | Purpose |
|---|---|---|
icon | string | Lucide icon name. Shown in menus. |
description | string | One sentence shown on the Egglet’s card in Settings. |
menu | "auto" or "hidden" | Default visibility in user-facing menus. The user’s per-Egglet toggle in Settings overrides this. Defaults to "auto". |
The uses block
The uses block names every runtime backing the Egglet relies on. The host checks each ctx.* call at runtime: a call to a backing that is not declared throws with a clear error pointing at the field to add. An Egglet that needs no platform backings beyond logging, routing, and tool registration can omit uses entirely.
// All four sections of the uses block.
"uses": {
"llm": { "role": "agent-secondary", "capability": "text" },
"gateway": { "schedules": true, "syncs": true, "headless": true, "writes": true },
"network": { "fetch": true },
"eggletIpc": true
}
uses.llm
Declared when the Egglet calls ctx.ai.text.chat.complete() or ctx.ai.text.chat.stream(). Object shape:
| Subfield | Values | Default |
|---|---|---|
role | "agent-primary" or "agent-secondary" | "agent-secondary" |
capability | "text" or "vision" | "text" |
Both subfields are optional. The Egglet picks the slot it draws from. Which provider serves the slot is a user setting, not the Egglet’s choice.
uses.gateway
Declares the Egglet’s dependency on the Gateway daemon. Each subfield is an explicit boolean.
| Subfield | What declaring it means |
|---|---|
schedules | The Egglet registers ctx.alwaysOn.poller() handlers driven by the Gateway scheduler. |
syncs | One or more of the Egglet’s tables has a sync_category set, so writes participate in cross-device sync. |
headless | The Egglet calls ctx.headless.fetch(). |
writes | The Egglet calls ctx.storage.exec(). Reads (storage.all, storage.one) do not require this declaration. |
None of these are auto-derived from other fields. The manifest is the source of truth.
uses.network
Declares in-process outbound HTTP. Currently a single subfield:
| Subfield | What declaring it means |
|---|---|
fetch | The Egglet calls ctx.fetch(). This routes through the platform’s in-process HTTP client and is distinct from uses.gateway.headless, which spawns a hidden browser. |
uses.eggletIpc
A single boolean. Set true if the Egglet calls ctx.eggletIpc.send() or ctx.eggletIpc.subscribe().
Tables and tools
Tables
The tables array declares every table the Egglet reads from or writes to. Each entry has a logical name (used in SQL placeholders) and either a schema file or an external physical name.
"tables": [
{ "name": "log", "schema": "./schemas/log.sql" },
{ "name": "items", "schema": "./schemas/items.sql", "sync_category": "items" },
{ "name": "feeds", "physical_name": "external_feeds_table" }
]
An entry with schema declares an owned table whose CREATE statement lives in the named SQL file. The host runs schema files at activation and re-runs them on every boot, so they should be idempotent (CREATE TABLE IF NOT EXISTS). An entry with physical_name binds the logical name to a table that exists outside the Egglet, useful during a port; that table is not owned and is not dropped on uninstall.
If any table has sync_category set, uses.gateway.syncs must also be declared. Activation fails otherwise.
Tools
The tools array declares every tool name the Egglet plans to register. The names listed here must match the names passed to ctx.tools.register(); tools registered without a manifest entry are rejected.
"tools": [
{ "name": "feed.poll", "external": true, "params": "./tools/poll.json" },
{ "name": "feed.unreadCount", "external": true },
{ "name": "feed.private" }
]
Set external: true when the tool is callable from another Egglet or from the agent harness. Tools without external are private to the registering Egglet. params is an optional path to a JSON Schema describing the tool’s arguments.
Refs and credentials
Refs
An Egglet that owns one or more ref kinds (the @kind:id handle system) declares them in the refs array.
"refs": ["e", "c"]
Each entry is a short kind id. The Egglet’s activate() must call ctx.refs.register() for every kind listed. A kind already claimed by another Egglet causes activation to fail. See the Refs system for details on how refs travel.
Credentials
The credentials array names every credential source the Egglet may read through ctx.credentials.get(). Sources not listed here cannot be read; the host throws.
"credentials": ["github", "reddit"]
Egglets cannot create or revoke credentials. That path lives in Settings, where the user makes the trust decision.
Permissions
An Egglet that calls tools registered by another Egglet must declare each tool name in permissions.
"permissions": ["people.count", "people.get"]
Self-calls (calling tools registered by the same Egglet) do not need an entry. Calls to tools owned by another Egglet without a matching entry are refused. Tools with external: false on the owner side cannot be called cross-Egglet regardless of what the caller declared.
Validation
The host checks the manifest at activation time and refuses to load Egglets that are internally inconsistent. The most common failures:
- A table with
sync_categoryexists, butuses.gateway.syncsis not declared. - The Egglet calls a
ctx.*backing without the matchingusesentry. Caught at runtime, not load time, but surfaces with the same message style. - A ref kind listed in
refsis already claimed by another Egglet. - A tool name is registered that is not in the
toolsarray. - The entry module does not export a default with an
activate()function.
When an Egglet fails to activate, Settings shows the failure reason on the Egglet’s card. Reload the Egglet after fixing the manifest to clear the error without restarting Egg.