Skip to content

Propagate multi-user context (user_id, namespace_id, session_id) to Evolve MCP server#194

Draft
gjt-prog wants to merge 5 commits into
mainfrom
feat/evolve-multi-user-integration
Draft

Propagate multi-user context (user_id, namespace_id, session_id) to Evolve MCP server#194
gjt-prog wants to merge 5 commits into
mainfrom
feat/evolve-multi-user-integration

Conversation

@gjt-prog
Copy link
Copy Markdown
Collaborator

@gjt-prog gjt-prog commented May 6, 2026

Problem

ALTK-Evolve's MCP server already supports multi-user parameters (user_id, namespace_id, session_id) across get_guidelines, save_trajectory, and get_entities. However, the CUGA agent never sends these values — all Evolve interactions appear as "default" user with no tenant or session context.
Root causes:

  1. event_stream() in server/main.py receives user_id from the authenticated request but never assigns it to local_state.user_id
  2. CugaLiteState lacks a user_id field, so the subgraph cannot propagate user identity
  3. EvolveIntegration.get_guidelines() and save_trajectory() don't accept or forward any context parameters

Changes

File Change
server/main.py Populate local_state.user_id = user_id after service_scope is set
cuga_lite_graph.py Add user_id: Optional[str] to CugaLiteState shared keys
evolve/integration.py Extend get_guidelines() and save_trajectory() with optional user_id, namespace_id, session_id
cuga_lite_graph.py Pass context from CugaLiteState to get_guidelines() call
cuga_lite_node.py Pass context from AgentState to save_trajectory() calls
tests/test_integration.py 4 new tests for parameter propagation and backward compatibility

Behavior

  • Retrieval stays broadget_guidelines does not hard-filter by user/session; context is logged for attribution only
  • Writes are scopedsave_trajectory stamps user_id, namespace_id, session_id into entity metadata
  • Fully backward-compatible — all new parameters default to None and are omitted from the MCP payload when not set

Testing

29/29 unit tests pass (25 existing + 4 new).

Summary by CodeRabbit

  • New Features

    • Added per-user context tracking to enable user-scoped guidance and state management
    • Extended trajectory persistence to include richer contextual information (user identification, namespace, and session data) for better attribution and tracking
  • Tests

    • Added tests for multi-user context parameter handling

visahak and others added 2 commits April 29, 2026 15:53
…) to Evolve MCP

- Fix event_stream to populate local_state.user_id from auth context
- Add user_id field to CugaLiteState for subgraph propagation
- Extend EvolveIntegration.get_guidelines() and save_trajectory() signatures
- Wire call sites in cuga_lite_graph.py and cuga_lite_node.py
- Add 4 new tests for parameter propagation and backward compat (29/29 pass)
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 6, 2026

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d2ef0431-d6b6-49d8-8fac-396be38a052d

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR adds per-user context tracking to the Evolve integration by introducing a user_id field to CugaLiteState, threading it through guideline retrieval and trajectory saving, populating it from authenticated requests, and adding tests to verify parameter passing.

Changes

Multi-User Context in Evolve Integration

Layer / File(s) Summary
Data Shape
src/cuga/backend/cuga_graph/nodes/cuga_lite/cuga_lite_graph.py
CugaLiteState adds user_id: Optional[str] = None field for per-user context attribution.
Integration Interface
src/cuga/backend/evolve/integration.py
EvolveIntegration.get_guidelines() and save_trajectory() extended with optional user_id, namespace_id, and session_id parameters; both methods forward these identifiers to the Evolve backend.
Node Implementation
src/cuga/backend/cuga_graph/nodes/cuga_lite/cuga_lite_graph.py, src/cuga/backend/cuga_graph/nodes/cuga_lite/cuga_lite_node.py
Guidelines retrieval passes user context to get_guidelines(); trajectory saving branches on async mode and invokes save_trajectory() with extracted user identifiers from agent state.
Server State Initialization
src/cuga/backend/server/main.py
Authenticated user_id is propagated into graph state in event_stream().
Tests
src/cuga/backend/evolve/tests/test_integration.py
New test cases verify get_guidelines() and save_trajectory() accept and omit multi-user parameters correctly when provided or None.

Sequence Diagram

sequenceDiagram
    actor User
    participant Server as Server<br>(main.py)
    participant Graph as CugaLite Graph<br>(cuga_lite_graph.py)
    participant Node as CugaLite Node<br>(cuga_lite_node.py)
    participant Evolve as EvolveIntegration<br>(integration.py)

    User->>Server: Authenticated request
    Server->>Graph: Initialize state with user_id
    Graph->>Evolve: get_guidelines(task, user_id, namespace_id, session_id)
    Evolve-->>Graph: Guidelines response
    Graph->>Node: Process with user context
    Node->>Evolve: save_trajectory(..., user_id, namespace_id, session_id)
    Evolve-->>Node: Trajectory saved
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 A user context thread runs through,
From server state to Evolve's view,
Guidelines know whose voice they aid,
Each trajectory user-made,
Multi-tenant dreams come true! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 57.14% 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 pull request title directly and accurately summarizes the main objective: propagating multi-user context identifiers (user_id, namespace_id, session_id) to the Evolve MCP server. It is concise, specific, and reflects the primary change throughout the changeset.
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 unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/evolve-multi-user-integration

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.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (1)
src/cuga/backend/server/main.py (1)

