Skip to content

feat: deriver custom instructions#609

Merged
VVoruganti merged 9 commits into
mainfrom
adavya/deriver-custom-instructions
May 11, 2026
Merged

feat: deriver custom instructions#609
VVoruganti merged 9 commits into
mainfrom
adavya/deriver-custom-instructions

Conversation

@adavyas

@adavyas adavyas commented Apr 24, 2026

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • New Features

    • Reasoning prompts can include user-provided custom instructions; token estimates and prompt construction account for them.
  • Configuration

    • Increased Deriver max input tokens from 23,000 to 25,000.
    • Added optional cap for custom-instructions token usage (default 2,000); disabling or setting the cap to 0 rejects non-blank custom instructions.
  • Behavior

    • Non-blank custom instructions are validated against the configured token cap.
  • Documentation

    • Configuration docs updated to reflect these changes.
  • Tests

    • New tests for custom-instructions handling, validation, and prompt/token estimation.

@coderabbitai

coderabbitai Bot commented Apr 24, 2026

Copy link
Copy Markdown
Contributor

Walkthrough

Raises Deriver max input tokens to 25,000, adds a deployable cap for reasoning.custom_instructions (2,000 tokens default), and wires that setting end-to-end: configuration files, settings model, schema validation, prompt token estimation and prompt construction, plus tests and docs.

Changes

Deriver: custom instructions and token limits

Layer / File(s) Summary
Configuration examples & template
.env.template, config.toml.example, docs/v3/contributing/configuration.mdx
Increase DERIVER/MAX_INPUT_TOKENS 23000 → 25000 and add commented/illustrative DERIVER_MAX_CUSTOM_INSTRUCTIONS_TOKENS / MAX_CUSTOM_INSTRUCTIONS_TOKENS = 2000.
Runtime settings model
src/config.py
Bump DeriverSettings.MAX_INPUT_TOKENS default/upper bound to 25000; add MAX_CUSTOM_INSTRUCTIONS_TOKENS field (default 2000, ge=0, le=2000).
Schema validation & defaults
src/schemas/configuration.py, src/utils/config_helpers.py
Add custom_instructions to reasoning schemas; introduce _validate_custom_instructions_budget enforcing deployment cap via settings.DERIVER.MAX_CUSTOM_INSTRUCTIONS_TOKENS and estimate_tokens (allow blank/whitespace); add validators on ReasoningConfiguration and ResolvedReasoningConfiguration; get_configuration now populates reasoning.custom_instructions default None.
Prompt generation & token estimation
src/deriver/prompts.py, src/deriver/deriver.py
Thread reasoning.custom_instructions through token estimation and prompt construction; minimal_deriver_prompt accepts optional custom_instructions and conditionally injects a "CUSTOM INSTRUCTIONS" section; add estimate_deriver_prompt_tokens(custom_instructions) and use it when building/validating prompts; deriver logic passes custom instructions into estimators and prompt builder.
Tests
tests/deriver/test_deriver_processing.py, tests/deriver/test_prompts.py, tests/test_config.py, tests/test_schema_validations.py, tests/utils/test_config_helpers.py
New/updated tests: prompt construction includes conditional CUSTOM INSTRUCTIONS; token-estimator behavior and error propagation with custom instructions; deriver processing uses custom instructions; DeriverSettings defaults and bounds; schema validation rejects/accepts custom_instructions per deployment cap; config resolution precedence for custom_instructions.

Sequence Diagram(s)

sequenceDiagram
    participant Message as Message\n(with ReasoningConfiguration)
    participant Config as Config Resolver\n(get_configuration)
    participant Schema as Schema Validator\n(ReasoningConfiguration)
    participant Deriver as Deriver\n(deriver.py)
    participant TokenEst as Token Estimator\n(estimate_deriver_prompt_tokens)
    participant Prompt as Prompt Generator\n(minimal_deriver_prompt)
    participant LLM as LLM Call

    Message->>Config: provide message with custom_instructions
    Config->>Schema: validate custom_instructions\nagainst deployment cap
    Schema-->>Config: validation result
    Config->>Deriver: resolved config (includes custom_instructions)
    Deriver->>TokenEst: estimate_deriver_prompt_tokens(custom_instructions)
    TokenEst-->>Deriver: token count
    Deriver->>Prompt: minimal_deriver_prompt(..., custom_instructions)
    Prompt-->>Deriver: formatted prompt (may include CUSTOM INSTRUCTIONS)
    Deriver->>LLM: honcho_llm_call(prompt)
    LLM-->>Deriver: response
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~28 minutes

Poem

🐰 A nibble here, a token there,
We stretch the limits with gentle care,
Twenty-five thousand for the run,
Two thousand for instructions fun,
Prompts now bloom — hop, hop, hooray!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 27.27% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: deriver custom instructions' clearly and specifically summarizes the main change—adding custom instructions support to the deriver component.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch adavya/deriver-custom-instructions

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
docs/v3/contributing/configuration.mdx (1)

298-326: ⚠️ Potential issue | 🟡 Minor

