Your GTM belongs in gitRegister
Blog

Building a Self-Driving GTM Engine with Agentic Infrastructure

21 Sept
11min read
AurelienAurelien

A self-driving GTM engine is a revenue system that keeps working when nobody is logged in: one data model of accounts and contacts, workflows that fire as that data changes, and AI agents that reason inside those workflows, all defined in code and deployed the way software is deployed. Agentic GTM infrastructure is the layer underneath it.

Most of what gets called GTM automation is task automation. A sequencer sends the email. An enrichment tool fills the column. A routing rule sets the owner. Each does one job well, and none of them holds the state of the account between jobs, so a person holds it instead: in a spreadsheet, in a saved view, in their head.

Agentic infrastructure moves that state into the system. The data model is the state. Workflows are the transitions. Agents are the part that reads context and decides, inside limits the workflow sets. What makes it infrastructure rather than a product tour is that all three are declared in files, reviewed as diffs, and reconciled into a running workspace by a deploy.

Three questions decide whether a platform can carry an engine like that, and they are the three sections below: how agents get built, versioned and deployed; how multi-step revenue logic is orchestrated and called; and how first- and third-party data becomes the single record that routing and scoring read. The layer itself is covered separately in what GTM infrastructure is, and the operating model in GTM as code.

Building and deploying custom AI sales agents #

A sales automation agent, in the common sense of the phrase, is a hosted assistant with a prompt box and a fixed menu of things it can do. It writes the email, books the meeting, updates the field. The work is one step long, and the vendor owns the definition.

An agent on GTM infrastructure is a declared resource with a prompt, a step budget, a set of capabilities, an explicit list of what it may call, and a grade it has to pass. It runs many steps, keeps state across them, and carries the same lifecycle as the rest of the workspace.

Sales automation agentAgent on GTM infrastructure
Where the definition livesThe vendor’s consoleA file in your repository
What it can callA fixed menuTools, connector actions, data models, sub-agents
State across stepsOne prompt, one answerRun memory, workspace memory, per-user memory
LimitsImplicitA step budget and a read-only flag per resource
QualityRead the outputAn evaluator with a rubric and a threshold
Change controlSomeone edited it on TuesdayA diff, a plan, a deploy, a version to roll back to

The definition

Here is a qualification agent as code. Every capability is passed as a handle, so the platform deploys dependencies first and injects their identifiers:

typescript
import { defineAgent } from "@cargo-ai/cdk";
import { hunter } from "../connectors/hunter";
import { openai } from "../connectors/openai";
import { contacts } from "../models/contacts";
import { enrich } from "../tools/enrich";

export const qualifier = defineAgent("qualifier", {
  connector: openai,
  languageModel: "gpt-4o",
  systemPrompt:
    "Qualify inbound leads against the ICP in workspace memory. Enrich what is missing. Explain every verdict with the fields it rests on.",
  maxSteps: 12,
  capabilities: ["webSearch", "memory"],
  uses: [
    { ref: contacts, readOnly: true }, // a data model, read-only
    enrich, //                            a tool
    hunter.actions.findEmail, //          a connector action
  ],
  triggers: [{ type: "cron", cron: "0 9 * * *", text: "Daily qualification" }],
  evaluator: { rubric: "Did it correctly qualify the lead?", threshold: 0.8 },
});

Four things in that file are the reason it behaves consistently rather than impressively. uses is a closed list, so the agent cannot reach anything nobody granted it. readOnly on the model means qualification cannot silently rewrite the record it is judging. maxSteps bounds the run. evaluator grades the output against a rubric and a threshold, which turns “the agent seems good” into a number attached to every run.

Persistent context, concretely

“Persistent context” is three separate mechanisms, and they fail differently, so it is worth keeping them apart. Run memory carries previous steps and action outputs inside a single execution. Workspace memory is shared across every agent and is where durable facts belong: the ICP definition, the positioning, the rules of engagement. User memory persists per end user for the assistant-style surfaces. Underneath all three, the agent reads structured resources through uses and unstructured ones (playbooks, transcripts, documentation) through file search over the workspace library.

The distinction that matters in practice: knowledge every agent should share belongs in workspace memory or in a versioned file, not in one agent’s prompt. A prompt is a copy, and copies drift.

How an agent gets deployed

Deployment is the reconcile loop, not an export step:

shellscript
cargo-ai project plan     # compile, diff against deployed state
cargo-ai project deploy   # apply in dependency order

