rigorloop 0.1.0__py3-none-any.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.
@@ -0,0 +1,649 @@
1
+ """Domain model: algebraic data types shared by the core and the shell.
2
+
3
+ Products are frozen dataclasses; sums are `type` unions matched exhaustively.
4
+ The dev/val/test split is encoded in the type system (`DevExample`,
5
+ `ValExample`, `TestExample`) so that leakage into agent-context prompts is a
6
+ type error, not a runtime bug.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from dataclasses import dataclass
12
+
13
+ # --------------------------------------------------------------------------
14
+ # Foundations
15
+ # --------------------------------------------------------------------------
16
+
17
+
18
+ @dataclass(frozen=True, slots=True)
19
+ class Some[T]:
20
+ value: T
21
+
22
+
23
+ @dataclass(frozen=True, slots=True)
24
+ class Nothing:
25
+ pass
26
+
27
+
28
+ type Option[T] = Some[T] | Nothing
29
+
30
+ NOTHING = Nothing()
31
+
32
+
33
+ @dataclass(frozen=True, slots=True)
34
+ class Ok[T]:
35
+ value: T
36
+
37
+
38
+ @dataclass(frozen=True, slots=True)
39
+ class Err[E]:
40
+ error: E
41
+
42
+
43
+ type Result[T, E] = Ok[T] | Err[E]
44
+
45
+ type JsonValue = None | bool | int | float | str | list["JsonValue"] | dict[str, "JsonValue"]
46
+
47
+
48
+ # --------------------------------------------------------------------------
49
+ # Examples and splits
50
+ # --------------------------------------------------------------------------
51
+
52
+
53
+ @dataclass(frozen=True, slots=True)
54
+ class Example:
55
+ example_id: str
56
+ input_text: str
57
+ expected_output: str
58
+
59
+
60
+ @dataclass(frozen=True, slots=True)
61
+ class DevExample:
62
+ """The only example type agent-context prompt builders accept."""
63
+
64
+ example: Example
65
+
66
+
67
+ @dataclass(frozen=True, slots=True)
68
+ class ValExample:
69
+ """Never enters any agent-context prompt."""
70
+
71
+ example: Example
72
+
73
+
74
+ @dataclass(frozen=True, slots=True)
75
+ class TestExample:
76
+ """Never enters any agent-context prompt; read once, at finalization."""
77
+
78
+ example: Example
79
+
80
+
81
+ @dataclass(frozen=True, slots=True)
82
+ class SplitRatios:
83
+ dev: float
84
+ val: float
85
+ test: float
86
+
87
+
88
+ @dataclass(frozen=True, slots=True)
89
+ class Split:
90
+ dev: tuple[DevExample, ...]
91
+ val: tuple[ValExample, ...]
92
+ test: tuple[TestExample, ...]
93
+
94
+
95
+ @dataclass(frozen=True, slots=True)
96
+ class ExampleDigest:
97
+ example_id: str
98
+ content_hash: str
99
+
100
+
101
+ @dataclass(frozen=True, slots=True)
102
+ class SplitManifest:
103
+ seed: int
104
+ ratios: SplitRatios
105
+ dev: tuple[ExampleDigest, ...]
106
+ val: tuple[ExampleDigest, ...]
107
+ test: tuple[ExampleDigest, ...]
108
+ eval_model: str
109
+ cli_version: str
110
+
111
+
112
+ @dataclass(frozen=True, slots=True)
113
+ class DuplicateWarning:
114
+ input_preview: str
115
+ occurrences: int
116
+
117
+
118
+ @dataclass(frozen=True, slots=True)
119
+ class PowerWarning:
120
+ split_name: str
121
+ n: int
122
+ half_width: float
123
+ message: str
124
+
125
+
126
+ # --------------------------------------------------------------------------
127
+ # Solutions
128
+ # --------------------------------------------------------------------------
129
+
130
+
131
+ @dataclass(frozen=True, slots=True)
132
+ class ScriptSolution:
133
+ pass
134
+
135
+
136
+ @dataclass(frozen=True, slots=True)
137
+ class SkillSolution:
138
+ pass
139
+
140
+
141
+ @dataclass(frozen=True, slots=True)
142
+ class GuidanceSolution:
143
+ pass
144
+
145
+
146
+ type SolutionKind = ScriptSolution | SkillSolution | GuidanceSolution
147
+
148
+
149
+ @dataclass(frozen=True, slots=True)
150
+ class Candidate:
151
+ candidate_id: str
152
+ loop_index: int
153
+ kind: SolutionKind
154
+ content: str
155
+ directive_id: str
156
+
157
+
158
+ # --------------------------------------------------------------------------
159
+ # Checks and outcomes
160
+ # --------------------------------------------------------------------------
161
+
162
+
163
+ @dataclass(frozen=True, slots=True)
164
+ class ExactMatch:
165
+ pass
166
+
167
+
168
+ @dataclass(frozen=True, slots=True)
169
+ class NormalizedMatch:
170
+ lowercase: bool
171
+ strip: bool
172
+ collapse_whitespace: bool
173
+
174
+
175
+ @dataclass(frozen=True, slots=True)
176
+ class JsonEquality:
177
+ pass
178
+
179
+
180
+ @dataclass(frozen=True, slots=True)
181
+ class RegexMatch:
182
+ pattern: str
183
+
184
+
185
+ @dataclass(frozen=True, slots=True)
186
+ class NumericTolerance:
187
+ atol: float
188
+ rtol: float
189
+
190
+
191
+ @dataclass(frozen=True, slots=True)
192
+ class CustomPython:
193
+ script_path: str
194
+
195
+
196
+ @dataclass(frozen=True, slots=True)
197
+ class LlmJudge:
198
+ rubric: str
199
+ n_samples: int
200
+ pass_threshold: float
201
+
202
+
203
+ type DeterministicCheck = (
204
+ ExactMatch | NormalizedMatch | JsonEquality | RegexMatch | NumericTolerance
205
+ )
206
+ type Check = DeterministicCheck | CustomPython | LlmJudge
207
+
208
+
209
+ @dataclass(frozen=True, slots=True)
210
+ class Passed:
211
+ pass
212
+
213
+
214
+ @dataclass(frozen=True, slots=True)
215
+ class Failed:
216
+ reason: str
217
+
218
+
219
+ @dataclass(frozen=True, slots=True)
220
+ class Errored:
221
+ detail: str
222
+
223
+
224
+ type CheckOutcome = Passed | Failed | Errored
225
+
226
+
227
+ @dataclass(frozen=True, slots=True)
228
+ class NamedOutcome:
229
+ check_name: str
230
+ outcome: CheckOutcome
231
+
232
+
233
+ @dataclass(frozen=True, slots=True)
234
+ class ExecutionOk:
235
+ output_text: str
236
+
237
+
238
+ @dataclass(frozen=True, slots=True)
239
+ class ExecutionFailed:
240
+ detail: str
241
+
242
+
243
+ type ExecutionResult = ExecutionOk | ExecutionFailed
244
+
245
+
246
+ @dataclass(frozen=True, slots=True)
247
+ class ExampleResult:
248
+ example_id: str
249
+ execution: ExecutionResult
250
+ outcomes: tuple[NamedOutcome, ...]
251
+
252
+
253
+ @dataclass(frozen=True, slots=True)
254
+ class JudgeVerdict:
255
+ passed: bool
256
+ reason: str
257
+
258
+
259
+ # --------------------------------------------------------------------------
260
+ # Scores
261
+ # --------------------------------------------------------------------------
262
+
263
+
264
+ @dataclass(frozen=True, slots=True)
265
+ class CheckPassRate:
266
+ check_name: str
267
+ passes: int
268
+ n: int
269
+
270
+
271
+ @dataclass(frozen=True, slots=True)
272
+ class CandidateScore:
273
+ n: int
274
+ passes: int
275
+ pass_rate: float
276
+ ci_low: float
277
+ ci_high: float
278
+ per_check: tuple[CheckPassRate, ...]
279
+ pass_vector: tuple[bool, ...] # aligned to example_id sort order of the set
280
+ eval_aborted: bool
281
+
282
+
283
+ @dataclass(frozen=True, slots=True)
284
+ class LeaderboardEntry:
285
+ candidate_id: str
286
+ loop_index: int
287
+ kind: SolutionKind
288
+ content: str
289
+ score: CandidateScore
290
+
291
+
292
+ # --------------------------------------------------------------------------
293
+ # Strategy
294
+ # --------------------------------------------------------------------------
295
+
296
+
297
+ @dataclass(frozen=True, slots=True)
298
+ class ChampionArtifact:
299
+ """Solution content ONLY — never scores, mistakes, or per-example failures."""
300
+
301
+ candidate_id: str
302
+ kind: SolutionKind
303
+ content: str
304
+
305
+
306
+ @dataclass(frozen=True, slots=True)
307
+ class DirectiveSpec:
308
+ """What the strategy agent asked for; the harness attaches the artifact."""
309
+
310
+ approach_summary: str
311
+ instructions: str
312
+ base_on_champion: bool
313
+
314
+
315
+ @dataclass(frozen=True, slots=True)
316
+ class Directive:
317
+ directive_id: str
318
+ approach_summary: str
319
+ instructions: str
320
+ base: Option[ChampionArtifact]
321
+
322
+
323
+ @dataclass(frozen=True, slots=True)
324
+ class ContinueDecision:
325
+ observations: str
326
+ hypotheses: str
327
+ directive_specs: tuple[DirectiveSpec, ...]
328
+ request_validation: bool
329
+
330
+
331
+ @dataclass(frozen=True, slots=True)
332
+ class StopRequested:
333
+ reason: str
334
+
335
+
336
+ type StrategyDecision = ContinueDecision | StopRequested
337
+
338
+
339
+ @dataclass(frozen=True, slots=True)
340
+ class BudgetExhausted:
341
+ max_loops: int
342
+
343
+
344
+ @dataclass(frozen=True, slots=True)
345
+ class ValidationPlateau:
346
+ checkpoints_without_improvement: int
347
+
348
+
349
+ @dataclass(frozen=True, slots=True)
350
+ class TargetReached:
351
+ pass_rate: float
352
+
353
+
354
+ @dataclass(frozen=True, slots=True)
355
+ class StrategyRequestedStop:
356
+ reason: str
357
+
358
+
359
+ @dataclass(frozen=True, slots=True)
360
+ class StrategyUnresponsive:
361
+ consecutive_fallbacks: int
362
+
363
+
364
+ type StopReason = (
365
+ BudgetExhausted
366
+ | ValidationPlateau
367
+ | TargetReached
368
+ | StrategyRequestedStop
369
+ | StrategyUnresponsive
370
+ )
371
+
372
+
373
+ @dataclass(frozen=True, slots=True)
374
+ class StrategyLogEntry:
375
+ loop_index: int
376
+ observations: str
377
+ hypotheses: str
378
+ directives: tuple[Directive, ...]
379
+ dev_summary: str
380
+ val_summary: Option[str]
381
+ fallback: bool
382
+
383
+
384
+ @dataclass(frozen=True, slots=True)
385
+ class ValidatedCandidate:
386
+ candidate_id: str
387
+ kind: SolutionKind
388
+ content: str
389
+ dev_score: CandidateScore
390
+ val_score: CandidateScore
391
+
392
+
393
+ @dataclass(frozen=True, slots=True)
394
+ class ValCheckpoint:
395
+ loop_index: int
396
+ candidate_id: str
397
+ dev_pass_rate: float
398
+ val_pass_rate: float
399
+ displaced_champion: bool
400
+
401
+
402
+ @dataclass(frozen=True, slots=True)
403
+ class RunState:
404
+ """Everything the loop needs between iterations; persisted for resume."""
405
+
406
+ loops_completed: int
407
+ leaderboard: tuple[LeaderboardEntry, ...]
408
+ strategy_log: tuple[StrategyLogEntry, ...]
409
+ val_champion: Option[ValidatedCandidate]
410
+ checkpoints: tuple[ValCheckpoint, ...]
411
+ peeks_used: int
412
+ last_peek_loop: Option[int]
413
+ consecutive_fallbacks: int
414
+
415
+
416
+ @dataclass(frozen=True, slots=True)
417
+ class FailureSample:
418
+ """A failing dev example shown to the strategy agent (dev-only by type)."""
419
+
420
+ dev_example: DevExample
421
+ actual_output: str
422
+ failed_checks: tuple[str, ...]
423
+
424
+
425
+ @dataclass(frozen=True, slots=True)
426
+ class StrategyContext:
427
+ """Everything the strategy agent is allowed to see, assembled as data.
428
+
429
+ Contains Dev-typed examples and aggregate scores only; the val/test
430
+ channel appears solely as pre-aggregated score lines."""
431
+
432
+ task_description: str
433
+ solution_kind: SolutionKind
434
+ loops_completed: int
435
+ max_loops: int
436
+ executors_per_loop: int
437
+ check_names: tuple[str, ...]
438
+ recent_log: tuple[StrategyLogEntry, ...]
439
+ compacted_log: tuple[str, ...]
440
+ leaderboard_lines: tuple[str, ...]
441
+ failure_samples: tuple[FailureSample, ...]
442
+ champion: Option[ChampionArtifact]
443
+ champion_dev_line: Option[str]
444
+ val_lines: tuple[str, ...]
445
+ dev_val_gap_warning: Option[str]
446
+ peeks_used: int
447
+ max_peeks: int
448
+ dev_subset_note: str
449
+
450
+
451
+ # --------------------------------------------------------------------------
452
+ # Prompt channels — two distinct types; leakage guards apply to the first
453
+ # --------------------------------------------------------------------------
454
+
455
+
456
+ @dataclass(frozen=True, slots=True)
457
+ class StrategyRole:
458
+ pass
459
+
460
+
461
+ @dataclass(frozen=True, slots=True)
462
+ class ExecutorRole:
463
+ pass
464
+
465
+
466
+ type AgentRole = StrategyRole | ExecutorRole
467
+
468
+
469
+ @dataclass(frozen=True, slots=True)
470
+ class AgentContextPrompt:
471
+ """Prompt for the strategy or executor agents. Builders accept Dev-typed
472
+ examples and aggregate scores only; val/test content is a type error."""
473
+
474
+ role: AgentRole
475
+ text: str
476
+
477
+
478
+ @dataclass(frozen=True, slots=True)
479
+ class EvalPrompt:
480
+ """Prompt that runs a solution-under-test or an LLM judge on ONE example
481
+ (any split). Its output returns to the harness as data, never into an
482
+ AgentContextPrompt."""
483
+
484
+ system_prompt: Option[str]
485
+ user_prompt: str
486
+
487
+
488
+ # --------------------------------------------------------------------------
489
+ # Effect descriptions the shell executes
490
+ # --------------------------------------------------------------------------
491
+
492
+
493
+ @dataclass(frozen=True, slots=True)
494
+ class AgentTextRequest:
495
+ user_prompt: str
496
+ system_prompt: Option[str]
497
+ model: str
498
+ timeout_s: float
499
+
500
+
501
+ @dataclass(frozen=True, slots=True)
502
+ class RunScriptRequest:
503
+ script_path: str
504
+ stdin_text: str
505
+ timeout_s: float
506
+
507
+
508
+ @dataclass(frozen=True, slots=True)
509
+ class BudgetEstimate:
510
+ """Kind-aware pre-run estimate of `claude` calls (upper bound)."""
511
+
512
+ strategy_calls: int
513
+ executor_calls: int
514
+ solution_eval_calls: int
515
+ judge_calls: int
516
+ total_calls: int
517
+
518
+
519
+ # --------------------------------------------------------------------------
520
+ # Errors
521
+ # --------------------------------------------------------------------------
522
+
523
+
524
+ @dataclass(frozen=True, slots=True)
525
+ class InvalidJsonLine:
526
+ line_number: int
527
+ detail: str
528
+
529
+
530
+ @dataclass(frozen=True, slots=True)
531
+ class NotAnObject:
532
+ line_number: int
533
+
534
+
535
+ @dataclass(frozen=True, slots=True)
536
+ class MissingField:
537
+ line_number: int
538
+ field: str
539
+
540
+
541
+ type ExampleParseError = InvalidJsonLine | NotAnObject | MissingField
542
+
543
+
544
+ @dataclass(frozen=True, slots=True)
545
+ class TooFewExamples:
546
+ n_total: int
547
+ minimum: int
548
+
549
+
550
+ @dataclass(frozen=True, slots=True)
551
+ class BadRatios:
552
+ detail: str
553
+
554
+
555
+ type SplitError = TooFewExamples | BadRatios
556
+
557
+
558
+ @dataclass(frozen=True, slots=True)
559
+ class TomlSyntax:
560
+ detail: str
561
+
562
+
563
+ @dataclass(frozen=True, slots=True)
564
+ class MissingKey:
565
+ key: str
566
+
567
+
568
+ @dataclass(frozen=True, slots=True)
569
+ class InvalidValue:
570
+ key: str
571
+ detail: str
572
+
573
+
574
+ type ConfigParseError = TomlSyntax | MissingKey | InvalidValue
575
+
576
+
577
+ @dataclass(frozen=True, slots=True)
578
+ class MalformedReply:
579
+ detail: str
580
+
581
+
582
+ @dataclass(frozen=True, slots=True)
583
+ class CallTimeout:
584
+ timeout_s: float
585
+
586
+
587
+ @dataclass(frozen=True, slots=True)
588
+ class CallFailed:
589
+ detail: str
590
+
591
+
592
+ @dataclass(frozen=True, slots=True)
593
+ class EnvelopeError:
594
+ detail: str
595
+
596
+
597
+ type AgentCallError = CallTimeout | CallFailed | EnvelopeError
598
+
599
+
600
+ # --------------------------------------------------------------------------
601
+ # Configuration
602
+ # --------------------------------------------------------------------------
603
+
604
+
605
+ @dataclass(frozen=True, slots=True)
606
+ class TaskConfig:
607
+ description_file: str
608
+ solution_kind: SolutionKind
609
+ examples_file: str
610
+
611
+
612
+ @dataclass(frozen=True, slots=True)
613
+ class SplitConfig:
614
+ ratios: SplitRatios
615
+ seed: int
616
+
617
+
618
+ @dataclass(frozen=True, slots=True)
619
+ class LoopConfig:
620
+ max_loops: int
621
+ executors_per_loop: int
622
+ dev_examples_in_prompt: int
623
+ max_consecutive_eval_failures: int
624
+ strategy_full_detail_loops: int
625
+
626
+
627
+ @dataclass(frozen=True, slots=True)
628
+ class ValidationConfig:
629
+ val_every: int
630
+ max_peeks: int
631
+ min_loops_between_peeks: int
632
+ patience: int
633
+ target_pass_rate: Option[float]
634
+
635
+
636
+ @dataclass(frozen=True, slots=True)
637
+ class AgentConfig:
638
+ model: str
639
+ timeout_s: float
640
+
641
+
642
+ @dataclass(frozen=True, slots=True)
643
+ class RunConfig:
644
+ task: TaskConfig
645
+ split: SplitConfig
646
+ loop: LoopConfig
647
+ validation: ValidationConfig
648
+ agents: AgentConfig
649
+ checks: tuple[Check, ...]
rigorloop/py.typed ADDED
File without changes
@@ -0,0 +1 @@
1
+ """The imperative shell: performs the effects the core describes."""