Wiki · Bid Center Path 2 · R597

Path 2 — direct chat

Mike opens the admin chat (or a direct chat URL), types an intent like re-quote ACC Distributors SY2627, and optionally drag-drops a PDF. There is no email step and no separate proposed-action gate — Mike is the loop step. The Worker resolves the customer via name_synonyms, picks the SY-version, preloads context, and runs the standardized 11-skill review. Same outputs as Path 1; same trace thread (customer + SY-version); NS writes still deferred to Path 3.

Path 2 · chat-initiated Mike in-the-loop · no extra HITL gate
What this is

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.

When to use it

Trigger conditions

No NS write on this path either

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.

★ Same trace thread as Path 1

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.

Worked example

Mike re-quotes ACC in 4 minutes

Scenario

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.

Step-by-step what happens

Chat opens → intent typed → review runs

  1. 01

    Mike opens chat

    Admin dashboard chat OR direct chat URL. Session row inserted in chat_session with origin='direct_chat'.

  2. 02

    Mike types intent

    Free-form: "re-quote ACC Distributors", "compare ACC v2.4 vs v2.5", "log new external bid from buyer X", …

  3. 03

    Attach / paste bid content

    Drag-drop PDF OR paste bid text. PDF bytes flow through document_converter → Markdown.

  4. 04

    Worker resolves customer

    name_synonyms.json → canonical customer_id. Ambiguous? Worker asks in chat: "did you mean X or Y?"

  5. 05

    Pick SY-version

    Latest is_latest=1 from bid_quote_versions; else mint a new currentSchoolYear() + '-v0.1'.

  6. 06

    Preload context

    Bid Markdown + customer.json + latest bid_quote_versions row attached to the chat session.

  7. 07

    Decide skill

    Mike picks one of 11, or the LLM suggests and Mike confirms.

  8. 08

    Run 11-skill standardized review

    Same dispatcher used by Path 1 and Path 3. router.dispatch(skill, payload).

  9. 09

    UPSERT D1 bid_*

    Migration 136 (Agent M owns). Until landed, writes go to folder + STATUS.json only.

  10. 10

    Regen .docx

    Per-customer per-SY-version .docx.

  11. 11

    Refresh STATUS.json + dashboard

    Freshness metadata updated; gfs-pricing.pages.dev re-renders on next request.

  12. 12

    Events fired

    bid.chat_review_completed, bid.quote_versioned.

Outcomes

What's different after Path 2 runs

HITL gate
0
Mike IS the gate
D1 tables
8 bid_*
UPSERT inline
NS write
none
deferred to Path 3
Latency
~4 min
drag-drop to done
Failure modes

What can go wrong

Ambiguous customer match in chat

If name_synonyms is below 0.85, the Worker asks Mike inline: "did you mean X or Y?" Mike picks; flow continues.

PDF drag-drop too large

Hard cap at ~25MB for chat upload. Workaround: email the file to bids@ (Path 1) or split the PDF.

document_converter throws on the PDF

Chat surfaces the error; Mike can paste the bid text inline OR open the source in another tool and paste excerpts.

Skill picked wrong

Mike can abort the chat session and start fresh with the right skill named explicitly. chat_session row stays for audit.

Migration 136 not yet landed

D1 bid_* writes no-op gracefully; folder + STATUS.json remain authoritative.

Related

Adjacent flows + diagrams

For developers

Code paths + invariants

ConcernWhere
Chat entrypoint/api/chat (src/index.ts)
Document convertersrc/document_converter.ts
Name synonymsdata/name_synonyms.json
Chat session rowchat_session (origin='direct_chat')
HITL invariantMike-in-the-loop (no proposed_actions row)
D1 tablesbid_customers, bid_quote_versions, bid_price_snapshots, …
Dispatcherrouter.dispatch(suggested_skill, payload)
Events firedbid.chat_review_completed, bid.quote_versioned
// Path 2 - chat-initiated, Mike-in-the-loop async function runPath2(env: Env, session: ChatSession, intent: ChatIntent) { const { customer_id } = await resolveCustomer(env, intent.raw_text); const sy_version = await pickOrMintSyVersion(env, customer_id); // trace thread established: customer_id + sy_version await session.preload([intent.bid_markdown, await loadCustomerJson(customer_id), await loadLatestVersion(env, customer_id)]); const skill = await chooseSkill(session, intent); await dispatcher.run(skill, { session, customer_id, sy_version }); await emit(env, 'bid.chat_review_completed', { customer_id, sy_version }); }
Changelog

Dated trail

DateRoundChangeTouched by
2026-05-26R597Wiki 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)
Schema · data contract

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 / tableField carrying customer + SY-versionSample value
chat_sessioncontext.customer_id + context.sy_version"ACC Distributors / SY2627-v2.5"
chat_messagesystem message with preloaded contextreferences above pair
bid_customerscustomer_id"ACC Distributors"
bid_quote_versionscustomer_id + sy_version"ACC Distributors / SY2627-v2.5"
bid_price_snapshotscustomer_id + sy_version + sku"ACC / SY2627-v2.5 / SKU-419"
STATUS.jsoncustomers[customer_id].latest_sy_version"SY2627-v2.5"

D1 tables written (Path 2)

TableOperationPurpose
chat_sessionINSERTsession origin + context
chat_messageINSERT (per turn)conversation log
bid_customersUPSERTcustomer normalize
bid_quote_versionsINSERT + demote priornew SY-version
bid_price_snapshotsINSERT-manyline-level snapshot
bid_customer_pricesUPSERTworkshop layer
eventsINSERTappend-only audit

Endpoints

MethodPathPurpose
POST/api/chatmain chat entrypoint (carries intent + attachments)
POST/api/chat/uploadPDF / XLSX upload for chat
GET/quote/<slug>/<version>customer-facing rendered quote

Events fired

event_typeWhen
bid.chat_session_openedchat_session row inserted
bid.chat_review_completedskill dispatcher finishes
bid.quote_versionednew bid_quote_versions row written
Runbook · when it breaks

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.

  1. Check upload endpoint: network tab on /api/chat/upload — 200 OK?
  2. Check file size: > 25MB? Split or use Path 1 email instead.
  3. Check document_converter: npx wrangler tail filter on document_converter
  4. 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.

  1. Abort cleanly: chat command /abort-skill rolls back the in-flight UPSERTs
  2. Roll back manually: UPDATE bid_quote_versions SET is_latest=0 WHERE customer_id=? AND sy_version=? on the just-written row; restore prior is_latest=1
  3. 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.

  1. Identify the bad rows: SELECT * FROM bid_quote_versions WHERE customer_id='<wrong>' ORDER BY created_at DESC LIMIT 1
  2. Demote / delete: set is_latest=0 on the bad row; restore prior
  3. Add alias: update name_synonyms.json to prevent future mismatches
  4. Re-run: open fresh chat, explicit customer this time

Logs to check

Kill switches

Backlog · open questions

What's not done · what's uncertain