FA©26
24 July 2026·38 min readZapierAI by ZapierAI agentsautomation migrationproduction integrations

Zapier Agents to AI by Zapier: How to Migrate Without Duplicate Actions, Runaway Tasks, or Silent Behavior Changes

Why did the migrated agent create the same customer twice?

Zapier's current migration flow converts a standalone Agent into a Zap containing an AI by Zapier step. It carries over the prompt, connected tools, and trigger. After you review and publish the new Zap, Zapier asks whether to turn off the original Agent. If the original stays published, both can run independently, and Zapier explicitly warns that this can create duplicate actions. The current process is documented in Migrating from Agents to AI by Zapier, published July 15, 2026.

The obvious fix is to remember to click Turn off agent. That is necessary. It is not enough.

You can still create duplicates when the old and new paths overlap for a few seconds, when a webhook retries, when a scheduled trigger fires during the cutover, when someone republishes the original while troubleshooting, or when the target application accepts the same write twice. A cutover checklist reduces operator error. An idempotent business action survives it.

Suppose an Agent receives a webhook after a Stripe checkout and uses tools to find or create a contact, create a project, send an email, and notify Slack. The migrated Zap receives the same checkout event. Your protection cannot be “we usually disable the old one quickly.” The checkout event needs a stable business key, such as the provider event ID or checkout session ID. Before any irreversible action, the workflow claims that key in a state store.

create table automation_event_claims (
  workflow_key text not null,
  event_key text not null,
  status text not null check (status in ('processing', 'completed', 'failed')),
  first_seen_at timestamptz not null default now(),
  completed_at timestamptz,
  output jsonb,
  primary key (workflow_key, event_key)
);

The first run inserts the key. A concurrent run hits the primary-key conflict and exits before sending another email or creating another project. If the first run dies halfway through, the record gives you a place to reconcile instead of guessing from Zap history.

Zapier Tables can be enough for a small workflow, but I prefer Postgres or Supabase when the action has financial, contractual, or customer-facing impact. I need a real unique constraint, explicit state, an audit trail, and controlled recovery. A lookup step followed by a create step is not atomic. Two runs can both see “not found” and both create.

export async function claimEvent(
  db: DatabaseClient,
  workflowKey: string,
  eventKey: string,
): Promise<"claimed" | "already_claimed"> {
  const result = await db.query(
    `insert into automation_event_claims (workflow_key, event_key, status)
     values ($1, $2, 'processing')
     on conflict (workflow_key, event_key) do nothing
     returning event_key`,
    [workflowKey, eventKey],
  );

  return result.rowCount === 1 ? "claimed" : "already_claimed";
}

[DIAGRAM] Draw the trigger splitting into the original Agent and migrated Zap, then converging on one atomic event-claim table before any CRM, email, ticketing, or Slack write. Show the duplicate path being rejected by the unique key.

The uncomfortable point is that duplicate prevention belongs below the migration interface. The interface can help you deactivate the old Agent. It cannot know what “the same business event” means inside your client operation.

What changed when Zapier Agents moved into the Zap editor?

The product boundary changed. Standalone Agents lived at agents.zapier.com. AI by Zapier now places agentic reasoning and tool calling inside a normal Zap step, surrounded by triggers, filters, paths, deterministic actions, and Zap history. That is a better place to build a production workflow because you can separate reasoning from execution. It also changes how you test, bill, observe, and govern the automation.

According to Zapier's migration documentation, each converted Agent becomes a Zap with an AI by Zapier step preconfigured with the original prompt and tools. The trigger is carried over when it has a corresponding Zap trigger. You review the step, preview it, run an end-to-end Zap test, publish it, and then turn off the original Agent.

Several behaviors deserve more attention than the migration screen gives them.

First, task accounting is now central. AI by Zapier uses model tiers with task multipliers. Zapier's model tier pricing documentation, updated August 7, 2026, lists Standard at 1x without tool support, Advanced at 3x with tools, Premium at 5x with tools, and Bring Your Own Key at 1x when an eligible Advanced or Premium model is used. New paid steps default to Premium. The published formula is:

Tasks per run = (1 * model rate) + (successful tool calls * model rate)

A Premium step that invokes two tools uses the model rate once for the step and twice more for successful tool calls. A prompt that occasionally loops through more tools can have variable task usage even when the trigger volume is stable. Zapier pauses a single AI by Zapier run when it reaches 75 tasks and asks for approval before continuing.

Second, the observability surface changed. Tool calls, model tier, input, output, and task usage appear in Zap history. That is useful, but only if your workflow records a business correlation key. Searching by a model-generated summary is not incident response. Searching by order_id, lead_id, ticket_id, or source_event_id is.

Third, governance changed. AI by Zapier supports tool-level approval settings and account-level controls. A tool can be configured to require approval before it runs. Organizations can restrict model choices or tool calling. This means a migration can work in the builder's account and fail after an administrator applies policy, or it can publish under another user's connected account and act with that user's permissions.

