Skip to content

Concept

A Lowkey plugin is a package installed into a project:

  1. A manifestmanifest.json: id, version, the capabilities it requests, and one block per declarative seam it uses. Static and machine-validated.
  2. An entry modulemain.ts (or main.js) exporting lifecycle handlers. Lowkey runs it on the host inside a Deno sandbox — a locked-down child process (a “jail”), never the daemon’s own process — with only the capabilities an admin consented to at install.
  3. Capabilities — the closed set of host-mediated doors the jail may use (net.egress, fs.project, session.fork, cards.emit, …), each granted explicitly. Undeclared access is denied by the sandbox, not by good behavior.

This is the one delivery method. The web installer — the only self-serve way a plugin reaches a Lowkey instance — requires a main.ts entry module and walks the admin through capability consent. There is no self-serve way to register a plugin that is only a manifest. See Sandboxed packages.

Author-hosted services plug in through a shim

Section titled “Author-hosted services plug in through a shim”

Lowkey does not host your app, your model, or your heavy compute. If your plugin is backed by a service you run elsewhere — a web app, an API, a GPU job, a non-JS stack — the installed plugin is a thin main.ts shim that reaches your service over the network with the net.egress capability (and, if it has a UI, renders it in a mount point via a surface). The jail stays light; your infrastructure does the heavy lifting. “Author-hosted” is this shim pattern, not a second kind of plugin.

Expose the outcome, seal the mechanism.

Every capability hands the jail a result and hides the machinery that produced it. You get a scoped, authenticated egress — not the credential. You get a forked observer turn — not the provider resume id or session path. You get a closed card schema — not a DOM. You get the resulting system-prompt mode — not the prompt-assembly code.

Consequences for plugin authors:

  • Adopt an external protocol where one exists. “I want my agent to do X” is almost always write an MCP server and register it — a borrowed, externally maintained protocol.
  • Typed capability where Lowkey adds value; opaque escape hatch otherwise. Cards are a typed, enumerated schema; a sandboxed iframe surface and net.egress are the escape hatches for what Lowkey cannot model.
  • The frozen set grows only under demonstrated demand. Once a capability is observable, it is depended on (Hyrum’s Law); see Compatibility.

Besides running code in the jail, the manifest can wire a set of declarative seams — an integration Lowkey performs from the manifest, without hosting your logic:

  • a surface iframe (your served URL in a host-owned mount point),
  • an mcp server (a command Lowkey spawns as a host subprocess at agent launch),
  • a webhook inbound trigger (a POST that dispatches an agent turn),
  • an agent contribution (a roster agent — on a built-in runtime, or runtime: external:<id> where Lowkey POSTs each turn to your endpoint),
  • cards and session-read HTTP routes.

A sandboxed package may declare these alongside its capabilities — the reference package renders its UI through a surface. But a plugin that uses only declarative seams and ships no main.ts cannot be self-installed: it is operator sideload only — a directory dropped into a plugins root by someone with host filesystem access (Seams, Quickstart). Note the mcp seam spawns your command on the host unsandboxed, which is why a manifest-only MCP plugin is an operator decision, not a self-serve one.

Validated shapes. Each is realized as a sandboxed package (the shim + capabilities), or — for the pure-declarative variants — as an operator-sideloaded manifest.

An external app the agent drives and that renders in a mount point. As a package: a main.ts shim + a surface + an mcp agent face + a webhook trigger + cards.

surface, MCP, webhook, cards.

An external service dispatches a turn (webhook) and receives the output over a scoped callback; the conversation may live entirely outside Lowkey. Lowkey never calls your service except through the callback you supply per request.

webhook source, scoped callback, turn-lifecycle event.

The plugin is (or reshapes) an agent — an agent contribution with its own systemPrompt, platformPromptMode, and translator: none (a personality agent). It renders as an ordinary roster agent. Two tiers:

  • Tier A — personality-as-profile. A roster agent on a built-in runtime with a custom prompt, platformPromptMode: minimal, translator: none. No service code.
  • Tier B — bring-your-own-agent. runtime: external:<id>. Lowkey proxies each turn to your endpoint over the versioned protocol; your service owns the model, prompt, and voice.

agent contribution, external runtime.

A package that reaches inward: an always-on side-panel over the session’s own corpus, recomputed on each turn. It reads the session’s own transcript + lineage (session.read / session-read), never platform internals.

side-panel, session-read, turn-lifecycle event.

Plugins get capabilities, events, scoped APIs, and tokens — never these:

  • the credential store (the token capability injects auth at the network boundary; the jail never holds a raw secret);
  • provider resume ids, absolute session paths, and the session JSONL format (session.read/session.fork return outcomes, resolved host-side);
  • the prompt-assembly mechanism (you get the resulting mode, not how it is built);
  • Lowkey’s internal AgentEvent type (wrapped by the external protocol);
  • run supervision, worktree isolation, and the auth stack;
  • in-process code execution in the daemon — the seal that makes every exposure safe. Your main.ts runs on the host but out of process, in a Deno jail; it never runs inside the daemon.