# Sync a lead to the client's system

## Requirement

"Whenever the agent writes a lead, it must appear in our system."

## Pieces

- **Custom data**: a collection `leads`. The agent writes leads with its
  `write_leads` tool (`write_tool_id` on the collection), attached to a node
  the way any tool is.
- **Connection**: the CRM's key, provider `http_key`.
- **One tool per action**: an HTTP tool that posts one lead to the CRM.
- **Flow**: read the record that was written, then post it.
- **Automation**: trigger `record_written` on the collection, running the flow.

## The shape to avoid

A tool the agent calls that writes the lead *and* posts it to the CRM. A lead
written any other way, by an import, by a person, by another agent, never
reaches the CRM; a CRM outage loses the lead entirely; and the key ends up on
the tool. Here the collection is the record of truth and the automation is
what keeps the CRM in step, whoever wrote the row.

## Steps

Ids from an earlier step are written as `{{step_N.field}}`; the test that runs
this page substitutes them.

1. The CRM's key, once.

   `create_connection`

   ```json
   {
     "provider": "http_key",
     "account_label": "CRM",
     "scopes": [],
     "grant_secret": "{\"header\":\"Authorization\",\"prefix\":\"Bearer \",\"value\":\"crm_live_replace_me\"}",
     "granted_via": "pasted"
   }
   ```

2. The leads. Name, phone and email are personal, so the retention window
   applies to them.

   `create_collection`

   ```json
   {
     "name": "Leads",
     "slug": "leads",
     "fields": [
       {"key": "name", "label": "Name", "type": "text", "required": true, "personal": true},
       {"key": "phone", "label": "Phone", "type": "text", "personal": true},
       {"key": "email", "label": "Email", "type": "text", "personal": true},
       {"key": "interest", "label": "Interest", "type": "text"}
     ]
   }
   ```

3. One tool that posts one lead. The parameters become the JSON body.

   `create_tool`

   ```json
   {
     "name": "push_lead",
     "description": "Create one lead in the CRM.",
     "source": "builtin",
     "config": {"endpoint": "https://crm.example.com/api/leads", "method": "POST"},
     "input_schema": {
       "type": "object",
       "properties": {
         "name": {"type": "string"},
         "phone": {"type": "string"},
         "email": {"type": "string"},
         "interest": {"type": "string"}
       },
       "required": ["name"]
     },
     "connection_id": "{{step_1.id}}"
   }
   ```

4. The flow.

   `create_flow`

   ```json
   {"name": "sync_lead"}
   ```

5. A `record_written` run starts with `collection_id`, `record_id` and
   `written_at`, not with the record's values. So the entry step reads the
   record, with the collection's own `get_leads` tool: a collection with no
   identity field looks up by `record_id`.

   `add_step` (`flow_id` = `{{step_4.id}}`)

   ```json
   {"tool_id": "{{step_2.get_tool_id}}", "param_overrides": {"record_id": "$ref:trigger.record_id"}}
   ```

6. Then post it. A get step answers `{"record": {...}}` with the record's
   fields, so each value is `$ref:<step>.record.<field>`.

   `add_step` (`flow_id` = `{{step_4.id}}`)

   ```json
   {
     "tool_id": "{{step_3.id}}",
     "param_overrides": {
       "name": "$ref:{{step_5.id}}.record.name",
       "phone": "$ref:{{step_5.id}}.record.phone",
       "email": "$ref:{{step_5.id}}.record.email",
       "interest": "$ref:{{step_5.id}}.record.interest"
     }
   }
   ```

7. Read, then post.

   `add_flow_edge` (`flow_id` = `{{step_4.id}}`)

   ```json
   {"source_step_id": "{{step_5.id}}", "target_step_id": "{{step_6.id}}"}
   ```

8. The trigger. Created switched off. `min_interval_seconds` is the floor
   between two runs of this automation.

   `create_automation`

   ```json
   {
     "name": "Sync leads to the CRM",
     "trigger_kind": "record_written",
     "trigger_config": {"collection_id": "{{step_2.id}}"},
     "flow_id": "{{step_4.id}}",
     "min_interval_seconds": 300
   }
   ```

9. Switch it on. Enabling has its own route on purpose: it is the
   consequential step.

   `set_automation_enabled` (`automation_id` = `{{step_8.id}}`)

   ```json
   {"enabled": true}
   ```

## What the agent sees

Nothing new. The agent writes a lead with `write_leads` as before; the
automation runs with no conversation, once per record written, and each run
is listed by `list_automation_runs` with what it was handed and how it ended.

## Variations

- **Only leads with a phone number**: add a conditional edge between step 5
  and step 6 with `{"field": "<step 5 id>.record.phone", "op": "exists"}`.
- **An update in the CRM rather than a create**: a second HTTP tool with
  `PUT`, and a first HTTP tool that looks the lead up; the edge on whether it
  was found picks one.
- **The other direction, CRM to platform**: an automation with
  `trigger_kind: "webhook"`; the CRM calls the webhook and the flow's entry
  step reads `$ref:trigger.<key>` from the delivered body, writing with
  `write_leads`.
