Skip to content

Refactor generation services for single configured extractor - #101

Merged
yyiilluu merged 4 commits into
mainfrom
yilu/single-extractor-generation-service
May 29, 2026
Merged

Refactor generation services for single configured extractor#101
yyiilluu merged 4 commits into
mainfrom
yilu/single-extractor-generation-service

Conversation

@yyiilluu

@yyiilluu yyiilluu commented May 29, 2026

Copy link
Copy Markdown
Contributor

Summary

  • simplify generation services around one configured extractor/evaluator per profile, playbook, and agent success flow
  • add canonical agent_success_config with legacy agent_success_configs first-entry migration/serialization
  • update docs configure UI and tests for single success evaluator behavior

Testing

  • PYTEST_ADDOPTS='--no-cov' uv run pytest open_source/reflexio/tests/server/services/agent_success_evaluation/test_agent_success_evaluation_services.py open_source/reflexio/tests/models/test_validators.py open_source/reflexio/tests/server/services/test_base_generation_service.py open_source/reflexio/tests/server/api_endpoints/test_evaluations_regenerate_api.py -q from enterprise repo: 203 passed
  • uv run ruff check ... on touched Python files
  • uv run pyright ... on touched backend files
  • cd open_source/reflexio/docs && npm run lint && npm run build (lint has existing warnings only)
  • local launch checklist phases 0-14 passed against backend 8081, cleanup restored config/test data

Review follow-up

  • add shared normalize_legacy_config_shape() to config_schema.py and apply it at the local_file_config_storage load boundary, so OSS on-disk configs persisted before the single-extractor refactor recover their singular extractor/evaluator fields instead of silently dropping the legacy list keys
  • drop the dead *_extractor_configs fallback in resume_worker._select_current_extractor_config now that the deprecated computed list views are gone
  • PYTEST_ADDOPTS='--no-cov' uv run pytest -o 'addopts=' open_source/reflexio/reflexio/server/services/configurator/test_config_storage.py open_source/reflexio/tests/server/services/extraction/test_resume_worker.py (incl. new test_load_config_upgrades_legacy_list_shape): passed
  • uv run ruff check + uv run pyright clean on touched files

Summary by CodeRabbit

  • Refactor

    • UI sections now manage a single extractor/evaluator config with an enable/disable switch (simpler controls, no list workflows).
    • Configuration schema moved to singular extractor/evaluator fields; PATCHing now requires the full nested object and setting a field to null disables that extractor.
    • Regenerate/validation now prefers the configured single evaluator when present.
  • User Experience

    • Code editor shows generated code until you edit it and runs the currently displayed code.
  • Stability

    • Settings load/persistence improved for more reliable initialization and storage.

Review Change Stack

@coderabbitai

coderabbitai Bot commented May 29, 2026

Copy link
Copy Markdown

Caution

Review failed

Pull request was closed or merged during review

📝 Walkthrough

Walkthrough

This PR converts extractor/evaluator config fields from plural lists to singular nullable objects across frontend and backend, refactors generation orchestration to run a single configured extractor per cycle, updates API/configurator aliasing and PATCH semantics, and adjusts UI and tests to the new shape.

Changes

Singular Extractor Config Refactor

