All Posts
· 9 min read

Building a CRM Where the AI Isn't a Feature, It's the Architecture

Every CRM on the market has an AI story now. It’s always the same: take your existing CRUD app, add a chatbot sidebar, wire it to OpenAI, ship a press release. The AI is a feature. A decoration. Something you could rip out and the product would still work.

We went the other direction. I lead engineering at a marketing agency, and we needed internal tooling that could handle the full client lifecycle: prospect research, web audits, proposals, content planning, writing, publishing, rank tracking. The whole pipeline. Small team, lots of clients. The math doesn’t work if a human has to touch every step.

We tried the obvious path first. Off-the-shelf CRM, Zapier automations, AI bolted on at the edges. It half-worked. The automations were brittle. The AI integrations couldn’t handle multi-step workflows. Every time a step failed, a human had to figure out where the chain broke and restart it manually. We spent more time maintaining the glue than doing client work.

So we asked a different question. Not “where can we add AI to our workflow?” but “what if the AI agent is the primary user of our internal tools?”

We built a CRM from scratch where the agent isn’t an assistant. It’s a coworker. The system is designed so an autonomous agent can run client pipelines end to end, and humans step in for review, judgment calls, and client-facing moments.

Stations, not features

Most agent integrations give the model a flat list of tools. “Here are 60 things you can do, figure it out.” That doesn’t scale. The agent wastes tokens deciding which tools are relevant, hallucinates tool names, or tries to use a content tool when it should be doing CRM work.

We structured the system around stations. A station is a discrete skill domain, like a workstation in a factory. Content generation is a station. Proposal creation is a station. Prospecting is a station. Each one has its own work queue, its own context, and its own set of available tools.

An agent session “enters” a station, does the work, and exits. When you’re at the content station, you don’t see proposal tools. When you’re at the scanning station, you don’t see CRM tools. The agent always knows where it is and what’s relevant.

The station model also carries a skill_context field: process steps, warnings, domain-specific knowledge the agent needs to do good work at that station. Think of it as the training manual that sits at each workstation.

Time-aware scheduling

The second piece is a heartbeat system. The agent polls the CRM periodically and asks: what should I do right now?

The answer depends on when the question is asked. We built a cadence system: hourly, morning, afternoon, daily, weekly, monthly. Each station has schedules attached to specific cadences. Rank data checks run daily. Performance summaries run Monday mornings. Content plan refreshes run monthly.

When the agent asks “what’s next?”, the system collects all active cadences for the current time, finds matching schedules across stations, gathers any pending work items, and returns a prioritized recommendation. The response tells the agent which station to enter and what’s waiting there.

The agent doesn’t maintain its own task list. The CRM IS the task list. Work items get created by user actions, scheduled jobs, or previous agent operations. The agent just asks “what’s next?” and the system tells it.

This was one of the better design decisions we made. Early versions had the agent trying to figure out its own priorities, and it was bad at it. Moving the scheduling logic into the application (where you can actually tune it, monitor it, and debug it) made the whole system more predictable.

Domain-decomposed MCP servers

MCP (Model Context Protocol) is how agents discover and use tools. We split our tools across multiple MCP servers, one per business domain. Content tools, CRM tools, scanning tools, proposal tools, prospecting tools, reporting tools, and a few more. Each server registers a curated set of tools relevant to its domain.

The split matters for the same reason stations matter: scoping. When the agent connects to the content server, it sees content tools. Clean, focused context. No confusion about whether create_draft is a content draft or a proposal draft.

Each tool is a self-contained unit with a schema and a handler. And the handlers dispatch async jobs instead of blocking. Content generation can take minutes. The agent triggers it and moves on. When the job finishes, the system notifies the agent to check back.

Notification-driven chains

The system doesn’t just wait for the agent to poll. When something important finishes, it pushes a notification to the agent gateway. The notification includes what happened and what to do next.

“Scan for [business] completed. Score: 72/100. Enter proposal station to generate a proposal.”

This creates autonomous chains. A human triggers one action. That action completes, the agent picks up the next step, that step completes, the agent picks up the next. One click from a human, finished deliverable out the other end.

The agent moves between stations following these notifications, doing the work at each stop. It’s less like a chatbot answering questions and more like a new employee working through their morning to-do list.

Observability on every LLM call

One decision we made early: every AI operation stores its full debug metadata on the model it produced. Not in a separate logging table. On the record itself. The model, token counts, latency, and enough context to understand what happened.

When a generated proposal looks off, you don’t grep through logs. You open the record and look at the debug panel. You see what went in and what came out. Across every AI-powered feature in the system, the same pattern.

This sounds obvious but most teams don’t do it. They log to stdout, maybe ship to Datadog, and when something goes wrong three weeks later the logs are either rotated or too noisy to find the relevant call. Putting the debug data on the model means it lives as long as the record does.

How this changes the work

The team’s day looks different now. The morning used to start with “what do I need to produce today?” Now it starts with “what did the agent produce overnight that I need to review?”

That shift sounds subtle but it changes everything. When you’re the one writing a proposal at 11pm to hit a deadline, you’re in production mode. You’re trying to get it done. You skip things. You miss tone mismatches, factual gaps, strategic misalignments. When you’re reviewing someone else’s output with fresh eyes, you catch all of that. The quality bar went up because the team moved from execution to judgment.

The work is now almost entirely judgment calls. Does this content match the client’s voice? Is this audit surfacing the right priorities? Does this proposal lead with the right problem? The agent handles volume. The humans handle taste.

The system does need constant feeding, though. The agent is only as good as the skill context you give it, the cadences you tune, and the approval gates you set. It’s not “set and forget.” It’s more like managing someone who’s fast, tireless, and has no instincts. You have to build the instincts into the system.

Where to draw the trust line

The hardest design problem isn’t technical. It’s deciding what the agent can do without asking.

We landed on three tiers:

Autonomous. The agent runs these without any human in the loop. Data collection, audits, research, draft generation, internal record-keeping. Anything where the output stays inside our system and a bad result is just a bad draft that gets caught in review. The cost of a mistake is low: we delete it and regenerate.

Review-required. The agent does the work, but it sits in a queue until a human approves it. Proposals, content plans, client-facing reports. The agent produces the artifact, the team reviews and adjusts before it goes anywhere. This is where most of the interesting work happens, because the review is where judgment gets applied.

Human-only. The agent can’t touch these at all. Publishing to client websites. Sending anything to a client. Anything that leaves our system and reaches the outside world. The blast radius of a mistake here is a client seeing something wrong with our name on it. No amount of automation is worth that risk.

The tiers map cleanly onto the station pattern. Each station has an approval level. The content-generation station runs autonomously because it produces drafts. The content-publishing station requires confirmation because it pushes to WordPress. Same agent, same pipeline, different trust levels depending on where it is in the workflow.

The line moves, too. When we first built the proposal station, everything required manual approval. After a few weeks of reviewing output and finding it consistently good, we loosened the gate on internal-only proposals. Client-facing proposals still require sign-off. You earn trust the same way a new hire does: by showing you get it right repeatedly.

The bet

We’re betting that a small team with agent-native tooling can deliver work that used to require a much larger team. Not by working harder, but by building systems where the humans focus on judgment and the agents focus on throughput.

The gap between “AI as a feature” and “AI as the architecture” is the gap between a chatbot that helps you write an email and a system that runs your morning workflow while you’re still making coffee. It’s a structural difference, not a prompting difference. Your data model, your job system, your notification layer, your permission system, all of it has to be built assuming an AI agent is a first-class user.

That’s harder to build. But once it works, the economics are different.