Plugin installation
Source:
harness/docs/plugins/install.md
Status: Current
Plugin installation
Section titled “Plugin installation”Floor: Agent Plugins 1.0 package shape
Delivery: npm install --save --ignore-scripts into the kernel folder
Identity: plugin.json name only
Enablement: {KERNEL}/config.json plugins.<plugin.json name>.enabled
Surfaces after install: plugins/tldr.md.
1. Package contract (npm + Agent Plugins)
Section titled “1. Package contract (npm + Agent Plugins)”| Rule | |
|---|---|
| Delivery | npm package (npm install --save --ignore-scripts into the kernel folder) |
plugin.json |
MUST live at the package root (same root as package.json for npm). No nested .plugin/plugin.json for our client. |
| Portable layout | Unchanged: skills/, mcp.json at that root |
| Our extension | extensions["tools.inhuman"]: plugins[], commands, bots, skills, states, mcp. drivers[] is still parsed and projected onto DriverSpec. The exemplar packs do not declare one. interceptingHooks and notifyingHooks are dropped at parse. |
| Identity | plugin.json name only — enable/disable, list, uninstall target. package.json name is never the plugin id. |
Invalid if plugin.json is missing at package root after install → not a plugin (a library).
Naming display (misalignment)
Section titled “Naming display (misalignment)”npm package name is provenance only (where the bits came from). When it differs from the manifest name, UI/CLI must say so explicitly:
installed plugin `plugin-fff` from `@inhuman.tools/plugin-fff`enabled plugin `plugin-fff` (from `@acme/legacy-fff-pkg`)uninstall plugin-fff # always plugin.json nameNever list or address a plugin by npm name alone. If two installs would share the same plugin.json name, reject the second install (collision on plugin id).
2. Where plugins live
Section titled “2. Where plugins live”The kernel folder (--kernel, default ~/.harness) is a quasi-package. It holds
package.json (consent), config.json (connections + plugin enablement),
kv.json (session KV bag), and secrets.json (connection secrets — never a sync
field). KernelReflector projects config.json + kv.json onto the KernelRoot CRDT.
| Consent | Install-root package.json dependencies — npm materializes the trees |
| Enablement | {KERNEL}/config.json plugins.<plugin.json name>.enabled — omit = enabled; persist only disables |
| Load | The pack loader does not import plugins[]. Host builtins construct InhumanPlugin and compile it with applyPackPlugins. commands → file-driven Scheme at importPath (Client Arrival). bots, states, and mcp are parsed by the behavior resolver and are not dispatched by the session loop. |
| Drivers | drivers[] is still accepted. A row is copied onto DriverSpec when importPath names that plugin’s own package. New behavior does not add a driver. Exterior = connections on builtins (/connect, MCP). ../runtime/drivers.md |
| List | File reflection (package.json + config.json). Not the write target. |
Same human / same machine / same kernel identity → same plugin set. Project trees do not fork “which plugins I have.”
Filesystem: package contents live under the kernel’s node_modules (path installs stay
symlinks). That path is referenced from package.json + config.json.
Kernel vs session document roles: ../canon/grain.md.
3. Install / uninstall
Section titled “3. Install / uninstall”From the kernel folder:
npm install --save --ignore-scripts @inhuman.tools/plugin-fff@0.0.1npm install --save --ignore-scripts ./plugins/inhuman-plugin-fffnpm uninstall --save @inhuman.tools/plugin-fff| Step | Behavior |
|---|---|
| install | npm install --save --ignore-scripts into the kernel folder → kernel package.json gains the dependency. File reflection lists it. |
| uninstall | npm uninstall --save of the manifest dependency. Consent gone; enablement key may linger unused. |
Local path install is still npm against the kernel package.json (symlink, live source).
Scope here means which packages are consented on this kernel (add/remove
package.json dependencies), not per-repo enable flags.
4. Enable / disable
Section titled “4. Enable / disable”Enablement is {KERNEL}/config.json. After install the plugin is enabled by
default (no plugins key). Disable writes { "enabled": false }; enable
deletes the key.
{ "plugins": { "plugin-fff": { "enabled": false } }}| Rule | |
|---|---|
| After install | enabled by default (no config.json key) |
| enable/disable | Writes {KERNEL}/config.json plugins — enable deletes the key; disable writes { "enabled": false } |
| Not | A live-document write for enablement (file reflection of config.json) |
| Not | Per-project toggle |
| Disabled | Not loaded into the catalog or as skills; remains consented |
5. Load flow (after install)
Section titled “5. Load flow (after install)”kernel package.json dependencies (consent) → plugin graph (reachable plugin.json) → config.json plugins: (omit = enabled) → skills/, mcp.json (Agent Plugins portable paths) → host builtins construct InhumanPlugin and applyPackPlugins tools, actions, mcpServers, loopEnd → extensions.tools.inhuman.plugins[] is parsed and not imported → extensions.tools.inhuman.commands → file-driven Scheme (Client) → bots / states / mcp → behavior resolver (parsed, not dispatched)interceptingHooks and notifyingHooks are dropped at parse. skills/ is the portable layout; the daemon does not load skills/.
Manifest-unreachable node_modules trees are not plugins.
A consented plugin with a missing tree: broken install (npm install in the kernel).
Installed means trusted. A consented, enabled plugin’s command importPath is loadable. plugins[] is not imported. There is no second JSON inventory of tools, hooks, or actions, and no HostImportWhitelist.
Host builtins produce tools and actions through InhumanPlugin. Behavior programs are guard/* and hook/* in the bot file. An Arrival behavior capability runs them when a test or caller loads them: the forms are macros, and toJS crosses the lambdas. Callback ambient: lifecycle.md.
6. Explicit non-goals (this cut)
Section titled “6. Explicit non-goals (this cut)”| Project-local plugin enablement | No |
pip install as first-class |
No (npm only) |
| Auto-trust MCP servers on install | No — /connect and mcp-server/adopt adopt user-visible servers. Enabling a trusted plugin activates its declared private bundled server bindings only: bound bots may call the intersection of binding names and tools/list. It does not register that server in the user’s general MCP catalog, grant tools to unbound agents, or auto-trust public/external MCP. Private means unavailable to unbound model callers, not invisible to the operator |
Running package postinstall |
No — --ignore-scripts |
Identity from package.json name |
No — plugin.json name only |
7. Relation to Agent Plugins
Section titled “7. Relation to Agent Plugins”| Spec | We do |
|---|---|
Package = directory + root plugin.json |
Yes |
| Install source undefined | We fix: npm (+ local path) into the kernel folder |
| Client manages install UX | npm for consent; config.json for enable/disable |
| Skills/MCP fixed paths | Yes |
| Client extensions | tools.inhuman: plugins[], commands, bots, skills, states, mcp. Hook rows are dropped. drivers[] is still projected |
Pointer sketches: plugins/reference/plugin.json.