Layer / File(s) Summary
Frontend config schema and serialization
docs/lib/config-schema.ts
ReflexioConfig uses profile_extractor_config, user_playbook_extractor_config, and agent_success_config (nullable single objects); defaultConfig() and serializeConfig() updated to match.
ConfigEditor wiring and UI sections
docs/components/configure/config-editor.tsx, docs/components/configure/sections.tsx
hydrate() normalizes incoming agent_success_config; AgentSuccessSection, ProfileExtractorsSection, and PlaybookExtractorsSection now manage a single optional config with enable toggle and patch-based updates; UI switched from list cards to a single SwitchField and conditional fields.
CodePanel and Settings
docs/components/method/code-panel.tsx, docs/hooks/use-settings.tsx
CodePanel uses memoized generatedCode and displayedCode to switch between generated and edited code; SettingsProvider initializes via loadSettings() and consolidates localStorage persistence.
Backend Config model and migration helpers
reflexio/models/config_schema.py
Replace plural backend fields with singular agent_success_config etc., remove computed-field list-view properties and JSON-field migrations, and add normalize_legacy_config_shape to upgrade legacy list-shaped on-disk/HTTP payloads.
Configurator aliasing and API changes
reflexio/server/services/configurator/base_configurator.py, reflexio/server/api.py
set_config_by_name now accepts None and assigns values directly; removed legacy per-field normalization helper; /api/update_config now shallow-merges partial directly; /api/evaluations/regenerate prefers canonical agent_success_config for validation with legacy fallback.
BaseGenerationService single-extractor orchestration
reflexio/server/services/base_generation_service.py
Introduce PreparedGenerationRun, change abstract loader to _load_extractor_config(), add _execute_extractor() and single-extractor prepare/run flow; update stride/filtering and precheck helpers for single-config processing.
Service implementations adopt single-config loading
reflexio/server/services/agent_success_evaluation/agent_success_evaluation_service.py, .../playbook/playbook_generation_service.py, .../profile/profile_generation_service.py
Replace list loaders with _load_extractor_config(); agent success applies evaluation_name_filter; playbook uses _configured_playbook_config() and single-definition prompts; profile simplifies criteria rendering to single-config form.
Playbook aggregator selection
reflexio/server/services/playbook/playbook_aggregator.py
Stop falling back to legacy user_playbook_extractor_configs list when selecting aggregation config; use singular path only.
Tests and fixtures
tests/**
Update tests/fixtures to set and assert singular config fields; add unit tests for agent_success _load_extractor_config() behavior and config-storage legacy-shape migration; adapt base generation tests to single-extractor semantics.

Estimated code review effort: 🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

"🐰 A rabbit scribbles in the grass tonight,
Singular configs hopped in, tidy and light.
One runner now runs, UI and tests align,
Old lists tucked away — the code feels fine.
🥕✨"

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 76.19% 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 clearly describes the main refactoring: extractors consolidated from multiple per config to a single configured extractor per service.
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 yilu/single-extractor-generation-service

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

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)
reflexio/server/services/base_generation_service.py (1)

1108-1126: 💤 Low value

Minor inconsistency in deprecated list-handling path.

When _collect_scoped_interactions_for_precheck is called with a list directly (bypassing _should_run_before_extraction), the returned scoped_config will be the last config in the list rather than the first, due to the loop reassigning it on each iteration. This is inconsistent with the "first-entry-wins" semantics documented elsewhere.

Since this path is deprecated and the main flow normalizes to a single config before calling this method (at lines 960-963), this has limited impact. However, if any legacy callers rely on the list interface directly, they might see unexpected behavior.

Optional fix for consistency
         if isinstance(extractor_config, list):
             deduped_sessions: dict[str, RequestInteractionDataModel] = {}
             scoped_config = extractor_config[0] if extractor_config else None
+            first_config = scoped_config
             for config in extractor_config:
                 session_data_models, scoped_config = (
                     self._collect_scoped_interactions_for_precheck(config)
                 )
                 for data_model in session_data_models:
                     request_id = getattr(data_model.request, "request_id", None)
                     dedupe_key = (
                         request_id
                         or data_model.session_id
                         or f"scoped_group_{len(deduped_sessions)}"
                     )
                     if dedupe_key not in deduped_sessions:
                         deduped_sessions[dedupe_key] = data_model
-            if scoped_config is None:
+            if first_config is None:
                 raise ValueError("extractor_config list must not be empty")
-            return list(deduped_sessions.values()), scoped_config
+            return list(deduped_sessions.values()), first_config
🤖 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 `@reflexio/server/services/base_generation_service.py` around lines 1108 -
1126, The current loop in the list-handling branch of
_collect_scoped_interactions_for_precheck reassigns scoped_config on every
iteration so the final scoped_config becomes the last config in
extractor_config; change the logic so scoped_config preserves the first non-None
value (first-entry-wins). Specifically, when iterating extractor_config, call
self._collect_scoped_interactions_for_precheck(config) to collect
session_data_models but only assign scoped_config from that call if
scoped_config is currently None (or initialize scoped_config=None and set it on
the first iteration), leaving deduped_sessions aggregation unchanged.
🤖 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 `@docs/hooks/use-settings.tsx`:
- Around line 38-44: The lazy initializer useState(() => loadSettings()) runs on
server and client causing SSR hydration mismatch; change settings initialization
to a server-safe constant (e.g., useState<Settings>({ apiEndpoint:
"http://localhost:8081" })) and move localStorage read into a client-only
useEffect that runs after mount to call setSettings(loadSettings()). Add a
useRef flag (e.g., isInitialMount) to prevent the persistence effect that writes
to localStorage (using STORAGE_KEY) from immediately overwriting stored values
on first render, and keep the existing effect that writes settings to
localStorage but guard it so it only runs after the client load has completed.

In `@reflexio/server/api.py`:
- Around line 1731-1737: The fallback to legacy_configs[0] isn't validated
before reading success_config.evaluation_name, which can raise AttributeError;
after assigning success_config from legacy_configs ensure the object has a valid
string evaluation_name (e.g., check isinstance(getattr(success_config,
"evaluation_name", None), str)) before using it, and build known safely (e.g.,
use getattr(success_config, "evaluation_name", None) and only add it if it's a
str) so that the variables success_config, legacy_configs, evaluation_name and
known are handled defensively.

---

Nitpick comments:
In `@reflexio/server/services/base_generation_service.py`:
- Around line 1108-1126: The current loop in the list-handling branch of
_collect_scoped_interactions_for_precheck reassigns scoped_config on every
iteration so the final scoped_config becomes the last config in
extractor_config; change the logic so scoped_config preserves the first non-None
value (first-entry-wins). Specifically, when iterating extractor_config, call
self._collect_scoped_interactions_for_precheck(config) to collect
session_data_models but only assign scoped_config from that call if
scoped_config is currently None (or initialize scoped_config=None and set it on
the first iteration), leaving deduped_sessions aggregation unchanged.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: a787ba57-e101-4bd3-a5fb-8df597e248f1

📥 Commits

Reviewing files that changed from the base of the PR and between f80d311 and 4fc5bbc.

📒 Files selected for processing (16)
  • docs/components/configure/config-editor.tsx
  • docs/components/configure/sections.tsx
  • docs/components/method/code-panel.tsx
  • docs/hooks/use-settings.tsx
  • docs/lib/config-schema.ts
  • reflexio/models/api_schema/eval_overview_schema.py
  • reflexio/models/config_schema.py
  • reflexio/server/api.py
  • reflexio/server/services/agent_success_evaluation/agent_success_evaluation_service.py
  • reflexio/server/services/base_generation_service.py
  • reflexio/server/services/configurator/base_configurator.py
  • reflexio/server/services/playbook/playbook_generation_service.py
  • reflexio/server/services/profile/profile_generation_service.py
  • tests/models/test_validators.py
  • tests/server/services/agent_success_evaluation/test_agent_success_evaluation_services.py
  • tests/server/services/test_base_generation_service.py

Comment thread docs/hooks/use-settings.tsx Outdated
Comment thread reflexio/server/api.py
Comment on lines +1731 to +1737
success_config = getattr(config, "agent_success_config", None)
if success_config is None or not isinstance(
getattr(success_config, "evaluation_name", None), str
):
legacy_configs = getattr(config, "agent_success_configs", None)
success_config = legacy_configs[0] if legacy_configs else None
known = {success_config.evaluation_name} if success_config else set()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Validate legacy config before accessing evaluation_name.

The canonical agent_success_config is validated for a string evaluation_name (lines 1732-1734), but when falling back to legacy_configs[0] (line 1736), no validation is applied. If the legacy config exists but lacks a valid evaluation_name, line 1737 will raise AttributeError when accessing success_config.evaluation_name.

🛡️ Recommended defensive fix

Add validation after the legacy fallback or use getattr defensively:

 success_config = getattr(config, "agent_success_config", None)
 if success_config is None or not isinstance(
     getattr(success_config, "evaluation_name", None), str
 ):
     legacy_configs = getattr(config, "agent_success_configs", None)
     success_config = legacy_configs[0] if legacy_configs else None
+    # Re-validate legacy config
+    if success_config and not isinstance(
+        getattr(success_config, "evaluation_name", None), str
+    ):
+        success_config = None
 known = {success_config.evaluation_name} if success_config else set()

Alternatively, use defensive attribute access:

-known = {success_config.evaluation_name} if success_config else set()
+known = (
+    {success_config.evaluation_name}
+    if success_config and isinstance(
+        getattr(success_config, "evaluation_name", None), str
+    )
+    else set()
+)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
success_config = getattr(config, "agent_success_config", None)
if success_config is None or not isinstance(
getattr(success_config, "evaluation_name", None), str
):
legacy_configs = getattr(config, "agent_success_configs", None)
success_config = legacy_configs[0] if legacy_configs else None
known = {success_config.evaluation_name} if success_config else set()
success_config = getattr(config, "agent_success_config", None)
if success_config is None or not isinstance(
getattr(success_config, "evaluation_name", None), str
):
legacy_configs = getattr(config, "agent_success_configs", None)
success_config = legacy_configs[0] if legacy_configs else None
# Re-validate legacy config
if success_config and not isinstance(
getattr(success_config, "evaluation_name", None), str
):
success_config = None
known = {success_config.evaluation_name} if success_config else set()
🤖 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 `@reflexio/server/api.py` around lines 1731 - 1737, The fallback to
legacy_configs[0] isn't validated before reading success_config.evaluation_name,
which can raise AttributeError; after assigning success_config from
legacy_configs ensure the object has a valid string evaluation_name (e.g., check
isinstance(getattr(success_config, "evaluation_name", None), str)) before using
it, and build known safely (e.g., use getattr(success_config, "evaluation_name",
None) and only add it if it's a str) so that the variables success_config,
legacy_configs, evaluation_name and known are handled defensively.

@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: 1

Caution

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

⚠️ Outside diff range comments (2)
reflexio/server/api.py (1)

1220-1228: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reject/normalize deprecated legacy PATCH keys in /api/update_config before Config(**merged)

update_config() shallow-merges partial over the existing config, but reflexio.models.config_schema.Config does not forbid/validate unknown extra top-level fields—so legacy keys like profile_extractor_configs, user_playbook_extractor_configs, and agent_success_configs are ignored during Config(**merged). The endpoint can therefore return success while silently dropping the intended update (no migration/422 path).

Suggested fix
     reflexio = get_reflexio(org_id=org_id)
     existing = reflexio.request_context.configurator.get_config().model_dump(
         mode="python"
     )
-    merged = {**existing, **partial}
+    legacy_keys = {
+        "profile_extractor_configs": "profile_extractor_config",
+        "user_playbook_extractor_configs": "user_playbook_extractor_config",
+        "agent_success_configs": "agent_success_config",
+    }
+    normalized_partial = dict(partial)
+    for old, new in legacy_keys.items():
+        if old in normalized_partial and new not in normalized_partial:
+            legacy_value = normalized_partial.pop(old)
+            normalized_partial[new] = (
+                legacy_value[0] if isinstance(legacy_value, list) else legacy_value
+            )
+
+    merged = {**existing, **normalized_partial}
🤖 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 `@reflexio/server/api.py` around lines 1220 - 1228, In update_config(), before
calling Config(**merged) on the merged dict, detect any deprecated top-level
keys (e.g. "profile_extractor_configs", "user_playbook_extractor_configs",
"agent_success_configs") present in merged and either normalize them to the new
shape or reject them with a 422 client error; implement by checking
merged.keys(), collecting any deprecated keys found, and if any exist return a
422 response (with a clear message listing the deprecated keys) instead of
proceeding to Config(**merged) so updates don’t silently drop legacy fields.
tests/server/services/test_base_generation_service.py (1)

1136-1137: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Forward extractor_configs by keyword to avoid binding it to extractor_config.

The parent signature is now __init__(self, llm_client, request_context, extractor_config=None, extractor_configs=None). Passing extractor_configs positionally binds the list to the parent's extractor_config parameter instead. Since a non-empty list is not None, the parent's if extractor_config is not None: branch assigns self._extractor_config = [MockExtractorConfig(...)] (the whole list) rather than the first entry.

This is currently masked because InProgressTrackingService._run_generation is overridden to only bump a counter and never consumes _extractor_config, but it will silently produce a list-valued _extractor_config for any subclass test that relies on the loaded config.

🐛 Proposed fix
     def __init__(self, llm_client, request_context, extractor_configs=None):
-        super().__init__(llm_client, request_context, extractor_configs)
+        super().__init__(llm_client, request_context, extractor_configs=extractor_configs)
         self._generation_count = 0  # Tracks _run_generation calls
🤖 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/server/services/test_base_generation_service.py` around lines 1136 -
1137, The subclass __init__ currently calls super().__init__(llm_client,
request_context, extractor_configs) which passes extractor_configs positionally
and accidentally binds it to the parent's extractor_config parameter; change the
call to forward extractor_configs by keyword (super().__init__(llm_client,
request_context, extractor_configs=extractor_configs)) so the parent receives
extractor_config and extractor_configs correctly and the parent's logic that
checks if extractor_config is not None behaves as intended.
🧹 Nitpick comments (2)
docs/components/configure/config-editor.tsx (1)

34-43: ⚡ Quick win

Consider explicitly normalizing all extractor config fields.

The hydrate function explicitly normalizes agent_success_config to ensure it's either the incoming value or null, but relies on the spread operator for profile_extractor_config and user_playbook_extractor_config. If the server omits these fields (returning undefined in the deserialized JSON), they would remain undefined in the merged config rather than becoming null.

For consistency and type safety, consider explicitly normalizing all singular extractor config fields:

 function hydrate(raw: unknown): ReflexioConfig {
   const base = defaultConfig();
   if (!raw || typeof raw !== "object") return base;
   const incoming = raw as Partial<ReflexioConfig>;
   return {
     ...base,
     ...incoming,
     agent_success_config: incoming.agent_success_config ?? null,
+    profile_extractor_config: incoming.profile_extractor_config ?? null,
+    user_playbook_extractor_config: incoming.user_playbook_extractor_config ?? null,
   };
 }

This ensures all extractor configs have consistent null (not undefined) semantics when missing from the server response.

🤖 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 `@docs/components/configure/config-editor.tsx` around lines 34 - 43, The
hydrate function currently spreads incoming onto base but only normalizes
agent_success_config to null; update hydrate (which uses defaultConfig() and the
incoming: Partial<ReflexioConfig> variable) to explicitly normalize all singular
extractor fields—e.g., set profile_extractor_config:
incoming.profile_extractor_config ?? null and user_playbook_extractor_config:
incoming.user_playbook_extractor_config ?? null—so the returned ReflexioConfig
never contains undefined for those extractor fields and retains the existing
agent_success_config normalization.
tests/server/services/profile/test_profile_generation_service.py (1)

214-271: ⚡ Quick win

Keep coverage for the no-criteria fast path.

The single-config tests cover positive rendering paths, but the branch where _build_should_run_prompt() should return None is no longer exercised. A tiny case with an empty/whitespace-only definition and no override would lock in the guard that prevents useless should-run prompts.

🤖 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/server/services/profile/test_profile_generation_service.py` around
lines 214 - 271, Add a test that asserts the fast-path returning None when there
is no meaningful criteria: create a ProfileExtractorConfig with extractor_name
(e.g., "none") and an extraction_definition_prompt set to "" or only whitespace
and no should_extract_profile_prompt_override, call
service._build_should_run_prompt(config, sample_request_interaction_models)
(patch service.configurator.get_agent_context as in other tests) and assert the
result is None to cover the no-criteria branch.
🤖 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 `@docs/lib/config-schema.ts`:
- Around line 138-140: The current defaultConfig seeds profile_extractor_config
and user_playbook_extractor_config using defaultProfileExtractor() /
defaultPlaybookExtractor(), causing enabled extractor objects with empty prompt
fields to be submitted; update the behavior so these fields are either set to
null by default (replace profile_extractor_config and
user_playbook_extractor_config defaults with null) or modify serializeConfig()
to normalize and collapse empty extractor objects into null by checking their
nested prompt/text fields (e.g., in serializeConfig(), detect if the extractor
prompt fields are empty/whitespace and replace the entire extractor object with
null before returning the payload) to prevent sending enabled-but-blank
extractor configs.

---

Outside diff comments:
In `@reflexio/server/api.py`:
- Around line 1220-1228: In update_config(), before calling Config(**merged) on
the merged dict, detect any deprecated top-level keys (e.g.
"profile_extractor_configs", "user_playbook_extractor_configs",
"agent_success_configs") present in merged and either normalize them to the new
shape or reject them with a 422 client error; implement by checking
merged.keys(), collecting any deprecated keys found, and if any exist return a
422 response (with a clear message listing the deprecated keys) instead of
proceeding to Config(**merged) so updates don’t silently drop legacy fields.

In `@tests/server/services/test_base_generation_service.py`:
- Around line 1136-1137: The subclass __init__ currently calls
super().__init__(llm_client, request_context, extractor_configs) which passes
extractor_configs positionally and accidentally binds it to the parent's
extractor_config parameter; change the call to forward extractor_configs by
keyword (super().__init__(llm_client, request_context,
extractor_configs=extractor_configs)) so the parent receives extractor_config
and extractor_configs correctly and the parent's logic that checks if
extractor_config is not None behaves as intended.

---

Nitpick comments:
In `@docs/components/configure/config-editor.tsx`:
- Around line 34-43: The hydrate function currently spreads incoming onto base
but only normalizes agent_success_config to null; update hydrate (which uses
defaultConfig() and the incoming: Partial<ReflexioConfig> variable) to
explicitly normalize all singular extractor fields—e.g., set
profile_extractor_config: incoming.profile_extractor_config ?? null and
user_playbook_extractor_config: incoming.user_playbook_extractor_config ??
null—so the returned ReflexioConfig never contains undefined for those extractor
fields and retains the existing agent_success_config normalization.

In `@tests/server/services/profile/test_profile_generation_service.py`:
- Around line 214-271: Add a test that asserts the fast-path returning None when
there is no meaningful criteria: create a ProfileExtractorConfig with
extractor_name (e.g., "none") and an extraction_definition_prompt set to "" or
only whitespace and no should_extract_profile_prompt_override, call
service._build_should_run_prompt(config, sample_request_interaction_models)
(patch service.configurator.get_agent_context as in other tests) and assert the
result is None to cover the no-criteria branch.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 868e051d-c620-49d2-a332-99d45dd70e06

📥 Commits

Reviewing files that changed from the base of the PR and between 4fc5bbc and fd0292b.

📒 Files selected for processing (28)
  • docs/components/configure/config-editor.tsx
  • docs/components/configure/sections.tsx
  • docs/lib/config-schema.ts
  • reflexio/models/api_schema/eval_overview_schema.py
  • reflexio/models/config_schema.py
  • reflexio/server/api.py
  • reflexio/server/services/agent_success_evaluation/agent_success_evaluation_service.py
  • reflexio/server/services/base_generation_service.py
  • reflexio/server/services/configurator/base_configurator.py
  • reflexio/server/services/configurator/test_config_storage.py
  • reflexio/server/services/playbook/playbook_aggregator.py
  • reflexio/server/services/playbook/playbook_generation_service.py
  • reflexio/server/services/profile/profile_generation_service.py
  • tests/cli/test_bootstrap_config.py
  • tests/lib/test_profile_workflows_unit.py
  • tests/models/test_validators.py
  • tests/server/api_endpoints/conftest.py
  • tests/server/api_endpoints/test_api_routes.py
  • tests/server/services/agent_success_evaluation/test_agent_success_evaluation_services.py
  • tests/server/services/playbook/test_cluster_change_detection.py
  • tests/server/services/playbook/test_playbook_aggregator.py
  • tests/server/services/playbook/test_playbook_generation_service.py
  • tests/server/services/playbook/test_playbook_generation_service_integration.py
  • tests/server/services/profile/test_profile_generation_service.py
  • tests/server/services/test_base_generation_service.py
  • tests/server/services/test_configurator.py
  • tests/server/services/test_profile_generation_service.py
  • tests/server/services/test_profile_source_filtering.py
💤 Files with no reviewable changes (4)
  • reflexio/server/services/playbook/playbook_aggregator.py
  • reflexio/server/services/agent_success_evaluation/agent_success_evaluation_service.py
  • reflexio/server/services/configurator/base_configurator.py
  • tests/models/test_validators.py
✅ Files skipped from review due to trivial changes (1)
  • reflexio/models/api_schema/eval_overview_schema.py

Comment thread docs/lib/config-schema.ts
@yyiilluu
yyiilluu force-pushed the yilu/single-extractor-generation-service branch from fd0292b to d121958 Compare May 29, 2026 04:18
yyiilluu added 2 commits May 28, 2026 21:24
Add shared normalize_legacy_config_shape() to config_schema and apply it
at the local-file storage load boundary so configs persisted before the
single-extractor refactor recover their singular extractor/evaluator
fields instead of silently dropping the legacy list keys. Drop the dead
legacy *_extractor_configs fallback in resume_worker now that the
computed list views are gone.
@yyiilluu
yyiilluu merged commit 93a17c3 into main May 29, 2026
1 check was pending
yilu331 added a commit that referenced this pull request May 29, 2026
The previous merge commit (cad4947) shipped only the merge metadata; the
backend-eng agent's actual conflict resolution work was stashed during
diff comparison and never restaged before the commit landed. As a
result, main's recent feature/refactor PRs (#98 cleanup, #99 Postgres
pool config, #100 operation polling guard, #101 single configured
extractor, #102 resumable extraction agent + pending tool call API)
were silently absent from the merge commit.

This commit applies the 113-file resolution that was on disk:
- Config schema: agent_success_config singular form, normalize_legacy_
  config_shape migration, pool_size/pool_acquire_timeout fields.
- Service layer: _load_extractor_config singular accessors throughout
  the agent_success_evaluation services.
- Storage: AgentRunMixin + SQLiteAgentRunMixin wired into the BaseStorage/
  SQLiteStorage MRO; _migrate_agent_runs_schema + _migrate_pending_tool_
  calls_schema run on SQLite startup.
- Client: min_started_at param on _poll_operation_status; submitted_at
  threaded through the rerun_* paths.
- API: pending_tool_call_api import + router registration; resume
  scheduler bootstrap.
- Dashboard: _is_storage_configured guards on braintrust methods.
- Braintrust: fail-closed Fernet, transport-error wrapping, client.close()
  in finally blocks.

F1 additions (shadow_comparison_verdicts table, ShadowWinRateTrend,
escalation_rate, per-turn judge, drawer endpoint) and F3 additions
(sampler, concurrency, grade_on_demand) are preserved on top of main's
structure.

Sanity:
- import reflexio OK.
- tests/server/services/storage/: 239 passed.
yilu331 added a commit that referenced this pull request Jun 3, 2026
## What

Fixes 3 stale tests in the profile-extraction suite that have been
failing on `main`. **Test-only — no production code changes.** All three
asserted contracts that two earlier refactors superseded; production is
correct.

### Root cause
- **#101** (per-extractor pre-check):
`_collect_scoped_interactions_for_precheck` /
`_should_run_before_extraction` moved from a
*consolidated-across-all-extractors (list)* design to **per-extractor
(single config)**. Two tests still passed a `list`, yielding
`AttributeError: 'list' object has no attribute
'extraction_definition_prompt'`.
- **#107** (scope async-info tools per extractor kind): profile
extractors now expose `attach_pending_info_request`, not `ask_human`.
The third test mocked an `ask_human` call for a profile extractor, so no
tool ran → 0 pending calls.

### Changes
1. `test_collect_scoped_interactions_for_precheck_uses_extractor_scope`
— single-config contract; still asserts extractor-specific window
(`k=150`) + source (`["api"]`), and adds a non-matching-source →
empty-groups (should_skip) assertion.
2. `test_should_run_before_extraction_combines_all_extractor_criteria` →
renamed `..._includes_extractor_definition_and_override` —
single-config; asserts the extractor's definition + override both reach
the prompt. Dropped the obsolete cross-extractor assertion (criteria are
no longer combined).
3. `test_ask_human_is_org_scoped_and_run_still_finalizes` → renamed
`test_attach_pending_info_request_is_org_scoped_and_run_still_finalizes`
— models the current profile attach-to-existing flow: seeds an
org-scoped `ask_human` pending call, attaches via
`attach_pending_info_request`, asserts org scope + run finalize + the
run-tool-dependency edge (`pending_tool_call_ids` stays `[]` because
attach returns a synchronous `Completed`, not `AsyncAccepted` — verified
correct-per-production).

## Test
- The 3 targeted tests pass; both full files green (32 passed).
- ruff + pyright clean on the changed files.
- The `ask_human`/`AsyncAccepted` → `pending_tool_call_ids` population
path remains covered elsewhere (`test_tools.py`,
resumable-agent/resume-worker/e2e tests).

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Tests**
* Updated test suite for profile extraction to validate pending tool
call attachment workflow.
* Refactored pre-check behavior tests to operate on individual extractor
configurations.
* Adjusted test assertions to align with updated behavior expectations
for extraction and dependency tracking.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
@yilu331
yilu331 deleted the yilu/single-extractor-generation-service branch June 12, 2026 07:11
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