Fourth, some standalone Agent patterns do not map directly. Zapier documents that agent-to-agent calls need Sub-Zap triggers and caller updates. On-demand chat-triggered Agents do not currently have a direct equivalent trigger in the Zap editor, though a webhook can replicate the entry point from Slack, a web application, or another chat surface. Knowledge sources available in Agents were listed as not yet supported in AI by Zapier at the time of the July 15 migration guide, with support planned for Q3 2026. That status can change, so I verify the current page before a migration rather than copying a capability matrix into a statement of work.

Fifth, full-Zap tests currently consume tasks in the same way as production runs, according to Zapier's migration guide. Testing is not free just because the run begins in the editor.

The move into the Zap editor gives you more control. It also removes the excuse that the agent is a black box outside the workflow. You can now put deterministic guards around it. You should.

What does a bad migration actually cost?

The cost is not just the extra Zapier tasks. Task waste is visible. Business duplication is not always visible until a customer complains.

Take a lead-routing Agent. It reads an inbound request, researches the account, classifies the opportunity, assigns an owner, creates or updates the CRM record, drafts a response, and alerts the sales channel. After migration, the model chooses a different tool order. It creates the CRM record before checking for an existing contact. The contact search then finds the record it just created and treats it as an established customer. The final classification changes. Every tool returned success.

A bad migration can create five kinds of cost.

The first is duplicate work. Two contacts, two opportunities, two tickets, two email sequences, or two invoices create cleanup that spreads across systems. Deleting the duplicate in one application does not unwind the downstream automation already triggered by it.

The second is lost work. A migrated prompt may produce a slightly different structured output. A downstream filter expects qualified = true, but the new step emits the string "yes" or leaves the field blank after a tool error. The Zap succeeds through the AI step, then skips the revenue action. This is quieter than duplication.

The third is variable task consumption. A tool-enabled step is not a fixed one-task unit. Model tier and successful tool calls determine the count. A broad prompt such as “find everything relevant, decide what to do, and update all systems” gives the model room to call tools repeatedly. The same business request can use a different number of tasks on different runs.

The fourth is permission risk. A migrated workflow may publish under a connection owned by a different person, or an administrator may enable tool calling after the prompt was written under the assumption that tools were unavailable. A read-oriented prompt can become a write-capable agent if tools are attached without a policy boundary.

The fifth is support cost. The team must compare the original Agent activity, the converted Zap history, target application logs, and the customer's report. Without a replayable input and expected outcome, you cannot answer the basic question: did the migration change behavior, or did the source data change?

I measure migration impact with business counters, not only platform status.

{
  "migration_run_id": "[UUID]",
  "source_event_id": "[PROVIDER EVENT ID]",
  "old_path_observed": true,
  "new_path_observed": true,
  "expected_business_actions": [
    "crm.contact.upsert",
    "project.create",
    "email.onboarding.send"
  ],
  "actual_business_actions": [],
  "duplicate_actions_blocked": [],
  "tool_calls": [],
  "task_usage": "[CAPTURE FROM ZAP HISTORY]",
  "final_state": "[PASS, FAIL, NEEDS_REVIEW]"
}

[REAL NUMBER] Insert the actual number of Agents, average and maximum tool calls per sampled run, duplicate writes blocked, failed comparisons, migration test tasks consumed, and manual review time. Mark any missing measurement as unverified.

A migration is complete when the new workflow produces the intended business state under normal input, duplicate input, delayed input, partial failure, and retry. “Published” is an editor state. It is not an operational outcome.

Why does clicking Migrate and running one preview prove almost nothing?

A preview proves that one sample input can produce one plausible output at one point in time. Production asks a different question: will the workflow preserve its business contract across the range of inputs, tools, permissions, retries, and model choices it will encounter?

The migration process carries over prompts and tools, but prompts are not executable specifications. Two systems can receive the same instruction and choose different sequences while both appearing reasonable. A converted Agent may call a search tool before a CRM tool, choose a different field for an identifier, stop after one successful action, or continue calling tools to improve an answer. The behavior can also vary by model tier.

Sample data is often cleaner than production data. A lead has an email, a company domain, a complete form, and a recognized country. Real inputs include aliases, missing domains, forwarded email bodies, HTML fragments, duplicate phone formats, unsupported attachments, and strings that resemble instructions to the model. One happy-path preview does not test any of that.

Tool mocks also hide consequences. In the editor, a preview may show that the AI intends to create an opportunity. An end-to-end test can actually create it. Zapier currently warns that full-Zap tests consume tasks. More importantly, they can perform writes in connected applications. If the test account points to production, your QA data becomes a real customer object.

The other issue is temporal behavior. The original Agent and converted Zap may not execute at exactly the same time. A CRM search at 10:00:01 and another at 10:00:04 can see different state. Comparing only final text output misses action-order differences.

I use a contract for each scenario:

interface MigrationScenario {
  name: string;
  sourcePayload: unknown;
  preconditions: Array<{
    system: string;
    query: string;
    expected: unknown;
  }>;
  allowedActions: string[];
  forbiddenActions: string[];
  expectedFinalState: Record<string, unknown>;
  maxToolCalls?: number;
  requiresHumanReview: boolean;
}

For a duplicate checkout event, the allowed action may be event.claim.read, while customer creation, email sending, and ticket creation are forbidden on the second delivery. For an ambiguous lead, the expected action may be to create a review task and stop, not to guess an owner.

A useful test suite includes malformed input, missing required data, duplicate events, stale events, target API throttling, one tool returning a 500, one tool returning a valid empty result, and an authorization error. The difference between “not found” and “could not search” matters. Agents often treat both as permission to create.

// [WORKFLOW JSON: Zap test workflow that loads a fixed scenario payload, invokes the migrated AI by Zapier step, records each tool call, compares final system state, and emits PASS or FAIL. This proves behavior rather than prompt similarity.]

The preview is useful for prompt iteration. It is not a release gate. The release gate is a recorded comparison against business invariants.

How do you inventory an agent before touching it?

Start by treating the Agent as a small production service. It has an entry point, inputs, dependencies, decisions, side effects, permissions, state, cost, and failure handling. The prompt is only one file in that service.

I create one inventory row per Agent. The minimum fields are owner, trigger, schedule or webhook source, prompt version, model or tier where visible, tools, connected accounts, write actions, downstream systems, expected volume, business criticality, duplicate key, timeout behavior, approval requirements, knowledge sources, sub-agent dependencies, and rollback owner.

Then I classify every tool operation by risk.

Read operations retrieve context. Search CRM, look up an order, read a ticket, fetch availability. They can still expose data or consume rate limits, but they do not usually create an irreversible business event.

Draft operations prepare a result without sending it. Generate an email draft, create a proposed field update, produce a support reply for review.

Write operations change state. Create contact, update opportunity, send email, cancel subscription, schedule meeting, post to a public channel, issue refund.

High-risk write operations create financial, legal, access-control, or customer-communication consequences. Those need deterministic validation, explicit approval, or both.

A simple matrix forces useful conversations:

Capability

Current tool

Read or write

Business key

Can repeat safely

Approval required

Recovery path

Find customer

CRM search

Read

Email plus tenant

Yes

No

Retry with backoff

Create customer

CRM create

Write

Source event ID

Only with idempotency

Usually no

Merge duplicate

Send contract

Email action

High-risk write

Contract version

No

Yes

Revoke and resend

Update ticket

Help desk action

Write

Ticket ID plus transition

Depends on transition

Sometimes

Restore prior state

The inventory must include hidden dependencies. Agent-to-agent calls are one. A parent Agent may call another by name, and the child may be owned by a different user. After migration, Zapier says the child should use a Sub-Zap trigger and the parent callers must point to the new Sub-Zap. Migrating the child without updating its callers creates a valid but unreachable workflow.

Knowledge sources are another hidden dependency. The original Agent may rely on HubSpot, Asana, Zendesk, Zoom, or Jira knowledge. The July migration guide said those sources were not yet supported in AI by Zapier and were planned for Q3 2026. You must check current support on migration day. If the feature is still missing, “the prompt migrated” is irrelevant because the evidence source did not.

Connections matter too. A tool may use a connection created by a former employee or a personal account with broader permissions than the service needs. Migration is the right time to replace personal connections with controlled service identities where the target application supports them.

Finally, capture the old Agent's behavior before changing it. Save representative inputs, tool-call sequences, outputs, and resulting system state. Screenshots are useful, but machine-readable fixtures are better.

The inventory tells you whether you are moving a text classifier, a tool-using workflow, or an unbounded operations bot. Those are not the same migration.

How do you build a replayable migration test rig?

The test rig sits outside the Agent and outside the converted Zap. Its job is to feed known inputs, isolate or label side effects, record the tool path, and compare business state. I usually build it in Next.js or a small Node service backed by Postgres. n8n can orchestrate the scenarios, but I keep the source fixtures and assertions in code when the migration is important.

Each fixture has a source payload and a setup function. The setup creates or resets test records in a sandbox CRM, test spreadsheet, staging database, or dedicated account. It also defines the event key so duplicate delivery can be tested intentionally.

The rig sends the input to the original Agent when that interface allows it, records the result, then sends the same logical input to the converted Zap through its trigger. For live actions that cannot be safely run twice, I compare against a defined expected state rather than allowing both paths to write production.

The critical distinction is between text equivalence and business equivalence. The old Agent may say “Qualified lead assigned to Alex,” while the migrated step says “I assigned this qualified lead to Alex.” Those are equivalent if the CRM owner is correct, the routing reason is stored, and no duplicate opportunity exists. Exact string comparison would fail for the wrong reason.

I compare invariants:

interface BusinessAssertions {
  contactCount: number;
  opportunityCount: number;
  ownerId: string | null;
  emailSentCount: number;
  ticketStatus: string | null;
  duplicateClaimCount: number;
  forbiddenSideEffects: string[];
}

For tool behavior, I record the normalized action rather than provider-specific text:

{
  "action": "crm.contact.upsert",
  "target_key": "tenant_123:email@example.com",
  "input_hash": "[SHA256]",
  "result_id": "[SANDBOX OBJECT ID]",
  "status": "succeeded",
  "attempt": 1,
  "started_at": "[TIMESTAMP]",
  "finished_at": "[TIMESTAMP]"
}

The rig should replay at least three delivery patterns. First, one normal event. Second, the same event twice in quick succession. Third, the same event after the first run has partially completed. The third pattern exposes the hardest recovery problem. A workflow that created the contact but failed before sending the email needs to resume from known state, not start from the prompt again and hope the model notices the existing contact.

