Source: https://docs.cumuluslabs.io/sdk-definitions
Content version: 1665608e

# Define agents, steps and tools

An agent definition describes a conversation as steps and transitions. Start with a small flow, then add tools and reusable components as the task grows. Import the builders from `@cumulus/sdk/agent`; they return serializable objects that Cumulus can validate and publish.

## A complete small definition

Save this as `cumulus/hello.mjs` in a project with the SDK installed. It exports the project and bindings expected by the CLI:

```javascript
import {
  defineAgent, defineNode, defineEdge, defineProject, defineEnvironment,
} from '@cumulus/sdk/agent';

const welcome = defineNode({
  key: 'welcome', kind: 'conversation',
  instruction: { type: 'static_text', text: 'Hello. How can I help?' },
});
const done = defineNode({
  key: 'done', kind: 'end',
  instruction: { type: 'static_text', text: 'Thanks for calling. Goodbye.' },
});
const agent = defineAgent({
  key: 'hello', name: 'Hello agent', channels: ['phone'], languages: ['en'],
  startNode: welcome, nodes: [welcome, done],
  edges: [defineEdge({
    from: welcome, to: done, slot: 'edges',
    condition: { type: 'prompt', prompt: 'The caller is finished or asks to stop.' },
  })],
});
export const project = defineProject({ key: 'hello_project', agents: [agent] });
export const bindings = defineEnvironment({});
```

Run `./node_modules/.bin/cumulus validate --project cumulus/hello.mjs`. This checks the definition locally. Connecting models, tools and a phone number comes later in [the complete tutorial](/docs/sdk-tutorial).

`startNode` identifies the first step. Each edge identifies the step it leaves (`from`), the next step (`to`) and when to follow it (`condition`). The `key` is the stable identity: changing a display name or moving a file should not change that key.

## Choose the step that fits the task

| Node kind | Use it when | Configuration and transitions |
| --- | --- | --- |
| `conversation` | Ask a question or explain something. | Supply a prompt or fixed text. Use `edges` for the conversational routes. |
| `extract_dynamic_variables` | Save information from what was said. | Declare the fields to extract, then use them in later prompts or conditions. |
| `branch` | Choose a route from existing information. | Define the conditions and fallback for that decision. |
| `function` | Request a tool in your backend. | Set `tool` and `waitForResult`. Use `always_edge` for the normal result and `else_edge` for failure. |
| `transfer_call` | Connect a phone caller to a person. | Configure an allowed destination and the transfer/fallback paths. See [phone transfers](/docs/phone). |
| `component` | Reuse a group of steps. | Reference a component with its own start node and return behavior. |
| `end` | Finish the call or task. | Optionally supply a final message. |
| `verify_identity` | Match private keypad input against authorized expected values. | Use `defineVerification` and its fixed outcome paths. |
| `tokenize_private` | Send private keypad input to a configured vault. | Use `defineTokenization` with its tokenization-purpose tool. |

Private keypad nodes have additional requirements described in [private workflows](/docs/sdk-private). The other native transition slots are `skip_response_edge`, `edge` and `success_edge`; use the slot defined for the node's behavior. An `always` condition does not make all slots interchangeable.

## Write instructions and conditions

Use `{ type: 'static_text', text }` when the agent should say specific text. Use `{ type: 'prompt', text }` when it should respond according to an instruction and the conversation.

Conditions can be unconditional (`always`), conversational (`prompt`), equations on variables (`equation`), or language selection (`conversation_language`, `en`/`es`). A prompt condition asks the model to interpret the conversation. Business authorization still belongs in your backend; a model interpreting a request does not grant permission to perform it.

Use string node keys for loops rather than circular JavaScript objects. A start node and all destinations must exist in the same scope. The compiler rejects duplicate nodes and missing references.

## Connect a tool

A tool definition declares an HTTP operation's input, output and execution policy. The handler is separate customer code. In the tutorial, `service_guide` accepts a delivery/repair topic and returns guidance. A function node references that tool, and `responseVariables` maps `$.guidance` into `service_guidance` for the next conversation step.

Tool input/output schemas determine TypeScript inference and runtime validation. Common strings, numbers, booleans, objects, arrays, enums and unions infer types. More complex valid schemas can infer `unknown`; validate and narrow those values in your application. Optional, omitted and null are different values.

Choose the response-mapping policy for the result your tool actually returns:

- **`required`**: every declared mapping must be present. Use it when a successful lookup always returns the promised fields.
- **`replace_available`**: replace the mapped values with those available in this response and clear omitted/null fields. Use it when an authorized lookup can legitimately return fewer fields.

For example, if the first lookup returns `name: 'Alex'` and the next returns no name, `replace_available` clears the old name. It does not leave Alex's name attached to the next subject. Map scalar paths, not entire objects or arrays.

See [the tutorial's tool contract](/docs/sdk-tutorial#3-describe-the-tool) and [customer handlers](/docs/sdk-server) for matching implementation code.

## Reuse a component

`defineComponent` groups nodes, edges and tools under a scoped key and start node. A component can represent a repeated task such as collecting an address or checking availability. A component node calls that subgraph and follows its return behavior. It remains part of the published graph; it is not another hosted service.

Use a factory to create separate instances under different component keys. Keep each instance's node identities unique within its scope. Tools or components with the same key must have the same definition.

## Definition API

| Function | Result |
| --- | --- |
| `defineAgent(definition)` | The agent's identity, channels, languages, graph and settings. |
| `defineNode(definition)` / `defineEdge(definition)` | A step and a transition. |
| `defineComponent(definition)` | A reusable scoped subgraph. |
| `defineTool(definition)` | An HTTP tool contract and execution policy. |
| `defineIntegration(definition)` | An agent's context, number and event connections. |
| `defineProject(definition)` | A group of agents/integrations selected independently for authoring. |
| `defineEnvironment(bindings)` | Endpoint, secret-reference, number and notification bindings. |
| `compileAgent(agent, bindings?, integration?)` | Resolved configuration checked against the packaged API schema. |
| `secretReference(metadata)` | A pinned secret reference from the server's metadata. |

`AgentDefinition` also accepts `prompt`, `startSpeaker`, default string `variables`, declared `contextVariables`, `settings`, `modelChoice`, `toolCallStrictMode` and native `edits`. Use typed `DraftEdit` values for provider, recording, analysis, telephony, knowledge, trigger and layout configuration. Server validation reports unsupported or missing configuration before publication.

Agent, tool, project and integration keys match `^[a-z][a-z0-9_]{0,62}$`. Node and component keys allow the native graph identity vocabulary up to 256 characters. Keep definition modules free of application startup code so importing or validating them remains a local operation.
