lunarlabs-core 0.1.0__tar.gz

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,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Lunar Labs
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,151 @@
1
+ Metadata-Version: 2.4
2
+ Name: lunarlabs-core
3
+ Version: 0.1.0
4
+ Summary: The product-neutral Lunar Labs spine: capture, deterministic verification, fail-open escalation, training-pair extraction, and the fitness gate.
5
+ Author: Lunar Labs
6
+ License: MIT
7
+ Requires-Python: >=3.9
8
+ Description-Content-Type: text/markdown
9
+ License-File: LICENSE
10
+ Dynamic: license-file
11
+
12
+ # lunarlabs-core
13
+
14
+ **The product-neutral spine shared by Lunar Labs products.**
15
+
16
+ ```
17
+ candidate (cheap/local) → verify (deterministic oracle) → pass? ✓ done
18
+ → fail/abstain? ↑ teacher (frontier) → verify
19
+ ↓
20
+ append-only trace capture (both verdicts label-worthy)
21
+ ↓
22
+ training-pair extraction (retrieval-task Q→A, NEVER raw transcripts)
23
+ ↓
24
+ nightly consolidation → FITNESS GATE (promote only if deflection
25
+ improves without regression) → ship or roll back
26
+ ```
27
+
28
+ Two products are built on this spine:
29
+
30
+ - **Artemis** — a small-model-first cascade for tool calls. It uses the spine to
31
+ deflect spend from frontier models to on-device models; its four oracle
32
+ families (agreement, provenance, structural, execution) are product-specific
33
+ and stay in Artemis.
34
+ - **Avalon** — an adaptive AI runtime. It uses the same spine for its own
35
+ capture → verify → consolidate → gate loop over runtime behavior. Avalon's
36
+ vision is much larger than this library; `lunarlabs-core` is only the shared
37
+ contracts both products import so the safety semantics (abstention, fail-open,
38
+ gating) are defined — and tested — exactly once.
39
+
40
+ This library is stdlib-only and deliberately boring. What is shared is the
41
+ *contract*; what is measured, drafted, and trained stays in each product.
42
+
43
+ ## What the spine defines
44
+
45
+ | module | contract | who consumes it |
46
+ |---|---|---|
47
+ | `verdict.py` | The three-valued verdict: PASS / FAIL / **ABSTAIN**, expressed as `ok` × `label`. | Both products' oracles. |
48
+ | `verifier.py` | The `Oracle` protocol (`output → Verdict`), `all_of` composition, one trivial example oracle. | Both products plug in their own domain oracles. |
49
+ | `escalation.py` | `verified_cascade`: candidate-first routing, **fail-open** on any internal error, `LUNARLABS_*` kill switch. | Artemis's router; Avalon's runtime decision path. |
50
+ | `trace.py` | `TraceRecord` + append-only JSONL `TraceWriter`, with the dual-label capture gate as the only write path. | Artemis's escalation capture; Avalon's observed-event capture. |
51
+ | `pairs.py` | `TrainingPair`: retrieval-task Q→A with provenance. **Never raw transcripts.** | Both products' nightly consolidation / LoRA pipelines. |
52
+ | `fitness.py` | `fitness_gate`: pure decision over measured metrics — promote only on meaningful deflection gain with no keep-set regression. | Both products' nightly retrain jobs. |
53
+
54
+ ## The ABSTAIN third verdict is load-bearing
55
+
56
+ Every oracle answers three things, not two. When a check cannot *mechanically*
57
+ decide, it declines:
58
+
59
+ - **PASS + label** — route on it, and it is strong enough to write training data.
60
+ - **FAIL + label** — real evidence of a wrong answer; the pair may be captured.
61
+ - **ABSTAIN** (`ok=False, label=False`) — escalate (costs money, never ships a
62
+ wrong answer) but the trace writer **refuses the pair**, so it poisons
63
+ nothing. Training on abstentions would teach the model the reference's
64
+ arbitrary surface conventions, which raises future agreement, which inflates
65
+ measured deflection: a self-fulfilling metric.
66
+
67
+ `TraceWriter.record_escalation` enforces both conjuncts — the teacher's pass
68
+ must be label-worthy AND the candidate's failure must be label-worthy — so a
69
+ weak oracle cannot poison the retrain it feeds.
70
+
71
+ ## Fail-open, and the kill switch
72
+
73
+ Every spine-internal failure — candidate down, timeout, malformed reply, a bug
74
+ in the verifier itself — routes to the teacher instead of raising. Breaking
75
+ the product loses the user permanently; a missed deflection costs a fraction
76
+ of a cent. Nothing in `verified_cascade` raises past its caller.
77
+
78
+ ```bash
79
+ export LUNARLABS_DISABLED=1 # every call goes straight to the teacher
80
+ export LUNARLABS_DEFLECT=off # same effect, second switch
81
+ ```
82
+
83
+ Both are read fresh on every call — flipping the env var takes effect without
84
+ a restart or redeploy.
85
+
86
+ ## Training pairs are retrieval-task Q→A — never raw transcripts
87
+
88
+ From the JARVIS research: a raw transcript trains surface imitation (phrasing,
89
+ length, the teacher's tics) and drowns the signal you paid an escalation for.
90
+ A Q→A pair isolates exactly the thing the candidate got wrong: *given THIS
91
+ observed context, the correct answer is THAT.* Every `TrainingPair` therefore
92
+ has `task_type="retrieval"`, a question projected from the trace's context,
93
+ the teacher's verified output as the answer, and full provenance (source trace
94
+ id, candidate failure stage/reasons). A pair without provenance is
95
+ unauditable and should be refused downstream.
96
+
97
+ ## Usage
98
+
99
+ ```python
100
+ from lunarlabs_core import (
101
+ TraceWriter, verified_cascade, non_empty_oracle,
102
+ extract_pairs, write_pairs_jsonl, FitnessMetrics, fitness_gate,
103
+ )
104
+
105
+ trace = TraceWriter("traces.jsonl")
106
+
107
+ result = verified_cascade(
108
+ candidate=lambda: small_model.answer(request), # cheap path first
109
+ teacher=lambda: frontier_model.answer(request), # only on verified failure
110
+ verify=my_oracle, # your deterministic oracle
111
+ context={"request_id": "..."}, # opaque to the spine
112
+ trace=trace,
113
+ )
114
+ result.handled_by # "candidate" | "teacher"
115
+ result.failed_open # True if the spine broke and got out of the way
116
+
117
+ # Nightly consolidation:
118
+ pairs = extract_pairs(trace.records())
119
+ write_pairs_jsonl(pairs, "train.jsonl")
120
+
121
+ decision = fitness_gate(
122
+ FitnessMetrics(deflection_rate=0.61, keep_set_score=0.92), # base, measured by you
123
+ FitnessMetrics(deflection_rate=0.68, keep_set_score=0.92), # candidate retrain
124
+ )
125
+ print(decision.summary()) # [PROMOTED] deflection 61% -> 68% (more deflection, no regression)
126
+ ```
127
+
128
+ ## What is deliberately NOT here
129
+
130
+ - Artemis's four oracle families, its providers/proxy, pricing, audit and
131
+ dashboard — cost/tool-call-specific, they stay in Artemis.
132
+ - Avalon's runtime, planners, and consolidation wiring.
133
+ - Any trainer. `fitness.py` decides; it does not train. Each product owns its
134
+ LoRA/fine-tune stack and its metric measurement.
135
+
136
+ ## Docs
137
+
138
+ - `docs/data-volume-falsifier.sql` — the JARVIS data-volume falsifier: SELECT-only
139
+ per-day counts of Carter-signal (user messages, observed-provenance commitments,
140
+ episodic memories, user facts, eval scorecards) over the last 30 days, to test
141
+ whether the real system produces enough usable training pairs/day to feed the
142
+ nightly trainer (the "~50/day starves it" question). Run with
143
+ `psql "$DATABASE_URL" -f docs/data-volume-falsifier.sql`.
144
+
145
+ ## Development
146
+
147
+ ```bash
148
+ python -m pytest # stdlib-only package; pytest is the only dev dependency
149
+ ```
150
+
151
+ MIT © Lunar Labs
@@ -0,0 +1,140 @@
1
+ # lunarlabs-core
2
+
3
+ **The product-neutral spine shared by Lunar Labs products.**
4
+
5
+ ```
6
+ candidate (cheap/local) → verify (deterministic oracle) → pass? ✓ done
7
+ → fail/abstain? ↑ teacher (frontier) → verify
8
+ ↓
9
+ append-only trace capture (both verdicts label-worthy)
10
+ ↓
11
+ training-pair extraction (retrieval-task Q→A, NEVER raw transcripts)
12
+ ↓
13
+ nightly consolidation → FITNESS GATE (promote only if deflection
14
+ improves without regression) → ship or roll back
15
+ ```
16
+
17
+ Two products are built on this spine:
18
+
19
+ - **Artemis** — a small-model-first cascade for tool calls. It uses the spine to
20
+ deflect spend from frontier models to on-device models; its four oracle
21
+ families (agreement, provenance, structural, execution) are product-specific
22
+ and stay in Artemis.
23
+ - **Avalon** — an adaptive AI runtime. It uses the same spine for its own
24
+ capture → verify → consolidate → gate loop over runtime behavior. Avalon's
25
+ vision is much larger than this library; `lunarlabs-core` is only the shared
26
+ contracts both products import so the safety semantics (abstention, fail-open,
27
+ gating) are defined — and tested — exactly once.
28
+
29
+ This library is stdlib-only and deliberately boring. What is shared is the
30
+ *contract*; what is measured, drafted, and trained stays in each product.
31
+
32
+ ## What the spine defines
33
+
34
+ | module | contract | who consumes it |
35
+ |---|---|---|
36
+ | `verdict.py` | The three-valued verdict: PASS / FAIL / **ABSTAIN**, expressed as `ok` × `label`. | Both products' oracles. |
37
+ | `verifier.py` | The `Oracle` protocol (`output → Verdict`), `all_of` composition, one trivial example oracle. | Both products plug in their own domain oracles. |
38
+ | `escalation.py` | `verified_cascade`: candidate-first routing, **fail-open** on any internal error, `LUNARLABS_*` kill switch. | Artemis's router; Avalon's runtime decision path. |
39
+ | `trace.py` | `TraceRecord` + append-only JSONL `TraceWriter`, with the dual-label capture gate as the only write path. | Artemis's escalation capture; Avalon's observed-event capture. |
40
+ | `pairs.py` | `TrainingPair`: retrieval-task Q→A with provenance. **Never raw transcripts.** | Both products' nightly consolidation / LoRA pipelines. |
41
+ | `fitness.py` | `fitness_gate`: pure decision over measured metrics — promote only on meaningful deflection gain with no keep-set regression. | Both products' nightly retrain jobs. |
42
+
43
+ ## The ABSTAIN third verdict is load-bearing
44
+
45
+ Every oracle answers three things, not two. When a check cannot *mechanically*
46
+ decide, it declines:
47
+
48
+ - **PASS + label** — route on it, and it is strong enough to write training data.
49
+ - **FAIL + label** — real evidence of a wrong answer; the pair may be captured.
50
+ - **ABSTAIN** (`ok=False, label=False`) — escalate (costs money, never ships a
51
+ wrong answer) but the trace writer **refuses the pair**, so it poisons
52
+ nothing. Training on abstentions would teach the model the reference's
53
+ arbitrary surface conventions, which raises future agreement, which inflates
54
+ measured deflection: a self-fulfilling metric.
55
+
56
+ `TraceWriter.record_escalation` enforces both conjuncts — the teacher's pass
57
+ must be label-worthy AND the candidate's failure must be label-worthy — so a
58
+ weak oracle cannot poison the retrain it feeds.
59
+
60
+ ## Fail-open, and the kill switch
61
+
62
+ Every spine-internal failure — candidate down, timeout, malformed reply, a bug
63
+ in the verifier itself — routes to the teacher instead of raising. Breaking
64
+ the product loses the user permanently; a missed deflection costs a fraction
65
+ of a cent. Nothing in `verified_cascade` raises past its caller.
66
+
67
+ ```bash
68
+ export LUNARLABS_DISABLED=1 # every call goes straight to the teacher
69
+ export LUNARLABS_DEFLECT=off # same effect, second switch
70
+ ```
71
+
72
+ Both are read fresh on every call — flipping the env var takes effect without
73
+ a restart or redeploy.
74
+
75
+ ## Training pairs are retrieval-task Q→A — never raw transcripts
76
+
77
+ From the JARVIS research: a raw transcript trains surface imitation (phrasing,
78
+ length, the teacher's tics) and drowns the signal you paid an escalation for.
79
+ A Q→A pair isolates exactly the thing the candidate got wrong: *given THIS
80
+ observed context, the correct answer is THAT.* Every `TrainingPair` therefore
81
+ has `task_type="retrieval"`, a question projected from the trace's context,
82
+ the teacher's verified output as the answer, and full provenance (source trace
83
+ id, candidate failure stage/reasons). A pair without provenance is
84
+ unauditable and should be refused downstream.
85
+
86
+ ## Usage
87
+
88
+ ```python
89
+ from lunarlabs_core import (
90
+ TraceWriter, verified_cascade, non_empty_oracle,
91
+ extract_pairs, write_pairs_jsonl, FitnessMetrics, fitness_gate,
92
+ )
93
+
94
+ trace = TraceWriter("traces.jsonl")
95
+
96
+ result = verified_cascade(
97
+ candidate=lambda: small_model.answer(request), # cheap path first
98
+ teacher=lambda: frontier_model.answer(request), # only on verified failure
99
+ verify=my_oracle, # your deterministic oracle
100
+ context={"request_id": "..."}, # opaque to the spine
101
+ trace=trace,
102
+ )
103
+ result.handled_by # "candidate" | "teacher"
104
+ result.failed_open # True if the spine broke and got out of the way
105
+
106
+ # Nightly consolidation:
107
+ pairs = extract_pairs(trace.records())
108
+ write_pairs_jsonl(pairs, "train.jsonl")
109
+
110
+ decision = fitness_gate(
111
+ FitnessMetrics(deflection_rate=0.61, keep_set_score=0.92), # base, measured by you
112
+ FitnessMetrics(deflection_rate=0.68, keep_set_score=0.92), # candidate retrain
113
+ )
114
+ print(decision.summary()) # [PROMOTED] deflection 61% -> 68% (more deflection, no regression)
115
+ ```
116
+
117
+ ## What is deliberately NOT here
118
+
119
+ - Artemis's four oracle families, its providers/proxy, pricing, audit and
120
+ dashboard — cost/tool-call-specific, they stay in Artemis.
121
+ - Avalon's runtime, planners, and consolidation wiring.
122
+ - Any trainer. `fitness.py` decides; it does not train. Each product owns its
123
+ LoRA/fine-tune stack and its metric measurement.
124
+
125
+ ## Docs
126
+
127
+ - `docs/data-volume-falsifier.sql` — the JARVIS data-volume falsifier: SELECT-only
128
+ per-day counts of Carter-signal (user messages, observed-provenance commitments,
129
+ episodic memories, user facts, eval scorecards) over the last 30 days, to test
130
+ whether the real system produces enough usable training pairs/day to feed the
131
+ nightly trainer (the "~50/day starves it" question). Run with
132
+ `psql "$DATABASE_URL" -f docs/data-volume-falsifier.sql`.
133
+
134
+ ## Development
135
+
136
+ ```bash
137
+ python -m pytest # stdlib-only package; pytest is the only dev dependency
138
+ ```
139
+
140
+ MIT © Lunar Labs
@@ -0,0 +1,35 @@
1
+ """lunarlabs-core: the product-neutral spine shared by Lunar Labs products.
2
+
3
+ capture -> verify/oracle -> escalate (fail-open) ->
4
+ training-pair extraction (retrieval Q->A) ->
5
+ nightly consolidation -> fitness gate / rollback
6
+
7
+ Products (Artemis, Avalon) plug in their own drafters, oracles, and metrics;
8
+ the contracts live here.
9
+ """
10
+ from .verdict import Verdict, abstain, failed, passed
11
+ from .verifier import Oracle, all_of, non_empty_oracle
12
+ from .trace import TraceRecord, TraceWriter
13
+ from .escalation import (
14
+ DEFLECT_ENV,
15
+ DISABLED_ENV,
16
+ Drafter,
17
+ EscalationResult,
18
+ deflection_disabled,
19
+ verified_cascade,
20
+ )
21
+ from .fitness import FitnessMetrics, GateDecision, fitness_gate
22
+ from .pairs import TrainingPair, extract_pairs, pair_from_trace, write_pairs_jsonl
23
+
24
+ __version__ = "0.1.0"
25
+
26
+ __all__ = [
27
+ "Verdict", "abstain", "failed", "passed",
28
+ "Oracle", "all_of", "non_empty_oracle",
29
+ "TraceRecord", "TraceWriter",
30
+ "Drafter", "EscalationResult", "deflection_disabled", "verified_cascade",
31
+ "DISABLED_ENV", "DEFLECT_ENV",
32
+ "FitnessMetrics", "GateDecision", "fitness_gate",
33
+ "TrainingPair", "extract_pairs", "pair_from_trace", "write_pairs_jsonl",
34
+ "__version__",
35
+ ]
@@ -0,0 +1,129 @@
1
+ """The escalation contract: candidate first, teacher only when the check fails.
2
+
3
+ The spine's thesis in one function. The candidate (small, cheap, local,
4
+ private) produces an output. A deterministic verifier checks it. Pass — done.
5
+ Fail or abstain — escalate to the teacher (frontier, strong, expensive),
6
+ verify again, and capture the (candidate-failed, teacher-passed) pair as
7
+ training data. The system relies on the teacher less every night the
8
+ candidate retrains on its own misses.
9
+
10
+ FAIL OPEN. Every spine-internal failure — candidate down, timeout, malformed
11
+ reply, a bug in the verifier itself — routes to the teacher instead of
12
+ raising. Breaking the product loses the user permanently; a missed deflection
13
+ costs a fraction of a cent. That asymmetry is not close, so nothing in this
14
+ module is allowed to raise past `verified_cascade`.
15
+
16
+ KILL SWITCH. Set LUNARLABS_DISABLED=1 (or LUNARLABS_DEFLECT=off) in the
17
+ environment and every call goes straight to the teacher — no code change, no
18
+ redeploy. The env is read fresh on every call, deliberately: flipping the
19
+ variable must take effect without restarting anything.
20
+ """
21
+ from __future__ import annotations
22
+
23
+ import os
24
+ from dataclasses import dataclass, field
25
+ from typing import Any, Callable, Dict, List, Optional
26
+
27
+ from .trace import TraceWriter
28
+ from .verdict import Verdict
29
+ from .verifier import Oracle
30
+
31
+ _TRUE = {"1", "true", "yes", "on"}
32
+ _OFF = {"off", "0", "false", "no"}
33
+
34
+ DISABLED_ENV = "LUNARLABS_DISABLED"
35
+ DEFLECT_ENV = "LUNARLABS_DEFLECT"
36
+
37
+ # Produces an output (text / tool call / plan / anything). Zero-arg so the
38
+ # caller closes over its own request state.
39
+ Drafter = Callable[[], Any]
40
+
41
+
42
+ def deflection_disabled(env: Optional[Dict[str, str]] = None) -> bool:
43
+ """The kill switch. Read fresh on every call, deliberately."""
44
+ e = os.environ if env is None else env
45
+ if str(e.get(DISABLED_ENV, "")).strip().lower() in _TRUE:
46
+ return True
47
+ return str(e.get(DEFLECT_ENV, "")).strip().lower() in _OFF
48
+
49
+
50
+ @dataclass
51
+ class EscalationResult:
52
+ output: Any # the chosen output
53
+ handled_by: str # "candidate" | "teacher"
54
+ verified: bool # did the chosen output pass verification?
55
+ escalated: bool
56
+ candidate_verdict: Verdict
57
+ teacher_verdict: Optional[Verdict] = None
58
+ failed_open: bool = False # the spine broke and got out of the way
59
+ failure: str = "" # why, for the operator
60
+ captured: bool = False # did this call write a training pair?
61
+
62
+
63
+ def verified_cascade(
64
+ candidate: Drafter,
65
+ teacher: Drafter,
66
+ verify: Oracle,
67
+ *,
68
+ context: Optional[Dict[str, Any]] = None,
69
+ trace: Optional[TraceWriter] = None,
70
+ enabled: Optional[bool] = None,
71
+ ) -> EscalationResult:
72
+ """Run the candidate, verify, escalate on failure, capture the fix.
73
+
74
+ `enabled=None` reads the kill switch from the environment; pass a bool to
75
+ decide programmatically (tests, per-request overrides).
76
+ """
77
+ off = (not enabled) if enabled is not None else deflection_disabled()
78
+ candidate_out: Any = None
79
+ cv = Verdict(False, ["deflection disabled"], "disabled", label=False)
80
+ failure = ""
81
+
82
+ if not off:
83
+ try:
84
+ candidate_out = candidate()
85
+ cv = verify(candidate_out)
86
+ except Exception as exc: # noqa: BLE001 — fail open, never break the caller
87
+ candidate_out, failure = None, f"{type(exc).__name__}: {exc}"
88
+ cv = Verdict(False, [failure], "spine_error", label=False)
89
+
90
+ if cv.ok:
91
+ return EscalationResult(
92
+ output=candidate_out, handled_by="candidate",
93
+ verified=True, escalated=False, candidate_verdict=cv,
94
+ )
95
+
96
+ teacher_out = teacher()
97
+ try:
98
+ tv = verify(teacher_out)
99
+ except Exception as exc: # noqa: BLE001 — a broken oracle must not eat the
100
+ # teacher's answer. Hand it back unverified and say why.
101
+ failure = failure or f"{type(exc).__name__}: {exc}"
102
+ tv = Verdict(False, [failure], "spine_error", label=False)
103
+
104
+ # Only capture when the candidate actually produced a wrong answer. A
105
+ # crash is not evidence about the model, and disabled routing produced
106
+ # nothing to learn from. record_escalation enforces both label flags.
107
+ captured = False
108
+ if trace is not None and candidate_out is not None and not failure:
109
+ try:
110
+ captured = trace.record_escalation(
111
+ context or {}, candidate_out, cv, teacher_out, tv,
112
+ ) is not None
113
+ except Exception: # noqa: BLE001 — capture is a bonus, never a blocker
114
+ pass
115
+
116
+ # A broken oracle must not eat the teacher's answer: when the verifier
117
+ # crashed (spine_error) rather than ruled, hand the teacher's output back
118
+ # unverified. When the teacher genuinely failed verification, prefer the
119
+ # candidate's attempt — both are known-wrong, but the candidate's failure
120
+ # at least carries the verifier's reasons.
121
+ chosen = (teacher_out
122
+ if tv.ok or tv.stage == "spine_error" or candidate_out is None
123
+ else candidate_out)
124
+ return EscalationResult(
125
+ output=chosen, handled_by="teacher",
126
+ verified=tv.ok, escalated=True, candidate_verdict=cv,
127
+ teacher_verdict=tv, failed_open=bool(failure), failure=failure,
128
+ captured=captured,
129
+ )
@@ -0,0 +1,79 @@
1
+ """The fitness gate: promote a retrain only if it is measurably better.
2
+
3
+ traces -> train candidate -> FITNESS GATE -> promote or rollback
4
+
5
+ The gate is the safety keystone of the nightly consolidation: a retrained
6
+ candidate is promoted only if it deflects MORE work to the cheap path without
7
+ regressing on a held-out "keep" set. That prevents the classic disaster of
8
+ silently shipping a worse model every night.
9
+
10
+ This module is the CONTRACT, not the wiring. It is a pure function over
11
+ measured metrics — it does not know what a model is, how deflection was
12
+ measured, or what the keep set contains. Each product measures its own
13
+ `FitnessMetrics` (Artemis: cascade replay over eval cases; Avalon: whatever its
14
+ runtime's held-out tasks are) and hands the numbers here. The rule is what is
15
+ shared:
16
+
17
+ 1. REGRESSION CHECK FIRST. If the candidate's keep-set score dropped by
18
+ more than `regression_tolerance`, reject — regardless of the gain.
19
+ 2. Then require a MEANINGFUL gain: candidate deflection must beat base
20
+ deflection by at least `min_gain`. Equal is not better; retraining has
21
+ a cost and a risk, so the bar is strictly above noise.
22
+ """
23
+ from __future__ import annotations
24
+
25
+ from dataclasses import dataclass
26
+ from typing import Optional
27
+
28
+
29
+ @dataclass
30
+ class FitnessMetrics:
31
+ """Measured performance of one model generation, produced by the product."""
32
+ deflection_rate: float # fraction of work handled by the cheap path, 0..1
33
+ keep_set_score: Optional[float] = None # held-out regression score, higher is better;
34
+ # None means "no keep set was run"
35
+
36
+
37
+ @dataclass
38
+ class GateDecision:
39
+ promoted: bool
40
+ base_deflection: float
41
+ candidate_deflection: float
42
+ reason: str
43
+
44
+ def summary(self) -> str:
45
+ arrow = f"{self.base_deflection:.0%} -> {self.candidate_deflection:.0%}"
46
+ verb = "PROMOTED" if self.promoted else "REJECTED"
47
+ return f"[{verb}] deflection {arrow} ({self.reason})"
48
+
49
+
50
+ def fitness_gate(
51
+ base: FitnessMetrics,
52
+ candidate: FitnessMetrics,
53
+ *,
54
+ min_gain: float = 0.01,
55
+ regression_tolerance: float = 0.0,
56
+ ) -> GateDecision:
57
+ """Decide whether `candidate` may replace `base`.
58
+
59
+ `min_gain`: the smallest deflection improvement that justifies a promote.
60
+ `regression_tolerance`: how much keep-set score the candidate may lose
61
+ (use a small positive value if the keep-set metric is noisy; 0.0 is strict).
62
+ """
63
+ if base.keep_set_score is not None and candidate.keep_set_score is not None:
64
+ if candidate.keep_set_score < base.keep_set_score - regression_tolerance:
65
+ return GateDecision(
66
+ False, base.deflection_rate, candidate.deflection_rate,
67
+ f"regressed on keep set "
68
+ f"{base.keep_set_score:.0%}->{candidate.keep_set_score:.0%}",
69
+ )
70
+
71
+ if candidate.deflection_rate >= base.deflection_rate + min_gain:
72
+ return GateDecision(
73
+ True, base.deflection_rate, candidate.deflection_rate,
74
+ "more deflection, no regression",
75
+ )
76
+ return GateDecision(
77
+ False, base.deflection_rate, candidate.deflection_rate,
78
+ "no meaningful gain",
79
+ )
@@ -0,0 +1,89 @@
1
+ """Training-pair extraction: traces become Q->A pairs, never raw transcripts.
2
+
3
+ THE FORMAT RULE, and it is not negotiable: every training pair is stored as a
4
+ RETRIEVAL-TASK question-answer pair — a question whose answering requires
5
+ recalling or deriving a specific fact from what the system observed, answered
6
+ by the verified teacher output. Pairs are NEVER stored as raw transcripts.
7
+
8
+ Why (from the JARVIS research): a raw transcript trains the model to imitate
9
+ the surface of a conversation — phrasing, length, the teacher's tics — and the
10
+ signal you actually paid an escalation for is drowned in tokens that carry no
11
+ information. Worse, transcripts teach continuity that never happened: the
12
+ model learns to complete conversations, not to retrieve facts. A Q->A pair
13
+ isolates exactly the thing the candidate got wrong: "given THIS situation,
14
+ the correct answer is THAT." Retrieval form is what lets the nightly retrain
15
+ move the fitness metric instead of the perplexity metric.
16
+
17
+ Concretely: the QUESTION is built from the trace record's context (the
18
+ situation the candidate faced), and the ANSWER is the teacher's verified
19
+ output. `question_from_context` is the one place a product plugs in how its
20
+ context becomes a question; the default just asks for the correct output given
21
+ the serialized context.
22
+
23
+ Provenance rides along on every pair: which trace it came from, what the
24
+ candidate got wrong, and when. A pair without provenance is unauditable and
25
+ should be refused by any downstream trainer.
26
+ """
27
+ from __future__ import annotations
28
+
29
+ import json
30
+ import time
31
+ from dataclasses import asdict, dataclass, field
32
+ from pathlib import Path
33
+ from typing import Any, Callable, Dict, List, Optional, Union
34
+
35
+ # The default context->question projection. Products with structured context
36
+ # (Artemis: messages + tools; Avalon: observed runtime state) should pass their
37
+ # own `question_from_context` that phrases the retrieval task in their domain.
38
+ def default_question(context: Dict[str, Any]) -> str:
39
+ return (
40
+ "Given the following observed context, produce the correct output.\n\n"
41
+ + json.dumps(context, sort_keys=True, default=str)
42
+ )
43
+
44
+
45
+ @dataclass
46
+ class TrainingPair:
47
+ """One retrieval-task Q->A pair with full provenance."""
48
+ question: str
49
+ answer: str
50
+ task_type: str = "retrieval" # the format contract; do not set to "transcript"
51
+ source_trace_id: str = ""
52
+ candidate_failure_stage: str = ""
53
+ candidate_failure_reasons: List[str] = field(default_factory=list)
54
+ ts: float = field(default_factory=time.time)
55
+
56
+
57
+ def pair_from_trace(
58
+ record: Dict[str, Any],
59
+ *,
60
+ question_from_context: Optional[Callable[[Dict[str, Any]], str]] = None,
61
+ ) -> TrainingPair:
62
+ """Extract one Q->A pair from a trace record (see trace.TraceRecord)."""
63
+ project = question_from_context or default_question
64
+ answer = record.get("teacher_output")
65
+ return TrainingPair(
66
+ question=project(record.get("context") or {}),
67
+ answer=answer if isinstance(answer, str) else json.dumps(answer, default=str),
68
+ source_trace_id=record.get("record_id", ""),
69
+ candidate_failure_stage=record.get("candidate_failure_stage", ""),
70
+ candidate_failure_reasons=list(record.get("candidate_failure_reasons") or []),
71
+ )
72
+
73
+
74
+ def extract_pairs(
75
+ records: List[Dict[str, Any]],
76
+ *,
77
+ question_from_context: Optional[Callable[[Dict[str, Any]], str]] = None,
78
+ ) -> List[TrainingPair]:
79
+ return [pair_from_trace(r, question_from_context=question_from_context)
80
+ for r in records]
81
+
82
+
83
+ def write_pairs_jsonl(pairs: List[TrainingPair], out: Union[str, Path]) -> int:
84
+ """Export pairs as JSONL for the nightly consolidation. Returns count."""
85
+ out = Path(out)
86
+ with out.open("w") as f:
87
+ for p in pairs:
88
+ f.write(json.dumps(asdict(p), default=str) + "\n")
89
+ return len(pairs)