mixle.task.task_decomposition module¶
Task decomposition as structure learning (CARD L4): does the loop’s five altitudes.
A task is a joint over (inputs, proposed intermediates, output). “Decomposition” is not a
heuristic split – it is DEPENDENCY-FOREST DISCOVERY over that joint, reusing the exact machinery
mixle.inference.structure already built for heterogeneous records: dependency_gain()
scores whether an edge (parent field -> child field) is worth its BIC complexity in nats, the same
model-based, family-agnostic test used for category->real, count->binary, or any other pair. A task’s
“is this decomposition good” question is the SAME question that module already answers for record
fields – routing output through a candidate intermediate is exactly a dependency edge, and MDL
gain (this module’s acceptance metric) IS the sum of dependency_gain()
along the discovered edges, by construction: a good decomposition is one whose edges pay for their own
complexity in compressed nats.
Three pieces of existing machinery are integrated here, not rebuilt:
mixle.inference.structure(dependency_gain/regression_gain/fit_linear_gaussian_edge) – scores and fits every edge (input -> intermediate, intermediate -> output) this module considers.mixle.task.plan_model/mixle.task.outcome_decomposer– a “decomposition” (which intermediates were used, in what order) is exactly a “plan” (which tool types were used, in what order):PlanModelfits a distribution over decompositions the same way it fits one over tool sequences, andDecompositionProposerrefits on high-outcome decompositions the same round-based waytrain_outcome_decomposer()refits a plan model on successful traces – so as(decomposition, outcome)pairs get logged from real task instances, FUTURE proposals shift toward what actually worked.mixle.task.design_prior(record_accepted_recipe/rank_design_families) – a decomposed vs. monolithic recipe is recorded under afamilytag on the existing design ledger, so which approach has actually won persists across tasks the same way a structural-family prior does elsewhere.forest = discover_decomposition(examples, candidate_intermediates) forest.chosen # [“m1”, “m2”], not [] (monolithic) – for a genuinely
# decomposable task
forest.mdl_gain # nats: positive means the decomposition compresses proposer = record_decomposition_outcome(proposer, forest.chosen, outcome) # trains the proposer
- class DecompositionProposer(plan_model, log=<factory>)[source]
Bases:
objectAn outcome-trained proposer over decompositions:
plan_modelscores/samples which intermediates (in what order) to route a task’s output through, and shifts toward higher-outcomedecompositions as they get logged –train_outcome_decomposer()’s refit-on-successes loop, applied to decomposition proposals instead of tool-call plans.
- class DependencyForest(chosen, edge_gains, mdl_gain, edges=<factory>)[source]
Bases:
objectA discovered decomposition of
output: the ordered list of parent fields it was routed through (chosen, e.g.["m1", "m2"]; empty means monolithic – no candidate intermediate or input clearedmin_gain), each step’s own gain, and the totalmdl_gain– the description-length gain (nats) of this decomposition over solvingoutputdirectly from the raw inputs. Positivemdl_gainmeans the decomposition COMPRESSES; by construction it is the sum of the chosen edges’ owndependency_gain()/regression_gain()scores.- Parameters:
- predict(inputs, candidate_intermediates)[source]
Sum of each chosen edge’s prediction from its own parent field – the decomposed model’s point estimate, used to compare predictive accuracy against the monolithic baseline.
- class TaskExample(inputs, output)[source]
Bases:
objectOne observed instance of a task: named inputs and the realized output. The joint this module reasons over is
(inputs, proposed_intermediates, output)–proposed_intermediatesare not stored here, they are RECOMPUTED per candidate bydiscover_decomposition()(a candidate intermediate is a function ofinputs, not a fixed observed field).
- discover_decomposition(task_examples, candidate_intermediates, *, max_parents=4, min_gain=0.0, max_its=30, seed=0)[source]
Discover which candidate intermediates
outputshould be routed through, by greedy forward selection scored withregression_gain()/dependency_gain()– the SAME model-based description-length testlearn_structure()uses for record fields, applied here per step against the current RESIDUAL so multiple intermediates (output = f(g(a), h(b))) can each earn their own edge, not just the single best one (a plainDependencyTreeDistributionforest allows one parent per field; a task’s output routinely needs several).Every raw input is itself a candidate parent, so a task with NO real decomposable structure correctly comes back with
chosen == [](monolithic: the raw inputs already explainoutputas well as anything) rather than inventing intermediates that do not pay for themselves.
- fit_decomposition(task_examples, decomposition, candidate_intermediates, *, max_its=30, seed=0)[source]
Fit a SPECIFIC, given
decomposition(in order) rather than discovering one – every named field is forced in, in order, scored and residualized the same waydiscover_decomposition()’s forward selection does. Lets a caller both score (DependencyForest.mdl_gain) and predict with (DependencyForest.predict()) a decomposition it did not necessarily search for – e.g. a deliberately-worse candidate, for the MDL-gain/outcome correlation check.
- init_decomposition_proposer(seed_decompositions)[source]
Fit the round-0 (imitation) proposer on a seed corpus of decompositions – e.g. every
chosena fewdiscover_decomposition()calls returned on early task instances.
- log_decomposition_recipe(design, mdl_gain, *, family)[source]
Record one decomposition attempt’s MDL gain into the existing design ledger under
family("decomposed"/MONOLITHIC) – a thin wrapper overrecord_accepted_recipe()sorank_design_families()andbest_family()answer “has decomposing this kind of task actually paid off” from real history, the same what-worked prior every other structural family search uses.
- mdl_score(task_examples, decomposition, candidate_intermediates, *, max_its=30, seed=0)[source]
The MDL gain (nats) of routing
outputthrough a SPECIFIC, givendecomposition– a thin accessor overfit_decomposition()for callers that only want the score (e.g. ranking several candidate decompositions for the MDL-gain/outcome correlation check).
- monolithic_predict(train, test)[source]
OLS fit of
outputon the raw inputs (every field jointly, closed form) – the “solve as one black box” baselinediscover_decomposition()is compared against. Matched compute against the decomposed model: both are single closed-form linear solves over the samenexamples.
- record_decomposition_outcome(proposer, decomposition, outcome, *, success_quantile=0.6, min_log=4)[source]
Log one
(decomposition, outcome)pair from a REAL task instance, and once at leastmin_logoutcomes are on file, refitplan_modelon the decompositions scoring at or above this round’s ownsuccess_quantile– literallytrain_outcome_decomposer()’s keep-the-successes-and-refit step, so futuresample()calls favor what actually worked, not just what the seed corpus imitated.