Skip to content

e2e-evolve 2026-07-04: drain the two lane-B blockers (extract $/mo ZodError + followup error_max_turns)#43

Merged
ryanxwy merged 2 commits into
mainfrom
evolve-20260704
Jul 4, 2026
Merged

e2e-evolve 2026-07-04: drain the two lane-B blockers (extract $/mo ZodError + followup error_max_turns)#43
ryanxwy merged 2 commits into
mainfrom
evolve-20260704

Conversation

@ryanxwy

@ryanxwy ryanxwy commented Jul 4, 2026

Copy link
Copy Markdown
Owner

Drains the two open blockers the 2026-07-04 /e2e-loop --provider claude run handed off.

B1 — dealer_reply_extract lane-B ZodError on monthly-payment replies

The mode-contract superRefine was enforced at the EMIT boundary, but z.toJSONSchema drops refinements, so a real lease + $389/mo emit deterministically failed the emit parse before the designed reclassifyRule2Failures demote hatch could run (lane A had the same defect = the deepseek payment-only flakiness). Fix: validate the emit boundary with a structural (unrefined) schema so the hatch runs; the persist floor still re-validates the full refined schema (Rule-1 bleed still fails closed). Plus a bounded extract retry cap (quote_extraction_attempts, migration 0009 — the armed scheduler had retried one poisoned message 46×) and lane-B fail_reason label unified to zod_validation.

B2 — negotiation_followup lane-B error_max_turns whole-batch death

allowedTools:[] is a no-op in Agent SDK 0.3.195, so the CLI still advertised its default toolset; a long/noisy thread made the model emit a tool_use, the deny-all canUseTool aborted it, and under maxTurns:1 the abort = error_max_turns → the throw killed the whole batch. Fix: advertise zero tools on the prose path (tools:[]) so the model can't emit a tool_use; add an extract-style per-thread draft catch (documented inv #4 degradation — the thread keeps draft_body null, is voiced + ledgered, stays queued; a draft is never fabricated).

Verification

  • Fresh-context code-review APPROVE + safety-audit SAFE for both fixes; drizzle-migration PASS for 0009.
  • RUN_UI_FUNCTIONAL=1 green.sh GREEN (4054 tests, ui:functional lane included).
  • Live re-verified on the claude lane: B1 — a real $/mo reply extracts, the lease-gap demotes to unspecified, both rows persist, no ZodError; B2 — the exact long/noisy draft prompt that reproduced error_max_turns now returns prose.

ryanxwy added 2 commits July 4, 2026 16:04
…so the Rule-2 reclassify hatch runs (lane-B $/mo ZodError) + bounded extract retry cap

Lane B (Claude OAuth) deterministically ZodErrored on any dealer reply carrying a
monthly-payment line ($/mo): the mode-contract superRefine (lease requires
money-factor/residual, finance requires APR) was embedded in the EMIT schema, but
z.toJSONSchema drops refinements — so the model faithfully emits lease + $389/mo and
the emit-boundary parse threw Rule-2 before reclassifyRule2Failures could demote it.
Lane A had the same defect (the deepseek payment-only flakiness).

- Split DealerReplyQuoteRowLooseSchema (structural .strict(), no refinement) from the
  refined row schema; the emit schema validates with the loose one. The model-facing
  JSON schema is byte-identical (refinements never reached it); only the post-parse
  strictness at the emit boundary changes, so the designed demote-to-unspecified hatch
  now runs and the persist floor still re-validates the full refined DealerQuoteSchema
  (a Rule-1 cross-mode bleed still fails closed per-message).
- Prompt grounding: a monthly payment is never otd_total — it belongs in that mode's
  monthly-payment field.
- Bounded retry cap: messages.quote_extraction_attempts (migration 0009); a failed
  message is re-queued only while attempts < MAX_EXTRACT_ATTEMPTS (was unbounded — the
  armed scheduler retried one poisoned message 46x).
- Unify the lane-B fail_reason ledger label (ZodError -> zod_validation, lane A's).
- harness/seed.test.ts: apply the full migration journal in both fixture DBs via a
  shared helper (the hand-picked 0000-0002 list lacked the new column).

Live-verified on the claude lane: a real $/mo lease reply now extracts (emit parse OK),
the lease-gap row demotes to unspecified (otd null, never ranks), the finance OTD row
persists, no ZodError. green GREEN (4054 tests); fresh-context APPROVE + SAFE + drizzle PASS.
…tools on the lane-B prose path (error_max_turns fix)

On lane B (Claude OAuth, Agent SDK 0.3.195) one per-thread draft call hitting
error_max_turns killed the whole follow-up batch (0 sends). Root cause: claudeOAuth set
allowedTools:[] — a no-op (the SDK never forwards an empty list; it is only a permission
pre-approval list) — so the CLI still advertised its default toolset; on a long/ambiguous
thread the model emitted a tool_use, the deny-all canUseTool aborted it, and under
maxTurns:1 the abort counted as a turn -> error_max_turns.

- claudeOAuth: on the PROSE path only, set tools:[] (advertise zero tools -> the model
  cannot emit a tool_use -> the single turn suffices). Not set on the json_schema path
  (keeps the CLI's internal structured-output delivery). maxTurns/allowedTools/
  disallowedTools/canUseTool unchanged — a strictly-more-restrictive fourth lock.
- negotiation_followup: a per-thread draft failure is CAUGHT (documented fail-closed
  degradation, inv #4): the thread keeps draft_body null (never fabricated), is recorded
  to draftFailures, excluded from the gate + the send by the existing !== null filters,
  and voiced in the summary; it stays a candidate next round. Every draft failing still
  rethrows (no zero-target gate). The budget/payment belts stay inside the try — a
  violating draft is discarded, never gated or sent.
- CLAUDE.md inv #4: document the per-thread catch as an approved degradation; note the
  extract re-queue is now bounded by MAX_EXTRACT_ATTEMPTS.

Live-verified on the claude lane: the exact long/noisy draft prompt that reproduced
error_max_turns now returns prose, no error. green GREEN; fresh-context APPROVE + SAFE.
@ryanxwy ryanxwy merged commit a3e0d78 into main Jul 4, 2026
1 check passed
@ryanxwy ryanxwy deleted the evolve-20260704 branch July 4, 2026 23:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant