run function, then use the same action as an agent tool or as a workflow step.
Use an action for a clear, deterministic capability: look up a customer, post a Slack message, classify a message, create an invoice, or call an internal API.
Example requests
Ask your coding agent for a single capability. It can turn that request into an action your workflows and agents reuse.“Build an action that reviews customer feedback submissions and creates a Linear issue if the feedback is bug related.”
“Create an agent tool that looks up a customer in our database by email and returns their billing plan and recent orders.”
How actions are defined
Actions live in your project code undersrc/actions/. Each file exports a defineAction() definition, and the runtime discovers it when you run or deploy the project.
src/actions/triage.ts
input and output Zod schemas are validated on every execution: the input is parsed before run is called, and the return value is parsed before it is handed back. This keeps an action’s contract honest no matter who calls it.
Configuration reference
defineAction() accepts these options. Only slug, input, output, and run are required.
| Option | Required | What it does |
|---|---|---|
slug | Yes | Stable key used for discovery and as the tool name when used as an agent tool |
input | Yes | Zod schema for the value passed into run |
output | Yes | Zod schema for the value run returns |
run | Yes | async (input, credentials) => output, the body |
name | No | Human-readable label shown in the platform and used for the agent tool label |
description | No | Description shown to the model when the action is an agent tool |
credentials | No | Credentials the action needs at runtime. See credentials. |
run function receives the validated input and, when the action declares credentials, a resolved credentials object. A third argument carries an AbortSignal ({ signal }) you can pass to long-running work so it cancels cleanly when a run is stopped.
Actions are leaf units
An action’srun must not call another action, not your own and not an integration action like slackSendMessage. Composition belongs in a workflow; an integration action is used directly as a workflow step or an agent tool, never wrapped in a custom action.
@keystrokehq/*/actions inside src/actions/. The fix is always to compose in a workflow, or attach the integration action directly as a step or tool.
An action may call an agent with .prompt(); agents are the one thing an action is allowed to invoke.
src/actions/research-signup.ts
Where actions run
The same action definition is reused by both consumers; there is no separate “tool” type.| Consumer | Usage |
|---|---|
| Workflow | await myAction.run(input) inside defineWorkflow(), a durable step |
| Agent | tools: [myAction] on defineAgent(), an agent tool |
slackSendMessage from @keystrokehq/slack/actions). You use them the same way, as a workflow step or an agent tool, without redefining them.
Discovery
Keystroke discovers one action per file undersrc/actions/. Use a default export or a single named export; a file with multiple actions is an error. The discovery key is the action’s slug, not the filename.
Next steps
Actions as agent tools
Let an agent call your actions as tools.
Actions as workflow steps
Run actions as durable steps inside a workflow.
Credentials
Declare and resolve API keys and OAuth for actions.
Integrations
Browse 1,000+ built-in integrations and their actions.