API Reference

This page documents the public SDK surface exported by @ageniti/core.

Top-Level Import

For most applications, start with the root export:

import {
  createAgenitiApp,
  createRuntime,
  defineAction,
  s,
} from "@ageniti/core";

Use subpath exports only when you want a narrower import boundary. See Entry Points.

Since 0.2.0: build/package/publish, docs, project, lint, and manifest helpers documented below are imported from dedicated subpaths, not the root entry. Each section below shows the exact subpath. The app.build(), app.package(), app.publish(), app.createGuideDoc(), app.exportDocs() methods on the result of createAgenitiApp() are unchanged.

defineAction(config)

Defines a typed action contract.

Required fields:

  • name: lowercase snake_case action name
  • description: human-readable action description
  • run(input, context): action implementation

Optional fields:

  • version
  • title
  • input
  • output
  • visibility
  • sideEffects
  • idempotency
  • permissions
  • supportedSurfaces
  • timeoutMs
  • retry
  • concurrency
  • requiresConfirmation
  • metadata
  • publicMetadata
  • docs
  • deprecated
  • deprecation

Metadata model:

  • metadata: internal action metadata for app code and build-time tooling; it is not copied into public manifests or tool metadata
  • publicMetadata: safe-to-expose metadata copied into public manifests, MCP tool metadata, and LLM tool adapters

Docs model:

  • docs: natural-language guidance used to generate a unified GUIDE.md
  • app-level docs supports summary, audience, whenToUse, quickStart, setup, operationalNotes, sections, and examples
  • action-level docs supports whenToUse, whenNotToUse, usageNotes, inputExample, and outputExample
  • GUIDE.md is deterministic output; it does not call a model or infer behaviour from UI code

Versioning model:

  • version: action contract version, default 1.0.0
  • deprecated: marks an action as deprecated without removing it
  • deprecation: optional message, replacement, and timeline metadata

Important defaults:

  • title: derived from name
  • input: s.object({})
  • visibility: "public"
  • sideEffects: "read"
  • idempotency: "unspecified"
  • permissions: []
  • supportedSurfaces: cli, json, http, mcp, react, dev, ai-sdk
  • retry: normalised to { retries, delayMs }
  • requiresConfirmation: true for destructive actions unless overridden

Example:

const deleteTask = defineAction({
  name: "delete_task",
  description: "Delete a task by id.",
  sideEffects: "destructive",
  requiresConfirmation: true,
  input: s.object({
    taskId: s.string().min(1),
  }),
  async run(input, ctx) {
    return ctx.services.tasks.remove(input.taskId);
  },
});

createAgenitiApp(options)

Creates an app object that owns actions, runtime, and surface helpers.

const app = createAgenitiApp({
  name: "task-app",
  description: "Workspace task operations for external tools, automation, and agent callers.",
  attribution: {
    text: "Powered by Ageniti",
    vendor: "Ageniti",
    product: "Ageniti Core",
    url: "https://ageniti.dev",
    docsUrl: "https://ageniti.dev/docs",
  },
  docs: {
    summary: "Use this app to create tasks and inspect status.",
  },
  actions,
  services,
  permissionChecker,
  middleware,
  adapters,
  build,
});

Options:

  • name: required app name
  • attribution: optional source attribution metadata for CLI help, MCP manifests, tool metadata, generated guides, and bundle artifacts
  • actions: array of Ageniti actions
  • services: shared services injected into context.services
  • permissionChecker: authorisation hook
  • middleware: runtime middleware chain
  • adapters: custom surface adapter list for manifest generation
  • build: default build settings reused by app.build() and CLI build commands

attribution is descriptive metadata, not telemetry. Use it when you want generated surfaces to show product origin or branding information in a structured way.

Returned members:

  • name
  • actions
  • adapters
  • runtime
  • manifest(options?)
  • actionManifest()
  • lint()
  • build()
  • package()
  • publish()
  • createGuideDoc()
  • exportDocs()
  • createCli()
  • createJsonRunner()
  • createHttpHandler()
  • createHttpServer()
  • createMcpHandler()
  • createMcpManifest()
  • createOpenAITools()
  • createOpenAIResponsesTools()
  • createAISDKTools()
  • createFunctionCallingManifest()
  • createReactAdapter()
  • createDevServer()

Use createAgenitiApp() when you want one app definition and convenient access to every supported surface.

createRuntime(options)

