Skip to main content

Bots (YAML)

A bot is the entity behind the slash-commands operators trigger from the chat (/hola, /registrar). Each bot owns a list of commands, and — optionally — an embedded automation graph (a ParametrizedBot) that runs when a command fires. Because a bot can invoke almost anything in the platform, it's applied last in a directory apply, after every resource it might reference.

bots is not bot-types. cotctl bots manages these Bot entities — the things with a name and commands[]. cotctl bot-types lists the catalog of ParametrizedBot building blocks (PBMessage, PBCreateTask, …) you reference inside stages[].name. They're different surfaces — see Bot types, and the migration note below if you used an older cotctl.

The shape of a bot​

kind: Bot
name: "Saludo Bot" # upsert key — unique per company, 1..80 chars
description: "Bot que saluda con /hola"
isActive: true
global: false

commands:
- description: "Saluda al usuario"
isSlash: true
slashCmd: "hola" # the literal an operator types (no leading slash)
isActive: true

parametrizedBot: # optional automation that runs when a command fires
start: send
stages:
- key: send
name: PBMessage
version: "2.2.0" # pinned: the default version never takes ERROR
data:
content: "Hola"
contentType: "text/plain"
sentBy: "6a000000000000000000beef" # the user shown as the sender
channelIds:
- "6a000000000000000000abcd"
next:
SUCCESS: ""
ERROR: notify_failure
- key: notify_failure # the error path of the stage above
name: PBMessage
version: "2.2.0"
data:
content: "El bot de saludo no pudo responder."
contentType: "text/plain"
sentBy: "6a000000000000000000beef"
channelIds:
- "6a000000000000000000fa11" # a channel the team watches
next:
SUCCESS: ""
ERROR: "" # the handling stage ends the run either way
FieldRequiredNotes
kindYesAlways Bot
nameYesThe upsert key. Unique per company, 1–80 chars. No format rules — any string
descriptionNoFree text
isActiveNoDefaults to true on create; an update that omits it keeps the stored value. Soft-delete by re-applying with isActive: false
globalNoDefaults to false. When true, the bot is available across companies — common in production, no warning on apply
commandsNoSlash-commands and survey-commands. See the three-way rule below
parametrizedBotNoThe automation graph, same shape as bots embedded in workflows
extraDataNoFree-form feature flags (messages, messagesWithSubsurveys). Omit it to keep the stored list; since 0.14.0 extraData: [] empties it

name is the identity — there is no code. Unlike most resources, a bot is looked up by name, and matching is exact. cotctl never exposes a version field either: it's fixed to V3 on create, which is the only engine the admin webclient lets you save.

Commands​

Each entry in commands[] is one action an operator can trigger. A command can be a pure slash-command, a survey-command, or both:

commands:
# pure slash-command: operator types /hola, the bot runs
- description: "Saluda"
isSlash: true
slashCmd: "hola"

# survey-command: operator types /registrar, a Survey is presented,
# then the bot runs after it's answered
- description: "Inicia el registro"
isSlash: true
slashCmd: "registrar"
isSurvey: true
surveyIds:
- "6a000000000000000000abcd" # a Survey ObjectId — NOT a survey code

A few things trip people up:

  • slashCmd is the literal text, without the leading slash — the chat prepends it. There are no format rules; whitespace and accents are matched as-is. It's required whenever isSlash: true.
  • surveyIds takes Survey ObjectIds, not codes. Codes aren't resolved here. Find the _id with cotctl surveys list -c <profile>. The field is a list because one command can present several surveys, though in practice it's usually one.
  • showHelp (default true) controls whether the command appears in the chat's auto-help. isActive on a command (default true) hides just that one slash while keeping the bot live — both the bot's and the command's isActive must be true for a command to be reachable.
  • arguments[] exists for slash-command parsing but is exotic — essentially unused in production. Treat it as a rarely-needed escape hatch.

A declared commands list is the complete list — and its absence means "keep". This is the single most important operational rule for bots. When you apply an update:

YAMLResult
commands omittedThe existing array is kept untouched
commands: [ … ]The list you write is the bot's list: every stored command not in it is deleted, and apply names each one on stderr before the write — before the prompt without -y, so declining still keeps them
commands: []All commands deleted. cotctl bots apply refuses to do this silently: it names them and makes you retype the bot name to confirm — even with -y

Since 0.14.0 each command you declare is paired with its stored version — by slashCmd, or by surveyIds for a survey command — and each of its arguments by name, so a key it omits keeps its stored value: a command that omits isActive: false stays deactivated. A survey command whose surveyIds you change is a different command to cotctl (the stored one is deleted and named in the warning), and a command that is neither a slash nor a survey command travels as written, with a warning naming the stored keys it loses.

