Skip to content

fix: shallow-copy flow context and model_middleware instead of deepcopy - #1695

Open
Christian-Sidak wants to merge 1 commit into
RailtownAI:mainfrom
Christian-Sidak:fix/issue-1675
Open

Christian-Sidak wants to merge 1 commit into
RailtownAI:mainfrom
Christian-Sidak:fix/issue-1675

Conversation

@Christian-Sidak

Copy link
Copy Markdown
Contributor

Summary

  • connection.py: replace deepcopy(flow._context) with dict(flow._context) to give per-run isolation without deep-copying user-supplied values
  • flow.py update_context: replace deepcopy(self) with copy.copy(self) and a shallow dict merge, avoiding a full deep-copy of the Flow including callbacks on every call
  • model_invoker.py create_with_llm_observe: replace deepcopy(middleware) with list(middleware), passing instances through by reference so stateful middleware accumulates state correctly across LLM calls

The dict(...) shallow copy preserves the existing contract: rt.context.put(...) inside a run writes to a per-run copy of the top-level dict, so it cannot leak into flow._context or the next run.

Test plan

  • test_flow_invoke_passes_non_copyable_context_object: threading.Lock in context no longer raises TypeError
  • test_flow_invoke_context_value_is_same_object_not_clone: run sees the callers exact object (is check)
  • test_flow_update_context_non_copyable_object: update_context with a lock in context no longer raises
  • test_flow_update_context_does_not_modify_original: original Flow is unchanged after update_context
  • test_flow_context_mutations_isolated_between_runs: rt.context.put inside a run does not bleed into flow._context
  • test_create_with_llm_observe_preserves_stateful_middleware: counting middleware accumulates state across two invoke calls (reads 2, not 0)
  • test_create_with_llm_observe_non_copyable_middleware_does_not_crash: middleware holding a threading.Lock no longer raises TypeError
  • Existing test suite passes: all 30 unit tests and 21 integration flow tests green locally

Fixes #1675

@Amir-R25
Amir-R25 self-requested a review October 20, 2026 23:27
deepcopy in two hot paths crashed on non-copyable objects (locks, DB
connections, sockets) and silently cloned stateful objects so writes
never reached the caller.

- connection.py: dict(flow._context) gives per-run isolation without
  deep-copying user values
- flow.py update_context: copy.copy(self) + shallow dict merge avoids
  deep-copying the whole Flow including callbacks
- model_invoker.py create_with_llm_observe: list(middleware) passes
  instances through by reference, matching MiddlewareChain behavior,
  so stateful middleware (rate limiters, counters) accumulates state
  correctly across LLM calls

Fixes RailtownAI#1675

Signed-off-by: Christian-Sidak <61099993+Christian-Sidak@users.noreply.github.com>

This branch has not been deployed

No deployments
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.

Flow context and agent model_middleware are deep-copied, crashing on non-copyable objects and silently dropping state

1 participant