File size: 13,343 Bytes
fdeeb34
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
# Vera Engagement Framework β€” Research: Current Merchant Data Access

**Status**: Research notes β€” companion to `engagement-design.md`.
**Last updated**: 2026-04-26
**Scope**: How the existing system loads merchant + customer data at conversation init and during a turn. Used to inform what the new framework must build vs. adapt.

---

## TL;DR

Two distinct paths exist today β€” **merchant-facing** (Vera ↔ Dr. Meera) and **customer-facing** (a customer asks Vera *about* Dr. Meera). They share infrastructure (vera-mcp + merchant-support-mcp + Redis) but compose context very differently.

Most of what the proposed `MerchantContext` needs **already exists in scattered form** (`_merchant_snapshot`, `_behavioral_profile`, `_session_scenario`). The genuinely new pieces are:

- `CategoryContext` (no existing equivalent)
- `TriggerContext` (no normalized abstraction today β€” every cron emits ad-hoc payloads)
- `CustomerContext.relationship` (visit history aggregation doesn't exist)
- `EngagementComposer` (no shared composer β€” each agent has its own prompt-builder)

The aryan dependency for `category` and `locality` is the soft underbelly β€” every customer-info-pack call hits aryan synchronously.

---

## Path 1 β€” Merchant-facing (`VeraMerchantAgent`)

### Init (per session, instance-level)

`agents/vera/merchant_agent.py:402` β€” `__init__`:

- Spawns its own `vera_mcp_client` (`HTTPMCPClient` β†’ `VERA_MCP_SERVER_URL`, default `vera-mcp:8000`).
- Inherits `BaseAgent._merchant_mcp_client` β€” class-level shared singleton pointing to `merchant-support-mcp` (`https://search5.magicpin.com/staging/mcp-server/mcp`).
- **No merchant data is loaded at init** β€” agent doesn't know who it's talking to yet.

### Per-turn (every inbound message)

`agents/vera/merchant_agent.py:2171` β€” `handle_input(agent_input)`:

1. Extract `merchant_id` from `metadata.context_data.merchant_id`, falling back to regex on the message body (`\d{7,}`).
2. Fire 2 parallel tasks:
    - `_detect_state(merchant_id)` β€” GBP connection + subscription status checks.
    - `_prefetch_merchant_context(merchant_id)` β€” full snapshot + behavioral profile.

### `_prefetch_merchant_context` β€” `merchant_agent.py:740`

Cache-first design:

1. **Redis check**: `vera:merchant_ctx:{merchant_id}` β€” TTL **30 min**. Hit β†’ return immediately, skip everything below.
2. Parallel via `asyncio.gather`:
    - `vera_merchant_snapshot` (vera-mcp tool)
    - `vera_get_merchant_profile` (vera-mcp tool)
3. If snapshot is empty: fall back to `get_aggregated_unassociated_merchant_data` (merchant-support-mcp) β€” basic GBP health, no commercial data. Wrap it in a snapshot-shaped envelope.
4. Sequential enrichments:
    - `_prefetch_product_context` β€” fans out to `vera_get_subscription_context`, `vera_get_performance_summary`, and (only if DA subscribed) `da_get_campaign_context`. ~2KB total, embedded into snapshot.
    - `_enrich_snapshot_with_pricing` β€” direct HTTP to `https://vera.magicpin.com/api/v1/merchant/pricing/get?mid=...` (vera-mcp's pricing path is unreachable locally). Mounted into snapshot as `pricing_recommendation`.
5. Stuff result into Redis at the same key, TTL 30 min.

Final state: `self._merchant_snapshot` and `self._behavioral_profile` are populated. `_get_system_prompt()` reads from these and serializes the snapshot directly into the LLM system prompt (`merchant_agent.py:996`).

### What `vera_merchant_snapshot` actually fetches

`vera-mcp/src/services/merchant_snapshot.py:51` β€” `build_merchant_snapshot()`:

1. **Resolve identity** via `gbp_resolve_merchant` β€” gets `place_id`, `location_name`, `merchant_title`.
2. **One async fan-out** of up to 13 tool calls in parallel:

| Bucket | Tools |
|---|---|
| Merchant-level (no GBP needed) | `vera_get_subscription_status`, `vera_get_pricing_by_merchant`, `vera_get_merchant_pain_points`, `vera_get_merchant_offer`, `vera_get_merchant_config`, `vera_get_onboarding_status`, `vera_get_enhancement_suggestions` |
| Location-level (skipped if no place_id) | `gbp_get_location`, `gbp_get_profile_completeness`, `gbp_get_performance_summary`, `gbp_get_search_keywords`, `gbp_get_review_stats`, `gbp_list_posts` |

3. Composes 7 sections: `identity`, `profile`, `reputation`, `growth`, `commercial`, `conversation_hooks`, `issues`.

> **Note**: `category` and `locality` are not first-class fields on the snapshot β€” they're buried inside `profile.business_info` (from `gbp_get_location`) and surfaced via `aryan_client.get_merchant_v2()` only when `vera_get_customer_info_pack` is called.

### Mid-conversation tool calls

The LLM gets the full toolset from `MERCHANT_MCP_ALLOWED_TOOLS` (declared on the agent class) merged with `_vera_mcp_tools_cache`. Mid-turn tool calls flow through:

- `self.vera_mcp_client.call_tool(name, args)` β€” for vera-mcp tools (HTTP)
- `BaseAgent._merchant_mcp_client.call_tool(name, args)` β€” for merchant-support-mcp tools (HTTP)

No re-prefetch of the snapshot mid-turn. The agent relies on the prefetched snapshot + LLM-initiated lookups when it needs fresh data.

---

## Path 2 β€” Customer-facing (`CustomerIncomingAgent`)

`agents/vera/customer_incoming_agent.py:91` β€” different agent, different state model.

### Init + per-turn

The instance carries:

- `_merchant_id` β€” the merchant the customer is asking about
- `_merchant_data`, `_merchant_name`, `_category`, `_offers`, `_jd_info` β€” about the **business**
- `_customer_phone`, `_customer_profile` β€” about the **customer** (the one chatting)
- `_chat_history`, `_session_state` β€” conversation continuity

### Merchant data load β€” `_load_merchant_data` (`customer_incoming_agent.py:221`)

Single shape, no Redis cache:

1. **Primary call**: `vera_get_customer_info_pack(merchant_id)` β€” one MCP call returns `business_info` + `reviews` + `photos` + `offers` + `metadata` in one shot. The aggregated customer-facing endpoint.
2. **Fallback chain** if `info_pack` is empty:
    - `get_unassociated_merchant_data` β€” basic merchant info
    - `vera_list_merchant_offers` β€” separate offer list
3. **Supplementary** (non-blocking): `vera_get_merchant_jd_info` β€” JustDial crawl data for additional name / address / phone.

Caching is at the request level (in-memory on the agent instance) β€” re-using the same `merchant_id` in a session avoids re-fetching.

### What `vera_get_customer_info_pack` does

`vera-mcp/src/tools/merchant_info.py:188`:

1. **Resolve place_id**: `_resolve_place_id(merchant_id)` β€” chain of MongoDB (`gbp_status`) β†’ local JSON β†’ `aryan_client.get_mapping()`.
2. **Parallel fetch**:
    - GBP data via `get_or_fetch(place_id)` β€” 24h cache in MongoDB, falls through to Google Places API
    - `_get_active_offers(merchant_id)` β€” direct MongoDB read of `offers` collection where `status=active`
    - `_get_merchant_metadata(merchant_id)` β€” `aryan_client.get_merchant_v2()` β†’ returns `name`, `category`, `locality`
3. **Last-resort fallback**: `_fetch_embed_data(place_id)` β€” vera REST `/embed` endpoint if everything else came back empty.

### Customer profile (the *caller*)

`_customer_profile` is loaded separately β€” populated in `_init_customer_data` from past conversation tickets keyed off `_customer_phone`. There's no rich CRM behind it today; it's mostly conversation continuity (last visit, last topic).

---

## Cross-cutting infrastructure

| Concern | Implementation |
|---|---|
| MCP transport | `HTTPMCPClient` (vera client) β€” keeps a session, calls `/mcp/tools/{name}` HTTP POST |
| Auth | Not required for challenge bot endpoints |
| Cache key for merchant context | `vera:merchant_ctx:{merchant_id}` β€” Redis, TTL 30 min, written by `_prefetch_merchant_context` |
| Cache key for GBP data | `gbp_health_report:{place_id}` β€” MongoDB, TTL 24h, in vera-mcp |
| Snapshot freshness for sends | Whatever's in Redis β€” not refreshed on send unless agent is in active conversation |
| Source of truth for `category` | aryan `get_merchant_v2` API (via `aryan_client`) β€” used in `_get_merchant_metadata` |
| Source of truth for `name` | aryan first, GBP `business_info` second |

---

## Observations relevant to the engagement framework

### What already exists

1. **Most of `MerchantContext` already exists** β€” spread across `_merchant_snapshot`, `_behavioral_profile`, `_session_scenario`, `_jd_info`. A `MerchantContext.from_existing(agent_state)` adapter could load ~80% of the fields without any new fetching.
2. **The customer agent already has half a `CustomerContext`** β€” `_customer_phone` + `_customer_profile` give us identity + conversation continuity. Missing: visit history, services received, lapse state.
3. **Two MCP servers, one orchestration** β€” vera-mcp (instance-level) for vera tools, merchant-support-mcp (class-level shared) for fallback merchant tools. The composer can just consume whatever `MerchantContext` already collected β€” no new MCP wiring required.
4. **Cache TTL of 30 min is fine** for engagement nudges that fire daily/weekly. Redis hits during conversation are plenty fresh for composition.

### What does NOT exist

1. **No `CategoryContext`.** Category is just a string buried in metadata. Voice rules, peer benchmarks, knowledge digests β€” none of it exists. Biggest greenfield area, but also the most leveraged (one CategoryContext serves all merchants in the vertical).
2. **No `customer_aggregate` field on the merchant snapshot.** No pipeline today aggregates per-merchant customer roster stats (active count, lapsed count, retention rate).
3. **No `TriggerContext` abstraction.** Every nudge type today has its own cron + its own fetch logic + its own send code. The proposed `TriggerContext` is the genuinely new architectural primitive; everything else is reorganization.
4. **No visit-history aggregation per (merchant, customer_phone).** Required for `CustomerContext.relationship`. BOTOPS chat history has the raw data; no derived view exists.
5. **No shared composer.** Each agent has its own prompt builder embedded in `_get_system_prompt()`. The proposed `EngagementComposer` is net new.

### Operational risks to mitigate

1. **Aryan is the synchronous bottleneck.** `aryan_client.get_merchant_v2()` is the only path to category and locality, and it's a remote HTTP call. If aryan is slow, every customer-info-pack call is slow. Worth caching aryan responses per merchant for ~24h before scaling engagement frequency.
2. **The 30-min Redis cache is keyed per-merchant** β€” fine for in-conversation reuse, but a daily engagement cron will miss this cache 100% of the time and pay the full snapshot-build cost per send. Consider a longer-TTL background-warmed cache for the engagement loop specifically.
3. **No version tracking on prompts today.** Every send loses the prompt-version provenance. The composer should record prompt version + context hash on every send so we can replay and A/B.

---

## Concrete recommendation for Phase 1 of the framework

Phase 1 is mostly an **adapter layer**, not new infrastructure:

| Layer | Effort | What it actually does |
|---|---|---|
| `CategoryContext` | Net new | Build for dentistry first (offer catalog, voice, peer stats, weekly digest, patient-content seed) |
| `MerchantContext` | Adapter | Wrap existing `_merchant_snapshot` + `_behavioral_profile` + a new `_customer_aggregate` derived from BOTOPS chat history |
| `TriggerContext` | Net new abstraction | Normalize the payloads existing crons emit into a single shape; new triggers (research_digest, recall_due) emit it natively |
| `CustomerContext` | Partial adapter | Wrap existing `_customer_profile`, plus a new visit-history aggregator |
| `EngagementComposer` | Net new | Single LLM-prompted module with versioned prompt; consumes the above |

Phase 1 should NOT require modifying any existing agent. The adapter reads from existing state; the composer is a new module that runs in parallel; the new triggers (research digest, recall) are new crons that don't touch the existing matrix-followup or campaign-engagement loops.

---

## Appendix: file pointers for follow-up implementation

- `agents/vera/merchant_agent.py:402` β€” VeraMerchantAgent init
- `agents/vera/merchant_agent.py:740` β€” `_prefetch_merchant_context` (the main load)
- `agents/vera/merchant_agent.py:899` β€” `_prefetch_product_context` (subscription + DA campaign + perf summary)
- `agents/vera/merchant_agent.py:996` β€” system-prompt builder reads `_merchant_snapshot`
- `agents/vera/merchant_agent.py:2171` β€” `handle_input` per-turn entry
- `agents/vera/customer_incoming_agent.py:91` β€” CustomerIncomingAgent class
- `agents/vera/customer_incoming_agent.py:221` β€” `_load_merchant_data` (single info-pack call + fallbacks)
- `agents/base_agent.py:80-82` β€” MERCHANT_MCP_SERVER_URL config
- `agents/base_agent.py:237-244` β€” class-level shared `_merchant_mcp_client`
- `vera-mcp/src/tools/merchant_snapshot.py` β€” `vera_merchant_snapshot` tool
- `vera-mcp/src/services/merchant_snapshot.py:51` β€” `build_merchant_snapshot` (the 13-call fan-out)
- `vera-mcp/src/tools/merchant_info.py:188` β€” `vera_get_customer_info_pack` tool
- `vera-mcp/src/tools/merchant_info.py:30` β€” `_resolve_place_id` chain (MongoDB β†’ JSON β†’ aryan)
- `vera-mcp/src/services/aryan_client.py` β€” aryan HTTP client (`get_merchant_v2`, `get_mapping`)