The problem: a CRM token is too much power
The usual build hands the agent a HubSpot private app token. That token can update any deal, any property, in any direction. Whatever guardrails sit in the prompt, the token itself does not know the difference between “move forward one stage” and “rewrite the pipeline”.The idea: narrow the action, check the real deal, ask a person
Three things make a CRM write safe enough to hand to an agent:- A narrow action. Not “update HubSpot”, but “change this deal’s stage, and maybe its amount”.
- The real state, not the agent’s story. If the agent says the deal is at “qualified to buy”, ask HubSpot.
- A person for every write. Someone who can be named has signed off on this deal being changed.
How Parmana does it
The HubSpot connector has two actions:hubspot:deal-fetch to read a deal and hubspot:deal-update to change it. The
update can touch exactly two properties, dealstage and amount, in one call. A request naming any other property is
refused before any network call, not silently trimmed.
The update runs under the policy hubspot-deal-update 1.1.0:
- Every update needs a signed approval for that deal, whatever the amount. Under the older 1.0.0, small updates ran with no person; since 2026-09-30 none do.
- Stages only move forward through a fixed order, from “appointment scheduled” to “closed won”, with one exception: “closed lost” is allowed from any stage that is not already closed. Moving backward, out of a closed stage, or to a stage the order does not know is refused, even with an approval.
- The agent’s facts are checked against HubSpot. Before the policy’s decision is trusted, a verifier fetches the real deal and re-derives the current stage, whether the move is allowed, and how much the amount changes. If the agent’s declared facts do not match, the update is refused. If HubSpot cannot be reached, it is refused too.
- The proposed values are bound. The stage and amount the agent declares must equal the stage and amount in the request, so it cannot get approval for one change and send another.
What was proven, and where
The connector is tested against a mock HubSpot server in every test run, through the realPOST /execute route. In
an earlier session it was also run live against a real HubSpot developer test account, three of three passing: a read
of a deal that does not exist reached HubSpot and came back with a real error; a refused update made no call; and a
real test deal’s amount was read, nudged by 1, confirmed changed by an independent read, then put back to its exact
original value.
That live run used a locally started server and a test account, not Parmana’s production deployment and not a
company’s real pipeline. It ran before every update needed a signed approval, and before the live suite was rewritten
to go through the published SDK. The rewritten suite has not been run live since.
What it does not do
- The approval covers the deal, not the new values. A person approves changing this deal; the approval does not fix which stage or what amount. The stage rules and the HubSpot check still apply, but within them the approver is trusting the agent’s numbers for that one use.
- Only deals, only stage and amount. No contacts, companies, deletes, other properties or webhooks.
- One stage order for everyone. Per-pipeline stage orders are not wired up.
- No real company pipeline is connected. The only live run was a test account.
- Reads need approval too. Even fetching a deal needs a signed approval for that deal, which makes reading slower than most teams expect. That is deliberate, and its own essay.
- One person approves. In Parmana’s own production one approver is trusted, and I hold that key.
- It does not stop a direct call. An agent that keeps its own HubSpot token can call HubSpot without Parmana.