For expensive or destructive tools, replace the tool with a test adapter. The adapter validates the proposed request, records it, and returns a realistic response. This lets you test whether the AI tries to issue two refunds without issuing any refund.

You also need model variance tests. Run the same scenario enough times to see whether the tool sequence changes. I do not publish a universal sample count because the right number depends on risk and volume. Record the count you actually run and label anything unmeasured as unverified.

async function assertNoDuplicateWrites(runId: string): Promise<void> {
  const rows = await db.query(
    `select action, target_key, count(*)::int as count
     from automation_action_log
     where run_id = $1 and status = 'succeeded'
     group by action, target_key
     having count(*) > 1`,
    [runId],
  );

  if (rows.rowCount > 0) {
    throw new Error(`Duplicate business writes detected for run ${runId}`);
  }
}

[DIAGRAM] Draw fixture storage, sandbox setup, original Agent, migrated Zap, action adapters, state assertions, and a comparison report. Show that the report evaluates CRM, email, ticket, and database state, not only final model text.

A replay rig turns a migration from a prompt review into a software release. That is the standard I use when a workflow can touch customers or money.

Which decisions belong to AI, and which belong in deterministic Zap steps?

The move into the Zap editor makes this separation easier, and I use it aggressively.

AI should handle ambiguity where a probabilistic answer has business value. Classify the intent of an inbound message. Extract structured fields from inconsistent text. Summarize a call. Propose a response. Rank likely matches. Decide which read-only source to consult when the path genuinely depends on context.

Deterministic steps should enforce policy. Validate required fields. Check whether a customer already exists. Apply exact routing rules. Calculate money. Compare dates. Enforce plan limits. Create an idempotency claim. Decide whether a refund is allowed. Send the approved message. Update the canonical state.

The weak pattern is one AI step with ten tools and a prompt that says, “Do whatever is necessary to onboard the customer.” It is easy to demo because the model can improvise around missing details. It is hard to operate because every action is conditional on hidden reasoning.

The stronger pattern is a deterministic shell:

Trigger
  -> validate source and required fields
  -> claim event key
  -> fetch bounded context
  -> AI classification and structured output
  -> validate output against schema and policy
  -> deterministic paths
  -> explicit writes
  -> record final state

The AI step returns a small contract:

{
  "intent": "new_customer_onboarding",
  "confidence_band": "high",
  "customer_match": "none",
  "requested_plan": "[NORMALIZED PLAN CODE]",
  "needs_human_review": false,
  "reason_codes": [
    "VALID_PAYMENT_EVENT",
    "NO_EXISTING_CUSTOMER_MATCH"
  ]
}

A Filter or code step rejects unknown intent values. A lookup checks the customer. A Path chooses the onboarding route. A CRM action performs an upsert with a stable external ID. An email action sends a template selected by code, not free-form model choice.

This structure reduces tool calls and task variability because the AI does not need to discover and execute every action. It also improves permissions. The AI step can operate with no write tools at all. You can still use tool calling where the model needs to choose among read sources, but writes happen after validation.

There are cases where agentic write tools are appropriate. A support triage Agent may update low-risk tags or assign a queue based on context that is difficult to express in static paths. Even then, I limit the tool schema. The tool accepts ticket_id, an enum of allowed queues, and a reason code. It does not accept arbitrary API paths or free-form updates.

const assignmentSchema = z.object({
  ticketId: z.string().min(1),
  queue: z.enum(["billing", "technical", "onboarding", "human_review"]),
  reasonCode: z.enum([
    "BILLING_QUESTION",
    "PRODUCT_ERROR",
    "SETUP_REQUEST",
    "AMBIGUOUS",
  ]),
});

The test is simple: if the model makes the wrong decision, how much damage can the available tool cause? If the answer is “send anything to anyone and update any record,” the tool is too broad.

[WORKFLOW JSON] This placeholder is intentionally invalid Markdown syntax and should not be used. Replace it with the fenced block below.

// [WORKFLOW JSON: migrated Zap showing source validation, idempotency claim, bounded context retrieval, one AI classification step with structured output, schema validation, deterministic Paths, and explicit write actions. This proves the model cannot directly bypass policy.]

The best migration often uses less agent behavior than the original. That is not a loss of sophistication. It is a gain in control.

How do you stop tool calls from becoming an open checkbook?

Start with the published task formula, then design around the behavior that drives it.

AI by Zapier charges task usage based on the selected model rate and successful tool calls. Standard is 1x but does not support tools. Advanced is 3x and Premium is 5x. New paid steps default to Premium. Bring Your Own Key can use a 1x Zapier task multiplier with eligible Advanced or Premium models, while the external model provider charges separately. Check the current AI by Zapier model tier pricing before budgeting because model availability and product rules can change.

The mistake is to estimate one migrated Agent run as one Zap task. A tool-enabled run includes the AI step and each successful tool call multiplied by the tier rate. A workflow with unpredictable tool loops has unpredictable platform usage.

You control that in four places.

First, reduce the number of tools. Do not attach every application the team uses. Give the step the minimum set required for one bounded job. If the prompt can search CRM, browse the web, read Slack, query a knowledge source, create tickets, send email, and update a spreadsheet, the model has too many ways to satisfy an ambiguous instruction.