Creates the headless runtime used by all app capability surfaces.

Options:

  • actions
  • services
  • permissionChecker
  • middleware
  • hooks
  • redact
  • idempotencyCache
  • idempotencyTtlMs
  • idempotencyMaxEntries

Returned API:

  • registry: Map<string, Action>
  • listActions({ surface? })
  • invoke(actionOrName, input?, invokeOptions?)
  • stream(actionOrName, input?, invokeOptions?)

invokeOptions supports:

  • invocationId
  • surface
  • user
  • auth
  • env
  • services
  • metadata
  • signal
  • timeoutMs
  • retry
  • confirm
  • idempotencyKey

The runtime is responsible for:

  • action lookup
  • supported surface checks
  • input validation
  • confirmation checks
  • permission checks
  • middleware execution
  • timeout and retry
  • output serialisation and output validation
  • success and failure envelopes

Read Runtime Semantics for the full execution model.

Manifest And Registry Helpers

createActionRegistry(actions)

Builds a Map keyed by action name and throws on duplicates.

createActionManifest(actions)

Returns a plain array describing the public action contract, including schemas, visibility, confirmation, and publicMetadata. This helper is useful for inspection, tooling, tests, or custom wrappers.

describeAction(action)

import { describeAction } from "@ageniti/core/manifest";

Normalises a single action into a manifest-friendly object.

createSurfaceManifest({ appName, actions, adapters })

import { createSurfaceManifest } from "@ageniti/core/manifest";

Returns a manifest object with:

  • app name
  • generation timestamp
  • action descriptions
  • surface descriptions and capabilities

CLI API

createCli(options)

Creates a CLI object.

const cli = createCli({
  name: "task-app",
  attribution: {
    text: "Powered by Ageniti",
  },
  actions,
  runtime,
  runtimeOptions,
  env,
  adapters,
  buildOptions,
});

Returned members:

  • name
  • actions
  • runtime
  • run(argv?, io?)
  • main(argv?, io?)

Built-in commands:

<app> <action> [options]
<app> <action> --json '{"field":"value"}'
<app> <action> --schema
<app> actions
<app> manifest
<app> diff --previous old.json --next new.json
<app> build [manifest|cli|mcp|docs|bundle] [options]
<app> docs [options]
<app> package [options]
<app> publish [options]
<app> init <react|expo|next|host-openai|host-ai-sdk|host-mcp|host-http> [options]
<app> doctor [options]
<app> lint
<app> mcp
<app> mcp --stdio
<app> dev --port 4321

Input behaviour:

  • snake_case actions can also be called as kebab-case commands
  • boolean flags accept --flag, --flag true, or --no-flag
  • array, object, and any inputs are parsed from JSON strings
  • runtime confirmation maps to --confirm

Build command options:

  • --out-dir <dir>
  • --app-module <module-path>
  • --app-export <name>
  • --package-json
  • --cwd <dir>
  • --filename <name> for docs

Launcher targets need a Node-safe app module so the generated files know what to import. If --app-module is omitted, Ageniti tries to discover a default entry such as ./src/ageniti/app.js.

JSON Runner API

createJsonRunner(options)

Creates a small structured invocation wrapper.

const runner = createJsonRunner({ actions, runtime, runtimeOptions });

Returned API:

  • runtime
  • invoke(payload)

Payload fields:

  • action
  • input
  • confirm
  • user
  • auth
  • metadata

If the payload is not an object, the runner returns INVALID_JSON_RUNNER_PAYLOAD.

MCP API

createMcpManifest(actions, options)

Returns:

{
  tools: [
    {
      name,
      title,
      description,
      inputSchema,
      metadata,
    },
  ],
}

Filtering rules:

  • action must support the mcp surface
  • private actions are excluded unless includePrivate: true
  • local actions are excluded unless includeLocal: true
  • destructive actions are excluded unless includeDestructive: true

createMcpHandler(options)

Creates a JSON-RPC handler for tools/list and tools/call.

const handle = createMcpHandler({
  actions,
  runtime,
  runtimeOptions,
  includePrivate,
  includeDestructive,
});

tools/call returns both a text block and structuredContent containing the runtime result.

When a destructive tool is intentionally exposed with includeDestructive: true, callers still need params.confirm: true for execution.

createMcpStdioServer(options)

Creates a stdio transport around createMcpHandler() with automatic newline and Content-Length framing support.

await createMcpStdioServer({ actions, runtime }).start();