Document the new DERIVER_MAX_CUSTOM_INSTRUCTIONS_TOKENS setting.

config.toml.example and .env.template both advertise MAX_CUSTOM_INSTRUCTIONS_TOKENS, and the schema validator rejects non-blank reasoning.custom_instructions when this cap is unset (raises "DERIVER.MAX_CUSTOM_INSTRUCTIONS_TOKENS is not set…"). The contributing guide should mention it here so operators know they must opt in (max 2000) to enable custom instructions, and note that it must not exceed MAX_INPUT_TOKENS.

📝 Suggested addition to the Deriver section
 DERIVER_MAX_INPUT_TOKENS=25000
+# Opt-in cap for reasoning.custom_instructions (max 2000, must be <= MAX_INPUT_TOKENS).
+# Unset means non-blank custom_instructions will be rejected at validation time.
+# DERIVER_MAX_CUSTOM_INSTRUCTIONS_TOKENS=2000
 # DERIVER_MODEL_CONFIG__THINKING_EFFORT=minimal
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/v3/contributing/configuration.mdx` around lines 298 - 326, Add
documentation for the DERIVER_MAX_CUSTOM_INSTRUCTIONS_TOKENS env/config setting
in the Deriver section to explain that operators must opt-in to enable non-blank
reasoning.custom_instructions (the schema validator rejects it if this cap is
unset), show an example env line (DERIVER_MAX_CUSTOM_INSTRUCTIONS_TOKENS=2000),
state the allowed maximum (2000) and that the value must not exceed
DERIVER_MAX_INPUT_TOKENS, and mention it corresponds to entries in
config.toml.example and .env.template so maintainers know to keep those samples
and the contributing guide in sync.
♻️ Duplicate comments (1)
src/schemas/configuration.py (1)

86-100: ⚠️ Potential issue | 🟠 Major

Validator propagates a server-config error to API clients; see src/config.py comment on effective_max_custom_instructions_tokens.

settings.DERIVER.effective_max_custom_instructions_tokens raises ValueError when MAX_CUSTOM_INSTRUCTIONS_TOKENS is None. Pydantic will wrap that into a 422 ValidationError whose detail string is the internal config-path message (set [deriver].MAX_CUSTOM_INSTRUCTIONS_TOKENS in config.toml). This is the user-facing manifestation of the root-cause concern raised in src/config.py; fixing it at the property (return int | None) and guarding here — if max_tokens is None: raise ValueError("custom_instructions are not enabled on this deployment") — would produce a much better API error.

Also note that because this validator reads the global settings lazily, it's sensitive to test ordering and import-time settings construction; the existing tests work around this with monkeypatch.setattr(settings.DERIVER, ...), which is fine but worth being aware of for future schema-construction outside request scope.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/schemas/configuration.py` around lines 86 - 100, The validator
_validate_custom_instructions_budget must not propagate the internal config
ValueError — fetch max_tokens via
settings.DERIVER.effective_max_custom_instructions_tokens, guard for None and
raise a clear API-facing ValueError like "custom_instructions are not enabled on
this deployment" (do this before calling estimate_tokens), and only perform the
token estimate and comparison if max_tokens is an int; this prevents the
internal config error from being wrapped into a 422 and ensures a deterministic,
testable behavior.
🧹 Nitpick comments (8)
.env.template (1)

115-116: Consider mirroring the explanatory comment from config.toml.example.

Optional nit: in config.toml.example this key carries # Required for non-blank reasoning.custom_instructions; max supported value is 2000. Adding the same inline note here helps users understand that unsetting it disables non-blank custom instructions.

📝 Suggested diff
-# DERIVER_MAX_CUSTOM_INSTRUCTIONS_TOKENS=2000
+# DERIVER_MAX_CUSTOM_INSTRUCTIONS_TOKENS=2000  # Required for non-blank reasoning.custom_instructions; max supported value is 2000
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.env.template around lines 115 - 116, Add an inline explanatory comment next
to the DERIVER_MAX_INPUT_TOKENS and DERIVER_MAX_CUSTOM_INSTRUCTIONS_TOKENS
entries in .env.template mirroring the wording from config.toml.example (e.g.,
note that DERIVER_MAX_CUSTOM_INSTRUCTIONS_TOKENS is "Required for non-blank
reasoning.custom_instructions; max supported value is 2000" and that leaving it
unset disables non-blank custom instructions) so users understand the setting's
effect and max value; update the commented lines for
DERIVER_MAX_CUSTOM_INSTRUCTIONS_TOKENS and optionally DERIVER_MAX_INPUT_TOKENS
to include this guidance.
tests/test_config.py (1)

39-43: Test name implies "defaults" but it exercises an explicit override.

test_deriver_defaults_allow_larger_custom_instruction_budget reads as "with defaults, a larger budget is allowed", but the test passes MAX_CUSTOM_INSTRUCTIONS_TOKENS=2000 explicitly — the default is None. The actual assertion of interest is that MAX_INPUT_TOKENS defaults to 25000 (the other assertion is redundant with test_effective_custom_instructions_tokens_uses_explicit_limit).

