@agent-action-runner/express
v0.8.2
Published
Express adapter for agent-safe action and workflow runner for TypeScript backends.
Downloads
1,705
Maintainers
Readme
@agent-action-runner/express
Express adapter for Agent Action Runner.
Use this package to expose a core AgentActionRunner through HTTP endpoints in an Express application while keeping user identity, allowed modes, approval tokens, approval context, and metadata under server-side control.
The adapter exposes registered actions. It does not execute agent-generated code or discover arbitrary Express routes.
Experimental / pre-1.0.
Install
npm install @agent-action-runner/core @agent-action-runner/http @agent-action-runner/express express zodexpress and @agent-action-runner/core are peer dependencies. @agent-action-runner/http contains the shared HTTP contract used by the official HTTP adapters.
Quickstart
import express from 'express';
import { createRunner } from '@agent-action-runner/core';
import { createExpressAdapter } from '@agent-action-runner/express';
import { z } from 'zod';
const app = express();
const runner = createRunner();
runner.registerAction({
name: 'delivery.searchJobs',
mode: 'read',
description: 'Search delivery jobs by status.',
inputSchema: z.object({
status: z.array(z.string()),
}),
handler: async (input) => ({
jobIds: input.status.includes('FAILED') ? ['job_1'] : [],
}),
});
app.use('/agent-runner', createExpressAdapter(runner, {
getUserId: (req) => req.header('x-user-id') ?? 'anonymous',
}));
app.listen(3000);The adapter installs express.json() by default. In production, you can provide your own parser or disable adapter-level parsing when the host app already owns body parsing and size limits:
app.use(express.json({ limit: '256kb' }));
app.use('/agent-runner', createExpressAdapter(runner, {
getUserId: (req) => req.user.id,
jsonParser: false,
}));Endpoints
With the adapter mounted at /agent-runner, the routes are:
| Method | Path | Description |
|---|---|---|
| GET | /agent-runner/actions | Lists registered action metadata. |
| POST | /agent-runner/actions/:name/execute | Executes one action. |
| POST | /agent-runner/workflows/execute | Executes a JSON workflow. |
GET /actions returns action metadata only. It does not serialize schemas.
{
"ok": true,
"actions": [
{
"name": "delivery.searchJobs",
"mode": "read",
"description": "Search delivery jobs by status.",
"approvalRequired": false,
"tags": ["delivery", "operations"],
"resourceType": "deliveryJob",
"riskLevel": "low"
}
]
}Execute An Action
curl -s http://localhost:3000/agent-runner/actions/delivery.searchJobs/execute \
-H "content-type: application/json" \
-H "x-user-id: operator_1" \
-d '{"input":{"status":["FAILED"]}}'Successful responses use this shape:
{
"ok": true,
"result": {
"executionId": "exec_1",
"actionName": "delivery.searchJobs",
"mode": "read",
"output": {
"jobIds": ["job_1"]
}
}
}Execute A Workflow
curl -s http://localhost:3000/agent-runner/workflows/execute \
-H "content-type: application/json" \
-H "x-user-id: operator_1" \
-d '{
"workflow": {
"workflowName": "search-failed-jobs",
"steps": [
{
"id": "jobs",
"action": "delivery.searchJobs",
"input": {
"status": ["FAILED"]
}
}
]
}
}'Server-Side Security Boundary
The adapter requires getUserId(req). Do not take user identity from the request body.
createExpressAdapter(runner, {
getUserId: (req) => req.user.id,
getAllowedModes: (req) => req.user.isOperator
? ['read', 'draft', 'dryRun', 'mutate']
: ['read', 'draft', 'dryRun'],
getApprovalToken: (req) => req.header('x-approval-token'),
getApprovalContext: (req) => ({
resourceIds: req.header('x-resource-ids')?.split(','),
dryRunHash: req.header('x-dry-run-hash'),
}),
getMetadata: (req) => ({
requestId: req.header('x-request-id'),
ip: req.ip,
}),
});By default, the adapter ignores these request body fields:
allowedModesapprovalTokenapprovalContextidempotencyKeymetadata
Set allowClientExecutionOptions: true only for trusted internal tooling.
Mutate Actions
mutate actions remain blocked unless the server allows mutate mode and the core approval hook approves the request.
app.use('/agent-runner', createExpressAdapter(runner, {
getUserId: (req) => req.user.id,
getAllowedModes: (req) => req.user.canMutate
? ['read', 'draft', 'dryRun', 'mutate']
: ['read', 'draft', 'dryRun'],
getApprovalToken: (req) => req.header('x-approval-token'),
}));The Express adapter does not implement authentication, sessions, approval signing, or persistent audit storage. Those stay in your application.
Error Responses
Errors use a stable JSON shape:
{
"ok": false,
"error": {
"code": "APPROVAL_REQUIRED",
"message": "Action \"admin.disableUser\" requires approval."
}
}Common status mappings:
| Error | Status |
|---|---|
| action not found | 404 |
| schema validation failed | 400 |
| action timeout | 408 |
| invalid step reference | 400 |
| mode not allowed | 403 |
| approval required | 403 |
| policy rejected | 403 |
Example
See examples/express-admin-ops for a runnable Express app with:
admin.searchUsersadmin.dryRunDisableUseradmin.disableUser- HMAC-bound approval tokens
- in-memory audit trail
License
Apache-2.0
