vera-engine / engagement-research.md
Rachit-Mangawa
feat: platinum deployment suite v1.0
fdeeb34
|
Raw History Blame Contribute Delete
13.3 kB

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
  1. 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)