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: object

A fitted Markov chain over tool-name sequences, plus the training traces’ own log-prob spread.

Parameters:
log_prob(plan)[source]

Exact log-probability of plan (a tool-name list, or the [{"tool":...}, ...] shape).

Parameters:

plan (Sequence[Any])

Return type:

float

sample(rng=None)[source]

Draw one plausible tool-name sequence from the fitted chain.

The underlying sampler draws a length from len_dist first, 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 as None. Truncate there rather than exposing that padding: only known, actually-reached tool names are emitted.

Parameters:

rng (RandomState | None)

Return type:

list[str]

is_typical(plan, *, quantile=0.05)[source]

False when plan scores below the training traces’ own quantile log-prob – the escalation signal: a plan that does not look like what this agent usually does.

Parameters:
Return type:

bool

fit_plan_model(traces, *, smoothing=0.5, init_p=1.0)[source]

Fit a PlanModel on harvested traces’ tool-name sequences.

smoothing is the Markov chain’s Dirichlet pseudo-count (higher = smoother transition estimates, matters most with few traces). Fits via mixle.inference.optimize() on the existing MarkovChainEstimator – the same declare-an-estimator/call-optimize path every other mixle model uses, not hand-rolled counting.

init_p defaults to 1.0 (use every trace for the init pass), not optimize’s own init_p=0.1 default: 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 crashes MarkovChainEstimator.estimate1 (all_keys ends 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.

Parameters:
Return type:

PlanModel