mcpField
A
automation-architecture

automation-architecture

github.com/getnexor-aiMarketing & growth365 installsUpdated 4w ago
Repository

About automation-architecture

automation-architecture is a skill for turning customer use cases into concrete Nexor configurations. It organizes requirements around explicit data fields, workflow statuses, agent boundaries, and platform primitives, with configuration preferred over code.

Key features

  1. 01Map information to fields and metadata
  2. 02Model state changes as workflow statuses
  3. 03Separate agent modes at transfer boundaries
  4. 04Choose among Nexor configuration primitives
  5. 05Prefer configuration over custom code

Use cases

  1. 01Designing a lead intake and qualification workflow
  2. 02Planning handoffs between distinct agent intents
  3. 03Selecting primitives for reminders and integrations
  4. 04Checking whether workflow gates can use required data

MCP support

The skill refers to Nexor MCP tools, including `create_workflow`, `update_workflow_structure`, and `review_agent_system_plan`, and describes their configuration contracts.

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/getnexor-ai/skills --skill automation-architecture

After installing, type/automation-architecturein Claude Code to confirm it loaded.

Bundled files

References and scripts installed along with SKILL.md.

references/
booking-agent.md
configuration-reference.md
mcp-tool-surface.md
recipes.md

Details

From the project README

Automation architecture

Goal

Turn a customer use case into an exact, minimal Nexor configuration: named primitives with concrete field values, connected end to end. The deliverable is a configuration, not a recommendation.

The three laws of decomposition

Apply these before choosing any primitive. They are not style preferences: each names the only shape the platform can gate, trigger, template, or verify. A design that breaks one is unreliable even when it demos correctly, because the platform cannot see the part that lives in prose.

Law 1 — every piece of information the system needs is a variable

