mixle.task.plan_model module¶
Fit plans as models over harvested agent traces.
A plan is the ordered sequence of tool NAMES an agent called for a request. Fitting a Markov chain
over those sequences (via the ordinary optimize entry point every mixle model goes through, not a
hand-rolled counter) turns “which plans look like what this agent usually does” into a real, scoreable
distribution: PlanModel.log_prob(plan) is exact, PlanModel.sample(rng) draws a plausible plan,
and PlanModel.is_typical(plan) flags a plan whose probability falls below the training traces’ own
log-prob quantile – an escalation signal, not a silent guess, the same discipline
sample_plans() uses for its generative sibling.
model = fit_plan_model(harvest_agent_traces()) model.log_prob([“lookup_order”, “notify”]) model.is_typical(candidate_plan) # False -> escalate; this plan does not look like the traces
- class PlanModel(dist, training_log_probs)[source]
Bases:
objectA fitted Markov chain over tool-name sequences, plus the training traces’ own log-prob spread.
- log_prob(plan)[source]
Exact log-probability of
plan(a tool-name list, or the[{"tool":...}, ...]shape).
- sample(rng=None)[source]
Draw one plausible tool-name sequence from the fitted chain.
The underlying sampler draws a length from
len_distfirst, then walks the chain; once the walk reaches an absorbing state (no fitted outgoing transition – typically the tool that always ends a workflow), the remaining, unreachable slots are returned asNone. Truncate there rather than exposing that padding: only known, actually-reached tool names are emitted.- Parameters:
rng (RandomState | None)
- Return type:
- fit_plan_model(traces, *, smoothing=0.5, init_p=1.0)[source]
Fit a
PlanModelon harvested traces’ tool-name sequences.smoothingis the Markov chain’s Dirichlet pseudo-count (higher = smoother transition estimates, matters most with few traces). Fits viamixle.inference.optimize()on the existingMarkovChainEstimator– the same declare-an-estimator/call-optimize path every other mixle model uses, not hand-rolled counting.init_pdefaults to1.0(use every trace for the init pass), notoptimize’s owninit_p=0.1default: that Bernoulli-subsamples observations for a low-cost init estimate, sized for large corpora, but a trace corpus here is typically tens to a few hundred sequences – with that few, a 10% subsample has a real chance of drawing ZERO sequences, which crashesMarkovChainEstimator.estimate1(all_keysends up empty, dividing by zero). Using the full corpus for this small an init pass is low-overhead and more reliable; override down only for corpora large enough that subsampling actually matters.