Second, separate research from action. One AI step can gather or classify. Deterministic Zap steps can perform the writes. This usually produces a more stable number of tool calls.

Third, add explicit stopping conditions to the prompt and output schema. Tell the model what constitutes enough evidence, what to do when a required fact is missing, and when to return needs_human_review instead of calling another tool.

Fourth, monitor distribution, not averages. An average of two tool calls can hide a tail where one class of request uses ten. Zapier's 75-task pause is a useful last guard, not a budget strategy. A run that pauses during customer onboarding is still an incident even if it prevents further usage.

I keep a cost envelope per workflow:

workflow: inbound_account_research
model_tier: advanced
expected_tool_calls:
  normal: 1
  maximum_without_review: 3
allowed_tools:
  - crm_find_company
  - approved_domain_lookup
fallback: human_review
measurement_source: zap_history
last_verified: "[DATE]"

Then I capture actual task usage from Zap history and compare it with the envelope. I do not invent a monthly dollar figure because Zapier plan pricing, included tasks, overages, and external model charges vary. Use the client's invoice and current plan.

Tool success semantics matter too. The pricing documentation defines a tool call as a successful use of a tool. A tool that returns a technically successful response with found: false may encourage another call. A wrapper should distinguish a valid empty result from an error and give the model an unambiguous stopping signal.

{
  "status": "not_found",
  "search_complete": true,
  "safe_to_create": false,
  "next_action": "request_human_review"
}

That is better than returning an empty array and hoping the prompt interprets it correctly.

[REAL NUMBER] Insert the actual task usage distribution per run, including median, high percentile, maximum, tool-call count, paused runs, test-run consumption, and external model cost when Bring Your Own Key is used. Use “unverified” for any field not captured from billing or history.

Cost control is mostly behavior control. The bill is the delayed signal that your Agent had too much freedom.

How should approvals and write permissions work?

Approval is not one switch for the entire Agent. It is a policy attached to a specific action, under specific conditions, for a specific identity.

Zapier's migration guide says tools run without pausing by default when the migrated Zap is published, and you can enable Require approval before running for selected tools. The tool configuration guide describes per-tool approval and model-tier requirements. Current account restrictions can differ, especially on managed plans, so verify the actual workspace controls instead of assuming the builder's test account matches production.

I divide actions into three approval classes.

No approval is reasonable for tightly scoped, read-only operations and low-risk internal writes that are idempotent. Examples include reading a customer record, retrieving an approved knowledge item, or adding an internal classification tag that can be overwritten safely.

Conditional approval is appropriate when the action is usually safe but becomes sensitive above a threshold or under ambiguity. Updating an opportunity stage may be automatic when a verified payment event exists, but require review when the model inferred payment from an email. Sending a standard scheduling link may be automatic, while sending a negotiated proposal requires review.

Mandatory approval is appropriate for refunds, contract sends, access changes, destructive deletes, external announcements, or any action where the reviewer needs to see the exact payload before execution.

The approval screen must include enough context. “Approve CRM update” is not useful. Show record ID, current value, proposed value, evidence, source event, and whether the action has already been attempted.

{
  "approval_type": "crm_stage_transition",
  "source_event_id": "[ID]",
  "record_id": "[CRM ID]",
  "from_stage": "proposal_sent",
  "to_stage": "closed_won",
  "evidence": {
    "payment_event_id": "[ID]",
    "payment_status": "paid"
  },
  "idempotency_key": "[KEY]",
  "expires_at": "[TIMESTAMP]"
}

Identity needs the same attention. Publishing on behalf of another user can run actions through that user's connected accounts. Zapier warns administrators to confirm before using another person's connections because live triggers and actions occur as that user. I prefer dedicated service connections with the smallest practical permissions. Personal Gmail, personal Slack, and owner-level CRM tokens make audit and offboarding harder.

Approval does not fix a broad tool. A reviewer may approve a tool name without seeing that the model supplied an unexpected recipient or record ID. Wrap sensitive actions in your own API or serverless function that validates tenant, allowed fields, value ranges, and idempotency before calling the target system.

if (request.tenantId !== authenticatedTenantId) {
  throw new Error("TENANT_MISMATCH");
}

if (!allowedTransitions[currentStage]?.includes(request.nextStage)) {
  throw new Error("INVALID_STAGE_TRANSITION");
}

await claimAction(request.idempotencyKey);
await crm.updateOpportunity(request.recordId, { stage: request.nextStage });

[DIAGRAM] Draw AI proposing an action, a policy service validating tenant and allowed transition, an approval step for high-risk changes, an idempotency claim, then the target API. Show that approval cannot bypass server-side policy.

Human approval is a safety boundary only when the human sees the real consequence and the backend still enforces the rules.

What do you do with agent-to-agent calls, chat triggers, and knowledge sources?

These are the migrations that reveal whether the original system was documented.

For agent-to-agent calls, Zapier's guidance is to migrate the called Agent, change its trigger to Sub-Zap by Zapier, and update every parent Agent or Zap to point to the new Sub-Zap. That creates a dependency graph. Build the graph before migrating anything.

