diff --git a/flexus_client_kit/ckit_erp.py b/flexus_client_kit/ckit_erp.py index 9df49f87..a5048719 100644 --- a/flexus_client_kit/ckit_erp.py +++ b/flexus_client_kit/ckit_erp.py @@ -184,6 +184,26 @@ async def erp_table_batch_upsert( return result +def format_table_meta_text(table_name: str, schema_class: type) -> str: + result = f"Table: erp.{table_name}\n\nColumns:\n" + for field_name, field_type in schema_class.__annotations__.items(): + type_str = str(field_type).replace("typing.", "") + meta = schema_class.__dataclass_fields__[field_name].metadata + line = f" • {field_name}: {type_str}" + if meta.get("pkey"): + line += " [PRIMARY KEY]" + if display_name := meta.get("display_name"): + line += f" — {display_name}" + if description := meta.get("description"): + line += f" ({description})" + result += line + "\n" + if examples := meta.get("examples"): + result += f" examples: {examples}\n" + if enum_values := meta.get("enum"): + result += " enum: " + ", ".join(f"{e['value']}" for e in enum_values) + "\n" + return result + + def check_record_matches_filters(record: dict, filters, col_names: set = None) -> bool: if not filters: return True diff --git a/flexus_client_kit/ckit_integrations_db.py b/flexus_client_kit/ckit_integrations_db.py index 0eb504ca..3bffd007 100644 --- a/flexus_client_kit/ckit_integrations_db.py +++ b/flexus_client_kit/ckit_integrations_db.py @@ -222,6 +222,30 @@ def _setup_erp(obj, rcx, _tam=tools_and_methods): integr_need_mongo=True, )) + elif name.startswith("crm"): # "crm[manage_contact, manage_deal, log_activity]" + from flexus_client_kit.integrations import fi_crm + subset = _parse_bracket_list(name) + tool_map = { + "manage_contact": (fi_crm.MANAGE_CRM_CONTACT_TOOL, "handle_manage_crm_contact"), + "manage_deal": (fi_crm.MANAGE_CRM_DEAL_TOOL, "handle_manage_crm_deal"), + "log_activity": (fi_crm.LOG_CRM_ACTIVITY_TOOL, "handle_log_crm_activity"), + } + if subset is None: + subset = list(tool_map.keys()) + tools_and_methods = [(tool_map[s][0], tool_map[s][1]) for s in subset] + async def _init_crm(rcx, setup): + return fi_crm.IntegrationCrm(rcx.fclient, rcx.persona.ws_id) + def _setup_crm(obj, rcx, _tam=tools_and_methods): + for tool, method_name in _tam: + rcx.on_tool_call(tool.name)(getattr(obj, method_name)) + result.append(IntegrationRecord( + integr_name="crm", + integr_tools=[t for t, _ in tools_and_methods], + integr_init=_init_crm, + integr_setup_handlers=_setup_crm, + integr_prompt=fi_crm.LOG_CRM_ACTIVITIES_PROMPT if "log_activity" in subset else "", + )) + else: raise ValueError(f"Unknown integration {name!r}") return result diff --git a/flexus_client_kit/erp_schema.py b/flexus_client_kit/erp_schema.py index 936f6952..9bc46020 100644 --- a/flexus_client_kit/erp_schema.py +++ b/flexus_client_kit/erp_schema.py @@ -10,7 +10,7 @@ class CrmContact: ws_id: str contact_first_name: str = field(metadata={"importance": 1, "display_name": "First Name"}) contact_last_name: str = field(metadata={"importance": 1, "display_name": "Last Name"}) - contact_email: str = field(metadata={"importance": 1, "extra_search": True, "display_name": "Email"}) + contact_email: Optional[str] = field(default=None, metadata={"importance": 1, "extra_search": True, "display_name": "Email"}) contact_phone: str = field(default="", metadata={"display_name": "Phone"}) contact_id: str = field(default="", metadata={"pkey": True, "display_name": "Contact ID"}) contact_notes: str = field(default="", metadata={"importance": 1, "display": "string_multiline", "display_name": "Notes"}) diff --git a/flexus_client_kit/integrations/fi_crm.py b/flexus_client_kit/integrations/fi_crm.py index 98357686..a2717cc8 100644 --- a/flexus_client_kit/integrations/fi_crm.py +++ b/flexus_client_kit/integrations/fi_crm.py @@ -1,6 +1,8 @@ import time from typing import Dict, Any, Optional +import gql.transport.exceptions + from flexus_client_kit import ckit_cloudtool, ckit_client, ckit_erp, erp_schema @@ -22,7 +24,41 @@ }, ) -LOG_CRM_ACTIVITIES_PROMPT = "After each conversation in a messenger platform or outbound message or email, call log_crm_activity with the contact_id, type, direction, and a brief summary. Do this before finishing the task." + +LOG_CRM_ACTIVITIES_PROMPT = ( + "After the conversation has ended on a messenger platform (not after each message), " + "or after sending an outbound message or email, call log_crm_activity with contact_id, type, direction, and a brief summary. " + "Do this before finishing the task." +) + + +MANAGE_CRM_CONTACT_TOOL = ckit_cloudtool.CloudTool( + strict=False, + name="manage_crm_contact", + description="Create or patch a CRM contact. Call op='help' to see available fields.", + parameters={ + "type": "object", + "properties": { + "op": {"type": "string", "enum": ["help", "create", "patch"], "order": 1}, + "args": {"type": "object", "description": "Contact fields; include contact_id for patch", "order": 2}, + }, + "required": ["op"], + }, +) + +MANAGE_CRM_DEAL_TOOL = ckit_cloudtool.CloudTool( + strict=False, + name="manage_crm_deal", + description="Create or patch a CRM deal. Call op='help' to see available fields.", + parameters={ + "type": "object", + "properties": { + "op": {"type": "string", "enum": ["help", "create", "patch"], "order": 1}, + "args": {"type": "object", "description": "Deal fields; include deal_id for patch", "order": 2}, + }, + "required": ["op"], + }, +) async def find_contact_by_platform_id(fclient, ws_id: str, platform: str, identifier: str) -> Optional[str]: @@ -38,6 +74,46 @@ def __init__(self, fclient: ckit_client.FlexusClient, ws_id: str): self.fclient = fclient self.ws_id = ws_id + async def handle_manage_crm_contact(self, toolcall: ckit_cloudtool.FCloudtoolCall, args: Dict[str, Any]) -> str: + op = args.get("op", "") + if op == "help": + return ckit_erp.format_table_meta_text("crm_contact", erp_schema.CrmContact) + fields = args.get("args", {}) + contact_id = str(fields.pop("contact_id", "") or "").strip() + try: + if op == "create": + new_id = await ckit_erp.create_erp_record(self.fclient, "crm_contact", self.ws_id, {"ws_id": self.ws_id, **fields}) + return f"✅ Created: {new_id}\n" + elif op == "patch": + if not contact_id: + return "❌ contact_id required for patch\n" + await ckit_erp.patch_erp_record(self.fclient, "crm_contact", self.ws_id, contact_id, fields) + return "✅ Updated\n" + else: + return f"❌ Unknown op: {op}\n" + except gql.transport.exceptions.TransportQueryError as e: + return ckit_cloudtool.gql_error_4xx_to_model_reraise_5xx(e, "fi_crm") + + async def handle_manage_crm_deal(self, toolcall: ckit_cloudtool.FCloudtoolCall, args: Dict[str, Any]) -> str: + op = args.get("op", "") + if op == "help": + return ckit_erp.format_table_meta_text("crm_deal", erp_schema.CrmDeal) + fields = args.get("args", {}) + deal_id = str(fields.pop("deal_id", "") or "").strip() + try: + if op == "create": + new_id = await ckit_erp.create_erp_record(self.fclient, "crm_deal", self.ws_id, {"ws_id": self.ws_id, **fields}) + return f"✅ Created: {new_id}\n" + elif op == "patch": + if not deal_id: + return "❌ deal_id required for patch\n" + await ckit_erp.patch_erp_record(self.fclient, "crm_deal", self.ws_id, deal_id, fields) + return "✅ Updated\n" + else: + return f"❌ Unknown op: {op}\n" + except gql.transport.exceptions.TransportQueryError as e: + return ckit_cloudtool.gql_error_4xx_to_model_reraise_5xx(e, "fi_crm") + async def handle_log_crm_activity(self, toolcall: ckit_cloudtool.FCloudtoolCall, args: Dict[str, Any]) -> str: contact_id = args.get("contact_id", "").strip() activity_type = args.get("activity_type", "").strip() @@ -58,5 +134,5 @@ async def handle_log_crm_activity(self, toolcall: ckit_cloudtool.FCloudtoolCall, "activity_occurred_ts": time.time(), }) return "✅ Activity logged\n" - except Exception as e: - return f"❌ {e}\n" + except gql.transport.exceptions.TransportQueryError as e: + return ckit_cloudtool.gql_error_4xx_to_model_reraise_5xx(e, "fi_crm") diff --git a/flexus_client_kit/integrations/fi_erp.py b/flexus_client_kit/integrations/fi_erp.py index b3b93ad6..1bc9eace 100644 --- a/flexus_client_kit/integrations/fi_erp.py +++ b/flexus_client_kit/integrations/fi_erp.py @@ -38,6 +38,7 @@ name="erp_table_data", description=( "Query ERP table data with filtering. " + "Ensure erp_table_meta() has already been called for the table before querying it." "Operators: =, !=, >, >=, <, <=, LIKE, ILIKE, CIEQL, IN, NOT_IN, IS_NULL, IS_NOT_NULL. " "LIKE/ILIKE use SQL wildcards: % matches any chars. CIEQL: Case Insensitive Equal. " "JSON path: details->subtype:=:welcome. " @@ -112,25 +113,6 @@ ) -def _format_table_meta_text(table_name: str, schema_class: type) -> str: - result = f"Table: erp.{table_name}\n\nColumns:\n" - for field_name, field_type in schema_class.__annotations__.items(): - type_str = str(field_type).replace("typing.", "") - meta = schema_class.__dataclass_fields__[field_name].metadata - line = f" • {field_name}: {type_str}" - if meta.get("pkey"): - line += " [PRIMARY KEY]" - if display_name := meta.get("display_name"): - line += f" — {display_name}" - if description := meta.get("description"): - line += f" ({description})" - result += line + "\n" - if examples := meta.get("examples"): - result += f" examples: {examples}\n" - if enum_values := meta.get("enum"): - result += " enum: " + ", ".join(f"{e['value']}" for e in enum_values) + "\n" - return result - def _rows_to_text(rows: list, table_name: str, safety_valve_chars: int = 5000) -> tuple[str, Optional[str]]: """ @@ -253,7 +235,7 @@ async def handle_erp_meta(self, toolcall: ckit_cloudtool.FCloudtoolCall, args: D schema_class = erp_schema.ERP_TABLE_TO_SCHEMA[tn] if result_text: result_text += "\n\n" - result_text += _format_table_meta_text(tn, schema_class) + result_text += ckit_erp.format_table_meta_text(tn, schema_class) return result_text diff --git a/flexus_simple_bots/vix/nurturing__s2_stall_deals.yaml b/flexus_simple_bots/vix/nurturing__s2_stall_deals.yaml new file mode 100644 index 00000000..4fcf09a2 --- /dev/null +++ b/flexus_simple_bots/vix/nurturing__s2_stall_deals.yaml @@ -0,0 +1,1259 @@ +messages: + - role: user + content: Work on the assigned task. + - role: assistant + tool_calls: + - id: fakecall_qjRHnp3u9RI6huU9 + type: function + function: + name: flexus_bot_kanban + arguments: '{"op": "assign_to_this_chat+help", "args": {"id": "josWw9ZIv0"}}' + - role: tool + content: |+ + Assigned task josWw9ZIv0 to this chat, here are the details: + + { + "id": "josWw9ZIv0", + "title": "Scheduled daily operations check", + "details": { + "description": "Run stall deal check per /sales-pipeline/stall-deals-policy." + } + } + + + flexus_bot_kanban() + Shows only tasks assigned to you. + + flexus_bot_kanban(op="search", args={"project": "Project 5", "assigned": "Bot 6", "quicksearch": "qqq", "group": "Marketing 7"}) + All arguments (project, assigned, quicksearch) are optional. The search starts with all tasks, and filters + only tasks having text qqq anywhere, belonging to a project, assigned, etc. + + flexus_bot_kanban(op="fetch_details", args={"batch": ["task1337", "task1338"]}) + Returns details, works for any task. + + flexus_bot_kanban(op="inbox_to_todo", args={"join": ["task1337", "task1338"]}) + Moves a task from inbox into todo column, optionally joining multiple tasks. Only the first task + in the join list will move to todo, others will get appended to the details of the first + task. + Tasks in todo will produce a new chat to address it. + Join inbox tasks that appear to refer to the same question. + + flexus_bot_kanban(op="inbox_irrelevant", args={"batch": ["task1337", "task1338"], "resolution_summary": "I don't care about potato knishes"}) + A shortcut to assign multiple tasks to this chat, move them to done, set resolution_code to IRRELEVANT, set resolution_summary. + See system prompt if you are supposed to use this, only bots that receive a lot of garbage tasks need it, such as chat bots. + + flexus_bot_kanban(op="assign_to_this_chat", args={"id": "task1337"}) + Assign a task from inbox to this chat, automatically shows all the details. + + flexus_bot_kanban(op="current_task_done", args={"resolution_code": "SUCCESS/INCONCLUSIVE/FAIL/IRRELEVANT", "resolution_summary": "Did this and that", "resolution_humanhours": 2.5, "resolution_pdoc_paths": ["/customer-research/interview-john-doe"]}) + Mark the task currently assigned as done with a resolution code, 50-character summary, and + your estimate of how much time a qualified human would have spent resolving this, in hours. + Optionally, a list of policy document paths produced in the task or related to its resolution. + + flexus_bot_kanban(op="add_details", args={"key1": "value1", "key2": "value2"}) + Adds details to the current task assigned to this chat. This op cannot overwrite existing + keys, only create new keys. + + flexus_bot_kanban(op="restart", args={"chat_summary": "I did these actions, and then this guy told me, and I saved this and this file."}) + Clean up the context, especially after you've completed a piece of work. This will start a new chat + that will only see the original message that started this chat, and your chat_summary. If this chat is + captured by a messenger thread, the new chat will be too, so you can continue the conversation. + + call_id: fakecall_qjRHnp3u9RI6huU9 + - role: assistant + content: Now let me run the stall deal check. First, I'll read the stall deals policy. + tool_calls: + - id: toolu_01NCKVLEQX7uGrUpdqgpRwF1 + type: function + function: + name: flexus_policy_document + arguments: '{"op": "cat", "args": {"p":"/sales-pipeline/stall-deals-policy"}}' + - role: tool + content: |- + 📄 /sales-pipeline/stall-deals-policy + + { + "stall-deals-policy": { + "meta": { + "author": "Vix", + "created": "20260306", + "updated": "20260306" + }, + "stall_days": 14, + "pipeline_id": "pipe_sales", + "archive_days": 45, + "pipeline_name": "Sales v1", + "stage_actions": [ + { + "tone": "value_add", + "action": "skip", + "stage_id": "stg_s1", + "stage_name": "To Contact" + }, + { + "tone": "value_add", + "action": "skip", + "stage_id": "stg_s2", + "stage_name": "1st Contact Failed" + }, + { + "tone": "direct", + "action": "email", + "stage_id": "stg_s3", + "stage_name": "Proposal Sent", + "template_path": "/sales-pipeline/stall-email" + }, + { + "tone": "direct", + "action": "skip", + "stage_id": "YeF52IKv0S", + "stage_name": "Closed - Won" + }, + { + "tone": "direct", + "action": "skip", + "stage_id": "b8wsoZrKag", + "stage_name": "Closed - Lost" + } + ], + "archive_to_stage_id": "b8wsoZrKag", + "archive_to_stage_name": "Closed - Lost", + "outreach_cutoff_stage_id": "stg_s3", + "outreach_cutoff_stage_name": "Proposal Sent" + } + } + call_id: toolu_01NCKVLEQX7uGrUpdqgpRwF1 + - role: assistant + content: |- + Good. Now let me compute timestamps and query for stalled deals. + + - Current time: 2026-03-09 17:14:08 EDT → Unix: approximately 1773357248 + - stall_ts = now - 14 * 86400 = 1773357248 - 1209600 = 1772147648 + - archive_ts = now - 45 * 86400 = 1773357248 - 3888000 = 1769469248 + + Let me query stalled deals in the Sales v1 pipeline that haven't had activity in 14+ days: + tool_calls: + - id: toolu_012uqVaUDjyPXoznnKwKfiWj + type: function + function: + name: erp_table_data + arguments: '{"table_name": "crm_deal", "options": + {"filters":{"AND":["deal_pipeline_id:=:pipe_sales","deal_stage_id:NOT_IN:YeF52IKv0S,b8wsoZrKag","contact.contact_last_outbound_ts:<:1772147648","contact.contact_last_inbound_ts:<:1772147648"]},"include":["contact"],"limit":100,"safety_valve":"10k"}}' + - role: tool + content: |- + Table: crm_deal + ⚠️ 16 rows total, showing rows 1:7. Full result saved to mongo. + + {"ws_id": "solarsystem", "deal_name": "CedarBridge Expansion", "deal_pipeline_id": "pipe_sales", "deal_stage_id": "stg_s3", "deal_id": "705LbGpwDy", "deal_contact_id": "XZ0163XFWn", "deal_value": 9000.0, "deal_expected_close_ts": 0.0, "deal_closed_ts": 0.0, "deal_lost_reason": "", "deal_notes": "", "deal_tags": [], "deal_details": {}, "deal_owner_fuser_id": "", "deal_priority": "HIGH", "deal_created_ts": 1773090561.12105, "deal_modified_ts": 1773090561.12105, "deal_archived_ts": 0.0, "contact": {"contact_id": "XZ0163XFWn", "contact_first_name": "Sandra", "contact_last_name": "Khoury", "contact_email": "s.khoury@cedarbridge.net", "contact_notes": "", "contact_details": {}, "contact_tags": [], "contact_address_line1": "", "contact_address_line2": "", "contact_address_city": "", "contact_address_state": "", "contact_address_zip": "", "contact_address_country": "", "contact_utm_first_source": "", "contact_utm_first_medium": "", "contact_utm_first_campaign": "", "contact_utm_first_term": "", "contact_utm_first_content": "", "contact_utm_last_source": "", "contact_utm_last_medium": "", "contact_utm_last_campaign": "", "contact_utm_last_term": "", "contact_utm_last_content": "", "contact_created_ts": 1773090544.872002, "ws_id": "solarsystem", "contact_archived_ts": 0.0, "contact_modified_ts": 1773090606.337094, "contact_bant_score": -1, "contact_phone": "+1-617-555-0664", "contact_commerce_external": {}, "contact_platform_ids": {}, "contact_last_inbound_ts": 0.0, "contact_last_outbound_ts": 1768778908.0}} + {"ws_id": "solarsystem", "deal_name": "PeakForm Fitness Package", "deal_pipeline_id": "pipe_sales", "deal_stage_id": "stg_s3", "deal_id": "Twh6276oyK", "deal_contact_id": "ct37XiqaRl", "deal_value": 7500.0, "deal_expected_close_ts": 0.0, "deal_closed_ts": 0.0, "deal_lost_reason": "", "deal_notes": "", "deal_tags": [], "deal_details": {}, "deal_owner_fuser_id": "", "deal_priority": "MEDIUM", "deal_created_ts": 1773090561.065688, "deal_modified_ts": 1773090561.065688, "deal_archived_ts": 0.0, "contact": {"contact_id": "ct37XiqaRl", "contact_first_name": "Derek", "contact_last_name": "Olsen", "contact_email": "d.olsen@peakform.fit", "contact_notes": "", "contact_details": {}, "contact_tags": [], "contact_address_line1": "", "contact_address_line2": "", "contact_address_city": "", "contact_address_state": "", "contact_address_zip": "", "contact_address_country": "", "contact_utm_first_source": "", "contact_utm_first_medium": "", "contact_utm_first_campaign": "", "contact_utm_first_term": "", "contact_utm_first_content": "", "contact_utm_last_source": "", "contact_utm_last_medium": "", "contact_utm_last_campaign": "", "contact_utm_last_term": "", "contact_utm_last_content": "", "contact_created_ts": 1773090544.801715, "ws_id": "solarsystem", "contact_archived_ts": 0.0, "contact_modified_ts": 1773090606.275178, "contact_bant_score": -1, "contact_phone": "+1-720-555-0512", "contact_commerce_external": {}, "contact_platform_ids": {}, "contact_last_inbound_ts": 0.0, "contact_last_outbound_ts": 1771716508.0}} + {"ws_id": "solarsystem", "deal_name": "ArcticWave Bulk Order", "deal_pipeline_id": "pipe_sales", "deal_stage_id": "stg_s3", "deal_id": "7VpPOynBZo", "deal_contact_id": "T5x45IdlEp", "deal_value": 12000.0, "deal_expected_close_ts": 0.0, "deal_closed_ts": 0.0, "deal_lost_reason": "", "deal_notes": "", "deal_tags": [], "deal_details": {}, "deal_owner_fuser_id": "", "deal_priority": "HIGH", "deal_created_ts": 1773090561.011374, "deal_modified_ts": 1773090561.011374, "deal_archived_ts": 0.0, "contact": {"contact_id": "T5x45IdlEp", "contact_first_name": "Tomoko", "contact_last_name": "Watanabe", "contact_email": "t.watanabe@arcticwave.co", "contact_notes": "", "contact_details": {}, "contact_tags": [], "contact_address_line1": "", "contact_address_line2": "", "contact_address_city": "", "contact_address_state": "", "contact_address_zip": "", "contact_address_country": "", "contact_utm_first_source": "", "contact_utm_first_medium": "", "contact_utm_first_campaign": "", "contact_utm_first_term": "", "contact_utm_first_content": "", "contact_utm_last_source": "", "contact_utm_last_medium": "", "contact_utm_last_campaign": "", "contact_utm_last_term": "", "contact_utm_last_content": "", "contact_created_ts": 1773090544.745269, "ws_id": "solarsystem", "contact_archived_ts": 0.0, "contact_modified_ts": 1773090606.23562, "contact_bant_score": -1, "contact_phone": "+1-206-555-0391", "contact_commerce_external": {}, "contact_platform_ids": {}, "contact_last_inbound_ts": 0.0, "contact_last_outbound_ts": 1771025308.0}} + {"ws_id": "solarsystem", "deal_name": "Horizon Labs Inquiry", "deal_pipeline_id": "pipe_sales", "deal_stage_id": "stg_s1", "deal_id": "6DIj9LwSzC", "deal_contact_id": "bubZwDVYy7", "deal_value": 2000.0, "deal_expected_close_ts": 0.0, "deal_closed_ts": 0.0, "deal_lost_reason": "", "deal_notes": "", "deal_tags": [], "deal_details": {}, "deal_owner_fuser_id": "", "deal_priority": "LOW", "deal_created_ts": 1773090560.952685, "deal_modified_ts": 1773090560.952685, "deal_archived_ts": 0.0, "contact": {"contact_id": "bubZwDVYy7", "contact_first_name": "Priya", "contact_last_name": "Chatterjee", "contact_email": "priya.c@horizonlabs.io", "contact_notes": "", "contact_details": {}, "contact_tags": [], "contact_address_line1": "", "contact_address_line2": "", "contact_address_city": "", "contact_address_state": "", "contact_address_zip": "", "contact_address_country": "", "contact_utm_first_source": "", "contact_utm_first_medium": "", "contact_utm_first_campaign": "", "contact_utm_first_term": "", "contact_utm_first_content": "", "contact_utm_last_source": "", "contact_utm_last_medium": "", "contact_utm_last_campaign": "", "contact_utm_last_term": "", "contact_utm_last_content": "", "contact_created_ts": 1773090544.682995, "ws_id": "solarsystem", "contact_archived_ts": 0.0, "contact_modified_ts": 1773090606.209483, "contact_bant_score": -1, "contact_phone": "+1-312-555-0283", "contact_commerce_external": {}, "contact_platform_ids": {}, "contact_last_inbound_ts": 1771370908.0, "contact_last_outbound_ts": 0.0}} + {"ws_id": "solarsystem", "deal_name": "Routine item", "deal_pipeline_id": "pipe_sales", "deal_stage_id": "stg_s3", "deal_id": "dDMa3ZyG9r", "deal_contact_id": "jfDF1vXgPs", "deal_value": 0.0, "deal_expected_close_ts": 0.0, "deal_closed_ts": 0.0, "deal_lost_reason": "", "deal_notes": "", "deal_tags": [], "deal_details": {"case": "c4", "seed_batch": "20260306-stall-seed"}, "deal_owner_fuser_id": "", "deal_priority": "NONE", "deal_created_ts": 1772801389.63589, "deal_modified_ts": 1772801389.63589, "deal_archived_ts": 0.0, "contact": {"contact_id": "jfDF1vXgPs", "contact_first_name": "Mateo", "contact_last_name": "Cruz", "contact_email": "mateo.cruz.20260306.d@example.invalid", "contact_notes": "", "contact_details": {"batch": "a9f3", "group": "set1"}, "contact_tags": [], "contact_address_line1": "", "contact_address_line2": "", "contact_address_city": "", "contact_address_state": "", "contact_address_zip": "", "contact_address_country": "", "contact_utm_first_source": "", "contact_utm_first_medium": "", "contact_utm_first_campaign": "", "contact_utm_first_term": "", "contact_utm_first_content": "", "contact_utm_last_source": "", "contact_utm_last_medium": "", "contact_utm_last_campaign": "", "contact_utm_last_term": "", "contact_utm_last_content": "", "contact_created_ts": 1772800694.496712, "ws_id": "solarsystem", "contact_archived_ts": 0.0, "contact_modified_ts": 1773073743.822755, "contact_bant_score": -1, "contact_phone": "", "contact_commerce_external": {}, "contact_platform_ids": {}, "contact_last_inbound_ts": 0.0, "contact_last_outbound_ts": 1768480694.0}} + {"ws_id": "solarsystem", "deal_name": "General item", "deal_pipeline_id": "pipe_sales", "deal_stage_id": "stg_s3", "deal_id": "qIUZEThUA7", "deal_contact_id": "eiENVujB7T", "deal_value": 0.0, "deal_expected_close_ts": 0.0, "deal_closed_ts": 0.0, "deal_lost_reason": "", "deal_notes": "", "deal_tags": [], "deal_details": {"case": "c3", "seed_batch": "20260306-stall-seed"}, "deal_owner_fuser_id": "", "deal_priority": "NONE", "deal_created_ts": 1772801389.610131, "deal_modified_ts": 1772801389.610131, "deal_archived_ts": 0.0, "contact": {"contact_id": "eiENVujB7T", "contact_first_name": "Priya", "contact_last_name": "Shah", "contact_email": "priya.shah.20260306.c@example.invalid", "contact_notes": "", "contact_details": {"batch": "a9f3", "group": "set1"}, "contact_tags": [], "contact_address_line1": "", "contact_address_line2": "", "contact_address_city": "", "contact_address_state": "", "contact_address_zip": "", "contact_address_country": "", "contact_utm_first_source": "", "contact_utm_first_medium": "", "contact_utm_first_campaign": "", "contact_utm_first_term": "", "contact_utm_first_content": "", "contact_utm_last_source": "", "contact_utm_last_medium": "", "contact_utm_last_campaign": "", "contact_utm_last_term": "", "contact_utm_last_content": "", "contact_created_ts": 1772800694.467831, "ws_id": "solarsystem", "contact_archived_ts": 0.0, "contact_modified_ts": 1772801433.348954, "contact_bant_score": -1, "contact_phone": "", "contact_commerce_external": {}, "contact_platform_ids": {}, "contact_last_inbound_ts": 0.0, "contact_last_outbound_ts": 1771072694.0}} + {"ws_id": "solarsystem", "deal_name": "Routine follow-up", "deal_pipeline_id": "pipe_sales", "deal_stage_id": "stg_s1", "deal_id": "IaS0lsLGiv", "deal_contact_id": "FUrvKkvzVa", "deal_value": 0.0, "deal_expected_close_ts": 0.0, "deal_closed_ts": 0.0, "deal_lost_reason": "", "deal_notes": "", "deal_tags": [], "deal_details": {"case": "c2", "seed_batch": "20260306-stall-seed"}, "deal_owner_fuser_id": "", "deal_priority": "NONE", "deal_created_ts": 1772801389.582444, "deal_modified_ts": 1772801389.582444, "deal_archived_ts": 0.0, "contact": {"contact_id": "FUrvKkvzVa", "contact_first_name": "Owen", "contact_last_name": "Reed", "contact_email": "owen.reed.20260306.b@example.invalid", "contact_notes": "", "contact_details": {"batch": "a9f3", "group": "set1"}, "contact_tags": [], "contact_address_line1": "", "contact_address_line2": "", "contact_address_city": "", "contact_address_state": "", "contact_address_zip": "", "contact_address_country": "", "contact_utm_first_source": "", "contact_utm_first_medium": "", "contact_utm_first_campaign": "", "contact_utm_first_term": "", "contact_utm_first_content": "", "contact_utm_last_source": "", "contact_utm_last_medium": "", "contact_utm_last_campaign": "", "contact_utm_last_term": "", "contact_utm_last_content": "", "contact_created_ts": 1772800694.434006, "ws_id": "solarsystem", "contact_archived_ts": 0.0, "contact_modified_ts": 1772801433.31729, "contact_bant_score": -1, "contact_phone": "", "contact_commerce_external": {}, "contact_platform_ids": {}, "contact_last_inbound_ts": 0.0, "contact_last_outbound_ts": 1771072694.0}} + + 💾 Full results stored in mongo at: erp_query_results/crm_deal_1773090960.json + Use mongo_store(op='cat', args={'path': 'erp_query_results/crm_deal_1773090960.json'}) to view full results. + call_id: toolu_012uqVaUDjyPXoznnKwKfiWj + - role: assistant + content: "Let me get the remaining 9 rows:" + tool_calls: + - id: toolu_013C1wP468MU6tDrLLmzzNDe + type: function + function: + name: mongo_store + arguments: '{"op": "cat", "args": + {"path":"erp_query_results/crm_deal_1773090960.json","lines_range":null,"safety_valve":"15k","pattern":null,"context":null,"content":null}}' + - role: tool + content: |- + 📄 File: erp_query_results/crm_deal_1773090960.json + Type: JSON + Size: 29,532 bytes (28.8 KB) + Items: 16 + Schema: + { + "$schema": "http://json-schema.org/schema#", + "type": "array", + "items": { + "type": "object", + "properties": { + "ws_id": { + "type": "string" + }, + "deal_name": { + "type": "string" + }, + "deal_pipeline_id": { + "type": "string" + }, + "deal_stage_id": { + "type": "string" + }, + "deal_id": { + "type": "string" + }, + "deal_contact_id": { + "type": "string" + }, + "deal_value": { + "type": "number" + }, + "deal_expected_close_ts": { + "type": "number" + }, + "deal_closed_ts": { + "type": "number" + }, + "deal_lost_reason": { + "type": "string" + }, + "deal_notes": { + "type": "string" + }, + "deal_tags": { + "type": "array" + }, + "deal_details": { + "type": "object", + "properties": { + "case": { + "type": "string" + }, + "seed_batch": { + "type": "string" + } + } + }, + "deal_owner_fuser_id": { + "type": "string" + }, + "deal_priority": { + "type": "string" + }, + "deal_created_ts": { + "type": "number" + }, + "deal_modified_ts": { + "type": "number" + }, + "deal_archived_ts": { + "type": "number" + }, + "contact": { + "type": "object", + "properties": { + "contact_id": { + "type": "string" + }, + "contact_first_name": { + "type": "string" + }, + "contact_last_name": { + "type": "string" + }, + "contact_email": { + "type": "string" + }, + "contact_notes": { + "type": "string" + }, + "contact_details": { + "type": "object", + "properties": { + "batch": { + "type": "string" + }, + "group": { + "type": "string" + } + } + }, + "contact_tags": { + "type": "array" + }, + "contact_address_line1": { + "type": "string" + }, + "contact_address_line2": { + "type": "string" + }, + "contact_address_city": { + "type": "string" + }, + "contact_address_state": { + "type": "string" + }, + "contact_address_zip": { + "type": "string" + }, + "contact_address_country": { + "type": "string" + }, + "contact_utm_first_source": { + "type": "string" + }, + "contact_utm_first_medium": { + "type": "string" + }, + "contact_utm_first_campaign": { + "type": "string" + }, + "contact_utm_first_term": { + "type": "string" + }, + "contact_utm_first_content": { + "type": "string" + }, + "contact_utm_last_source": { + "type": "string" + }, + "contact_utm_last_medium": { + "type": "string" + }, + "contact_utm_last_campaign": { + "type": "string" + }, + "contact_utm_last_term": { + "type": "string" + }, + "contact_utm_last_content": { + "type": "string" + }, + "contact_created_ts": { + "type": "number" + }, + "ws_id": { + "type": "string" + }, + "contact_archived_ts": { + "type": "number" + }, + "contact_modified_ts": { + "type": "number" + }, + "contact_bant_score": { + "type": "integer" + }, + "contact_phone": { + "type": "string" + }, + "contact_commerce_external": { + "type": "object" + }, + "contact_platform_ids": { + "type": "object" + }, + "contact_last_inbound_ts": { + "type": "number" + }, + "contact_last_outbound_ts": { + "type": "number" + } + }, + "required": [ + "contact_address_city", + "contact_address_country", + "contact_address_line1", + "contact_address_line2", + "contact_address_state", + "contact_address_zip", + "contact_archived_ts", + "contact_bant_score", + "contact_commerce_external", + "contact_created_ts", + "contact_details", + "contact_email", + "contact_first_name", + "contact_id", + "contact_last_inbound_ts", + "contact_last_name", + "contact_last_outbound_ts", + "contact_modified_ts", + "contact_notes", + "contact_phone", + ... + } + ⚠️ Truncated to 15 KB (safety_valve=15k) + ────────────────────────────────────────────────── + [ + { + "ws_id": "solarsystem", + "deal_name": "CedarBridge Expansion", + "deal_pipeline_id": "pipe_sales", + "deal_stage_id": "stg_s3", + "deal_id": "705LbGpwDy", + "deal_contact_id": "XZ0163XFWn", + "deal_value": 9000.0, + "deal_expected_close_ts": 0.0, + "deal_closed_ts": 0.0, + "deal_lost_reason": "", + "deal_notes": "", + "deal_tags": [], + "deal_details": {}, + "deal_owner_fuser_id": "", + "deal_priority": "HIGH", + "deal_created_ts": 1773090561.12105, + "deal_modified_ts": 1773090561.12105, + "deal_archived_ts": 0.0, + "contact": { + "contact_id": "XZ0163XFWn", + "contact_first_name": "Sandra", + "contact_last_name": "Khoury", + "contact_email": "s.khoury@cedarbridge.net", + "contact_notes": "", + "contact_details": {}, + "contact_tags": [], + "contact_address_line1": "", + "contact_address_line2": "", + "contact_address_city": "", + "contact_address_state": "", + "contact_address_zip": "", + "contact_address_country": "", + "contact_utm_first_source": "", + "contact_utm_first_medium": "", + "contact_utm_first_campaign": "", + "contact_utm_first_term": "", + "contact_utm_first_content": "", + "contact_utm_last_source": "", + "contact_utm_last_medium": "", + "contact_utm_last_campaign": "", + "contact_utm_last_term": "", + "contact_utm_last_content": "", + "contact_created_ts": 1773090544.872002, + "ws_id": "solarsystem", + "contact_archived_ts": 0.0, + "contact_modified_ts": 1773090606.337094, + "contact_bant_score": -1, + "contact_phone": "+1-617-555-0664", + "contact_commerce_external": {}, + "contact_platform_ids": {}, + "contact_last_inbound_ts": 0.0, + "contact_last_outbound_ts": 1768778908.0 + } + }, + { + "ws_id": "solarsystem", + "deal_name": "PeakForm Fitness Package", + "deal_pipeline_id": "pipe_sales", + "deal_stage_id": "stg_s3", + "deal_id": "Twh6276oyK", + "deal_contact_id": "ct37XiqaRl", + "deal_value": 7500.0, + "deal_expected_close_ts": 0.0, + "deal_closed_ts": 0.0, + "deal_lost_reason": "", + "deal_notes": "", + "deal_tags": [], + "deal_details": {}, + "deal_owner_fuser_id": "", + "deal_priority": "MEDIUM", + "deal_created_ts": 1773090561.065688, + "deal_modified_ts": 1773090561.065688, + "deal_archived_ts": 0.0, + "contact": { + "contact_id": "ct37XiqaRl", + "contact_first_name": "Derek", + "contact_last_name": "Olsen", + "contact_email": "d.olsen@peakform.fit", + "contact_notes": "", + "contact_details": {}, + "contact_tags": [], + "contact_address_line1": "", + "contact_address_line2": "", + "contact_address_city": "", + "contact_address_state": "", + "contact_address_zip": "", + "contact_address_country": "", + "contact_utm_first_source": "", + "contact_utm_first_medium": "", + "contact_utm_first_campaign": "", + "contact_utm_first_term": "", + "contact_utm_first_content": "", + "contact_utm_last_source": "", + "contact_utm_last_medium": "", + "contact_utm_last_campaign": "", + "contact_utm_last_term": "", + "contact_utm_last_content": "", + "contact_created_ts": 1773090544.801715, + "ws_id": "solarsystem", + "contact_archived_ts": 0.0, + "contact_modified_ts": 1773090606.275178, + "contact_bant_score": -1, + "contact_phone": "+1-720-555-0512", + "contact_commerce_external": {}, + "contact_platform_ids": {}, + "contact_last_inbound_ts": 0.0, + "contact_last_outbound_ts": 1771716508.0 + } + }, + { + "ws_id": "solarsystem", + "deal_name": "ArcticWave Bulk Order", + "deal_pipeline_id": "pipe_sales", + "deal_stage_id": "stg_s3", + "deal_id": "7VpPOynBZo", + "deal_contact_id": "T5x45IdlEp", + "deal_value": 12000.0, + "deal_expected_close_ts": 0.0, + "deal_closed_ts": 0.0, + "deal_lost_reason": "", + "deal_notes": "", + "deal_tags": [], + "deal_details": {}, + "deal_owner_fuser_id": "", + "deal_priority": "HIGH", + "deal_created_ts": 1773090561.011374, + "deal_modified_ts": 1773090561.011374, + "deal_archived_ts": 0.0, + "contact": { + "contact_id": "T5x45IdlEp", + "contact_first_name": "Tomoko", + "contact_last_name": "Watanabe", + "contact_email": "t.watanabe@arcticwave.co", + "contact_notes": "", + "contact_details": {}, + "contact_tags": [], + "contact_address_line1": "", + "contact_address_line2": "", + "contact_address_city": "", + "contact_address_state": "", + "contact_address_zip": "", + "contact_address_country": "", + "contact_utm_first_source": "", + "contact_utm_first_medium": "", + "contact_utm_first_campaign": "", + "contact_utm_first_term": "", + "contact_utm_first_content": "", + "contact_utm_last_source": "", + "contact_utm_last_medium": "", + "contact_utm_last_campaign": "", + "contact_utm_last_term": "", + "contact_utm_last_content": "", + "contact_created_ts": 1773090544.745269, + "ws_id": "solarsystem", + "contact_archived_ts": 0.0, + "contact_modified_ts": 1773090606.23562, + "contact_bant_score": -1, + "contact_phone": "+1-206-555-0391", + "contact_commerce_external": {}, + "contact_platform_ids": {}, + "contact_last_inbound_ts": 0.0, + "contact_last_outbound_ts": 1771025308.0 + } + }, + { + "ws_id": "solarsystem", + "deal_name": "Horizon Labs Inquiry", + "deal_pipeline_id": "pipe_sales", + "deal_stage_id": "stg_s1", + "deal_id": "6DIj9LwSzC", + "deal_contact_id": "bubZwDVYy7", + "deal_value": 2000.0, + "deal_expected_close_ts": 0.0, + "deal_closed_ts": 0.0, + "deal_lost_reason": "", + "deal_notes": "", + "deal_tags": [], + "deal_details": {}, + "deal_owner_fuser_id": "", + "deal_priority": "LOW", + "deal_created_ts": 1773090560.952685, + "deal_modified_ts": 1773090560.952685, + "deal_archived_ts": 0.0, + "contact": { + "contact_id": "bubZwDVYy7", + "contact_first_name": "Priya", + "contact_last_name": "Chatterjee", + "contact_email": "priya.c@horizonlabs.io", + "contact_notes": "", + "contact_details": {}, + "contact_tags": [], + "contact_address_line1": "", + "contact_address_line2": "", + "contact_address_city": "", + "contact_address_state": "", + "contact_address_zip": "", + "contact_address_country": "", + "contact_utm_first_source": "", + "contact_utm_first_medium": "", + "contact_utm_first_campaign": "", + "contact_utm_first_term": "", + "contact_utm_first_content": "", + "contact_utm_last_source": "", + "contact_utm_last_medium": "", + "contact_utm_last_campaign": "", + "contact_utm_last_term": "", + "contact_utm_last_content": "", + "contact_created_ts": 1773090544.682995, + "ws_id": "solarsystem", + "contact_archived_ts": 0.0, + "contact_modified_ts": 1773090606.209483, + "contact_bant_score": -1, + "contact_phone": "+1-312-555-0283", + "contact_commerce_external": {}, + "contact_platform_ids": {}, + "contact_last_inbound_ts": 1771370908.0, + "contact_last_outbound_ts": 0.0 + } + }, + { + "ws_id": "solarsystem", + "deal_name": "Routine item", + "deal_pipeline_id": "pipe_sales", + "deal_stage_id": "stg_s3", + "deal_id": "dDMa3ZyG9r", + "deal_contact_id": "jfDF1vXgPs", + "deal_value": 0.0, + "deal_expected_close_ts": 0.0, + "deal_closed_ts": 0.0, + "deal_lost_reason": "", + "deal_notes": "", + "deal_tags": [], + "deal_details": { + "case": "c4", + "seed_batch": "20260306-stall-seed" + }, + "deal_owner_fuser_id": "", + "deal_priority": "NONE", + "deal_created_ts": 1772801389.63589, + "deal_modified_ts": 1772801389.63589, + "deal_archived_ts": 0.0, + "contact": { + "contact_id": "jfDF1vXgPs", + "contact_first_name": "Mateo", + "contact_last_name": "Cruz", + "contact_email": "mateo.cruz.20260306.d@example.invalid", + "contact_notes": "", + "contact_details": { + "batch": "a9f3", + "group": "set1" + }, + "contact_tags": [], + "contact_address_line1": "", + "contact_address_line2": "", + "contact_address_city": "", + "contact_address_state": "", + "contact_address_zip": "", + "contact_address_country": "", + "contact_utm_first_source": "", + "contact_utm_first_medium": "", + "contact_utm_first_campaign": "", + "contact_utm_first_term": "", + "contact_utm_first_content": "", + "contact_utm_last_source": "", + "contact_utm_last_medium": "", + "contact_utm_last_campaign": "", + "contact_utm_last_term": "", + "contact_utm_last_content": "", + "contact_created_ts": 1772800694.496712, + "ws_id": "solarsystem", + "contact_archived_ts": 0.0, + "contact_modified_ts": 1773073743.822755, + "contact_bant_score": -1, + "contact_phone": "", + "contact_commerce_external": {}, + "contact_platform_ids": {}, + "contact_last_inbound_ts": 0.0, + "contact_last_outbound_ts": 1768480694.0 + } + }, + { + "ws_id": "solarsystem", + "deal_name": "General item", + "deal_pipeline_id": "pipe_sales", + "deal_stage_id": "stg_s3", + "deal_id": "qIUZEThUA7", + "deal_contact_id": "eiENVujB7T", + "deal_value": 0.0, + "deal_expected_close_ts": 0.0, + "deal_closed_ts": 0.0, + "deal_lost_reason": "", + "deal_notes": "", + "deal_tags": [], + "deal_details": { + "case": "c3", + "seed_batch": "20260306-stall-seed" + }, + "deal_owner_fuser_id": "", + "deal_priority": "NONE", + "deal_created_ts": 1772801389.610131, + "deal_modified_ts": 1772801389.610131, + "deal_archived_ts": 0.0, + "contact": { + "contact_id": "eiENVujB7T", + "contact_first_name": "Priya", + "contact_last_name": "Shah", + "contact_email": "priya.shah.20260306.c@example.invalid", + "contact_notes": "", + "contact_details": { + "batch": "a9f3", + "group": "set1" + }, + "contact_tags": [], + "contact_address_line1": "", + "contact_address_line2": "", + "contact_address_city": "", + "contact_address_state": "", + "contact_address_zip": "", + "contact_address_country": "", + "contact_utm_first_source": "", + "contact_utm_first_medium": "", + "contact_utm_first_campaign": "", + "contact_utm_first_term": "", + "contact_utm_first_content": "", + "contact_utm_last_source": "", + "contact_utm_last_medium": "", + "contact_utm_last_campaign": "", + "contact_utm_last_term": "", + "contact_utm_last_content": "", + "contact_created_ts": 1772800694.467831, + "ws_id": "solarsystem", + "contact_archived_ts": 0.0, + "contact_modified_ts": 1772801433.348954, + "contact_bant_score": -1, + "contact_phone": "", + "contact_commerce_external": {}, + "contact_platform_ids": {}, + "contact_last_inbound_ts": 0.0, + "contact_last_outbound_ts": 1771072694.0 + } + }, + { + "ws_id": "solarsystem", + "deal_name": "Routine follow-up", + "deal_pipeline_id": "pipe_sales", + "deal_stage_id": "stg_s1", + "deal_id": "IaS0lsLGiv", + "deal_contact_id": "FUrvKkvzVa", + "deal_value": 0.0, + "deal_expected_close_ts": 0.0, + "deal_closed_ts": 0.0, + "deal_lost_reason": "", + "deal_notes": "", + "deal_tags": [], + "deal_details": { + "case": "c2", + "seed_batch": "20260306-stall-seed" + }, + "deal_owner_fuser_id": "", + "deal_priority": "NONE", + "deal_created_ts": 1772801389.582444, + "deal_modified_ts": 1772801389.582444, + "deal_archived_ts": 0.0, + "contact": { + "contact_id": "FUrvKkvzVa", + "contact_first_name": "Owen", + "contact_last_name": "Reed", + "contact_email": "owen.reed.20260306.b@example.invalid", + "contact_notes": "", + "contact_details": { + "batch": "a9f3", + "group": "set1" + }, + "contact_tags": [], + "contact_address_line1": "", + "contact_address_line2": "", + "contact_address_city": "", + "contact_address_state": "", + "contact_address_zip": "", + "contact_address_country": "", + "contact_utm_first_source": "", + "contact_utm_first_medium": "", + "contact_utm_first_campaign": "", + "contact_utm_first_term": "", + "contact_utm_first_content": "", + "contact_utm_last_source": "", + "contact_utm_last_medium": "", + "contact_utm_last_campaign": "", + "contact_utm_last_term": "", + "contact_utm_last_content": "", + "contact_created_ts": 1772800694.434006, + "ws_id": "solarsystem", + "contact_archived_ts": 0.0, + "contact_modified_ts": 1772801433.31729, + "contact_bant_score": -1, + "contact_phone": "", + "contact_commerce_external": {}, + "contact_platform_ids": {}, + "contact_last_inbound_ts": 0.0, + "contact_last_outbound_ts": 1771072694.0 + } + }, + { + "ws_id": "solarsystem", + "deal_name": "Holt purchase", + "deal_pipeline_id": "pipe_sales", + "deal_stage_id": "9Tjzi44QY7", + "deal_id": "8hBd0HTnRE", + "deal_contact_id": "HSw68ghSOA", + "deal_value": 4000.0, + "deal_expected_close_ts": 0.0, + "deal_closed_ts": 1768570056.0, + "deal_lost_reason": "", + "deal_notes": "", + "deal_tags": [], + "deal_details": {}, + "deal_owner_fuser_id": "", + "deal_priority": "HIGH", + "deal_created_ts": 1772745935.049109, + "deal_modified_ts": 1772745935.049109, + "deal_archived_ts": 0.0, + "contact": { + "contact_id": "HSw68ghSOA", + "contact_first_name": "Avery", + "contact_last_name": "Holt", + "contact_email": "a.holt@example.com", + "contact_notes": "Attended webinar", + "contact_details": {}, + "contact_tags": [], + "contact_address_line1": "", + "contact_address_line2": "", + "contact_address_city": "", + "contact_address_state": "", + "contact_address_zip": "", + "contact_address_country": "", + "contact_utm_first_source": "", + "contact_utm_first_medium": "", + "contact_utm_first_campaign": "", + "contact_utm_first_term": "", + "contact_utm_first_content": "", + "contact_utm_last_source": "", + "contact_utm_last_medium": "", + "contact_utm_last_campaign": "", + "contact_utm_last_term": "", + "contact_utm_last_content": "", + "contact_created_ts": 1772717307.230566, + "ws_id": "solarsystem", + "contact_archived_ts": 0.0, + "contact_modified_ts": 1772717307.230566, + "contact_bant_score": -1, + "contact_phone": "", + "contact_commerce_external": {}, + "contact_platform_ids": {}, + "contact_last_inbound_ts": 1768397256.0, + "contact_last_outbound_ts": 0.0 + } + }, + { + "ws_id": "solarsystem", + "deal_name": "Voss order", + "deal_pipeline_id": "pipe_sales", + "deal_stage_id": "D7q0nkBbUp", + "deal_id": "rMK1Z2XPZf", + "deal_contact_id": "m00LW64OLZ", + "deal_value": 1500.0, + "deal_expected_close_ts": 0.0, + "deal_closed_ts": 1768570056.0, + "deal_lost_reason": "No budget", + "deal_notes": "", + "deal_tags": [], + "deal_details": {}, + "deal_owner_fuser_id": "", + "deal_priority": "NONE", + "deal_created_ts": 1772745935.023156, + + ... (truncated) + + 💡 To see full content, download the file or increase safety_valve + call_id: toolu_013C1wP468MU6tDrLLmzzNDe + - role: assistant + content: "Let me get the remaining rows:" + tool_calls: + - id: toolu_01DjQQgoSV9ZPBho1EXxDoHs + type: function + function: + name: mongo_store + arguments: '{"op": "grep", "args": + {"path":"erp_query_results/crm_deal_1773090960.json","lines_range":null,"safety_valve":"10k","pattern":"deal_name|deal_stage_id|deal_id|contact_first_name|contact_last_name|contact_email|contact_last_inbound_ts|contact_last_outbound_ts|deal_details|last_followup","context":0,"content":null}}' + - role: tool + content: |- + + === erp_query_results/crm_deal_1773090960.json === + 3: "deal_name": "CedarBridge Expansion", + 5: "deal_stage_id": "stg_s3", + 6: "deal_id": "705LbGpwDy", + 14: "deal_details": {}, + 22: "contact_first_name": "Sandra", + 23: "contact_last_name": "Khoury", + 24: "contact_email": "s.khoury@cedarbridge.net", + 52: "contact_last_inbound_ts": 0.0, + 53: "contact_last_outbound_ts": 1768778908.0 + 58: "deal_name": "PeakForm Fitness Package", + 60: "deal_stage_id": "stg_s3", + 61: "deal_id": "Twh6276oyK", + 69: "deal_details": {}, + 77: "contact_first_name": "Derek", + 78: "contact_last_name": "Olsen", + 79: "contact_email": "d.olsen@peakform.fit", + 107: "contact_last_inbound_ts": 0.0, + 108: "contact_last_outbound_ts": 1771716508.0 + 113: "deal_name": "ArcticWave Bulk Order", + 115: "deal_stage_id": "stg_s3", + 116: "deal_id": "7VpPOynBZo", + 124: "deal_details": {}, + 132: "contact_first_name": "Tomoko", + 133: "contact_last_name": "Watanabe", + 134: "contact_email": "t.watanabe@arcticwave.co", + 162: "contact_last_inbound_ts": 0.0, + 163: "contact_last_outbound_ts": 1771025308.0 + 168: "deal_name": "Horizon Labs Inquiry", + 170: "deal_stage_id": "stg_s1", + 171: "deal_id": "6DIj9LwSzC", + 179: "deal_details": {}, + 187: "contact_first_name": "Priya", + 188: "contact_last_name": "Chatterjee", + 189: "contact_email": "priya.c@horizonlabs.io", + 217: "contact_last_inbound_ts": 1771370908.0, + 218: "contact_last_outbound_ts": 0.0 + 223: "deal_name": "Routine item", + 225: "deal_stage_id": "stg_s3", + 226: "deal_id": "dDMa3ZyG9r", + 234: "deal_details": { + 245: "contact_first_name": "Mateo", + 246: "contact_last_name": "Cruz", + 247: "contact_email": "mateo.cruz.20260306.d@example.invalid", + 278: "contact_last_inbound_ts": 0.0, + 279: "contact_last_outbound_ts": 1768480694.0 + 284: "deal_name": "General item", + 286: "deal_stage_id": "stg_s3", + 287: "deal_id": "qIUZEThUA7", + 295: "deal_details": { + 306: "contact_first_name": "Priya", + 307: "contact_last_name": "Shah", + 308: "contact_email": "priya.shah.20260306.c@example.invalid", + 339: "contact_last_inbound_ts": 0.0, + 340: "contact_last_outbound_ts": 1771072694.0 + 345: "deal_name": "Routine follow-up", + 347: "deal_stage_id": "stg_s1", + 348: "deal_id": "IaS0lsLGiv", + 356: "deal_details": { + 367: "contact_first_name": "Owen", + 368: "contact_last_name": "Reed", + 369: "contact_email": "owen.reed.20260306.b@example.invalid", + 400: "contact_last_inbound_ts": 0.0, + 401: "contact_last_outbound_ts": 1771072694.0 + 406: "deal_name": "Holt purchase", + 408: "deal_stage_id": "9Tjzi44QY7", + 409: "deal_id": "8hBd0HTnRE", + 417: "deal_details": {}, + 425: "contact_first_name": "Avery", + 426: "contact_last_name": "Holt", + 427: "contact_email": "a.holt@example.com", + 455: "contact_last_inbound_ts": 1768397256.0, + 456: "contact_last_outbound_ts": 0.0 + 461: "deal_name": "Voss order", + 463: "deal_stage_id": "D7q0nkBbUp", + 464: "deal_id": "rMK1Z2XPZf", + 472: "deal_details": {}, + 480: "contact_first_name": "Riley", + 481: "contact_last_name": "Voss", + 482: "contact_email": "r.voss@example.com", + 510: "contact_last_inbound_ts": 1768397256.0, + 511: "contact_last_outbound_ts": 0.0 + 516: "deal_name": "Brennan follow-up", + 518: "deal_stage_id": "stg_s2", + 519: "deal_id": "w1uianLTbR", + 527: "deal_details": {}, + 535: "contact_first_name": "Casey", + 536: "contact_last_name": "Brennan", + 537: "contact_email": "c.brennan@example.com", + 565: "contact_last_inbound_ts": 1768397256.0, + 566: "contact_last_outbound_ts": 0.0 + 571: "deal_name": "Ashby proposal", + 573: "deal_stage_id": "stg_s3", + 574: "deal_id": "fyqUXkVJZC", + 582: "deal_details": {}, + 590: "contact_first_name": "Morgan", + 591: "contact_last_name": "Ashby", + 592: "contact_email": "m.ashby@example.com", + 620: "contact_last_inbound_ts": 1770989256.0, + 621: "contact_last_outbound_ts": 0.0 + 626: "deal_name": "Reeves lead", + 628: "deal_stage_id": "stg_s1", + 629: "deal_id": "1YX4BRvM6x", + 637: "deal_details": {}, + 645: "contact_first_name": "Taylor", + 646: "contact_last_name": "Reeves", + 647: "contact_email": "t.reeves@example.com", + 675: "contact_last_inbound_ts": 1770989256.0, + 676: "contact_last_outbound_ts": 0.0 + 681: "deal_name": "Tanaka Pilot", + 683: "deal_stage_id": "stg_s1", + 684: "deal_id": "deal_5", + 692: "deal_details": {}, + 700: "contact_first_name": "Hiro", + 701: "contact_last_name": "Tanaka", + 702: "contact_email": "hiro.t@cleanenergy.jp", + 730: "contact_last_inbound_ts": 0.0, + 731: "contact_last_outbound_ts": 0.0 + 736: "deal_name": "Nguyen Rooftop", + 738: "deal_stage_id": "stg_s1", + 739: "deal_id": "deal_3", + 747: "deal_details": {}, + 755: "contact_first_name": "Frank", + 756: "contact_last_name": "Nguyen", + 757: "contact_email": "frank.nguyen@gmail.com", + 785: "contact_last_inbound_ts": 0.0, + 786: "contact_last_outbound_ts": 0.0 + 791: "deal_name": "First Op", + 793: "deal_stage_id": "stg_s1", + 794: "deal_id": "deal_1", + 802: "deal_details": {}, + 810: "contact_first_name": "Alice", + 811: "contact_last_name": "Johnson", + 812: "contact_email": "alice.johnson@example.com", + 840: "contact_last_inbound_ts": 0.0, + 841: "contact_last_outbound_ts": 0.0 + 846: "deal_name": "F", + 848: "deal_stage_id": "stg_s2", + 849: "deal_id": "deal_2", + 857: "deal_details": {}, + 865: "contact_first_name": "Alice", + 866: "contact_last_name": "Johnson", + 867: "contact_email": "alice.johnson@example.com", + 895: "contact_last_inbound_ts": 0.0, + 896: "contact_last_outbound_ts": 0.0 + call_id: toolu_01DjQQgoSV9ZPBho1EXxDoHs + - role: assistant + content: |- + Now I have a complete picture. Let me analyze all 16 deals: + + **Key timestamps:** + - now = 1773357248 + - stall_ts = 1772147648 (14 days ago) + - archive_ts = 1769469248 (45 days ago) + + **Policy stages:** + - stg_s1 (To Contact) → skip + - stg_s2 (1st Contact Failed) → skip + - stg_s3 (Proposal Sent) → email (cutoff stage, template: /sales-pipeline/stall-email) + - 9Tjzi44QY7, D7q0nkBbUp → unknown stages, not in policy + + Let me categorize each deal: + + | # | Deal | Stage | last_outbound | last_inbound | Action | + |---|------|-------|---------------|--------------|--------| + | 1 | CedarBridge Expansion | stg_s3 | 1768778908 (< archive_ts) | 0 | **ARCHIVE** (45+ days) | + | 2 | PeakForm Fitness | stg_s3 | 1771716508 | 0 | **EMAIL** (stalled 14+, not 45+) | + | 3 | ArcticWave Bulk | stg_s3 | 1771025308 | 0 | **EMAIL** (stalled 14+, not 45+) | + | 4 | Horizon Labs | stg_s1 | 0 | 1771370908 | **SKIP** (stg_s1 = skip) | + | 5 | Routine item | stg_s3 | 1768480694 (< archive_ts) | 0 | **ARCHIVE** | + | 6 | General item | stg_s3 | 1771072694 | 0 | **EMAIL** (stalled 14+, not 45+) | + | 7 | Routine follow-up | stg_s1 | 1771072694 | 0 | **SKIP** (stg_s1 = skip) | + | 8 | Holt purchase | 9Tjzi44QY7 | 0 | 1768397256 | **UNKNOWN STAGE** | + | 9 | Voss order | D7q0nkBbUp | 0 | 1768397256 | **UNKNOWN STAGE** | + | 10 | Brennan follow-up | stg_s2 | 0 | 1768397256 | **SKIP** (stg_s2 = skip) | + | 11 | Ashby proposal | stg_s3 | 0 | 1770989256 | **stg_s3 but no outbound** - no prior outreach to follow up on | + | 12 | Reeves lead | stg_s1 | 0 | 1770989256 | **SKIP** (stg_s1 = skip) | + | 13 | Tanaka Pilot | stg_s1 | 0 | 0 | **SKIP** (stg_s1 = skip) | + | 14 | Nguyen Rooftop | stg_s1 | 0 | 0 | **SKIP** (stg_s1 = skip) | + | 15 | First Op | stg_s1 | 0 | 0 | **SKIP** (stg_s1 = skip) | + | 16 | F | stg_s2 | 0 | 0 | **SKIP** (stg_s2 = skip) | + + For Ashby proposal (#11): stage stg_s3 with action=email, but contact_last_outbound_ts=0, meaning we never sent them anything outbound. There's nothing to follow up on. I'll skip this one. + + Now let me load the stall email template and company summary for the emails: + tool_calls: + - id: toolu_013YkpY6sizPjxvzj4H369kd + type: function + function: + name: flexus_policy_document + arguments: '{"op": "cat", "args": {"p":"/sales-pipeline/stall-email"}}' + - id: toolu_011SDiJZTtUXcCc3i5j5RxjA + type: function + function: + name: flexus_policy_document + arguments: '{"op": "cat", "args": {"p":"/company/summary"}}' + - role: tool + content: |- + 📄 /sales-pipeline/stall-email + + { + "name": "Sales v1 stalled proposal follow-up", + "tone": "direct", + "usage": "Use when a Sales v1 deal is in Proposal Sent and has had no activity for 14 or more days.", + "subject": "Checking in on your proposal", + "body_text": "Hi {{contact_first_name}},\n\nJust checking in on the proposal I sent over. If you have any questions or want to talk through anything, I’m happy to help.\n\nIf the timing has changed or this is no longer a priority, no problem — just let me know and I can close this out for now.\n\nBest,\n{{sender_name}}\n{{company_name}}", + "stage_name": "Proposal Sent", + "pipeline_name": "Sales v1" + } + call_id: toolu_013YkpY6sizPjxvzj4H369kd + - role: tool + content: |- + 📄 /company/summary + + { + "mission": "Help people conquer mornings through innovative wake-up technology", + "tagline": "Never Hit Snooze Again", + "website": "https://ejectorbed.lovable.app/", + "industry": "Smart Home / Sleep Technology", + "company_name": "Ejector Bed Inc.", + "shopify_store": "https://flexus-testing.myshopify.com/", + "flagship_product": "Ejector Bed - pneumatic bed that launches you awake at alarm time" + } + call_id: toolu_011SDiJZTtUXcCc3i5j5RxjA + - role: assistant + content: |- + Now I have everything I need. Let me execute: + + **1. Send follow-up emails** to 3 contacts (PeakForm/Derek, ArcticWave/Tomoko, General item/Priya Shah) + **2. Archive 2 deals** (CedarBridge, Routine item) - move to Lost + + Let me start with the 3 emails and 2 archive actions. First the emails: + tool_calls: + - id: toolu_015PegVYxToN7L41hWrtfjb6 + type: function + function: + name: email_send + arguments: "{\"from\": \"Vix \", \"to\": [\"d.olsen@peakform.fit\"], + \"subject\": \"Checking in on your proposal\", \"html\": \"\", \"text\": \"Hi + Derek,\\n\\nJust checking in on the proposal I sent over. If you have any questions or + want to talk through anything, I'm happy to help.\\n\\nIf the timing has changed or this + is no longer a priority, no problem — just let me know and I can close this out for + now.\\n\\nBest,\\nVix\\nEjector Bed Inc.\", \"cc\": [], \"bcc\": [], \"reply_to\": + \"support@test1.yusta.eu\"}" + - id: toolu_01SvVceaXwn4y9Jaokh3Bm4W + type: function + function: + name: email_send + arguments: "{\"from\": \"Vix \", \"to\": [\"t.watanabe@arcticwave.co\"], + \"subject\": \"Checking in on your proposal\", \"html\": \"\", \"text\": \"Hi + Tomoko,\\n\\nJust checking in on the proposal I sent over. If you have any questions or + want to talk through anything, I'm happy to help.\\n\\nIf the timing has changed or this + is no longer a priority, no problem — just let me know and I can close this out for + now.\\n\\nBest,\\nVix\\nEjector Bed Inc.\", \"cc\": [], \"bcc\": [], \"reply_to\": + \"support@test1.yusta.eu\"}" + - id: toolu_01AbTw4FukWwRyYBMcN48ip3 + type: function + function: + name: email_send + arguments: "{\"from\": \"Vix \", \"to\": + [\"priya.shah.20260306.c@example.invalid\"], \"subject\": \"Checking in on your + proposal\", \"html\": \"\", \"text\": \"Hi Priya,\\n\\nJust checking in on the proposal + I sent over. If you have any questions or want to talk through anything, I'm happy to + help.\\n\\nIf the timing has changed or this is no longer a priority, no problem — just + let me know and I can close this out for now.\\n\\nBest,\\nVix\\nEjector Bed Inc.\", + \"cc\": [], \"bcc\": [], \"reply_to\": \"support@test1.yusta.eu\"}" + - id: toolu_01EmCbBScorY4tqiTPhpkZBc + type: function + function: + name: manage_crm_deal + arguments: '{"op": "patch", "args": + {"deal_id":"705LbGpwDy","deal_stage_id":"b8wsoZrKag","deal_lost_reason":"Stalled 45+ + days - no activity since proposal sent"}}' + - id: toolu_01JuW1EuZvCCXDhdhu7hUH2E + type: function + function: + name: manage_crm_deal + arguments: '{"op": "patch", "args": + {"deal_id":"dDMa3ZyG9r","deal_stage_id":"b8wsoZrKag","deal_lost_reason":"Stalled 45+ + days - no activity since proposal sent"}}' + - role: tool + content: "Email sent (id: 9fc240c1-255a-46b9-8bc0-1a73902fb58d)" + call_id: toolu_015PegVYxToN7L41hWrtfjb6 + - role: tool + content: "Email sent (id: 0a5c075e-348f-4257-990d-945f92d09179)" + call_id: toolu_01SvVceaXwn4y9Jaokh3Bm4W + - role: tool + content: "Email sent (id: 4523a18c-265d-49ae-baa5-8d48eb51303a)" + call_id: toolu_01AbTw4FukWwRyYBMcN48ip3 + - role: tool + content: | + ✅ Updated + call_id: toolu_01EmCbBScorY4tqiTPhpkZBc + - role: tool + content: | + ✅ Updated + call_id: toolu_01JuW1EuZvCCXDhdhu7hUH2E + - role: assistant + content: "Good. Now let me log CRM activities for the emails sent, update deal_details with + last_followup_stage, and log activities:" + tool_calls: + - id: toolu_014dnrNUBf82sAUwBr9NNjuq + type: function + function: + name: log_crm_activity + arguments: '{"contact_id": "ct37XiqaRl", "activity_type": "EMAIL", "direction": "OUTBOUND", + "platform": "EMAIL", "title": "Stall follow-up: Proposal Sent", "summary": "Sent stall + follow-up email to Derek Olsen (PeakForm Fitness Package) - deal stalled 14+ days in + Proposal Sent stage."}' + - id: toolu_01AUFD1UoWcxyEWqgWHXG5x6 + type: function + function: + name: log_crm_activity + arguments: '{"contact_id": "T5x45IdlEp", "activity_type": "EMAIL", "direction": "OUTBOUND", + "platform": "EMAIL", "title": "Stall follow-up: Proposal Sent", "summary": "Sent stall + follow-up email to Tomoko Watanabe (ArcticWave Bulk Order) - deal stalled 14+ days in + Proposal Sent stage."}' + - id: toolu_015yHS8sDyFS7nx3cAmPkvLp + type: function + function: + name: log_crm_activity + arguments: '{"contact_id": "eiENVujB7T", "activity_type": "EMAIL", "direction": "OUTBOUND", + "platform": "EMAIL", "title": "Stall follow-up: Proposal Sent", "summary": "Sent stall + follow-up email to Priya Shah (General item) - deal stalled 14+ days in Proposal Sent + stage."}' + - id: toolu_0177P97DB65MfsVPYt9NtdaN + type: function + function: + name: manage_crm_deal + arguments: '{"op": "patch", "args": + {"deal_id":"Twh6276oyK","deal_details":{"last_followup_stage":"stg_s3"}}}' + - id: toolu_01EmSdeXgmg3rozfdsrtRFNe + type: function + function: + name: manage_crm_deal + arguments: '{"op": "patch", "args": + {"deal_id":"7VpPOynBZo","deal_details":{"last_followup_stage":"stg_s3"}}}' + - id: toolu_0189t94QRNJTkwZiSEXEPBqY + type: function + function: + name: manage_crm_deal + arguments: '{"op": "patch", "args": + {"deal_id":"qIUZEThUA7","deal_details":{"case":"c3","seed_batch":"20260306-stall-seed","last_followup_stage":"stg_s3"}}}' + - role: tool + content: | + ✅ Activity logged + call_id: toolu_014dnrNUBf82sAUwBr9NNjuq + - role: tool + content: | + ✅ Activity logged + call_id: toolu_01AUFD1UoWcxyEWqgWHXG5x6 + - role: tool + content: | + ✅ Activity logged + call_id: toolu_015yHS8sDyFS7nx3cAmPkvLp + - role: tool + content: | + ✅ Updated + call_id: toolu_0177P97DB65MfsVPYt9NtdaN + - role: tool + content: | + ✅ Updated + call_id: toolu_01EmSdeXgmg3rozfdsrtRFNe + - role: tool + content: | + ✅ Updated + call_id: toolu_0189t94QRNJTkwZiSEXEPBqY + - role: assistant + content: |- + Now I need to flag the unknown stages. Deals "Holt purchase" (stage 9Tjzi44QY7) and "Voss order" (stage D7q0nkBbUp) are in stages not defined in the policy. These stages are not stg_s1, stg_s2, or stg_s3, and they're not Won/Lost either. Since I don't know where they fall relative to the cutoff, I need to alert about them. + + Let me also check: are stages 9Tjzi44QY7 and D7q0nkBbUp at or after the cutoff? I don't have stage ordering info, but they're not in the stage_actions at all. Per the instructions, I should print a widget for stages that are at or after cutoff but have no entry. Since these stages have no entry at all and I can't determine their position, I should flag them. + + However, looking more carefully - both Holt and Voss have contact_last_outbound_ts = 0, meaning no outbound was ever sent. So even if they were actionable stages, there's nothing to follow up on. But I should still flag the unknown stages. + + Let me now mark the task done with a summary: + tool_calls: + - id: toolu_0185XyocyfMg3EHdKEDaX4dr + type: function + function: + name: print_widget + arguments: '{"t": "restart-chat", "q": "⚠️ Stages \"9Tjzi44QY7\" (Holt purchase deal) and + \"D7q0nkBbUp\" (Voss order deal) have stalled deals but no action defined in the + stall-deals-policy — click to decide what to do."}' + - role: tool + content: |- + ⏸️WAIT_FOR_USER + Printing UI widget: restart-chat + call_id: toolu_0185XyocyfMg3EHdKEDaX4dr diff --git a/flexus_simple_bots/vix/vix_bot.py b/flexus_simple_bots/vix/vix_bot.py index d74c64a3..b45ba9e2 100644 --- a/flexus_simple_bots/vix/vix_bot.py +++ b/flexus_simple_bots/vix/vix_bot.py @@ -38,13 +38,13 @@ "flexus_policy_document", "print_widget", "erp[meta, data, crud, csv_import]", + "crm[manage_contact, manage_deal, log_activity]", ], builtin_skills=vix_install.VIX_SKILLS, ) TOOLS = [ fi_mongo_store.MONGO_STORE_TOOL, - fi_crm.LOG_CRM_ACTIVITY_TOOL, fi_crm_automations.CRM_AUTOMATION_TOOL, fi_resend.RESEND_SEND_TOOL, fi_resend.RESEND_SETUP_TOOL, @@ -60,7 +60,6 @@ async def vix_main_loop(fclient: ckit_client.FlexusClient, rcx: ckit_bot_exec.Ro setup = ckit_bot_exec.official_setup_mixing_procedure(vix_install.VIX_SETUP_SCHEMA, rcx.persona.persona_setup) await ckit_integrations_db.main_loop_integrations_init(VIX_INTEGRATIONS, rcx, setup) - crm_integration = fi_crm.IntegrationCrm(fclient, rcx.persona.ws_id) automations_integration = fi_crm_automations.IntegrationCrmAutomations( fclient, rcx, setup, available_erp_tables=ERP_TABLES, ) @@ -145,10 +144,6 @@ async def toolcall_email_send(toolcall: ckit_cloudtool.FCloudtoolCall, model_pro async def toolcall_email_setup(toolcall: ckit_cloudtool.FCloudtoolCall, model_produced_args: Dict[str, Any]) -> str: return await resend_integration.setup_called_by_model(toolcall, model_produced_args) - @rcx.on_tool_call(fi_crm.LOG_CRM_ACTIVITY_TOOL.name) - async def toolcall_log_crm_activity(toolcall: ckit_cloudtool.FCloudtoolCall, model_produced_args: Dict[str, Any]) -> str: - return await crm_integration.handle_log_crm_activity(toolcall, model_produced_args) - @rcx.on_tool_call(fi_crm_automations.CRM_AUTOMATION_TOOL.name) async def toolcall_crm_automation(toolcall: ckit_cloudtool.FCloudtoolCall, model_produced_args: Dict[str, Any]) -> str: return await automations_integration.handle_crm_automation(toolcall, model_produced_args) diff --git a/flexus_simple_bots/vix/vix_install.py b/flexus_simple_bots/vix/vix_install.py index 99ebd7ec..55e67873 100644 --- a/flexus_simple_bots/vix/vix_install.py +++ b/flexus_simple_bots/vix/vix_install.py @@ -58,7 +58,7 @@ ("default", ckit_bot_install.FMarketplaceExpertInput( fexp_system_prompt=vix_prompts.vix_prompt_marketing, fexp_python_kernel="", - fexp_block_tools="shopify_cart", + fexp_block_tools="shopify_cart,manage_crm_contact,manage_crm_deal", fexp_allow_tools="", fexp_inactivity_timeout=3600, fexp_description="Marketing assistant for CRM management, contact import, automated outreach, and company/product setup.", @@ -67,7 +67,7 @@ ("sales", ckit_bot_install.FMarketplaceExpertInput( fexp_system_prompt=vix_prompts.vix_prompt_sales, fexp_python_kernel="", - fexp_block_tools="crm_automation,shopify,*setup", + fexp_block_tools="crm_automation,shopify,*setup,erp_table_crud", fexp_allow_tools="", fexp_inactivity_timeout=3600, fexp_description="Conducts sales conversations using C.L.O.S.E.R. Framework, qualifies leads with BANT, and handles objections with consultative approach.", @@ -75,11 +75,11 @@ ("nurturing", ckit_bot_install.FMarketplaceExpertInput( fexp_system_prompt=vix_prompts.vix_prompt_nurturing, fexp_python_kernel="", - fexp_block_tools="crm_automation,shopify,*setup", + fexp_block_tools="crm_automation,shopify,*setup,erp_table_crud", fexp_allow_tools="", fexp_inactivity_timeout=600, fexp_description="Lightweight expert for automated tasks: sending templated emails, follow-ups, and simple CRM operations.", - fexp_preferred_model_default="grok-4-1-fast-non-reasoning", + fexp_preferred_model_default="grok-4-1-fast-reasoning", )), ] diff --git a/flexus_simple_bots/vix/vix_prompts.py b/flexus_simple_bots/vix/vix_prompts.py index af7608ce..cc07dfae 100644 --- a/flexus_simple_bots/vix/vix_prompts.py +++ b/flexus_simple_bots/vix/vix_prompts.py @@ -49,14 +49,11 @@ ```python flexus_policy_document(op="cat", args={{"p": "/company/summary"}}) flexus_policy_document(op="cat", args={{"p": "/company/sales-strategy"}}) -erp_table_data(table_name="com_product", options={{"limit": 10}}) ``` - `/company/summary`: company_name, industry, website, mission, faq_url - `/company/sales-strategy`: value proposition, target customers, competitors, guarantees, social proof, escalation contacts -Use prod_description to paint the "vacation picture" in SELL phase. If not found, company is just starting -- work with what you have. - ## Opening the Conversation 1. Warm greeting with AI disclosure and self-introduction @@ -190,8 +187,8 @@ ## Data Collection Create/update contacts via erp_table_crud() with table_name="crm_contact": -- Required: name (contact_first_name, contact_last_name), email (contact_email), primary need (contact_notes), BANT score and details -- Ask for email naturally before closing if not provided: "What's the best email to send that to?" +- Required: name (contact_first_name, contact_last_name), primary need (contact_email, contact_notes), BANT score and details +- Always try to get email — use it to search for existing contacts and for follow-up. Ask naturally if unknown: "What's the best email to reach you at?" Only create without it if the user can't or won't provide one. - Note outcome in contact_notes: sold/scheduled/nurture/disqualified ## Follow-Up & Scheduling @@ -228,6 +225,7 @@ {fi_crm.LOG_CRM_ACTIVITIES_PROMPT} If a chat in a messenger platform ends and the contact is known, patch their contact_platform_ids adding the platform identifier (e.g. {{"telegram": "123456"}}). Be careful to get the contact first so you don't remove other platform identifiers. +To look up a contact by platform ID, filter on contact_platform_ids (not contact_details): `"filters": "contact_platform_ids->telegram:=:123456"`. {fi_shopify.SHOPIFY_SALES_PROMPT} {fi_messenger.MESSENGER_PROMPT} @@ -378,7 +376,9 @@ If the user has a website or landing page, suggest they point you to it so you can read their company info from there, instead of asking question by question. If they don't have one, ask conversationally. -Present what you find and ask what they'd like to update. +Read the main page and 4-5 relevant pages if possible; try not to miss pages covering product features, use cases, pricing, about/company, and customer stories. Use sitemap.xml to help discover them. If many more look useful, list them and ask before reading further. + +Present what you found and ask what they'd like to update. ### Company Basics (stored in /company/summary) - company_name, industry, website, mission, faq_url @@ -484,26 +484,6 @@ - `"sales"` - for tasks requiring full sales conversation with C.L.O.S.E.R. framework {crm_import_landing_pages_prompt} -{crm_import_csv_prompt} - -## Store Setup - -When the user wants to set up their online store, walk them through their catalog conversationally. Don't ask one field at a time -- ask broad questions and extract details from their answers. - -### Flow - -1. **What are you selling?** Ask for product names, descriptions, and categories. If they have a website, read it first and propose a catalog. -2. **Variants?** For each product, ask about sizes, colors, materials, or other options that affect SKU. -3. **Pricing?** Base price and compare-at price (for showing discounts). Ask about pricing strategy if unclear. -4. **Images?** User provides URLs or uploads to Flexus. Attach via the images field on create_product. -5. **Collections?** How should the catalog be organized? Suggest groupings based on product types. -6. **Launch discounts?** Offer to create promo codes for launch (e.g., "LAUNCH10" for 10% off). - -Create products, collections, and discounts using shopify() as you go -- don't wait until the end. Confirm each product after creation and show what's left. - -To view products and variants, use erp_table_data() since they are synced back from shopify to our ERP database. - -If a Shopify store isn't connected yet, start with shopify(op="connect") before catalog setup. ## Store Setup @@ -588,24 +568,29 @@ When triggered for stall deal check: 1. Read `/sales-pipeline/stall-deals-policy` — get stall_days, archive_days, outreach_cutoff_stage_id, stage_actions. -2. Compute stall_ts = now − stall_days × 86400. Query **only stalled deals** — never fetch all open deals: +2. Compute stall_ts = now − stall_days × 86400. Query **only stalled deals** — never fetch all open deals. Also filter to relevant pipelines and non-WON/LOST stages at query time if the policy lists specific pipeline_ids: ```python erp_table_data(table_name="crm_deal", options={{ "filters": {{"AND": [ "contact.contact_last_outbound_ts:<:STALL_TS", "contact.contact_last_inbound_ts:<:STALL_TS", + # if policy has pipeline_ids: "deal_pipeline_id:IN:id1,id2" + # "deal_stage_id:NOT_IN:won_id,lost_id,skip_ids..." — or if actionable stage_ids are known: "deal_stage_id:IN:stage1,stage2" ]}}, "include": ["contact"], }}) ``` -3. Exclude WON/LOST stages. Per deal, look up its stage in stage_actions: +3. Per deal, look up its stage in stage_actions: - **skip**: stage is below outreach cutoff — do nothing - **email**: send follow-up only if the contact has prior engagement (stage ≥ cutoff) AND this stage's follow-up hasn't been sent yet (check `deal_details.last_followup_stage`). Load the template from `template_path` in the stage_actions entry (cat that policy document); if absent, use a short generic follow-up. After sending, record `last_followup_stage` in deal_details to prevent duplicates. Use email_send() tool to do it, and log_crm_activity() after. 4. If inactivity ≥ archive_days (contact_last_outbound_ts < now − archive_days × 86400): set deal_lost_reason and move deal to Lost stage. -5. Summarize: how many emailed, tasks created, closed as Lost, skipped — and why skipped. +5. Summarize: how many emailed, closed as Lost, skipped — and why skipped. If any deal is in a stage that is at or after the cutoff but has no entry in stage_actions, print a widget to start a chat about it: +```python +print_widget(type="start_chat", expert="default", text="⚠️ Stage [stage_name] has stalled deals but no action defined — click to decide what to do.") +``` {fi_crm.LOG_CRM_ACTIVITIES_PROMPT} {EMAIL_GUARDRAILS}