Egglets
An Egglet is a first-class app that runs inside Egg. The platform provides storage, tools, an LLM router, Gateway services, cross-Egglet messaging, refs, credentials, and headless fetch through a single SDK surface. Each Egglet declares which of those it uses, and the runtime enforces that contract.
If you are familiar with progressive web apps, an Egglet is the closest analogy. The difference is that Egglets are not built on a sandboxed third-party origin: they are first-party code shipped with Egg, with direct access to a richer set of platform services than a browser can safely give a website. In return for that access, every Egglet ships a manifest that declares what it touches.
What is an Egglet
An Egglet is a self-contained app that ships inside Egg. Feed (the RSS and social aggregator), People (the contacts directory), Studio (the media generator), Explore (the research canvas), and Agent (the chat surface) are all Egglets. Each one owns a top-level route, has its own page in the chrome, shows up in the Eggs menu, and has a card in Settings.
You build an Egglet when you want to add a first-class app to Egg, not a website that runs in a tab. The user installs Egg and gets your Egglet alongside everything else.
When to use an Egglet
Both Egglets and ordinary web apps run on the user’s machine. The differences are about where they live, what services they get, and how the user finds them.
| Dimension | Web app | Egglet |
|---|---|---|
| Where it runs | In any browser, isolated by origin | On the user’s hardware, inside Egg, with platform access |
| How users find it | A URL, a bookmark, a search result | The Eggs menu, the Egg landing page, a card in Settings |
| Distribution | You host and maintain it; the user visits | Ships with Egg; updates with the app |
| Background work | Service Worker, subject to OS suspension | Always-on pollers driven by the Gateway daemon, survive a closed window |
| Where data lives | IndexedDB or localStorage in the browser, or your own backend | Local SQL on the user’s disk, never leaves the device unless the user opts into sync |
| LLM access | Bring your own provider, your own API key, your own billing | The user’s configured model slot, often a local Ollama install with zero per-call cost and nothing leaving the machine |
| Network identity | Datacenter IP from your server, or the user’s IP from a tab | The user’s real residential IP for both ctx.fetch and headless fetches, so sites do not block you as scraper traffic |
| Cross-app surfaces | postMessage between same-origin frames | Tools the agent can call, IPC to other Egglets, shared @kind:id refs |
| Authentication | Cookies, OAuth flows you implement | User-authorized credential vault, accessed by declared source name |
Pick a web app when your audience is the open web and the standard browser surface is enough. Pick an Egglet when the work has to happen on the user’s hardware: their disk holds the data, their machine runs the model (often a local Ollama install, no per-call cost, nothing leaves the device), and their browser does the fetching from a real residential IP that sites do not block as scraper traffic. The agent’s tool catalog, cross-Egglet refs, and cross-device sync are bonuses on top of that local-first foundation.
Chrome extensions answer a different question. They exist to modify other people’s websites in real time. An Egglet cannot do that, but it has a much bigger surface for everything else.
| Dimension | Chrome extension | Egglet |
|---|---|---|
| Where it runs | Sandboxed inside someone else’s browser (Chrome, Edge, Brave) | First-class app inside Egg, on the user’s hardware |
| Page injection | Can inject content scripts into any URL pattern the user grants | Cannot inject. Page-level work goes through Browser Automation and the agent’s URL-pattern firewall |
| UI surface | A popup, a sidebar, a devtools panel, optional new-tab override | A full route in the chrome with its own page, its own card in Settings, its own place in the Eggs menu |
| Background work | Service worker, killed after about 30 seconds of idle under MV3 | Always-on pollers driven by the Gateway daemon, survive a closed window |
| Distribution | Chrome Web Store review, per-user install | Ships with Egg; updates with the app |
| API surface | chrome.* APIs, intentionally restricted; MV3 removed many capabilities | ctx.* SDK with structured storage, an LLM router, refs, IPC, credentials, and headless fetch |
| Storage | chrome.storage with small quotas, or IndexedDB | Local SQL on the user’s disk, with optional cross-device sync |
| LLM access | Bring your own API key, your own server, your own billing | The user’s configured slot, often a local Ollama install with zero per-call cost |
| Cross-app communication | chrome.runtime.sendMessage between extensions, rarely used in practice | Egglet IPC, shared refs, and a shared tool catalog. Designed for collaboration. |
Pick a Chrome extension when your job is to modify other people’s websites in real time. That is the one thing Egglets cannot do. Pick an Egglet for everything else.
Structurally, an Egglet is a folder with a manifest.json and an index.ts. The host loads both at app launch, calls the entry’s activate(), and the Egglet registers its tools, mounts its page, and starts whatever work it needs. The next sections cover what goes in the manifest, what the runtime hands you, and the contract between the two.
What the platform provides
An Egglet receives a single ctx object on activation. Every backing it can reach is on that object.
| Surface | What it does |
|---|---|
ctx.log | Tagged logger scoped to the Egglet’s id. |
ctx.routes | Mount the Egglet’s top-level React view at its declared route. |
ctx.tools | Register named tools the agent or other Egglets can call. |
ctx.storage | Read and write the Egglet’s own tables. SQL placeholders resolve to physical names. |
ctx.alwaysOn | Register pollers and other recurring work the platform drives. |
ctx.fetch | In-process outbound HTTP through the platform’s HTTP client. |
ctx.headless | Hidden-browser page fetches for SPAs and login-walled feeds. |
ctx.ai | Model surface: text (chat / embed / rerank), image, audio, video. |
ctx.eggletIpc | Fire-and-forget messaging between Egglets. |
ctx.refs | Resolve and own @kind:id refs that travel between Egglets. |
ctx.credentials | Read user-authorized credentials for declared sources. |
Each surface has a corresponding declaration in the manifest’s uses block. The host refuses calls to surfaces the manifest did not declare.
Declaring what you use
Every Egglet’s manifest declares the platform surfaces it uses: which LLM slot, which Gateway services, whether it makes outbound HTTP, whether it sends messages to other Egglets. Those declarations live in a uses block at the top level of the manifest.
This is not a documentation hint. The host checks every ctx.* call at runtime and throws if the manifest did not declare the matching surface. An Egglet that calls ctx.fetch without uses.network.fetch fails on its first call with a clear error naming the field to add.
// Excerpt from a manifest that uses the LLM router and a daemon-driven scheduler.
{
"id": "myEgglet",
"version": "0.1.0",
"label": "My Egglet",
"route": "/my",
"entry": "./index.ts",
"uses": {
"llm": { "role": "agent-secondary", "capability": "text" },
"gateway": { "schedules": true, "writes": true }
}
}
The reason for the rigor is user trust. When someone looks at an Egglet’s card in Settings, they should see a truthful picture of what it does, not a pinky-promise. Making every backing a declared field, then enforcing the declaration where the call happens, means the manifest is something the user (and the platform) can rely on without reading the code.
Full reference for every field in the uses block is on the Manifest page.
Quickstart
The smallest Egglet is a folder with two files. Create them under src/egglets/hello/.
// src/egglets/hello/manifest.json
{
"id": "hello",
"version": "0.1.0",
"label": "Hello",
"icon": "egg",
"description": "Demo Egglet with a greeting tool.",
"route": "/hello",
"entry": "./index.ts"
}
// src/egglets/hello/index.ts
import { defineEgglet } from "@egg/sdk";
export default defineEgglet({
async activate(ctx) {
ctx.log.info("activated v" + ctx.manifest.version);
ctx.routes.mount(() => import("./HelloPage"));
ctx.tools.register<{ name?: string }, { greeting: string }>(
"hello.greet",
async ({ name } = {}) => ({
greeting: `Hello, ${name ?? "world"}!`,
}),
);
},
});
// src/egglets/hello/HelloPage.tsx
export default function HelloPage() {
return <div>Hello from inside Egg.</div>;
}
That is a complete, working Egglet. On the next launch the host discovers manifest.json, calls activate(), mounts the page at /hello, and registers hello.greet as a callable tool. Settings shows the Egglet under the Egglets section, and the route is reachable from the Egg menu.
The example uses no platform backings beyond log, routes, and tools (none of which require a uses declaration). To call the LLM, write to a table, fetch a URL, or send a message to another Egglet, declare the corresponding entry under uses in the manifest first.
Where to go next
The Manifest page is the field-by-field reference, including the full uses block and validation rules. The SDK reference covers every ctx.* surface with signatures and examples. The Storage, Integrations, and Lifecycle pages go into specific topics in more depth.