Skip to content

Comments

feat(gds-games): enhance DSL with new composition patterns and utilities#23

Merged
rororowyourboat merged 4 commits intomainfrom
feat/gds-games-dsl-extensions
Feb 23, 2026
Merged

feat(gds-games): enhance DSL with new composition patterns and utilities#23
rororowyourboat merged 4 commits intomainfrom
feat/gds-games-dsl-extensions

Conversation

@rororowyourboat
Copy link
Collaborator

Summary

This PR introduces several enhancements to the gds-games DSL, improving composition capabilities and developer ergonomics.

Key Changes

  • New Composition Primitives: Added FeedbackFlow, ParallelComposition.from_list, and Pattern.specialize.
  • Enhanced Flow Handling: Implemented object-reference coercion for Flow.
  • Reactive Agents: Added support for reactive_decision_agent flags.
  • Utility Functions: Added parallel(), multi_agent_composition(), and discover_patterns() helpers.
  • Version Bump: Updated gds-games to version 0.3.0.

Testing

  • Added 74 new tests covering all new DSL APIs and features.

…Composition.from_list, Pattern.specialize, reactive_decision_agent flags, parallel(), multi_agent_composition(), and discover_patterns()

Seven additive DSL extensions enabling N-agent patterns without manual string
wiring, configurable single-agent loops, and a pattern registry:

- Flow/FeedbackFlow: source_game/target_game now accept OpenGame instances,
  coerced to name strings at construction; FeedbackFlow subclass defaults
  direction to CONTRAVARIANT
- ParallelComposition.from_list(): compose a dynamic list of games in parallel
- Pattern.specialize(): derive a named pattern variant from a base, inheriting
  the game tree and inputs
- reactive_decision_agent(): @overload signatures + include_outcome /
  include_feedback flags for open-loop and 4-game variants
- parallel(): free function delegating to ParallelComposition.from_list()
- multi_agent_composition(): compose N open-loop agents in parallel, wire into
  a shared router, and auto-generate N×K contravariant feedback flows
- discover_patterns(): registry scan via importlib returning all Pattern
  instances from a package
Full coverage for the features added in the preceding commit:
- TestFlowObjectRef (7): Flow accepts OpenGame instances for source/target
- TestFeedbackFlow (6): CONTRAVARIANT default and object-ref coercion
- TestParallelFromList (7): ParallelComposition.from_list() including edge cases
- TestParallelFreeFunction (4): parallel() wrapper
- TestReactiveDecisionAgentFlags (9): all 4 flag combinations and return types
- TestMultiAgentComposition (11): structure, feedback wiring, and error paths
- TestPatternSpecialize (16): inheritance, overrides, and input handling
- TestDiscoverPatterns (14): registry scan, filtering, and error handling
Minor version bump for the 7 additive DSL extensions landed in this cycle.
No breaking changes; all existing APIs are preserved.
Copilot AI review requested due to automatic review settings February 23, 2026 15:34
@rororowyourboat rororowyourboat self-assigned this Feb 23, 2026
@rororowyourboat rororowyourboat merged commit 3e5b60f into main Feb 23, 2026
9 checks passed
@rororowyourboat rororowyourboat deleted the feat/gds-games-dsl-extensions branch February 23, 2026 15:38
Copy link

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

This PR extends the gds-games (OGS) DSL with additional composition utilities (feedback/parallel/multi-agent), adds pattern specialization and a filesystem-based pattern discovery registry, and bumps the package version.

Changes:

  • Add FeedbackFlow, ParallelComposition.from_list(), and Pattern.specialize() to support richer composition patterns.
  • Add parallel(), multi_agent_composition(), and discover_patterns() convenience utilities and re-export them via the public API.
  • Add a comprehensive new test suite covering the new APIs and bump ogs version to 0.3.0.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
packages/gds-games/tests/test_new_features.py Adds new tests covering all newly introduced DSL APIs/utilities.
packages/gds-games/ogs/registry.py Introduces discover_patterns() to import Pattern objects from a directory.
packages/gds-games/ogs/dsl/pattern.py Adds Pattern.specialize() for derived patterns sharing a game tree.
packages/gds-games/ogs/dsl/library.py Enhances reactive_decision_agent() configurability; adds parallel() and multi_agent_composition().
packages/gds-games/ogs/dsl/composition.py Adds OpenGame-ref coercion for Flow, introduces FeedbackFlow, and adds ParallelComposition.from_list().
packages/gds-games/ogs/dsl/init.py Re-exports new DSL APIs/utilities (incl. lazy library imports).
packages/gds-games/ogs/init.py Bumps version to 0.3.0 and re-exports FeedbackFlow + discover_patterns.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +101 to +109
# Register so intra-project imports resolve correctly
sys.modules[stem] = mod

try:
spec.loader.exec_module(mod) # type: ignore[union-attr]
except Exception:
# Silently skip modules that fail to import (missing deps, etc.)
sys.modules.pop(stem, None)
continue
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

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

Registering discovered modules in sys.modules under the bare stem name can clobber existing modules (or conflict across multiple discover_patterns() calls/directories) and leaves the entry behind on success. Consider namespacing the module key (e.g., a unique prefix based on the directory) and/or cleaning up sys.modules after extracting the Pattern, unless persistent registration is required.

