feat(gds-games): enhance DSL with new composition patterns and utilities#23
feat(gds-games): enhance DSL with new composition patterns and utilities#23rororowyourboat merged 4 commits intomainfrom
Conversation
…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.
There was a problem hiding this comment.
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(), andPattern.specialize()to support richer composition patterns. - Add
parallel(),multi_agent_composition(), anddiscover_patterns()convenience utilities and re-export them via the public API. - Add a comprehensive new test suite covering the new APIs and bump
ogsversion to0.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.
| # 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 |
There was a problem hiding this comment.
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.
| # 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) |
| @@ -99,6 +104,18 @@ class Flow(BaseModel, frozen=True): | |||
| target_port: str | |||
| direction: FlowDirection = FlowDirection.COVARIANT | |||
There was a problem hiding this comment.
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.
| # 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", | ||
| ), | ||
| ], | ||
| ) |
There was a problem hiding this comment.
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.
| # 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", | |
| ), | |
| ], | |
| ) |
| from pathlib import Path | ||
|
|
There was a problem hiding this comment.
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).
| from ogs.dsl.games import CovariantFunction, DecisionGame | ||
| from ogs.dsl.library import multi_agent_composition | ||
| from ogs.dsl.pattern import ( | ||
| ActionSpace, |
There was a problem hiding this comment.
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.
| ActionSpace, |
| from typing import Literal, overload | ||
|
|
||
| from ogs.dsl.composition import FeedbackLoop, Flow, SequentialComposition | ||
|
|
||
| from ogs.dsl.base import OpenGame | ||
| from ogs.dsl.composition import ( |
There was a problem hiding this comment.
reduce and FlowDirection are imported but not used anywhere in this module, which will fail Ruff (F401) in CI. Please remove these unused imports.
|
|
||
| from ogs.dsl.pattern import Pattern | ||
|
|
There was a problem hiding this comment.
Any is imported but not used in this module, which will fail Ruff (F401) in CI. Please remove the unused import.
Summary
This PR introduces several enhancements to the
gds-gamesDSL, improving composition capabilities and developer ergonomics.Key Changes
FeedbackFlow,ParallelComposition.from_list, andPattern.specialize.Flow.reactive_decision_agentflags.parallel(),multi_agent_composition(), anddiscover_patterns()helpers.gds-gamesto version 0.3.0.Testing