1177-1181: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Persist user_id into the checkpoint on resume flows.

When resume is truthy, run_stream(..., state=None, resume=resume) ignores the patched local_state, so this assignment never reaches the graph state for resumed/HITL conversations. Older threads will still hit Evolve without user_id.

Suggested fix
     if local_state:
         from cuga.config import get_service_instance_id, get_tenant_id

         local_state.service_scope = {"tenant_id": get_tenant_id(), "instance_id": get_service_instance_id()}
         local_state.user_id = user_id  # Propagate authenticated user into graph state
+        if resume and thread_id:
+            run_agent.graph.update_state(
+                {"configurable": {"thread_id": thread_id}},
+                {
+                    "service_scope": local_state.service_scope,
+                    "user_id": local_state.user_id,
+                },
+            )
         if os.getenv("CUGA_DEMO_MODE") == "health" and not local_state.pi:
🤖 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 `@src/cuga/backend/server/main.py` around lines 1177 - 1181, The patched
local_state.user_id isn't propagated when resume flows are used because
run_stream is invoked with state=None; fix by ensuring the resume checkpoint
carries the same fields you set on local_state: when resume is truthy, copy
service_scope and user_id into the resume/checkpoint object (e.g., set
resume.checkpoint.service_scope = {"tenant_id": get_tenant_id(), "instance_id":
get_service_instance_id()} and resume.checkpoint.user_id = user_id) or
alternatively call run_stream with state=local_state instead of None; update the
logic around local_state, run_stream(..., state=...), and the resume/checkpoint
handling so resumed/HITL conversations include user_id.
🧹 Nitpick comments (1)
src/cuga/backend/evolve/tests/test_integration.py (1)

153-177: ⚡ Quick win

Add one resume-path regression test for context propagation.

These tests only prove EvolveIntegration shapes its args correctly. They would not catch event_stream(..., resume=...) dropping user_id before Evolve is reached, which is the easiest place for this feature to regress.

🤖 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 `@src/cuga/backend/evolve/tests/test_integration.py` around lines 153 - 177,
Add a regression test that exercises the resume-path context propagation by
calling event_stream(..., resume=...) with user_id/namespace_id/session_id set
and asserting EvolveIntegration._call_tool receives those fields; specifically,
in src/cuga/backend/evolve/tests/test_integration.py add an async test (patching
EvolveIntegration._call_tool as AsyncMock and settings similar to existing
tests) that calls event_stream(...,
resume={"user_id":"user-1","namespace_id":"tenant-a","session_id":"thread-99"})
or otherwise simulates the resume flow and then verifies
mock_call_tool.assert_called_once_with("get_guidelines", {"task": "...",
"user_id":"user-1","namespace_id":"tenant-a","session_id":"thread-99"}) so any
regression that drops user context in event_stream's resume handling is caught.
🤖 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.

Outside diff comments:
In `@src/cuga/backend/server/main.py`:
- Around line 1177-1181: The patched local_state.user_id isn't propagated when
resume flows are used because run_stream is invoked with state=None; fix by
ensuring the resume checkpoint carries the same fields you set on local_state:
when resume is truthy, copy service_scope and user_id into the resume/checkpoint
object (e.g., set resume.checkpoint.service_scope = {"tenant_id":
get_tenant_id(), "instance_id": get_service_instance_id()} and
resume.checkpoint.user_id = user_id) or alternatively call run_stream with
state=local_state instead of None; update the logic around local_state,
run_stream(..., state=...), and the resume/checkpoint handling so resumed/HITL
conversations include user_id.

---

Nitpick comments:
In `@src/cuga/backend/evolve/tests/test_integration.py`:
- Around line 153-177: Add a regression test that exercises the resume-path
context propagation by calling event_stream(..., resume=...) with
user_id/namespace_id/session_id set and asserting EvolveIntegration._call_tool
receives those fields; specifically, in
src/cuga/backend/evolve/tests/test_integration.py add an async test (patching
EvolveIntegration._call_tool as AsyncMock and settings similar to existing
tests) that calls event_stream(...,
resume={"user_id":"user-1","namespace_id":"tenant-a","session_id":"thread-99"})
or otherwise simulates the resume flow and then verifies
mock_call_tool.assert_called_once_with("get_guidelines", {"task": "...",
"user_id":"user-1","namespace_id":"tenant-a","session_id":"thread-99"}) so any
regression that drops user context in event_stream's resume handling is caught.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8df50321-736b-47c9-9b55-6498a79845ea

📥 Commits

Reviewing files that changed from the base of the PR and between ce02fb4 and 3f8d0dd.

📒 Files selected for processing (5)
  • src/cuga/backend/cuga_graph/nodes/cuga_lite/cuga_lite_graph.py
  • src/cuga/backend/cuga_graph/nodes/cuga_lite/cuga_lite_node.py
  • src/cuga/backend/evolve/integration.py
  • src/cuga/backend/evolve/tests/test_integration.py
  • src/cuga/backend/server/main.py

@sami-marreed sami-marreed marked this pull request as draft May 12, 2026 12:51
gjt-prog added 3 commits May 15, 2026 13:44
Resolved conflict in cuga_lite_graph.py by:
- Keeping filesystem/sandbox tools setup from main
- Using refactored Evolve integration (build_evolve_special_instructions_extension)
- Removing old EvolveIntegration.get_guidelines() implementation
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