A ReAct AI agent that answers natural-language questions about any account by searching across HubSpot, Gmail, and Google Docs in parallel, then delivers the unified brief straight into Slack, where the sales team already works.
Ask @bot what's the proposal status for Acme? in Slack; the agent plans a multi-source search, runs it, reviews what's missing, and replies with a synthesized answer plus its sources, confidence, and gaps.
- A sales team's account data lived in three disconnected systems (HubSpot, Gmail, a support portal) with nothing tying them together.
- The team burned 15+ hours a week on manual lookups just to assemble basic context before a call.
- As the sales lead put it: "my team spends more time looking things up than following up."
- Adoption had to require zero behavior change, so the interface is a Slack mention and the engine is a ReAct agent.
- The team went from three-system manual lookups to a single mention, and deal reviews now start from agent output instead of hand-assembled context.
The problem wasn't just pulling data from three systems. It's that what you find in one system changes what you need to look for in the next. A HubSpot deal stage tells you which email thread matters; an email reference tells you which doc to read. A fixed query-per-source would miss those connections.
So the agent plans, executes, reviews, and synthesizes, adapting between passes:
Slack mention / REST query
│
▼
┌─────────────────┐ ┌──────────────────────────────┐
│ Planner │────▶│ Parallel tool execution │
│ (Bedrock/Nova) │ │ - HubSpot (deals/contacts) │
└────────┬────────┘ │ - Gmail (threads) │
│ review pass │ - Google Docs (proposals) │
│◀─────────────│ dependency-aware, concurrent│
▼ └──────────────────────────────┘
┌─────────────────┐
│ Synthesize │──▶ answer + sources + confidence + gaps ──▶ Slack thread
└─────────────────┘
- Natural-language account queries from Slack or a REST API.
- Parallel, dependency-aware tool execution across HubSpot, Gmail, and Google Docs; independent steps run concurrently and dependent steps wait on their inputs.
- Self-review loop: after the first pass the agent identifies gaps and issues follow-up steps before answering.
- Evidence quality gates: system-specific ranking (CRM prioritized over low-signal docs in a company-context query), email-anchor matching, and noise filtering.
- Sync + async: light queries answer within the API Gateway window; heavy ones queue to a Lambda worker with results in S3, polled by a status endpoint.
- Durable Slack dedup: event-id deduplication + worker-side session claiming prevent double-processing of Slack retries.
Serverless, Lambda-first:
Slack Events / REST ──▶ API Gateway ──▶ Lambda (API handler)
│ light -> answer inline
│ heavy -> enqueue
▼
SQS FIFO (+ DLQ)
▼
Lambda (worker) ──▶ ReAct pipeline
│ │
S3 (async status) ├─ HubSpot (MCP / HTTP)
Secrets Manager ├─ Gmail + Google Docs
(all credentials) └─ Bedrock (plan/review/synthesize)
| Layer | Tooling |
|---|---|
| Language | Python 3.12 |
| API | FastAPI + Uvicorn |
| Compute | AWS Lambda (API + worker) behind API Gateway |
| Queue | SQS FIFO + dead-letter queue |
| Reasoning | AWS Bedrock (Amazon Nova Pro) |
| Integrations | HubSpot (MCP server / HTTP), Gmail, Google Docs, Slack |
| State | S3 (async status), PostgreSQL/SQLAlchemy (execution trace, optional) |
| Secrets | AWS Secrets Manager |
| IaC / build | SAM (template.yaml), Docker to ECR via CodeBuild |
account-intelligence-agent/
├── src/react_hubspot_slack_agent/
│ ├── planner.py # ReAct planner: initial plan + multi-pass review
│ ├── execution.py # Parallel, dependency-aware execution engine
│ ├── hubspot_client.py # HubSpot via MCP server or direct HTTP
│ ├── google_client.py # Gmail + Google Docs, term-aware evidence ranking
│ ├── bedrock_client.py # Plan / review / synthesize calls
│ ├── slack_client.py # Outbound Slack posting
│ ├── slack_security.py # Slack signature verification + dedup
│ ├── lambda_handlers.py # Sync/async resolution, Slack event handling
│ ├── async_status_store.py # S3-backed async status
│ ├── session_store.py / execution_store.py / models.py # persistence
│ └── main.py / service.py / worker.py / queue.py / config.py
├── template.yaml # SAM: Lambda, SQS, API Gateway
├── Dockerfile / buildspec.yml
├── scripts/ # PowerShell deploy + provisioning automation
├── tests/ # planner-graph + evidence-priority + handler tests
└── docs/ # architecture, runbooks, DEV_LOG.md
- Model-first ReAct planner. Bedrock plans the search, and the system adapts between passes rather than running a hardcoded query per source.
- Parallel execution with a dependency graph. Independent lookups run concurrently; dependent ones key off prior step outputs, with cycle detection.
- Async fallback for heavy queries. API Gateway's ~29s timeout forces a clean split: light queries answer inline, heavy ones go to a worker + S3 status, so Slack never times out.
- Durable deduplication. Slack retries are deduped by event-id and worker-side session claiming, so a single mention is processed exactly once.
- Evidence quality over recall. Per-source ranking and anchor matching keep CRM signal from being drowned out by low-signal document hits.
# Local API
uv sync # or: pip install -r requirements.txt
uvicorn react_hubspot_slack_agent.main:app --reload # http://127.0.0.1:8000/health
# Deploy (SAM, Lambda-first), credentials sourced from AWS Secrets Manager
pwsh scripts/deploy_proto_lambda.ps1Example query:
curl -X POST .../api/v1/query -d '{"query":"proposal status for acme company hubspot"}'
# returns { "session_id": "...", "status": "completed|queued",
# "answer": "...", "sources": [...], "confidence": 0.0-1.0, "issues": [...] }Note: Portfolio extract of a production system. All credentials live in AWS Secrets Manager and none are committed. AWS account IDs, API IDs, and resource IDs in the docs have been replaced with placeholders (
<AWS_ACCOUNT_ID>,<API_ID>,<VPC_ID>). The deep development log is preserved atdocs/DEV_LOG.md.