Abstract blue light streaks in dark space
Tutorial

How to Build a Sales Ops Agent in 30 Minutes

From amodal init to a working deal triage agent in Slack. Every command, every file, every decision explained.

Amodal TeamMarch 30, 202615 min read

This is a real walkthrough. Not a concept. Not a pitch. By the end, you'll have an agent that connects to Salesforce, triages your pipeline using your methodology, and answers questions in Slack. Every command is real. Every file is shown.

Prerequisites
Node 20+, a Salesforce instance with API access, a Slack workspace where you can add a bot. No ML experience. No AI background. If you can write markdown, you can build this.
What we'll build, step by step
Init
2 min
Connect
5 min
Skill
10 min
Test
5 min
Automate
3 min
Deploy
5 min
1

Initialize the project

Start with a directory. amodal init scaffolds the project structure, creates the config file, and sets up .gitignore.

terminal
$ mkdir acme-sales-ops && cd acme-sales-ops
$ npx amodal init
Creating project...
.amodal/config.json
.amodal/packages.lock
.env
.gitignore
✓ Project initialized

What you get:

project structure
acme-sales-ops/
├── .amodal/
│ ├── config.json # agent settings
│ ├── packages.lock # committed to git
│ └── packages/ # gitignored cache
├── .env # credentials (gitignored)
└── .gitignore
Key concept
The project is a git repo. Skills are markdown. Connections are JSON + markdown. Everything is version-controlled. The .amodal/packages/ directory is gitignored — it's a local cache of installed packages, like node_modules/.
2

Connect Salesforce

amodal connect does three things in one command: installs the connection package from the registry, runs the OAuth flow, and verifies the connection works against live data.

terminal
$ amodal connect salesforce
Installing connection salesforce@2.1.0... ✓
Salesforce requires OAuth2 authentication.
Opening browser for Salesforce login...
✓ Authenticated as you@acme.com (Acme Corp)
✓ Stored credentials in .env
Testing connection...
GET /sobjects/Opportunity → 200 (847 records)
GET /sobjects/Account → 200 (234 records)
GET /sobjects/Contact → 200 (1,891 records)
Connected. 47 endpoints available, 3 tested.

What just happened:

  1. 1.
    The salesforce@2.1.0 package was downloaded to the local cache
  2. 2.
    The package contains 5 files: spec.json, surface.md (47 endpoints), access.json, entities.md, rules.md
  3. 3.
    OAuth tokens stored in .env (gitignored). Never in the snapshot. Never in git.
  4. 4.
    Lock file updated: packages.lock now pins salesforce@2.1.0
  5. 5.
    No files created in your repo. Package is in the gitignored cache.

Now connect Slack:

terminal
$ amodal connect slack
Installing connection slack@1.3.0... ✓
Slack requires a Bot Token.
? Slack Bot Token (xoxb-...): ********
✓ Stored credentials in .env
Testing connection...
GET /conversations.list → 200 (23 channels)
Connected. 42 endpoints available, 1 tested.
3

Write the deal triage skill

This is where your domain expertise goes. A skill is a markdown file that tells the agent how to think about a class of problem. It's not code. It's methodology.

First, install the community deal-triage skill as a starting point:

terminal
$ amodal install skill deal-triage
Installed deal-triage v2.0.1

The community skill works out of the box. But your sales process is specific to Acme. Create an override file that adds your domain context on top of the package:

skills/deal-triage/SKILL.md
---
import: deal-triage
---
# Acme Sales Process
Our sales cycle is 45 days for mid-market, 90 for enterprise.
Weight urgency recommendations accordingly.
## Staleness thresholds by stage
- Prospecting: 7 days
- Qualification: 10 days
- Needs Analysis: 14 days
- Proposal: 14 days
- Negotiation: 21 days
- Enterprise deals: 2x all thresholds
## Rules
- A decision maker must be involved by Proposal stage.
If Opportunity is in Proposal and no Contact with
Role = "Decision Maker" exists, flag it.
- Never recommend discounting unless there's evidence the
deal is stuck on price.
- When referencing deal health, use our custom
Acme_Health_Score__c field (0-100, computed nightly).
The override model
The import: deal-triage header means this file inherits the community skill's base methodology and appends your additions. The community skill has generic deal triage logic. Your file adds Acme-specific thresholds, rules, and custom fields. When the package updates (new best practices), you get them automatically. Your overrides stay.

Your repo now looks like:

project structure
acme-sales-ops/
├── .amodal/
│ ├── config.json
│ ├── packages.lock # salesforce, slack, deal-triage pinned
│ └── packages/ # gitignored
├── skills/
│ └── deal-triage/
│ └── SKILL.md # your override
├── .env
└── .gitignore

