Skip to content

feat: Add conversation variable persistence layer #89

Open
tomerqodo wants to merge 21 commits intogreptile_combined_20260121_qodo_grep_cursor_copilot_1_base_feat_add_conversation_variable_persistence_layer__pr429from
greptile_combined_20260121_qodo_grep_cursor_copilot_1_head_feat_add_conversation_variable_persistence_layer__pr429
Open

feat: Add conversation variable persistence layer #89
tomerqodo wants to merge 21 commits intogreptile_combined_20260121_qodo_grep_cursor_copilot_1_base_feat_add_conversation_variable_persistence_layer__pr429from
greptile_combined_20260121_qodo_grep_cursor_copilot_1_head_feat_add_conversation_variable_persistence_layer__pr429

Conversation

@tomerqodo
Copy link
Copy Markdown

Benchmark PR from qodo-benchmark#429

laipz8200 and others added 21 commits January 21, 2026 15:54
… 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.
@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Jan 21, 2026

Greptile Summary

Refactored conversation variable persistence to use a dedicated graph engine layer instead of direct database updates in variable assigner nodes, improving separation of concerns and architectural cleanliness.

Key changes:

  • Extracted ConversationVariableUpdaterImpl from core.workflow to new services.conversation_variable_updater module
  • Created ConversationVariablePersistenceLayer that listens to NodeRunSucceededEvent and persists conversation variable updates
  • Refactored VariableAssignerNode (v1 and v2) to store updated variables in process_data instead of directly updating database
  • Updated ReadOnlyVariablePool protocol to use selector sequence parameter instead of separate node_id/key parameters
  • Added comprehensive test coverage for the new persistence layer

Issues found:

  • Critical: ConversationVariableUpdaterImpl.update() creates SQLAlchemy session without context manager (line 18), violating style guide rule feat: dark theme icon support  #6 and causing resource leaks. The original implementation correctly used with Session(db.engine) as session:

Confidence Score: 2/5

  • Critical resource leak bug in database session management must be fixed before merge
  • Score reflects one critical bug that will cause database connection leaks in production. The architectural refactoring is sound and well-tested, but the session management regression from the original implementation makes this unsafe to merge as-is
  • Pay close attention to api/services/conversation_variable_updater.py which has a critical session management bug

Important Files Changed

Filename Overview
api/services/conversation_variable_updater.py New service file with critical session management bug - missing context manager
api/core/app/layers/conversation_variable_persist_layer.py New persistence layer for conversation variables, handles NodeRunSucceededEvent filtering and updates
api/core/app/apps/advanced_chat/app_runner.py Added ConversationVariablePersistenceLayer to the graph engine layers
api/core/workflow/nodes/variable_assigner/v1/node.py Refactored to use process_data with common_helpers instead of direct database updates
api/core/workflow/nodes/variable_assigner/v2/node.py Refactored to use process_data with common_helpers instead of direct database updates

Sequence Diagram

sequenceDiagram
    participant User
    participant AppRunner as AdvancedChatAppRunner
    participant WorkflowEntry
    participant GraphEngine
    participant PersistLayer as ConversationVariablePersistenceLayer
    participant VariableAssigner as VariableAssignerNode (v1/v2)
    participant Updater as ConversationVariableUpdaterImpl
    participant DB as Database

    User->>AppRunner: run()
    AppRunner->>WorkflowEntry: create WorkflowEntry
    AppRunner->>Updater: conversation_variable_updater_factory()
    Updater-->>AppRunner: updater instance
    AppRunner->>PersistLayer: new ConversationVariablePersistenceLayer(updater)
    AppRunner->>GraphEngine: layer(persistence_layer)
    
    GraphEngine->>VariableAssigner: execute node
    VariableAssigner->>VariableAssigner: _run()
    VariableAssigner->>VariableAssigner: update variable in variable_pool
    VariableAssigner->>VariableAssigner: set updated_variables in process_data
    VariableAssigner-->>GraphEngine: NodeRunSucceededEvent with process_data
    
    GraphEngine->>PersistLayer: on_event(NodeRunSucceededEvent)
    PersistLayer->>PersistLayer: check if VARIABLE_ASSIGNER node
    PersistLayer->>PersistLayer: extract updated_variables from process_data
    PersistLayer->>PersistLayer: filter for CONVERSATION_VARIABLE_NODE_ID
    
    loop For each conversation variable
        PersistLayer->>PersistLayer: get variable from variable_pool
        PersistLayer->>Updater: update(conversation_id, variable)
        Updater->>DB: SELECT ConversationVariable
        Updater->>DB: UPDATE data field
        Updater->>DB: commit()
        PersistLayer->>Updater: flush()
    end
    
    PersistLayer-->>GraphEngine: continue execution
Loading

Copy link
Copy Markdown

@greptile-apps greptile-apps Bot left a comment

Choose a reason for hiding this comment

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

14 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

Comment on lines +18 to +23
session = Session(db.engine)
row = session.scalar(stmt)
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.

syntax: session created without context manager, violates style guide rule #6 and causes resource leak

Suggested change
session = Session(db.engine)
row = session.scalar(stmt)
if not row:
raise ConversationVariableNotFoundError("conversation variable not found in the database")
row.data = variable.model_dump_json()
session.commit()
with Session(db.engine, expire_on_commit=False) as session:
row = session.scalar(stmt)
if not row:
raise ConversationVariableNotFoundError("conversation variable not found in the database")
row.data = variable.model_dump_json()
session.commit()

Context Used: Context from dashboard - CLAUDE.md (source)

Prompt To Fix With AI
This is a comment left during a code review.
Path: api/services/conversation_variable_updater.py
Line: 18:23

Comment:
**syntax:** session created without context manager, violates style guide rule #6 and causes resource leak

```suggestion
        with Session(db.engine, expire_on_commit=False) as session:
            row = session.scalar(stmt)
            if not row:
                raise ConversationVariableNotFoundError("conversation variable not found in the database")
            row.data = variable.model_dump_json()
            session.commit()
```

**Context Used:** Context from `dashboard` - CLAUDE.md ([source](https://app.greptile.com/review/custom-context?memory=a5f96311-5bd1-49f2-9828-2ee0c089c012))

How can I resolve this? If you propose a fix, please make it concise.

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