Use this when another process expects JSON-RPC over stdio. Ageniti auto-detects newline-delimited and Content-Length framed requests.

LLM Tool Adapters

createOpenAITools(actions, options)

Returns Chat Completions-style function tools.

createOpenAIResponsesTools(actions, options)

Returns Responses-style function tools.

createAISDKTools(actions, options)

Returns a Vercel AI SDK-style tools object.

const tools = createAISDKTools(actions, {
  runtime,
  returnEnvelope: true,
});

Each tool contains:

  • description
  • parameters: Ageniti schema object
  • inputSchema: JSON Schema representation
  • execute(input, options?)

createFunctionCallingManifest(actions, options)

Returns a combined summary:

  • openaiChatTools
  • openaiResponsesTools
  • aiSdkTools as action names

Shared adapter options:

  • runtime
  • strict
  • includePrivate
  • includeLocal
  • includeDestructive
  • surface
  • returnEnvelope
  • filter

Filtering rules:

  • action must support the selected surface, default ai-sdk
  • private actions are excluded unless includePrivate: true
  • local actions are excluded unless includeLocal: true
  • destructive actions are excluded unless includeDestructive: true
  • filter(action) can apply additional filtering

React API

createReactActionAdapter(options)

Creates a React-friendly wrapper over the shared runtime.

const { runtime, useAction } = createReactActionAdapter({ actions, runtime });

useAction(action) returns an async function that invokes the action with surface: "react".

Dev Server API

createDevServer(options)

Creates the local dev console server.

const devServer = createDevServer({
  name: "task-app",
  actions,
  runtime,
});
 
const listener = await devServer.listen(4321, "127.0.0.1");

Routes:

  • GET /: HTML dev console
  • GET /api/actions: action manifest for the dev surface
  • POST /api/actions/:name/invoke: invoke one action

HTTP API

app.createHttpHandler(options)

Creates a framework-friendly HTTP JSON handler. Request body auth / user fields are ignored by default; inject trusted identity on the request object or via resolveContext.

const handle = app.createHttpHandler();
 
const response = await handle({
  method: "POST",
  path: "/ageniti/actions/create_task/invoke",
  body: {
    input: {
      title: "Follow up with design review",
      priority: "high",
    },
  },
});

Routes:

  • GET /ageniti/actions
  • POST /ageniti/actions/:name/invoke

app.createHttpServer(options)

Creates a small Node HTTP server around the same handler.

const server = app.createHttpServer();
const listener = await server.listen(4322);

HTTP actions must support the http surface. Private, local, and destructive actions are filtered out by default.

Server-only options:

  • maxBodyBytes
  • requireJsonContentType

Build API

import { buildArtifacts, packageArtifacts, publishArtifacts } from "@ageniti/core/build";
import { createGuideDoc, exportDocs } from "@ageniti/core/docs";

The app.build(), app.package(), app.publish(), app.createGuideDoc(), and app.exportDocs() methods on the result of createAgenitiApp() continue to work unchanged.

app.build(options)

Builds official Ageniti distribution artifacts from an app object.

await app.build({
  targets: ["bundle"],
  appModule: "./src/ageniti/app.js",
  appExport: "app",
  outDir: "./dist/ageniti",
});

Common options:

  • targets: manifest, cli, mcp, docs, or bundle
  • outDir: output directory, default dist/ageniti
  • appModule: headless Node-safe module that exports your app
  • appExport: export name, default app
  • includePackageJson: force a generated package.json
  • cwd: working directory used for path resolution
  • filename: custom file name for docs, default GUIDE.md

bundle expands to:

  • ageniti.manifest.json
  • ageniti.actions.json
  • cli.mjs
  • mcp-stdio.mjs
  • ageniti.mcp.json
  • GUIDE.md
  • package.json
  • README.md
  • ageniti.bundle.json

The generated README.md is part of the app distribution. It explains how to run the CLI locally, publish the generated package, install the resulting bin, connect MCP clients, and deploy HTTP through your own backend.

app.createGuideDoc(options)

Returns one Markdown guide string.

const markdown = app.createGuideDoc();

It reads the app description / docs, plus each action's description, permissions, side effects, surfaces, publicMetadata, and docs.

app.exportDocs(options)

Writes a unified guide document to disk.

await app.exportDocs({
  outDir: "./dist/ageniti",
});

Default output:

  • GUIDE.md

The same capability is available through the CLI:

