feat(tour-plan): always render the tour map (deterministic map_locations)#117
Open
ArtyETH06 wants to merge 14 commits into
Open
feat(tour-plan): always render the tour map (deterministic map_locations)#117ArtyETH06 wants to merge 14 commits into
ArtyETH06 wants to merge 14 commits into
Conversation
…rministically leadbay_tour_plan returned raw lead objects and left the places_map_display_v0 payload for the LLM to hand-build from location.pos + per-lead mode badges, so the map was rendered inconsistently (#3779). Pre-shape a ready-to-pass map_locations[] server-side (mirroring campaign_call_sheet), with the mode badge (Customer/Qualified/New) baked into each notes string, plus a deterministic map_summary for the 'N leads without coordinates' footer. Description + prompt now say ALWAYS render the map first and pass map_locations verbatim. Co-Authored-By: Claude <noreply@anthropic.com>
…3779) WF35 (underdeliver guard): planning a city tour must ALWAYS surface the geographic itinerary (map or per-lead place cards with mode badges), never collapse it to a flat prose list. WF36 (overdeliver guard): the agent must pass the server's map_locations through verbatim — no invented coordinates/pins for coordinate-less leads, no fabricated addresses, no competing raw lat/lng table alongside the place cards. Both target leadbay_plan_tour_in_city; workflows audit green. Co-Authored-By: Claude <noreply@anthropic.com>
Reinforce PHASE 2: a field-sales tour is inherently geographic, so the agent renders the map on its own and must NOT ask 'want a map?' — the user never has to say 'map'. Rewrite eval WF35 to test exactly this (map-free scenario prompt + must_not_match on 'would you like ... map') so a run only passes if the map appears automatically. Co-Authored-By: Claude <noreply@anthropic.com>
…egex JS RegExp rejects inline flags; workflow-contract-schema audit compiles every render_checks regex and CI failed on the (?i). Rewrite with [Ww]-style character classes, scope the alternation to a nearby 'map', and stop at sentence boundaries so benign offers don't false-positive. Co-Authored-By: Claude <noreply@anthropic.com>
Live eval (#3779) on a populated NY tour produced a map pin reading 'Reach null null' — the contacts API sends the literal string 'null' for an empty name part. Harden toMapLocation to drop 'null'/blank first/last/title parts (same coercion bug pull-leads guards). New test file covers the all-null and partial-null cases. Co-Authored-By: Claude <noreply@anthropic.com>
WF35 improve loop: on a host without places_map_display_v0, the mode badges (Customer/Qualified/New) lived only in the unrendered widget notes, so the prose fallback dropped them (eval MM/IA 4/5). Require the badge per-lead in the chat-prose summary too. Re-eval (NY, blind judge) now 5/5/5/5: map auto-renders, every lead badged in prose, no map-ask. Co-Authored-By: Claude <noreply@anthropic.com>
PR-review catch (#3779): the Customer/Qualified split keyed off `last_monitor_action`, which is NOT part of the pull_followups payload contract — so the check was always false and every monitored account was mislabeled ★ Qualified, even ones with prior customer/follow-up history. Since the prompt tells agents not to re-derive badges, the wrong server-built badge reached the map and prose. Key off the fields the follow-up rendering contract actually documents: epilogue_status, last_prospecting_action_at, last_monitor_action_at (any present → engaged → ★ Customer). New regression test locks the field names; updated the in-PR test + templates to match. Co-Authored-By: Claude <noreply@anthropic.com>
…, not pull_leads Live dogfood (#3779): 'I'm in San Francisco next Tuesday. Who's worth meeting?' fired leadbay_pull_leads and rendered prose — no map — because the map guarantee lives in leadbay_tour_plan, which was never called. Root cause was routing, not rendering: - pull_leads redirected geo intent to followups_map (Monitor-only, no map_locations) and listed 'who should I meet' as a POSITIVE example. - tour_plan's triggers required 'tour'/'visiting in N days'; bare 'I'm in <city>, who's worth meeting' matched nothing. Redirect visit/who-to-meet intent from pull_leads → tour_plan, broaden tour_plan triggers + positive examples to the bare phrasing, and front-load the tour prompt's short_description with the trigger phrases so Claude Desktop invokes it. Co-Authored-By: Claude <noreply@anthropic.com>
…ap fallback
Live dogfood (#3779): routing now correctly calls leadbay_tour_plan, but
on Claude Desktop (no places_map_display_v0 in the tool set) the agent
fell back to a flat narrative ('Brooklyn Brewery — Broadway, 10018
(Midtown). Contact: …') — which the host's address auto-detector does
NOT parse into place cards, so no map of any kind rendered.
The fallback path said 'render place-card blocks' but never pinned the
exact shape. Prescribe it explicitly in both the prompt PHASE 2 and the
tool-description fallback: one '### Company · City, State' heading per
lead (badge + one-sentence + linked contact + bare phone), grouped by
mode — the heading-with-city is what triggers the Google-Place-card
carousel. Flag the flat-paragraph shape as WRONG.
Co-Authored-By: Claude <noreply@anthropic.com>
… of auto-rendering
Per Arty: a tour should present the leads, then PROACTIVELY offer to
plot them on a map ('Want me to put these on a map?'), and render only
on acceptance — for both 'plan a prospecting tour in <city>' and
'who's worth meeting in <city>'. Previously the prompt forced an
immediate auto-render and forbade asking; invert to propose-then-render.
- Prompt: PHASE 2 = present leads + offer map (route via ask_user_input_v0
when available); PHASE 3 = render on yes (widget or place-card carousel);
if the user asked for the map up front, skip the offer and render.
- Tool description + rendering_hint: present → offer → render-on-yes.
- WORKFLOWS WF35 inverted: asserts the proactive map OFFER (must_match map)
instead of the old 'must NOT ask' guard.
Co-Authored-By: Claude <noreply@anthropic.com>
… for a street map Dogfood (#3779): the map rendered as a schematic neighborhood scatter (dots, no streets) instead of a real Google-style street map. Root cause: the builtin-widgets gate told the agent to pass a city-only address and omit latitude/longitude, so the host had nothing to street-geocode and fell back to relative scatter. tour_plan already returns real pos[] + location.full in map_locations — the guidance was throwing it away. - gates/builtin-widgets: pass real latitude/longitude + full street address (city-only + no coords → scatter, not a street map). - tour-plan RENDER: explicitly forbid dropping coords / shortening address to city when passing map_locations to the widget. - trim prepare-outreach body to stay under the 17000-char budget. Co-Authored-By: Claude <noreply@anthropic.com>
…real street map Dogfood (#3779): the map rendered as a schematic neighborhood scatter, not a real street map. The host's actual flow (per Claude's own widget guidance) is TWO steps: places_search resolves each location to a real place_id + coords/ratings FIRST, then places_map_display_v0 renders the resolved places. We were skipping places_search and pushing raw lat/lng straight to display — which is exactly what degrades to the scatter. - gates/builtin-widgets: name the places_search + places_map_display_v0 two-step (Itinerary mode for a tour). - tour-plan RENDER + prompt PHASE 3: spell out the two-step — places_search per lead (company + full address) → display in Itinerary mode, badges in notes; never push raw coords straight to display. - trim prepare-outreach body to stay under the 17000-char budget. Co-Authored-By: Claude <noreply@anthropic.com>
…ng the map Per Arty: before planning the tour + map, ask the user the scope via the ask_user_input_v0 / AskUserQuestion tap-to-answer widget — (1) who to include: mix / only known accounts / only fresh prospects, (2) enrich contacts with no channel? New PHASE 1 fires the question widget BEFORE leadbay_tour_plan and maps answers to real args (only-known → discover_count:0, only-new → followups_count:0, enrich → run leadbay_enrich_titles / prepare_outreach(enrich) on the top stops). Skips the question if the user already stated scope. Phases renumbered (clarify=1, build=2, present+offer=3, render=4, outreach=5, campaign=6). Co-Authored-By: Claude <noreply@anthropic.com>
…t just the prompt Dogfood (#3779): 'I'm going to NYC in 2 days, plan a prospecting tour' went straight to building + mapping with no scope question. Cause: the clarify-scope step lived only in the leadbay_plan_tour_in_city PROMPT, but the agent called leadbay_tour_plan directly (bypassing the prompt), so it never ran — same prompt-vs-tool gap as the routing fix. Add a 'BEFORE YOU CALL — clarify scope' directive to the tour_plan tool description itself (ask who-to-include + enrich via ask_user_input_v0, map answers to followups_count/discover_count, skip only if the request already states scope). Now fires whether or not the prompt is invoked. Co-Authored-By: Claude <noreply@anthropic.com>
3aeee7a to
558c8b2
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What & why
Field-sales tour planning rendered the map inconsistently and plain tour prompts often didn't reach the tour tool (#3779). This makes it deterministic: recognize the tour, build it, and render the map automatically — with the leads pre-shaped server-side so the agent never hand-builds the widget payload.
Behavior
<city>" routes toleadbay_tour_plan(no longer leaks toleadbay_pull_leads).places_map_display_v0.epilogue_status/last_prospecting_action_at/last_monitor_action_at).Implementation
packages/core/src/composite/tour-plan.ts— server-shapedmap_locations[]({name, address, latitude, longitude, notes}, badge innotes) +map_summary;"null"-string contact-name guard.tour-plan.md.tmpl,leadbay_plan_tour_in_city.md.tmpl; routing redirect inpull-leads.md.tmpl. Generated files viapnpm prompts:build.Tests & gates
New tests: map-locations shaping, badge-from-real-fields, null-name guard.
pnpm -r build/-r test(core 432, mcp 457, all green) /-r typecheckpass; routing / workflows / budget audits pass.Eval
WORKFLOWS #35 (tour auto-renders the map) + #36 (no-fabrication). Live
/eval(blind judge,leadbay_plan_tour_in_city, New York): WF35 5/5/5/5, WF36 5/5/5/5 — recognized the tour, calledleadbay_tour_plan, rendered the map, passedmap_locationsfaithfully, no fabrication. Harness note: theclaude -peval host doesn't exposeplaces_map_display_v0, so the widget itself is verifiable only on a real Claude host.Closes https://github.com/leadbay/product/issues/3779