Skip to content

feat: Add conversation variable persistence layer #4

Open
tomerqodo wants to merge 21 commits into
copilot_full_base_feat_add_conversation_variable_persistence_layer__pr4from
copilot_full_head_feat_add_conversation_variable_persistence_layer__pr4
Open

feat: Add conversation variable persistence layer #4
tomerqodo wants to merge 21 commits into
copilot_full_base_feat_add_conversation_variable_persistence_layer__pr4from
copilot_full_head_feat_add_conversation_variable_persistence_layer__pr4

Conversation

@tomerqodo

Copy link
Copy Markdown

Benchmark PR from agentic-review-benchmarks#4

laipz8200 and others added 21 commits January 25, 2026 12:04
… 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.
Copilot AI review requested due to automatic review settings January 25, 2026 22:12

Copilot AI 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.

Pull request overview

This PR moves conversation-variable persistence out of VariableAssigner nodes and into a GraphEngine layer, so updated conversation variables can be persisted based on node run results rather than via direct DB calls inside node execution.

Changes:

  • Added ConversationVariablePersistenceLayer to persist updated conversation variables on NodeRunSucceededEvent for VARIABLE_ASSIGNER.
  • Removed conversation-variable DB persistence from VariableAssigner v1/v2 nodes and updated unit tests to assert “updated variables” in process_data.
  • Updated the read-only variable pool interface to accept a selector sequence (aligning wrappers/protocols/mocks).

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
api/core/app/layers/conversation_variable_persist_layer.py New GraphEngine layer that persists conversation variables based on assigner updated-variable metadata.
api/core/app/apps/advanced_chat/app_runner.py Registers the new persistence layer in the advanced chat workflow execution pipeline.
api/core/workflow/nodes/variable_assigner/v1/node.py Removes in-node DB persistence; relies on process_data metadata for downstream persistence.
api/core/workflow/nodes/variable_assigner/v2/node.py Removes in-node DB persistence; relies on process_data metadata for downstream persistence.
api/services/conversation_variable_updater.py Provides a services-level updater/factory used by the new layer and conversation service.
api/services/conversation_service.py Switches to the services-level conversation_variable_updater_factory import.
api/core/workflow/runtime/read_only_wrappers.py Updates read-only wrapper get() signature to take selector sequences.
api/core/workflow/runtime/graph_runtime_state_protocol.py Updates protocol get() signature to take selector sequences.
api/tests/unit_tests/core/app/layers/test_conversation_variable_persist_layer.py Adds unit tests for the new conversation variable persistence layer behavior.
api/tests/unit_tests/core/app/layers/test_pause_state_persist_layer.py Updates test mock variable-pool interface to match selector-based get().
api/tests/unit_tests/core/workflow/nodes/variable_assigner/v1/test_variable_assigner_v1.py Updates tests to validate assigner “updated variables” in node run process_data.
api/tests/unit_tests/core/workflow/nodes/variable_assigner/v2/test_variable_assigner_v2.py Adds a test ensuring node factory creates the v2 assigner node.
api/core/workflow/nodes/node_factory.py Minor formatting change.
api/.importlinter Removes no-longer-needed ignore for the old variable assigner DB import path.
Comments suppressed due to low confidence (1)

api/services/conversation_variable_updater.py:23

  • ConversationVariableUpdaterImpl.update() creates a SQLAlchemy Session but never closes it. This will leak DB connections over time under load. Use a context manager (with Session(...) as session:) or ensure session.close() runs in a finally block (and rollback on exceptions as appropriate).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +38 to +53
for item in updated_variables:
selector = item.selector
if len(selector) < 2:
logger.warning("Conversation variable selector invalid. selector=%s", selector)
continue
if selector[0] != CONVERSATION_VARIABLE_NODE_ID:
continue
variable = self.graph_runtime_state.variable_pool.get(selector)
if not isinstance(variable, Variable):
logger.warning(
"Conversation variable not found in variable pool. selector=%s",
selector,
)
continue
self._conversation_variable_updater.update(conversation_id=conversation_id, variable=variable)
self._conversation_variable_updater.flush()

Copilot AI Jan 25, 2026

Copy link

Choose a reason for hiding this comment

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

flush() is called inside the per-variable loop. This defeats the batching semantics described by ConversationVariableUpdater (and can add unnecessary commits/round-trips). Consider calling update() for each conversation variable, then call flush() once after the loop (or in on_graph_end) only if at least one update occurred.

Copilot uses AI. Check for mistakes.
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.

3 participants