🎸 TM GO. architecture
● real compiled graphs
the tour-manager agent

How TM GO works today β€” and the refactor.

Today the bot is a ReAct agent: one big loop where the model decides each step. The refactor makes it an explicit StateGraph β€” a router that sends each message down a fixed pipeline. Both diagrams below are drawn from really-compiled graphs. Click any node to see what it does.

Now Β· ReAct agent

One loop, the LLM drives

The live bot (create_react_agent). Every turn the model reads the whole chat, decides whether to call a tool or reply, and loops β€” capped at 3 tool calls. There's no fixed path; the behavior lives in a ~300-line system prompt.

wants a toolobserve Β· loop ≀3Γ—no tool β†’ reply πŸ“¨ message1🧠 agent (LLM)2πŸ› οΈ tools3πŸ’¬ reply5
πŸ‘† Tap any step to see what it does β€” and what it did on this run.
⚠ send_last_draft is just another tool in the loop β€” the model can fire a send on any turn; only a prompt rule says "ask first." That's the safety gap the refactor closes structurally.
The tool belt β€” ~25 tools the model can pick from
read_google_sheetwrite_google_sheet update_google_sheetcreate_tour_sheet store_venue_noteget_venue_notes check_tour_emailgenerate_advancing_email send_last_draftgenerate_follow_up_email generate_thank_you_emailparse_contract parse_venue_responsevalidate_response_completeness send_sms_updategenerate_day_of_reminder_sms search_flightssearch_hotels update_hotelupdate_transport track_travel_expense
The guardrails that keep the loop honest β€” all prompt rules
Never claim an untaken action
It can't say "sent" unless it actually called send_last_draft this turn.
Follow through on "yes / send it"
A confirmation triggers the send immediately β€” no "which email?".
Resolve the active venue
Pronouns ("the wifi", "send it") resolve to the venue from recent turns.
Never fabricate data
Every fact must come from a tool result β€” no invented contacts or times.
Don't loop
≀3 tool calls per turn; never the same tool twice with the same args.
Refactor Β· one graph, three business functions

Docs in Β· Email Β· Questions β€” around the sheet

Everything TM GO does is three jobs, all revolving around the Google DB (system of record): πŸ“„ Docs in (upload β†’ parse β†’ ⏸review β†’ write), βœ‰ Email (assess what's missing β†’ draft β†’ ⏸approve β†’ send; replies come back in and update the DB), ❓ Questions (read β†’ answer) β€” plus a tool loop for everything else. Two human gates: review-before-write and approve-before-send. The amber trail below is a real "advance a show" run.

πŸ”˜ Buttons = deterministic β€” a button knows its intent and enters at its node, skipping the router. πŸ’¬ Chat = free-form β€” the router classifies it. Same graph either way.
uploadcheck emailadvancequestiontoolschatreplyPDFinfoquestionproblem Β· unclearcommit βœ“add to DBreplyreadsapprove βœ“logruns tools β–Ά start🧭 routerπŸ“‹ parse fileπŸ™‹ reviewπŸ—‚οΈ writeπŸ—„οΈ Google DBπŸ“§ check inboxπŸ“₯ parse replyπŸ”€ triageπŸ™‹ escalateβœ… assess✍️ draftπŸ™‹ approveβœ‰ sendβ–  endπŸ”Ž answerπŸ› οΈ toolsπŸ’¬ chat
read Β· answers now write Β· stage β†’ approve β†’ commit human gate ⏸ Google DB β–¬ amber trail = the path this run took
πŸ‘† Click any step to see what it does.
Why this shape?
One shared core, two doors: chat runs the graph, the app's buttons call the same nodes. The LLM only drafts & parses; the approval gate is a real interrupt(), not a prompt rule; and send is pulled out of the tool loop so the cycle can read / draft / log but never send.
The safety win

The approval gate is structural, not a prompt rule

Advancing spans draft β†’ send. Today the ReAct bot can fire the send on an LLM "send" classification. In the StateGraph, send has one inbound edge β€” from an interrupt() a human must resume. Draft = LLM Β· Approve = human Β· Send = code. No router or LLM path can reach send.