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.
Quick start
Section titled “Quick start”# Scaffold from the templatenpx @yttri/plugin-cli create my-plugincd my-pluginnpm install
# Edit yttri-plugin.json and src/index.ts, then:npm run buildnpx yttri-plugin validatenpx yttri-plugin packThe 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.
Entry point
Section titled “Entry point”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. }, },});YttriHost
Section titled “YttriHost”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.
Assistant (SDK 1.1)
Section titled “Assistant (SDK 1.1)”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).
Lifecycle
Section titled “Lifecycle”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.
Agent tools
Section titled “Agent tools”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.
Background jobs
Section titled “Background jobs”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.
Debugging
Section titled “Debugging”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.