Skip to content
indraft
Start free

Cookbook

Keep your CRM, and put an agent beside it

You are not leaving Salesforce this year, or HubSpot, or the system your recruiters live in. This is the job that lets an agent work anyway, without a migration and without your nightly sync undoing it.

The situation

Your CRM has seven years of history, a managed package, and reports the board reads. Nobody is replacing it. But it was built for people typing into forms, and the thing you now want is an agent that reads a call, updates the account, and tells you what changed. Handing an agent write access to the system of record is how you find out what an agent will do at three in the morning.

So do not. Keep the incumbent as the system of record, put Indraft beside it as the layer the agent writes to, and move what is worth moving between them.

What must be true when you are done

  • Your CRM is still the system of record and nothing about it changed.
  • Your agent writes freely, and every value it writes says so.
  • The nightly import cannot undo what the agent learned yesterday.
  • What flows back is only what the agent learned, never what you imported.
  • You can tell, per field, which system said it.

Decide who owns which field, before anything else

This is the whole recipe and the rest is plumbing. Two systems holding the same field is fine; two systems believing they decide it is not. Write the split down before you wire anything.

  • The incumbent owns identity and money. Account names, owners, amounts, close dates, anything a report or a commission plan is built on. These flow one way, in.
  • Indraft owns what was said. Call summaries, what a person committed to, the risk somebody named, the reading of a thread. These are the things your CRM has no field for and your agent produces constantly.
  • A small contested middle. Industry, lifecycle, job titles. Pick one owner per field. The next two sections are what happens when you get this wrong, which you will.

Mark what came from the incumbent

The import says where each value came from. One field, and it is what everything below depends on.

Agent

POST /v1/companies:upsert name "Halden Systems" domain "haldensystems.io" industry "Industrial software" assertionKind "imported" attributionLabel "HubSpot nightly" idempotencyKey "hubspot-company-4471"

Key it on the incumbent's own record id and the job is safe to re-run. The second night finds the same record rather than making another:

Agent

replayed truematchedBy domaintargetId co_01M0TTFJYRJE2WJ5C4P3RJADFK

The agent writes what it learned

Now the part your CRM cannot do. The agent records the call and updates what it now believes, and what it writes is marked as something it observed rather than something it was handed.

You

Halden call just finished. SSO is a hard blocker but SAML is acceptable at launch, and budget sits with Marek rather than procurement. They are a logistics company, not an industrial software one; whoever typed that into HubSpot was guessing.

Ask which system said what, and you get an answer per field:

Agent

domain importedname importedindustry observed

The night the import tries to undo it

This is where every "put a tool beside your CRM" plan dies. Tonight's export still says Industrial software, and it runs at two in the morning with nobody watching. In most setups it wins, silently, and the agent's work is gone by Tuesday.

Here it does not win, and it says so:

Agent

status noopchanges noneheld field industry kept Logistics software proposed Industrial software standing observed incoming imported

Something a person or an agent observed outranks something an import carried. The import is not rejected and not applied quietly: the response names the field, both values, and the standing of each, so your workflow can report it. A disagreement between your two systems is now a line in a log rather than a value that changed while you slept.

That is also your answer for the contested middle above. Get the ownership split wrong and you find out from a held-fields report rather than from a customer.

Push back only what the agent learned

The other direction is where people build a loop: they push everything back, the incumbent exports it again, and the two systems trade the same value forever. The change feed carries the assertion kind, so the filter that prevents it is one condition.

Agent

GET /v1/changes?since=...upsert_company observed industryrecord_interaction observed -upsert_company imported domain, name, industry

Send back the observed rows. Never send back the imported ones: those came from the incumbent in the first place, and echoing them is how a sync becomes a fight. Everything else about the wiring is the scheduling recipe: a trigger, an HTTP call, retries, and somewhere the failures go.

Verify it

  • Run the import twice. One record, not two. If you get two, the incumbent's record id is not reaching the idempotency key.
  • Have the agent change a field, then run the import again. The agent's value must survive and the response must name it as held. This is the test that decides whether the whole arrangement works.
  • Read the held-fields report after a week. Every entry is a field two systems disagree about. A long list means the ownership split needs a decision, not that the product is fighting you.
  • Check nothing imported flowed back. Look at what your workflow sent and confirm every row was observed. One imported row in there is a loop waiting for a quiet week.

Run it again

Nightly for the import, and whenever the agent works for the other direction. Revisit the ownership split once a quarter using the held report: the fields that keep appearing are the ones where you never actually decided, and deciding is cheaper than the argument you are currently having twice a month.