automation-architecture
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
- 01Map information to fields and metadata
- 02Model state changes as workflow statuses
- 03Separate agent modes at transfer boundaries
- 04Choose among Nexor configuration primitives
- 05Prefer configuration over custom code
Use cases
- 01Designing a lead intake and qualification workflow
- 02Planning handoffs between distinct agent intents
- 03Selecting primitives for reminders and integrations
- 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-architectureAfter installing, type/automation-architecturein Claude Code to confirm it loaded.
Bundled files
References and scripts installed along with SKILL.md.
references/booking-agent.mdconfiguration-reference.mdmcp-tool-surface.mdrecipes.mdDetails
From the project READMEAutomation 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, withextraction_hints. - An external tool (a CRM, a form, the customer's own backend) supplies lead identity/contact information → write
first_name,last_name,email, andphonethrough 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
metadataJSON 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(andintake_value_mapwhen needed) so metadata pre-fills the field instead of being asked.metadata_key,options,extraction_hints, andvalidationcan only be written in the initialcreate_workflowcall —update_workflow_structureaccepts justkey,label,type,required,sort_order. Finish the variable ledger before creating the agent. - A tool produces it →
llm_response_fieldskeeps 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, andtransition_rulescannot see metadata. - Deterministic senders read metadata only. Status-automation
args_template, webhookpayload_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 orpatch_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: trueplustransfer_config: { "target_workflow_id": "<real id>" }. This is the only shape that auto-fires a transfer. Every agent path checksis_terminalbefore readingtransfer_config, so a non-terminal status with atransfer_confignever fires — the config saves cleanly and silently does nothing. - Never name a boundary status
future_*,contact_later, orcolder. Those keys are treated as soft terminals and are excluded from the terminal side-effect path beforetransfer_configis 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: truewhen the handoff is executed out of band — a background job'sworkflow_transfer/force_transferaction, 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_botis a status flag and does not setworkflow_runs.is_paused, so a job with the standardexclusions.skip_paused: truestill 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.decompositionassingle_mode_explicitly_requested: trueplusoperator_instruction;review_agent_system_planblocks 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_requestedcarries 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:
| # | Primitive | Trigger | Code? | Scope |
|---|---|---|---|---|
| 1 | Agent config (statuses + intake fields) | Lead's message, mid-conversation | No | One lead, in-turn |
| 2 | Workflow HTTP/MCP tool | Agent needs external information or action during its turn | No | One lead, synchronous |
| 3 | Rules (reminders / host notifications / recontact) | Meeting lifecycle, staleness | No | One lead |
| 4 | Outbound webhook | Platform event (status change, meeting lifecycle) | No | Notify an external API (CRM, ERP, own backend), every occurrence |
| 5 | Status automation | Lead reaches a specific status | No | Call one HTTP/MCP tool, exactly once per lead |
| 6 | Background job | cron or tag event | No | Filtered cohort, declarative steps |
| 7 | Cloud function | A lead.* / workflow.* / meeting.* event | JS | One lead per event |
| 8 | Scheduled function | Cron + timezone | JS | Cohort from a lookup query |
| 9 | Workflow transfer | Terminal status with transfer_config | No | One lead, agent-to-agent handoff |
| 10 | Intake API / inbound webhook | An external tool (CRM, form, Zapier, own backend) pushes a lead in | No | Lead creation + enrollment |
| 11 | Cadence config (contact blocks + day config) | Outreach scheduling: windows, per-channel intensity, hot contact | No | A workflow's entire outbound initiative |
| 12 | Knowledge-base assignment | Agent needs selected account-owned knowledge at runtime | No | Exact per-agent subset, ordered by retrieval priority |
| 13 | Pre-execution hook (tool with mode: "on_entry") | Lead enters the workflow, before the first agent message | No | One lead, deterministic, cannot be stage-gated |
| 14 | Pre-processor | Inbound lead arrives, before any agent owns it | No | Ordered condition rules choosing the entry workflow |
| 15 | Stage timeout (timeout_config) | Time elapsed since the lead entered a status ("no reply for 48h") | No | One 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.
- 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.
- 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_configon the waiting status (set_status_timeout_rule, or inline increate_workflow/update_workflow_status), pointing at the next status — or at a terminal status withtransfer_configfor 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 recontacton_exhaustedis never the answer to a per-lead wait (recipe 23). - Is it a standard lifecycle nudge? Booking confirmations, pre-meeting reminders, host notifications, stale-lead recontact → rules. Configure and stop.
- 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. - 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. - 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 withavailable_in_statuseswhen valid only after a stage such asqualified. - Is it "filter leads → condition → action"? → background job with declarative steps and actions. Always set
max_leads_per_cycleandcooldown_minutes; dry-run anything that messages humans. - 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.
- 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_nowandqualified_later, each with its owntransfer_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:
| Environment | API origin |
|---|---|
| Staging | https://api-staging.getnexor.ai |
| Production | https://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.leadorlead; 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 resolvedctx.leadsarray in code. Do not query/api/public/leadsfor the same cohort. Leads have noworkflow_idcolumn: current agent/workflow scope is the pairworkflow_runs.workflow_id = <id>andworkflow_runs.is_active = truein the relationship-aware query. - Use
updateLead/updateMetadataandeffects.*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, andphone. 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 ametadataobject or shallow-merge it withPATCH/PUT /api/public/leads/:id; usePATCH /api/public/leads/metadatafor 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_keyonly 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.
| Direction | Cadence | Use | Why |
|---|---|---|---|
| Nexor → external | Status change | Outbound webhook or status automation | Platform state fires it deterministically; no model tool-selection decision |
| Nexor → external | Field/variable or lead event | Cloud function | The event fires deterministically; custom code can inspect the changed key and call any endpoint |
| Nexor ↔ external | During the agent's turn, no usable state trigger | Workflow HTTP/MCP tool | Use 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 → external | Batch | Scheduled function sweep | Cohort in ctx.leads[], push out per lead or aggregated |
| External → Nexor | Push (they call you) | Public leads API / inbound hook | Upsert + metadata + enrollment in one request, no code |
| External → Nexor | Pull (Nexor calls them) | Scheduled function | Query 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 → Nexor | The 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:
- 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.
- Keep the authoritative business decision in the endpoint. Nexor passes facts; neither the agent nor a function should duplicate the client's algorithm.
- 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.
- For remaining agent-callable tools, set
available_in_statusesto the exact valid status keys. Pair aqualifiedgate withrequires_all_fields/required_field_keysso availability and booking cannot run before qualification. - Use
call_oncefor irreversible conversational operations that must run once per workflow run; leave it off read-only tools that may need a legitimate refresh. - 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.
- Call
list_meeting_typesandlist_executivesfor 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. - 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.
- 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 theuser_idreturned bylist_executives. If a rule names an unknown or unassigned person, resolve that mismatch with the operator (invite_user, thenadd_executive) before writing anything. - 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 byfieldwithmin,agents(user ids),moderound_robin/cycle,weights,pattern, andaction: "disqualify"; it is applied at offer time and book time. A soft preference in prose isagent_selection_criteriaviaupdate_meeting_type: keep it short and ordered, and never claim it is server-enforced — the booking agent reads it when choosing among returned slots. - Read the rules or criteria back (
list_meeting_types/ theset_meeting_type_routingresponse) 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:
create_workflow({ goal_type: "appointment", timezone, … })— pass the IANAtimezoneexplicitly: 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.create_meeting_type({ workflow_id, name, duration_minutes, location_type })— or reuse the single typelist_meeting_typesreturns. One workflow, one meeting type;location_type: "in_person"requireslocation_details. Created active, bookable once a host with availability exists.add_executive({ workflow_id, user_id })for every human who takes meetings (list_teamfor the id;invite_userfirst for someone not on the team). One eligible member →auto_assign_solo_host.set_host_schedule({ user_id, timezone, slots: [{ weekday, start, end }] })— the complete weekly schedule, in the host's timezone;set_host_blocksfor days off.connect_calendar({ user_id, provider: "google" | "outlook" })→ aconnect_urlonly the host can complete in a browser. Send it, say who must open it, and pollget_calendar_connections({ user_id })untilstatus: "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).get_reminder_catalog({ workflow_id }), thenset_reminder_ruleper rule with the full contract —trigger_event(event_created,event_cancelled,call_analyzed,event_enrolled; no reschedule trigger exists,event_createdre-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 forretell/sms/email), signeddelay_minutes,delay_referencetriggerorevent_start.set_host_reminder_ruleis email-only and fires onevent_createdonly.- With two or more hosts,
set_meeting_type_routing(Meeting-host selection above). review_agent_system_plan({ plan })— the appointment agent carriesmeeting_type,hosts[]andcalendar_plan(orbooking_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.- Read everything back —
get_account_readinessshows zero booking blockers andget_workflow_slotsreturns slots — thenset_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
- 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).
- 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.
- Call
describe_agent_configurationbefore proposing any non-trivial or multi-agent build, and read current state withlist_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. - Walk the ladder. Note every rung you skip and why — that reasoning is part of the deliverable.
- 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.
- 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.
- Read the account before planning: call
get_account_readinesspluslist_whatsapp_numbers,list_email_senders, andlist_phone_numbers(andget_integration_statusforcalendars[]/booking_providers[]when the agent books). Treat SMS as the active phone-number rows withsms_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 atcreate_workflow(see step 10). Plan modes without achannelskey (a named channel goes into the description and prompt as intent, never as a binding or a question) and derivecontact_schedulefrom 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_plannever asks which channels to enable and returnsbirth_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 achannel_bindingsoutcome reported bycreate_workflow. - Only on an explicit operator request (a specific number or sender, a narrower mix for one mode, or a
create_workflowresult reportingowned_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 oneworkflow_id, and an SMS route has onesms_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 withset_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. - Preflight the whole system with
review_agent_system_plan({ plan })before mutating anything, expressing handoffs astransfer_to_agent_refbetween plan-local agent refs, one plan agent per mode. A single-mode plan is blocked unlessplan.decompositioncarriessingle_mode_explicitly_requested: trueand the operator's quotedoperator_instruction; five or more modes are blocked unlesslarge_system_explicitly_requestedcarries it; a mode nothing transfers into raises a clarification unless it isentry_point: true. Resolve everyblocking_issueandclarification_questionuntilready_for_signoffis true, then show the returned summary and ask the exactsignoff_promptquestion. Do not call any create/update/run tool until the user approves theplan_fingerprint. - 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_workflowrequiresmaster_workflow_idand 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 fromlist_agent_groupswhen the operator means that agent, or the agent whose chat you are already in), then everycreate_workflowwith 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; anameargument is ignored and echoed asrequested_name_ignored. Present the agent by the returned name; rename later withupdate_agent_grouponly when the operator explicitly asks. Put the placement in the plan asplan.agent_group = { id }or{ description? }soreview_agent_system_plancan check it. The agent is born with the account's channels and every mode inherits them:create_workflowresolves, 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 returnsinheritancepluschannel_bindings. So omitchannels,contact_scheduleandagent_nameoncreate_workflowunless 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: nullis refused — move it to another agent instead). - 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 returnedpipeline_reconciliationreport and resolve every leftover seeded status, missing key, and warning before the next step → workflow tools →update_workflow_configfor 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_workflowalready bound the inherited channels) → transfers (update_workflow_statuswithtransfer_configonce 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. - For every agent build, inventory the account catalog with
list_knowledge_baseswithoutworkflow_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. - 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), andtarget_agent_ref. Keep this manifest independent of creation order. - After every involved agent has a real id, reconcile all three manifests. For channels, read each
create_workflowresult'sinheritance(what the mode was born with and from where) andchannel_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_availableneed nothing more.owned_by_other_agentmeans 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_resourcemeans the account has nothing on that channel: say so in one line, offer to connect one, and never "fix" it by pointingfirst_contact_channelat 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, andset_number_sms), synchronizeconfig.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. - Produce the configuration spec (see Output).
- 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 apipeline_reconciliationreport. The funnel is correct only when the result's top-levelstatusis"created"(i.e.unrequested_statuses_remaining,missing_requested_statuses,duplicate_labels, andwarningsare all empty) — resolve leftovers withget_pipeline_impact+delete_workflow_statusand missing keys withupdate_workflow_structurebefore 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 straypayment_sentnext to yoursend_link) mis-route leads into dead stages.- The agent routes on
entry_hint, notdescription. A status'sdescriptionnever reaches the prompt. The placement rule ("annual income is under 800,000 — place the lead here") goes inentry_hint; gate advancement withvariable_refs/required_field_keys. When the branch is fully determined by saved field values, encode it astransition_rules.rule_groups(server-evaluatedfield/operator/valueconditions) withauto_evaluate: true, so the platform routes the lead the moment the fields are saved instead of depending on the agent callingset_lead_status. is_qualifiedmarks; gates enforce. The qualification stage type is a reporting/UI flag. The server-side gate isrequired_field_keys(takes precedence) /requires_all_fieldsplustransition_rules.rule_groups— and gates only block entry into non-terminal statuses andwon-category terminals; entry intolost/transfer terminals is guided byentry_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: truegates 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 withcooldown_minutes, and say which approximation you chose. - Discard rules are statuses, not automations — and
loststops initiating, not responding. "If the lead doesn't qualify, stop contacting them" is a terminalcategory: "lost"status whoseentry_hintandtransition_rulescarry 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_botis the only control that fully silences replies;futurology_queuedefers for recontact; only a terminalloststatus 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
metadataJSON object first. Its values may use any JSON shape and the complete object reaches the runtime prompt.workflow_fieldsdefines only what the agent asks for, validates, or uses to gate status advancement; bridge the specific inbound key withmetadata_keyonly 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, andphoneto 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_statusesto 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.keyfires 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_terminalis checked beforetransfer_configis 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 istimeout_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_idequal 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_basesis the client-owned catalog;workflow_knowledge_basesis 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 afterlist_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 eachcreate_workflowinherits 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_bindingsin 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 nochannelskey in the plan. The resource flow runs only when the operator explicitly asks for a specific resource or a narrower mix, or to resolve anowned_by_other_agentoutcome — 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_scheduleon that mode in the plan and increate_workflow({mode:"always"}or{mode:"windows", windows:[{start,end,days_of_week}]});review_agent_system_planholds 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 withset_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_workflowbinds 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 emailcan_sendis 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 viaset_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: falseon 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_hintvalues and onetransfer_configon 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_linkconfigured 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_workflowsfor the siblings,get_workflowon 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_conversionover MCP). A conversion type is optional; the lead identifier alone (lead_id,lead_emailorlead_phone) is enough. Put every fact about the conversion (deal id, product, seller, branch, plan) in the event's free-formmetadata, never in the description — metadata reaches the lead, theconversion.detectedcloud-function event and the list filters. Destination precedence: type > agent (set_conversion_destination) > group. Credit follows the same idea: passworkflow_idto credit one agent, ormaster_workflow_idalone 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_entryandagent_decidesare mutually exclusive, and an on-entry hook cannot be stage-gated — the call is rejected if you passavailable_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
configbag and are written withupdate_workflow_config({ config: { status_automations: [...] } }). The bag merges at the top level but the array is replaced wholesale, so readget_workflowand send the complete array every time. Rules fire onon_status_keyforenabled !== false, exactly once per (workflow, lead, rule key) forever via a ledger, with one automatic retry. A missingargs_templatevalue 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 bystop_automation, and what jobexclusions.skip_pausedfilters on), and statuspause_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_planblocks 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 internalplan_fingerprintand the exact sign-off question. Never show theplan_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_activedoes not check for a meeting type, a host with availability, or a connected calendar;get_account_readinessdoes, per agent, underbooking. Activation is the last step of the Booking agent path, never the proof that it worked —get_workflow_slotsreturning 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_fieldandpatch_metadatarun inside the conversation and are not callable over MCP. Name them only to gate (available_in_statuses) or disable (disabled_builtin_tools) them; usebook_meeting,reschedule_meeting,cancel_meetingandupdate_leadwhen the operator wants the action performed now.
Guardrails
- Never mutate before
review_agent_system_planreturnsready_for_signoff: trueand the user approves the summarized plan (ask "Does this look ok? Let me know and I'll build it."; theplan_fingerprintstays 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_workflowarchives an agent and releases its channels, but the backend refuses with HTTP 409AGENT_HAS_LEADS(with distinctlead_count) while any activeworkflow_runremains. Call it once withoutconfirm:trueto obtain its write-freeneeds_confirmationpreview, then call it withconfirm:trueonly 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 theleads:writetools, or permanently erase them withhard_delete_lead/bulk_hard_delete_leadsonly when those tools are exposed and the key has the separately granted, non-defaultleads:deletescope. Permanent lead deletion follows the same preview-then-confirm contract: first call withoutconfirm:true, present the exact lead id(s), then repeat withconfirm:trueafter 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_promptoverwrites the whole prompt,update_workflow_configreplaces any array you touch,set_workflow_cadenceis a whole-document PUT for blocks, andupdate_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_workflowmutatesname,description,goal_statement, the agent identity (language,timezone,region_style,agent_name,agent_role,company_name,begin_message), andmaster_workflow_id(the agent to move the mode into — nevernull: a mode always belongs to an agent, so it is moved, not detached); every call must carry at least one of them or it failsINVALID_INPUT. Pause state goes throughset_workflow_activeand the channel mix through channel-binding tools plusconfig.disabled_channels— neither is anupdate_workflowparameter. 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_promptand into every channel prompt that has a body (get_workflow→ channel prompts) with oneset_channel_promptper 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_xand anargs_templatemust 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_oncewhere 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_scopedfilter 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 anAPPROVEDgreeting/opening/legacy_greeting/outbound template, configure it withset_opening_templates, and read it back withget_template_poolbefore 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_hintcriterion, 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_idbinding 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→ statuslow_incomeviaentry_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.