I migrate leaf agents first. A leaf does not call another Agent. Once its Sub-Zap contract is tested, I update one parent and verify the input and output mapping. Then I move upward. Migrating a parent before its children produces confusing mixed behavior because some calls still target standalone Agents and others target Zaps.

Sub-Zaps also force a useful contract. Define required inputs and outputs. Do not pass one giant untyped object just because the original Agent accepted a conversational message.

{
  "input": {
    "tenant_id": "string",
    "ticket_id": "string",
    "customer_message": "string",
    "allowed_actions": ["classify", "draft_reply"]
  },
  "output": {
    "category": "billing | technical | onboarding | unknown",
    "draft_reply": "string",
    "requires_review": "boolean",
    "reason_codes": ["string"]
  }
}

On-demand chat Agents do not currently have a direct equivalent trigger in the Zap editor, according to the migration guide. A webhook can receive messages from Slack, a web interface, or another chat surface. But a webhook is not a chat product. You must add user identity, tenant resolution, conversation state, authorization, rate limiting, and response delivery.

A Next.js endpoint can accept the message, verify the session, store the conversation turn in Postgres, generate a stable request ID, and call the Zap webhook. The Zap returns or posts the result through a callback. Without that shell, anyone who discovers the webhook may be able to invoke the workflow with arbitrary text.

Knowledge sources require a current capability check. At the July 15 publication date, Zapier listed Agent knowledge sources as not yet supported in AI by Zapier, with support planned in Q3 2026. Because this article is dated August 8, 2026, that is still a moving status. Do not claim the feature exists until the target workspace shows it or the current documentation confirms it.

When a source is unavailable, I use one of three temporary paths. For small controlled content, retrieve it through a deterministic app step and pass the relevant fields to the AI. For larger content, expose a scoped retrieval endpoint backed by Supabase pgvector, Postgres full-text search, or the client's existing search service. For high-risk answers, return source citations and require review rather than pretending the migration preserved the old behavior.

The worst workaround is pasting the entire knowledge base into the prompt. It creates context cost, stale content, and no source-level access control.

// [WORKFLOW JSON: parent Zap calling a migrated Sub-Zap with typed inputs and outputs, plus a webhook-backed chat entry point that verifies identity and stores conversation state before invoking AI by Zapier. This proves the unsupported standalone patterns have explicit replacements.]

Migration exposes architecture that the conversational interface kept hidden. That is useful, provided you do not rush past it.

How do you cut over without running the old agent and new Zap together?

Use a cutover sequence that assumes someone will click the wrong thing and a trigger will arrive at the worst moment.

First, freeze changes to the original Agent. Record its publishing status, prompt, tools, connections, schedule, webhook URL, and owner. Export or capture representative activity. Any change after the baseline invalidates the comparison.

Second, migrate the Agent but keep the converted Zap unpublished. Replace unsafe production connections with sandbox connections where possible. Add idempotency, schema validation, correlation IDs, and explicit error paths before testing.

Third, run the replay suite. Record final system state, task usage, tool calls, and failures. Fix the workflow, not just the prompt. Repeat until the business assertions pass.

Fourth, choose the trigger strategy. A scheduled Agent can be paused before its next window, then the Zap can be published. A webhook Agent needs a routing layer or a brief controlled handoff. If you own the incoming endpoint, route events to an inbox first and change the consumer from old to new. If the provider points directly to Zapier, update the endpoint only after the new Zap is ready and retain the previous configuration for rollback.

Fifth, create the cutover guard. The event claim table should be live before either path changes. This protects the overlap window.

Sixth, turn off the original Agent. Verify the UI states that it is not published. Do not rely on the migration dialog alone. Capture evidence.

Seventh, publish the new Zap. Trigger one controlled canary event and verify the target systems. Then monitor the next real runs with a narrower alert threshold.

Eighth, keep rollback available, but understand what rollback means. Republishing the original Agent after the new Zap has performed writes can create duplicates unless both use the same claim store. Rollback is a routing decision plus state reconciliation, not just a button.

A cutover record should look like this:

agent: customer_onboarding
change_window: "[TIMESTAMP RANGE]"
old_agent_unpublished_at: "[TIMESTAMP]"
old_agent_status_verified_by: "[NAME OR ROLE]"
new_zap_published_at: "[TIMESTAMP]"
first_canary_event_id: "[ID]"
canary_result: "[PASS OR FAIL]"
idempotency_store: automation_event_claims
rollback_owner: "[ROLE]"
open_incidents: []

For scheduled workflows, account for timezone and daylight-saving behavior. For polling triggers, understand the cursor or last-seen state. Turning off a poller and enabling another can either miss the gap or reread prior records. A shared last-seen checkpoint is safer than two independent “new item” assumptions.

For instant triggers, watch delivery retries. A provider may retry because the old endpoint timed out during shutdown. The same event can reach the new endpoint later. Again, the event ID is your protection.

The click order matters. The shared state matters more.

What breaks under retries, parallel runs, and partial failures?

The workflow breaks where its mental model assumes one request, one run, one uninterrupted sequence.

Retries create duplicate intent. A webhook sender retries after a network timeout even though Zapier accepted the request. Zapier or a connected app retries a failed action. An operator replays a run from history. The source event is the same, but the execution ID is different. Deduplicate on the business event, not the Zap run.