♻️ Suggested rename / tightening
-def test_deriver_defaults_allow_larger_custom_instruction_budget() -> None:
-    settings = _make_deriver_settings(MAX_CUSTOM_INSTRUCTIONS_TOKENS=2000)
-
-    assert settings.MAX_INPUT_TOKENS == 25000
-    assert settings.effective_max_custom_instructions_tokens == 2000
+def test_deriver_default_max_input_tokens_is_25000() -> None:
+    settings = _make_deriver_settings()
+
+    assert settings.MAX_INPUT_TOKENS == 25000
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_config.py` around lines 39 - 43, Rename or tighten the test so its
name matches what it asserts: either remove the explicit override and call
_make_deriver_settings() with default (no MAX_CUSTOM_INSTRUCTIONS_TOKENS) to
verify settings.MAX_INPUT_TOKENS == 25000, or rename the test to reflect that it
verifies an explicit override (e.g.,
test_deriver_explicit_custom_instruction_budget_allows_larger_value) and remove
the redundant assertion on settings.effective_max_custom_instructions_tokens
since test_effective_custom_instructions_tokens_uses_explicit_limit already
covers that; update references to _make_deriver_settings,
settings.MAX_INPUT_TOKENS and settings.effective_max_custom_instructions_tokens
accordingly.
tests/utils/test_config_helpers.py (2)

26-28: raising=False is unnecessary here and weakens the guard.

MAX_CUSTOM_INSTRUCTIONS_TOKENS is a declared field on DeriverSettings, so the attribute will always exist on settings.DERIVER. Passing raising=False hides a would-be typo (e.g., renaming the field) that these tests would otherwise catch. Consider dropping it (same on Line 50).

♻️ Proposed change
-    monkeypatch.setattr(
-        settings.DERIVER, "MAX_CUSTOM_INSTRUCTIONS_TOKENS", 100, raising=False
-    )
+    monkeypatch.setattr(settings.DERIVER, "MAX_CUSTOM_INSTRUCTIONS_TOKENS", 100)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/utils/test_config_helpers.py` around lines 26 - 28, Remove the
unnecessary raising=False on the monkeypatch.setattr calls that modify
settings.DERIVER attributes: ensure monkeypatch.setattr(settings.DERIVER,
"MAX_CUSTOM_INSTRUCTIONS_TOKENS", 100) (and the analogous call around line 50)
are called without raising=False so attribute existence is enforced; update both
occurrences to omit the raising parameter to let tests fail if the
DeriverSettings field is renamed or missing.

23-78: Coverage gap: no negative test for message-level custom_instructions exceeding the cap.

Both tests set MAX_CUSTOM_INSTRUCTIONS_TOKENS=100 and use short strings that easily fit. Since MessageConfigurationReasoningConfiguration.validate_custom_instructions is the user-facing enforcement point for this PR, it would be valuable to assert that get_configuration (or MessageConfiguration construction) raises when the resolved/message-level custom_instructions blows the budget, and that it does not raise when the workspace-level string is within budget. The PR summary says tests/test_schema_validations.py covers schema-level rejection, so this is an optional belt-and-suspenders addition at the get_configuration layer.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/utils/test_config_helpers.py` around lines 23 - 78, Add a negative test
that monkeypatches settings.DERIVER.MAX_CUSTOM_INSTRUCTIONS_TOKENS to a small
value, then constructs a MessageConfiguration whose
reasoning.custom_instructions exceeds that cap and assert that get_configuration
(or MessageConfiguration construction) raises via
ReasoningConfiguration.validate_custom_instructions; also include a control
asserting that a workspace-level custom_instructions string that is within the
same cap does not raise when passed to get_configuration. Reference
MAX_CUSTOM_INSTRUCTIONS_TOKENS, MessageConfiguration,
ReasoningConfiguration.validate_custom_instructions, and get_configuration to
locate the relevant code paths.
src/schemas/configuration.py (2)

153-160: Duplicated validator on ResolvedReasoningConfiguration is redundant given upstream enforcement.

ResolvedReasoningConfiguration is only ever constructed from dicts that were themselves merged from already-validated ReasoningConfiguration instances (see src/utils/config_helpers.py:get_configuration). Since _validate_custom_instructions_budget is deterministic over the string, running it again on the resolved model just repeats the same estimate_tokens call on the deriver hot path. Not a correctness issue — I'd keep it only if you specifically want belt-and-suspenders against ResolvedConfiguration(**dict) being constructed from untrusted input elsewhere.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/schemas/configuration.py` around lines 153 - 160, The
ResolvedReasoningConfiguration class currently re-runs
_validate_custom_instructions_budget via the field validator method
validate_custom_instructions, which is redundant because
ResolvedReasoningConfiguration instances are constructed from already-validated
ReasoningConfiguration dicts (see get_configuration). Remove the
`@field_validator`("custom_instructions") method validate_custom_instructions from
ResolvedReasoningConfiguration so the model only defines enabled and
custom_instructions without re-invoking _validate_custom_instructions_budget;
keep the helper _validate_custom_instructions_budget and the original validator
on ReasoningConfiguration unchanged.

