Skip to content

Shape a motion

motion.yaml is the motion’s outline. It’s short on purpose: the logic is in the steps, and this file says how they connect and who decides. Here’s the Quickstart’s:

motions/domain-check/motion.yaml
title: Domain check
approvers: [ana]
reasons: [no_domain, unregistered]
input: { list: first-batch }
steps:
- id: domain
title: Look up the domain
run: steps/domain.ts
sources: [rdap.lookup]
- id: note
title: Draft a CRM note
run: steps/note.ts
crm: { propose: [notes.create] }
- id: write
kind: changeset
title: Write the notes
approval: required
queues:
domain_owner: { title: Who owns this domain?, owner: ana, deadline: 1d, default: hold }

This page walks through laying one out. Every key is in the motion.yaml reference.

List the steps in the order they run. Each has an id, lowercase with underscores, a title people will see on the batch screen, and for steps that run code, run: the file, relative to the motion folder.

steps:
- id: identity
title: Match the CNPJ
run: steps/identity.ts
- id: fit
title: Check the fit
run: steps/fit.ts

Each step runs after the one before it. A later step reads what earlier steps found as company.facts.<step id>.

A step can’t do anything it wasn’t granted. List, for each:

  • sources: the operations it may call, such as [cnpj.match, rdap.lookup], or a custom source’s brasilapi.cnpj.
  • crm.read: the CRM objects it reads: companies, contacts, deals, owners, notes.
  • crm.propose: the changes it may propose: companies.update, contacts.create, contacts.update, associate, notes.create, notes.pin.
  • cap: the most it may spend on sources, per_company_usd and per_batch_usd.
- id: people
title: Find people
run: steps/people.ts
sources: [harvestapi.lead_search, harvestapi.profile]
crm: { read: [contacts] }
cap: { per_company_usd: 0.40, per_batch_usd: 80 }

motions check fails if a step reads or proposes something your CRM can’t do. A call to a source that isn’t listed fails at run time with “add it to the step’s sources”.

If any step proposes CRM changes, add a changeset step after them. It runs no code: it gathers the proposals of the steps before it into one package for an approver.

- id: write
kind: changeset
title: Write the contacts

A motion can have several changesets, each collecting the proposals made since the previous one: contacts first, then the notes that refer to them.

approvers: [hugo]
queues:
identity: { title: Which registration is it?, owner: ari, deadline: 4h, default: hold }
  • approvers: the handles who approve changesets, release checkpoints, upgrade batches and make versions live. At least one.
  • queues: one per question type your steps ask, with its owner, deadline and default. See Ask questions.
  • reasons: the reasons steps may give when they hold or drop a company. When you list them, a step that uses any other fails, which keeps the batch’s reasons tidy.

after overrides “the step before”. Two steps that both need only identity can run side by side, and a third can wait for both:

- { id: identity, run: steps/identity.ts }
- { id: signals, run: steps/signals.ts, after: [identity] }
- { id: people, run: steps/people.ts, after: [identity] }
- { id: score, run: steps/score.ts, after: [signals, people] }

when runs a step only for companies where an earlier fact, a core field or a CRM field has one of the listed values. The others show the step as skipped.

- id: cnpj
run: steps/cnpj.ts
when: { fact: core.country, in: [BR, Brazil] }

fact starts with core, fields, or the id of an earlier step: fit.segment.

Some steps need the whole batch: finding duplicates within the list, ranking, or stopping until Monday. Use a batch step or a checkpoint.

A step that fails for a passing reason, such as a timeout or a provider’s rate limit, runs again, twice by default. Set retries from 0 to 10. Errors in your code aren’t retried: they park the company until someone retries it.

Motions is built by Pruma. These docs describe what the platform does today; Limitations lists what it doesn't do yet. Open the app