One file in your repo. Everything else comes from packages. That's the point.

4

Test locally

amodal dev starts a local dev server with a chat UI. It hot-reloads when you edit skills or knowledge.

terminal
$ amodal dev
Resolving packages...
connection/salesforce@2.1.0 + overrides ✓
connection/slack@1.3.0 ✓
skill/deal-triage@2.0.1 + overrides ✓
Dev server running at http://localhost:3000
Watching for changes...

Open the chat UI. Try it:

Y
What should I focus on this week?
A
Queried Salesforce: 847 opportunities, filtered to 23 with activity flags
3 deals need attention:
1. Acme Enterprise ($240K) — in Negotiation for 52 days (2.5x your 21-day threshold). Last contact 18 days ago. No activity since the pricing call on March 2.
2. DataFlow Mid-Market ($85K) — moved to Proposal but no Decision Maker contact on the account. Your rule requires one by this stage.
3. CloudFirst ($42K) — in Discovery but last two meetings were cancelled. Qualification call hasn't been rescheduled. 12 days since last activity.
Health scores: Acme 34/100, DataFlow 61/100, CloudFirst 47/100

The agent queried Salesforce live, applied your staleness thresholds from the skill, checked for decision makers per your rule, and used the custom health score field. All from the markdown file you wrote.

Hot reload
Edit SKILL.md and save. The agent picks up the changes immediately. Change a threshold from 21 days to 14. Ask again. The results change. This is the dev loop: edit markdown, test in chat, iterate.
5

Add a weekly automation

Instead of asking manually, schedule the agent to post a pipeline review every Monday.

terminal
$ amodal install automation weekly-pipeline-review
Installed weekly-pipeline-review v1.0.0

The automation defines a prompt + schedule + output channel. On deploy, the platform runs it every Monday at 8am and posts results to #sales-ops in Slack.

6

Validate and build

Before deploying, validate that everything resolves correctly. Then build the snapshot.

terminal
$ amodal validate
Resolving packages...
connection/salesforce@2.1.0 ✓
connection/slack@1.3.0 ✓
skill/deal-triage@2.0.1 ✓
automation/weekly-pipeline-review@1.0.0 ✓
Resolving overrides...
skills/deal-triage/SKILL.md ← import: deal-triage ✓
Validating resolved config...
skill/deal-triage references entities: opportunity, contact, activity
matching connections found: salesforce ✓
All checks passed.
terminal — build the snapshot
$ amodal build
Resolving... ✓
Validating... ✓
Wrote resolved-config.json (187KB)
The snapshot
resolved-config.json is a single JSON blob with every package merged, every import resolved, every override applied. No package references remain. This is what the runtime loads. You can inspect it, diff it, or bake it into Docker. This is the free, OSS deploy path — no platform account needed.
7

Deploy

Two options. Pick one:

Option A: Self-hosted (free)

$ docker build -t acme-sales-ops .
$ docker run \
-e SALESFORCE_CLIENT_ID=... \
-e SLACK_BOT_TOKEN=... \
acme-sales-ops

Zero platform dependency. Your Docker, your infra, your data.

Option B: Platform (paid)

$ amodal deploy -m "initial deploy"
Resolving... ✓
Validating... ✓
Uploading snapshot (187KB)... ✓
✓ deploy-a3f8d2e
✓ Live at https://acme.amodal.dev/v1

Adds: rollback, environments, secrets store, observability, admin UI.

Act 2

The sales team uses it

The developer is done. Now the actual users take over.

8

Monday morning: the sales manager asks a question

It's 8:05am. The weekly pipeline automation already posted to #sales-ops. But Sarah, the sales manager, has a specific question before the 9am pipeline meeting:

# sales-ops
S
Sarah Chen 8:05 AM
@amodal what's the full story on the Acme Enterprise deal? Should I escalate to David?
A
Amodal 8:05 AM
Acme Enterprise — $240K, Negotiation stage
Queried: Opportunities, Activities, Contacts, Account for Acme Corp
In Negotiation for 52 days (your threshold: 42 days for enterprise, 2x the 21-day standard). Last activity was a pricing call on March 2 — 18 days ago with no follow-up.
The account has 3 contacts but none with Role = "Economic Buyer." The VP of Procurement (Lisa Park) was added to the account 2 weeks ago but has had zero engagement.
Health score: 34/100 (down from 67 three weeks ago).
Recommendation: yes, escalate. This deal needs executive attention. Suggest David reach out to Lisa Park directly. The 18-day gap after a pricing call usually signals internal budget review or competing vendor.
S
Sarah Chen 8:06 AM
Didn't know about Lisa Park. Pulling David in now. Thanks.

