Skip to content

Building a plugin

A third-party plugin is TypeScript plus a declarative yttri-plugin.json manifest. Code talks to Yttri only through the typed YttriHost.

Terminal
# Scaffold from the template
npx @yttri/plugin-cli create my-plugin
cd my-plugin
npm install
# Edit yttri-plugin.json and src/index.ts, then:
npm run build
npx yttri-plugin validate
npx yttri-plugin pack

The build creates my-plugin-0.1.0.yttri-plugin. Install it through Settings → Plugins. For iterations, use the Developer folder: after npm run build, click Update — files are replaced while settings and state survive.

src/index.ts
import { definePlugin, type YttriHost } from '@yttri/plugin-api';
export default definePlugin({
async activate(host: YttriHost) {
host.log.info('activated');
},
tools: {
// The key matches capabilities.tools[].name in the manifest.
async acme_ping(host: YttriHost) {
const token = await host.secrets.get('api_token');
const res = await host.network.fetch({
url: 'https://api.acme.com/ping',
headers: token ? { authorization: `Bearer ${token}` } : {},
});
return { ok: res.ok, status: res.status };
},
},
jobs: {
async sync(host: YttriHost) {
// A background job declared in capabilities.jobs.
},
},
});
  • host.invoke({ capability, operation, payload }) — calls into data domains: notes, tasks, calendar, contacts, projects, and search (read and create), documents, mail, and meetings (read). Write operations require a write grant.
  • host.secrets.{get,set,delete,list} — secrets in the plugin’s isolated namespace; values are encrypted.
  • host.network.fetch(req) — HTTP only to hosts declared in the manifest.
  • host.settings.getAll() — plugin settings; Yttri generates the form from the schema.
  • host.accounts.* — the plugin’s connected-account registry: register, list, status, mark-synced, delete.
  • host.log.{info,warn,error} — a sanitized log.

A plugin can hand the situational layer a small normalized fact and read a compact status of related situations:

import { publishObservation, lookupSituations } from '@yttri/plugin-api';
await publishObservation(host, { kind: 'source_updated', payload: { … } });
const status = await lookupSituations(host, { … });

Publishing needs an agent/write grant, reading needs agent/read. The plugin gets no table access and cannot create or modify situations, plans, outcomes or scores — only contribute a fact and read a status. The observationKinds and outcomeCapabilities manifest fields require an sdkVersion that explicitly excludes SDK 1.0 (for example ^1.1.0).

  • activate(host) runs once when the plugin starts. Register module state and read settings here; network and domain writes are unavailable at this stage.
  • After that the plugin lives in its own process: module state survives between calls, and calling a tool again does not restart the plugin.
  • Disabling stops the process. In-memory state is lost — anything that must survive a restart belongs in Yttri data or in secrets.
  • A tool error is terminal for that call: Yttri does not retry it for you.

Tools from an enabled plugin join the AI agent’s registry as plugin_<name>. The function receives host first and the call arguments second, parsed against the parameters schema from the manifest.

By default, every call asks for user confirmation. A trusted plugin can be allowed to run without prompts through an explicit toggle on its page — this does not widen its rights; grants are still checked.

Mark each tool with an honest sideEffect: read_only, mutating or external. It shapes what the agent sees and how the call is gated.

capabilities.jobs declares jobs: manual (a button on the plugin page) and cron-scheduled. They run through Yttri’s unified queue with timeouts and retries, and need no open window — the plugin executes in a background process.

A job must not rely on state from a previous run: the process may be stopped in between.

  • host.log.{info,warn,error} — plugin logs appear on its page under Logs, with sensitive data stripped.
  • Install and enable errors state the reason: wrong entry, incompatible SDK version, package fails to load.
  • Edit files and press Refresh in the developer folder — reinstalling keeps settings and granted rights.