task-app docs
task-app docs --out-dir ./dist/ageniti
task-app build docs --out-dir ./dist/ageniti

app.package(options)

Builds a bundle and then runs npm pack in the generated output directory.

const result = await app.package({
  appModule: "./src/ageniti/app.js",
  outDir: "./dist/ageniti",
});

The result includes:

  • outDir
  • packageDir
  • packageFile
  • build

app.publish(options)

Builds a bundle, runs npm pack, and then runs npm publish.

const result = await app.publish({
  appModule: "./src/ageniti/app.js",
  outDir: "./dist/ageniti",
  dryRun: true,
  access: "public",
});

Important defaults:

  • dryRun defaults to true
  • pass dryRun: false only for a real publish

Deployment shapes:

  • CLI package: publish or distribute the generated npm package and expose its bin.
  • MCP server: run mcp-stdio.mjs locally or the generated <bin-name>-mcp command after install.
  • HTTP gateway: mount app.createHttpHandler() in your own backend; Ageniti does not require a hosted runtime.

The returned result includes:

  • ok
  • outDir
  • packageDir
  • packageFile
  • published
  • stdout
  • stderr
  • build

buildArtifacts(options)

Lower-level build helper used by app.build().

packageArtifacts(options)

Lower-level packaging helper used by app.package().

const result = await packageArtifacts({
  appName: "task-app",
  actions,
  adapters,
  appModule: "./src/ageniti/app.js",
  appExport: "app",
});

The result includes:

  • ok
  • outDir
  • packageDir
  • packageFile
  • build

publishArtifacts(options)

Lower-level publish helper used by app.publish().

const result = await publishArtifacts({
  appName: "task-app",
  actions,
  adapters,
  appModule: "./src/ageniti/app.js",
  appExport: "app",
  dryRun: true,
});

The result includes:

  • ok
  • outDir
  • packageDir
  • packageFile
  • published
  • stdout
  • stderr
  • build

Manifest Diff

import { diffActionManifests } from "@ageniti/core/manifest";

diffActionManifests(previous, next)

Compares two action manifests or surface manifests and reports breaking changes, warnings, and informational changes.

const diff = diffActionManifests(previousManifest, nextManifest);

Breaking changes include removed actions and input/output schema changes.

const result = await buildArtifacts({
  appName: "task-app",
  actions,
  adapters,
  targets: ["bundle"],
  appModule: "./src/ageniti/app.js",
  appExport: "app",
});

Use this helper when you want to build launchers or manifests without first constructing an app object.

Project Tools

import {
  initProject,
  doctorProject,
  loadProjectConfig,
  findDefaultAppModule,
  detectTypeScriptRuntime,
  supportsTypeScriptEntrypoints,
} from "@ageniti/core/project";

initProject(options)

Scaffolds a headless Ageniti entry for app projects, including react, expo, next, and host starter templates such as host-openai, host-ai-sdk, host-mcp, and host-http.

const result = await initProject({
  cwd: process.cwd(),
  template: "react",
});

The result includes:

  • ok
  • template
  • cwd
  • files
  • appModule
  • nextSteps

doctorProject(options)

Inspects a project for framework type, default Ageniti entry discovery, and common launcher or build issues.

const result = await doctorProject({ cwd: process.cwd() });

The result includes:

  • ok
  • kind
  • cwd
  • defaultAppModule
  • checks
  • recommendations

findDefaultAppModule(options)

Finds the default Node-safe Ageniti app entry used by zero-config build flows.

const result = await findDefaultAppModule({ cwd: process.cwd() });

Possible outcomes include:

  • found: true with modulePath and reason node-safe-default
  • found: true with modulePath and reason configured
  • found: false with reason typescript-only-entry
  • found: false with reason missing

loadProjectConfig(options)

Loads ageniti.config.json, ageniti.config.js, ageniti.config.mjs, or ageniti.config.cjs from a project root.

const config = await loadProjectConfig({ cwd: process.cwd() });

Returned fields may include:

  • build.appModule
  • build.appExport
  • build.outDir
  • build.includePackageJson
  • build.typescriptRuntime
  • mcp.transport
  • mcp.env
  • package.name
  • package.version
  • package.description
  • package.private
  • package.binName
  • configPath

detectTypeScriptRuntime(options)

Detects whether the current project is configured to generate TypeScript launchers.

const runtime = detectTypeScriptRuntime({ packageJson, config });

