The commercial chat cutover
What/w/:slug/chat became, why each piece moved, and what is still owed.
The routing half of this document is superseded by
the chat manager. classifyChatRoute
is deleted: a regex ladder held the authority over meaning and failed by a
conjugation, so one typed manager decision replaced both it and the research
layer’s second semantic router on the Chat path. Everything below about
EXECUTION — the Work Session, the activity trail, the report artifact, the
customer-safe projection — is unchanged and still current.
The request path, end to end
Root cause of each observed incident
Incidents 6 and 7 are one cause. The router existed and was already
deterministic; it was missing the shapes people use — an offer framing
(“vendo”, “fabricamos”), the demand side (“quién compra”, “quiénes han
ganado”), and a supply-side noun list that omitted
fabricantes and
proveedores. contact_request also sat above the market ladder, so the bare
word contacto in a supplier search sent the errand to the CRM.
Routing, before and after
Deletion ledger
Every deletion below was proven unreachable by reference before it was made.Deleted
Total: 7,864 lines removed, 30 added, in the consolidation commit alone.
Kept, because the experience rests on them
redesign/chatThreads.ts (thread creation, message POST, regenerate, checkpoint
resolution), redesign/agentic-events.ts (A1 parser and reducer), the SSE
subscription in api.ts, the Work Session snapshot route, ResearchReport and
its artifacts, stop and retry.
Kept, with the deletion condition named
The second row is the honest reading of “quiero que todo el UI se utilice”:
the nine-route client and its three domain hooks are built and correct, and
exactly one of the three domains — opportunities — currently has rows in the
report to render. The other two are one backend field away, and are named here
rather than deleted quietly or left to look used.
Capability isolation
The classifier can be wrong. Safety does not rest on it being right. A commercial capability used to be reachable from the general runtime through two doors, both open by default:- the belt.
chat-tools.tsregistereddiscover_market_capabilities,query_market_signalsandget_signal_evidencebehindincludeMarketTools, computed as “not a market route” — so the tools built for commercial work were withheld from commercial turns and handed to every other one. - the manual.
chat.skill.tsopened its market section by instructing the model to call the first two in sequence.
marketIntelligence, runMarketIntelligence and
includeMarketTools are gone from ChatToolDeps; there is no dependency to
inject and no flag to get wrong. The manual carries a boundary instead, naming
Collections, Topics and Projects explicitly as things that must not be
substituted for market data.
apps/api/src/chat/commercial-isolation.spec.ts pins all eight invariants:
Plus two the eight imply: the SEMANTIC operations are not on the general belt
either (replacing one leak with a newer one is the same defect), and a
market-research turn builds no chat tool belt at all.
The activity trail
The runner has always reported every stage throughstep_started /
step_finished. The turn pipeline dropped all of them and forwarded only tool
calls — which is why the wait began after the search was already prepared.
The channel is the A1 contract’s own ACTIVITY_DELTA / ActivityEntry, already
frozen, already reduced into snapshot.activity, already served by the Work
Session endpoint. No second stream was built, and the trail survives a reload
because it is the same durable projection everything else here reads.
investigate is deliberately not narrated: it is the tool loop and its children
already name themselves.
Allowed in an entry: a human sentence (summary), a domain (name), a
status, ok, and a safe count or sanitized reason (preview). Never: an
operation name, a semantic code, a UUID as a label, SQL, a prompt, reasoning, a
capability catalogue, warehouse rows, or a provider — providerId is always
null.
Ids are stable KEYS (phase:route_research, tool:q_0), never labels. The
prefix also keeps them non-numeric, so object key order stays insertion order
and the trail rehydrates in the order it happened.
Known residual exposure
A commercial question that slips the ladder still classifies asgeneral. That
degradation is now bounded rather than dangerous: the general belt holds no
market capability, so the worst case is an honest “I can’t answer that from
here” instead of a tour of Topics and Collections.
The legacy Opportunity Flow still runs where the market-research dependencies
are absent. Its deletion condition is unchanged: those dependencies ceasing to
be @Optional().
What is NOT proven
No live end-to-end run against staging, and no screenshots. The container has no API onlocalhost:3000, no Docker, no database URL, no model credential and no
Clerk session; Playwright is not resolvable from apps/dashboard, and /chat
sits behind an authenticated workspace route, so a browser would reach a sign-in
screen rather than any of the states above. Every claim here rests on hermetic
tests and on reading the code.
The live E2E checklist, for whoever has staging
/w/<slug>/chat, with the network tab open on the SSE stream:
Repeat 1 for each domain:
Busca fabricantes de bombas en Jalisco con contacto publicado. · ¿Qué licitaciones accionables hay para tratamiento de agua? ·
¿Quiénes han ganado contratos de alumbrado público y por cuánto, separados por moneda y ámbito?