plan marks every resource + create, ~ update, = noop, or - delete, and changes nothing. deploy applies in dependency order and persists state after each resource, so an interrupted deploy is re-runnable rather than half-applied. Deletions are opt-in with --prune. Drift is explicit: --refresh re-reads the live resources and folds out-of-band edits into the plan, where code wins, and a resource deleted in the UI stops the deploy rather than reappearing silently.

Two properties follow from that loop, and they are what “as code” is for. The definition of the agent qualifying your inbound is readable without opening a product, and any change to it arrives as a diff someone can approve or reject.

Agents built in the UI and agents built in code are the same resource. A team can draw the first version, then adopt it into the project and manage it as code from there.

Where the agent actually runs

Deployment and invocation are different questions. Once deployed, an agent is reachable from a play (on a schedule or as rows change), from a Slack mention, from the Chrome extension, from embedded chat, from a button in the CRM, from the CLI, and from an MCP server that publishes it to external assistants. The same definition answers all of them, which is the point: the qualification logic does not fork per surface.

shellscript
cargo-ai ai chat create --agent-uuid <uuid> --trigger '{"type":"draft"}' --name "Qualify"
cargo-ai ai message create --chat-uuid <uuid> \
  --parts '[{"type":"text","text":"Qualify acme.com"}]' --wait-until-finished

For how several agents divide work between them, see multi-agent workflows for B2B revenue teams. For what agents are doing across the funnel today, with the human gate on each, see AI agents for GTM.

Orchestrating end-to-end sales workflows with AI #

Agents decide. They do not, on their own, constitute a revenue process. A qualification verdict is worth nothing until something enriches the record before it, routes on it after it, writes it back to the CRM, and retries the step that failed at 3am. That connective logic is the workflow, and whether it is programmable is what separates orchestration from a folder of automations.

The test to apply to any workflow automation platform: can one definition express a multi-step process with branching, iteration, sub-agents and typed inputs, and can that same definition be called from every surface without being rebuilt? Isolated task execution does not compose into a sales cycle, and a canvas that cannot be diffed cannot be reviewed.

Revenue logic as a typed function

typescript
import { defineWorkflow } from "@cargo-ai/cdk";
import { z } from "zod";
import { qualifier } from "../agents/qualifier";
import { enrich } from "../tools/enrich";

export const qualifyLead = defineWorkflow(
  "qualify-lead",
  {
    input: z.object({ email: z.string() }),
    output: z.object({ company: z.string(), verdict: z.string() }),
    uses: { enrich, qualifier },
  },
  ({ input, uses }) => {
    const enriched = uses.enrich({ email: input.email });
    const verdict = uses.qualifier({
      prompt: `Qualify ${input.email} at ${enriched.company} against the ICP.`,
    }).answer;

    return { company: enriched.company, verdict };
  },
);

input and output are Zod schemas, so the workflow’s contract is typed at authoring time rather than discovered at runtime. uses declares the tools and agents it calls, which is what lets the deploy order dependencies. Branching, switches and loops are ordinary JavaScript control flow. Scoring, classification, allocation, delays, file search and email are native steps rather than glue you maintain.

Workflows authored in code and workflows drawn on the canvas compile to the same artifact and run identically. The code path is an authoring surface, not a separate product, which matters when one person prefers a diagram and another prefers a pull request.

One definition, every caller

A deployed workflow is reachable as a tool, on demand or in bulk:

shellscript
cargo-ai orchestration run create --workflow-uuid <uuid> \
  --data '{"email":"john@acme.com"}' --wait-until-finished

cargo-ai orchestration batch create --workflow-uuid <uuid> \
  --data '{"kind":"records","records":[{"email":"john@acme.com"}]}'

The same logic runs from the UI, from the CLI, over the API, on a cron trigger declared in the file next to it, inside another workflow, inside an agent’s uses array, and over MCP to an external assistant. Bind it to a data model with definePlay and it becomes continuous: the play watches the model and emits a run per row that changes, with changeKinds selecting which changes count and a schedule that can be real time, cron, dbt-completion, or dependent on another play finishing.

That is the reduction in manual setup worth caring about. Not a shorter onboarding, but one place where revenue logic lives, with every surface calling it instead of reimplementing it. The wider argument for that layer is in revenue orchestration.

Integrating data for automated lead routing and scoring #

