Skip to content

fix(quick-router): require a status-shaped question for bare-appliance status reads - #862

Open
rsnetworkinginc wants to merge 1 commit into
GeniePod:mainfrom
rsnetworkinginc:fix/bare-appliance-status-shape
Open

fix(quick-router): require a status-shaped question for bare-appliance status reads#862
rsnetworkinginc wants to merge 1 commit into
GeniePod:mainfrom
rsnetworkinginc:fix/bare-appliance-status-shape

Conversation

@rsnetworkinginc

@rsnetworkinginc rsnetworkinginc commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Summary

The bare-appliance branches in home_status_target — water pressure, sump pump, sous vide, baby monitor, and the water+hot pair — key on the appliance noun phrase alone, with no status-question shape required. Unlike the multi-word household-scenario phrases above them, a command can plausibly contain the noun, and keying on the noun alone hijacks that write into a status read: the router answers with a device report instead of abstaining so the LLM can set the reminder. A wrong tool fires and the reminder is silently never set. Closes #861.

utterance on main expected
remind me to check the water pressure home_status{entity:"water pressure"} ABSTAIN
remind me to test the sump pump home_status{entity:"sump pump"} ABSTAIN
remind me to buy a baby monitor home_status{entity:"baby monitor"} ABSTAIN
remind me to buy a sous vide home_status{entity:"sous vide"} ABSTAIN
how do i cook sous vide home_status{entity:"sous vide"} ABSTAIN
remind me to buy hot water bottles home_status{entity:"water heater"} ABSTAIN

Changes

  • Compute status_shaped = looks_like_status_query(text) || text.starts_with("did ") once, and require it on the five bare-appliance branches. This is exactly the gate the sibling iron block already applies for exactly this reason ("remind me to iron my shirt" used to hijack the iron branch — see its comment); these branches were the same bug without the gate.
  • The water+hot branch additionally admits a "how hot " prefix, so the genuine reading "how hot is the water" (whose how prefix none of the gate shapes cover) keeps resolving to water heater.
  • Every canonical phrasing in the existing test corpus is status-shaped and unchanged: Check the water pressure (check ), Is the sump pump running? / Is the sous vide on? / Is the baby monitor on? / Is the water hot? (is ), What's the current water pressure? (what ). The did -prefixed branches (car locks, package, mailbox, stove) and the multi-word scenario phrases above are untouched.
  • Add bare_appliance_status_needs_a_status_shaped_question: the six command phrasings abstain, and six genuine status questions (including How hot is the water?) keep their exact entities.

Real Behavior Proof

  • I have built and run the affected code locally (or noted why I could not).
  • I have verified the change end-to-end on Jetson hardware.
  • I have NOT verified on Jetson hardware, and I explain the equivalent verification path or validation gap below.

Tested profile / hardware (check all that apply):

  • jetson
  • raspberry_pi
  • portable_sbc
  • laptop
  • mac
  • CI-only / docs-only
  • Not run locally

What I ran

x86_64 Linux dev machine (laptop profile), rustc 1.97.1, branch cut from 9bff670. The changed path is pure string routing inside quick::route — no audio, Home Assistant, or hardware dependency — so route() tests exercise it end-to-end. I probed route() over command-shaped utterances containing each appliance noun against unmodified main first, then wrote the regression test and confirmed it fails before applying the fix.

# 1. repro against unmodified main (fix reverted, test kept)
cargo test -p genie-core --lib bare_appliance_status_needs_a_status_shaped_question

# 2. after the fix — the full local CI set
cargo fmt --all -- --check
RUSTFLAGS="-D warnings" cargo clippy --workspace --all-targets --locked -- -D warnings
GENIE_RUN_RELEASE_TESTS=1 cargo build --workspace --locked --all-targets
GENIE_RUN_RELEASE_TESTS=1 cargo test  --workspace --locked --all-targets
cargo test --workspace --locked --doc
cargo run --locked -p genie-ctl -- bfcl-predict-quick --cases tests/bfcl/home_tool_cases.jsonl --out target/bfcl-live-predictions.jsonl
cargo run --locked -p genie-ctl -- bfcl-score --cases tests/bfcl/home_tool_cases.jsonl --predictions target/bfcl-live-predictions.jsonl --min-strict 96
RUSTFLAGS="-D warnings" cargo clippy -p genie-core -p genie-ctl --no-default-features --all-targets --locked -- -D warnings
cargo test -p genie-core -p genie-ctl --no-default-features --all-targets --locked

What I observed

Probe on unmodified main (each should abstain; each fired home_status instead):

"remind me to check the water pressure" => Some(ToolCall { name: "home_status", arguments: {"entity": "water pressure"} })
"remind me to test the sump pump"       => Some(ToolCall { name: "home_status", arguments: {"entity": "sump pump"} })
"remind me to buy a baby monitor"       => Some(ToolCall { name: "home_status", arguments: {"entity": "baby monitor"} })
"remind me to buy a sous vide"          => Some(ToolCall { name: "home_status", arguments: {"entity": "sous vide"} })
"how do i cook sous vide"               => Some(ToolCall { name: "home_status", arguments: {"entity": "sous vide"} })
"remind me to buy hot water bottles"    => Some(ToolCall { name: "home_status", arguments: {"entity": "water heater"} })

Repro before the fix (fix reverted, test kept):

thread 'tools::quick::tests::bare_appliance_status_needs_a_status_shaped_question' panicked:
"Remind me to check the water pressure" is a command, not a status question — it must abstain
test result: FAILED. 0 passed; 1 failed

After the fix: all six command phrasings abstain; all six genuine status questions keep their exact entities.

Gate results on this branch:

  • cargo fmt --all -- --check — clean.
  • cargo clippy --workspace --all-targets --locked -- -D warnings — clean; --no-default-features clippy for genie-core/genie-ctl — clean.
  • GENIE_RUN_RELEASE_TESTS=1 cargo test --workspace --locked --all-targetstools::quick 102 passed / 0 failed, genie-core lib 957 passed / 0 failed / 6 ignored, and every other target green, including the release-gated tool_dispatch_test target (with binary_size_budget passing).
  • cargo test --workspace --locked --doc — green.
  • BFCL live quick-router accuracy gatestrict_accuracy: 96.15% (25/26) against the --min-strict 96 floor, the single holdout being the documented two-call multi-homework-timer case (quick-router multi-intent prompts emit a single, wrong-priority tool #533). Byte-identical to main.
  • --no-default-features test for genie-core/genie-ctl — green (one unrelated flake under full-suite load, llm::provider::tests::loopback_127_range_allowed_without_remote_flag, passes clean in isolation and is untouched by this diff).
  • Release genie-core binary: 7,078,648 bytes vs. the 7,078,520-byte main baseline built with the same toolchain — +128 bytes for the added gate bool.

Jetson gap: no Jetson hardware and no aarch64 cross toolchain on this box, so the cross build is left to the Cross-compile (aarch64 / Jetson) CI job. The change is a pure &str prefix/contains gate with no platform-dependent behaviour, no I/O, and no new dependency, so it is architecture-independent by construction.

Test plan

  1. cargo test -p genie-core --lib bare_appliance_status_needs_a_status_shaped_question — passes here, fails on main (panics on the first command phrasing, which routes to home_status).
  2. cargo test -p genie-core --lib tools::quick — the existing routes_household_status_targets assertions for these branches (Check the water pressure, Is the sump pump running?, Is the sous vide on?, Is the baby monitor on?, Is the water hot?, What's the current water pressure?) are unchanged and still pass, along with the other router tests (102 total).

Notes for reviewers

  • I deliberately did not gate speed limit (its misroute is a context question, not a shape question — "What's the speed limit here?" is already status-shaped), printer ink (the command form is intercepted by memory_recall earlier in route(), so a gate here changes nothing observable), or baby breathing / nursery air quality (no realistic command phrasing contains them). Happy to widen if you'd rather gate every bare-noun branch symmetrically.
  • The gate binding sits above the first gated branch rather than inside each condition so the five branches and the iron block can converge on one helper later if you want that cleanup.

Summary by CodeRabbit

  • Bug Fixes
    • Improved appliance status detection to avoid treating reminder and command-style requests as status questions.
    • Added support for recognizing “How hot is the water?” as a water-heater status query.
    • Improved routing accuracy for requests involving water pressure, sump pumps, sous vide devices, and baby monitors.

…e status reads

A command that merely contains an appliance noun phrase ("remind me to
check the water pressure", "remind me to buy a baby monitor") was
hijacked into a home_status read: the router answered with a device
report instead of abstaining so the LLM can set the reminder. Gate the
water-pressure/sump-pump/sous-vide/baby-monitor/hot-water branches on a
status-question shape, exactly as the iron block already does, keeping
"how hot is the water" alive via its own prefix.
@github-actions github-actions Bot added the bug Something isn't working label Jul 24, 2026
@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The quick router now gates bare appliance status matches on status-shaped wording, preserves “how hot is the water” detection, and adds regression tests for command-like abstention and genuine status queries.

Changes

Home status routing

Layer / File(s) Summary
Status-shaped appliance matching
crates/genie-core/src/tools/quick.rs
Bare appliance matches require a status-shaped query or a leading did , while water-heater matching also accepts “how hot is the water”.
Routing regression coverage
crates/genie-core/src/tools/quick.rs
Tests verify command-like phrases abstain and status-shaped questions resolve to the expected home_status entities.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

Suggested reviewers: ultrahighsuper, jaytbarimbao-collab, matedev01

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: tightening quick-router status reads for bare-appliance queries.
Linked Issues check ✅ Passed The changes gate the listed bare-appliance branches on status-question shape and preserve genuine status queries, matching #861.
Out of Scope Changes check ✅ Passed The patch appears narrowly scoped to the reported routing bug and its regression test, with no unrelated changes evident.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
crates/genie-core/src/tools/quick.rs (1)

5569-5603: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Add regressions for the remaining false-positive shapes.

Also test "Did I buy a baby monitor?", "Remind me to check the water pressure status", and "How hot should I make the water for tea?"; assert that none resolve to home_status.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/genie-core/src/tools/quick.rs` around lines 5569 - 5603, Extend the
test function bare_appliance_status_needs_a_status_shaped_question with the
three specified utterances, asserting route(utterance) returns None for each.
Keep these cases separate from the genuine status-question assertions and
preserve the existing home_status expectations.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/genie-core/src/tools/quick.rs`:
- Around line 2486-2496: The status_shaped predicate in the target-selection
logic should only accept live device-state questions, not reminder or
historical/ownership queries. Tighten the logic around looks_like_status_query
and starts_with("did ") to reject reminder/history prefixes while preserving
valid live-status handling for water pressure, sump pump, and sous vide targets.
- Around line 2557-2562: In the water-heater routing condition, narrow the
text-based exception in the status-shaped check from the broad “how hot ” prefix
to the intended interrogative form such as “how hot is ”, while preserving the
existing water and hot checks. Add a negative regression covering advice
questions like “How hot should I make the water for tea?” to ensure they are not
routed to water heater.

---

Nitpick comments:
In `@crates/genie-core/src/tools/quick.rs`:
- Around line 5569-5603: Extend the test function
bare_appliance_status_needs_a_status_shaped_question with the three specified
utterances, asserting route(utterance) returns None for each. Keep these cases
separate from the genuine status-question assertions and preserve the existing
home_status expectations.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 01842f87-6251-4469-9090-00e5d2a0acae

📥 Commits

Reviewing files that changed from the base of the PR and between 9bff670 and 113885d.

📒 Files selected for processing (1)
  • crates/genie-core/src/tools/quick.rs

Comment on lines +2486 to +2496
let status_shaped = looks_like_status_query(text) || text.starts_with("did ");

if status_shaped && text.contains("water pressure") {
return Some("water pressure".into());
}

if text.contains("sump pump") {
if status_shaped && text.contains("sump pump") {
return Some("sump pump".into());
}

if text.contains("sous vide") {
if status_shaped && text.contains("sous vide") {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Tighten status_shaped to require a live device-state question.

looks_like_status_query matches any " status" substring, and starts_with("did ") accepts history/ownership questions. For example, "Remind me to check the water pressure status" and "Did I buy a baby monitor?" can still return home_status targets instead of reaching reminder or memory handling. Reject reminder/history prefixes or use a stricter live-status predicate.

Also applies to: 2518-2519

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/genie-core/src/tools/quick.rs` around lines 2486 - 2496, The
status_shaped predicate in the target-selection logic should only accept live
device-state questions, not reminder or historical/ownership queries. Tighten
the logic around looks_like_status_query and starts_with("did ") to reject
reminder/history prefixes while preserving valid live-status handling for water
pressure, sump pump, and sous vide targets.

Comment on lines +2557 to +2562
// "how hot is the water" is a genuine water-heater reading whose "how "
// prefix none of the gate shapes cover, so admit it alongside them here.
if (status_shaped || text.starts_with("how hot "))
&& text.contains("water")
&& text.contains("hot")
{

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Narrow the how hot exception to water-temperature questions.

text.starts_with("how hot ") also matches advice requests such as "How hot should I make the water for tea?", routing them to water heater. Require the intended interrogative form (for example, "how hot is …") rather than the broad prefix, and add a negative regression.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/genie-core/src/tools/quick.rs` around lines 2557 - 2562, In the
water-heater routing condition, narrow the text-based exception in the
status-shaped check from the broad “how hot ” prefix to the intended
interrogative form such as “how hot is ”, while preserving the existing water
and hot checks. Add a negative regression covering advice questions like “How
hot should I make the water for tea?” to ensure they are not routed to water
heater.

@matedev01 matedev01 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed both of CodeRabbit's findings directly against route() (added throwaway tests, not just reading the code) — both defeat this PR's own goal of requiring a genuine status question:

  • route("remind me to check the water pressure status")home_status{entity:"water pressure"} — should abstain for the reminder. looks_like_status_query's bare .contains(" status") doesn't require a live-status question shape, so a reminder that happens to say the word "status" still qualifies.
  • route("did i buy a baby monitor")home_status{entity:"baby monitor"} — should abstain for the purchase-history question. The bare starts_with("did ") accepts any "did ..." phrasing, not just live-state questions ("did i leave the iron on").
  • route("how hot should i make the water for tea")home_status{entity:"water heater"} — should abstain for the advice question. The starts_with("how hot ") exception is too broad, same class as the other two.

All three are the exact regression this PR is meant to close for the bare-appliance branches — worth tightening before merge rather than carrying the gap forward under a title that says it's fixed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

2 participants