Parallel runs create races. Two lead messages from the same contact arrive close together. Both search the CRM before either update is visible. Both ask the model to decide whether a new opportunity is needed. Both create one. A table lookup does not serialize the decision.

Use an atomic lock or unique constraint keyed by the business object and transition. For example, tenant_id + customer_id + onboarding_version can ensure onboarding runs once, while allowing a later re-onboarding version intentionally.

Partial failure creates ambiguous state. The AI calls a CRM tool successfully, then the email tool times out. Did the email send? A timeout is not proof of failure. Repeating the entire Agent may duplicate the CRM write and email. Recovery must inspect target state.

I log each side effect before and after execution:

create table automation_actions (
  action_id uuid primary key,
  event_key text not null,
  action_type text not null,
  target_key text not null,
  idempotency_key text not null unique,
  request jsonb not null,
  status text not null check (status in (
    'planned', 'started', 'succeeded', 'failed', 'unknown'
  )),
  provider_result jsonb,
  created_at timestamptz not null default now(),
  updated_at timestamptz not null default now()
);

unknown is a real state. It means the request may have succeeded but the client did not receive a definitive response. An automatic retry should first query the provider using the idempotency key or target business key. If the provider supports idempotency headers, use them. If it does not, build a read-before-retry reconciliation path.

Model output introduces another race. Two runs can produce different classifications for the same changing record. Store the source version or timestamp used for the decision. Before writing, verify the record has not changed. That is optimistic concurrency.

if (currentRecord.updatedAt !== decision.sourceRecordUpdatedAt) {
  return {
    status: "needs_redecision",
    reason: "SOURCE_CHANGED_AFTER_AI_DECISION",
  };
}

Under load, rate limits move failures downstream. The Agent can reason correctly and still fail because the CRM, Slack, email provider, or custom API throttles. A backoff inside a long AI run can increase task usage or cross execution limits. I prefer to emit a durable action job and let a worker execute it with provider-specific retry policy.

The action job can run in n8n, a Supabase Edge Function, or a Node worker. The key is that the model decides once, the validated decision is stored, and execution retries do not ask the model to decide again.

[DIAGRAM] Draw AI decision storage separated from action execution. Show duplicate events converging on one event claim, validated actions written to an outbox, workers retrying provider calls, and reconciliation handling unknown outcomes.

A production migration succeeds when retries repeat safe work, not reasoning.

When should you keep AI by Zapier, and when should you move the work elsewhere?

I keep AI by Zapier when the workflow benefits from being close to Zapier's app ecosystem, the trigger and actions are already well supported, the reasoning is bounded, tool permissions can be narrowed, and the business can tolerate Zapier's operational model. It is a good fit for internal classification, drafting, enrichment, triage, and controlled workflows where Zap history gives the team useful visibility.

I move the work behind custom code when state and correctness dominate convenience.

A long-running process with callbacks, approvals, and retries often needs a database state machine. A high-volume webhook receiver needs fast acknowledgement and a durable queue. A multi-tenant SaaS product needs tenant-aware authorization, versioned schemas, secrets management, and per-customer limits. A financial workflow needs atomic idempotency and auditable transitions. A voice agent tool endpoint needs predictable latency and a response contract that does not depend on a visual workflow waking up in time.

Sometimes the right answer is hybrid. Zapier remains the orchestration and operator interface. A Next.js or Node service handles event claims, policy, database transactions, and provider adapters. The AI by Zapier step handles classification. Zap actions handle low-risk notifications.

External event
  -> custom ingress API
  -> verify signature
  -> persist event and claim key
  -> Zapier webhook
  -> AI classification
  -> custom policy API
  -> durable action outbox
  -> workers
  -> Zapier notification and review

I would avoid an all-in-one AI by Zapier step for subscription cancellation, refunds, account permissions, or inventory allocation. Those jobs have hard invariants. The model can interpret a request, but code should decide whether the transition is allowed and perform it transactionally.

I would also avoid migrating a standalone chat Agent to a public Catch Hook URL without an authenticated application in front. The webhook is transport, not user security or conversation state.

The choice is not “no-code versus code.” It is where you want the failure boundary. Put visual steps where an operator benefits from seeing and changing the flow. Put state, authorization, idempotency, and concurrency where the database and code can enforce them.

A practical decision table helps:

Requirement

AI by Zapier centered

Hybrid

Custom service centered

Low-volume app orchestration

Good fit

Good fit

Often unnecessary

Ambiguous text classification

Good fit

Good fit

Good fit

Atomic financial transition

Poor fit alone

Strong fit

Strong fit

Public multi-tenant webhook

Weak without front end

Strong fit

Strong fit

Operator-editable routing

Strong fit

Strong fit

Requires admin UI

Deep replay and reconciliation

Limited alone

Strong fit

Strong fit

Variable tool exploration

Supported, watch cost

Controlled

Fully controlled

The migration is a chance to stop pretending one platform should own every layer.

What does the safer architecture cost you?

It costs engineering time, another state store, more explicit contracts, and a release process that is slower than clicking Migrate.

