Authoring Plugins
Build local or published Agent Plugins v1 capabilities for Assembly Line.
Assembly Line uses Agent Plugins v1 as its one executable capability package boundary. A plugin may remain checked into one agent or be published without changing shape.
Use a plugin for one cohesive capability or provider role: its tools, external
connection, webhook contract, trusted hooks, model registration, channel, or
infrastructure implementation. Keep schedules and agent instructions in
agent.md, and keep unrelated utilities out of the plugin.
Channels and infrastructure remain explicit selections in agent.md. Their
provider packages use the provider contract described at the end of this page;
they are not hidden inside the agent's capabilities: list.
Minimal local plugin
Create a conforming checked-in plugin with:
assembly-line plugin init meal-memory ./agentThe resulting shape is:
agent/plugins/meal-memory/
├── plugin.json
└── ai.assemblyline/
└── index.tsEvery immediate child of root plugins/ is validated and activated
automatically. Do not repeat a local plugin ID in agent.md.
plugin.json is the standard Agent Plugins v1 envelope:
{
"$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
"name": "meal-memory",
"version": "0.1.0",
"description": "Durable meal capture and editing.",
"extensions": {
"ai.assemblyline": {
"entry": "./ai.assemblyline/index.ts"
}
}
}Portable plugin components use the standard fixed locations:
skills/<name>/SKILL.mdfor reusable Agent Skills.mcp.jsonfor portable MCP servers and their tool surfaces.ai.assemblyline/for typed Assembly Line-native capabilities.
Static extension entry
The compiler statically inspects the declared extension entry. It never imports
or executes the entry during discovery. Keep it declarative: export an object
literal, or pass one to defineAssemblyLinePlugin. Executable implementations
live in contained modules referenced by definition.
// plugins/meal-memory/ai.assemblyline/index.ts
import { defineAssemblyLinePlugin } from "@assemblyline-agents/core";
export default defineAssemblyLinePlugin({
tools: {
save_meal: {
definition: "./ai.assemblyline/save-meal.ts",
description: "Save one meal to durable memory.",
inputSchema: {
type: "object",
properties: {
meal: { type: "string" },
eatenAt: { type: "string" }
},
required: ["meal", "eatenAt"]
},
capability: {
visibility: "always",
execution: "direct",
tags: ["nutrition", "memory"]
}
}
}
});The implementation keeps the existing typed tool contract:
// plugins/meal-memory/ai.assemblyline/save-meal.ts
import { defineTool } from "@assemblyline-agents/core";
export default defineTool({
description: "Save one meal to durable memory.",
inputSchema: {
type: "object",
properties: {
meal: { type: "string" },
eatenAt: { type: "string" }
},
required: ["meal", "eatenAt"]
},
async execute(input, ctx) {
await ctx.step("save-meal", async () => {
// Perform the durable, idempotent implementation here.
});
return { saved: true };
}
});The plugin entry key, save_meal, is the model-facing tool name. Internal
module names and folders have no discovery semantics.
Native contribution types
The ai.assemblyline entry has one optional root configuration contract and a
closed, typed set of contribution sections:
| Root key | Owns |
|---|---|
configSchema | JSON Schema for agent-owned options under capabilities.<plugin>.config. |
tools | Native model-callable tools, schemas, approval, visibility, execution boundary, and environment requirements. |
connections | External identities, protocols, credentials, grants, events, and native/MCP/HTTP/SDK/CLI execution. |
hooks | After-persist event observers associated with the plugin capability. |
lifecycle | Trusted automation prepare and finalize implementations. |
composition | Bounded conditional setup logic that cannot fit the declarative composition DSL. |
contexts | Custom prompt/context policies. |
instrumentation | Custom observability setup. |
channels | A custom implementation for an explicitly declared channel. |
providers | Deploy, state, blob, sandbox, secrets, or channel-media provider declarations. |
models | Model-provider selection, static metadata, and runtime package binding. |
channelHooks | Named channel-specific behaviors, such as a first-contact attachment. |
Unknown sections and malformed descriptors fail compilation. Source paths must remain inside the plugin. The runtime packages only declared implementations; it does not scan or execute arbitrary plugin files.
Plugin-owned per-agent configuration
A reusable published capability plugin can expose one typed configuration object. The schema is statically inspected and locked with the plugin:
export default defineAssemblyLinePlugin({
configSchema: {
type: "object",
properties: {
dataset: { type: "string" },
resultLimit: { type: "integer", minimum: 1, maximum: 100 }
},
required: ["dataset"],
additionalProperties: false
},
tools: {
search_meals: {
definition: "./ai.assemblyline/search-meals.ts",
factory: "createSearchMealsTool",
description: "Search the configured meal dataset.",
inputSchema: { type: "object" },
optionalConfig: ["MEAL_MEMORY_URL"],
config: { resultLimit: 20 }
}
}
});The agent supplies only its deviation:
capabilities:
meal-memory:
config:
dataset: household
resultLimit: 50The compiler rejects the selection before code generation if it does not match
configSchema. Native tool and connection factories receive their
contribution defaults merged with the selected plugin config. Hook and
lifecycle descriptors may name a factory that receives the selected config;
composition handlers receive it as their argument. Static exports, Skills,
and portable MCP declarations remain static.
Checked-in local plugins are already agent-specific and activate by presence,
so keep their settings in their source declarations. config is for reusable
published capability selection. Authored tools may declare only non-secret
requiredConfig and optionalConfig; authenticated provider work belongs in
a reviewed connection with explicit credential metadata.
Connections
A connection contribution preserves Assembly Line's existing connection runtime. It does not have to become a remote MCP server.
export default {
connections: {
meals_api: {
definition: "./ai.assemblyline/meals-connection.ts",
provider: "meals",
protocol: "http",
transport: "http",
subject: "user",
required: true,
scopes: ["meals:read", "meals:write"],
requiredConfig: ["MEALS_API_URL"],
requiredCredentials: ["MEALS_API_TOKEN"],
optionalConfig: [],
optionalCredentials: [],
events: {
catalog: ["meal.created", "meal.updated"],
defaultEvents: ["meal.created"]
}
}
}
};The referenced module exports a validated connection definition or a named
factory. Protocols remain mcp, a2a, openapi, http, sdk, cli, or
credential; the existing runtime continues to own authorization, account
binding, credentials, tool discovery, approval policy, webhook verification,
event normalization, and sandbox materialization.
The plugin advertises events. agent.md decides which event starts a run:
automations:
update-plan:
trigger: meals_api.meal.updated
skill: update-nutrition-plan
delivery: silentNo authored automation means no implicit run.
Hooks and automation lifecycle
Hooks belong in a plugin when they are part of that plugin's named capability. They are not a general dumping ground for agent behavior.
export default {
hooks: {
audit: {
definition: "./ai.assemblyline/audit.ts",
eventTypes: ["run.completed"]
}
},
lifecycle: {
daily_context: {
definition: "./ai.assemblyline/daily-context.ts",
phases: ["prepare", "finalize"]
}
}
};Reference lifecycle code by its qualified name:
automations:
daily-review:
schedule: daily at 06:30
skill: daily-review
lifecycle: meal-memory.daily_contextGeneric durability, retries, approvals, scheduling, state transitions, and delivery remain framework responsibilities. A plugin hook should be removable with the capability it supports.
Published plugins
A published plugin keeps the same plugin.json and extension layout. Include
the runtime files in the package:
{
"name": "@acme/meal-memory-plugin",
"version": "1.2.0",
"type": "module",
"files": [
"dist",
"plugin.json",
"ai.assemblyline",
"skills",
"mcp.json"
],
"peerDependencies": {
"@assemblyline-agents/core": "^6.0.0"
}
}Select the installed package by the stable ID from its plugin.json:
capabilities: [meal-memory]Use object form for plugin-owned config and contribution-scoped typed exceptions:
capabilities:
meal-memory:
config:
dataset: household
tools:
disable: [delete_meal]
approval:
save_meal: alwaysassembly-line add @acme/meal-memory-plugin ./agent installs, selects, and
locks the package. Compile never downloads dependencies implicitly.
The plugin's configSchema owns accepted per-agent configuration. Environment
requirements own credentials and deployment bindings.
Locking and authority review
plugins.lock records exact package versions, integrity, portable components,
local content hashes, and the native authority inventory.
Lock format version 3 is generated from the compiler's canonical plugin graph;
the compiler does not maintain a reader for obsolete lock versions.
After editing a checked-in plugin, run:
assembly-line plugin lock ./agentThe command prints the capability and authority diff and records the developer's
explicit local source change. A published package upgrade that changes Skills,
MCP servers, tools, connections, hooks, or another executable contribution
fails closed until the diff is reviewed and the command is rerun with
--confirm-upgrade.
Ordinary runtime calls then follow the compiled grant and approval policy; a user is not asked to approve the plugin repeatedly.
Model providers
A model provider uses the same Agent Plugins v1 package boundary. Its static entry binds a provider prefix to a complete contract and runtime package:
module.exports = {
models: {
nebula: {
definition: "@acme/assembly-line-nebula",
provider: "nebula",
defaultModel: "nebula/standard",
contract: "./ai.assemblyline/models/nebula.json"
}
}
};The runtime module exports assemblyLineModelProvider with the matching
metadata and a create function returning a Pi provider. Optional hooks own
dynamic metadata discovery, payload transformation, normalized response
accounting, and semantic-memory embeddings. Authentication and deployment
requirements belong in the static model contract. Pi stays provider-neutral.
The compiler rejects disagreement between the locked static contract and the
runtime registration. Selecting model: nebula/standard requires no framework
registry edit; assembly-line add @acme/assembly-line-nebula agent --role model
installs, locks, and writes the plugin's declared default model.
Infrastructure and channel providers
Deploy, state, blob, sandbox, scheduler, secrets, and channels are explicit
agent.md fields. Channel-scoped audio processors are selected only through
channels.<name>.audio. A package that supplies an infrastructure or channel
implementation is an Agent Plugin whose runtime module exports
assemblyLineProvider:
import type { ProviderModule } from "@assemblyline-agents/core";
export const assemblyLineProvider: ProviderModule = {
providers: [{
metadata: {
kind: "acme-state",
role: "state",
packageName: "@acme/assembly-line-state",
stability: "supported",
requiredConfig: ["ACME_DATABASE_URL"],
optionalConfig: [],
requiredCredentials: [],
optionalCredentials: [],
capabilities: ["durable-state"]
},
create(ctx) {
return createAcmeState(ctx.env.ACME_DATABASE_URL!, ctx.options);
}
}]
};Provider metadata flows into preflight, deployment planning, artifact
dependencies, and runtime construction. An env entry in a deploy provider's
conditional deployment.requirements or deployment.warnings must declare
classification: "config" or classification: "credential". The compiler
uses the active conditional credential entries to scope the selected deploy
capability before reading any value; do not duplicate them in an unconditional
credential list. The same package declares its
authoring contract in the statically inspected Agent Plugin entry:
export default {
providers: {
state: {
definition: "@acme/assembly-line-state",
name: "acme-state",
role: "state",
kind: "acme-state",
contract: {
kind: "acme-state",
role: "state",
packageName: "@acme/assembly-line-state",
stability: "supported",
requiredConfig: ["ACME_DATABASE_URL"],
optionalConfig: [],
requiredCredentials: [],
optionalCredentials: [],
capabilities: ["durable-state"]
},
configSchema: {
type: "object",
additionalProperties: false,
properties: {
poolSize: { type: "integer", minimum: 1 }
}
}
}
}
};assemblyLineProvider owns runtime construction. The statically inspected
Agent Plugin contribution owns the compile-time contract: selection name,
role, kind, complete provider metadata, defaults, package binding, and option
schema. The contract must match the runtime registration; official
conformance tests reject drift. Compilation never imports executable provider
code to discover declarative authority.
The MD-first field grammar is closed, but the provider set is extensible.
Adding state: acme-state requires installing and locking this plugin; it does
not require a compiler registry edit. Agents cannot inject packageName,
configuration/credential declarations, capabilities, runtime, or a generic
options envelope.
Testing
At minimum, test that:
plugin.jsonvalidates against Agent Plugins v1.- The entry can be statically inspected without executing top-level code.
- Every declared definition is contained and ships in the package.
- Plugin configuration, tool and connection schemas, environment requirements, approval policy, events, and authority match the documented surface.
plugins.lockchanges when content or authority changes.- A fixture
agent.mdcompiles, packages, loads, and invokes each executable contribution through the real runtime path. - Credentials and account bindings never enter source, lockfiles, or model context.
The official package conformance and plugin runtime tests in this repository are the reference suite.