Skip to content

feat: Add conversation variable persistence layer - #4

Open
tomerqodo wants to merge 21 commits into
cursor_only-issues-20260113-cursor_completion_base_feat_add_conversation_variable_persistence_layer__pr167from
cursor_only-issues-20260113-cursor_completion_head_feat_add_conversation_variable_persistence_layer__pr167
Open

feat: Add conversation variable persistence layer #4
tomerqodo wants to merge 21 commits into
cursor_only-issues-20260113-cursor_completion_base_feat_add_conversation_variable_persistence_layer__pr167from
cursor_only-issues-20260113-cursor_completion_head_feat_add_conversation_variable_persistence_layer__pr167

Conversation

@tomerqodo

@tomerqodo tomerqodo commented Jan 20, 2026

Copy link
Copy Markdown

Benchmark PR from qodo-benchmark#167


Note

Introduces centralized persistence for conversation variables and removes DB coupling from nodes.

  • Add ConversationVariablePersistenceLayer to capture VARIABLE_ASSIGNER successes and persist updated conversation variables; wire into AdvancedChatAppRunner
  • Implement services/conversation_variable_updater (batched update + flush) and use it in the new layer and ConversationService
  • Refactor v1/v2 VariableAssignerNode to stop direct DB updates; emit updated_variables only and simplify constructors
  • Update read-only runtime interfaces: ReadOnlyVariablePool.get now accepts Sequence[str]; adjust wrappers and tests
  • Minor factory/node cleanup; remove obsolete impl; tweak importlinter config
  • Add/adjust unit tests for the new layer, pause-state mocks, variable assigner behavior, and node factory creation

Written by Cursor Bugbot for commit 84196e6. Configure here.

laipz8200 and others added 21 commits January 5, 2026 13:19
… factory to pass the ConversationVariableUpdater factory (the only non-VariablePool dependency), plus a unit test to verify the injection path.

- `api/core/workflow/nodes/variable_assigner/v2/node.py` adds a kw-only `conv_var_updater_factory` dependency (defaulting to `conversation_variable_updater_factory`) and stores it for use in `_run`.
- `api/core/workflow/nodes/node_factory.py` now injects the factory when creating VariableAssigner v2 nodes.
- `api/tests/unit_tests/core/workflow/nodes/variable_assigner/v2/test_variable_assigner_v2.py` adds a test asserting the factory is injected.

Tests not run.

Next steps (optional):
1) `make lint`
2) `make type-check`
3) `uv run --project api --dev dev/pytest/pytest_unit_tests.sh`
…ructor args.

- `api/core/workflow/nodes/node_factory.py` now directly instantiates `VariableAssignerNode` with the injected dependency, and uses a direct call for all other nodes.

No tests run.
Add a new command for GraphEngine to update a group of variables. This command takes a group of variable selectors and new values. When the engine receives the command, it will update the corresponding variable in the variable pool. If it does not exist, it will add it; if it does, it will overwrite it. Both behaviors should be treated the same and do not need to be distinguished.
…be-kanban 0941477f)

Create a new persistence layer for the Graph Engine. This layer receives a ConversationVariableUpdater upon initialization, which is used to persist the received ConversationVariables to the database. It can retrieve the currently processing ConversationId from the engine's variable pool. It captures the successful execution event of each node and determines whether the type of this node is VariableAssigner(v1 and v2). If so, it retrieves the variable name and value that need to be updated from the node's outputs. This layer is only used in the Advanced Chat. It should be placed outside of Core.Workflow package.
…rs/conversation_variable_persist_layer.py` to satisfy SIM118

- chore(lint): run `make lint` (passes; warnings about missing RECORD during venv package uninstall)
- chore(type-check): run `make type-check` (fails: 1275 errors for missing type stubs like `opentelemetry`, `click`, `sqlalchemy`, `flask`, `pydantic`, `pydantic_settings`)
…tType validation and casting

- test(graph-engine): update VariableUpdate usages to include value_type in command tests
… drop common_helpers usage

- refactor(variable-assigner-v2): inline updated variable payload and drop common_helpers usage

Tests not run.
…n and remove value type validation

- test(graph-engine): update UpdateVariablesCommand tests to pass concrete Variable instances
- fix(graph-engine): align VariableUpdate values with selector before adding to VariablePool

Tests not run.
…e handling for v1/v2 process_data

- refactor(app-layer): read updated variables from process_data in conversation variable persistence layer
- test(app-layer): adapt persistence layer tests to use common_helpers updated-variable payloads

Tests not run.
…fter venv changes)

- chore(type-check): run `make type-check` (fails: 1275 missing type stubs across dependencies)

Details:
- `make lint` fails with `ModuleNotFoundError: No module named 'dotenv_linter.cli'`.
- `make type-check` fails with missing stubs for `opentelemetry`, `click`, `sqlalchemy`, `flask`, `pydantic`, `pydantic_settings`, etc.
…ableUnion and remove value type validation"

This reverts commit 5ebc87a.
…h SegmentType validation and casting"

This reverts commit 3edd525.
…y out of core.workflow into `api/services/conversation_variable_updater.py`

- refactor(app): update advanced chat app runner and conversation service to import the new updater factory

Tests not run.
…-linter module missing)

- chore(type-check): run `make type-check` (fails: 1275 missing type stubs)

Details:
- `make lint` reports: `No matches for ignored import core.workflow.nodes.variable_assigner.common.impl -> extensions.ext_database` and ends with `ModuleNotFoundError: No module named 'dotenv_linter.cli'`.
- `make type-check` fails with missing type stubs for `opentelemetry`, `click`, `sqlalchemy`, `flask`, `pydantic`, `pydantic_settings`, etc.

@cursor cursor 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.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

conv_var_updater.flush()
updated_variables = [common_helpers.variable_to_processed_data(assigned_variable_selector, updated_variable)]

updated_variables = [common_helpers.variable_to_processed_data(assigned_variable_selector, original_variable)]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Wrong variable passed stores old value as new

High Severity

The code passes original_variable to variable_to_processed_data instead of updated_variable. The variable_to_processed_data function extracts seg.value to populate the new_value field. Since original_variable contains the value before the update, the process_data will incorrectly report the old value as the new value. The unit tests explicitly expect new_value to contain the updated value (e.g., input_variable.value), so these tests will fail.

Fix in Cursor Fix in Web

if not row:
raise ConversationVariableNotFoundError("conversation variable not found in the database")
row.data = variable.model_dump_json()
session.commit()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Flush doesn't clear pending causing duplicate writes

Medium Severity

The flush() method processes all items in _pending_updates but never clears the list afterward. Combined with the persistence layer calling flush() inside the loop (after each update() call), this causes the same variables to be written to the database multiple times. For n variables, this results in O(n²) database operations instead of O(n), and earlier variables get re-written repeatedly.

Additional Locations (1)

Fix in Cursor Fix in Web

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