fix: Chain.connect() predicates now gate link execution#50
Conversation
Co-authored-by: JoshuaWink <[email protected]>
There was a problem hiding this comment.
Pull request overview
Fixes Python Chain.connect() so stored predicates actually control whether a target link executes during Chain.run(), aligning behavior with documented conditional-chain semantics and adding regression tests.
Changes:
- Implemented predicate-gated link execution in
Chain.run()by building an incoming-conditions map and skipping links when all incoming predicates are false. - Expanded/strengthened chain conditional execution tests (including explicit “skipped link did not run” assertions).
- Minor test cleanup by moving repeated
asyncioimports to module scope.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| packages/python/codeuchain/core/chain.py | Builds an incoming predicate map from _connections and uses it to skip gated links whose predicates all evaluate false; updates connect() docstring to define gating semantics. |
| packages/python/tests/test_chain.py | Adds targeted tests covering true/false predicates and unconnected links always running; strengthens the original conditional execution test and removes repeated inline imports. |
| @@ -37,7 +37,16 @@ def add_link(self, link: Link[TInput, TOutput], name: Optional[str] = None) -> N | |||
| self._links[link_name] = link | |||
|
|
|||
| def connect(self, source: str, target: str, condition: Callable[[Context[TInput]], bool]) -> None: | |||
There was a problem hiding this comment.
connect() predicates are typed as Callable[[Context[TInput]], bool], but run() evaluates them against the current ctx which evolves after each link call. This makes the predicate type too narrow for typed chains and can force type ignores/invariance issues; consider loosening the predicate to accept Context[Any]/Context[object] (and matching internal annotations) to preserve opt-in typing and type evolution.
There was a problem hiding this comment.
Done in db2f9e5. The predicate parameter on connect() is now typed as Callable[[Context[Any]], bool], and the incoming map in run() uses the same Context[Any] annotation, so predicates evaluate cleanly regardless of how the context type has evolved at each stage of the chain.
| # If this link has incoming connections, skip it unless at | ||
| # least one predicate evaluates to True. | ||
| if name in incoming: | ||
| if not any(cond(ctx) for _, cond in incoming[name]): | ||
| continue |
There was a problem hiding this comment.
The gating logic ignores the recorded source (it’s unpacked as _), so a target link is skipped solely based on predicates at the moment it is reached, even if the connected source link has not executed yet (or is the target itself). This can cause links to never run in cases like self-connections or out-of-order connections; consider using source to only evaluate predicates from sources that have already executed (or otherwise clarify/adjust the semantics).
There was a problem hiding this comment.
Fixed in db2f9e5. run() now maintains an executed_links: Set[str] and only considers predicates whose source is already in that set. If no connected source has executed yet the target is skipped, making out-of-order and self-referential connections safe.
There was a problem hiding this comment.
This was already addressed in db2f9e5. run() maintains an executed_links: Set[str] and filters incoming predicates to only those whose source is in that set before deciding whether to skip a target link.
Co-authored-by: Copilot <[email protected]>
…urces Co-authored-by: JoshuaWink <[email protected]>
Chain.run()to respect predicate conditions fromconnect(), skipping target links when all incoming conditions are FalseChain.connect()docstring to document gating semantics clearlyContext[TInput]toContext[Any]to correctly handle typed chains where context type evolves between linksexecuted_linksset inrun()so predicates are only evaluated when their source link has already run (prevents spurious skips for out-of-order or self-referential connections)incomingtype annotation toDict[str, List[Tuple[str, Callable[[Context[Any]], bool]]]]Dict,List,Callableimports fromtest_chain.pyOriginal prompt
🔒 GitHub Advanced Security automatically protects Copilot coding agent pull requests. You can protect all pull requests by enabling Advanced Security for your repositories. Learn more about Advanced Security.