Overview
intentic is a lean core plus extensions: a folder with a manifest that can extend the agent itself, not just the editor UI.
On this page(11 sections)
The whole lifecycle
Seven steps take an extension from an empty directory to a payout, and not all are yours: Discover and Install happen on the other side of the listing. What you publish is only ever met as what somebody else installs.
What an extension is
A directory with an intentic-extension.json file at its root. That's the whole definition. The manifest names a publisher, a name, and the things the extension contributes; if it contributes UI, the directory also holds a prebuilt bundle exporting an activate() function.
Usually that directory is a git repository, because that is how an extension travels to somebody else's sandbox. It need not be: a directory inside your own sandbox is already an extension, which is what makes "ask the agent to build me one" a reasonable sentence.
{
"publisher": "acme",
"name": "incidents",
"version": "1.0.0",
"category": "work",
"icon": "exclamation-triangle",
"engines": { "intentic": "^2.0.0" },
"permissions": { "sandbox": ["GET /logs", "GET /logs/tail"] },
"contributes": {
"views": [{ "id": "incidents", "label": "Incidents", "surface": "rail", "badge": true }]
}
}Identity is derived, never declared: this extension is acme.incidents everywhere in the product, because that's what publisher and name say. There is no id field to contradict the one the install dialog showed you.
It extends the agent, not just the editor
This has no equivalent in an editor's plugin model, and it is the reason to build one. A sandbox is a whole machine: an agent, its tools, its credentials, its image. An extension can reach all of it.
The UI half
| Contributes | What it adds |
|---|---|
views | A tile in the left rail, a per-repo panel in the Workspace tree, or a tab on the Sandbox hub. |
viewers | A renderer for a file type: the host fetches the bytes, your component draws them. |
documents | A per-directory page in the Workspace tree: an icon on the rows your provider can explain, opened as a tab. |
commands | An entry in the command palette, optionally with a global keyboard shortcut. |
settings | Typed settings the host renders and stores for you, including secrets and env vars for the agent. |
files | Which workspace files back your view, so the daemon pushes you a refresh instead of you polling. |
The agent and daemon half
| Contributes | What it adds |
|---|---|
agent | A directory of skills, subagents, hooks and MCP servers, loaded into the agent every turn. |
bin | Executables prepended to the agent's PATH, which is how you ship it a command-line tool. |
capabilities | A whole third-party integration as data: a card, a config form, env vars, and a cheatsheet. |
processes | A long-lived background process the daemon supervises, optionally on a tunneled preview URL. |
listener | A realtime event source that can wake the agent, the way Discord and IMAP do. |
environment | Dockerfile layers baked into the sandbox image, so your tools are actually installed. |
The backend half
An extension can also ship a backend: a manifest server entry naming a bundle of real server code. It runs beside the daemon, serves the extension's own route namespace behind the daemon's ordinary auth, and reaches the daemon's routes only through a second declared allowlist. That is what lets an extension be a whole product, while its reach stays a list the owner read and approved.
One format, any subset
Everything above ships in one envelope. There is no separate skill-pack format, theme format, connector format or app format: there is one manifest, and every part of it is optional. What you fill in is what your extension is:
| You fill in | What you've made |
|---|---|
agent only | A skill pack. No code runs anywhere; the agent learns something. |
capabilities only | A connector: a card, a config form, env vars and a cheatsheet: data, not code. |
entry + views | A UI extension, like an editor plugin. |
processes + listener | A realtime gateway that wakes the agent, the way Discord and IMAP do. |
entry + server + agent + bin | A whole product: its screen, its backend, its skills, its CLI. |
One envelope because the things it groups succeed or fail together. A user installs a product: "deployments", "knowledge", "Reddit research", not a skill here and a view there. One install is one identity, one trust decision, one switch that unwires all of it, one row a marketplace can list. Split that across four package kinds and every one of those sentences needs four answers.
The grouping never taxes the small case: a manifest with one section filled in is complete, installs the same way, and shows the owner a one-line approval instead of a four-line one. And the parts that are portable stay portable. The agent contribution is a standard agent-plugin directory with skills, subagents, hooks and MCP servers that work in other harnesses unchanged, and a registry file is the same marketplace format those harnesses already read. The envelope wraps the standard; it doesn't fork it.
The one deliberate limit: an extension contributes cards and code, never privilege. The handlers that grant real power stay in the core: running the sandbox privileged, joining a private network, installing other extensions. A manifest naming one fails to parse. Shared surfaces stay core too, so the model picker, terminal and chat are things an extension asks the shell for, and every extension offers the same current list rather than five worse copies.
What bounds an extension
Extension code runs in somebody's browser and somebody's sandbox. Official discovery adds exact-sha security review, and the install makes declared host integrations legible before approval. These are layered controls, not runtime isolation, and they constrain what you design:
- Code that comes from elsewhere is pinned to a commit. Installing takes a full 40-character commit sha, not a branch. The code you approved is the code that runs, and an update is an explicit act, never something that happens under you. An extension you wrote in your own sandbox is pinned to nothing, for the same reason your own repositories are not: there is no upstream to change it behind your back.
- The manifest is the host-integration surface. The install dialog shows exactly the declared contributions. Afterwards the host enforces them: a view, command, viewer, setting or process the approved manifest never declared is refused at runtime. That enforcement applies to host APIs, not ordinary browser globals.
- Cooperative daemon calls are allowlisted. Each half declares the routes it calls through the extension API as
"GET /logs"-style entries, in two lists because they run as different principals. Undeclared API calls are refused; this is not a claim that arbitrary browser or process code is confined to that list. - Official listings pass two automated security checks. Trivy checks the exact source for known dependency flaws, committed secrets and dangerous deployment configuration. An intentic agent then reads the whole tree without running author code: browser egress, server/process/agent/bin/build surfaces, dependencies, and source against shipped artifacts. A finding or an uninspectable audit fails closed, and both run identities are recorded against the repository and commit.
The extension API injects transport auth, and secret settings round-trip as "still set" rather than their value. That reduces routine credential handling; it is not a secrecy boundary from browser code sharing the app realm, which is why the source is audited.
Verification & trust explains what those mechanics guarantee and what listed, verified and blocked mean. What they look like from the other side, at the moment somebody clicks install, is Install & manage extensions.
Everything first-party is an extension too
Most of the product you already use is extensions: over twenty-five. Activity, Automations, Memory, Knowledge, Deployments, Pipelines, the file viewers, the commit graph, the Discord, Slack, Telegram, WhatsApp and IMAP gateways, Google Workspace, the connector catalog. They ship through the same @intentic/extension-api a third-party bundle compiles against, and a lint rule fails the build if one of them reaches into app internals instead.
That's a deliberate constraint rather than a boast: it means the public API is the API the product is built on, so it can't quietly rot into the second-class path.
How extensions reach a sandbox
- Baked into the image: first-party ones are present in every sandbox and can be switched off, not removed.
- Git-installed: everything else, as a sha-pinned clone into
.intentic/local/extensions/, added from a repo URL or picked from a registry. - Written in the workspace: a directory under
.intentic/config/workspace-extensions/, run straight from where it sits. No clone, no install step, no publishing: see Build an extension.
All three land in one list on Sandbox → Extensions, with one on/off switch each. Switching one off unwires it everywhere: no view, no command, no PATH entry, no connector card, no env var, no background process.
The other thing you can ship
Everything above is an extension: code of yours that runs in somebody else's sandbox, free to install and free to run forever. There is a second artifact here, and it is not a smaller version of this one. A service is a single HTTPS endpoint that the platform forwards metered, signed calls to. No manifest, no bundle, no repo pointer, and none of your code on anybody's machine.
The line between them is who pays per run. An extension costs you nothing each time it is used, so it is free. A service is for when every run costs you real money, such as a paid data API, heavy compute or a licensed corpus, where giving it away per run would mean paying to be used. Almost everything should be an extension; if yours is the exception, Offer a paid service is the provider's whole side of it.
Related pages
- Build an extension: a working rail view, from empty directory to installed.
- Manifest reference: every contribution point and every field.
- Publish & registries: get yours listed, without an account or an upload.