Integrations
This page covers the platform surfaces that let an Egglet reach beyond its own walls. Cross-Egglet tool calls, the refs system, fire-and-forget messaging, the credential vault, the AI surfaces, headless page fetches, and always-on pollers. The SDK reference gives the signatures; this page covers when to use each surface and how they fit together.
Tools across Egglets
Every Egglet can register named tools. Tools are how the agent harness drives Egglet behavior: a tool is a JSON-callable function with a stable name. By default tools are private to the Egglet that registered them. Marking a tool external: true in the manifest opens it up.
To call a tool registered by another Egglet, the caller must:
- Know the tool name (by convention,
<ownerEgglet>.<verb>). - Have an entry for that name in its own
permissionsarray. - Find that the owning Egglet declared the tool
external: true.
Cross-Egglet calls fail if any of those checks does not pass. Self-calls (calling tools registered by the same Egglet) skip the permission check.
// In feed's manifest: "permissions": ["people.count", "people.get"] // In feed's activate(): const { count } = await ctx.tools.call<Record<string, never>, { count: number }>( "people.count", );
The agent harness uses the same surface. When the LLM emits a tool call, the harness dispatches through the same registry the Egglets use. From the model’s perspective, every Egglet’s external tools are part of one catalog.
Refs system
A ref is a stable, serializable handle of the form @kind:id. Refs travel: they can be embedded in tables, agent prompts, sync payloads, IPC messages, and tool arguments. Resolution happens through the kind’s registered resolver, not through a direct read of the owning Egglet’s tables.
Each kind has exactly one owning Egglet. The owner declares the kind in manifest.refs and registers a resolver during activate(). A second Egglet trying to claim the same kind fails activation. Refs whose kind nobody owns resolve to null, and the renderer falls back to displaying the raw ref string.
// People owns the "e" (entity) kind. "refs": ["e"] // In people's activate(): ctx.refs.register("e", async (id) => { const row = await ctx.storage.one<{ id: string; name: string }>( "SELECT id, name FROM {{entities}} WHERE id = ?", [id], ); if (!row) return null; return { kind: "e", id: row.id, label: row.name, icon: "user", route: `/people/${row.id}`, }; });
Other Egglets render refs without knowing who owns them. ctx.refs.resolve("@e:alice-123") returns a RefView the consumer can format. ctx.refs.parse(text) finds every ref in a block of text, returning the raw substring, kind, id, and index, so an agent harness can expand them in prompts.
The format is @<kind>:<id> where kind matches [a-z][a-z0-9]* and id matches [a-zA-Z0-9_-]+. The colon is what disambiguates a ref from a casual @username mention; user-typed @bob is not a ref because it has no colon.
Egglet IPC
Egglet IPC is fire-and-forget messaging between Egglets. ctx.eggletIpc.send queues a payload to the target Egglet’s subscribers. If the target is not loaded or has not subscribed, the message is dropped silently with a debug log; the sender cannot tell.
Use Egglet IPC for one-way signals: “a new item arrived,” “a profile was visited,” “please refresh.” Use ctx.tools.call when you need a response. Both methods on ctx.eggletIpc require uses.eggletIpc: true in the manifest.
Egglet IPC is unrelated to the user-facing Agent Inbox. The Agent Inbox is a different concept at a different layer (a queue of items the user has sent the agent). Egglet IPC is an in-memory message bus between platform-level Egglets.
Subscribers receive an EggletIpcMessage with the sender’s id, the payload, and a delivery timestamp. Handler errors are isolated: one bad subscriber does not stop the others. Handlers run asynchronously after the synchronous send returns, so a sender does not reentrantly recurse into a receiver.
const handle = ctx.eggletIpc.subscribe<{ kind: string }>((msg) => {
if (msg.payload.kind === "refresh") refresh();
});
// later
handle.cancel();
Credentials
Credentials are user-authorized secrets the Egglet can read but cannot create or revoke. The user manages them in Settings; the Egglet declares which sources it needs in manifest.credentials and reads through ctx.credentials.get(source).
The host refuses any get for a source not listed in the manifest. The returned CredentialEntry has a credentialType (such as "token", "api_key", "cookies") and a dataJson string the Egglet parses. Some entries carry an expiresAt; treat null as “does not expire.”
const entry = await ctx.credentials.get("github");
if (!entry) throw new Error("no GitHub credential authorized");
const { token } = JSON.parse(entry.dataJson);
const r = await ctx.fetch("https://api.github.com/user", {
headers: { Authorization: `token ${token}` },
});
An Egglet that needs a credential the user has not authorized should detect null from get and surface a clear message in its UI. There is no API for prompting the user from inside the Egglet; that path lives in the Settings credential editor.
AI surfaces
EggletContext.ai exposes every model-backed operation, grouped by modality: text (chat / embed / rerank), image, audio, video. Every call routes through the Gateway daemon: capability resolution, tier gating, credential lookup, and the provider HTTP call all happen there. The Egglet never sees an API key or an endpoint.
Chat slot routing is driven by manifest.uses.llm:
| Role | Typical use |
|---|---|
agent-secondary | Cheap or fast model. Default. Good for summarization, classification, extraction. |
agent-primary | The user’s headline model. Reserve for high-value turns. |
| Capability | Effect |
|---|---|
text | Text-only. Default. |
vision | Pick a model in the slot that accepts images. |
Both ctx.ai.text.chat.complete and ctx.ai.text.chat.stream require uses.llm in the manifest. Token counts are reported in the response; the platform’s usage rollups display per-Egglet totals in Settings.
Other modalities (ctx.ai.image.*, ctx.ai.audio.*, ctx.ai.video.*) currently gate only at the daemon’s tier check, not at the manifest level. The Egglet picks a capability slug (e.g. imagen, vision, tts); the user picks which local or cloud asset backs each one in Settings.
Headless fetch
Some pages cannot be fetched with plain HTTP because they render in the browser at runtime. SPAs, login-walled feeds, anything that ships a thin shell with everything else painted by JavaScript. ctx.headless.fetch spawns a hidden browser, navigates, optionally evaluates a JS expression after a configurable wait, and returns the rendered HTML.
Headless is more expensive than ctx.fetch: a fresh browser context per call, real navigation, real JS execution. Reach for it when ctx.fetch returns the wrong content because the page needs a runtime.
const r = await ctx.headless.fetch("https://twitter.com/", {
waitMs: 4_000,
evalJs: "document.querySelectorAll('article').length",
credentialSource: "twitter",
});
ctx.log.info(`title=${r.title} articles=${r.evalResult}`);
If a credential source is named, it must appear in manifest.credentials. The Gateway loads stored cookies for that source and injects them before navigation.
Always-on pollers
A poller is recurring work the platform drives. The Egglet supplies the handler; the platform supplies the cadence. Pollers belong on the daemon side so they can survive a closed window and catch up missed cycles after the user re-opens the app.
Each poller has a name scoped to the Egglet. The platform records the last fire time per name and uses it on activation to decide whether to fire immediately (the "if-due" default), wait the full interval ("never"), or fire on every register ("always").
ctx.alwaysOn.poller({
name: "refresh",
interval: 600,
fireOnActivate: "if-due",
handler: async () => {
await refreshAll();
},
});
Errors thrown by the handler are logged and do not stop the poller. Handlers should be self-contained, idempotent enough to tolerate a missed catch-up running shortly after a regular fire, and brief; the next interval starts from the end of the previous run.
To stop a poller manually, hold onto the returned handle and call handle.cancel(). The platform also cancels every handle on Egglet deactivate.