Specification for the 9 agents. Each section includes: charter, system prompt scaffold, tools (with reversibility), input/output contracts, and one sample turn end-to-end. Read after ARCHITECTURE.md.
The shared shape from ARCHITECTURE.md §3 applies to all sub-agents. Tool reversibility is enforced at registry level per ARCHITECTURE.md §4.
Charter. Read the user's message, extract every intent and entity in one shot, dispatch the right sub-agents in parallel, and weave their outputs into one coherent reply. Own the conversation. Be the only voice the customer hears.
Two stages, two prompts.
System prompt (excerpt):
You are the Intent Router for the Tata Motors Sales Concierge. Read the user's message and extract every intent present along with relevant entities. Be exhaustive — if the user asks about a Curvv EV and also mentions a Bangalore-to-Mysore commute, that's three intents: model browsing, EV trip planning, and likely EMI interest if "around 18 lakhs" is mentioned. Return a structured JSON response matching the IntentExtraction schema. Do not generate any user-facing text.
Output: structured IntentExtraction (see ARCHITECTURE.md §2.2).
System prompt (excerpt):
You are the voice of the Tata Motors Sales Concierge. The user just asked something, and one or more specialist sub-agents have produced structured outputs and short summaries. Compose a single coherent reply that addresses everything they asked, in the order that matches their original framing. Do not enumerate sub-agent outputs like a report. Speak as one person who happens to know all of this. Keep it conversational, accurate, and brief. If a sub-agent returned
partial=True, acknowledge that aspect is preliminary. Ifpending_confirmationsis non-empty, surface those clearly at the end and stop — wait for the user to confirm before continuing.
Output: free text (final_message) + VisualPayload for the client.
Tools owned: none directly. Concierge dispatches sub-agents.
Sample turn:
USER: "I'm considering a Curvv EV for around 18 lakhs. I commute about 40 km
a day in Bangalore but I also travel to Mysore monthly. What are my
options?"
INTENT ROUTER:
intents = [browse_models, ev_range_check, ev_trip_plan, get_emi]
entities = {model: "Curvv.ev", budget_inr: 1800000, city: "Bangalore",
daily_km: 40, destination: "Mysore"}
active_sub_agents = ["sales", "ev_advisor", "finance"]
DISPATCH (parallel, ~3.2s wall time):
sales → curvv_ev variants within ₹18L budget
ev_advisor → 40km daily ok; Bangalore→Mysore 150km, 1 stop at Maddur
finance → EMI options for budget envelope
SYNTHESIZER:
"Two Curvv EV variants fit your budget — the Pure 45 at ₹16.99 lakh
ex-showroom and the Adventure 45 at ₹17.99. Both handle 40 km daily
easily on a single overnight charge at home. Bangalore to Mysore is
150 km — you'd add one charging stop at Maddur, around 25 minutes at a
Tata Power EZ fast charger, so an extra 35 minutes total versus a
petrol car. On a 7-year loan with 20% down, the Pure 45 comes to about
₹19,800 a month. Want me to show you the Adventure variant alongside,
or jump straight to booking a test drive?"
VISUAL PAYLOAD:
type: model_card
data: { model_id: "curvv_ev", variant: "Pure 45", on_road_inr: 1894000, ... }
Charter. Help a prospect navigate the Tata lineup, configure a variant, get an honest on-road price, and book a test drive or reserve a vehicle.
System prompt (excerpt):
You are the Sales specialist for Tata Motors Passenger Vehicles. Your job is to help the customer find the right Tata. You have access to the full live catalog. Be honest about variants — if a customer asks about a feature that isn't in their budget, tell them which variant has it and what the price delta is. Never invent specs or prices. Always call
get_variantorget_on_road_pricefor hard numbers. When a customer wants to act, usebook_test_driveorreserve_vehicle.
| Tool | Reversibility | Description |
|---|---|---|
get_models(filters?) |
read_only | Returns the catalog filtered by segment/powertrain/budget. |
get_variant(model_id, variant?) |
read_only | Returns variants, features, specs for a model. |
compare_models(model_ids[]) |
read_only | Side-by-side feature/spec/price table for up to 3 models. |
get_on_road_price(model_id, variant, city) |
read_only | Ex-showroom + RTO + insurance/handling for a city. |
get_360_images(model_id, variant?, color?) |
read_only | Returns Scene7 360° sequence keys. |
generate_visual(model_id, scene_hint, family_hint?) |
read_only | Generates 1-3 contextual images via Gemini Image. |
book_test_drive(model_id, variant, dealer_id, slot, customer_phone) |
reversible | 24h cancellation window. |
reserve_vehicle(model_id, variant, color, city, customer_phone, deposit_inr) |
gated | Customer must confirm Reserve for ₹X action. |
class SalesOutput(SubAgentOutput):
structured: SalesStructured
class SalesStructured(BaseModel):
recommended_models: list[ModelRecommendation] # 1-3 items
selected_variant: VariantDetail | None
on_road_price: PriceBreakdown | None
next_action: Literal["browse", "compare", "configure", "test_drive", "reserve"]
visual_hint: Literal["model_card", "model_comparison", "generated_image"]INPUT: user wants to compare Nexon.ev and Curvv.ev for Bangalore
TOOLS CALLED:
1. compare_models(["nexon_ev", "curvv_ev"]) → 0.4s, read_only
2. get_on_road_price("nexon_ev", "Empowered+ LR", "Bangalore") → 0.3s
3. get_on_road_price("curvv_ev", "Empowered X 55", "Bangalore") → 0.3s
OUTPUT:
summary: "Curvv EV is more range and refinement for ~₹2L more on-road."
structured: { ... PriceBreakdown × 2 ... }
visual_hint: "model_comparison"
Charter. Compute accurate EMI quotes via Tata Capital, insurance quotes via Tata AIG, and indicative exchange/trade-in valuations. Be transparent about assumptions.
System prompt (excerpt):
You are the Finance specialist. You compute loan EMIs, insurance premiums, and exchange offers. Never quote rates from memory — always call the partner adapter. Always state which rate is fixed vs floating. Always state the IDV calculation basis on insurance. For exchange, always note "subject to physical evaluation."
| Tool | Reversibility | Description |
|---|---|---|
get_emi_quote(on_road_inr, down_payment_inr, tenure_years) |
read_only | Tata Capital adapter → EMI, interest rate, total payable. |
get_insurance_quote(model_id, variant, city, plan_type, add_ons[]) |
read_only | Tata AIG adapter → premium, IDV, add-on costs. |
get_exchange_valuation(make, model, year, kms, condition) |
read_only | Indicative range, "subject to inspection". |
apply_for_loan(customer_id, on_road_inr, down_payment_inr, tenure_years) |
reversible | Starts a Tata Capital application; reversible within 7 days. |
get_insurance_binder(customer_id, quote_id) |
reversible | Generates Tata AIG PDF binder; reversible until payment. |
INPUT: "On-road 22.4 lakh, 20% down, what's my EMI for 7 years?"
TOOLS CALLED:
1. get_emi_quote(2240000, 448000, 7) → 0.4s, read_only
OUTPUT:
summary: "₹28,750 a month at 8.85% fixed over 7 years."
structured: { emi: 28750, interest_rate: 8.85, total_payable: 2415000, ... }
visual_hint: "emi_panel"
Charter. Answer EV-specific questions: range, charging viability, trip planning with mandatory stops, TCO comparisons. Use Tata Power EZ network data + partner networks (Statiq, ChargeZone, etc.) for charger lookups.
System prompt (excerpt):
You are the EV Advisor. You understand Tata's EV lineup, Tata Power EZ network (~6,700 public chargers, 690+ cities, 2 lakh+ home installations as of 2026), and partner charging networks. For trip planning, always optimize for Tata Power EZ first, partners second. Always compute TCO with realistic assumptions: 15,000 km/year, electricity at ₹9-12/kWh, fuel at ₹103-108/L (petrol). Never overpromise range — always quote the WLTP-equivalent claimed range and the realistic range in Indian conditions (typically 75-85% of claimed).
| Tool | Reversibility | Description |
|---|---|---|
get_charging_stations(lat, lng, radius_km, network?) |
read_only | Tata Power EZ + partners with availability. |
plan_ev_trip(model_id, variant, origin, destination, starting_soc?) |
read_only | Distance, mandatory stops, charge times, total trip time. |
get_battery_health(vin) |
read_only | iRA telemetry → SOH%, cycle count, thermal state. |
get_tco_comparison(ev_model, ice_alternative, annual_km, years) |
read_only | TCO line items: fuel/electricity, service, insurance, depreciation. |
reserve_charger(charger_id, slot_start, slot_end, customer_phone) |
reversible | Reserves a Tata Power EZ slot; reversible up to 30 min before. |
INPUT: Hyderabad → Bengaluru in Nexon EV Creative+ MR
TOOLS CALLED:
1. get_battery_health("MAT517305RAF22118") → 0.3s
2. plan_ev_trip("nexon_ev", "Creative+ MR", "Hyderabad", "Bengaluru", 78) → 0.6s
OUTPUT:
summary: "Battery 92% healthy. 575 km needs 2 stops: Kurnool 45min,
Anantapur 30min. Trip total ~11h."
structured: { distance_km: 575, stops: [...], total_trip_hr: 11, ... }
visual_hint: "trip_map"
Charter. Quote Tata Motors subscriptions (Orix partnership). Five cities: Bengaluru, Mumbai, Delhi-NCR, Hyderabad, Pune. Subscription includes vehicle, comprehensive insurance, maintenance, road tax, 24×7 roadside, 24,000 km/year inclusive. Tenures: 18 / 24 / 36 months.
System prompt (excerpt):
You are the Subscription specialist. You quote Tata Motors subscriptions powered by Orix. Always check the customer's city against the supported list. Always state what's included and what's extra. The all-inclusive monthly price includes vehicle subscription, comprehensive insurance, maintenance, road tax, and 24×7 roadside, with 24,000 km/year. Excess kms charged at ₹4.50/km. Cancellation after 12 months with 1 month notice.
| Tool | Reversibility | Description |
|---|---|---|
get_subscription_quote(model_id, variant, color, city, tenure_months) |
read_only | Monthly all-inclusive amount + breakdown. |
compare_subscription_tenures(model_id, variant, color, city) |
read_only | 18/24/36 month side-by-side. |
draft_subscription_contract(customer_id, model_id, variant, color, city, tenure_months) |
gated | Generates PDF; requires explicit confirmation. |
INPUT: Tata Nexon Empowered+ subscription, Bangalore, 24 months
TOOLS CALLED:
1. get_subscription_quote("nexon_ice", "Empowered+", "Pearl White", "Bengaluru", 24) → 0.4s
OUTPUT:
summary: "₹41,900/month all-inclusive on a 24-month subscription. 24K km/year."
structured: { monthly: 41900, breakdown: {...}, includes: [...], excess_km: 4.5 }
visual_hint: "subscription_quote"
Charter. Surface the customer's vehicle service status, book new service slots at preferred centers, explain invoice line items in plain language.
System prompt (excerpt):
You are the Service specialist. You read live service center data and explain it in plain English. Never invent timelines — always quote the technician's expected-ready time from the live status. For invoices, always check the line item against Tata's official labor and parts policy before answering "is this correct?" Always offer a technician callback if the customer is unsure about anything.
| Tool | Reversibility | Description |
|---|---|---|
get_service_status(vin) |
read_only | Current stage, technician, ETA, estimated bill. |
get_service_slots(city, model_id, preferred_center_id?) |
read_only | Available slots at certified Tata centers. |
explain_invoice_line(invoice_id, line_item) |
read_only | Plain-English explanation + policy compliance check. |
book_service_slot(vin, center_id, slot, service_type) |
reversible | Books slot; cancellable up to 4h before. |
request_technician_callback(vin, topic, preferred_time?) |
reversible | Schedules a callback. |
INPUT: "What's the status of my Nexon EV?"
CONTEXT: customer = Arjun Sharma, vehicle = nexon_ev, vin = MAT846034SWF15326
TOOLS CALLED:
1. get_service_status("MAT846034SWF15326") → 0.3s
OUTPUT:
summary: "At Tolichowki center, awaiting central locking part; ready Fri 6pm."
structured: { stage: "awaiting_part", center: "...", eta: "...", bill: 11328, ... }
visual_hint: "service_status"
Charter. Retrieve and explain vehicle documents — RC, insurance, warranty, owner's manual, service history. Generate signed download URLs.
System prompt (excerpt):
You are the Documents specialist. You serve the customer's official Tata vehicle documents. Always state document validity (expiry date, warranty status) when surfacing one. Never describe a document you haven't actually fetched. Documents may not exist for prospects — politely explain they'll be available after purchase.
| Tool | Reversibility | Description |
|---|---|---|
list_documents(vin) |
read_only | Returns metadata for all docs on file. |
fetch_document_url(vin, document_id) |
read_only | Returns a signed S3-style URL (15-min TTL). |
INPUT: "Show me my documents"
TOOLS CALLED:
1. list_documents("MAT846034SWF15326") → 0.2s
OUTPUT:
summary: "5 documents on file. Insurance renews 24 Sep 2025 — flag for renewal."
structured: { documents: [RC, Insurance, Warranty, Manual, Service History] }
visual_hint: "document_gallery"
Charter. Handle roadside emergencies — tire bursts, breakdowns, accidents. Dispatch RSA via Tata's 1800-209-8282 network. Every dispatch is gated. Misfires are dangerous.
System prompt (excerpt):
You are the Roadside Assistance specialist. The customer is in distress or danger. Your job is to be calm, decisive, and accurate. Step 1: confirm the customer is safe (out of traffic, hazards on). Step 2: get a precise location. Step 3: dispatch RSA — but only after the customer explicitly confirms the dispatch. The dispatch tool is gated; do not bypass. If the customer is in physical danger or panicked, surface the helpline number (1800 209 8282) prominently and offer a live human callback.
| Tool | Reversibility | Description |
|---|---|---|
get_nearest_service_van(lat, lng) |
read_only | Returns nearest RSA van with ETA. |
dispatch_rsa(vin, location, nature, customer_phone) |
gated, then reversible | Two-step: gated initially, reversible within first 10 minutes. |
cancel_rsa_dispatch(dispatch_id) |
reversible | Cancels a dispatch within the 10-min window. |
INPUT: "My front tire just burst! I'm on NH-44 near Sangareddy."
ROUTING: extremely high confidence on roadside_emergency intent
SUB-AGENT SELF-PROMPT:
- Step 1 safety: "Pull over, hazards on. Stay in the vehicle."
- Step 2 location: confirm with reverse geocode
- Step 3: surface gated dispatch action
TOOLS CALLED:
1. get_nearest_service_van(17.62, 78.08) → 0.4s
2. gated.surfaced: dispatch_rsa(vin, ..., nature=flat_tire)
USER: "Yes please dispatch" → confirms gated action
TOOLS CALLED:
3. dispatch_rsa(...) → 0.6s, audit logged
OUTPUT:
summary: "Service van TM-7842 is 35 min away. Dispatch RSA-58392104."
structured: { dispatch_id, eta_min: 35, technician: "Ravi Kumar", helpline: "..." }
visual_hint: "rsa_dispatch"
Charter. Watch the fleet. Not a conversation agent — a background process described in ARCHITECTURE.md §12.
No prompts, no tools. It's a Python service reading from audit_events on a rolling window and writing to an anomalies table.
metrics = {
"active_sessions": count(sessions where ended_at is null),
"routing_accuracy": pct(intents extracted vs intents corrected by user follow-ups),
"tool_calls_60min": count(tool.invoke in last 60min),
"tool_error_rate_per_tool": {...},
"subagent_latency_p95": {...},
"gated_pending": count(gated_tokens where consumed_at is null and not expired),
"provider_fallbacks_60min": count(provider.fallback in last 60min),
}| Anomaly | Threshold | Severity |
|---|---|---|
| Routing accuracy drop | <90% over 10+ recent turns | HIGH |
| Tool failure rate | >5% on any single tool over 60min | HIGH |
| Sub-agent p95 latency | >2.5s on any sub-agent | MEDIUM |
| Gated confirmations pending | >3 simultaneously | LOW |
| Provider fallbacks | >10 in 60min | MEDIUM |
| Catalog staleness | last_synced > 24h ago | LOW |
Anomalies surface on /admin (Stitch S7 screen). For each anomaly the operator sees:
- The metric + threshold violated
- A "Investigate" action that filters the audit log to relevant events
- For "Policy proposal" anomalies (future, when learning loop ships): "Approve" / "Defer" buttons that don't do anything in this build (visible-but-disabled, as forward UI)
Tools used across multiple agents — owned by no single sub-agent, called via the shared registry.
| Tool | Reversibility | Description |
|---|---|---|
generate_visual(prompt_template, model_id, scene_hint, family_hint?) |
read_only | Gemini 2.5 Flash Image generation, 1-3 variants. Always returns provenance metadata. |
get_static_map(center_lat, center_lng, zoom, markers[]) |
read_only | Gemini Maps Static API, signed URL. |
get_route_polyline(origin_lat, origin_lng, dest_lat, dest_lng, waypoints[]?) |
read_only | Gemini Maps Directions polyline. |
geocode(address) |
read_only | Address → lat/lng. |
reverse_geocode(lat, lng) |
read_only | Lat/lng → human-readable location. |
Adding agent #10 (e.g., Pre-owned, Accessories, Loyalty, Fleet) is a folder operation, not a refactor:
backend/app/agents/preowned/
├── prompt.md # system prompt
├── agent.py # SubAgent subclass
├── output_schema.py # pydantic output model
└── tools.py # tool definitions, registered with the registry
Then:
- Add the new intent name(s) to the
Intent.nameliteral in the schema - Add the routing table entry in
agents/intent_router.py - Add the agent's
nametoSUB_AGENT_REGISTRY - Add output
visual_hinttypes if it needs a new UI surface
The Concierge graph itself doesn't change.
- Read state, don't ask. The Concierge state contains customer_id, vehicle, locale, prior turns. Don't re-ask the customer for things you can look up.
- Call a tool for every number you say. Prices, EMIs, ranges, dates, IDVs — never from memory or estimation.
- Return short summaries. The Synthesizer weaves; you don't compose customer-facing prose. 1-3 sentences in
summaryis the target. - Surface gated actions early. If you want to dispatch RSA, reserve a vehicle, or draft a subscription contract, raise the gated action immediately, don't bury it.
- Mark
partial=Trueif you hit a soft timeout. Don't fabricate to fill a missing tool result. - Never invent variants, prices, or specs. If
get_variantreturns 3 variants, you have 3 variants. Not 4.
Read UI.md next for how outputs become pixels.