If the build depends on knowing something, it exists as a named workflow field or as lead metadata. Never as prose in a prompt, never as "the agent will remember it from the conversation."

  • The agent must obtain it → workflow field, required: true, with extraction_hints.
  • An external tool (a CRM, a form, the customer's own backend) supplies lead identity/contact information → write first_name, last_name, email, and phone through the standard lead create/update contract. Never hide those values in metadata; metadata does not change Nexor's contact destinations.
  • An external tool supplies other contextual information → put it directly in the lead's metadata JSON object. Keys may hold strings, numbers, booleans, null, arrays, or nested objects; the complete metadata object is injected into the next runtime agent prompt, so the agent can access every parameter and value without one field per key.
  • The agent must ask for, validate, normalize, or gate on externally supplied information → add a workflow field with metadata_key (and intake_value_map when needed) so metadata pre-fills the field instead of being asked. metadata_key, options, extraction_hints, and validation can only be written in the initial create_workflow call — update_workflow_structure accepts just key, label, type, required, sort_order. Finish the variable ledger before creating the agent.
  • A tool produces it → llm_response_fields keeps it in the turn; a post-tool hook writes it to metadata when a later step needs it.
  • Gates read fields only. required_field_keys, requires_all_fields, and transition_rules cannot see metadata.
  • Deterministic senders read metadata only. Status-automation args_template, webhook payload_template, and HTTP tool templates address {{lead.<path>}} / {{lead.metadata.<key>}} and cannot read collected field values. Anything an automation must transmit must be mirrored into metadata (post-tool hook or patch_metadata) before the lead can reach the triggering status. The full reader/store matrix is in references/configuration-reference.md.

For every fact the design uses, name its key and its store. A fact that lives only in conversation history cannot gate a status, route a lead, fill a request body, or filter a job.

Law 2 — every state change inside one agent is a status

If the agent's behavior, permissions, or obligations change at some moment, that moment is a status in that agent's pipeline. Never a "phase" described in the prompt, never a boolean parked in metadata.

Statuses are the only handle the rest of the platform can grab. A stage that is not a status is invisible to workflow_tools.available_in_statuses, status_automations.on_status_key, webhook filters on to_status.key, timeout_config, transition_rules / required_field_keys, background-job workflow_filters.status_keys, recontact trigger_status_key, and assignment_config.

Give each stage a key, a label, an entry_hint stating its entry criterion in terms of Law 1 variables, and an explicit gate. When field values fully determine the move, add transition_rules.rule_groups with auto_evaluate: true so the platform advances the lead instead of depending on the agent calling set_lead_status.

Express every gate as required_field_keys (or requires_all_fields) plus transition_rules. variable_refs is not writable through MCP — it is a dashboard display hint, so a design that relies on it has no gate at all. review_agent_system_plan enforces this law for you: any non-initial status with neither entry_hint nor transition_rules comes back as a clarification question.

When reordering an existing pipeline, preserve the semantic core exactly: Lead created (core_new), Contacted (core_contacted), and Engaged (core_engaged) stay first through third. Call get_workflow, pass every remaining persisted status key exactly once to reorder_workflow_statuses, then call get_workflow again and compare the complete order before reporting success. Never reorder by sending raw sort_order values, include a synthetic Discarded column, or infer core identity from editable labels. Read references/configuration-reference.md for grouping behavior and references/mcp-tool-surface.md for the write contract.

Law 3 — every switch of agent identity happens at a boundary status that transfers

Changing tone, persona, goal, prompt content, tool set, channel mix, or contact intensity means a different agent. The switch is never made inside a prompt. It is made by a boundary status the lead exits through:

  • Terminal handoff (default): is_terminal: true plus transfer_config: { "target_workflow_id": "<real id>" }. This is the only shape that auto-fires a transfer. Every agent path checks is_terminal before reading transfer_config, so a non-terminal status with a transfer_config never fires — the config saves cleanly and silently does nothing.
  • Never name a boundary status future_*, contact_later, or colder. Those keys are treated as soft terminals and are excluded from the terminal side-effect path before transfer_config is read, so the transfer is skipped even on a correctly terminal status. This is the most silent failure in the whole surface.
  • Pause boundary: pause_bot: true when the handoff is executed out of band — a background job's workflow_transfer / force_transfer action, the one-off transfer API, or a human. A pause boundary must name its executor or the lead sits silent forever. It is safe to build on: pause_bot is a status flag and does not set workflow_runs.is_paused, so a job with the standard exclusions.skip_paused: true still picks these leads up.

Either shape ends the source agent's ownership at the boundary: exactly one agent is live per lead. The target always starts at its own initial status, its cadence starts over, and the source's collected fields travel as a read-only transfer-chain snapshot.

Forbidden shapes: a prompt that says "once qualified, switch to a closing tone"; a tool whose purpose is to change the agent's persona; two agents live on the same lead; a transfer aimed at a specific status inside the target.

Law 2 or Law 3?

Same goal, same persona, same tool set, same cadence — only more known about the lead → status. Different goal, prompt, tools, channels, or contact intensity → new mode behind a boundary status. The ~80% test runs only within one intent: two candidate modes that share the same intent and register, and would share ~80% of prompt, tools, and schedule, collapse into one mode with more statuses. It never merges two different intents.

Default shape: split by intent, 2–3 modes

What the operator sees and names is the agent (the master workflow). What you build inside it are modes (workflows): one per intent. The default is to split, not to write one broad prompt. Unless the operator says explicitly and unmistakably that they want exactly one mode ("I absolutely want this to be one single mode"), decompose:

  • A new mode whenever the intent changes — what the conversation is trying to get the lead to do: sell, follow up, offer a discount, book, collect a payment, retain.
  • A new mode whenever the register changes — tone, intensity, cadence, persona, or channel mix, even inside "the same sale": a warm informative opener, an insistent reminder, and a single concession are three registers.
  • 2–3 modes for a typical ask. "Sell tickets on our ecommerce and follow up with a discount" is two or three modes (sell → follow up unpaid → offer the discount), not one and not five. Recipe 25 in references/recipes.md works it end to end.
  • One mode only on explicit instruction. Quote the operator's words in plan.decomposition as single_mode_explicitly_requested: true plus operator_instruction; review_agent_system_plan blocks a one-mode plan without them.
  • Never 5 or more. More than four modes fragments one conversation (every hop resets cadence and re-triggers first contact); the review blocks it unless large_system_explicitly_requested carries the operator's words. Merge modes that share both intent and register.
  • Every mode is reachable. The first listed mode is the entry point (or mark others entry_point: true); every other mode is the target of some mode's terminal transfer, or the review asks how leads reach it.

describe_agent_configuration returns this contract as mode_split; state each mode's intent and register in the plan, and read the review's decomposition summary back to the operator.

The primitives

Everything a customer asks for lands on one or more of these:

#PrimitiveTriggerCode?Scope
1Agent config (statuses + intake fields)Lead's message, mid-conversationNoOne lead, in-turn
2Workflow HTTP/MCP toolAgent needs external information or action during its turnNoOne lead, synchronous
3Rules (reminders / host notifications / recontact)Meeting lifecycle, stalenessNoOne lead
4Outbound webhookPlatform event (status change, meeting lifecycle)NoNotify an external API (CRM, ERP, own backend), every occurrence
5Status automationLead reaches a specific statusNoCall one HTTP/MCP tool, exactly once per lead
6Background jobcron or tag eventNoFiltered cohort, declarative steps
7Cloud functionA lead.* / workflow.* / meeting.* eventJSOne lead per event
8Scheduled functionCron + timezoneJSCohort from a lookup query
9Workflow transferTerminal status with transfer_configNoOne lead, agent-to-agent handoff
10Intake API / inbound webhookAn external tool (CRM, form, Zapier, own backend) pushes a lead inNoLead creation + enrollment
11Cadence config (contact blocks + day config)Outreach scheduling: windows, per-channel intensity, hot contactNoA workflow's entire outbound initiative
12Knowledge-base assignmentAgent needs selected account-owned knowledge at runtimeNoExact per-agent subset, ordered by retrieval priority
13Pre-execution hook (tool with mode: "on_entry")Lead enters the workflow, before the first agent messageNoOne lead, deterministic, cannot be stage-gated
14Pre-processorInbound lead arrives, before any agent owns itNoOrdered condition rules choosing the entry workflow
15Stage timeout (timeout_config)Time elapsed since the lead entered a status ("no reply for 48h")NoOne lead; moves it to another status of the same agent — the platform's only delay primitive. Aimed at a terminal status with transfer_config, it is a delayed handoff

Decision ladder — config beats code

Config is operator-visible, editable in the UI, and cannot crash; code is invisible to operators and yours to maintain forever. Walk the ladder top-down and stop at the first rung that expresses the requirement.

  1. Can native conversation config express it? A status, intake field, prompt rule, or deterministic branch → agent config. "Ask for X and branch on it" is statuses + fields, not code. Laws 1 and 2 have usually already placed most of this rung.
  2. Is it a wait? "If they don't reply in N hours…", "after 3 days move them to…", "hand off to the follow-up agent after 48h" → timeout_config on the waiting status (set_status_timeout_rule, or inline in create_workflow / update_workflow_status), pointing at the next status — or at a terminal status with transfer_config for a delayed handoff. Chain statuses for multi-step waits; the clock is time since entering the status and a reply does not reset it. This is the dashboard's "If the lead does not reply → After N hrs → Move to" control. A scheduled function, background job, cloud function or recontact on_exhausted is never the answer to a per-lead wait (recipe 23).
  3. Is it a standard lifecycle nudge? Booking confirmations, pre-meeting reminders, host notifications, stale-lead recontact → rules. Configure and stop.
  4. Can a state change define exactly when it should happen? Prefer deterministic execution over asking the model to choose a tool. Status change → filtered outbound webhook when the receiver can act from the event, or status automation when Nexor must call a configured endpoint once with a custom body. Field/variable or other lead event → trigger cloud function (information.collected, information.updated, lead.updated, or the narrowest supported event) guarded to the relevant key/change.
  5. Must the result exist before the agent's first message? → pre-execution hook: set_workflow_tool_execution({ mode: "on_entry" }). The platform runs the tool the moment the lead enters the workflow, before the agent speaks, with no model decision involved. This is the right shape for enrichment, eligibility lookups, and account context — and for a transfer target that needs data ready on arrival. It cannot be stage-gated (the two modes are mutually exclusive), so use it only when "on entry" really is the moment.
  6. Does the agent genuinely need an external result before it can continue the active turn, with no state event that can run it first? Expose the client endpoint as a workflow HTTP/MCP tool in mode: "agent_decides". This is the least reliable rung because the model must select and call it. Keep client-specific logic in the client's system, make the contract narrow, and gate it with available_in_statuses when valid only after a stage such as qualified.
  7. Is it "filter leads → condition → action"? → background job with declarative steps and actions. Always set max_leads_per_cycle and cooldown_minutes; dry-run anything that messages humans.
  8. Does asynchronous work need custom logic or an external API the job vocabulary can't express? Trigger grammar routes it: "when a lead …" → cloud function (event, one lead). "every morning / Monday …" → scheduled function (cron, cohort). A "when" that tolerates hours of latency across many leads → prefer the scheduled sweep (its dry run shows the whole cohort first). A "when" that is just elapsed time in a stage is rung 2, not this one.
  9. Does the lead need a fundamentally different conversation or outreach intensity? Goal, persona, cadence, channel mix, or tool set changes → workflow transfer from a boundary status (Law 3). A qualification agent can fan out through terminal statuses such as qualified_now and qualified_later, each with its own transfer_config; the targets own the high-intensity sales or low-frequency nurture behavior. A plain "nurture" ask defaults to its own low-intensity, email-only workflow that reaches out roughly once every 2-4 weeks — never the sales cadence slowed down or the same agent on more channels. If both targets share the same intent and register and would share 80% of their prompt, tools, and schedule, keep one mode with more statuses — but never collapse two different intents into one prompt: the default shape is 2–3 modes (see "Default shape" above).

The integration compass

When the requirement is integration — moving data between Nexor and an external tool (a CRM, an ERP, the customer's own backend) — direction and cadence pick the primitive. Functions are the fully flexible path: editable JavaScript with axios available, so any external API is reachable in both directions.

Canonical Nexor API origins

Use these exact API origins when a configuration, example, or customer integration must call Nexor directly:

EnvironmentAPI origin
Staginghttps://api-staging.getnexor.ai
Productionhttps://api.getnexor.ai

Append the documented route to the origin, for example /api/public/leads. Do not substitute a dashboard origin, an MCP origin, staging-api.getnexor.ai, or a legacy onrender.com backend. Keep the credential and API origin in the same environment, and resolve the target environment before any side-effecting call instead of silently choosing production.

These origins are for integrations that genuinely need to call Nexor. They are not the default data path inside Nexor Functions. Runtime-native access takes precedence:

  • In an event Cloud Function, read the triggering lead from ctx.lead or lead; never refetch it from /api/public/leads.
  • In a Scheduled Function, translate get/list/search/filter-leads requirements into lookup_config, preview that cohort, and consume the resolved ctx.leads array in code. Do not query /api/public/leads for the same cohort. Leads have no workflow_id column: current agent/workflow scope is the pair workflow_runs.workflow_id = <id> and workflow_runs.is_active = true in the relationship-aware query.
  • Use updateLead / updateMetadata and effects.* for supported Nexor mutations. Reserve direct Nexor API calls for a genuinely unsupported atomic operation, such as create/upsert + workflow enrollment + first contact when the new id is required in the same run—not for redundant reads.
Resolve sync direction before choosing a surface

Treat “sync this information” as incomplete until the direction is explicit. Determine whether the data is moving into Nexor or out of Nexor before recommending or configuring anything. If the request does not make the direction clear, present those two mutually exclusive choices and wait for the answer; do not guess from the word “sync.”

For data moving into Nexor, classify every incoming key before mapping it:

  • Put lead identity and delivery coordinates in the standard lead columns: first_name, last_name, email, and phone. Update them through the normal lead create/update contract, never through metadata. Metadata does not change the address or number Nexor uses to contact the lead.
  • Put every other reusable customer fact the agent should know in lead.metadata. Create/upsert with a metadata object or shallow-merge it with PATCH/PUT /api/public/leads/:id; use PATCH /api/public/leads/metadata for bulk merges. The complete metadata object is loaded into the next agent execution, so a value written now is available on the next turn/run without copying it into prompt prose.
  • Add a workflow field with metadata_key only when the same value must pre-fill an intake question or participate in a field-only status gate. Metadata remains the source of inbound context; the field is the structural bridge.

For data moving out of Nexor, choose the outbound trigger from the table below. Metadata may be the source value included in a payload, but writing metadata is not an outbound sync mechanism and sends nothing by itself.

DirectionCadenceUseWhy
Nexor → externalStatus changeOutbound webhook or status automationPlatform state fires it deterministically; no model tool-selection decision
Nexor → externalField/variable or lead eventCloud functionThe event fires deterministically; custom code can inspect the changed key and call any endpoint
Nexor ↔ externalDuring the agent's turn, no usable state triggerWorkflow HTTP/MCP toolUse only when the agent needs the response immediately; the client's endpoint remains the source of truth, but model-selected invocation is less reliable
Nexor → externalBatchScheduled function sweepCohort in ctx.leads[], push out per lead or aggregated
External → NexorPush (they call you)Public leads API / inbound hookUpsert + metadata + enrollment in one request, no code
External → NexorPull (Nexor calls them)Scheduled functionQuery the entire leads object with Supabase-style chained filters, axios the external API, then create/edit leads with custom metadata via effects or the public API
External → NexorThe outcome happened elsewhere (paid, signed, activated)Conversion event: POST /api/public/conversions with a lead identifier (+ amount, currency, description, metadata)Deterministic; Nexor moves the lead to the agent/status configured on the type, the agent or its group

Deterministic mechanisms win when they express the timing: rules, webhooks, status automations, and event-triggered cloud functions do not depend on the model remembering or deciding to call a tool. Do not copy a client's round-robin, CRM ownership, pricing, or eligibility logic into a prompt or function when their endpoint already owns it. Use an agent-callable tool only when the active conversation truly requires its response before continuing.

Client-owned capabilities and deterministic execution

Treat the client's endpoint as the capability and Nexor as the orchestrator:

  1. Identify whether a status, field, variable, lead, meeting, or workflow event defines the moment of execution. If it does, wire that event to a webhook, status automation, or cloud function; do not ask the agent to call the endpoint.
  2. Keep the authoritative business decision in the endpoint. Nexor passes facts; neither the agent nor a function should duplicate the client's algorithm.
  3. Use a conversational HTTP/MCP tool only when the agent must obtain the result inside the active turn and a deterministic trigger cannot run it first.
  4. For remaining agent-callable tools, set available_in_statuses to the exact valid status keys. Pair a qualified gate with requires_all_fields / required_field_keys so availability and booking cannot run before qualification.
  5. Use call_once for irreversible conversational operations that must run once per workflow run; leave it off read-only tools that may need a legitimate refresh.
  6. Define success, failure, persistence, and retry behavior. Never let the agent invent an assignment, slot, price, or external result after a failed call.

Read the workflow-tool and stage-gate contract in references/configuration-reference.md and use the round-robin mapping in references/recipes.md when the ask resembles sales-rep assignment.

Meeting-host selection

Meeting-host selection decides which of an appointment agent's assigned executives supplies the slots shown to a lead. It is not lead ownership, pipeline assignment, or an external CRM round robin.

  1. Call list_meeting_types and list_executives for the exact workflow before proposing a change. The agent must use exactly one existing meeting type. If several exist, stop and help the operator consolidate them to one; never create or silently choose another type.
  2. Treat zero or one assigned executive as having no routing decision. With two or more executives and no selection criteria, explain that availability is distributed evenly by round robin. After adding a second executive, proactively offer a choice between that default and an explicit host-selection rule.
  3. Build a rule only from facts the booking agent can actually know: standard lead properties, lead metadata, or workflow fields returned by get_workflow. Reference assigned executives by the user_id returned by list_executives. If a rule names an unknown or unassigned person, resolve that mismatch with the operator (invite_user, then add_executive) before writing anything.
  4. Choose the layer by what must be true. A field-driven or weighted split ("renta over 50000 to Ana, the rest 2:1 between Bruno and Carla, negatives disqualified"; "strict alternation") is enforced with set_meeting_type_routing({ workflow_id, agent_selection_rules }) — tiers by field with min, agents (user ids), mode round_robin / cycle, weights, pattern, and action: "disqualify"; it is applied at offer time and book time. A soft preference in prose is agent_selection_criteria via update_meeting_type: keep it short and ordered, and never claim it is server-enforced — the booking agent reads it when choosing among returned slots.
  5. Read the rules or criteria back (list_meeting_types / the set_meeting_type_routing response) and compare exactly. Report success only after the same meeting type contains the requested value; otherwise report the mismatch and do not imply that routing changed.

Do not use legacy meeting-selection tools, direct database access, or a second meeting type to express routing. When the client's own endpoint decides the host, use set_host_assigner instead — see references/booking-agent.md.

Booking agent

An appointment agent is not bookable when it is created. Six prerequisites must exist, in dependency order, before set_workflow_active produces an agent that can offer a slot — and get_account_readiness reports each missing one as a booking blocker with the tools that fix it. The complete argument contracts are in references/booking-agent.md; the order is:

  1. create_workflow({ goal_type: "appointment", timezone, … }) — pass the IANA timezone explicitly: it is the anchor every offered slot is rendered in. The three core stages and the virtual Discarded column always exist; design your booking stages after them.
  2. create_meeting_type({ workflow_id, name, duration_minutes, location_type }) — or reuse the single type list_meeting_types returns. One workflow, one meeting type; location_type: "in_person" requires location_details. Created active, bookable once a host with availability exists.
  3. add_executive({ workflow_id, user_id }) for every human who takes meetings (list_team for the id; invite_user first for someone not on the team). One eligible member → auto_assign_solo_host.
  4. set_host_schedule({ user_id, timezone, slots: [{ weekday, start, end }] }) — the complete weekly schedule, in the host's timezone; set_host_blocks for days off.
  5. connect_calendar({ user_id, provider: "google" | "outlook" }) → a connect_url only the host can complete in a browser. Send it, say who must open it, and poll get_calendar_connections({ user_id }) until status: "active". Never report a calendar as connected before the poll says so. Calendly replaces steps 4–5 (connect_calendly → map_calendly_hosts → set_calendly_binding).
  6. get_reminder_catalog({ workflow_id }), then set_reminder_rule per rule with the full contract — trigger_event (event_created, event_cancelled, call_analyzed, event_enrolled; no reschedule trigger exists, event_created re-fires for the new time), channel (only one the catalog marks available for this account), template_name (an APPROVED WhatsApp template, or a variant key for retell / sms / email), signed delay_minutes, delay_reference trigger or event_start. set_host_reminder_rule is email-only and fires on event_created only.
  7. With two or more hosts, set_meeting_type_routing (Meeting-host selection above).
  8. review_agent_system_plan({ plan }) — the appointment agent carries meeting_type, hosts[] and calendar_plan (or booking_provider); the review refuses an appointment plan without them. Sign-off on the summarized plan ("Does this look ok? Let me know and I'll build it.") precedes every mutation in steps 1–7.
  9. Read everything back — get_account_readiness shows zero booking blockers and get_workflow_slots returns slots — then set_workflow_active, confirmed separately.

Operating the agent afterwards is a different tool family: list_meetings / get_meeting_stats to see bookings, book_meeting / reschedule_meeting / change_meeting_host / cancel_meeting for operator-driven changes (each previews without confirm: true), and set_meeting_status({ status: "no_show" | "completed" }) to record outcomes. get_available_slots, confirm_and_book, create_event, reschedule_event, cancel_event, request_channel_swap and schedule_followup are runtime tools the agent uses mid-conversation — not callable over MCP. They appear in configuration only as names to stage-gate or to disable for an external booker.

When the client's own endpoint owns the calendar, set_external_booking requires all four verbs — slots, create (tool), reschedule_tool, cancel_tool — because a missing verb is silently never mirrored: phantom meetings, reminders that keep firing. The only exception is the operator accepting that limitation explicitly, in writing, in the conversation; record the sentence in the spec. See references/booking-agent.md.

Workflow

  1. Restate the ask as trigger → condition → action, resolve sync direction as into Nexor / out of Nexor before choosing a surface, classify its timing as in-turn / event / batch / inbound, and separate what the agent must ask (intake fields), what the customer's systems send (standard lead identity columns versus metadata), and what must happen (tool call or automation).
  2. Apply the three laws to draft the skeleton before choosing any primitive: list every fact the design needs as a named variable with its store and its readers (Law 1); list every in-agent stage as a status key with its entry criterion and gate (Law 2); list every identity switch as a boundary status with its shape, target agent, and — for a pause boundary — its executor (Law 3). Then size the system: one mode per intent and per register, 2–3 modes by default, one mode only when the operator explicitly asked for exactly one, never 5+ (see "Default shape"). Anything you cannot place in one of those three lists is not designed yet; place it before continuing.
  3. Call describe_agent_configuration before proposing any non-trivial or multi-agent build, and read current state with list_workflows / get_workflow / list_workflow_tools / list_webhooks. The platform ships its own required process and surface map; follow it rather than a remembered one. When the agent being edited belongs to an agent group (master_workflow_id), the group is the default unit of reading: its siblings, the statuses that transfer into and out of the edited agent, and the shared channel bindings — never just the one agent.
  4. Walk the ladder. Note every rung you skip and why — that reasoning is part of the deliverable.
  5. Prefer a deterministic state trigger. For a status change choose webhook/status automation first; for a field/variable or other lead event choose a cloud function. Use an in-turn tool only when the agent must receive the result before continuing, then gate it to the statuses where it is valid.
  6. Read references/recipes.md and start from the closest worked mapping; read references/configuration-reference.md to fill in exact field names and values for the chosen primitives; read references/mcp-tool-surface.md before the first write.
  7. Read the account before planning: call get_account_readiness plus list_whatsapp_numbers, list_email_senders, and list_phone_numbers (and get_integration_status for calendars[] / booking_providers[] when the agent books). Treat SMS as the active phone-number rows with sms_enabled: true. Account facts are evidence, not clarification prompts: never ask whether the customer already has a WhatsApp number, email sender, call number, SMS channel, or saved timezone. Channels and contact hours are never questions, for any agent — not even mixed into another question. An agent is born with every channel the account holds a verified resource for (get_account_readiness.inventory.channels), and every mode inherits its agent's channels, contact schedule and persona name at create_workflow (see step 10). Plan modes without a channels key (a named channel goes into the description and prompt as intent, never as a binding or a question) and derive contact_schedule from what the operator already said; with no hint the Mon–Fri 09:00–18:00 default applies and the summary names what the agent was born with in one line. review_agent_system_plan never asks which channels to enable and returns birth_defaults (the channels every new agent gets). Run the resource flow in step 8 only when the operator explicitly asks this mode to use a specific resource or a narrower mix, or to repair a channel_bindings outcome reported by create_workflow.
  8. Only on an explicit operator request (a specific number or sender, a narrower mix for one mode, or a create_workflow result reporting owned_by_other_agent / channel_binding_failed): write an expected channel manifest for that agent: enabled channel plus the exact selected resource id for WhatsApp, email, call, and SMS. Include each resource's current owner. Email senders are shareable; a WhatsApp number has one direct owner, a call number has one workflow_id, and an SMS route has one sms_workflow_id. Call and SMS may legitimately route the same physical number to different agents. Reject or resolve duplicate exclusive claims across the whole plan before mutation. For the genuinely provisioned channels (WhatsApp, email, call): if exactly one usable resource exists, offer that observed resource versus configuring a new one; if several exist, show every observed resource in a finite selector plus the new-resource path; if none exist, say so and offer setup versus continuing without the channel. SMS is not one of those provisioned channels — it is not "set up," it is activated per agent on a phone number the account already has. As long as an active Twilio number exists, do not start a buy-a-number flow: the only open question is which agent(s) should receive SMS, then activate it with set_number_sms({ number_id, enabled: true, sms_workflow_id }). SMS requires a Twilio-carrier number (Telnyx numbers cannot carry SMS); there is no country restriction. Only when the account has no phone number at all does provisioning enter the SMS conversation, falling to the same number-setup path as call. Never turn these finite choices into open-text questions.
  9. Preflight the whole system with review_agent_system_plan({ plan }) before mutating anything, expressing handoffs as transfer_to_agent_ref between plan-local agent refs, one plan agent per mode. A single-mode plan is blocked unless plan.decomposition carries single_mode_explicitly_requested: true and the operator's quoted operator_instruction; five or more modes are blocked unless large_system_explicitly_requested carries it; a mode nothing transfers into raises a clarification unless it is entry_point: true. Resolve every blocking_issue and clarification_question until ready_for_signoff is true, then show the returned summary and ask the exact signoff_prompt question. Do not call any create/update/run tool until the user approves the plan_fingerprint.
  10. An agent first, then its modes — always. Every workflow is a mode inside an agent (master workflow); a workflow never exists on its own, whatever the request looks like ("create an agent", "create a workflow", "add a mode", one or many). create_workflow requires master_workflow_id and refuses a call without it, and the API never leaves a mode without an agent. So the first write of any build is the agent: create_agent_group({ description }) (or the id of an existing one from list_agent_groups when the operator means that agent, or the agent whose chat you are already in), then every create_workflow with that id. The operator never names an agent at creation and you never ask for a name: Nexor picks a human first name from its pool (skipping the account's existing agents) and returns it; a name argument is ignored and echoed as requested_name_ignored. Present the agent by the returned name; rename later with update_agent_group only when the operator explicitly asks. Put the placement in the plan as plan.agent_group = { id } or { description? } so review_agent_system_plan can check it. The agent is born with the account's channels and every mode inherits them: create_workflow resolves, before the create, the mode's channels, contact schedule, persona name (agent_name = the agent's name), language and timezone from the agent's other live modes — or, for the first mode, from the account (every channel with a verified resource, the backend's schedule default or the operator's hours hint) — then binds the resources (one WhatsApp number and one SMS route per agent shared by its modes, the account-wide number for calls, the agent's email sender on every mode) and returns inheritance plus channel_bindings. So omit channels, contact_schedule and agent_name on create_workflow unless the operator explicitly asked this mode for something different, and never ask about them. An agent with no modes is unfinished: never end a build with an empty agent, and never detach a mode from every agent (update_workflow.master_workflow_id: null is refused — move it to another agent instead).
  11. Mutate in dependency order so nothing references an id or key that does not exist yet: create_agent_group (unless the agent exists) → create_workflow (the complete funnel — statuses carry their full stage config including gates, hints, timeouts and flags, plus the complete fields array; most field properties cannot be added later) → check the returned pipeline_reconciliation report and resolve every leftover seeded status, missing key, and warning before the next step → workflow tools → update_workflow_config for status automations → webhooks and rules → for an appointment agent, meeting type → hosts → availability → calendar (human step) → reminders (see Booking agent) → channel bindings only for an explicit resource request or a reported binding outcome (create_workflow already bound the inherited channels) → transfers (update_workflow_status with transfer_config once targets have real ids) → knowledge-base attachments. Agents are created paused; keep them paused through the whole build. See references/mcp-tool-surface.md for the exact tool per step and what each write overwrites.
  12. For every agent build, inventory the account catalog with list_knowledge_bases without workflow_id, then write an expected knowledge manifest: one row per agent with the exact account-owned KB ids/names it should use and their retrieval order. Account ownership is availability, not assignment. Infer relevance only when the KB description and agent responsibility make it unambiguous; otherwise resolve the choice before mutating. Never attach every account KB by default unless the operator explicitly wants every agent to use all of them.
  13. For a multi-agent design, write an expected connection manifest before mutating anything: one row per handoff with source_agent_ref, source_status_key, boundary_shape (terminal transfer or pause + executor), and target_agent_ref. Keep this manifest independent of creation order.
  14. After every involved agent has a real id, reconcile all three manifests. For channels, read each create_workflow result's inheritance (what the mode was born with and from where) and channel_bindings (per channel: bound, already_bound_in_group, client_scoped_available, owned_by_other_agent, no_resource, inventory_unavailable); bound / already_bound_in_group / client_scoped_available need nothing more. owned_by_other_agent means the tool refused to take another agent's resource: ask the operator before moving it (name the resource, old owner, new owner and routing impact), never move it silently. no_resource means the account has nothing on that channel: say so in one line, offer to connect one, and never "fix" it by pointing first_contact_channel at a channel the account has not connected. Only for an explicit operator request write selected real ids yourself (config.email_sender_id, assign_whatsapp_to_workflow, assign_number_to_workflow, and set_number_sms), synchronize config.disabled_channels, and re-run the inventory. Never silently steal a number already assigned to another agent: approval must name the resource, capability, old owner, new owner, and routing impact. Include every displaced agent in read-back and either disable its lost capability or apply its separately confirmed replacement. For transfers, update each source status with the destination's real id, including returning to an earlier-created source after a later target is created. For knowledge, attach missing expected KBs and detach unexpected KBs from each agent without deleting the client-owned KB. Read every involved agent, its channel inventory, and its attached KB list back. Repair and re-read until there are zero missing, unresolved, misdirected, or extra channels, connections, and assignments. Do not finalize, report success, or activate while any manifest is incomplete.
  15. Produce the configuration spec (see Output).
  16. State the verification path: which previews/dry runs, channel/connection/KB read-backs, and blocked/allowed tool tests to perform, and what they must show, before anything is activated.

Facts that decide designs

These are the semantics customers (and naive designs) most often get wrong:

  • create_workflow's statuses are the entire funnel — the backend seeds defaults, and seeded stages must not survive. The create endpoint seeds a default pipeline (New, Contacted, In conversation, system stages) and appends your statuses to it; the MCP tool reconciles this automatically and returns a pipeline_reconciliation report. The funnel is correct only when the result's top-level status is "created" (i.e. unrequested_statuses_remaining, missing_requested_statuses, duplicate_labels, and warnings are all empty) — resolve leftovers with get_pipeline_impact + delete_workflow_status and missing keys with update_workflow_structure before configuring anything that references a status. A seeded stage left in place is invisible to your design's gates, hints, and transfers, and duplicates (e.g. a stray payment_sent next to your send_link) mis-route leads into dead stages.
  • The agent routes on entry_hint, not description. A status's description never reaches the prompt. The placement rule ("annual income is under 800,000 — place the lead here") goes in entry_hint; gate advancement with variable_refs / required_field_keys. When the branch is fully determined by saved field values, encode it as transition_rules.rule_groups (server-evaluated field / operator / value conditions) with auto_evaluate: true, so the platform routes the lead the moment the fields are saved instead of depending on the agent calling set_lead_status.
  • is_qualified marks; gates enforce. The qualification stage type is a reporting/UI flag. The server-side gate is required_field_keys (takes precedence) / requires_all_fields plus transition_rules.rule_groups — and gates only block entry into non-terminal statuses and won-category terminals; entry into lost/transfer terminals is guided by entry_hint, not blocked.
  • Channel hours are one flag, not per-channel. Calls are always gated to the cadence windows (hard-coded, never 24/7); WhatsApp/email/SMS run 24/7 unless config.gate_outbound_to_hours: true gates them all together. Inbound replies bypass scheduling entirely. No weekly frequency cap exists for any channel — express "at most N per week" with per-block intensity, disabled_channels, or a background job with cooldown_minutes, and say which approximation you chose.
  • Discard rules are statuses, not automations — and lost stops initiating, not responding. "If the lead doesn't qualify, stop contacting them" is a terminal category: "lost" status whose entry_hint and transition_rules carry the rule, configured visibly in the agent and processed during the conversation. Entering it halts all proactive outbound and the run cannot be reactivated — but the agent still replies briefly if the lead writes in (built-in lost-lead behavior: no selling, no booking offers). pause_bot is the only control that fully silences replies; futurology_queue defers for recontact; only a terminal lost status discards.
  • Metadata is the default custom-context path, not the intake config. Whenever a customer asks how to give the agent arbitrary information about a lead, offer the lead's metadata JSON object first. Its values may use any JSON shape and the complete object reaches the runtime prompt. workflow_fields defines only what the agent asks for, validates, or uses to gate status advancement; bridge the specific inbound key with metadata_key only for those needs (one direction: metadata → field prefill). Metadata updates shallow-merge top-level keys, so resend a complete nested object when changing only part of it.
  • “Sync” has a direction before it has a primitive. Establish whether information is entering or leaving Nexor. For inbound data, map first_name, last_name, email, and phone to standard lead columns and put other agent-readable facts in metadata; those metadata values become available on the next agent execution. For outbound data, use an outbound trigger—metadata writes do not transmit anything.
  • Deterministic triggers beat model-selected tools. If "when" can be expressed as a status, field, variable, lead, meeting, or workflow event, let the platform fire the integration. Tool descriptions and prompt instructions can still be hallucinated or skipped; event subscriptions and triggers do not depend on model choice.
  • Tool descriptions guide; stage gates enforce. Put invocation guidance in the tool description and prompt, but use workflow_tools.available_in_statuses to block execution outside valid statuses. null / an empty list means unrestricted. Pair the gate with required fields on the qualifying status; changing to that status can unlock the tool within the same turn.
  • Custom logic belongs behind the endpoint. If HubSpot or a client service already performs round robin, territory routing, eligibility, or pricing, a deterministic trigger should call it whenever possible; an agent tool calls it only when the response is needed in-turn. Nexor should not maintain a second copy of that logic.
  • "Notify me" has two shapes. A webhook subscription filtered on to_status.key fires on every matching status change (your endpoint dedupes); a status automation fires exactly once per lead per rule and can template the request body from lead data.
  • Transfers fire only from hard-terminal statuses. is_terminal is checked before transfer_config is even read, and soft-terminal keys (future_*, contact_later, colder) are filtered out first — either mistake saves cleanly and never fires. Auto-transfer is also skipped while the lead is in human support (in_support). Transfers always create a fresh run on the target agent at its initial status — a transfer cannot land on a chosen status in another agent (the only status→status pointer is timeout_config.target_status_key, same workflow only). Source cadence is cancelled, the target's cadence starts over, and collected fields travel as a read-only transfer-chain snapshot — they are not copied into the target's fields. If the "transfer" is just a stage of the same conversation, it's a status, not a transfer.
  • Agent creation order never satisfies a connection. A handoff exists only after the source's terminal status has been read back with transfer_config.target_workflow_id equal to the destination's real id. Preserve the expected connection manifest across the build, wait until all referenced ids exist, then reconcile every source. Creating A before B means returning to A after B exists; creating B before A changes nothing. A multi-agent build is incomplete until every expected edge passes this read-back check.
  • Account knowledge is not agent knowledge. knowledge_bases is the client-owned catalog; workflow_knowledge_bases is the per-agent assignment. One KB may be shared by several agents, and each agent may receive a different ordered subset. Listing the account catalog proves only that a KB exists. Agent access exists only after list_knowledge_bases({ workflow_id }) reads that KB back on the intended agent. Create or reuse the account KB first, attach by its real id, detach only the relationship when removing access, and never delete shared content to change one agent's assignment.
  • An agent is born named, with the account's channels, and its modes inherit everything. Nexor picks the agent's human first name at create_agent_group (never ask for one, never pass one; rename later only on an explicit request). The agent has every channel the account holds a verified resource for, and each create_workflow inherits the agent's channels, contact schedule, persona name, language and timezone — from the agent's other modes, else from the account — and binds the resources itself (inheritance + channel_bindings in the result). Channels and contact hours are never questions, for any agent: no channel picker, no "use this number or connect another?", no "business hours or 24/7?", no "channel & hours" form, and no channels key in the plan. The resource flow runs only when the operator explicitly asks for a specific resource or a narrower mix, or to resolve an owned_by_other_agent outcome — which the tool never resolves silently.
  • Contact hours at birth derive from the operator's words. A mode inherits its agent's windows when the agent already has modes; the first mode gets Mon–Fri 09:00–18:00 only when nobody said anything about hours. Any hint — after-hours, overnight, 24/7, weekends, specific times, a use case that only exists outside office hours — becomes an explicit contact_schedule on that mode in the plan and in create_workflow ({mode:"always"} or {mode:"windows", windows:[{start,end,days_of_week}]}); review_agent_system_plan holds sign-off when a mode's own words hint at hours and no schedule is set. Never ask for hours the conversation already implied; when the default applies, say so in one line ("contacts Mon–Fri 09:00–18:00; can be changed"). Existing agents change hours with set_workflow_cadence.
  • Account channels are not questions. The account already knows its saved timezone, connected WhatsApp numbers, send-capable email senders, active call numbers, and which active numbers currently have SMS enabled. Read them before planning; every new agent is born with all of them and create_workflow binds them. The selector below applies only when the operator explicitly asks for a specific resource: one resource means “use this exact resource or configure another”; several means a real resource selector; zero means saying so in one line and offering setup. Never ask “do you already have a WhatsApp number?” or request an email/phone the platform can list. verification_status: "verified" is not enough when email can_send is explicitly false. SMS is not provisioned and is not "set up" as its own channel — it rides on a phone number the account already has. As long as an active Twilio number exists, do not start a buy-a-number flow: read the phone inventory and the only question is which agent(s) should receive SMS, then activate it per agent via set_number_sms({ number_id, enabled: true, sms_workflow_id }). SMS requires a Twilio-carrier number (Telnyx numbers cannot carry SMS); there is no country restriction. sms_enabled: false on an active Twilio row means SMS is simply not activated yet—not that a channel is missing. Only when the account has no number at all does provisioning enter the SMS conversation.
  • The source selects intensity; the target implements it. Put mutually exclusive routing criteria in source entry_hint values and one transfer_config on each terminal branch. Put sales urgency, booking tools, nurture tone, enabled channels, and contact frequency on the corresponding target workflows — never on the source status.
  • The source never speaks the hand-off; the target's first message is designed. On transfer the platform makes the target agent speak next — a contextual transition composed from the target's own prompt when the messaging window is open, its opening template otherwise. So the source prompt moves the lead to the boundary status and ends the turn with no hand-off dialogue (no "te paso con mi colega", no "the link is on its way", no farewell), and the target prompt carries an explicit arrival rule. For a payment-link agent that arrival message is one line acknowledging what was accepted plus the payment link, with set_payment_link configured beforehand (recipe 22).
  • An agent inside an agent group is never edited in isolation. If the agent being edited has a master_workflow_id, default to reading the whole group first: list_workflows for the siblings, get_workflow on the edited agent and on every sibling whose statuses transfer into or out of it, and the shared channel bindings (WhatsApp and iMessage numbers are shared across a group). Know who hands the lead in, on which status and with which context, and who receives it, before touching a prompt, a status, a first message, or a transfer — one agent's change alters what its neighbours must say and not say.
  • Function effects are buffered and return nothing, so a scheduled function cannot create a lead and enroll it in the same run. To sync leads in and start conversations, call the public leads API (it upserts, merges metadata, enrolls, and starts first contact in one request) from the function, or use a two-phase sweep.
  • Send-once is stateful, never assumed. Cohort messaging without then_status_key (or a cooldown) re-sends every cycle.
  • Nexor only sees conversions that happen in the conversation. A meeting the agent books or a Nexor payment link is counted automatically; anything that closes in a CRM, ERP, POS, store or billing system needs the conversion event webhook (POST /api/public/conversions, create_conversion over MCP). A conversion type is optional; the lead identifier alone (lead_id, lead_email or lead_phone) is enough. Put every fact about the conversion (deal id, product, seller, branch, plan) in the event's free-form metadata, never in the description — metadata reaches the lead, the conversion.detected cloud-function event and the list filters. Destination precedence: type > agent (set_conversion_destination) > group. Credit follows the same idea: pass workflow_id to credit one agent, or master_workflow_id alone to record a group-level conversion (no agent credited, the group's destination applies); explicit target > type scope > the lead's active agent.
  • A tool can run without the model choosing to. set_workflow_tool_execution({ mode: "on_entry" }) makes a tool a pre-execution hook that fires when the lead enters the workflow, before the first agent message. on_entry and agent_decides are mutually exclusive, and an on-entry hook cannot be stage-gated — the call is rejected if you pass available_in_statuses. Before proposing a conversational tool, ask whether "on entry" is actually the moment; if it is, the model never gets a chance to skip it.
  • Status automations have no tool of their own. They live in the workflow config bag and are written with update_workflow_config({ config: { status_automations: [...] } }). The bag merges at the top level but the array is replaced wholesale, so read get_workflow and send the complete array every time. Rules fire on on_status_key for enabled !== false, exactly once per (workflow, lead, rule key) forever via a ledger, with one automatic retry. A missing args_template value aborts the rule instead of sending a partial body — which is why the value must be in metadata before the lead can arrive.
  • Three unrelated things are called "paused." workflows.is_paused (the agent is off — every new agent starts here), workflow_runs.is_paused (one lead's run, set by stop_automation, and what job exclusions.skip_paused filters on), and status pause_bot (the agent goes quiet while the lead sits there). They do not imply each other.
  • The platform validates your plan before you build it. review_agent_system_plan blocks on undefined transfer targets, duplicate refs, and missing goals, and raises clarifications for missing responsibility, language, timezone, channels, or a status with no entry criterion. For an appointment agent it also blocks on a missing meeting type, an empty host list, or the absence of both a calendar plan and an external booking provider. It returns an internal plan_fingerprint and the exact sign-off question. Never show the plan_fingerprint, any hash, or the word "fingerprint" to the user: close the summary with "Does this look ok? Let me know and I'll build it." Treat a clean review plus explicit user approval as the precondition for the first mutation, not a formality.
  • An active appointment agent with booking blockers cannot book. set_workflow_active does not check for a meeting type, a host with availability, or a connected calendar; get_account_readiness does, per agent, under booking. Activation is the last step of the Booking agent path, never the proof that it worked — get_workflow_slots returning slots is.
  • Runtime tools are the agent's, not yours. get_available_slots, confirm_and_book, create_event, reschedule_event, cancel_event, request_channel_swap, schedule_followup, save_field and patch_metadata run inside the conversation and are not callable over MCP. Name them only to gate (available_in_statuses) or disable (disabled_builtin_tools) them; use book_meeting, reschedule_meeting, cancel_meeting and update_lead when the operator wants the action performed now.

Guardrails

  • Never mutate before review_agent_system_plan returns ready_for_signoff: true and the user approves the summarized plan (ask "Does this look ok? Let me know and I'll build it."; the plan_fingerprint stays internal and is never shown). Agents are created paused; keep them paused until read-back passes and activation is confirmed separately.
  • Deletion is explicit, two-step, and scope-gated; never empty an agent's pipeline silently. Use these deletion capabilities only when the corresponding tools are exposed in the current MCP session; a host product may intentionally omit them. delete_workflow archives an agent and releases its channels, but the backend refuses with HTTP 409 AGENT_HAS_LEADS (with distinct lead_count) while any active workflow_run remains. Call it once without confirm:true to obtain its write-free needs_confirmation preview, then call it with confirm:true only after the operator approves that exact agent. When blocked, report the count and offer an explicit choice to move those leads, archive them reversibly with the leads:write tools, or permanently erase them with hard_delete_lead / bulk_hard_delete_leads only when those tools are exposed and the key has the separately granted, non-default leads:delete scope. Permanent lead deletion follows the same preview-then-confirm contract: first call without confirm:true, present the exact lead id(s), then repeat with confirm:true after approval. Clarify every ambiguous “delete/remove” request as reversible archive versus permanent erasure. Permanent erasure is irreversible and forensically audited. Never archive, delete, or status-change leads as an unannounced workaround for agent deletion. Agent-group deletion (delete_agent_group) stays relationship-only — never delete, pause, or mutate a member agent as a substitute.
  • Know what each write destroys before sending it. set_workflow_prompt overwrites the whole prompt, update_workflow_config replaces any array you touch, set_workflow_cadence is a whole-document PUT for blocks, and update_workflow_config({ replace: true }) wipes the config bag. Read the current value first — see references/mcp-tool-surface.md.
  • Know what each write accepts before sending it. update_workflow mutates name, description, goal_statement, the agent identity (language, timezone, region_style, agent_name, agent_role, company_name, begin_message), and master_workflow_id (the agent to move the mode into — never null: a mode always belongs to an agent, so it is moved, not detached); every call must carry at least one of them or it fails INVALID_INPUT. Pause state goes through set_workflow_active and the channel mix through channel-binding tools plus config.disabled_channels — neither is an update_workflow parameter. Persona and business rules are prompt text: set_workflow_prompt / set_channel_prompt, never identity fields.
  • A behavior change lands on every prompt surface, not just the base prompt. When the operator adds a rule, a condition, or a branch driven by a lead field or metadata ("you will now receive X; if X > 50 do Y") without naming a channel, write it into the base prompt with set_workflow_prompt and into every channel prompt that has a body (get_workflow → channel prompts) with one set_channel_prompt per channel, phrased in that channel's register and merged into the existing text. SMS and iMessage outbound messages are composed from the channel prompt alone when one exists, and the call and WhatsApp overlays are what the agent follows turn by turn, so a rule left only in the base prompt silently never reaches those channels. Scope it to one channel only when the operator named that channel; report which surfaces you wrote.
  • Say what is Nexor and what is outside it. You speak in the first person, so "the system" and "your system" are ambiguous: the operator cannot tell whether you mean Nexor or their own tooling. Call Nexor "Nexor" (or the agent, the status, the webhook, the function). Call anything outside it by what it is: "your CRM", "your ERP", "your own backend", "your spreadsheet", "your external API", or, when you do not know, "your external API (outside Nexor)". An outbound webhook "notifies your external API"; an inbound webhook "receives leads from your external tool"; a conversion "is sent by your CRM or backend to Nexor"; an HTTP tool "calls your external API". Reuse the proper name once the customer gives it ("your HubSpot", "your Shopify").
  • A one-mode plan without the operator's explicit single-mode instruction is a design error, not a simplification: split at every change of intent or register (2–3 modes by default) before asking for sign-off. A plan of five or more modes is the opposite error; merge modes that share both intent and register.
  • Reject any design that breaks a law and fix its shape rather than compensating with prompt wording. Name the violated law: a needed fact carried only in prompt prose or conversation history (Law 1); a stage change with no status key (Law 2); a tone, persona, or tool-set switch that is not a boundary status with a named target agent (Law 3). Prompt text is speech; fields, statuses, and transfers are structure — only structure is enforced.
  • A pause boundary with no named executor is an unfinished transfer. Either make the boundary terminal with transfer_config, or state the job/API/human that performs the handoff and verify it moves a test lead.
  • A build whose goal is not booking or a Nexor payment is incomplete until the conversion source is named and either wired (test event read back with list_conversions) or explicitly deferred by the customer.
  • Never make a fact reachable in only one store when both a gate and an automation need it. If a status gates on field_x and an args_template must send it, the value must exist as a field and in metadata before the lead reaches that status.
  • Dry-run first for anything that messages humans or mutates many leads, and read the candidate list before activating. Manual function runs preview effects without applying them — but outbound HTTP in a dry run is real; point test runs at test endpoints.
  • Get explicit customer confirmation before activating any cohort automation or anything that sends messages.
  • Make side-effecting tools idempotent and return a persistent external operation ID. Set call_once where a second successful call would be wrong, and define what the agent says when the endpoint times out or fails.
  • For every proposed agent tool, state why a webhook, status automation, or event cloud function cannot provide the same behavior. If no immediate conversational dependency exists, replace the tool with the deterministic mechanism.
  • Test every stage-gated tool twice: a call before the allowed status must return tool_not_available_in_stage, and the same valid call after qualification must reach the endpoint. Do not rely on prompt wording as the gate.
  • Do not design on unsupported surface: API-triggered background jobs, workflow_scoped filter mode, and CRM-event job triggers (other than tag and payment events) are not currently functional — see the reference for the supported list.
  • Never place credentials in function code or tool config; store them as Environment Variables and reference env.KEY / {{env.KEY}}.
  • If the requirement genuinely fits no primitive, say exactly what is missing instead of forcing an approximation.
  • Treat multi-agent finalization as graph closure, not agent-count completion. If any expected transfer is missing, unresolved, attached to the wrong source status, or points to the wrong target id, report the build as incomplete and continue reconciliation or surface the exact failed edge. Never emit a success claim for a partially connected system.
  • Treat knowledge configuration as assignment closure. Compare each agent's observed KB ids and priority order with its expected manifest; attach missing links and detach extra links, then read back again. Never infer access from the account catalog, and never claim an agent is fully configured while its KB set differs from the plan.
  • Treat channel configuration as assignment closure. Compare every requested channel and selected resource id with the post-build account inventory and workflow config. Never infer a binding from “channel enabled,” never reassign an already-bound number without targeted confirmation, and re-read the exclusive owner immediately before the write so stale approval cannot move a resource from a different incumbent. Never claim completion while a selected sender/number is missing, unusable, bound to the wrong agent, disabled by config, or contradicted by first_contact_channel. If WhatsApp will initiate outreach, list an APPROVED greeting/opening/legacy_greeting/outbound template, configure it with set_opening_templates, and read it back with get_template_pool before activation.

Output

Produce a configuration spec containing:

  • A variable ledger (Law 1): every fact the design needs, its key, its store (workflow field / metadata / both), how it is obtained (agent asks, intake payload, tool response), and what reads it (gate, template, filter, prompt).
  • A mode ledger: every mode with its intent (what it gets the lead to do), its register (tone, intensity, cadence, channel mix), and the boundary that makes it a separate mode — 2–3 rows by default, one row only with the operator's quoted single-mode instruction.
  • A stage ledger (Law 2): every status key per agent with its entry_hint criterion, its gate (required_field_keys / transition_rules), and what entering it unlocks or fires.
  • Each primitive used: its name, its trigger, and the exact configuration (JSON snippets with real field names from the reference).
  • For every workflow HTTP/MCP tool: endpoint owner, input/output schema, available_in_statuses, call_once, response fields stored or shown to the agent, prompt invocation rule, and failure behavior.
  • For every integration action: explain why its invocation is deterministic, or explicitly justify why an agent-selected tool is unavoidable and accept the lower reliability.
  • For every transfer branch: define its source status criterion and target workflow, then specify the target's goal, prompt focus, tools, channels, contact cadence/recontact rule, and transferred fields it must not ask for again.
  • A connection manifest for every multi-agent system (Law 3): each edge's source agent, source boundary status, boundary shape (terminal transfer, or pause plus its named executor), and target agent — followed by observed read-back evidence for each edge (source agent + source status → target agent id). The order agents were created must not affect this list.
  • A knowledge manifest for every created or edited agent, followed by observed read-back evidence for the exact assigned KB ids/names and priority order. Distinguish account-owned availability from per-agent access.
  • A channel manifest for every created or edited agent, followed by observed read-back evidence for the exact WhatsApp number, email sender, call number, and SMS ids selected and assigned. SMS read-back is the sms_workflow_id binding on the account's Twilio number, not a separately provisioned resource. Distinguish account availability from per-agent binding and call out any reassignment explicitly.
  • How the pieces connect (e.g. "field salary → status low_income via entry_hint → transfer_config → agent B").
  • The ladder rungs you rejected and why, in one line each.
  • A verification checklist: the previews/dry runs to perform and the observable result that means "safe to activate."

Resources

  • Read references/recipes.md when matching a customer ask to a known pattern — start from the closest recipe.
  • Read references/configuration-reference.md when writing the concrete configuration for any primitive.
  • Read references/mcp-tool-surface.md before the first write: which tool writes what, its read-back pair, what it overwrites, and which properties MCP cannot reach at all.
  • Read references/booking-agent.md when the agent books meetings: the ordered path (meeting type → hosts → availability → calendar → reminders), the full reminder-rule contract, host routing, operator meeting operations, and the Calendly and external-booker alternatives.

Comments

Markdown supported