Chapter 8 of 11 · ~2 min
Connect, wait, repeat
Three things remain after the decision: write the deal to the CRM, wait for the prospect, and stop waiting eventually.
The CRM through a connection. The workflow contains a Tool step that calls create deal on a connection named crm. That is all the definition knows. The connection itself, with its base address and credentials, is created once in the workspace, and the deployment binds the name to it. The same definition deploys to staging bound to a sandbox and to production bound to the real system, and the model in the research step never sees either. The validator adds a warning when a mutating operation like this one does not sit behind an explicit confirmation; here it sits behind the rep's approval on the high-discount path and goes straight through on the low one, which is the policy.
Waiting. A Delay node of seven days. The run's status becomes delayed and it costs nothing while it waits. Then a Loop with a cap of two iterations sends a reminder and asks the prospect, through an Input with a three-day timeout that is skipped rather than failed when nobody answers. When the loop ends without a yes, the run ends; the CRM already holds the quote.
Repeating on a schedule. Stale quotes are the sales lead's problem, so they get their own small workflow. It has a cron trigger for Monday mornings, one Tool step that lists open quotes, a Code step that keeps the ones older than fourteen days, and a Message and Input for the rep. A schedule is a trigger on the root node, materialised when the workflow is deployed.
Experiment
Runs in your browserThe CRM step, the wait, the reminder loop and the scheduled sweep as the agent wrote them, and the deployment call that binds the connection.
The CRM step, the wait, the reminders, the sweep. Excerpts from enquiry-to-quote.json and stale-quote-sweep.json, then the deployment call that binds the connection.
// enquiry-to-quote.json, after the quote is shown{"type": "Tool","id": "record_deal","tool": "create_deal","connection": "crm","input": { "company": "{{enquiry.company}}", "contact_name": "{{enquiry.contact_name}}", "email": "{{enquiry.email}}","line": "{{price.line}}", "quantity": "{{price.quantity}}", "discount": "{{decision.final_discount}}","total": "{{decision.total}}", "stage": "quoted" }},{ "type": "Message", "id": "sent", "participant_role": "prospect", "content": "Your quote is above, {{enquiry.contact_name}}: … We will check back in a week." },{ "type": "Delay", "id": "wait_for_reply", "duration": "168h" },{"type": "Loop","id": "reminders","loop_while": { "operator": "not_exists", "field": "reminders.reply" },"max_iterations": 2,"children": [{ "type": "Message", "id": "nudge", "participant_role": "prospect", "content": "Hello {{enquiry.contact_name}}, checking in on the quote …" },{ "type": "Input", "id": "reply", "participant_role": "prospect", "prompt": "Would you like to go ahead?", "input_type": "Boolean","timeout_hours": 72, "on_timeout": "skip" }]}// stale-quote-sweep.json, the root node{"type": "Workflow","id": "stale-quote-sweep","triggers": [ { "type": "cron", "schedule": "0 9 * * MON", "timezone": "Europe/London", "input": { "older_than_days": 14 } } ],"children": [{ "type": "Tool", "id": "open_quotes", "tool": "list_deals", "connection": "crm", "input": { "stage": "quoted" } },{ "type": "Code", "id": "stale", "input": { "deals": "{{open_quotes.deals}}", "older_than_days": "{{older_than_days}}" }, "expression": "function ({ deals, older_than_days }) { … }" },{ "type": "Message", "id": "digest", "participant_role": "rep", "content": "**{{stale.count}} quotes older than {{older_than_days}} days with no reply:**\n{{stale.lines}}" },{ "type": "Input", "id": "action", "participant_role": "rep", "prompt": "What should happen to them?", "input_type": "SingleSelect","options": ["Send one more reminder", "Close them as lost", "Leave them for now"], "timeout_hours": 48, "on_timeout": "skip" }],"participants": [ { "name": "rep", "type": "human", "bind": "team_member", "team": "sales", "surface": "inbox" } ]}// The deployment call (MCP deploy_workflow, or the CLI's --bind flag){"workflow_id": "a3fbcf11-…","environment": "production","connection_bindings": { "crm": "crm-production" }}// What the validator said about the definitionwarning [crm] (connection_confirmation): mutating operation "create_deal" should follow explicit participant review and confirmation
- 1A fixed call with arguments from state. No model chooses whether to make it or what to send.
- 2Seven days. The run's status becomes delayed and it costs nothing while it waits; a person's thinking time never counts against a budget either.
- 3Chase twice at most, from the brief. The loop ends when the prospect answers or the cap is reached.
- 4No answer in three days is not a failure. The step is skipped and the loop decides what to do.
- 5A trigger on the root node. Deploying the workflow materialises the schedule; no person starts these runs.
- 6The same definition, bound to a sandbox for staging and to the real CRM for production. Rotating the CRM's credentials needs no redeploy.
- 7The validator's warning about a write without an explicit confirmation. Here the write sits behind the rep's approval on the high-discount path and goes straight through on the low one; that is the policy, and the warning made it a conscious choice.
Big question
Which of your systems would the workflow need to reach, and who owns the connection?