91-92: Whitespace-only custom_instructions is returned verbatim — consider normalizing to None.

When custom_instructions = " ", the validator returns the original whitespace string untouched. Downstream, _normalized_custom_instructions in src/deriver/prompts.py correctly collapses that to no section, so there's no user-visible bug today. However, persisting a whitespace-only string in the workspace/session/message configuration is surprising and will fail any future exact-match comparisons to "no custom instructions set". Consider normalizing blank/whitespace to None at validation time for consistency.

♻️ Proposed change
-    if custom_instructions is None:
-        return None
-    if not custom_instructions.strip():
-        return custom_instructions
+    if custom_instructions is None or not custom_instructions.strip():
+        return None
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/schemas/configuration.py` around lines 91 - 92, The validator currently
returns the original whitespace-only string when it hits the check "if not
custom_instructions.strip(): return custom_instructions"; change this to return
None instead so blank/whitespace-only custom_instructions are normalized to None
at validation time (update the branch in the validator that inspects
custom_instructions.strip()). Ensure callers that expect None (e.g.,
_normalized_custom_instructions in src/deriver/prompts.py) continue to work
unchanged.
src/deriver/prompts.py (1)

83-105: Token-estimation strategy is consistent with the existing pattern.

Using empty peer_id/messages and letting the caller add those tokens separately matches how estimate_base_prompt_tokens() is intended to be used, and including custom_instructions in the estimate here is correct because the caller cannot substitute it as a simple string concatenation (it adds the CUSTOM INSTRUCTIONS: header).

One small concern: estimate_minimal_deriver_prompt_tokens() is @cached, but estimate_deriver_prompt_tokens() re-renders the full prompt and re-tokenizes on every call when custom_instructions is non-empty. For the deriver hot path (called per message) this is likely fine, but if you want, the token count for a given custom_instructions string could be cached too.

Based on learnings: "the approach uses empty inputs to get the base prompt template token count via estimate_base_prompt_tokens(), then manually adds tokens for actual inputs (peer_card, working_representation, new_turns). This avoids double-counting wrapper overhead since the base prompt already includes template structure."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/deriver/prompts.py` around lines 83 - 105, The per-call tokenization for
non-empty custom instructions in estimate_deriver_prompt_tokens causes
unnecessary work; cache the token count per normalized custom instructions
string to avoid re-rendering/minimizing heat on the deriver hot path: normalize
via _normalized_custom_instructions(custom_instructions) as you already do, then
compute the prompt and estimate_tokens using minimal_deriver_prompt and
estimate_tokens for that normalized string once and memoize it (e.g., add a
small cached helper or apply `@cache` to a helper like
_estimate_deriver_prompt_tokens_cached(normalized_custom_instructions) that
calls minimal_deriver_prompt and estimate_tokens), while keeping the current
fast-return to estimate_minimal_deriver_prompt_tokens() when
normalized_custom_instructions is None.
src/config.py (1)

771-778: Cross-field check is effectively unreachable given field bounds.

MAX_CUSTOM_INSTRUCTIONS_TOKENS is bounded by le=2000 and MAX_INPUT_TOKENS is bounded by ge of REPRESENTATION_BATCH_MAX_TOKENS (ge=128) and below by gt=0. So this validator can only fire when MAX_INPUT_TOKENS is set to something in the narrow range where it's below MAX_CUSTOM_INSTRUCTIONS_TOKENS (≤2000), which is almost certainly a broken deployment for unrelated reasons. Keeping the check is fine (it's defensive), but the error message could tell the operator the real action — usually "raise MAX_INPUT_TOKENS" — rather than implying the custom-instructions budget is at fault.

Nit only; happy to defer.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/config.py` around lines 771 - 778, The cross-field check in the validator
block referencing MAX_CUSTOM_INSTRUCTIONS_TOKENS and MAX_INPUT_TOKENS should
keep the guard but change the ValueError to direct operators to increase
MAX_INPUT_TOKENS (since MAX_CUSTOM_INSTRUCTIONS_TOKENS is tightly bound by
le=2000 and MAX_INPUT_TOKENS has lower bounds like
REPRESENTATION_BATCH_MAX_TOKENS); update the error raised in that block to
include both current values and a clear actionable suggestion such as "increase
MAX_INPUT_TOKENS to at least X" or "raise MAX_INPUT_TOKENS" while still
reporting the offending values (reference the variables
MAX_CUSTOM_INSTRUCTIONS_TOKENS, MAX_INPUT_TOKENS, and
REPRESENTATION_BATCH_MAX_TOKENS to locate the code).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/config.py`:
- Around line 781-788: The property effective_max_custom_instructions_tokens
should not raise a ValueError for a missing MAX_CUSTOM_INSTRUCTIONS_TOKENS
because that surfaces internal config paths to API clients; instead make the
property return None (i.e., keep its type as int | None) when
MAX_CUSTOM_INSTRUCTIONS_TOKENS is unset, and update the validators in
ReasoningConfiguration.validate_custom_instructions and
ResolvedReasoningConfiguration.validate_custom_instructions to explicitly check
for None and raise a user-facing validation error like "custom_instructions is
not enabled on this deployment" when a non-blank custom_instructions is
submitted; alternatively you can introduce a separate flag (e.g.,
CUSTOM_INSTRUCTIONS_ENABLED) and use that in the schema validators rather than
throwing from effective_max_custom_instructions_tokens.

