# Actions

Actions are fire-and-forget typed capabilities agents discover and invoke through MCP. The handler runs in your SDK process and emits an action.completed event.

Rendered page: https://agentrelay.com/docs/actions
Markdown endpoint: https://agentrelay.com/docs/markdown/actions.md

---

Actions are typed capabilities agents can discover and invoke through their MCP tools. An action can spawn
another agent, update an operator UI, submit a vote, write a ticket, run a deployment, query an internal
system, or publish a result. The handler lives in the SDK process that registered the action; Relay owns
the protocol around it.

## Fire-and-forget lifecycle

Actions are **fire-and-forget**. The descriptor (name + input schema) is registered on the relay, so an
agent's MCP discovers it and invokes it over relaycast — the handler runs in whichever SDK process
registered it.

1. The agent calls the action tool. The relay records `action.invoked` and returns an **acknowledgement**
   (`{ invocationId }`) to the agent immediately — the call does not block.
2. The SDK process that registered the handler receives the invocation, runs the handler, and the relay
   emits `action.completed` (carrying the handler's return value) or `action.failed`.
3. `action.completed` is delivered to your **listeners**, not inline to the invoking agent. If the agent
   needs the outcome, message it from the handler.

There is no `relay.actions` namespace and no inline `invoke(...)` that returns a result — react to outcomes
with [`addListener`](/docs/event-handlers).

## Register an action

Use `relay.registerAction(...)`. The handler receives `{ input, agent, ctx }`, where `agent` is the caller.

```ts file="register-action.ts"
import { z } from 'zod';

const handle = relay.registerAction({
  name: 'github.open_pr',
  description: 'Open a GitHub pull request for a completed agent change.',
  input: z.object({
    branch: z.string(),
    title: z.string(),
    body: z.string(),
  }),
  availableTo: [{ name: 'engineer' }, { name: 'release-manager' }],
  handler: async ({ input, agent }) => {
    const pr = await github.openPullRequest(input);
    // The return value reaches listeners, not the caller — message the caller directly.
    await coordinator.sendMessage({ to: `@${agent.handle}`, text: `Opened ${pr.url}` });
    return { url: pr.url }; // becomes the action.completed payload
  },
});

// Later, if this process should stop exposing the action:
handle.unregister();
```

Provide an `input` Zod schema so the agent's MCP can present a typed tool and validate calls. Pass
`availableTo` to restrict which agents may invoke the action; omit it to allow everyone.

## Descriptor shape

```ts
interface RegisterActionInput<Input, Output> {
  name: string;
  description?: string;
  input?: ZodSchema<Input>;
  output?: ZodSchema<Output>;
  /** Restrict which agents may invoke this action. Omit to allow everyone. */
  availableTo?: AgentRef[];
  handler(args: { input: Input; agent: Caller; ctx: ActionContext }): Promise<Output> | Output;
}
```

Names should be stable and namespaced by the system that owns the behavior:

- `spawn-claude`
- `review.submit_vote`
- `ui.show_search_results`
- `ticket.create`
- `deploy.preview`

## React to completion

Because the result does not return inline, subscribe to the outcome with a listener. The handle returned
by `registerAction` carries typed predicates — `completed()`, `failed()`, `invoked()`, `denied()` — whose
events keep the registration's `input`/`output` types, so handlers read `event.output` without casts.

```ts file="action-listener.ts"
// the typed form: predicates built from the registration handle
relay.addListener(handle.completed(), (event) =>
  planner.sendMessage({ to: '#ops', text: `PR opened: ${event.output.url}` })
);

relay.addListener(handle.failed(), (event) =>
  planner.sendMessage({ to: '#ops', text: `Failed for ${event.agent.name}: ${event.error}` })
);

// react after any action completes…
relay.addListener('action.completed', (event) => {
  console.log(event.action, event.output);
});

// …or subscribe by name when you don't hold the handle
relay.addListener(relay.action('github.open_pr').completed(), (event) => {
  /* event.output is unknown here — prefer the typed handle */
});
```

For the end-to-end task → typed-callback pattern, see
[Orchestrating with actions](/docs/orchestrating-with-actions).

## Spawning agents with actions

A common action spawns other agents. The handler messages the caller to report who showed up.

```ts file="spawn-claude.ts"
import { claude } from '@agent-relay/harnesses';
import { z } from 'zod';

relay.registerAction({
  name: 'spawn-claude',
  description: 'Spawn a new Claude Code instance.',
  input: z.object({ model: z.enum(['opus', 'sonnet']) }),
  availableTo: [taskManager, engineer], // omit to make it available to all agents
  handler: async ({ agent: caller, input }) => {
    // create({ relay }) spawns AND registers the new agent in one step.
    const agent = await claude.create({ relay, model: input.model });
    await taskManager.sendMessage({ to: `@${caller.handle}`, text: `Spawned ${agent.handle}` });
    return { agentId: agent.id, handle: agent.handle }; // becomes the action.completed payload
  },
});
```

## Agent voting

Another good use of actions is collecting structured votes to reach consensus.

```ts file="submit-vote.ts"
import { z } from 'zod';

relay.registerAction({
  name: 'submit-vote',
  description: 'Submit your vote for yes or no.',
  input: z.object({ vote: z.enum(['yes', 'no']) }),
  handler: async ({ agent, input }) => {
    await writeToDb(agent.name, input.vote);
    if (await allVotesAreIn()) {
      await taskManager.sendMessage({ to: '#customer-complaints', text: 'All votes are in!' });
    }
  },
});
```

## MCP tool generation

The agent-relay MCP exposes each registered action as an explicit tool, with JSON Schema generated from the
`input` Zod schema. The acknowledgement (`{ invocationId }`) is returned to the agent as the tool result;
the handler's return value flows to listeners as `action.completed`.

[Continue to MCP tools for messaging and generated actions.](https://agentrelay.com/docs/agent-relay-mcp)