Suggested change
# Register so intra-project imports resolve correctly
sys.modules[stem] = mod
try:
spec.loader.exec_module(mod) # type: ignore[union-attr]
except Exception:
# Silently skip modules that fail to import (missing deps, etc.)
sys.modules.pop(stem, None)
continue
# Register so intra-project imports (including circular imports during
# module initialisation) resolve correctly. Make this registration
# temporary to avoid clobbering existing modules or polluting
# sys.modules.
existed_before = stem in sys.modules
previous_mod = sys.modules.get(stem)
sys.modules[stem] = mod
try:
spec.loader.exec_module(mod) # type: ignore[union-attr]
except Exception:
# Silently skip modules that fail to import (missing deps, etc.).
# Restore any previous module under this name.
if existed_before:
sys.modules[stem] = previous_mod # type: ignore[assignment]
else:
sys.modules.pop(stem, None)
continue
else:
# Import succeeded; restore previous sys.modules state to avoid
# leaking temporary discovery modules.
if existed_before:
sys.modules[stem] = previous_mod # type: ignore[assignment]
else:
sys.modules.pop(stem, None)

Copilot uses AI. Check for mistakes.
Comment on lines 101 to 105
@@ -99,6 +104,18 @@ class Flow(BaseModel, frozen=True):
target_port: str
direction: FlowDirection = FlowDirection.COVARIANT
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

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

Flow now claims to accept OpenGame objects for source_game/target_game, but the field types are still str. This makes passing OpenGame values a static type error (and the new library.py code does so). Consider adding a TYPE_CHECKING __init__ overload (or adjusting annotations) so type checkers accept str | OpenGame while the stored attribute remains str.

Copilot uses AI. Check for mistakes.
Comment on lines +281 to +309
# innermost: Policy >> Reactive Decision
pol_rd = SequentialComposition(
name=f"{name} Policy+RD",
first=pol,
second=rd,
wiring=[
Flow(
source_game=pol,
source_port="Latest Policy",
target_game=rd,
target_port="Latest Policy",
),
],
)

# History >> (Policy >> RD)
hist_pol_rd = SequentialComposition(
name=f"{name} Core",
first=hist,
second=pol_rd,
wiring=[
Flow(
source_game=hist,
source_port="Latest History",
target_game=pol,
target_port="Latest History",
),
],
)
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

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

pol_rd (and the subsequent hist_pol_rd) are built unconditionally, but in the include_outcome=True branch neither is used to build the returned chain/loop. Consider constructing these only in the include_outcome=False path (or refactoring to reuse them) to avoid dead intermediate compositions and reduce confusion.

Suggested change
# innermost: Policy >> Reactive Decision
pol_rd = SequentialComposition(
name=f"{name} Policy+RD",
first=pol,
second=rd,
wiring=[
Flow(
source_game=pol,
source_port="Latest Policy",
target_game=rd,
target_port="Latest Policy",
),
],
)
# History >> (Policy >> RD)
hist_pol_rd = SequentialComposition(
name=f"{name} Core",
first=hist,
second=pol_rd,
wiring=[
Flow(
source_game=hist,
source_port="Latest History",
target_game=pol,
target_port="Latest History",
),
],
)
if not include_outcome:
# innermost: Policy >> Reactive Decision
pol_rd = SequentialComposition(
name=f"{name} Policy+RD",
first=pol,
second=rd,
wiring=[
Flow(
source_game=pol,
source_port="Latest Policy",
target_game=rd,
target_port="Latest Policy",
),
],
)
# History >> (Policy >> RD)
hist_pol_rd = SequentialComposition(
name=f"{name} Core",
first=hist,
second=pol_rd,
wiring=[
Flow(
source_game=hist,
source_port="Latest History",
target_game=pol,
target_port="Latest History",
),
],
)

Copilot uses AI. Check for mistakes.
Comment on lines +13 to +14
from pathlib import Path

Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

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

sys and types are imported but never used in this test module; CI runs ruff check packages/ so this will fail with unused-import errors. Remove these imports (or use them if needed).

Copilot uses AI. Check for mistakes.
from ogs.dsl.games import CovariantFunction, DecisionGame
from ogs.dsl.library import multi_agent_composition
from ogs.dsl.pattern import (
ActionSpace,
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

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

Several names imported from ogs.dsl appear unused in this file (e.g., context_builder, outcome, policy, reactive_decision). Since CI runs Ruff, please drop unused imports from this import block to avoid F401 failures.

Suggested change
ActionSpace,

Copilot uses AI. Check for mistakes.
Comment on lines +26 to +30
from typing import Literal, overload

from ogs.dsl.composition import FeedbackLoop, Flow, SequentialComposition

from ogs.dsl.base import OpenGame
from ogs.dsl.composition import (
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

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

reduce and FlowDirection are imported but not used anywhere in this module, which will fail Ruff (F401) in CI. Please remove these unused imports.

Copilot uses AI. Check for mistakes.
Comment on lines +23 to +25

from ogs.dsl.pattern import Pattern

Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

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

Any is imported but not used in this module, which will fail Ruff (F401) in CI. Please remove the unused import.

Copilot uses AI. Check for mistakes.
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.

1 participant