30 seconds. The agent queried Salesforce live, pulled opportunities + activities + contacts, applied the staleness rules from the skill, found the missing executive engagement, and made a specific recommendation. Sarah didn't open Salesforce. She didn't run a report. She asked a question in Slack.

Act 3

The VP of Sales customizes it

Jane doesn't use git. She uses the admin UI.

9

Jane updates the playbook

After the pipeline meeting, Jane decides the Proposal threshold should be 21 days, not 14. She opens the admin UI in her browser:

Admin UI → Skills → deal-triage
1 unpublished change|Editing as Jane Smith (VP Sales)
## Staleness thresholds by stage
- Prospecting: 7 days
- Qualification: 10 days
- Needs Analysis: 14 days
+ - Proposal: 14 21 days
- Negotiation: 21 days

Jane clicks Preview. A sandbox chat opens. She tests: "Which deals in Proposal are stale?" The agent uses the new 21-day threshold. Looks right.

Jane clicks Publish. Behind the scenes, the platform merges her branch to main. Webhook fires. New snapshot deploys. The production agent now uses 21 days for Proposal.

The developer sees a clean commit on main:

git log
a3f8d2e
Updated Proposal staleness threshold to 21 days (Jane Smith via Admin UI)
What just happened
A VP of Sales changed the agent's behavior in production. No git. No PR. No developer involvement. She edited in a rich text editor, previewed against a sandbox, and published. The change is in git (the developer can see it, review it, revert it). But Jane never touched the terminal.
10

Jane talks to the agent to add a rule

Jane has another change in mind, but this time it's easier to just tell the agent:

Admin UI → Chat (Configure via conversation)
J
Add a rule that deals over $100K need VP sign-off before moving to Proposal stage.
A
I'll add this to the deal-triage skill rules:
- Deals over $100K require VP sign-off before
moving to Proposal. If an opportunity > $100K is
in Proposal without a note from a VP, flag it.
Want me to save this change?
J
Yes. Publish it.
A
Done. Rule added and published. Live in production now.

30 seconds. A new business rule is in production. Jane described what she wanted in English. The agent translated it to the skill format, saved it, and deployed it.

Act 4

The controller monitors it

Cost, quality, and the agent's own learning.

11

Cost check

The controller, Mike, opens the admin UI dashboard on the last day of the month:

Admin UI → Costs → acme-sales-ops
$12.40
This month
340
Sessions
$0.036
Per session
8
Active users
deal-triage
$8.06
pipeline-review
$3.14
ad-hoc questions
$1.20
Model Arena suggestion:Switch pipeline-review to Claude Haiku (currently Sonnet). Estimated savings: $1.80/mo with no quality loss based on eval scores.

$12.40/month for 340 sessions across 8 users. Cheaper than one hour of a BDR's time. Mike clicks the Model Arena suggestion and saves $1.80/month on the automation.

12

Session replay: is it actually answering correctly?

Mike clicks into a specific session to see exactly what happened:

Session Replay → session-8f3a2e1 (Sarah Chen, 8:05 AM today)
0.0s
User: What's the full story on the Acme Enterprise deal?
0.2s
Skill: deal-triage activated (matched: deal query + specific account)
0.4s
Dispatch: task agent → Salesforce (Opportunity + Account + Contact + Activity)
1.1s
Task result: 247 tokens. Acme Corp, $240K, Negotiation, 52 days, 3 contacts, health 34
2.3s
Reasoning: Applied enterprise threshold (42d), checked decision maker presence, health trend
3.1s
Agent: In Negotiation for 52 days... recommend escalate...
Cost
$0.004 — 1,680 input, 420 output (Claude Sonnet)

Full transparency. Every tool call, every skill activation, every token, every penny. Mike can see exactly why the agent said what it said.

Act 5

The agent learns

Month 1 is good. Month 6 is transformational.

13

Knowledge proposals: the agent discovers a pattern

After 30 days of triaging 847 opportunities, the agent notices something:

Admin UI → Knowledge Proposals
Proposed by agent · Tuesday at 2:14 PM

"Deals with no activity for 14+ days in Negotiation stage close at 8% vs 34% baseline."

I analyzed all 847 opportunities closed in the last 6 months. Of the 123 that went silent for 14+ days in Negotiation, only 10 closed-won (8.1%). Baseline close rate for Negotiation-stage deals is 34%. Recommend auto-flagging these as high-risk.

Source: 847 opportunities, 6-month lookback, Salesforce Activity + Opportunity data

Proposed by agent · Wednesday at 9:30 AM

"Rep Marcus Rivera consistently leaves deals in Discovery 2x longer than team average."

