Skip to content

Extraction waits in the queue, not in the worker (#526) - #640

Merged
WaylandYang merged 9 commits into
deeplethe:devfrom
rollroyces:refactor-chat-extract-ui
Sep 13, 2026
Merged

WaylandYang merged 9 commits into
deeplethe:devfrom
rollroyces:refactor-chat-extract-ui

Conversation

@rollroyces

@rollroyces rollroyces commented Sep 13, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Four commits for four issues. The maintainer's stated direction in each issue body drives the shape:

  • Extraction holds a worker while the ontology index is built #526 — extraction::run no longer holds a worker waiting on the ontology index. utopia_core::Deferred { retry_in } is the symmetric counterpart of Terminal; jobs::mark_failed recognises it and writes queued + attempts-1 + run_at = now + retry_in. The same waiting condition produces the same offset, not 30s × attempts². ontology_index::gate_required enqueues embed_ontology once, returns Ok(true) so run() defers immediately, and the old inline refresh (which raced with the queued job for the per-kb lock) is gone.
  • An answer with no sources looks like every other answer #547 — S.chat.noSourcesConsulted (未引用任何来源) renders under the assistant bubble when sources is empty and the turn is no longer streaming. text-ink-2, no new colour.
  • The chat loop is hand-written, and every policy lands as another branch #546 — chat.rs:790-1015's five branches become named functions in api/chat_policy.rs: no_tool_on_empty_answer, force_final_answer_message, should_nudge_stalled_turn, classify_nudge_response, should_degrade_to_one_shot_rag. The chat loop calls them; stall_tests now exercise them directly. The next step is rig-core's on_completion_call / ModelTurnAction / tool_choice: required — the trait rewrite will be a translation, not a redesign.
  • a_deferred_job_does_not_spend_its_budget — DB-gated test pinning both contracts: two Deferred round-trips leave attempts at 0, and Terminal wins when both markers are attached.

#520 (TPC-H harness) is not in this PR. Its sql_right / answer_right columns belong in their own harness binary.

Test plan

  • cargo check --workspace --tests clean
  • cargo test --workspace --lib → 241 pass
  • cargo test -p utopia-server chat_policy stall_tests → 12 pass
  • pnpm typecheck, pnpm test, pnpm guard, cargo fmt --check clean
  • DB-gated tests skip locally (no Postgres) — CI picks them up via UTOPIA_TEST_REQUIRE_DB

Notes

  • is_terminal / is_deferred read the top-level anyhow::Error, not chain(). anyhow's Context::context(E) for non-Error inner types (e.g. &str) selects the Display impl, which doesn't preserve the marker as an Error — chain().find_map(...) would silently miss it. Both real call-site patterns (Error::new(MARKER).context(...) and anyhow::Error::from(e).context(MARKER)) place the marker at the top.
  • gate_required reuses extraction::build_lists via a pub(crate) full_ontology_chars() so the budget calculation can't drift from the actual 铺全部 path.

@rollroyces
rollroyces force-pushed the refactor-chat-extract-ui branch from f97cab1 to 20ece3c Compare September 13, 2026 04:13
rollroyces and others added 4 commits September 13, 2026 12:32
* A Deferred marker joins Terminal in utopia_core
* jobs::mark_failed recognises Deferred (queued + attempts-1 + run_at = now + retry_in)
* ontology_index::gate_required enqueues embed_ontology and signals a wait
* extraction::run defers at the top of run(); set_graph_status no longer fires before the wait
* Drop the redundant inline ontology_index::refresh; the gate is now the single owner of that work
* gate_required propagates a missing-kb as Err instead of Ok(false)
* Add an integration test that two Deferred round-trips don't burn attempts

Signed-off-by: Royce <roycelam@umich.edu>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* S.chat.noSourcesConsulted joins the i18n bundles in en + zh
* Chat.tsx renders the mark under the bubble when sources is empty and the turn is no longer streaming
* The mark sits in text-ink-2 and adds no new colour

Signed-off-by: Royce <roycelam@umich.edu>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* chat_policy.rs holds the five branches chat.rs:790-1015 grew: no_tool_on_empty_answer, force_final_answer_message, should_nudge_stalled_turn, classify_nudge_response, should_degrade_to_one_shot_rag
* AfterNudge lives in chat_policy; chat.rs aliases it
* chat.rs now calls into the policies; the existing stall_tests exercise the new functions directly
* Drop the duplicate answer_rests_on_nothing in chat.rs

The maintainer's stated direction is still rig-core (on_completion_call, ModelTurnAction, tool_choice: required). The five functions here are the building blocks for that port.

Signed-off-by: Royce <roycelam@umich.edu>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
DB-gated test against a real job row:
* Two Deferred round-trips leave attempts at 0, not at the failed-after-3s threshold
* run_at lands ~30s in the future, so the same waiting condition can't race the queued embed_ontology job for the per-kb lock

A second test pins the Terminal-overrides-Deferred contract.

Signed-off-by: Royce <roycelam@umich.edu>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
@rollroyces
rollroyces force-pushed the refactor-chat-extract-ui branch from 87ec05d to 0b8ac49 Compare September 13, 2026 04:33
WaylandYang and others added 3 commits September 13, 2026 12:54
This reverts commit 1ec110f.

Signed-off-by: WaylandYang <wayland0916@gmail.com>
This reverts commit 2afaf7d.

Signed-off-by: WaylandYang <wayland0916@gmail.com>
Signed-off-by: WaylandYang <wayland0916@gmail.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@WaylandYang WaylandYang changed the title Refactor chat loop policies, queue-gate extraction, surface ungrounded answers Extraction waits in the queue, not in the worker (#526) Sep 13, 2026
@WaylandYang

Copy link
Copy Markdown
Contributor

Thanks @rollroyces — this was a useful PR, and splitting the commits per issue made it easy to take apart. I've split it so each piece can be reviewed and merged on its own:

Follow-up on #526 (b7019be8):

  1. No embedding model means no wait. Over budget with no embedding client/model now extracts with the full ontology, as before, instead of failing Terminal. Deployments with only a chat model have no retrieval to wait for, and failing would stop every document. The gate uses the same client+model test as refresh_scoped, so it never waits for a refresh that would do nothing.
  2. A wait has a window. Deferred refunded attempts with no bound, so if embed_ontology kept failing, the extraction would wake every 30s forever. mark_failed now records deferred_since in the payload on the first deferral. Past DEFER_WINDOW_SECS (1h), the job falls through to the normal backoff and spends its budget. The window doesn't use created_at, because a batch upload can sit queued for hours without waiting on anything. There's a new DB test for this (a_wait_past_its_window_is_a_failure).
  3. Removed the .gitignore line (.hermes-* files are local to your setup).

cargo check --workspace --tests, the core/store lib tests and the three DB tests in a_deferred_job_does_not_spend_its_budget.rs pass locally.

🤖 Generated with Claude Code

WaylandYang and others added 2 commits September 13, 2026 13:17
Signed-off-by: WaylandYang <wayland0916@gmail.com>

# Conflicts:
#	crates/utopia-core/src/error.rs
Signed-off-by: WaylandYang <wayland0916@gmail.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.

2 participants