Skip to content

feat(advisor) 3/4: wire activation into the query layer - #178

Merged
ericleepi314 merged 1 commit into
advisor/02-provider-preservationfrom
advisor/03-query-wiring
May 19, 2026
Merged

feat(advisor) 3/4: wire activation into the query layer#178
ericleepi314 merged 1 commit into
advisor/02-provider-preservationfrom
advisor/03-query-wiring

Conversation

@ericleepi314

Copy link
Copy Markdown
Collaborator

3/4 in the /advisor stack. Stacked on PR 2.

Summary

With helpers + provider preservation in place, this is the actual server-side parity wiring — same beta header, same schema, same instructions, same cache-preserving order as typescript/src/services/api/claude.ts.

  • src/state/app_state.py — adds advisor_model: str | None = None + _on_advisor_model_change handler. The handler persists to settings.advisor_model via the shared default ConfigManager AND invalidates the settings cache, so mid-session toggles via the reactive store reach the next API call without a process restart. Registered in _FIELD_HANDLERS to satisfy the coverage contract.
  • src/query/query.py_call_model_sync now:
    1. Computes advisor_active via a single try/except wrapper: first-party Anthropic + env not disabled + settings.advisor_model set + model_supports_advisor(provider.model) + is_valid_advisor_model(candidate). Any exception → inactive (caught + logged).
    2. When inactive, strips historical advisor blocks via strip_advisor_blocks (API 400s on advisor blocks without the beta header).
    3. When active, appends the advisor schema AFTER the cached tool list (preserves the cache_control marker position), sets betas=[ADVISOR_BETA_HEADER], and appends ADVISOR_TOOL_INSTRUCTIONS to the system prompt's tail. The append order mirrors TS claude.ts:1395, 1411-1421 — toggling /advisor doesn't bust the cache prefix.
    4. After the response, preserves raw_content_blocks (from PR 2) into assistant_blocks so the pair survives history replay.

A stale advisor_model setting under a non-supporting base model is silently ignored — never sent to the API. Mirrors the TS "skipping advisor — base model X does not support advisor" branch.

Stack

Test plan

  • tests/test_advisor_request_wiring.py — 12 tests: full activation matrix on first-party Anthropic + defensive activation (synthetic exception in the gate → turn proceeds without advisor). Verifies the beta header attaches, schema appends AFTER regular tools, instructions append to both string and block-list system prompts, advisor blocks survive in history when active. Negative: env disabled, custom base_url, unsupported base model, invalid advisor model, settings unset.
  • tests/integration/test_advisor_smoke.py — 3 tests: mocked Anthropic stream with the full advisor pair preserved across turns. Interrupt scenario: orphan use stripped on next-turn replay even when beta IS active.

🤖 Generated with Claude Code

Stacked on #PR2. With helpers + provider preservation in place, this
is the actual server-side parity wiring — same beta header, same
schema, same instructions, same cache-preserving order as TS
typescript/src/services/api/claude.ts.

- `src/state/app_state.py`: adds `advisor_model: str | None = None`
  field + `_on_advisor_model_change` handler. The handler persists
  to `settings.advisor_model` via the shared default ConfigManager
  AND invalidates the settings cache, so mid-session toggles via the
  reactive store reach the next API call without a process restart.
  Registered in `_FIELD_HANDLERS` to satisfy the coverage contract.

- `src/query/query.py`: `_call_model_sync` now:
  1. Computes `advisor_active` via a single try/except wrapper:
     `is_advisor_enabled(provider)` (first-party Anthropic AND env
     not disabled) AND `get_settings().advisor_model` non-empty AND
     `model_supports_advisor(provider.model)` AND
     `is_valid_advisor_model(candidate)`. Any exception → inactive.
  2. When inactive, strips historical advisor blocks via
     `strip_advisor_blocks` (the API 400s on advisor blocks without
     the beta header).
  3. When active, appends the advisor schema AFTER the cached tool
     list (preserves the `cache_control` marker position), sets
     `betas=[ADVISOR_BETA_HEADER]`, and appends
     `ADVISOR_TOOL_INSTRUCTIONS` to the system prompt's tail. The
     append order mirrors TS claude.ts:1395, 1411-1421 — toggling
     /advisor doesn't bust the cache prefix.
  4. After the response, preserves `raw_content_blocks` (from PR2)
     into `assistant_blocks` so the pair survives history replay.

A stale `advisor_model` setting under a non-supporting base model is
silently ignored — never sent to the API. Mirrors the TS "skipping
advisor — base model X does not support advisor" branch.

## Test plan
- [x] `tests/test_advisor_request_wiring.py` (12 tests): full
      activation matrix on a first-party Anthropic provider, plus
      defensive activation (synthetic exception in the gate → turn
      proceeds without advisor). Verifies the beta header attaches,
      schema appends AFTER regular tools, instructions append to
      both string and block-list system prompts, advisor blocks
      survive in history when active. Negative cases: env disabled,
      custom base_url, unsupported base model, invalid advisor
      model, settings unset — none send the schema/header.
- [x] `tests/integration/test_advisor_smoke.py` (3 tests): mocked
      Anthropic stream produces text + server_tool_use(advisor) +
      advisor_tool_result + text — verify pair preserved across
      turns. Interrupt scenario: orphan use stripped on next-turn
      replay even when beta IS active (orphan strip is unconditional
      because the API rejects orphans regardless of header).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@ericleepi314
ericleepi314 merged commit ce47fdb into advisor/02-provider-preservation May 19, 2026
ericleepi314 added a commit that referenced this pull request Jul 7, 2026
feat(advisor) 3/4: wire activation into the query layer
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