Routing and scoring are only as good as the record underneath them. Most stacks hold six copies of an account, one per tool, each partly stale, and reconcile them with a job that is really a person. A unified data model is what removes that reconciliation from the process.

Cargo holds three kinds of model in one warehouse, and what differs is who owns the schema and who writes the rows:

Model typeSchema ownerWritable from a workflowTypical use
ObjectYou (built-in or custom)YesSourced lists, internal pipelines
IntegrationThe connected sourceNo, through the source’s actionsMirrored CRM, warehouse, webhook, file data
UnifiedCargoRead-onlyOne deduplicated view of accounts and contacts

Unification resolves records that describe the same real-world entity across connected sources using shared identifiers, and produces four native models: Account, Contact, Account Event, and Contact Event. It runs automatically and needs no deploy. The result is one canonical row per company that plays, agents and SQL all read.

First-party data arrives through the CRM, warehouse, product events and webhooks. Third-party data arrives through connector actions that enrich against providers, waterfall style, so a missing firmographic or a missing work email is filled by the next source rather than left blank. Both land in the same model, which is why a context-aware SDR workflow is possible at all: the agent reading the record sees the CRM history and the enrichment in one query instead of stitching two exports.

What routing looks like on top of it

Routing reads that record and assigns an owner:

  • Members are your reps, imported from the connected CRM and enriched with metadata such as languages or specializations.
  • Territories are named pools of members, distributed across with weighted round-robin and an optional fallback member so no lead falls through.
  • Capacities are workload limits, fixed per period or computed from live pipeline data such as open deals.
  • Allocation history records every assignment with a link to the play run that caused it, and the assignment syncs back to the CRM.

The team structure is itself code, so the routing decision and the roster it routes across ship in the same diff. A member is addressed by email and adopted from the CRM sync rather than created by a deploy, which is what lets the same territory file deploy into a second workspace where every uuid differs:

typescript
import { defineCapacity, defineMember, defineTerritory } from "@cargo-ai/cdk";
import { leads } from "../models/leads";

const dana = defineMember("dana@acme.com");
const sam = defineMember("sam@acme.com");

export const emea = defineTerritory("emea", {
  members: [
    { ref: dana, weight: 2 },
    { ref: sam, weight: 1 },
  ],
  fallback: dana,
});

export const weekly = defineCapacity("weekly_inbound", {
  model: leads,
  memberCapacity: 25,
});

Inside a workflow, the allocation reads those handles directly, and scoring is a list of weighted criteria rather than a formula buried in a filter:

typescript
// inside the defineWorkflow body: ({ input, uses, scoring, allocate }) => { … }
const scored = scoring({
  criterias: [
    { name: "Target industry", value: enriched.isTargetIndustry, score: 40 },
    {
      name: "Hiring for the role we sell to",
      value: enriched.isHiring,
      score: 30,
    },
  ],
});

const allocated = allocate({
  recordId: input.recordId,
  type: "territory",
  territoryUuid: emea.uuid,
  capacityUuid: weekly.uuid,
});

A lead enters through a play trigger, matching rules narrow the eligible reps, capacity checks filter to those with room, round-robin picks, and the CRM is updated. Scoring is the same shape: a native scoring step, a classification step, or an agent verdict, all reading the unified record and writing back a column other plays can trigger on. The older, purely rules-based version of this problem is covered in lead routing for B2B SaaS.

The technical benefit at scale is narrow and worth stating plainly: because the model is one table rather than six exports, routing and scoring are queries over current state, not batch jobs over yesterday’s snapshot, and a change to either ships as a diff rather than as an afternoon of clicking through admin panels.

What this is not #

A self-driving GTM engine is not an unattended one. The agents run continuously; the decisions that reach a human being still pass a gate, because a wrong email is not a wrong row. What the infrastructure changes is where the gate sits: on a reviewable diff and on evaluator thresholds, rather than on a person watching every run.

It is also not the right first move for one-off work. If the job is a list of 300 accounts by Thursday, a spreadsheet-shaped tool gets there faster. The trade shows up the third time the same job runs, when someone owns the result, and when the person who built it changes teams. That is where an application has to become a system.

For the properties that make a platform operable by an agent rather than merely callable by one, see the agent-ready GTM platform.

FAQ #

AurelienAurelienSept 21, 2026

Give your agents a runtime

Bring the agents you have.Start free, deploy in one command.