glitchlings 0.2.5__cp312-cp312-win_amd64.whl → 0.9.3__cp312-cp312-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- glitchlings/__init__.py +36 -17
- glitchlings/__main__.py +0 -1
- glitchlings/_zoo_rust/__init__.py +12 -0
- glitchlings/_zoo_rust.cp312-win_amd64.pyd +0 -0
- glitchlings/assets/__init__.py +180 -0
- glitchlings/assets/apostrofae_pairs.json +32 -0
- glitchlings/assets/ekkokin_homophones.json +2014 -0
- glitchlings/assets/hokey_assets.json +193 -0
- glitchlings/assets/lexemes/academic.json +1049 -0
- glitchlings/assets/lexemes/colors.json +1333 -0
- glitchlings/assets/lexemes/corporate.json +716 -0
- glitchlings/assets/lexemes/cyberpunk.json +22 -0
- glitchlings/assets/lexemes/lovecraftian.json +23 -0
- glitchlings/assets/lexemes/synonyms.json +3354 -0
- glitchlings/assets/mim1c_homoglyphs.json.gz.b64 +1064 -0
- glitchlings/assets/pipeline_assets.json +29 -0
- glitchlings/attack/__init__.py +53 -0
- glitchlings/attack/compose.py +299 -0
- glitchlings/attack/core.py +465 -0
- glitchlings/attack/encode.py +114 -0
- glitchlings/attack/metrics.py +104 -0
- glitchlings/attack/metrics_dispatch.py +70 -0
- glitchlings/attack/tokenization.py +157 -0
- glitchlings/auggie.py +283 -0
- glitchlings/compat/__init__.py +9 -0
- glitchlings/compat/loaders.py +355 -0
- glitchlings/compat/types.py +41 -0
- glitchlings/conf/__init__.py +41 -0
- glitchlings/conf/loaders.py +331 -0
- glitchlings/conf/schema.py +156 -0
- glitchlings/conf/types.py +72 -0
- glitchlings/config.toml +2 -0
- glitchlings/constants.py +59 -0
- glitchlings/dev/__init__.py +3 -0
- glitchlings/dev/docs.py +45 -0
- glitchlings/dlc/__init__.py +17 -3
- glitchlings/dlc/_shared.py +296 -0
- glitchlings/dlc/gutenberg.py +400 -0
- glitchlings/dlc/huggingface.py +37 -65
- glitchlings/dlc/prime.py +55 -114
- glitchlings/dlc/pytorch.py +98 -0
- glitchlings/dlc/pytorch_lightning.py +173 -0
- glitchlings/internal/__init__.py +16 -0
- glitchlings/internal/rust.py +159 -0
- glitchlings/internal/rust_ffi.py +432 -0
- glitchlings/main.py +123 -32
- glitchlings/runtime_config.py +24 -0
- glitchlings/util/__init__.py +29 -176
- glitchlings/util/adapters.py +65 -0
- glitchlings/util/keyboards.py +311 -0
- glitchlings/util/transcripts.py +108 -0
- glitchlings/zoo/__init__.py +47 -24
- glitchlings/zoo/assets/__init__.py +29 -0
- glitchlings/zoo/core.py +301 -167
- glitchlings/zoo/core_execution.py +98 -0
- glitchlings/zoo/core_planning.py +451 -0
- glitchlings/zoo/corrupt_dispatch.py +295 -0
- glitchlings/zoo/ekkokin.py +118 -0
- glitchlings/zoo/hokey.py +137 -0
- glitchlings/zoo/jargoyle.py +179 -274
- glitchlings/zoo/mim1c.py +106 -68
- glitchlings/zoo/pedant/__init__.py +107 -0
- glitchlings/zoo/pedant/core.py +105 -0
- glitchlings/zoo/pedant/forms.py +74 -0
- glitchlings/zoo/pedant/stones.py +74 -0
- glitchlings/zoo/redactyl.py +44 -175
- glitchlings/zoo/rng.py +259 -0
- glitchlings/zoo/rushmore.py +359 -116
- glitchlings/zoo/scannequin.py +18 -125
- glitchlings/zoo/transforms.py +386 -0
- glitchlings/zoo/typogre.py +76 -162
- glitchlings/zoo/validation.py +477 -0
- glitchlings/zoo/zeedub.py +33 -86
- glitchlings-0.9.3.dist-info/METADATA +334 -0
- glitchlings-0.9.3.dist-info/RECORD +80 -0
- {glitchlings-0.2.5.dist-info → glitchlings-0.9.3.dist-info}/entry_points.txt +1 -0
- glitchlings/zoo/_ocr_confusions.py +0 -34
- glitchlings/zoo/_rate.py +0 -21
- glitchlings/zoo/reduple.py +0 -169
- glitchlings-0.2.5.dist-info/METADATA +0 -490
- glitchlings-0.2.5.dist-info/RECORD +0 -27
- /glitchlings/{zoo → assets}/ocr_confusions.tsv +0 -0
- {glitchlings-0.2.5.dist-info → glitchlings-0.9.3.dist-info}/WHEEL +0 -0
- {glitchlings-0.2.5.dist-info → glitchlings-0.9.3.dist-info}/licenses/LICENSE +0 -0
- {glitchlings-0.2.5.dist-info → glitchlings-0.9.3.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"""Impure execution dispatch for Gaggle orchestration.
|
|
2
|
+
|
|
3
|
+
This module handles the actual execution of glitchling plans, including
|
|
4
|
+
Rust FFI calls and Python fallback execution. It is the impure counterpart
|
|
5
|
+
to core_planning.py.
|
|
6
|
+
|
|
7
|
+
**Design Philosophy:**
|
|
8
|
+
|
|
9
|
+
This module is explicitly *impure* - it invokes compiled Rust functions
|
|
10
|
+
and executes Python corruption functions with side effects. All Rust
|
|
11
|
+
dispatch for Gaggle orchestration flows through this module.
|
|
12
|
+
|
|
13
|
+
The separation allows:
|
|
14
|
+
- Pure planning logic to be tested without Rust
|
|
15
|
+
- Clear boundaries between plan construction and execution
|
|
16
|
+
- Mocking execution for integration tests
|
|
17
|
+
|
|
18
|
+
See AGENTS.md "Functional Purity Architecture" for full details.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
from typing import TYPE_CHECKING, cast
|
|
24
|
+
|
|
25
|
+
from glitchlings.internal.rust_ffi import compose_glitchlings_rust
|
|
26
|
+
|
|
27
|
+
if TYPE_CHECKING:
|
|
28
|
+
from .core_planning import ExecutionPlan, PipelineDescriptor
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
# ---------------------------------------------------------------------------
|
|
32
|
+
# Plan Execution
|
|
33
|
+
# ---------------------------------------------------------------------------
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def execute_plan(
|
|
37
|
+
text: str,
|
|
38
|
+
plan: ExecutionPlan,
|
|
39
|
+
master_seed: int,
|
|
40
|
+
) -> str:
|
|
41
|
+
"""Execute an orchestration plan against input text.
|
|
42
|
+
|
|
43
|
+
This function dispatches plan steps to either the Rust pipeline or
|
|
44
|
+
Python fallback execution as appropriate.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
text: Input text to transform.
|
|
48
|
+
plan: Execution plan from build_execution_plan().
|
|
49
|
+
master_seed: Master seed for Rust pipeline.
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
Transformed text after all plan steps complete.
|
|
53
|
+
"""
|
|
54
|
+
# Fast path: all glitchlings support pipeline
|
|
55
|
+
if plan.all_pipeline and plan.step_count == 1:
|
|
56
|
+
descriptors = list(plan.steps[0].descriptors)
|
|
57
|
+
return compose_glitchlings_rust(text, descriptors, master_seed)
|
|
58
|
+
|
|
59
|
+
# Hybrid path: mix of pipeline batches and individual fallbacks
|
|
60
|
+
result = text
|
|
61
|
+
|
|
62
|
+
for step in plan.steps:
|
|
63
|
+
if step.is_pipeline_step:
|
|
64
|
+
# Execute the batch through the Rust pipeline
|
|
65
|
+
descriptors = list(step.descriptors)
|
|
66
|
+
result = compose_glitchlings_rust(result, descriptors, master_seed)
|
|
67
|
+
elif step.fallback_glitchling is not None:
|
|
68
|
+
# Execute single glitchling via Python fallback
|
|
69
|
+
result = cast(str, step.fallback_glitchling.corrupt(result))
|
|
70
|
+
|
|
71
|
+
return result
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def execute_descriptors(
|
|
75
|
+
text: str,
|
|
76
|
+
descriptors: list[PipelineDescriptor],
|
|
77
|
+
master_seed: int,
|
|
78
|
+
) -> str:
|
|
79
|
+
"""Execute a list of pipeline descriptors through Rust.
|
|
80
|
+
|
|
81
|
+
This is a thin wrapper over compose_glitchlings_rust for cases
|
|
82
|
+
where the caller has already constructed descriptors directly.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
text: Input text to transform.
|
|
86
|
+
descriptors: Pipeline descriptors for each glitchling.
|
|
87
|
+
master_seed: Master seed for Rust pipeline.
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
Transformed text.
|
|
91
|
+
"""
|
|
92
|
+
return compose_glitchlings_rust(text, descriptors, master_seed)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
__all__ = [
|
|
96
|
+
"execute_plan",
|
|
97
|
+
"execute_descriptors",
|
|
98
|
+
]
|
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
"""Pure orchestration planning functions.
|
|
2
|
+
|
|
3
|
+
This module contains the deterministic, side-effect-free logic for building
|
|
4
|
+
glitchling execution plans. It converts glitchling metadata into structured
|
|
5
|
+
plans that can be executed by the impure dispatch layer.
|
|
6
|
+
|
|
7
|
+
**Design Philosophy:**
|
|
8
|
+
|
|
9
|
+
All functions in this module are *pure* - they perform plan construction
|
|
10
|
+
based solely on their inputs, without side effects. They do not:
|
|
11
|
+
- Import or invoke Rust FFI
|
|
12
|
+
- Read configuration files
|
|
13
|
+
- Create RNG instances
|
|
14
|
+
- Perform I/O of any kind
|
|
15
|
+
|
|
16
|
+
The separation allows:
|
|
17
|
+
- Plan verification without Rust dependencies
|
|
18
|
+
- Unit testing of orchestration logic in isolation
|
|
19
|
+
- Clear boundaries between planning and execution
|
|
20
|
+
|
|
21
|
+
See AGENTS.md "Functional Purity Architecture" for full details.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
from collections.abc import Mapping, Sequence
|
|
27
|
+
from dataclasses import dataclass
|
|
28
|
+
from typing import Any, Protocol, TypedDict, Union, cast, runtime_checkable
|
|
29
|
+
|
|
30
|
+
# ---------------------------------------------------------------------------
|
|
31
|
+
# Type Definitions
|
|
32
|
+
# ---------------------------------------------------------------------------
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class PlanSpecification(TypedDict):
|
|
36
|
+
"""Raw mapping describing glitchling orchestration metadata."""
|
|
37
|
+
|
|
38
|
+
name: str
|
|
39
|
+
scope: int
|
|
40
|
+
order: int
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class PipelineOperationPayload(TypedDict, total=False):
|
|
44
|
+
"""Typed mapping describing a Rust pipeline operation."""
|
|
45
|
+
|
|
46
|
+
type: str
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class PipelineDescriptor(TypedDict):
|
|
50
|
+
"""Typed mapping representing a glitchling's Rust pipeline descriptor."""
|
|
51
|
+
|
|
52
|
+
name: str
|
|
53
|
+
operation: PipelineOperationPayload
|
|
54
|
+
seed: int
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
PlanEntry = Union["GlitchlingProtocol", Mapping[str, Any]]
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@runtime_checkable
|
|
61
|
+
class GlitchlingProtocol(Protocol):
|
|
62
|
+
"""Protocol describing the glitchling attributes needed for planning."""
|
|
63
|
+
|
|
64
|
+
name: str
|
|
65
|
+
level: Any # AttackWave enum
|
|
66
|
+
order: Any # AttackOrder enum
|
|
67
|
+
seed: int | None
|
|
68
|
+
|
|
69
|
+
def pipeline_operation(self) -> PipelineOperationPayload | None:
|
|
70
|
+
"""Return the Rust pipeline descriptor or None."""
|
|
71
|
+
...
|
|
72
|
+
|
|
73
|
+
def corrupt(self, text: Any) -> Any:
|
|
74
|
+
"""Apply corruption to text (may handle str or Transcript)."""
|
|
75
|
+
...
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
# ---------------------------------------------------------------------------
|
|
79
|
+
# Plan Specification Normalization
|
|
80
|
+
# ---------------------------------------------------------------------------
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@dataclass(slots=True)
|
|
84
|
+
class NormalizedPlanSpec:
|
|
85
|
+
"""Concrete representation of orchestration metadata consumed by Rust.
|
|
86
|
+
|
|
87
|
+
This dataclass normalizes glitchling metadata into a form suitable for
|
|
88
|
+
the Rust orchestration planner. It can be constructed from either a
|
|
89
|
+
Glitchling instance or a raw mapping specification.
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
name: str
|
|
93
|
+
scope: int
|
|
94
|
+
order: int
|
|
95
|
+
|
|
96
|
+
@classmethod
|
|
97
|
+
def from_glitchling(cls, glitchling: GlitchlingProtocol) -> NormalizedPlanSpec:
|
|
98
|
+
"""Create a plan spec from a Glitchling instance.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
glitchling: A glitchling with name, level, and order attributes.
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
Normalized plan specification.
|
|
105
|
+
"""
|
|
106
|
+
return cls(glitchling.name, int(glitchling.level), int(glitchling.order))
|
|
107
|
+
|
|
108
|
+
@classmethod
|
|
109
|
+
def from_mapping(cls, mapping: Mapping[str, Any]) -> NormalizedPlanSpec:
|
|
110
|
+
"""Create a plan spec from a raw mapping.
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
mapping: Dictionary with 'name', 'scope', and 'order' keys.
|
|
114
|
+
|
|
115
|
+
Returns:
|
|
116
|
+
Normalized plan specification.
|
|
117
|
+
|
|
118
|
+
Raises:
|
|
119
|
+
ValueError: If required fields are missing or invalid.
|
|
120
|
+
"""
|
|
121
|
+
try:
|
|
122
|
+
name = str(mapping["name"])
|
|
123
|
+
scope_value = int(mapping["scope"])
|
|
124
|
+
order_value = int(mapping["order"])
|
|
125
|
+
except KeyError as exc:
|
|
126
|
+
raise ValueError(f"Plan specification missing required field: {exc.args[0]}") from exc
|
|
127
|
+
except (TypeError, ValueError) as exc:
|
|
128
|
+
raise ValueError("Plan specification fields must be coercible to integers") from exc
|
|
129
|
+
|
|
130
|
+
return cls(name, scope_value, order_value)
|
|
131
|
+
|
|
132
|
+
@classmethod
|
|
133
|
+
def from_entry(cls, entry: PlanEntry) -> NormalizedPlanSpec:
|
|
134
|
+
"""Create a plan spec from either a Glitchling or mapping.
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
entry: A Glitchling instance or raw specification mapping.
|
|
138
|
+
|
|
139
|
+
Returns:
|
|
140
|
+
Normalized plan specification.
|
|
141
|
+
|
|
142
|
+
Raises:
|
|
143
|
+
TypeError: If entry is neither a Glitchling nor a mapping.
|
|
144
|
+
"""
|
|
145
|
+
if isinstance(entry, GlitchlingProtocol):
|
|
146
|
+
return cls.from_glitchling(entry)
|
|
147
|
+
if not isinstance(entry, Mapping):
|
|
148
|
+
message = "plan_glitchlings expects Glitchling instances or mapping specifications"
|
|
149
|
+
raise TypeError(message)
|
|
150
|
+
return cls.from_mapping(entry)
|
|
151
|
+
|
|
152
|
+
def as_mapping(self) -> PlanSpecification:
|
|
153
|
+
"""Convert to a raw mapping for Rust consumption.
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
Dictionary with name, scope, and order keys.
|
|
157
|
+
"""
|
|
158
|
+
return {"name": self.name, "scope": self.scope, "order": self.order}
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def normalize_plan_entries(entries: Sequence[PlanEntry]) -> list[NormalizedPlanSpec]:
|
|
162
|
+
"""Normalize a collection of orchestration plan entries.
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
entries: Sequence of Glitchling instances or raw specifications.
|
|
166
|
+
|
|
167
|
+
Returns:
|
|
168
|
+
List of normalized plan specifications.
|
|
169
|
+
"""
|
|
170
|
+
return [NormalizedPlanSpec.from_entry(entry) for entry in entries]
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def normalize_plan_specs(specs: Sequence[Mapping[str, Any]]) -> list[NormalizedPlanSpec]:
|
|
174
|
+
"""Normalize raw plan specification mappings.
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
specs: Sequence of raw specification mappings.
|
|
178
|
+
|
|
179
|
+
Returns:
|
|
180
|
+
List of normalized plan specifications.
|
|
181
|
+
"""
|
|
182
|
+
return [NormalizedPlanSpec.from_mapping(spec) for spec in specs]
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
# ---------------------------------------------------------------------------
|
|
186
|
+
# Pipeline Descriptor Construction
|
|
187
|
+
# ---------------------------------------------------------------------------
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
@dataclass(slots=True)
|
|
191
|
+
class PipelineDescriptorModel:
|
|
192
|
+
"""In-memory representation of a glitchling pipeline descriptor.
|
|
193
|
+
|
|
194
|
+
This model captures the data needed to invoke a glitchling through
|
|
195
|
+
the Rust pipeline: its name, computed seed, and operation payload.
|
|
196
|
+
"""
|
|
197
|
+
|
|
198
|
+
name: str
|
|
199
|
+
seed: int
|
|
200
|
+
operation: PipelineOperationPayload
|
|
201
|
+
|
|
202
|
+
def as_mapping(self) -> PipelineDescriptor:
|
|
203
|
+
"""Convert to a dictionary for Rust consumption.
|
|
204
|
+
|
|
205
|
+
Returns:
|
|
206
|
+
Dictionary with name, operation, and seed keys.
|
|
207
|
+
"""
|
|
208
|
+
return {"name": self.name, "operation": self.operation, "seed": self.seed}
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def build_pipeline_descriptor(
|
|
212
|
+
glitchling: GlitchlingProtocol,
|
|
213
|
+
*,
|
|
214
|
+
master_seed: int | None,
|
|
215
|
+
derive_seed_fn: Any, # Callable[[int, str, int], int]
|
|
216
|
+
) -> PipelineDescriptorModel | None:
|
|
217
|
+
"""Materialise the Rust pipeline descriptor for a glitchling when available.
|
|
218
|
+
|
|
219
|
+
This is a pure function that constructs the descriptor data without
|
|
220
|
+
invoking Rust. The actual Rust dispatch happens in core_execution.
|
|
221
|
+
|
|
222
|
+
Args:
|
|
223
|
+
glitchling: The glitchling to build a descriptor for.
|
|
224
|
+
master_seed: Master seed for the enclosing Gaggle.
|
|
225
|
+
derive_seed_fn: Function to derive child seeds from master seed.
|
|
226
|
+
Signature: (master_seed: int, name: str, index: int) -> int
|
|
227
|
+
|
|
228
|
+
Returns:
|
|
229
|
+
PipelineDescriptorModel if the glitchling supports pipeline execution,
|
|
230
|
+
None otherwise.
|
|
231
|
+
|
|
232
|
+
Raises:
|
|
233
|
+
RuntimeError: If seed cannot be determined for a pipeline-enabled glitchling.
|
|
234
|
+
"""
|
|
235
|
+
operation = glitchling.pipeline_operation()
|
|
236
|
+
if operation is None:
|
|
237
|
+
return None
|
|
238
|
+
|
|
239
|
+
if not isinstance(operation, Mapping):
|
|
240
|
+
raise TypeError("Pipeline operations must be mappings or None")
|
|
241
|
+
|
|
242
|
+
operation_payload = dict(operation)
|
|
243
|
+
operation_type = operation_payload.get("type")
|
|
244
|
+
if not isinstance(operation_type, str):
|
|
245
|
+
message = f"Pipeline operation for {glitchling.name} is missing a string 'type'"
|
|
246
|
+
raise RuntimeError(message)
|
|
247
|
+
|
|
248
|
+
seed = glitchling.seed
|
|
249
|
+
if seed is None:
|
|
250
|
+
index = getattr(glitchling, "_gaggle_index", None)
|
|
251
|
+
if index is None or master_seed is None:
|
|
252
|
+
raise RuntimeError(
|
|
253
|
+
"Glitchling %s is missing deterministic seed configuration" % glitchling.name
|
|
254
|
+
)
|
|
255
|
+
seed = derive_seed_fn(master_seed, glitchling.name, index)
|
|
256
|
+
|
|
257
|
+
payload = cast(PipelineOperationPayload, operation_payload)
|
|
258
|
+
return PipelineDescriptorModel(glitchling.name, int(seed), payload)
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
# ---------------------------------------------------------------------------
|
|
262
|
+
# Execution Plan Construction
|
|
263
|
+
# ---------------------------------------------------------------------------
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
@dataclass(slots=True, frozen=True)
|
|
267
|
+
class ExecutionStep:
|
|
268
|
+
"""A single step in the execution plan.
|
|
269
|
+
|
|
270
|
+
Represents either a batch of pipeline descriptors (for Rust execution)
|
|
271
|
+
or a single glitchling for fallback Python execution.
|
|
272
|
+
|
|
273
|
+
Attributes:
|
|
274
|
+
descriptors: List of pipeline descriptors for batch Rust execution.
|
|
275
|
+
Empty when this step is a fallback.
|
|
276
|
+
fallback_glitchling: Glitchling to execute via Python when
|
|
277
|
+
pipeline execution is not available. None when using pipeline.
|
|
278
|
+
"""
|
|
279
|
+
|
|
280
|
+
descriptors: tuple[PipelineDescriptor, ...]
|
|
281
|
+
fallback_glitchling: GlitchlingProtocol | None
|
|
282
|
+
|
|
283
|
+
@property
|
|
284
|
+
def is_pipeline_step(self) -> bool:
|
|
285
|
+
"""Return True if this step uses the Rust pipeline."""
|
|
286
|
+
return len(self.descriptors) > 0
|
|
287
|
+
|
|
288
|
+
@property
|
|
289
|
+
def is_fallback_step(self) -> bool:
|
|
290
|
+
"""Return True if this step uses Python fallback."""
|
|
291
|
+
return self.fallback_glitchling is not None
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
@dataclass(slots=True, frozen=True)
|
|
295
|
+
class ExecutionPlan:
|
|
296
|
+
"""Complete execution plan for a Gaggle's text corruption.
|
|
297
|
+
|
|
298
|
+
The plan consists of ordered steps that can be either:
|
|
299
|
+
- Pipeline batches: Multiple glitchlings executed together via Rust
|
|
300
|
+
- Fallback steps: Single glitchlings executed via Python
|
|
301
|
+
|
|
302
|
+
Consecutive pipeline-enabled glitchlings are batched together to
|
|
303
|
+
minimize tokenization overhead.
|
|
304
|
+
|
|
305
|
+
Attributes:
|
|
306
|
+
steps: Ordered sequence of execution steps.
|
|
307
|
+
all_pipeline: True if all glitchlings support pipeline execution.
|
|
308
|
+
"""
|
|
309
|
+
|
|
310
|
+
steps: tuple[ExecutionStep, ...]
|
|
311
|
+
all_pipeline: bool
|
|
312
|
+
|
|
313
|
+
@property
|
|
314
|
+
def step_count(self) -> int:
|
|
315
|
+
"""Return the number of execution steps."""
|
|
316
|
+
return len(self.steps)
|
|
317
|
+
|
|
318
|
+
@property
|
|
319
|
+
def pipeline_step_count(self) -> int:
|
|
320
|
+
"""Return the number of pipeline batch steps."""
|
|
321
|
+
return sum(1 for step in self.steps if step.is_pipeline_step)
|
|
322
|
+
|
|
323
|
+
@property
|
|
324
|
+
def fallback_step_count(self) -> int:
|
|
325
|
+
"""Return the number of fallback steps."""
|
|
326
|
+
return sum(1 for step in self.steps if step.is_fallback_step)
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
def build_execution_plan(
|
|
330
|
+
apply_order: Sequence[GlitchlingProtocol],
|
|
331
|
+
*,
|
|
332
|
+
master_seed: int | None,
|
|
333
|
+
derive_seed_fn: Any, # Callable[[int, str, int], int]
|
|
334
|
+
) -> ExecutionPlan:
|
|
335
|
+
"""Build an execution plan that batches consecutive pipeline-supported glitchlings.
|
|
336
|
+
|
|
337
|
+
This is a pure function that analyzes glitchlings and constructs an
|
|
338
|
+
optimal execution plan. The plan batches consecutive pipeline-enabled
|
|
339
|
+
glitchlings to reduce tokenization overhead, while isolating fallback
|
|
340
|
+
glitchlings that require Python execution.
|
|
341
|
+
|
|
342
|
+
Args:
|
|
343
|
+
apply_order: Ordered sequence of glitchlings to execute.
|
|
344
|
+
master_seed: Master seed for seed derivation.
|
|
345
|
+
derive_seed_fn: Function to derive child seeds.
|
|
346
|
+
|
|
347
|
+
Returns:
|
|
348
|
+
ExecutionPlan containing ordered execution steps.
|
|
349
|
+
|
|
350
|
+
Example:
|
|
351
|
+
If glitchlings A, B (pipeline), C (fallback), D, E (pipeline):
|
|
352
|
+
Plan: [(A, B descriptors), (C fallback), (D, E descriptors)]
|
|
353
|
+
"""
|
|
354
|
+
steps: list[ExecutionStep] = []
|
|
355
|
+
current_batch: list[PipelineDescriptor] = []
|
|
356
|
+
|
|
357
|
+
for glitchling in apply_order:
|
|
358
|
+
descriptor = build_pipeline_descriptor(
|
|
359
|
+
glitchling,
|
|
360
|
+
master_seed=master_seed,
|
|
361
|
+
derive_seed_fn=derive_seed_fn,
|
|
362
|
+
)
|
|
363
|
+
if descriptor is not None:
|
|
364
|
+
current_batch.append(descriptor.as_mapping())
|
|
365
|
+
else:
|
|
366
|
+
# Flush any accumulated batch before the fallback item
|
|
367
|
+
if current_batch:
|
|
368
|
+
steps.append(ExecutionStep(tuple(current_batch), None))
|
|
369
|
+
current_batch = []
|
|
370
|
+
# Add the fallback step
|
|
371
|
+
steps.append(ExecutionStep((), glitchling))
|
|
372
|
+
|
|
373
|
+
# Flush any remaining batch
|
|
374
|
+
if current_batch:
|
|
375
|
+
steps.append(ExecutionStep(tuple(current_batch), None))
|
|
376
|
+
|
|
377
|
+
all_pipeline = len(steps) == 1 and steps[0].fallback_glitchling is None if steps else True
|
|
378
|
+
|
|
379
|
+
return ExecutionPlan(tuple(steps), all_pipeline)
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
# ---------------------------------------------------------------------------
|
|
383
|
+
# Plan Validation Helpers
|
|
384
|
+
# ---------------------------------------------------------------------------
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
def validate_plan_coverage(
|
|
388
|
+
plan: Sequence[tuple[int, int]],
|
|
389
|
+
expected_count: int,
|
|
390
|
+
) -> set[int]:
|
|
391
|
+
"""Validate that an orchestration plan covers all expected indices.
|
|
392
|
+
|
|
393
|
+
Args:
|
|
394
|
+
plan: Sequence of (index, seed) tuples from Rust planner.
|
|
395
|
+
expected_count: Number of glitchlings that should be covered.
|
|
396
|
+
|
|
397
|
+
Returns:
|
|
398
|
+
Set of missing indices (empty if plan is complete).
|
|
399
|
+
"""
|
|
400
|
+
covered = {index for index, _ in plan}
|
|
401
|
+
expected = set(range(expected_count))
|
|
402
|
+
return expected - covered
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
def extract_plan_ordering(plan: Sequence[tuple[int, int]]) -> list[int]:
|
|
406
|
+
"""Extract the execution order from an orchestration plan.
|
|
407
|
+
|
|
408
|
+
Args:
|
|
409
|
+
plan: Sequence of (index, seed) tuples.
|
|
410
|
+
|
|
411
|
+
Returns:
|
|
412
|
+
List of indices in execution order.
|
|
413
|
+
"""
|
|
414
|
+
return [index for index, _ in plan]
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
def extract_plan_seeds(plan: Sequence[tuple[int, int]]) -> dict[int, int]:
|
|
418
|
+
"""Extract the seed assignments from an orchestration plan.
|
|
419
|
+
|
|
420
|
+
Args:
|
|
421
|
+
plan: Sequence of (index, seed) tuples.
|
|
422
|
+
|
|
423
|
+
Returns:
|
|
424
|
+
Dictionary mapping index to assigned seed.
|
|
425
|
+
"""
|
|
426
|
+
return {index: seed for index, seed in plan}
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
__all__ = [
|
|
430
|
+
# Types
|
|
431
|
+
"PlanSpecification",
|
|
432
|
+
"PipelineOperationPayload",
|
|
433
|
+
"PipelineDescriptor",
|
|
434
|
+
"PlanEntry",
|
|
435
|
+
"GlitchlingProtocol",
|
|
436
|
+
# Normalization
|
|
437
|
+
"NormalizedPlanSpec",
|
|
438
|
+
"normalize_plan_entries",
|
|
439
|
+
"normalize_plan_specs",
|
|
440
|
+
# Descriptors
|
|
441
|
+
"PipelineDescriptorModel",
|
|
442
|
+
"build_pipeline_descriptor",
|
|
443
|
+
# Execution planning
|
|
444
|
+
"ExecutionStep",
|
|
445
|
+
"ExecutionPlan",
|
|
446
|
+
"build_execution_plan",
|
|
447
|
+
# Validation
|
|
448
|
+
"validate_plan_coverage",
|
|
449
|
+
"extract_plan_ordering",
|
|
450
|
+
"extract_plan_seeds",
|
|
451
|
+
]
|