Agent Plugin Complete API reference.
The Agent plugin provides two access points with slightly different APIs:
ctx.agent (in component setup):
Available methods: register, unregister, execute, executeBatch, executeSequence, hasAction, describeAction, listActions, describe, dispatch, onCommand, getLog, clearLog, and optionally inspect, snapshot, diff
app.agent (full Agent instance):
All of the above (as internal methods), plus direct access to internal state for advanced use cases.
// In component setup - use ctx.agent
app.component("MyComponent", {
setup({ agent }) {
// agent.register, agent.execute, etc. are available
}
});
// Outside components - use app.agent or convenience methods
app.agentExecute("actionName", payload);
app.agentDispatch({ type: "COMMAND" });
Registers a callable action with an optional typed schema.
Note: If an action with the same name already exists, it is silently overwritten. The handler must be a function or an error is thrown.
// Signature
agent.register(name: string, handler: Function, schema?: AgentActionSchema): void
// Simple action
agent.register("greet", (payload) => `Hello, ${payload.name}!`);
// Action with schema
agent.register("calculate", (payload) => payload.a + payload.b, {
input: { a: "number", b: "number" },
output: "number",
errors: ["INVALID_INPUT"]
});
// Async action
agent.register("fetchUser", async (payload) => {
const response = await fetch(`/api/users/${payload.id}`);
return response.json();
}, {
input: { id: "string" },
output: "User"
});
Removes a registered action. Warns if the action does not exist.
// Signature
agent.unregister(name: string): void
// Example
agent.unregister("greet");
// Console warning if not found: [AgentPlugin] Action "greet" not found for unregister
Executes a registered action. Always returns a Promise regardless of whether the handler is sync or async.
Note: Permission is checked before execution. If the action is not found or permission is denied, the error is passed to
onError(if configured) and then thrown. Each error path firesonErrorexactly once.
// Signature
agent.execute(name: string, payload?: unknown, scope?: string): Promise<unknown>
// Simple execution
const greeting = await agent.execute("greet", { name: "World" });
// With scope for permission check
const result = await agent.execute("calculate", { a: 2, b: 3 }, "ui-agent");
// Error handling
try {
await agent.execute("nonExistent");
} catch (error) {
console.error(error.message); // [AgentPlugin] Action "nonExistent" not found
}
Checks if an action is registered.
// Signature
agent.hasAction(name: string): boolean
// Example
if (agent.hasAction("greet")) {
await agent.execute("greet", { name: "World" });
}
Returns the descriptor for a registered action, including its schema. Returns null if the action is not found.
// Signature
agent.describeAction(name: string): AgentActionDescriptor | null
// Example
const descriptor = agent.describeAction("calculate");
// { name: "calculate", schema: { input: { a: "number", b: "number" }, output: "number" } }
const missing = agent.describeAction("nonExistent");
// null
Returns an array of all registered action descriptors.
// Signature
agent.listActions(): AgentActionDescriptor[]
// Example
const actions = agent.listActions();
// [
// { name: "greet", schema: null },
// { name: "calculate", schema: { input: { a: "number", b: "number" }, output: "number" } }
// ]
Dispatches a structured command to all registered handlers for that command type. Handlers are called sequentially. Handler errors are caught and passed to onError without stopping subsequent handlers.
Note: The command must have a string
typeproperty. Permission is checked before dispatch. The command is logged in the audit log.
// Signature
agent.dispatch(command: AgentCommand, scope?: string): Promise<void>
// Basic dispatch
await agent.dispatch({ type: "UPDATE_UI", payload: { theme: "dark" } });
// With target and scope
await agent.dispatch(
{ type: "REFRESH", target: "Dashboard", payload: { force: true } },
"ui-agent"
);
Registers a handler for a command type. Returns an unsubscribe function. Multiple handlers can be registered for the same command type.
// Signature
agent.onCommand(type: string, handler: Function): () => void
// Register handler
const unsubscribe = agent.onCommand("UPDATE_UI", async (command) => {
console.log("Target:", command.target);
console.log("Payload:", command.payload);
});
// Later: stop handling
unsubscribe();
Returns audit log entries, optionally filtered by type, timestamp, or action name.
// Signature
agent.getLog(filter?: AgentLogFilter): AgentLogEntry[]
// Get all entries
const allLogs = agent.getLog();
// Filter by type
const actionLogs = agent.getLog({ type: "action" });
const commandLogs = agent.getLog({ type: "command" });
const eventLogs = agent.getLog({ type: "event" });
// Filter by timestamp
const recentLogs = agent.getLog({ since: Date.now() - 60000 }); // Last minute
// Filter by action name
const greetLogs = agent.getLog({ action: "greet" });
// Combined filters
const recentActions = agent.getLog({
type: "action",
since: Date.now() - 60000
});
Clears all audit log entries.
// Signature
agent.clearLog(): void
// Example
agent.clearLog();
console.log(agent.getLog().length); // 0
Note: When
enableInspectionisfalse, these methods are omitted entirely from thectx.agentAPI in components — they will beundefined. Onapp.agent(the internal instance),inspect()andsnapshot()still exist but log a warning and return empty results. The default isenableInspection: true.
Returns information about the component registry.
// Signature
agent.inspect(): object
// Example
const info = agent.inspect();
// {
// components: [
// { name: "Counter", hasSetup: true, hasTemplate: true, hasChildren: false, hasStyle: false },
// { name: "Dashboard", hasSetup: true, hasTemplate: true, hasChildren: true, hasStyle: true }
// ]
// }
Creates a serializable snapshot of the current application state, including registered components and publicly registered plugins.
Note: The plugin list uses
eleva.plugins(the public Map maintained by each plugin’s install/uninstall) as the sole authoritative source. Plugins that don’t register there won’t appear in the snapshot.
// Signature
agent.snapshot(): AgentSnapshot
// Example
const snap = agent.snapshot();
// {
// timestamp: 1707321600000,
// components: [
// { name: "Counter", hasSetup: true, hasChildren: false },
// { name: "Dashboard", hasSetup: true, hasChildren: true }
// ],
// plugins: ["attr", "agent"]
// }
Compares two snapshots and returns which components were added or removed between them.
// Signature
agent.diff(snapshotA: AgentSnapshot, snapshotB: AgentSnapshot): AgentDiffResult
// Example
const snap1 = agent.snapshot();
// ... register a new component ...
app.component("NewWidget", { template: () => `<div>New</div>` });
const snap2 = agent.snapshot();
const changes = agent.diff(snap1, snap2);
// { added: ["NewWidget"], removed: [] }
The plugin also adds shortcuts to the Eleva instance:
// These are equivalent:
app.agent.execute("action", payload, scope);
app.agentExecute("action", payload, scope);
app.agent.dispatch(command, scope);
app.agentDispatch(command, scope);
The Agent plugin emits events via eleva.emitter for cross-plugin observability. These events fire automatically during normal Agent operations and can be listened to by any component or plugin. All emitter events are failure-isolated — if a listener throws, the Agent operation completes normally.
| Event | Fired When | Payload |
|---|---|---|
agent:register |
After an action is registered | { name, hasSchema, timestamp } |
agent:unregister |
After an action is unregistered | { name, timestamp } |
agent:execute |
After a successful action execution | { name, payload, result, durationMs, timestamp } |
agent:execute:error |
After a failed action execution | { name, payload, error, durationMs, timestamp } |
agent:dispatch |
After a command is dispatched | { type, target, payload, errors?, durationMs, timestamp } |
// Listen for Agent events directly
app.emitter.on("agent:execute", (data) => {
console.log(`Action "${data.name}" completed in ${data.durationMs}ms`);
});
// Capture in another Agent instance's audit log
app.use(Agent, {
emitterEvents: ["agent:"]
});
The Agent plugin exposes two reactive signals on ctx.agent for template-driven monitoring.
A Signal<number> that updates whenever an action is registered or unregistered. Reflects the current number of registered actions.
app.component("ActionMonitor", {
setup({ agent }) {
agent.register("ping", () => "pong");
// agent.actionCount.value === 1
return { count: agent.actionCount };
},
template: (ctx) => `<p>Registered actions: ${ctx.count.value}</p>`
});
A Signal<AgentLogEntry|null> that updates on every audit log addition (actions, commands, and captured emitter events). Returns the most recent log entry or null if no activity has occurred.
app.component("ActivityFeed", {
setup({ agent }) {
return { last: agent.lastActivity };
},
template: (ctx) => `
<div>
${ctx.last.value
? `<p>Last: ${ctx.last.value.action} (${ctx.last.value.type})</p>`
: `<p>No activity yet</p>`}
</div>
`
});
Actions registered via ctx.agent.register() and command handlers registered via ctx.agent.onCommand() inside a component’s setup() are automatically cleaned up when the component unmounts. This prevents memory leaks and stale action references without requiring manual cleanup.
setup() are automatically unregistered on unmountonUnmount callbacks are preserved and called before cleanuponUnmount throws (via try/finally)ctx.agent.unregister() can only remove actions owned by that component; attempts to unregister actions owned by other components (or globally) are a no-op with a console.warnapp.component("Ephemeral", {
setup({ agent, onUnmount }) {
// These are auto-cleaned when the component unmounts
agent.register("tempAction", () => "temporary");
agent.onCommand("TEMP_CMD", (cmd) => { /* ... */ });
// Your own cleanup still runs first
onUnmount(() => console.log("Component unmounting"));
return {};
},
template: () => `<div>Temporary component</div>`
});
The Agent plugin provides an uninstall() method to completely remove it from an Eleva instance.
Removes the Agent plugin and restores the original Eleva behavior.
import Eleva from "eleva";
import { Agent } from "eleva/plugins";
const app = new Eleva("MyApp");
app.use(Agent, {
actions: { ping: () => "pong" }
});
// Use the agent...
await app.agentExecute("ping");
// Later, to completely remove the Agent plugin:
Agent.uninstall(app);
// After uninstall, these are removed:
// - app.agent (undefined)
// - app.agentExecute (undefined)
// - app.agentDispatch (undefined)
// - Original mount() method is restored
Agent.uninstall() Doesapp.mount — restored to originalapp._mountComponents — restored to originalapp.agentapp.agentExecuteapp.agentDispatch_destroyed flag (neutralizes orphaned emitter wrapper)emitter.emit if still owned by this wrapperapp.plugins.delete("agent")When using multiple plugins, uninstall in reverse order of installation:
// Installation order
app.use(Attr);
app.use(Store, { state: {} });
app.use(Agent, { actions: {} });
// Uninstall in reverse order (LIFO)
Agent.uninstall(app);
Store.uninstall(app);
Attr.uninstall(app);
Note: Agent’s
uninstall()is synchronous (not async), unlike Router’s which is async.
The Agent plugin includes TypeScript type definitions for full type safety.
import Eleva from "eleva";
import { Agent } from "eleva/plugins";
import type {
AgentOptions,
AgentActionSchema,
AgentCommand,
AgentLogEntry,
AgentLogFilter,
AgentSnapshot,
AgentDiffResult,
AgentActionDescriptor,
AgentApi
} from "eleva/plugins/agent";
const app = new Eleva("TypedApp");
// Type-safe options
const options: AgentOptions = {
maxLogSize: 200,
enableInspection: true,
onError: (error: Error, context: AgentErrorContext) => {
console.error(`[${context.method}] ${context.code}:`, error);
},
actions: {
greet: (payload: { name: string }) => `Hello, ${payload.name}!`
},
permissions: {
"ui-agent": { actions: ["greet"], commands: ["UPDATE_UI"] }
},
strictPermissions: false
};
app.use(Agent, options);
// Define action schema
const calculateSchema: AgentActionSchema = {
input: { a: "number", b: "number" },
output: "number",
errors: ["INVALID_INPUT", "OVERFLOW"]
};
// Register with schema
agent.register("calculate", (payload) => payload.a + payload.b, calculateSchema);
// Introspect the schema
const descriptor: AgentActionDescriptor | null = agent.describeAction("calculate");
if (descriptor) {
console.log(descriptor.name); // "calculate"
console.log(descriptor.schema); // { input: { a: "number", b: "number" }, ... }
}
interface MyComponentContext {
agent: AgentApi;
signal: <T>(value: T) => Signal<T>;
emitter: {
on: (event: string, handler: Function) => void;
emit: (event: string, data?: unknown) => void;
};
}
app.component("TypedComponent", {
setup({ agent, signal }: MyComponentContext) {
const result = signal<string>("");
const handleGreet = async () => {
const greeting = await agent.execute("greet", { name: "World" }) as string;
result.value = greeting;
};
return { result, handleGreet };
},
template: (ctx) => `
<div>
<p>${ctx.result.value}</p>
<button @click="handleGreet">Greet</button>
</div>
`
});
// Define a typed command
const command: AgentCommand = {
type: "UPDATE_UI",
target: "Dashboard",
payload: { theme: "dark", sidebar: false }
};
// Dispatch with type safety
await agent.dispatch(command, "ui-agent");
// Type-safe filter
const filter: AgentLogFilter = {
type: "action",
since: Date.now() - 3600000
};
const entries: AgentLogEntry[] = agent.getLog(filter);
entries.forEach((entry) => {
console.log(entry.type); // "action" | "command" | "event"
console.log(entry.action); // string
console.log(entry.payload); // unknown
console.log(entry.timestamp); // number
console.log(entry.source); // string
});
const snap1: AgentSnapshot = agent.snapshot();
// ... make changes ...
const snap2: AgentSnapshot = agent.snapshot();
const diff: AgentDiffResult = agent.diff(snap1, snap2);
console.log(diff.added); // string[]
console.log(diff.removed); // string[]
Problem: Error: [AgentPlugin] Action "actionName" not found
Solutions:
// 1. Make sure the action is registered
agent.register("myAction", (payload) => { /* ... */ });
// 2. Check action name spelling
agent.execute("myAction"); // Must match exactly
// 3. Verify the action hasn't been unregistered
if (agent.hasAction("myAction")) {
await agent.execute("myAction");
}
Problem: Error: [AgentPlugin] Permission denied: scope "x" cannot execute "y"
Solutions:
// 1. Add the action to the scope's allowed list
app.use(Agent, {
permissions: {
"my-scope": { actions: ["actionName"] }
}
});
// 2. If using strictPermissions, always provide a scope
await agent.execute("actionName", payload, "my-scope");
// 3. Without strictPermissions, omit scope for unrestricted access
await agent.execute("actionName", payload);
// 4. Check resolved permissions via public API
console.log(app.agent.describe("my-scope").permissions);
Problem: No entries in the audit log.
Solutions:
// 1. Actions and commands are logged automatically
await agent.execute("myAction", payload); // Creates log entry
console.log(agent.getLog()); // Should have entries
// 2. For emitter events, configure emitterEvents
// Both Router (router:*) and Store (store:*) emit events via eleva.emitter
app.use(Agent, {
emitterEvents: ["router:", "store:"] // Must specify prefixes
});
// 3. Check filter criteria
const all = agent.getLog(); // All entries
const filtered = agent.getLog({ type: "action" }); // Only actions
Problem: agent.inspect(), agent.snapshot(), or agent.diff() are undefined on ctx.agent.
When enableInspection is false, these methods are omitted entirely from the ctx.agent API injected into components. They won’t exist as properties, so calling them throws a TypeError.
Note: On
app.agent(the internal instance),inspect()andsnapshot()still exist but log a warning and return empty results when inspection is disabled.
Solutions:
// These require enableInspection: true (the default)
app.use(Agent, {
enableInspection: true // Explicitly enable
});
// Guard before calling in case inspection is disabled
if (agent.inspect) {
const info = agent.inspect();
}
Problem: [AgentPlugin] Already installed. Uninstall first to reconfigure.
Solution:
// Uninstall before reinstalling
Agent.uninstall(app);
app.use(Agent, newOptions);
Problem: Command handler is not being called.
Solutions:
// 1. Register handler BEFORE dispatching
const unsub = agent.onCommand("MY_CMD", handler);
await agent.dispatch({ type: "MY_CMD" }); // Handler called
// 2. Check command type spelling
agent.onCommand("MY_CMD", handler);
await agent.dispatch({ type: "MY_CMD" }); // Must match exactly
// 3. Verify handler wasn't unsubscribed
const unsub = agent.onCommand("MY_CMD", handler);
unsub(); // This removes the handler
await agent.dispatch({ type: "MY_CMD" }); // Handler NOT called
A compact, machine-oriented reference for AI models generating Eleva Agent code. Every method, its exact signature, what it returns, when it throws, and what side effects it produces.
| Method | Arguments | Returns | Throws | Side Effects |
|---|---|---|---|---|
register |
(name: string, handler: Function, schema?: AgentActionSchema) |
void |
AGENT_DESTROYED, AGENT_HANDLER_NOT_FUNCTION |
Overwrites existing action with same name silently |
unregister |
(name: string) |
void |
AGENT_DESTROYED |
console.warn if action not found |
execute |
(name: string, payload?: unknown, scope?: string) |
Promise<unknown> |
AGENT_DESTROYED, AGENT_PERMISSION_DENIED, AGENT_ACTION_NOT_FOUND, AGENT_SCHEMA_VIOLATION, AGENT_HANDLER_ERROR (or custom handler code) |
Permission check → schema validation (opt-in) → handler invocation → audit log with outcome |
executeBatch |
(actions: Array<{action, payload?}>, scope?: string) |
Promise<unknown[]> |
AGENT_DESTROYED, first error from any action |
Executes all actions in parallel via Promise.all |
executeSequence |
(actions: Array<{action, payload?}>, scope?: string) |
Promise<unknown> |
AGENT_DESTROYED, first error in sequence |
Executes actions sequentially, piping each result as next payload |
hasAction |
(name: string) |
boolean |
Never | None |
describeAction |
(name: string) |
AgentActionDescriptor \| null |
Never | None |
listActions |
() |
AgentActionDescriptor[] |
Never | None |
describe |
(scope?: string) |
AgentCapabilityManifest |
Never | None |
dispatch |
(command: AgentCommand, scope?: string) |
Promise<void> |
AGENT_DESTROYED, AGENT_COMMAND_INVALID_TYPE, AGENT_PERMISSION_DENIED |
Permission check → all handlers called sequentially → audit log with outcome |
onCommand |
(type: string, handler: Function) |
() => void (unsubscribe) |
AGENT_DESTROYED, AGENT_HANDLER_NOT_FUNCTION |
None |
getLog |
(filter?: AgentLogFilter) |
AgentLogEntry[] |
Never | None |
clearLog |
() |
void |
AGENT_DESTROYED |
None |
inspect |
() |
object |
Never | console.warn if inspection disabled; returns { components: [] } |
snapshot |
() |
AgentSnapshot |
Never | console.warn if inspection disabled; returns empty snapshot |
diff |
(snapA: AgentSnapshot, snapB: AgentSnapshot) |
AgentDiffResult |
Never | None |
Note on
ctx.agentvsapp.agent: WhenenableInspection: false, the methodsinspect,snapshot, anddiffare omitted entirely fromctx.agent(they areundefined). Onapp.agent,inspect()andsnapshot()still exist but warn and return empty results;diff()remains available and compares snapshots normally.
AgentActionSchema = { input?: Record<string, string>, output?: string, errors?: string[] }
AgentActionDescriptor = { name: string, schema: AgentActionSchema | null }
AgentCommand = { type: string, target?: string, payload?: unknown }
AgentLogEntry = { type: "action"|"command"|"event", action: string, payload: unknown, timestamp: number, source: string, result?: unknown, error?: string, durationMs?: number }
AgentLogFilter = { type?: "action"|"command"|"event", since?: number, action?: string, status?: "ok"|"error" }
AgentSnapshot = { timestamp: number, components: object[], plugins: string[] }
AgentDiffResult = { added: string[], removed: string[] }
AgentPermissionRule = { actions?: string[], commands?: string[] }
AgentErrorContext = { method: string, code: string, action?: string, scope?: string, commandType?: string }
AgentCapabilityManifest = { actions: Array<{name: string, schema: AgentActionSchema|null, allowed: boolean}>, commands: string[], permissions: {scope: string, actions: string[], commands: string[]}|null, config: {strictPermissions: boolean, maxLogSize: number, inspectionEnabled: boolean, validateSchemas: boolean} }
AgentOptions = { maxLogSize?: number, enableInspection?: boolean, onError?: (error: Error, context: AgentErrorContext) => void, actions?: Record<string, Function>, permissions?: Record<string, AgentPermissionRule>, emitterEvents?: string[], strictPermissions?: boolean, validateSchemas?: boolean }
execute(name, payload, scope): permission check → schema validation (if validateSchemas: true) → await handler(payload) → audit log entry with result/error/durationMs. If handler throws, onError(error, { method, code, action }) is called once, then the error is re-thrown. Log entry is written after handler completes (success or failure).
dispatch(command, scope): validate command.type is string → permission check → call all onCommand handlers sequentially → audit log entry with durationMs and any handler errors. If a handler throws, onError(error, { method, code, commandType }) is called, but remaining handlers still execute. Log entry is written after all handlers complete.
Failure isolation: Both
onErrorcallbacks and emitter event listeners are failure-isolated — if they throw, the Agent operation completes normally. This ensures that observability hooks never alter core control flow.
source Field| Entry Type | source value |
|---|---|
"action" |
scope parameter, or "global" if no scope |
"command" |
command.target, else scope, else "global" |
"event" |
Always "emitter" |
Every error and warning the Agent plugin can produce, with exact strings and trigger conditions.
All thrown errors have a machine-readable error.code property for programmatic error handling.
| # | Code | Method | Exact Message | Trigger |
|---|---|---|---|---|
| 1 | AGENT_DESTROYED |
register(), unregister(), execute(), executeBatch(), executeSequence(), dispatch(), onCommand(), clearLog() |
[AgentPlugin] Agent has been destroyed. Create a new instance via app.use(AgentPlugin). |
Any mutating method called on a destroyed agent instance (stale reference after uninstall) |
| 2 | AGENT_HANDLER_NOT_FUNCTION |
register() |
[AgentPlugin] Action handler must be a function |
handler argument is not a function |
| 3 | AGENT_PERMISSION_DENIED |
execute() |
[AgentPlugin] Permission denied: scope "${scope}" cannot execute "${name}" |
Scope provided but not allowed for this action |
| 4 | AGENT_ACTION_NOT_FOUND |
execute() |
[AgentPlugin] Action "${name}" not found |
Action name not in registry |
| 5 | AGENT_SCHEMA_VIOLATION |
execute() |
[AgentPlugin] Schema violation for "${name}": ${violations} |
Payload fails schema validation (when validateSchemas: true) |
| 6 | AGENT_COMMAND_INVALID_TYPE |
dispatch() |
[AgentPlugin] Command must have a string 'type' |
Command is falsy or command.type is not a string |
| 7 | AGENT_PERMISSION_DENIED |
dispatch() |
[AgentPlugin] Permission denied: scope "${scope}" cannot dispatch "${command.type}" |
Scope provided but not allowed for this command |
| 8 | AGENT_HANDLER_NOT_FUNCTION |
onCommand() |
[AgentPlugin] Command handler must be a function |
handler argument is not a function |
| 9 | AGENT_HANDLER_ERROR |
execute() (thrown), dispatch() (caught) |
(original handler error message) | Handler threw without a pre-existing error.code. In execute(), re-thrown. In dispatch(), caught — passed to onError and logged, but dispatch still resolves. |
onErrorcoverage: Not all errors are passed toonError. TheAGENT_DESTROYEDerrors,register()(invalid handler),dispatch()(invalid command type), andonCommand()(invalid handler) throw directly without callingonError. Onlyexecute()anddispatch()invokeonError(error, context)— specifically on permission-denied, action-not-found, schema-violation, and handler errors.contextis a structuredAgentErrorContextobject with{ method, code, action?, scope?, commandType? }. Handler errors that don’t already have acodereceiveAGENT_HANDLER_ERROR.
| # | Method | Exact Message | Trigger |
|---|---|---|---|
| 1 | install() |
[AgentPlugin] Already installed. Uninstall first to reconfigure. |
app.use(Agent, ...) called when Agent is already installed |
| 2 | unregister() |
[AgentPlugin] Action "${name}" not found for unregister |
Unregistering a name that doesn’t exist (global API) |
| 3 | unregister() (scoped) |
[AgentPlugin] Scoped unregister ignored: "${name}" is not owned by this component |
Scoped ctx.agent.unregister() called for an action not owned by the current component |
| 4 | inspect() / snapshot() |
[AgentPlugin] Inspection is disabled. Enable with { enableInspection: true } |
Calling these on app.agent when inspection is disabled |
Behavioral rules that affect correctness. Read these before generating Agent code.
_checkPermission(scope, kind, name):
│
├── strictPermissions: true
│ ├── No rules configured OR no scope provided → DENY
│ └── Rules exist AND scope provided → check specifics ↓
│
├── strictPermissions: false (default)
│ ├── No rules configured → ALLOW (zero-config path)
│ ├── Rules exist, no scope provided → ALLOW (trusted call)
│ └── Rules exist AND scope provided → check specifics ↓
│
└── Check specifics:
├── scope not in permissions object → DENY
├── kind ("actions"/"commands") not in scope rule → DENY
└── name in allowed list → ALLOW, otherwise → DENY
Implication: In default mode, passing an unrecognized scope is worse than passing no scope. No scope = unrestricted; unknown scope = denied.
Calling app.use(Agent, options) when the Agent is already installed does not reconfigure it. It warns via console.warn and returns immediately. To change options, call Agent.uninstall(app) first, then app.use(Agent, newOptions).
Calling agent.register(name, handler) with an already-registered name silently overwrites the previous handler and schema. No warning is emitted. The last register() call wins.
ctx.agentWhen enableInspection: false, the inspect, snapshot, and diff methods are not present on the ctx.agent object injected into components. They are undefined, not no-ops. Calling ctx.agent.inspect() will throw a TypeError.
agent.snapshot() lists plugins from eleva.plugins (the public Map populated by each plugin’s install()). Only plugins that register themselves in this Map appear. If a plugin does not call eleva.plugins.set(name, metadata), it will be absent from snapshots.
Log entries are written after handler completion (success or failure) and include:
result — the value returned by the handler (actions only, absent on error)error — the error message if the handler threw (absent on success)durationMs — wall-clock execution time in millisecondsFailed executions appear in the log with an error field. Use getLog({ status: "error" }) to filter failures.
When the log exceeds maxLogSize, the oldest entry is evicted (FIFO via Array.shift()).
Action handlers registered via register(name, handler) receive a single argument: the payload passed to execute(). There is no ctx, name, or scope argument injected into the handler.
// Correct
agent.register("greet", (payload) => `Hello, ${payload.name}!`);
// Wrong — handler does NOT receive (ctx, name)
agent.register("greet", (ctx, name) => `Hello, ${name}!`);
| Metric | Value |
|---|---|
| API Methods | 16 (register, execute, executeBatch, executeSequence, describe, dispatch, getLog, snapshot, etc.) |
| Log Entry Types | 3 (action, command, event) |
| Permission Modes | 2 (default permissive, strict) |
| State Inspection | Optional (enabled by default) |
| Async Support | Native (actions and commands can be async) |
| Method | Purpose |
|---|---|
agent.register(name, handler, schema?) |
Register an action |
agent.unregister(name) |
Remove an action |
agent.execute(name, payload?, scope?) |
Execute an action |
agent.executeBatch(actions, scope?) |
Execute multiple actions in parallel |
agent.executeSequence(actions, scope?) |
Execute actions sequentially (piped) |
agent.hasAction(name) |
Check if action exists |
agent.describeAction(name) |
Get action descriptor |
agent.listActions() |
List all actions |
agent.describe(scope?) |
Get full capability manifest |
agent.dispatch(command, scope?) |
Send a command |
agent.onCommand(type, handler) |
Handle commands |
agent.getLog(filter?) |
Query audit log |
agent.clearLog() |
Clear audit log |
agent.inspect() |
Inspect components |
agent.snapshot() |
Capture app state |
agent.diff(snapA, snapB) |
Compare snapshots |
agent:register, agent:unregister, agent:execute, agent:execute:error, agent:dispatch for observabilityagent.actionCount and agent.lastActivity for template-driven monitoringFor questions or issues, visit the GitHub repository.
For AI systems that need structured tool definitions (OpenAI function-calling, Anthropic tool-use, etc.), a machine-readable JSON manifest is available:
URL: https://elevajs.com/agent-manifest.json
The manifest includes all 16 methods with parameters, return types, error codes, and side effects — plus complete type definitions, the error catalog, and the permission decision logic. AI integrations can consume this file directly to auto-generate tool definitions.
| ← Back to Usage Patterns | Back to Agent Overview | Plugins → |