---

Outside diff comments:
In `@docs/v3/contributing/configuration.mdx`:
- Around line 298-326: Add documentation for the
DERIVER_MAX_CUSTOM_INSTRUCTIONS_TOKENS env/config setting in the Deriver section
to explain that operators must opt-in to enable non-blank
reasoning.custom_instructions (the schema validator rejects it if this cap is
unset), show an example env line (DERIVER_MAX_CUSTOM_INSTRUCTIONS_TOKENS=2000),
state the allowed maximum (2000) and that the value must not exceed
DERIVER_MAX_INPUT_TOKENS, and mention it corresponds to entries in
config.toml.example and .env.template so maintainers know to keep those samples
and the contributing guide in sync.

---

Duplicate comments:
In `@src/schemas/configuration.py`:
- Around line 86-100: The validator _validate_custom_instructions_budget must
not propagate the internal config ValueError — fetch max_tokens via
settings.DERIVER.effective_max_custom_instructions_tokens, guard for None and
raise a clear API-facing ValueError like "custom_instructions are not enabled on
this deployment" (do this before calling estimate_tokens), and only perform the
token estimate and comparison if max_tokens is an int; this prevents the
internal config error from being wrapped into a 422 and ensures a deterministic,
testable behavior.

---

Nitpick comments:
In @.env.template:
- Around line 115-116: Add an inline explanatory comment next to the
DERIVER_MAX_INPUT_TOKENS and DERIVER_MAX_CUSTOM_INSTRUCTIONS_TOKENS entries in
.env.template mirroring the wording from config.toml.example (e.g., note that
DERIVER_MAX_CUSTOM_INSTRUCTIONS_TOKENS is "Required for non-blank
reasoning.custom_instructions; max supported value is 2000" and that leaving it
unset disables non-blank custom instructions) so users understand the setting's
effect and max value; update the commented lines for
DERIVER_MAX_CUSTOM_INSTRUCTIONS_TOKENS and optionally DERIVER_MAX_INPUT_TOKENS
to include this guidance.

In `@src/config.py`:
- Around line 771-778: The cross-field check in the validator block referencing
MAX_CUSTOM_INSTRUCTIONS_TOKENS and MAX_INPUT_TOKENS should keep the guard but
change the ValueError to direct operators to increase MAX_INPUT_TOKENS (since
MAX_CUSTOM_INSTRUCTIONS_TOKENS is tightly bound by le=2000 and MAX_INPUT_TOKENS
has lower bounds like REPRESENTATION_BATCH_MAX_TOKENS); update the error raised
in that block to include both current values and a clear actionable suggestion
such as "increase MAX_INPUT_TOKENS to at least X" or "raise MAX_INPUT_TOKENS"
while still reporting the offending values (reference the variables
MAX_CUSTOM_INSTRUCTIONS_TOKENS, MAX_INPUT_TOKENS, and
REPRESENTATION_BATCH_MAX_TOKENS to locate the code).

In `@src/deriver/prompts.py`:
- Around line 83-105: The per-call tokenization for non-empty custom
instructions in estimate_deriver_prompt_tokens causes unnecessary work; cache
the token count per normalized custom instructions string to avoid
re-rendering/minimizing heat on the deriver hot path: normalize via
_normalized_custom_instructions(custom_instructions) as you already do, then
compute the prompt and estimate_tokens using minimal_deriver_prompt and
estimate_tokens for that normalized string once and memoize it (e.g., add a
small cached helper or apply `@cache` to a helper like
_estimate_deriver_prompt_tokens_cached(normalized_custom_instructions) that
calls minimal_deriver_prompt and estimate_tokens), while keeping the current
fast-return to estimate_minimal_deriver_prompt_tokens() when
normalized_custom_instructions is None.

In `@src/schemas/configuration.py`:
- Around line 153-160: The ResolvedReasoningConfiguration class currently
re-runs _validate_custom_instructions_budget via the field validator method
validate_custom_instructions, which is redundant because
ResolvedReasoningConfiguration instances are constructed from already-validated
ReasoningConfiguration dicts (see get_configuration). Remove the
`@field_validator`("custom_instructions") method validate_custom_instructions from
ResolvedReasoningConfiguration so the model only defines enabled and
custom_instructions without re-invoking _validate_custom_instructions_budget;
keep the helper _validate_custom_instructions_budget and the original validator
on ReasoningConfiguration unchanged.
- Around line 91-92: The validator currently returns the original
whitespace-only string when it hits the check "if not
custom_instructions.strip(): return custom_instructions"; change this to return
None instead so blank/whitespace-only custom_instructions are normalized to None
at validation time (update the branch in the validator that inspects
custom_instructions.strip()). Ensure callers that expect None (e.g.,
_normalized_custom_instructions in src/deriver/prompts.py) continue to work
unchanged.

