Skip to main content
An action is a named TypeScript operation loaded by the customer-hosted runner. It turns a stable production workflow into a typed catalog capability without giving the caller its implementation credentials. Actions live in your runner repository and are authored against the runner SDK (@polycore/runner).

Action boundary

The caller supplies

Schema-valid input through Slack, MCP, a dashboard, or another configured interface.

The runner supplies

The action’s declared local secrets and a structured logger. Secret values do not enter the input schema.
The runner is already bound to one project and environment, so an action does not select a credential bundle from a caller-provided environment argument.

Directory-derived identity

Actions are discovered recursively:
actions
billing
set-plan
action.ts
The loader derives the action id from the folder path. Do not add an id field to the definition.

Complete example

actions/billing/set-plan/action.ts
Never log secrets, authorization headers, connection strings, or raw credential errors. Logs and structured outputs can cross into the control-plane request history.

Definition fields

string
required
Human-readable operation name used in catalog and approval surfaces.
string
required
Tell an agent when to use the action, what it changes, and important preconditions. Keep it customer-specific only in the customer’s integration repository.
string[]
Optional organization metadata for the catalog.
"read" | "write"
default:"\"write\""
What the action is capable of. With no policy it is the whole decision: reads dispatch and writes wait for a human. The default is write, so an omitted classification fails safe.
Zod schema
required
Defines and validates all caller-controlled arguments before execution.
Zod schema
required
Defines the structured result advertised to callers. The action implementation must validate untrusted downstream data against it before returning, as the example does with output.parse(data).
record<string, string>
Maps every local secret key the action needs to a human-readable description.
function
required
Receives { input, secrets, log } and returns the declared output, either directly or as a promise.
function
Decides whether this invocation dispatches, waits for an admin, or is refused, overriding the hazard default. Receives { input, environment, via } and returns { decision: "allow" }, { decision: "admin_approval", because }, or { decision: "never", because }.
It must be a pure function of its context: no I/O, no clock, no counters. because reaches the approver and the audit record. See approvals and audit.

Import rules

Import defineAction, z, and action types from the runner SDK:
The runner re-exports its pinned Zod instance. Installing and importing another Zod copy can break the JSON Schema conversion used to advertise tool contracts. Actions are headless Node.js modules. They must not import React, dashboard components, browser APIs, or @polycore/ui.

Hazard selection

Use only for side-effect-free operations: the implementation and credential must not mutate state. Marking an action read states that the operation carries no side effects.
If an operation appears side-effect-free but triggers metering, sends notifications, advances a cursor, or changes a remote cache, classify it as write.

Secret resolution

An action declares names, not values:
At invocation time, the runner:
  1. Confirms that each declared key has a local value.
  2. Builds the secrets object for that action.
  3. Executes on the runner host.
  4. Never adds the values to capability metadata or the caller’s input.
Give different actions different credentials when their authority differs. A read action and a write action should not share a broad token merely because they call the same service.

Input design

Good action inputs are narrow, explicit, and reviewable.

Prefer stable identifiers

Accept accountId rather than an unbounded search phrase when the action targets one account.

Encode limits

Use enums, numeric bounds, URL validation, and discriminated unions so invalid authority cannot be requested.

Avoid secret arguments

Secrets belong in the action declaration and runner environment, not in caller input.

Keep approval legible

An approver should be able to understand the requested change from the action title and validated input.

Output design

Return enough information to confirm the effect without leaking the downstream credential or a large raw response. Recommended output fields include:
  • The stable id of the affected resource.
  • The previous and current state relevant to the operation.
  • A downstream operation id when it is safe and useful.
  • An explicit status for no-op or already-complete behavior.
Avoid returning full customer records when the caller needs only a confirmation.

Failure behavior

Throw an error when the action cannot establish the declared result. Do not convert permission failures, timeouts, validation errors, or downstream rejections into successful-looking output. Use the logger for concise progress:
  • log.info for meaningful start or stage information.
  • log.warn for recoverable conditions the caller should know.
  • log.error before a failure only when it adds context.
  • log.success for a completed effect.
Keep successful actions quiet enough that the structured output remains the primary result.

Review checklist

The action imports only from the runner SDK (@polycore/runner) for its Polycore and Zod API.
The folder path provides the intended catalog id and the definition has no id field.
Input and output schemas are narrow, and the hazard matches every side effect.
Declared secrets are the minimum authority required and are not logged or returned.
Downstream failures remain failures and outputs are bounded.
Tests cover success, validation, authorization failure, and relevant no-op or idempotency behavior before deployment.