The event-claim table needs retention rules. The action log contains customer identifiers and request bodies that may need redaction. A custom policy API needs deployment, monitoring, secrets, and on-call ownership. Sandbox accounts need maintenance. Test fixtures drift when target applications change fields or permissions.

The deterministic shell also reduces improvisation. That is intentional, but it can make the workflow less flexible for rare requests. You must decide whether a rare ambiguous case should go to human review or justify another supported path. The original Agent may have attempted an answer. The safer system may stop.

Approvals create queue time. A refund that waits for review is slower than an autonomous tool call. The organization needs reviewers, service expectations, and escalation. An approval that nobody monitors is just a different failure.

Task optimization has tradeoffs too. Moving from Premium to Advanced can reduce the Zapier task multiplier, but the lower tier may perform differently on the actual workload. Bring Your Own Key changes the billing split and adds responsibility for provider keys, quotas, and external charges. Do not select a tier from a price table alone. Test the same fixtures.

A custom worker can lower visual platform task usage, but it shifts cost to hosting, development, logs, and support. A Postgres-backed inbox is inexpensive until it becomes business critical, then backups, migrations, retention, and access controls matter.

The right comparison is total operating cost:

platform_cost:
  zapier_tasks: "[ACTUAL]"
  external_model_usage: "[ACTUAL OR UNVERIFIED]"
engineering_cost:
  migration_build: "[ACTUAL HOURS]"
  test_rig: "[ACTUAL HOURS]"
operations_cost:
  monthly_review: "[ACTUAL HOURS]"
  incident_response: "[ACTUAL HOURS]"
risk_cost:
  duplicate_actions: "[ACTUAL INCIDENT DATA OR UNVERIFIED]"
  missed_actions: "[ACTUAL INCIDENT DATA OR UNVERIFIED]"

[REAL NUMBER] Insert the actual before-and-after platform tasks, model charges, engineering hours, incident count, review queue time, and manual cleanup. Do not convert an estimate into a claimed saving.

The safer design is not automatically cheaper. It is easier to reason about, test, and recover. For production work, that usually matters more than minimizing the number of boxes in the editor.

What would I do differently before migrating the next agent?

I would start with the event and action model, not the prompt.

For each Agent, I would write down the source event, stable event key, allowed decisions, allowed actions, irreversible actions, expected final state, and recovery owner. That takes less time than debugging a duplicated onboarding sequence.

I would create the replay fixtures before clicking Migrate. Capturing the original behavior after the original has been edited is too late. The fixtures should include the ugly cases: missing email, duplicate checkout, stale CRM data, target API timeout, ambiguous customer match, and a message containing instructions that should not control tools.

I would split broad tools immediately. A generic “update CRM record” tool becomes specific operations such as set_lead_category, assign_owner, or request_human_review, each with enum inputs and tenant checks.

I would make task usage a release metric. Every test report would include model tier, tool calls, and tasks consumed. A migration that preserves the final output but triples tool use needs review.

I would establish one owner for old-Agent shutdown. “Someone will turn it off” is not a control. The cutover record needs a named role and timestamp evidence.

I would not bulk-migrate every published Agent just because Zapier offers a bulk option. Bulk conversion is useful for creating drafts. It is not a reason to publish many untested workflows in one window. Migrate by risk and dependency order.

I would add a kill switch outside the AI step. The switch can block writes while allowing reads and classification, or route all actions to review. That gives operations a safer response than unpublishing the entire Zap during an incident.

create table automation_controls (
  workflow_key text primary key,
  mode text not null check (mode in ('active', 'read_only', 'review_only', 'disabled')),
  reason text,
  changed_by text not null,
  changed_at timestamptz not null default now()
);

I would also pin the article's source facts to a verification date. Zapier's migration timeline, supported features, model list, pricing rules, and enterprise restrictions are active product details. The official pages are the source of truth on the day of release, not an old screenshot or this article.

Most migration pain comes from discovering the original Agent had no contract. Fix that before asking whether the converted prompt looks right.

What is still hard after the migration passes every test?

The hard part is not moving the Agent. It is deciding how much autonomy the business can tolerate when the model encounters a case you did not put in the fixture set.

Structured outputs reduce ambiguity. Narrow tools reduce damage. Idempotency prevents repeated side effects. Approvals protect sensitive actions. Replay tests catch known failure modes. None of those guarantees that a future input will be interpreted the same way, especially after a model, tool, source record, or connected application changes.

That is why I keep one operational loop after launch. Sample real runs. Compare task and tool-call distributions. Review unknown outcomes. Add failed cases to the fixture set. Tighten tools when the model discovers an unsafe path. Remove permissions that are not used. Reverify after model-tier changes, app action updates, or administrator policy changes.

The open loop from the duplicate customer incident closes here. Turning off the old Agent prevents one obvious duplicate path. The atomic event claim prevents the same business event from executing twice across old, new, retried, or replayed runs. The action log tells you whether the CRM write occurred before the email failed. The test rig proves the migrated workflow preserves the business state, not merely the wording.

You can make the migration controlled. You cannot make an agentic workflow deterministic by calling it migrated.

I am happy to look at a migration map or one failing run, but the part most teams still get wrong is defining the business action that must happen exactly once before they give an AI step permission to do it.