In `@tests/test_config.py`:
- Around line 39-43: Rename or tighten the test so its name matches what it
asserts: either remove the explicit override and call _make_deriver_settings()
with default (no MAX_CUSTOM_INSTRUCTIONS_TOKENS) to verify
settings.MAX_INPUT_TOKENS == 25000, or rename the test to reflect that it
verifies an explicit override (e.g.,
test_deriver_explicit_custom_instruction_budget_allows_larger_value) and remove
the redundant assertion on settings.effective_max_custom_instructions_tokens
since test_effective_custom_instructions_tokens_uses_explicit_limit already
covers that; update references to _make_deriver_settings,
settings.MAX_INPUT_TOKENS and settings.effective_max_custom_instructions_tokens
accordingly.

In `@tests/utils/test_config_helpers.py`:
- Around line 26-28: Remove the unnecessary raising=False on the
monkeypatch.setattr calls that modify settings.DERIVER attributes: ensure
monkeypatch.setattr(settings.DERIVER, "MAX_CUSTOM_INSTRUCTIONS_TOKENS", 100)
(and the analogous call around line 50) are called without raising=False so
attribute existence is enforced; update both occurrences to omit the raising
parameter to let tests fail if the DeriverSettings field is renamed or missing.
- Around line 23-78: Add a negative test that monkeypatches
settings.DERIVER.MAX_CUSTOM_INSTRUCTIONS_TOKENS to a small value, then
constructs a MessageConfiguration whose reasoning.custom_instructions exceeds
that cap and assert that get_configuration (or MessageConfiguration
construction) raises via ReasoningConfiguration.validate_custom_instructions;
also include a control asserting that a workspace-level custom_instructions
string that is within the same cap does not raise when passed to
get_configuration. Reference MAX_CUSTOM_INSTRUCTIONS_TOKENS,
MessageConfiguration, ReasoningConfiguration.validate_custom_instructions, and
get_configuration to locate the relevant code paths.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 32d2cf71-525a-497e-8924-ae4ee05a69b4

📥 Commits

Reviewing files that changed from the base of the PR and between 07e7a99 and 08269b0.

📒 Files selected for processing (13)
  • .env.template
  • config.toml.example
  • docs/v3/contributing/configuration.mdx
  • src/config.py
  • src/deriver/deriver.py
  • src/deriver/prompts.py
  • src/schemas/configuration.py
  • src/utils/config_helpers.py
  • tests/deriver/test_deriver_processing.py
  • tests/deriver/test_prompts.py
  • tests/test_config.py
  • tests/test_schema_validations.py
  • tests/utils/test_config_helpers.py

Comment thread src/config.py Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (4)
tests/test_schema_validations.py (3)

230-245: Tighten the oversized-instructions test.

This test only asserts that some error is raised at loc == ("custom_instructions",), but Pydantic would also produce an error at that location for unrelated validation failures (e.g., a type error in the future). Consider also asserting that the message contains the expected "exceeds" text to distinguish it from the "not enabled" path covered by the previous test — otherwise both tests could pass even if the validator collapsed both branches into one.

♻️ Proposed tightening
         assert any(
             error["loc"] == ("custom_instructions",)
+            and "exceeds" in error["msg"]
             for error in exc_info.value.errors()
         )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_schema_validations.py` around lines 230 - 245, The test
test_reasoning_configuration_rejects_oversized_custom_instructions should not
only check the error location but also assert the error message indicates the
size limit was exceeded; update the assertion that iterates
exc_info.value.errors() to require both error["loc"] == ("custom_instructions",)
and that the error["msg"] contains the expected substring (e.g., "exceeds" or
"exceeds maximum") so the test specifically verifies the oversized-instructions
branch of ReasoningConfiguration validation rather than any unrelated validation
error.

214-216: Drop raising=False on the monkeypatch calls.

MAX_CUSTOM_INSTRUCTIONS_TOKENS is a declared field on DeriverSettings (see src/config.py:738), so the attribute always exists. Using raising=False silently swallows AttributeError, which would mask a typo in the attribute name and turn a real test failure into a passing no-op. Prefer the default (raising=True) so the tests fail loudly if the setting is ever renamed.

♻️ Proposed change
-        monkeypatch.setattr(
-            settings.DERIVER, "MAX_CUSTOM_INSTRUCTIONS_TOKENS", None, raising=False
-        )
+        monkeypatch.setattr(
+            settings.DERIVER, "MAX_CUSTOM_INSTRUCTIONS_TOKENS", None
+        )

(apply the equivalent change at the three other call sites)

Also applies to: 233-235, 250-252, 280-282

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_schema_validations.py` around lines 214 - 216, Remove
raising=False from the monkeypatch.setattr calls that target
settings.DERIVER.MAX_CUSTOM_INSTRUCTIONS_TOKENS in
tests/test_schema_validations.py so that monkeypatch will raise on missing
attributes; specifically, update the monkeypatch.setattr invocations referencing
settings.DERIVER and the attribute MAX_CUSTOM_INSTRUCTIONS_TOKENS (and the three
equivalent call sites in the same file) to use the default raising=True by
omitting the raising parameter.