Marcus's deals average 22 days in Discovery vs 11 days team average. His Qualification-to-Proposal conversion rate is the same as the team (68%), suggesting the extra Discovery time isn't adding value. Consider flagging his Discovery deals at 14 days instead of 21.

Source: per-rep stage duration analysis, last 90 days

Sarah approves the first proposal. The agent now auto-flags silent Negotiation deals. The second proposal she edits: she changes it from a flag to a private note to Marcus's manager, not a public flag. Then approves.

The flywheel
Every approval makes the next triage sharper. After 6 months, the agent knows: which reps leave deals too long, which verticals have longer cycles, which deal sizes need executive sponsors, which competitors trigger price-sensitivity patterns, and which "stale" deals are actually just slow enterprise procurement. Session 200 is materially faster and more accurate than session 1.

The full lifecycle

Who
Where
What they do
How often
Developer
CLI
init, connect, write skills, build, deploy
Once (setup) + when methodology changes
Sales manager
Slack
Asks questions, gets pipeline briefings
Daily
VP of Sales
Admin UI
Edits skills, adds rules, previews, publishes
Weekly
Controller
Admin UI
Checks costs, reviews sessions, approves model swaps
Monthly
The agent
Runtime
Triages deals, runs automations, proposes knowledge updates
Continuous

What you built

1
file in your repo
2
systems connected
847
deals triaged
30
minutes

One markdown file with your sales methodology. Two amodal connect commands. One amodal install for the skill. One amodal install for the automation. The agent now triages 847 deals using your specific thresholds, checks for decision makers per your rules, and uses your custom Salesforce fields. Every Monday at 8am, it posts a pipeline summary to Slack.

The flywheel starts here
After a month, the agent proposes a knowledge update: "Deals with no activity for 14+ days in negotiation close at 8% vs 34% baseline." You approve it. Every future triage is sharper. After six months, the agent has learned which reps leave deals too long, which verticals have longer cycles, which deal sizes need executive sponsors. The system gets smarter from use. (See Act 5 for how this looks in the admin UI.)
Bonus

Connect a second system

The real power: the agent sees across systems.

14

Add LinkedIn Sales Navigator

The sales team wants the agent to cross-reference deal contacts with LinkedIn data. Who's the decision maker? Have they changed jobs recently? Are they connected to anyone at Acme?

terminal
$ amodal connect linkedin-sales-nav
Installing connection linkedin-sales-nav@1.1.0... ✓
LinkedIn Sales Navigator requires an API key.
? LinkedIn API Key: ********
✓ Stored credentials in .env
Testing connection...
GET /people/search → 200
Connected. 12 endpoints available.

One command. Now the agent can see both Salesforce and LinkedIn. Update the skill to use it:

skills/deal-triage/SKILL.md (add to the bottom)
## Cross-system enrichment
When analyzing a deal, check LinkedIn for the key contacts:
- Has the champion changed roles in the last 90 days?
If yes, flag it. Role changes often stall deals.
- Is there a mutual connection between our team and
the decision maker? Suggest a warm intro.
- What's the decision maker's recent activity?
If they're posting about budget cuts or reorgs, note it.

Now Sarah asks the same question, but gets a richer answer:

# sales-ops
S
Sarah 9:15 AM
@amodal full story on the DataFlow deal?
A
Amodal 9:15 AM
Queried: Salesforce (Opportunity, Account, Contacts) + LinkedIn Sales Nav (3 contacts)
DataFlow Mid-Market — $85K, Proposal
Issue: No Decision Maker contact on the account (your rule requires one by Proposal).
LinkedIn insight: The account's VP of Engineering (James Wu) changed roles 3 weeks ago — he was promoted from Director. He posted about "modernizing our tooling stack" last Tuesday. This is likely the decision maker.
Connection: Your AE (Marcus) has 2 mutual connections with James. Suggest a warm intro through either Alex Chen or Dana Park.
Recommendation: have Marcus reach out to James via Alex Chen. Add James as the Decision Maker contact in Salesforce. The deal is viable but needs executive engagement before it can move forward.
Cross-system intelligence
The agent queried two systems (Salesforce + LinkedIn), correlated the data (matched contacts by name/company), applied the skill's rules (champion changed roles? mutual connections?), and produced an actionable recommendation. The human didn't alt-tab between two apps or manually cross-reference. One question. One answer. Two systems.

Add a third connection tomorrow? amodal connect gong gives the agent call transcripts. amodal connect hubspot gives it marketing engagement. Each connection adds a dimension. The skill tells the agent what to do with it. The connections are just plumbing. The skill is where your expertise lives.

This tutorial used real CLI commands and real file structures. Fork the reference repo and try it yourself.