Current built-in support:

  • returns tsx when tsx is configured or installed
  • returns undefined when no supported TypeScript runtime is available

supportsTypeScriptEntrypoints(options)

Returns whether the current project can generate launcher files from a TypeScript-only headless app entry.

const supported = supportsTypeScriptEntrypoints({ packageJson, config });

Surface Adapter API

defineSurfaceAdapter(adapter)

Creates a custom adapter description.

Required field:

  • name

Defaulted fields:

  • description: ""
  • capabilities: {}
  • canExpose: () => true
  • describe: (action) => action

Built-In Adapters

  • cliAdapter
  • jsonAdapter
  • httpAdapter
  • mcpAdapter
  • reactAdapter
  • devAdapter
  • aiSdkAdapter

defaultSurfaceAdapters()

Returns the built-in adapter list used by createAgenitiApp() and createCli().

findAdapter(adapters, name)

Finds one adapter by name.

Lint API

import { lintActions } from "@ageniti/core/lint";

lintActions(actions)

Runs static checks against action definitions.

Current findings include:

  • duplicate action names
  • invalid action naming
  • weak descriptions
  • destructive MCP exposure
  • unspecified idempotency on write or destructive actions
  • non-object input shapes
  • write or destructive actions without permissions

Schema API

s

Lightweight schema builder.

Available builders:

  • s.string()
  • s.number()
  • s.boolean()
  • s.enum(values)
  • s.array(schema)
  • s.object(shape)
  • s.literal(value)
  • s.union(schemas)
  • s.record(schema)
  • s.any()

Common modifiers:

  • .describe(text)
  • .default(value)
  • .optional()
  • .nullable()
  • .meta(data)

Type-specific helpers:

  • string: .min(), .max(), .pattern(), .url(), .datetime()
  • number: .min(), .max(), .int()
  • object: .passthrough()

toJSONSchema(schema)

Converts an Ageniti schema into JSON Schema.

SchemaValidationError

Thrown by schema parsing paths when validation fails.

Error And Result Types

AgenitiError

Custom runtime error with:

  • code
  • issues
  • retryable

Runtime Result Shape

Success:

{
  "ok": true,
  "data": {},
  "artifacts": [],
  "logs": [],
  "meta": {
    "action": "create_task",
    "invocationId": "invocation-id",
    "surface": "cli",
    "durationMs": 12
  }
}

Failure:

{
  "ok": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Invalid action input.",
    "issues": [],
    "retryable": false
  },
  "artifacts": [],
  "logs": [],
  "meta": {
    "action": "create_task",
    "invocationId": "invocation-id",
    "surface": "mcp",
    "durationMs": 3
  }
}

Bulk Registration

defineActions(map, options?)

Register a record of actions in one call. CamelCase keys are normalized to snake_case action names.

import { defineActions, s } from "@ageniti/core";
 
export const actions = defineActions({
  createTask: {
    description: "Create a task.",
    input: s.object({ title: s.string() }),
    run: async ({ title }) => ({ id: crypto.randomUUID(), title }),
  },
  ping: () => ({ ok: true }),
});

Options:

  • defaults: shared config applied to every entry
  • rename(key): custom name strategy

actionFromHandler(handler, config)

Wrap a single existing function as an action. Handlers can be 1-arg (input) or 2-arg (input, ctx) — Ageniti detects and forwards correctly.

actionsFromHandlers(handlers, metadata?)

Pair a record of plain functions with metadata.

import * as handlers from "./app/actions/tasks";
import { actionsFromHandlers, s } from "@ageniti/core";
 
export const actions = actionsFromHandlers(handlers, {
  createTask: { description: "Create a task.", input: s.object({ title: s.string() }) },
});

Schema Interop

wrapSchema(foreign, options?)

Wrap any Zod-style schema (.safeParse / .parse) or Standard Schema v1 as an Ageniti Schema. JSON Schema is generated through best-effort introspection; pass { jsonSchema } to override.

assertSchema (used internally by defineAction) auto-wraps foreign schemas, so you usually never call wrapSchema directly.

s.object(shape).strict()

Reject unknown properties at validation time (companion to .passthrough()).

Streaming Events

runtime.stream(actionOrName, input, options?)

Returns an AsyncIterableIterator<RuntimeStreamEvent> that yields events as the action runs:

type RuntimeStreamEvent =
  | { type: "log"; level: string; message: string; time: string; fields: Record<string, unknown> }
  | { type: "artifact"; artifact: Artifact }
  | { type: "progress"; message?: string; percent?: number; fields?: Record<string, unknown>; time: string }
  | { type: "result"; envelope: RuntimeResult };

Events come from ctx.logger.*, ctx.progress.report(), and ctx.artifacts.add() inside run(). The stream always ends with one result event.

Typed Client

createClient(options)

import { createClient } from "@ageniti/core/client";
 
// In-process
const client = createClient({ runtime });
const task = await client.create_task({ title: "Hello" });
 
// Remote
const remote = createClient({ url: "https://api.example.com" });

Special methods:

  • client.$invoke(name, input, options?) — typed invocation by string
  • client.$stream(name, input, options?) — async iterable of events
  • client.$transport — underlying transport object

Notes:

  • Remote HTTP clients can send metadata, confirm, and idempotencyKey in the request body.
  • Remote HTTP clients cannot send trusted user or auth in the request body. Use headers / custom fetch and resolve identity with resolveContext on the server.
  • Transport failures and invalid remote responses also throw AgenitiClientError, with client-side codes such as TRANSPORT_ERROR or INVALID_RESPONSE.

AgenitiClientError

Thrown on failure with code, issues, retryable, and optional envelope. Transport-level failures also use this error type.

generateClientTypes(actions, options?)

Generate a TypeScript .d.ts from an action manifest:

import { generateClientTypes } from "@ageniti/core/client-gen";
import { writeFile } from "node:fs/promises";
 
await writeFile(".ageniti/client.d.ts", generateClientTypes(actions, {
  interfaceName: "TasksClient",
}));

React Hook

useAction(action, { runtime })

State machine hook. Subscribes to runtime.stream so logs / progress / artifacts update live. Imported from @ageniti/core/react-hooks.

State shape: { status, data, error, logs, artifacts, progress, invoke, cancel, reset }.

Component unmount auto-aborts; new invoke() calls cancel the previous one.

Test Utilities

import {
  createTestRuntime, expectOk, expectError, expectLog,
  collectStream, stubAction,
} from "@ageniti/core/test-utils";
 
const t = createTestRuntime([myAction], {
  services: { db: stubDb },
  allow: true, // or "denied reason" or async permission checker
});
const env = await t.invoke("my_action", { x: 1 });
expectOk(env);

Runtime Hooks & Redaction

const runtime = createRuntime({
  actions,
  hooks: {
    onInvocationStart: ({ action, invocationId, input }) => metrics.timer.start(invocationId),
    onInvocationEnd: ({ action, invocationId, envelope }) => metrics.timer.end(invocationId, envelope.ok),
  },
  redact: { keys: ["ssn", "credit_card"], placeholder: "[REDACTED]" },
  idempotencyMaxEntries: 5000,
  idempotencyTtlMs: 10 * 60 * 1000,
});

Default redaction keys: password, passwd, secret, token, apikey, api_key, authorization, cookie, session, x-api-key, access_token, refresh_token, private_key, client_secret. Error messages and issue text additionally have known token shapes redacted (Bearer …, JWT, sk-…, ghp_…, xoxb-…, key=value).

Error Codes

| Code | HTTP | CLI exit | When | |---|---|---|---| | VALIDATION_ERROR | 400 | 2 | input failed schema | | OUTPUT_VALIDATION_ERROR | 502 | 2 | run returned invalid output | | OUTPUT_SERIALIZATION_ERROR | 502 | 2 | run returned non-JSON-safe value | | AUTHENTICATION_ERROR | 401 | 3 | missing credentials | | AUTHORIZATION_ERROR | 403 | 3 | permission denied | | CONFIRMATION_REQUIRED | 409 | 3 | destructive action without confirm | | ACTION_NOT_FOUND | 404 | 4 | unknown action | | UNSUPPORTED_SURFACE | 405 | 4 | action not on this surface | | RATE_LIMITED | 429 | 5 | upstream rate limit | | CONCURRENCY_LIMIT | 429 | 5 | per-action limit hit (retryable) | | EXTERNAL_SERVICE_ERROR | 502 | 5 | upstream failure | | TIMEOUT | 504 | 124 | exceeded timeoutMs | | CANCELLED | 499 | 130 | user/runtime aborted | | CONFLICT | 409 | 1 | optimistic concurrency / state conflict | | INTERNAL_ERROR | 500 | 1 | uncaught run failure |