We built a GTM agent that wraps 79 sales and marketing providers and shipped it to Slack. The agent reasoning worked on day one. The chat layer took four weeks.
If you're thinking about building one, this is the post I wish we'd had: architecture, what broke, what we'd do differently.
Architecture
We ran two implementations in parallel.
LangGraph agent
Deep Agents framework with LangChain tools. Calls the Deepline HTTP API directly.
Managed agent
Anthropic's sandbox with CLI tool execution. Runs deepline tools execute <tool_id> --payload '{...}' via subprocess.
Both use the same Deepline tool catalog. The managed agent path meant zero custom tool definitions: every tool in Deepline works automatically.
Slack Events API → FastAPI (Railway) → Claude Agent → Deepline API (79 providers)
What worked
Claude Code patterns transferred
Waterfall enrichment, tool catalogs, provider playbooks - all worked out of the box. The skill docs we'd built for Claude Code gave the agent exactly the context it needed.
Key patterns that carried over:
- Full tool catalog embedded in the
deepline_calltool description - Waterfall functions that exhaust multiple providers before failing
- Structured output formats (person cards, company cards)
The sandbox model
Real filesystem, CLI access, persistent state. We upload the Deepline binary at session start. Every tool works automatically with zero custom definitions.
The agent just runs CLI commands and parses JSON output. No complex tool schemas to maintain.
Skill docs eliminate hallucination
300KB of context about provider patterns and exact tool IDs, fetched from CDN at startup:
SKILLS_BASE = "https://code.deepline.com/.well-known/skills/deepline-gtm"
CORE_SKILL_DOCS = [
"SKILL.md",
"finding-companies-and-contacts.md",
"provider-playbooks/apollo.md",
"provider-playbooks/hubspot.md",
]
With this context, the agent picks the right tools and constructs valid payloads without hallucinating field names.
What broke
Slack formatting (80% of bugs)
We spent more time debugging Slack's mrkdwn than building the agent.
Two markdown converters with different behavior
The LangGraph version and the managed-agent version produced inconsistent output for the same input. We had two converters and didn't know it until output started disagreeing across the same conversation.
Headers glued to text
The agent emits multiple text blocks. When they concatenate: Done.## Next Steps instead of proper separation. Slack's mrkdwn parser treats this as one paragraph, so the header never renders.
Verbose narration
Despite prompting for concise responses, the agent kept narrating: "I'll search for…" before every action, then the actual search, then "I found…", then the results. We cut this with explicit prompt rules and JSON-only output for data payloads.
Markdown tables don't render in Slack
Slack's mrkdwn doesn't support tables. We built a converter that emits aligned monospace text, but it was brittle on long values.
Memory leak in dedup cache
We cleared the whole cache at 10K entries:
if len(_seen_event_ids) > 10_000:
_seen_event_ids.clear() # Wrong
Brief window after clear meant duplicate events if Slack retried. Fixed with LRU eviction:
from collections import OrderedDict
_seen_event_ids: OrderedDict[str, None] = OrderedDict()
_MAX_SEEN_EVENTS = 5000
def _mark_event_seen(event_id: str) -> bool:
if event_id in _seen_event_ids:
return True
_seen_event_ids[event_id] = None
while len(_seen_event_ids) > _MAX_SEEN_EVENTS:
_seen_event_ids.popitem(last=False)
return False
Blocking I/O
Sync tool functions called from async FastAPI. Every deepline_execute() blocked the event loop. Concurrent requests queued behind each other.
No timeouts
Slow provider meant hung session. Added 120s timeout and retry logic for read-only operations.
What we'd do differently
Use Vercel AI SDK for chat
The Slack bot was 40% of the codebase and 80% of the bugs. Streaming, reconnection, formatting - all solved problems we rebuilt from scratch.
If we rebuilt today:
- Keep the managed agent core (it works)
- Replace custom Slack bot with AI SDK + thin adapter
Agents should call tools and reason. Chat layers should present output. We mixed them, and the coupling made both harder to maintain.
Opus for planning
Sonnet handles simple enrichments fine. Complex workflows (build TAM, research 20 companies, add to Instantly) need Opus - it plans better and makes fewer tool-call mistakes.
Structured output for data
Free-form markdown for GTM data is fragile. JSON for data, markdown for summaries.
Cost tracking
We had no visibility into credit consumption per request. Users burned quota on expensive waterfalls without knowing until their balance hit zero.
Cost patterns from Claude Code
We borrowed three patterns from Claude Code's open source.
Truncate large results
Cap raw tool output at the presentation layer so a 200KB Apify scrape doesn't blow the context window:
MAX_TOOL_RESULT_CHARS = 8000
def truncate_tool_result(result):
if isinstance(result, str) and len(result) > MAX_TOOL_RESULT_CHARS:
return f"{result[:2000]}\n\n... ({len(result)} chars total)"
Short error stacks
Trim Python tracebacks to the last few frames. Five is enough to debug; the rest is token waste:
def short_error_stack(e, max_frames=5):
# 5 frames is enough. Save tokens.
Skill budget
Claude Code caps skill descriptions at roughly 1% of context. We were embedding 300KB at startup, which is way over. Trimming to the slice the agent actually used per session was the single biggest cost win.
Results after fixes
| Metric | Before | After |
|---|---|---|
| Formatting bugs | 4–5 per week | 0 |
| Duplicate events | ~1% | 0 |
| Timeout errors | ~2% | 0 |
| Response time | 12s | 8s |
The lesson
Agent logic was easy. Claude with good context and well-designed tools makes a capable agent.
Chat infrastructure was hard. Streaming, formatting, reconnection, error recovery - none of this is agent-specific. It's plumbing that every chat app needs.
Build the agent as a pure API. Use existing chat infrastructure for the UI.
Get started
Install Deepline to give your agent access to 79 GTM providers:
bash <(curl -sS https://code.deepline.com/api/v2/cli/install)
Then build your Slack bot using Vercel AI SDK and call Deepline tools via the CLI or HTTP API.
Run GTM workflows from Claude Code
Deepline connects to 79 providers. Enrich, validate, and sequence - all through natural language.