The synchronous, in-the-loop intake path
Path 2 is the fast path. When Mike is already at the desk and the question is "let's re-quote X" or "log this new RFP", going through the email mailbox + HITL gate cycle is overhead. Path 2 skips it. Mike opens the admin chat at chat.ai-globalfoodsolutions.co (or any chat surface) and either types the customer + intent or drag-drops a bid PDF.
Because Mike is the operator, there is no separate proposed_actions approval gate — every action Mike takes IS the approval. ADR-031's HITL invariant is satisfied by Mike's presence; the gate moves from before the review runs to any NS-bound write inside the skill (which today is none on this path — NS writes are deferred to Path 3).
The dispatch is identical to Path 1 from step 4 onward: customer resolved via name_synonyms.json, SY-version picked from bid_quote_versions, customer + SY-version becomes the trace thread, one of 11 skills runs, 8 D1 bid_* tables UPSERT, .docx regenerates, STATUS.json refreshes, the dashboard at gfs-pricing.pages.dev re-renders. Events fire (bid.chat_review_completed, bid.quote_versioned) and the new SY-version sits in the Bid Center until July 1.
Diagram: ns-bid-center-path-2-direct-chat.html. Sibling paths: Path 1 email · Path 3 Jul 1 rollover. Master: master wiki.
Trigger conditions
- Mike wants to re-quote a known customer right now (no email round trip needed).
- A PDF or pricing sheet is sitting on the desk — drag-drop into chat is faster than emailing it to
bids@. - Mike wants to run
COMPARE_CUSTOMER_QUOTESon two versions for an existing customer. - Onboarding a brand-new customer via
CREATE_NEW_CUSTOMER. - Logging an external bid that arrived via a non-email channel (portal, phone, conversation notes).
- Quick admin tasks:
SYSTEM_HEALTH_CHECK,BID_PIPELINEreview,ARCHIVE_CUSTOMER.
Like Path 1, Path 2 only writes to D1 + folder + dashboard. NetSuite writes are deferred to Path 3 (Jul 1 rollover). The Bid Center is the rolling-state pillar; NS named price lists are the annual snapshot.
customer_id + sy_version threads through chat_session, bid_quote_versions, bid_price_snapshots, and STATUS.json. Sample: ACC Distributors / SY2627-v2.5. From the audit trail, a Path 1 row and a Path 2 row are indistinguishable except for the origin field on the chat_session.
Mike re-quotes ACC in 4 minutes
April 14, 2026, 11:02 AM. Mike just got off a call with ACC Distributors' buyer. They want the cheddar items re-quoted today. Mike has the new pricing PDF from the call notes.
Mike opens chat at chat.ai-globalfoodsolutions.co and drag-drops the 3-page PDF. Types: "re-quote ACC Distributors — cheddar refresh, SY2627."
The Worker calls document_converter on the PDF bytes; Markdown is in context within ~3 seconds. name_synonyms.json resolves "ACC Distributors" to the canonical customer at confidence 1.0 (exact match). bid_quote_versions picks SY2627-v2.4 as the latest. The trace thread ACC Distributors / SY2627-v2.5 is established (auto-incremented).
Chat preloads: bid Markdown, customer.json, bid_quote_versions latest row. The LLM suggests ADD_UPDATE_CUSTOMER_PRICING; Mike confirms with a single "go." The skill runs, snapshots 47 line items, applies the HPS MAP GPO allowance, UPSERTs the 8 bid_* tables, regenerates quotes/SY2627-v2.5.docx, refreshes STATUS.json, and re-renders the dashboard.
Total elapsed: ~4 minutes from drag-drop to "done, v2.5 live on dashboard." No proposed_actions row was created on this path — the chat session row itself is the audit record. Events fire: bid.chat_review_completed, bid.quote_versioned. The new pricing sits in the Bid Center until Path 3 picks it up on July 1.
Chat opens → intent typed → review runs
- 01
Mike opens chat
Admin dashboard chat OR direct chat URL. Session row inserted in
chat_sessionwithorigin='direct_chat'. - 02
Mike types intent
Free-form: "re-quote ACC Distributors", "compare ACC v2.4 vs v2.5", "log new external bid from buyer X", …
- 03
Attach / paste bid content
Drag-drop PDF OR paste bid text. PDF bytes flow through
document_converter→ Markdown. - 04
Worker resolves customer
name_synonyms.json→ canonicalcustomer_id. Ambiguous? Worker asks in chat: "did you mean X or Y?" - 05
Pick SY-version
Latest
is_latest=1frombid_quote_versions; else mint a newcurrentSchoolYear() + '-v0.1'. - 06
Preload context
Bid Markdown +
customer.json+ latestbid_quote_versionsrow attached to the chat session. - 07
Decide skill
Mike picks one of 11, or the LLM suggests and Mike confirms.
- 08
Run 11-skill standardized review
Same dispatcher used by Path 1 and Path 3.
router.dispatch(skill, payload). - 09
UPSERT D1
bid_*Migration 136 (Agent M owns). Until landed, writes go to folder + STATUS.json only.
- 10
Regen
.docxPer-customer per-SY-version
.docx. - 11
Refresh STATUS.json + dashboard
Freshness metadata updated; gfs-pricing.pages.dev re-renders on next request.
- 12
Events fired
bid.chat_review_completed,bid.quote_versioned.
What's different after Path 2 runs
- One new
bid_quote_versionsrow (is_latest=1) per approved chat run; prior version demoted. chat_sessionrow is the audit record (no separateproposed_actionsinsert).- Per-customer
.docxquote regenerated; STATUS.json freshness updated. - Dashboard at
gfs-pricing.pages.devreflects the new SY-version. - Same downstream outputs as Path 1 — the artifacts are indistinguishable except for the
originfield.
What can go wrong
If name_synonyms is below 0.85, the Worker asks Mike inline: "did you mean X or Y?" Mike picks; flow continues.
Hard cap at ~25MB for chat upload. Workaround: email the file to bids@ (Path 1) or split the PDF.
Chat surfaces the error; Mike can paste the bid text inline OR open the source in another tool and paste excerpts.
Mike can abort the chat session and start fresh with the right skill named explicitly. chat_session row stays for audit.
D1 bid_* writes no-op gracefully; folder + STATUS.json remain authoritative.
Adjacent flows + diagrams
Code paths + invariants
| Concern | Where |
|---|---|
| Chat entrypoint | /api/chat (src/index.ts) |
| Document converter | src/document_converter.ts |
| Name synonyms | data/name_synonyms.json |
| Chat session row | chat_session (origin='direct_chat') |
| HITL invariant | Mike-in-the-loop (no proposed_actions row) |
| D1 tables | bid_customers, bid_quote_versions, bid_price_snapshots, … |
| Dispatcher | router.dispatch(suggested_skill, payload) |
| Events fired | bid.chat_review_completed, bid.quote_versioned |
Dated trail
| Date | Round | Change | Touched by |
|---|---|---|---|
2026-05-26 | R597 | Wiki ships alongside Path 2 diagram. Documents the Mike-in-the-loop invariant (no separate HITL gate on this path). Same customer + SY-version trace thread as Path 1. | Mike + Claude (Agent L) |
The machine-readable spec
workflow_type · bid_center_direct_chat_path · risk_level 2. Parent: bid_center_lifecycle (master).
Customer + SY-version threading (the trace thread)
Identical to Path 1 except the session row replaces the proposed_actions row at the top of the trail.
| Record / table | Field carrying customer + SY-version | Sample value |
|---|---|---|
chat_session | context.customer_id + context.sy_version | "ACC Distributors / SY2627-v2.5" |
chat_message | system message with preloaded context | references above pair |
bid_customers | customer_id | "ACC Distributors" |
bid_quote_versions | customer_id + sy_version | "ACC Distributors / SY2627-v2.5" |
bid_price_snapshots | customer_id + sy_version + sku | "ACC / SY2627-v2.5 / SKU-419" |
STATUS.json | customers[customer_id].latest_sy_version | "SY2627-v2.5" |
D1 tables written (Path 2)
| Table | Operation | Purpose |
|---|---|---|
chat_session | INSERT | session origin + context |
chat_message | INSERT (per turn) | conversation log |
bid_customers | UPSERT | customer normalize |
bid_quote_versions | INSERT + demote prior | new SY-version |
bid_price_snapshots | INSERT-many | line-level snapshot |
bid_customer_prices | UPSERT | workshop layer |
events | INSERT | append-only audit |
Endpoints
| Method | Path | Purpose |
|---|---|---|
POST | /api/chat | main chat entrypoint (carries intent + attachments) |
POST | /api/chat/upload | PDF / XLSX upload for chat |
GET | /quote/<slug>/<version> | customer-facing rendered quote |
Events fired
| event_type | When |
|---|---|
bid.chat_session_opened | chat_session row inserted |
bid.chat_review_completed | skill dispatcher finishes |
bid.quote_versioned | new bid_quote_versions row written |
It broke — what now
Scenario · Chat upload of PDF fails silently
Drag-drop seems to work but the bid Markdown never lands in the session.
- Check upload endpoint: network tab on
/api/chat/upload— 200 OK? - Check file size: > 25MB? Split or use Path 1 email instead.
- Check document_converter:
npx wrangler tailfilter ondocument_converter - Workaround: paste the bid text inline in chat
Scenario · Wrong skill ran
Dispatcher picked LOG_EXTERNAL_BID but it should have been ADD_UPDATE_CUSTOMER_PRICING.
- Abort cleanly: chat command
/abort-skillrolls back the in-flight UPSERTs - Roll back manually:
UPDATE bid_quote_versions SET is_latest=0 WHERE customer_id=? AND sy_version=?on the just-written row; restore prioris_latest=1 - Restart: open fresh chat, name the skill explicitly, re-run
Scenario · Customer match was wrong (typo, alias)
Skill ran against the wrong customer; need to undo + redo.
- Identify the bad rows:
SELECT * FROM bid_quote_versions WHERE customer_id='<wrong>' ORDER BY created_at DESC LIMIT 1 - Demote / delete: set
is_latest=0on the bad row; restore prior - Add alias: update
name_synonyms.jsonto prevent future mismatches - Re-run: open fresh chat, explicit customer this time
Logs to check
chat_session· origin='direct_chat'chat_message· turn-by-turn logevents·bid.chat_*trailworkflow_run_log· per-skill dispatcher auditnpx wrangler tail· live Worker logs
Kill switches
kill:bid_chat_dispatch· pauses Path 2 skill dispatch (chat sessions still open)kill:bid_writes· stops allbid_*UPSERTs
What's not done · what's uncertain
- STUBMigration 136 (8 bid_* tables)
Agent M owns. Until landed, Path 2 writes go to folder + STATUS.json only.
- STUBSkill abort + rollback semantics
Today
/abort-skillis best-effort. Need clean transactional rollback for any partial UPSERT batch. - OPENVoice intake
Chat could accept dictation.
POST /api/chat/voice→ Whisper → intent. Useful when Mike is on the phone with the customer. - OPENMulti-customer batch in one session
"Re-quote ACC + BCD + CDE all on SY2627 with new cheddar pricing" — today three separate sessions. Could batch in one.
- DECISIONAuto-skill confidence threshold
Today the LLM suggests + Mike confirms. With baked confidence (> 0.97), should we auto-run for trivial intents like "show ACC's current quote"?