221-228: Weak regression guard on line 228.

The "not in" check targets the literal "[deriver].MAX_CUSTOM_INSTRUCTIONS_TOKENS", but the current validator in src/schemas/configuration.py never emits that exact bracketed-prefix format in any branch — the "not enabled" branch doesn't reference the setting at all, and the "exceeds" branch uses DERIVER.MAX_CUSTOM_INSTRUCTIONS_TOKENS (no brackets). As written, this assertion will pass regardless of what leaks into the message, so it doesn't actually guard against settings-key leakage.

Consider either removing it or broadening it to catch any form of the settings key (e.g., assert that "MAX_CUSTOM_INSTRUCTIONS_TOKENS" does not appear in the "not enabled" message).

♻️ Proposed change
-        assert "[deriver].MAX_CUSTOM_INSTRUCTIONS_TOKENS" not in str(exc_info.value)
+        assert "MAX_CUSTOM_INSTRUCTIONS_TOKENS" not in str(exc_info.value)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_schema_validations.py` around lines 221 - 228, The regression
guard is too specific and will always pass because the validator never emits the
bracketed key "[deriver].MAX_CUSTOM_INSTRUCTIONS_TOKENS"; update the test in
tests/test_schema_validations.py to assert against the broader token name
instead of the bracketed form (e.g., assert "MAX_CUSTOM_INSTRUCTIONS_TOKENS" not
in str(exc_info.value)) or remove the assertion entirely; target the same
objects used in the test (errors and exc_info.value) so the "not enabled" branch
in src/schemas/configuration.py is validated to not leak the settings key in any
form.
tests/utils/test_config_helpers.py (1)

23-74: Consider adding a session-over-workspace precedence test.

The two tests cover "workspace only" and "message overrides all" cases, but not the intermediate "session overrides workspace" case (with no message configuration). Given that get_configuration applies overrides in a specific Workspace → Session → Message order in src/utils/config_helpers.py, a dedicated test for session-level override would round out the precedence matrix and guard against regressions in the middle tier.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/utils/test_config_helpers.py` around lines 23 - 74, Add a new unit test
that asserts session-level reasoning.custom_instructions overrides
workspace-level when no MessageConfiguration is provided: set monkeypatch for
settings.DERIVER.MAX_CUSTOM_INSTRUCTIONS_TOKENS (as in other tests), create a
workspace via _workspace(...) with reasoning.custom_instructions = "Use the
workspace-specific guidance.", create a session via _session(...) with
reasoning.custom_instructions = "Use the session-specific guidance.", call
get_configuration(None, session, workspace) and assert
configuration.reasoning.custom_instructions == "Use the session-specific
guidance."; reference get_configuration, _workspace, _session, and
ReasoningConfiguration to locate where to add the test.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@tests/test_schema_validations.py`:
- Around line 230-245: The test
test_reasoning_configuration_rejects_oversized_custom_instructions should not
only check the error location but also assert the error message indicates the
size limit was exceeded; update the assertion that iterates
exc_info.value.errors() to require both error["loc"] == ("custom_instructions",)
and that the error["msg"] contains the expected substring (e.g., "exceeds" or
"exceeds maximum") so the test specifically verifies the oversized-instructions
branch of ReasoningConfiguration validation rather than any unrelated validation
error.
- Around line 214-216: Remove raising=False from the monkeypatch.setattr calls
that target settings.DERIVER.MAX_CUSTOM_INSTRUCTIONS_TOKENS in
tests/test_schema_validations.py so that monkeypatch will raise on missing
attributes; specifically, update the monkeypatch.setattr invocations referencing
settings.DERIVER and the attribute MAX_CUSTOM_INSTRUCTIONS_TOKENS (and the three
equivalent call sites in the same file) to use the default raising=True by
omitting the raising parameter.
- Around line 221-228: The regression guard is too specific and will always pass
because the validator never emits the bracketed key
"[deriver].MAX_CUSTOM_INSTRUCTIONS_TOKENS"; update the test in
tests/test_schema_validations.py to assert against the broader token name
instead of the bracketed form (e.g., assert "MAX_CUSTOM_INSTRUCTIONS_TOKENS" not
in str(exc_info.value)) or remove the assertion entirely; target the same
objects used in the test (errors and exc_info.value) so the "not enabled" branch
in src/schemas/configuration.py is validated to not leak the settings key in any
form.

In `@tests/utils/test_config_helpers.py`:
- Around line 23-74: Add a new unit test that asserts session-level
reasoning.custom_instructions overrides workspace-level when no
MessageConfiguration is provided: set monkeypatch for
settings.DERIVER.MAX_CUSTOM_INSTRUCTIONS_TOKENS (as in other tests), create a
workspace via _workspace(...) with reasoning.custom_instructions = "Use the
workspace-specific guidance.", create a session via _session(...) with
reasoning.custom_instructions = "Use the session-specific guidance.", call
get_configuration(None, session, workspace) and assert
configuration.reasoning.custom_instructions == "Use the session-specific
guidance."; reference get_configuration, _workspace, _session, and
ReasoningConfiguration to locate where to add the test.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b0829ed4-3ef2-458b-9da7-76e3d539af06

📥 Commits

Reviewing files that changed from the base of the PR and between 08269b0 and 59a33d9.

📒 Files selected for processing (6)
  • .env.template
  • docs/v3/contributing/configuration.mdx
  • src/schemas/configuration.py
  • tests/test_config.py
  • tests/test_schema_validations.py
  • tests/utils/test_config_helpers.py
✅ Files skipped from review due to trivial changes (1)
  • docs/v3/contributing/configuration.mdx
🚧 Files skipped from review as they are similar to previous changes (3)
  • tests/test_config.py
  • .env.template
  • src/schemas/configuration.py

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
tests/test_config.py (1)

6-20: ⚡ Quick win

Use snake_case parameter names in test helper.

Helper parameters should be snake_case to match project Python conventions.

Proposed change
 def _make_deriver_settings(
     *,
-    MAX_INPUT_TOKENS: int = 25000,
-    MAX_CUSTOM_INSTRUCTIONS_TOKENS: int = 2000,
-    REPRESENTATION_BATCH_MAX_TOKENS: int = 1024,
+    max_input_tokens: int = 25000,
+    max_custom_instructions_tokens: int = 2000,
+    representation_batch_max_tokens: int = 1024,
 ) -> DeriverSettings:
     return DeriverSettings(
         MODEL_CONFIG=ConfiguredModelSettings(
             model="gpt-5.4-mini",
             transport="openai",
         ),
-        MAX_INPUT_TOKENS=MAX_INPUT_TOKENS,
-        MAX_CUSTOM_INSTRUCTIONS_TOKENS=MAX_CUSTOM_INSTRUCTIONS_TOKENS,
-        REPRESENTATION_BATCH_MAX_TOKENS=REPRESENTATION_BATCH_MAX_TOKENS,
+        MAX_INPUT_TOKENS=max_input_tokens,
+        MAX_CUSTOM_INSTRUCTIONS_TOKENS=max_custom_instructions_tokens,
+        REPRESENTATION_BATCH_MAX_TOKENS=representation_batch_max_tokens,
     )

As per coding guidelines: "**/*.py: Use snake_case for variables and functions; PascalCase for classes".

🤖 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 `@tests/test_config.py` around lines 6 - 20, The helper _make_deriver_settings
currently uses Pascal/SCREAMING_STYLE parameter names; rename its parameters to
snake_case (e.g., max_input_tokens, max_custom_instructions_tokens,
representation_batch_max_tokens) and update the function body to pass those
snake_case variables into DeriverSettings (mapping to MAX_INPUT_TOKENS,
MAX_CUSTOM_INSTRUCTIONS_TOKENS, REPRESENTATION_BATCH_MAX_TOKENS) so callers and
the test follow project Python naming conventions; also update any test calls to
use the new snake_case argument names.
🤖 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.

Nitpick comments:
In `@tests/test_config.py`:
- Around line 6-20: The helper _make_deriver_settings currently uses
Pascal/SCREAMING_STYLE parameter names; rename its parameters to snake_case
(e.g., max_input_tokens, max_custom_instructions_tokens,
representation_batch_max_tokens) and update the function body to pass those
snake_case variables into DeriverSettings (mapping to MAX_INPUT_TOKENS,
MAX_CUSTOM_INSTRUCTIONS_TOKENS, REPRESENTATION_BATCH_MAX_TOKENS) so callers and
the test follow project Python naming conventions; also update any test calls to
use the new snake_case argument names.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6ddd371c-8c4e-4e6d-98ec-f3c1eb48711d

📥 Commits

Reviewing files that changed from the base of the PR and between 59a33d9 and c31b3ae.

📒 Files selected for processing (6)
  • .env.template
  • config.toml.example
  • src/config.py
  • src/schemas/configuration.py
  • tests/test_config.py
  • tests/test_schema_validations.py
🚧 Files skipped from review as they are similar to previous changes (3)
  • config.toml.example
  • src/schemas/configuration.py
  • tests/test_schema_validations.py

@VVoruganti VVoruganti merged commit a420264 into main May 11, 2026
9 checks passed
murraysu added a commit to murraysu/honcho that referenced this pull request Jun 3, 2026
Sync fork with upstream through 9f26fdd (v3.0.2 → v3.0.9).
No conflicts; local CLAUDE.md (GitNexus block) auto-merged.
Notable: base_url threading to LLM clients (plastic-labs#643), configurable
embeddings (plastic-labs#678), deriver custom instructions (plastic-labs#609).

Co-Authored-By: Claude Opus 4.8 <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