--- name: pipefy-ai-agents description: > Use this skill when the user wants to create, read, update, delete, or troubleshoot AI agents (conversational agents with behaviors). Includes pre-flight validation and pipe-scoped knowledge bases (list, plain text/document/data lookup CRUD, access probe) attached via dataSourceIds. For traditional automations and AI automations, see `pipefy-automations`. tags: [pipefy, ai-agents, behaviors, conversational] --- # AI Agents Read only the reference for your active surface: [MCP](references/mcp.md) or [CLI](references/cli.md). The workflows below use shared operation names and arguments. Conversational AI agents attached to pipes. Each agent has an agent-level instruction and 1–5 behaviors, each with its own trigger event, prompt, and actions. For traditional automations and AI automations (prompt-driven), see `pipefy-automations`. --- ## Tools | Operation | Read-only | Purpose | | ------------ | ----------- | --------- | | `get_ai_agents` | Yes | List AI agents for a pipe (`repo_uuid` = pipe UUID, not numeric `id`). | | `get_ai_agent` | Yes | Full agent config including behaviors. | | `create_ai_agent` | No | Create a new conversational agent (active by default; `active=false` to start disabled). | | `update_ai_agent` | No | **Full-replace** (not patch). Always send complete `behaviors`. Preserves disabled state. | | `delete_ai_agent` | No | **Destructive; review and approve first.** | | `toggle_ai_agent_status` | No | Explicit activate/deactivate. | | `validate_ai_agent_behaviors` | Yes | **Pre-flight check before create/update.** | Execution logs live in `pipefy-observability` (`get_ai_agent_logs`, `get_ai_agent_log_details`). --- ## Active lifecycle - Agents are **active by default**. Create-active clears the API default disabled shell via the configure update (omits `disabledAt`). Create-inactive (`active=false`) sets `disabled_at` explicitly on create and the chained update. - Routine `update_ai_agent` **preserves** disabled state — it does not intentionally reactivate. Prefer passing `disabled_at` from a prior `get_ai_agent` (`disabledAt`) to skip the preserve re-read; when omitted, the SDK re-reads and re-sends. - Explicit activate/deactivate: `toggle_ai_agent_status`. - After create/update, check the response's `disabled_at` and `active` (`active` is true when `disabled_at` is null). On `get_ai_agent`, `disabledAt` null means active; `behaviors[].active` is not agent enablement. An agent with no active behavior is disabled by the API regardless of enablement flags. --- ## Creation workflow (discover → validate → create → verify) **Consent:** create or suggest an AI agent only when the user explicitly asked for AI / an agent. If AI seems useful but was not requested, ask first — never introduce agents without being asked. Never guess event IDs, phase IDs, action types, or field IDs. ### 1 — Get pipe metadata Call `get_pipe(pipe_id)` and extract: - `uuid` → use as `repo_uuid` in all AI-agent tools. - `phases[].id` / `phases[].name` → needed for `move_card` actions. - Fields via `get_start_form_fields(pipe_id)` and/or `get_phase_fields(phase_id)` → needed for `update_card` actions. ### 2 — Check existing agents `get_ai_agents(repo_uuid)` to avoid duplicates. To modify an existing agent, use `update_ai_agent` (not create). For full config, use `get_ai_agent(uuid)`. ### 3 — Discover valid trigger events `get_automation_events(pipe_id)`. Common events: | Event (`event_id` value) | `event_params` required | Example | |------------|-------------------------|---------| | card_created | None | `{}` | | card_moved | `{"to_phase_id":""}` | Fires only when card enters that phase. | | field_updated | `{"triggerFieldIds":["", ...]}` | Fires only when those fields change. | | manually_triggered | None | User clicks button on card. | For `card_moved` and `field_updated`, you MUST include `event_params`. Omitting it makes the behavior fire on every occurrence. ### 4 — Discover valid action types `get_automation_actions(pipe_id)`. Known `actionType` values and their required `metadata`: | Action (`actionType` value) | `metadata` required | |--------------|---------------------| | update_card | `pipeId` + `fieldsAttributes` (each entry needs `fieldId` + `inputMode`) | | move_card | `destinationPhaseId` | | create_card | `pipeId` + `fieldsAttributes` | | create_connected_card | `pipeId` + `fieldsAttributes` (requires pipe relation) | | create_table_record | `tableId` + `fieldsAttributes` (table field IDs; **no** `pipeId`) | | send_email_template | `emailTemplateId`; optional `allowTemplateModifications` (bool) | | human_validation | `emails` (recipient list) and `title`. The API accepts either one alone but rejects empty `metadata` | | mcp_tool | `mcpServerId` + `toolName`; optional `toolInputs`, each with `name` and `source`: `fixed_value` with a `value`, or `card_field` with a `fieldId` | `fieldId` values for card actions accept slug or numeric `internal_id`; for `create_table_record` they are **table** field IDs (validate with `get_table` / `get_table_record`, not the pipe). ### 5 — Build the behavior dict ```json { "name": "", "event_id": "", "event_params": {}, "actionParams": { "aiBehaviorParams": { "instruction": "", "actionsAttributes": [ { "name": "", "actionType": "", "metadata": { } } ] } } } ``` - Each behavior MUST have at least one action in `actionsAttributes`. - **Maximum 5 behaviors per agent.** - **`inputMode` is required on every `fieldsAttributes` entry** (omitting it fails model validation). Values: `fill_with_ai` (AI writes the value into an **output** field), `fixed_value` (use the literal `value`), `copy_from` (`value` is a `%{…}` template copying another field). - **Input** field references (`%{field:}` in the behavior `instruction`, auto-populated into `referencedFieldIds` on create/update) are needed **only when** the AI must read card field values, not for every `fill_with_ai` (e.g. instruction-only, OCR/attachment, or knowledge-base context). When card inputs are needed and omitted, `card.fields` arrives empty at trigger time and the model may hallucinate. A wrong **numeric** input id is accepted silently (validate and create/update) and becomes a dead `referencedFieldId`; a wrong **slug** never resolves and is dropped by the digits-only extractor (unresolved token). Either way `card.fields` stays empty (same hallucination); confirm the id with `get_start_form_fields` / `get_phase_fields`. Dotted connected-pipe refs (`%{field:.}`) are not forwarded at runtime; to read a connected card field, use a field on the current pipe. - For `update_card`: set `destinationPhaseId: ""` when not moving the card. #### Example identifiers (fictional) Use real values from `get_pipe` / `get_start_form_fields` for your org. Placeholders below match unit-test fixtures in this repo. **The syntax matters** (`pipeId`, `fieldId`, `%{field:}`, `inputMode`) — **the example digits do not**; substitute each pipe's numeric `internal_id` and phase id. | Role | Example value | |------|----------------| | Pipe (numeric repo id) | `987654321` | | Field `internal_id` | `900000101` | | Destination phase (`move_card`) | `900000201` | | Target pipe (`create_card`) | `900000301` | #### Metadata examples ```json // update_card — output field fill_with_ai; input fields referenced in instruction { "pipeId": "987654321", "destinationPhaseId": "", "fieldsAttributes": [{ "fieldId": "900000101", "inputMode": "fill_with_ai", "value": "" }] } // companion instruction (aiBehaviorParams.instruction), not metadata: // "Read %{field:900000102} (title) and %{field:900000103} (description), then fill the category." // move_card { "destinationPhaseId": "900000201", "fieldsAttributes": [] } // create_card { "pipeId": "900000301", "fieldsAttributes": [{ "fieldId": "title", "inputMode": "fill_with_ai", "value": "" }] } // create_table_record (fieldsAttributes are TABLE field IDs; no pipeId) { "tableId": "", "fieldsAttributes": [{ "fieldId": "", "inputMode": "fill_with_ai", "value": "" }] } // send_email_template { "emailTemplateId": "", "allowTemplateModifications": false } // human_validation { "emails": ["reviewer@example.com"], "title": "Review this card" } // mcp_tool (mcpServerId is an MCP server registered on the pipe) { "mcpServerId": "", "toolName": "", "toolInputs": [{ "name": "query", "source": "fixed_value", "value": "acme" }, { "name": "email", "source": "card_field", "fieldId": "customer_email" }] } ``` ### 5b — Optional: capabilities and LLM provider Inside `actionParams.aiBehaviorParams` a behavior may also carry: - **`capabilitiesAttributes`** — advanced tools the behavior can use. Each entry is exactly `{ "capabilityType": "", "enabled": true|false }` (both keys required, no extra keys — bare strings or `{ "type": ... }` are rejected). | Product name | `capabilityType` | |---|---| | IDP / Intelligent Document Processing | `advanced_ocr` | | Calculations & Analysis | `math_operations` | | Web Search | `web_search` | | Web Scraping | `web_scraping` | | Max effort | `max_effort` | `capabilityType` is not checked against a fixed set — any value passes through and the API validates the enum on write, so new capabilities work without a toolkit update. Validation checks **shape only, not entitlement** — a capability may still require organization-level enablement to have any effect, so a green pre-flight does not guarantee the capability is active for the org. - **`providerId`** / **`systemProviderId`** — pick the behavior's LLM provider. Set **at most one** (a behavior resolves to a single active provider). Discover valid IDs with `get_llm_providers`: each provider carries `type` — use `providerId` for a custom (`byom`) provider and `systemProviderId` for a Pipefy-managed (`system`) one. `get_default_llm_provider` shows what a behavior falls back to when neither is set. IDs are also visible in the organization's AI settings in the Pipefy UI. **Bring your own model (custom provider).** First probe `validate_llm_provider_access`: read access does not prove the stronger `manage_ai_providers` permission or eligible plan required for writes. Create a provider with `configuration_file_path` (a local JSON file) and use its `id` as `providerId`. For `update_llm_provider`, send the full configuration: retain `__REDACTED__` placeholders to preserve existing secrets or supply new values to rotate them. Check `get_llm_provider_dependencies` before `delete_llm_provider`; use `set_llm_provider_active_status` for activation. `set_default_llm_provider` takes exactly one of `provider_id` / `system_provider_id`; `reset_default_llm_provider` clears the default. - **`dataSourceIds`** — knowledge base sources the behavior can draw on. Each ID is a knowledge base item ID from `get_ai_knowledge_bases`. Agents also carry an agent-level `data_source_ids`; the two are unioned. See [Knowledge bases](#knowledge-bases-data-sources) below for the create → attach flow. ```json { "instruction": "Extract totals from the attached invoice.", "capabilitiesAttributes": [{ "capabilityType": "advanced_ocr", "enabled": true }], "actionsAttributes": [ /* ... */ ] } ``` ### 6 — Validate (recommended for complex behaviors) `validate_ai_agent_behaviors(pipe_id, behaviors)` checks: - Output field IDs (`fieldsAttributes[].fieldId`) exist in the pipe - Phase IDs exist - Pipe relations exist for `create_connected_card` - Action types are valid (`KNOWN_AI_ACTION_TYPES`; `create_table_record` `fieldsAttributes` are **table** field IDs, so they are not checked against the pipe and surface a warning to verify with `get_table`; `send_email_template` metadata runs no pipe field-ID checks) - Behavior structure passes Pydantic validation (including canonical `capabilitiesAttributes` shape and at most one of `providerId` / `systemProviderId`) - `fieldsAttributes[].fieldId` values (outputs) are checked against start-form and phase fields, accepting both slug `id` and numeric `internal_id`. Instruction `%{field:...}` tokens (inputs) are **not** existence-checked: a missing id/slug still yields `valid: true`. Slug → numeric rewrite happens only on create/update, not here. - Pass `data_source_ids` (agent-level) to also check knowledge base membership: it is unioned with each behavior's `dataSourceIds` and checked against the pipe's knowledge bases. Unknown IDs are **warnings only** (`valid` stays true); if the knowledge base list cannot be read, a single warning is added and the check is skipped. **`strict_unknown_action_types`** (default `true`): an `actionType` outside `KNOWN_AI_ACTION_TYPES` is reported in `problems` (blocking). Set `false` to demote unknown action types to `warnings` only, so `valid` stays true. ### 7 — Create the agent `create_ai_agent` with `name`, `repo_uuid`, `instruction`, and `behaviors`. One-call creation is preferred — avoids partial agent shells. Agents are **active by default**; pass `active=false` to start disabled (see [Active lifecycle](#active-lifecycle)). On create/update, slug `fieldId` values are resolved to numeric `internal_id`, `%{field:}` is rewritten to `%{field:}`, and `referencedFieldIds` is auto-populated when applicable. The SDK injects action `referenceId` values and `%{action:}` placeholders for every surface; do not generate them yourself. ### 8 — Handle responses - **Success with `agent_uuid`** → check `disabled_at` and `active` on the write response to verify agent enablement. - **Partial failure (behaviors rejected)** → the SDK raises `AiAgentConfigureError`; use its `.agent_uuid` to recover the existing shell. The MCP response carries `agent_uuid`. Call `update_ai_agent` with the **full required payload**: `uuid`, `repo_uuid` (same pipe UUID used on create), `name`, `instruction`, and complete `behaviors` (full-replace, not patch). Do NOT create a second agent. The create shell is often disabled; update preserves that state — call `toggle_ai_agent_status` after a successful recovery update if you need the agent active. - **Failure without UUID** → validation or API error. Trust the hint text in the enriched error. ### 9 — Verify `get_ai_agent(uuid)` to confirm behaviors and enablement (`disabledAt` null means active). Do not treat `behaviors[].active` as agent enablement; never assume inactive without confirmation. --- ## Knowledge bases (data sources) Knowledge bases are pipe-scoped data sources an agent draws on. Attach one by putting its ID in a behavior's `dataSourceIds` (or the agent-level `data_source_ids`). All knowledge base operations are scoped by the pipe **UUID** (`pipe_uuid`), not the numeric pipe ID — `get_pipe` returns the `uuid`. | Operation | Read-only | Purpose | | ------------ | ----------- | --------- | | `get_ai_knowledge_bases` | Yes | List every item on a pipe (plain texts, documents, data lookups); each has an `id` for `dataSourceIds` and a `type` (`knowledge_base_plain_texts`, `knowledge_base_documents`, or `data_lookups`). | | `get_ai_knowledge_base_plain_text` | Yes | Fetch one plain text with its content. | | `create_ai_knowledge_base_plain_text` | No | Create a plain text (`name`, `content` 1-3500, `description` 1-900 — all required). | | `update_ai_knowledge_base_plain_text` | No | Partial update; pass at least one of name/content/description. | | `delete_ai_knowledge_base_plain_text` | No | **Destructive; review and approve first.** | | `get_ai_knowledge_base_document` | Yes | Fetch one document's metadata (`content` is the stored URL, not text). | | `create_ai_knowledge_base_document` | No | Upload a PDF (`name`, `description` 1-900, `file_path`). `.pdf` + 20 MiB cap; indexing is asynchronous. | | `update_ai_knowledge_base_document` | No | Metadata-only update (name/description); no file replacement. | | `delete_ai_knowledge_base_document` | No | **Destructive; review and approve first.** | | `get_ai_knowledge_base_data_lookup` | Yes | Fetch one data lookup; the payload never includes `conditions` — keep the definition client-side. | | `create_ai_knowledge_base_data_lookup` | No | Create a data lookup (`name`, `description` 1-900, `source_repo_id` numeric pipe ID, `output_fields` 1-30, `conditions` — all required). | | `update_ai_knowledge_base_data_lookup` | No | Full replacement: resend `source_repo_id`/`output_fields`/`conditions` every call; omitted `search_query` clears it; only name/description are partial. | | `delete_ai_knowledge_base_data_lookup` | No | **Destructive; review and approve first.** | | `validate_knowledge_base_access` | Yes | Probe read access before writes. | ### Flow: validate-access → create plain text → attach 1. **Probe access** — `validate_knowledge_base_access(pipe_uuid)`. A green result proves read access only (`read_ai_agents`), never the `manage_ai_agents` entitlement writes need. 2. **Create the source** — `create_ai_knowledge_base_plain_text(pipe_uuid, name, content, description)`. Limits fail fast client-side: `content` 1-3500 chars, `description` 1-900 chars (both required). Keep the returned `id`. 3. **Attach** — add that `id` to a behavior's `dataSourceIds` (or the agent-level `data_source_ids`) when calling `create_ai_agent` / `update_ai_agent`. Validate first with `validate_ai_agent_behaviors(pipe_id, behaviors, data_source_ids=[...])` — unknown IDs surface as warnings. For a **PDF document**, use `create_ai_knowledge_base_document` with `pipe_uuid`, `name`, `description`, and `file_path` at step 2. Only `.pdf` is accepted, with a 20 MiB cap. Indexing is asynchronous, so the document may not be searchable immediately. Keep the returned `id` and attach it. ### Data lookups: create with an AI-filled condition → attach → update (full replacement) A **data lookup** lets the agent search cards in a source pipe by conditions and return selected field values. Same flow as above at step 2, with three rules of its own: 1. **Create** — `create_ai_knowledge_base_data_lookup(pipe_uuid, name, description, source_repo_id, output_fields, conditions)`. `source_repo_id` is the **numeric** ID of the source pipe (a UUID is accepted by the API but the lookup then breaks when the agent runs it). `output_fields` takes 1-30 field IDs (field slugs plus static fields like `id`, `title`, `created_at`). Each condition needs `field` + `operator` (opaque backend string, e.g. `"eq"`, `"contains"`) and is either **static** (string `value` required) or **AI-filled** — the AI asks the user for the value at runtime: ```json [{"field": "customer_email", "operator": "eq", "usingFillWithAi": true, "inputName": "Customer email", "inputType": "text", "inputDescription": "The customer's email address"}] ``` 2. **Attach** — keep the returned `id` and add it to `dataSourceIds`, exactly as for the other kinds. **Also keep the definition you sent**: reads never return `conditions`, so your copy is the only complete record of the lookup. 3. **Update replaces everything** — `update_ai_knowledge_base_data_lookup` requires `source_repo_id`, `output_fields`, and `conditions` on every call (the complete condition set, not a delta), and omitting `search_query` clears it. Only `name`/`description` keep their stored values when omitted. --- ## Token normalization & slug resolution Instructions accept five token aliases — all normalize to canonical `%{field:}`: | Form | Behavior | |------|----------| | `%{}` | Canonical short form. | | `{}` | Bare; auto-prefixed with `%`. | | `{field:}` | Bare-with-prefix; auto-`%`. | | `{field:}` | Bare slug; resolved to numeric when behavior action carries `pipeId`. | | `%{field:}` | Canonical full form. | `%{field:}` is rewritten to `%{field:}` when an action in the behavior supplies `pipeId`. If the Pipefy UI shows plain text instead of chips in token slots, the payload probably still has non-canonical tokens. --- ## Template params / placeholders Per behavior you can pass `template_params` (or `placeholders`) with `str → str` values and use `{{name}}` in any string (instruction, metadata IDs, etc.). Optionally set `instruction_template` instead of `aiBehaviorParams.instruction` — the final instruction is interpolated and written before the API call (by the MCP tools, the CLI, and the SDK's `CreateAiAgentInput` / `UpdateAiAgentInput`). These keys are stripped during validation. ```json { "name": "Classify card", "event_id": "card_created", "instruction_template": "Read {{field_ref}} and classify the card.", "template_params": { "field_ref": "%{field:900000101}" }, "actionParams": { "aiBehaviorParams": { "actionsAttributes": [ { "name": "Fill classification", "actionType": "update_card", "metadata": { "pipeId": "{{pipe}}", "fieldsAttributes": [{ "fieldId": "{{class_field}}", "inputMode": "fill_with_ai", "value": "" }] } } ] } }, "placeholders": { "pipe": "987654321", "class_field": "900000101" } } ``` `template_params` and `placeholders` merge (placeholders wins on conflict). --- ## Naming differences (UI vs API) | Pipefy UI | API / Tool field | |-----------|------------------| | Description (agent creation step 1) | `instruction` (agent-level) | | Instruction / Prompt (per behavior) | `actionParams.aiBehaviorParams.instruction` | | Pipe UUID | `repo_uuid` (from `get_pipe().uuid`, NOT the numeric `id`) | --- ## Success criteria - Agent enablement matches the intended state: `get_ai_agent.disabledAt` is null when active; `behaviors[].active` does not establish agent enablement. - `validate_ai_agent_behaviors` reports no errors before creation. - Agent appears in the Pipefy UI under the pipe's AI settings. ## Failure modes - **`update_ai_agent` is full-replace, not patch.** Read the agent with `get_ai_agent`, change what you need, and send the complete behaviors list back. A behavior you leave out is deleted. You can send the read behaviors back as they are; the ids and `%{action:…}` lines they carry are replaced on save. Update never reactivates a disabled agent; use `toggle_ai_agent_status` for that. - **A rejected update is not rolled back.** Pipefy applies the name and instruction and removes the current behaviors before it saves the new list. When it then rejects the list (`RECORD_NOT_SAVED`), the agent is left with no behaviors, or only part of the new list, and stays enabled. Before `update_ai_agent`, keep the `get_ai_agent` result and run `validate_ai_agent_behaviors`, which checks each `event_id` against `get_automation_events` — warning you when those events cannot be loaded, so an unverified `event_id` never reads as a clean pass — and validates the `human_validation`/`mcp_tool` action metadata. After a failure, call `get_ai_agent` to see what is left, fix the payload, and send the full list again. - **`RECORD_NOT_SAVED` does not name the cause.** `validate_ai_agent_behaviors` now names several of them — an unknown `event_id` (when the pipe's automation events load), and a `human_validation` or `mcp_tool` action with missing metadata — so run it first. The same message still covers payload errors the pre-flight cannot see, so rule those named causes out before you conclude the pipe does not support AI agent behaviors. - **Partial-failure recovery.** If `create_ai_agent` returns a UUID but reports failure, call `update_ai_agent(uuid, repo_uuid, name, instruction, behaviors)` — all five are required. Reuse the create `repo_uuid`; send the full behaviors list. Do NOT create a second agent. Update preserves disabled state; use `toggle_ai_agent_status` to change enablement. - **Cross-pipe `PERMISSION_DENIED`.** Behaviors with `create_connected_card` or cross-pipe `create_card` require the service account to be a member of **both** source and destination pipes. When it is not, the API returns a bare `PERMISSION_DENIED`. Recovery: `get_pipe_members` + `invite_members` on the destination pipe. - **Phase transition rule on `move_card`.** Destination must be reachable from the source phase (`cards_can_be_moved_to_phases`). Both `validate_ai_agent_behaviors` and `create_ai_agent` / `update_ai_agent` enrich this error with `valid_destinations` and a hint that transition rules are editable in the Pipefy UI only. - **Maximum 5 behaviors per agent.** Adding a 6th rejects the whole save. - **Ghost agents.** An agent listed by `get_ai_agents` may return "Agent not found" on `get_ai_agent` — a Pipefy backend artifact, persists across sessions, do not retry. - **GraphQL error hints.** When a dedicated read tool returns permission-denied or not-found, the `error.message` may cite concrete tools (e.g. `"Use 'get_ai_agents' to list agents..."`). Trust the hint; don't improvise alternative flows. - **Validation rejections.** Common issues: invalid `trigger_event`, prompt too long, missing required action config. Read the `errors` field per behavior. ## See also - `pipefy-automations` — traditional automations and AI automations (different from AI agents). - `pipefy-observability` — agent execution logs and credit usage. - [docs/mcp/tools/identifiers.md#ai-agents-and-knowledge-bases](https://github.com/pipefy/ai-toolkit/blob/main/docs/mcp/tools/identifiers.md#ai-agents-and-knowledge-bases) — canonical map of which tool/argument expects slug vs `internal_id` vs uuid vs numeric id (AI agents scope by `repo_uuid` = pipe UUID).