apply --dir does not ask for the bot name. Without -y, its preview names the commands a commands: [] deletes before its single confirmation prompt; with -y it deletes them without asking. The name prompt is a safeguard of cotctl bots apply — don't use a directory apply to get around it.

To add one command to an existing bot, export it, append the entry, and re-apply:

cotctl bots export "Saludo Bot" -c acme -o bot.yaml
# edit bot.yaml — append to commands[]
cotctl bots apply -f bot.yaml -c acme

Or, since 0.14.0, apply a partial: true document that names only the commands it adds or changes — the stored ones it leaves out are kept, and under the marker even commands: [] deletes nothing:

kind: Bot
name: "Saludo Bot"
partial: true
commands:
- slashCmd: "adios"
isSlash: true
description: "Se despide"

The automation: parametrizedBot​

parametrizedBot is the graph that runs when a command fires. It's the same shape as the bots you embed in workflow states, SLAs, and schedules — a start stage and a list of stages[], each with a name (the bot type), a data payload, and next branches. For the full ParametrizedBot reference, see Workflows.

The one thing worth calling out here: a stage can invoke a standalone Routine (PBScript). This is the most common pattern in production bots:

parametrizedBot:
start: invoke_routine
stages:
- key: invoke_routine
name: PBScript
data:
code: rutina_calcular_riesgo # must be a real Routine code
data: {} # the routine's input — {} when it takes none
next:
SUCCESS: send_message
ERROR: ""

The routine's input goes under data.data, one key per entry of its dataType — the only part of the stage's data that PBScript passes on. data.data is required even when the routine takes no input: since 0.14.0 every apply refuses a new PBScript stage without it (see Required data entries).

When your YAML declares a PBScript stage, cotctl checks that the routine code actually exists in the profile before applying — so a typo fails at apply time with a "did you mean…?" suggestion, not silently at runtime. It also validates that start points to a real stage and that every next branch lands on a real stage or the empty string (a terminal branch).

The bot's user​

Every bot carries a dedicated user account that the backend creates automatically on the first apply. Its email is derived from the bot name and company subdomain (saludo-bot.acme.bot@cotalker.com), it's flagged system: 'bot', and it gets the bot-default role.

You don't manage the bot user through cotctl bots. It never appears in the YAML — not on create, not on export (the email is emitted as a comment for reference). Because cotctl never reads or writes it, any edits you make to it from the admin webclient (roles, phone, avatar) survive re-applies. The flip side: cotctl can't revert those edits either. To change the bot user, edit it directly with cotctl users apply or the admin UI. You'll also find it listed among users — it's the one carrying system: 'bot'.

Working with bots​

# Read
cotctl bots list # active bots (default)
cotctl bots list --all # include inactive
cotctl bots list --search "greet" # backend search
cotctl bots list --global-only # only global: true
cotctl bots get "Saludo Bot"
cotctl bots export "Saludo Bot" -o bot.yaml

# Write
cotctl bots apply -f bot.yaml --dry-run # preview
cotctl bots apply -f bot.yaml -y

apply takes -f/--file (required), --dry-run, -y/--yes, -q/--quiet, and --allow-script-bots (required when parametrizedBot has a PBScript, CCJS or ESMCode stage), and handles multi-document files. As always, --dry-run first — especially against production — to see the create/update plan and any validation errors before anything is written.

No deactivate, test, or logs subcommand. There's no cotctl bots deactivate (soft-delete with isActive: false instead) and no cotctl bots test — the backend has no consolidated endpoint for it yet. There's no cotctl bots logs either: a bot's runs are read with cotctl logs, for example cotctl logs list --bot "<bot name>" --since 24h.

A recent rename​

If you've used an older cotctl, the bots namespace changed meaning:

  • cotctl bots list used to list the type catalog (PBMessage, …). It now lists your Bot entities. There is no alias — scripts that expected the catalog must move to cotctl bot-types list.
  • cotctl bots versions <BotType> still works but is deprecated: it prints a warning and delegates to cotctl bot-types versions <BotType>. It's slated for removal in cotctl 1.0.0.

See Bot types for the catalog commands.

See also​

  • Bot types — the catalog of stage building blocks (bots ≠ bot-types)
  • Routines — the PBScripts a PBScript stage invokes
  • Workflows — the full ParametrizedBot reference
  • Surveys, Users — what a bot references
  • apply — bots are applied last