Learning Layer¶
learning_layer — Unified T0-T3 learning layer with OODA-driven self-training loops.
Cluster: Uncategorised | Type: component | MCP Tools: None
Overview¶
Unified T0-T3 learning pipeline orchestrating OODA-driven self-training loops. Manages dataset construction from JSONL sources, tier-specific learning strategies (T0 baseline through T3 theory-building), failure classification, prompt evolution, skill generation, and harness artifact production.
Supports any LLM or coding agent backend via pluggable AgentFn callables. MCP, REST, and GUI surfaces can also use the built-in LLMBlock runner by passing agent_runner="llm" with model/scoring options. Includes a TheoryStore for persisting learned theories, LearningRunner for executing training loops, and an observable event system with JSONL/log/callback observers.
When to use:
- Training a component to improve accuracy on a domain-specific benchmark
- Running tiered learning loops (T0 baseline, T1 audit, T2 evolve, T3 theory-build)
- Building and evaluating datasets from JSONL problem sets
Dataset JSONL fields:
Each line should contain task text plus either an expected answer or a rubric. The loader accepts common aliases:
- Task text:
input,prompt,question,goal,task, ordescription - Expected answer:
expected,expected_output,answer,target,ground_truth,reference,reference_answer, oroutput - Optional scoring metadata:
rubric,criteria,score_mode,score_threshold,domain
Scoring modes:
normalized_exact-- strict normalized exact match; best for short deterministic answerscontains-- passes when expected text appears in the answertoken_f1-- partial-credit token overlap for longer answersrubric/llm_judge-- asks the configured LLM to score against a rubric from 0.0 to 1.0
Production controls:
- Public MCP/REST/GUI calls enforce a workspace root for dataset import and harness import/export.
- Preflight limits can cap
max_dataset_instances,max_estimated_llm_calls, andmax_estimated_cost_usd. holdout=true/train_with_holdoutsplits a dataset, trains on the train split, and reports holdout accuracy.overwrite=trueis required to replace an existing run ID.backend_healthreports configured runner/model and live-smoke environment state.capabilities/learning_capabilitiesreports strategies, scoring modes, executable ops, backend probe evidence, promotion resolver status, and the current weakest link.- Learning outputs preserve existing
success,message, anddatafields and addcompletion_state,warning_card, andevidence. States use the canonical trio directly:verified,qualified-draft,blocked-escalated. - Harness export/import scans artifacts for dangerous code, network/file writes, prompt-injection phrases, and likely secret references.
Example:
from mvp.learning_layer import LearningLayerBlock, LearningInput
block = LearningLayerBlock()
block.register_agent_fn(my_solver_fn)
result = block.infer(LearningInput(
op="train",
dataset_name="benchmarks",
strategy="T1",
))
# result.value -> LearningOutput with accuracy, theories, artifacts
Caveats and known limitations:
learning_trainandlearning_evaluatecan call a live LLM backend. Before a customer pilot or production workflow, verify the exact deployment machine,G6_WORKSPACE, model/provider, credentials, and environment by running:
$env:G6_WORKSPACE="C:\path\to\pilot-workspace"
$env:G6_LEARNING_LAYER_LIVE_SMOKE="1"
python -m pytest -q tests/mvp/learning_layer/test_live_llm_smoke.py
If this smoke test fails, treat the backend/model/workspace configuration as not launch-ready. The normal unit suite mocks LLM calls and does not prove the live provider path works. - Exact-match scoring is only appropriate for short deterministic answers. Use contains, token_f1, or a rubric/LLM judge for open-ended outputs. - Rubric/LLM-judge scoring costs an extra LLM call per evaluated task and is only as reliable as the rubric. Keep rubrics concrete and review borderline cases. - Training improves a harness, prompt, theory store, or generated artifacts. It does not fine-tune model weights. - Lift is not guaranteed. Saturated datasets, vague expected answers, or weak rubrics may show no improvement. - Use held-out or frozen validation data before trusting artifacts in production. Do not judge launch readiness only on the same examples used for training. - Public surfaces reject dataset and harness paths outside the configured workspace root. Set G6_WORKSPACE deliberately for deployments. - Preflight budgets are estimates; actual provider billing can still vary by model, prompt length, retries, and judge calls. - For durable learning, set a persistent LearningConfig.db_path; in-memory stores are useful for tests but do not survive process restart. - For durable REST/MCP process state, set LearningConfig.state_dir; otherwise loaded datasets and run summaries are process-local. - Harness import/export preserves theories and artifacts, but users should review generated prompts/skills before applying them to sensitive workflows. - Checkpoint resume relies on stable instance IDs. Changing the dataset between runs can invalidate resume assumptions.
Works well with: self_training, align_evals, token_budget
Public API¶
DatasetBuilder¶
Builds and manipulates datasets for the learning loop.
Methods:
from_jsonl(path: str, name: str | None = None) -> Dataset¶
from_generator(gen: ProceduralGenerator, n: int, name: str = 'generated') -> Dataset¶
split(dataset: Dataset, train_ratio: float = 0.8, seed: int | None = None) -> tuple[Dataset, Dataset]¶
sample(dataset: Dataset, n: int, seed: int | None = None) -> Dataset¶
merge(datasets: list[Dataset], name: str | None = None) -> Dataset¶
filter(dataset: Dataset, predicate: Callable[[TaskInstance], bool]) -> Dataset¶
FailureClassifier(Protocol)¶
Protocol for failure classification — users can provide custom implementations.
Methods:
classify(result: TaskResult) -> FailureAnalysis¶
DefaultClassifier¶
Heuristic-based failure classifier (zero LLM cost).
Constructor:
| Parameter | Type | Default |
|---|---|---|
timeout_ms | int | _TIMEOUT_THRESHOLD_MS |
high_turns | int | _HIGH_TURN_THRESHOLD |
Methods:
classify(result: TaskResult) -> FailureAnalysis¶
LearningInput¶
| Field | Type | Default |
|---|---|---|
op | str | required |
source_path | str | '' |
generator_code | str | '' |
dataset_name | str | '' |
strategy | str | 'T0' |
n | int | 0 |
split_ratio | float | 0.8 |
config | dict[str, Any] | field(default_factory=dict) |
run_id | str | '' |
output_path | str | '' |
bundle_path | str | '' |
checkpoint_path | str | '' |
status_filter | str | 'active' |
LearningOutput¶
| Field | Type | Default |
|---|---|---|
op | str | '' |
success | bool | True |
message | str | '' |
data | dict[str, Any] | field(default_factory=dict) |
completion_state | str | 'qualified-draft' |
warning_card | str | '' |
evidence | dict[str, Any] | field(default_factory=dict) |
request_id | str | '' |
task_id | str | '' |
run_id | str | '' |
LearningLayerBlock(AIBlock['LearningInput', 'LearningOutput', dict])¶
Unified learning layer — T0-T3 OODA strategies, dataset construction, self-training loops.
Constructor:
| Parameter | Type | Default |
|---|---|---|
config | LearningConfig \| None | None |
Methods:
register_agent_fn(name: str, fn: AgentFn) -> None¶
register_dataset(dataset: Dataset) -> None¶
infer(data: LearningInput) -> Result[LearningOutput]¶
list_patterns() -> list[dict[str, Any]]¶
Read-only catalog of the agentic-pattern slugs this component applies.
LearningEvent¶
| Field | Type | Default |
|---|---|---|
event_type | str | required |
timestamp | str | field(default_factory=lambda: datetime.now(timezone.utc).isoformat()) |
data | dict[str, Any] | field(default_factory=dict) |
observer_errors | list['ObserverFailure'] | field(default_factory=list) |
LearningObserver(Protocol)¶
Methods:
on_event(event: LearningEvent) -> None¶
LogObserver¶
Logs events via stdlib logging.
Constructor:
| Parameter | Type | Default |
|---|---|---|
logger_name | str | 'learning_layer' |
Methods:
on_event(event: LearningEvent) -> None¶
JsonlObserver¶
Appends events as JSONL to a file.
Constructor:
| Parameter | Type | Default |
|---|---|---|
path | str | required |
Methods:
on_event(event: LearningEvent) -> None¶
CallbackObserver¶
Wraps a user-provided callback function.
Constructor:
| Parameter | Type | Default |
|---|---|---|
callback | Callable[[LearningEvent], None] | required |
Methods:
on_event(event: LearningEvent) -> None¶
EventEmitter¶
Dispatches events to all registered observers.
Constructor:
| Parameter | Type | Default |
|---|---|---|
observers | list[LearningObserver] \| None | None |
Methods:
add_observer(observer: LearningObserver) -> None¶
emit(event_type: str, **data: Any) -> list[ObserverFailure]¶
Dispatch
event_typeto every observer.
ObservationResult¶
Raw observations from running tasks — the Observe phase output.
| Field | Type | Default |
|---|---|---|
results | list[TaskResult] | field(default_factory=list) |
accuracy | float | 0.0 |
signals_of_interest | list[str] | field(default_factory=list) |
Methods:
n_passed() -> int¶
n_failed() -> int¶
Orientation¶
Mental model update — the Orient phase output.
| Field | Type | Default |
|---|---|---|
failure_analyses | list[FailureAnalysis] | field(default_factory=list) |
matched_theories | list[Theory] | field(default_factory=list) |
new_theories | list[Theory] | field(default_factory=list) |
implicit_actions | list[HarnessArtifact] | field(default_factory=list) |
context | dict[str, Any] | field(default_factory=dict) |
Methods:
all_theories() -> list[Theory]¶
TempoDecision¶
Whether to continue cycling and at what pace.
| Field | Type | Default |
|---|---|---|
converged | bool | False |
reason | str | '' |
next_stage | str \| None | None |
next_n | int \| None | None |
PromptEvolver¶
Selects top theories by strength and builds a compact prompt addendum.
Constructor:
| Parameter | Type | Default |
|---|---|---|
store | TheoryStore | required |
max_theories | int | 10 |
max_chars | int | 6000 |
Methods:
build_addendum(task_meta: dict[str, Any] | None = None) -> PromptAddendum¶
record_applications(instance_id: str, theory_ids: list[str]) -> None¶
record_outcome(instance_id: str, theory_ids: list[str], passed: bool, same_failure: bool = False) -> None¶
LearningRunner¶
Drives OODA cycles through the selected strategy.
Constructor:
| Parameter | Type | Default |
|---|---|---|
config | LearningConfig \| None | None |
observers | list[LearningObserver] \| None | None |
Methods:
run(strategy: LearningStrategy, dataset: Dataset, agent_fn: AgentFn, run_dir: str | None = None) -> LearningResult¶
resume(checkpoint_path: str, strategy: LearningStrategy, dataset: Dataset, agent_fn: AgentFn) -> LearningResult¶
TaskInstance¶
| Field | Type | Default |
|---|---|---|
input | str | required |
expected_output | str | required |
instance_id | str | '' |
metadata | dict[str, Any] | field(default_factory=dict) |
DatasetSplit¶
| Field | Type | Default |
|---|---|---|
name | str | required |
tasks | list[dict[str, Any]] | field(default_factory=list) |
Methods:
to_instances() -> list[TaskInstance]¶
Dataset¶
| Field | Type | Default |
|---|---|---|
name | str | required |
instances | list[TaskInstance] | field(default_factory=list) |
split | str | 'full' |
splits | list[DatasetSplit] | field(default_factory=list) |
AgentTrace¶
| Field | Type | Default |
|---|---|---|
tool_calls | list[str] | field(default_factory=list) |
num_turns | int | 0 |
cost_usd | float | 0.0 |
duration_ms | int | 0 |
error | str \| None | None |
patch_bytes | int | 0 |
TaskResult¶
| Field | Type | Default |
|---|---|---|
instance_id | str | required |
actual_output | str | required |
passed | bool | required |
score | float | 0.0 |
signals | dict[str, Any] | field(default_factory=dict) |
trace | AgentTrace | field(default_factory=AgentTrace) |
FailureAnalysis¶
| Field | Type | Default |
|---|---|---|
instance_id | str | required |
failure_codes | list[str] | field(default_factory=list) |
primary_code | str | '' |
signals | dict[str, Any] | field(default_factory=dict) |
context | str | '' |
Theory¶
| Field | Type | Default |
|---|---|---|
theory_id | str | required |
failure_code | str | required |
trigger | str | 'always' |
instruction | str | '' |
rationale | str | '' |
confirmations | int | 0 |
disconfirmations | int | 0 |
strength | float | 0.5 |
status | str | 'active' |
source_instances | list[str] | field(default_factory=list) |
skill_content | str \| None | None |
tool_code | str \| None | None |
tool_name | str \| None | None |
created_at | float | field(default_factory=time.time) |
updated_at | float | field(default_factory=time.time) |
Methods:
compute_strength() -> float¶
should_retire() -> bool¶
PromptAddendum¶
| Field | Type | Default |
|---|---|---|
text | str | required |
theory_ids | list[str] | field(default_factory=list) |
ConfigOverride¶
| Field | Type | Default |
|---|---|---|
overrides | dict[str, Any] | field(default_factory=dict) |
theory_ids | list[str] | field(default_factory=list) |
SkillDocument¶
| Field | Type | Default |
|---|---|---|
content | str | required |
theory_id | str | '' |
GeneratedTool¶
| Field | Type | Default |
|---|---|---|
code | str | required |
name | str | required |
theory_id | str | '' |
OODACycleRecord¶
| Field | Type | Default |
|---|---|---|
cycle | int | required |
stage | str | required |
phase | str | 'train' |
accuracy | float | 0.0 |
failure_breakdown | dict[str, int] | field(default_factory=dict) |
theories_matched | int | 0 |
theories_created | int | 0 |
implicit_actions | int | 0 |
explicit_decisions | int | 0 |
artifacts_produced | list[HarnessArtifact] | field(default_factory=list) |
theories_updated | list[str] | field(default_factory=list) |
observations | list[TaskResult] | field(default_factory=list) |
LearningResult¶
| Field | Type | Default |
|---|---|---|
cycles | list[OODACycleRecord] | field(default_factory=list) |
ooda_cycles_completed | int | 0 |
final_accuracy | float | 0.0 |
baseline_accuracy | float | 0.0 |
lift | float | 0.0 |
p_value | float | 1.0 |
artifacts | list[HarnessArtifact] | field(default_factory=list) |
theories | list[Theory] | field(default_factory=list) |
convergence_reason | str | '' |
phase | str | 'train' |
started_at | str | '' |
completed_at | str | '' |
validation_summary | dict[str, Any] | field(default_factory=dict) |
LearningCheckpoint¶
| Field | Type | Default |
|---|---|---|
cycle | int | 0 |
stage | str | 'pretest' |
completed_instances | list[str] | field(default_factory=list) |
partial_results | list[TaskResult] | field(default_factory=list) |
artifacts_so_far | list[HarnessArtifact] | field(default_factory=list) |
theories_snapshot | list[Theory] | field(default_factory=list) |
orientation_state | dict[str, Any] | field(default_factory=dict) |
timestamp | str | '' |
LearningConfig¶
| Field | Type | Default |
|---|---|---|
db_path | str | '' |
max_cycles | int | 10 |
stagnation_limit | int | 2 |
stagnation_delta | float | 0.01 |
budget_usd | float \| None | None |
max_theories | int | 10 |
max_addendum_chars | int | 6000 |
strength_retirement_threshold | float | 0.2 |
strength_implicit_threshold | float | 0.8 |
min_observations_for_retirement | int | 5 |
seed_theories | list[dict[str, str]] | field(default_factory=list) |
phase | str | 'train' |
mutation_allowed | bool \| None | None |
state_dir | str | '' |
workspace_root | str | '' |
max_dataset_instances | int \| None | None |
max_estimated_llm_calls | int \| None | None |
max_estimated_cost_usd | float \| None | None |
estimated_cost_per_call_usd | float | 0.01 |
SkillGenerator¶
Generates markdown skill guides when theories need escalation.
Constructor:
| Parameter | Type | Default |
|---|---|---|
store | TheoryStore | required |
Methods:
should_generate(failure_code: str) -> bool¶
generate(theory: Theory, analyses: list[FailureAnalysis] | None = None) -> SkillDocument | None¶
get_deployable_skills() -> list[dict[str, str]]¶
LearningStrategy(Protocol)¶
Each tier implements the OODA cycle with different depth and learning rules.
Methods:
tier() -> str¶
observe(dataset: Dataset, agent_fn: AgentFn) -> ObservationResult¶
orient(observations: ObservationResult, store: TheoryStore) -> Orientation¶
decide(orientation: Orientation) -> list[HarnessArtifact]¶
act(artifacts: list[HarnessArtifact], agent_fn: AgentFn) -> AgentFn¶
check_tempo(history: list[ObservationResult]) -> TempoDecision¶
TheoryStore¶
SQLite-backed theory persistence with Bayesian strength tracking.
Constructor:
| Parameter | Type | Default |
|---|---|---|
db_path | str | ':memory:' |
legacy_path | str \| None | None |
Methods:
close() -> None¶
seed_theories(seeds: list[dict[str, str]]) -> list[str]¶
create_theory(failure_code: str, trigger: str = 'always', instruction: str = '', rationale: str = '', initial_strength: float = 0.5, source_instances: list[str] | None = None) -> str¶
upsert_theory(theory: Theory) -> None¶
Insert or replace a serialized theory while preserving its id.
import_theories(theories: list[Theory]) -> int¶
get_active_theories() -> list[Theory]¶
get_theory(theory_id: str) -> Theory | None¶
find_by_failure_code(failure_code: str) -> list[Theory]¶
record_application(instance_id: str, theory_id: str) -> None¶
record_outcome(instance_id: str, theory_id: str, passed: bool, same_failure: bool = False) -> None¶
retire_theory(theory_id: str) -> None¶
update_source_instances(theory_id: str, instance_id: str) -> None¶
update_skill(theory_id: str, skill_content: str) -> None¶
update_tool(theory_id: str, tool_code: str, tool_name: str) -> None¶
get_application_count(failure_code: str) -> int¶
all_theories() -> list[Theory]¶
Functions¶
export_formal_experience_bundle(run_dirs: Iterable[str | Path], output_path: str | Path, forbidden_terms: Iterable[str] = (), deploy_db_path: str = '', allow_empty: bool = False) -> dict[str, Any]¶
Export answer-free formal T3 experience as a learning harness bundle.