Skip to content

fix: Chain.connect() predicates now gate link execution#50

Merged
JoshuaWink merged 4 commits intomainfrom
copilot/clarify-predicates-in-chain-connect
Mar 1, 2026
Merged

fix: Chain.connect() predicates now gate link execution#50
JoshuaWink merged 4 commits intomainfrom
copilot/clarify-predicates-in-chain-connect

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Mar 1, 2026

  • Fix Chain.run() to respect predicate conditions from connect(), skipping target links when all incoming conditions are False
  • Update Chain.connect() docstring to document gating semantics clearly
  • Loosen predicate type annotation from Context[TInput] to Context[Any] to correctly handle typed chains where context type evolves between links
  • Track executed_links set in run() so predicates are only evaluated when their source link has already run (prevents spurious skips for out-of-order or self-referential connections)
  • Tighten incoming type annotation to Dict[str, List[Tuple[str, Callable[[Context[Any]], bool]]]]
  • Remove unused Dict, List, Callable imports from test_chain.py
  • All 74 tests pass
Original prompt

This section details on the original issue you should resolve

<issue_title>Clarification: Predicates don't gate/prevent link execution in linear chains</issue_title>
<issue_description>## Issue: Clarification on Predicates in Chain.connect()

We discovered that Chain.connect() predicates do NOT prevent link execution in linear pipelines. All links execute regardless of predicate conditions.

Expected Behavior

When connecting links with a predicate that returns False, the target link should be skipped:

chain.connect('link1', 'link2', lambda ctx: some_condition)
# If some_condition is False, link2 should NOT run

Actual Behavior

All links run regardless of predicate evaluation. The predicate has no gating effect.

Workaround

Conditional logic must be implemented INSIDE Links using return statements:

class MyLink(Link):
    async def call(self, ctx):
        if skip_condition:
            return ctx  # Skip operation
        # ... proceed with logic ...
        return ctx

Questions

  1. Is this intentional design (predicates are just metadata/documentation)?
  2. Are predicates intended for DAG-based routing only, not linear chain gating?
  3. Should documentation clarify this behavior?

Context

We're building a multi-calendar scheduling system using CodeUChain. We need to prevent event creation when availability slots are empty. Initially we tried using predicates on Chain.connect() but discovered this pattern doesn't work for flow control in linear chains.

Our implementation includes:

  • 8 Links for calendar operations
  • 3 Chains for workflows (schedule_meeting, daily_sync, cross_calendar_move)
  • Comprehensive unit tests (21 tests, all passing)

Reference: https://github.com/JoshuaWink/random-research-journal/blob/main/implementations/calendar-workflow/FIX_AVAILABILITY_GATING.md

Suggested Enhancement

Could CodeUChain:

  1. Add documentation clarifying the intended purpose of predicates?
  2. Explain if this is design-as-intended (metadata only) or a limitation?
  3. Provide guidance on the recommended pattern for conditional link execution?

Environment

  • CodeUChain version: 1.0.0+
  • Python: 3.11
  • Use case: Linear workflow with conditional link execution
    </issue_description>

Comments on the Issue (you are @copilot in this section)


🔒 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.

Copilot AI changed the title [WIP] Clarify predicate behavior in Chain.connect() fix: Chain.connect() predicates now gate link execution Mar 1, 2026
@JoshuaWink JoshuaWink marked this pull request as ready for review March 1, 2026 00:19
@JoshuaWink JoshuaWink requested a review from Copilot March 1, 2026 00:19
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

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 asyncio imports 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:
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@copilot apply changes based on this feedback

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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.

Comment on lines +73 to +77
# 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
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

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

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).

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@copilot apply changes based on this feedback

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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.

@JoshuaWink JoshuaWink merged commit 7879a1c into main Mar 1, 2026
1 check passed
@JoshuaWink JoshuaWink deleted the copilot/clarify-predicates-in-chain-connect branch March 1, 2026 00:57
JoshuaWink added a commit that referenced this pull request Mar 2, 2026
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.

Clarification: Predicates don't gate/prevent link execution in linear chains

3 participants