Engineering the Learning Marketing Environment: Causal ML, Agentic Systems, and Open-Source Tooling
A technical walkthrough of how machine learning, causal inference, and autonomous agents combine into a production marketing intelligence stack — with code, architecture, and the open-source frameworks I’ve built to run it

Marketing environments are reflexive complex systems. Budgets respond to outcomes; bidding algorithms respond to conversions they partially caused; creative rotates based on engagement it also shapes. Apply naive supervised learning to a system like that and you get confident, self-fulfilling, wrong answers.
This article is the technical companion to that problem. It walks through the full stack of a modern marketing intelligence environment — predictive ML, Bayesian media mix modeling, causal inference, simulation, and agentic orchestration — with working code and the design decisions behind Papilon, PyCausalSim, and Daedalus, the open-source tools I’ve built at Vector1 Research to operationalize this approach, plus how Memory-Node Encapsulation (MNE) informs the memory layer of marketing agents.
1. The System Model: Marketing as a Complex Adaptive System
Before any model is fit, write down the system. A marketing environment is a directed graph of interacting components: exogenous demand drivers feed category search volume; media spend passes through carryover and saturation transforms before producing response; and — critically — a budget optimizer closes a feedback loop from revenue back to spend.

Two properties fall out of this immediately:
- Nonlinearity everywhere. Saturation (Hill functions), carryover (geometric or Weibull adstock), threshold effects, and interaction terms between channels.
- Endogeneity by construction. Spend is not exogenous — it responds to performance. Any model that ignores this confuses the optimizer’s policy with the market’s response function.
This is why the stack below is causal-first: prediction models operate inside a causal scaffold, never instead of one.
2. Bayesian Media Mix Modeling with Explicit Structure
The workhorse for strategic allocation is a hierarchical Bayesian MMM. The advantage over black-box regressors is that adstock, saturation, and priors are encoded, not hoped for. Here’s the core response function in PyMC:
import pymc as pm
import pytensor.tensor as pt
def geometric_adstock(x, alpha, l_max=12):
"""Carryover: exponentially decaying memory of spend."""
w = pt.power(alpha, pt.arange(l_max))
w = w / pt.sum(w)
return pt.signal.conv.conv2d(
x.reshape((1, 1, -1, 1)),
w.reshape((1, 1, -1, 1)),
border_mode='full'
).flatten()[:x.shape[0]]
def hill_saturation(x, k, s):
"""Diminishing returns: Hill transform."""
return x**s / (k**s + x**s)
with pm.Model() as mmm:
# Channel-level priors — weakly informative, calibrated by geo-lift tests
alpha = pm.Beta("adstock_alpha", 2, 2, shape=n_channels)
k = pm.Gamma("hill_k", 3, 1, shape=n_channels)
s = pm.Gamma("hill_s", 3, 1, shape=n_channels)
beta = pm.HalfNormal("channel_beta", 1.0, shape=n_channels)
contributions = []
for c in range(n_channels):
transformed = hill_saturation(
geometric_adstock(spend[:, c], alpha[c]), k[c], s[c]
)
contributions.append(beta[c] * transformed)
baseline = pm.Normal("baseline", mu=0, sigma=1)
trend = pm.Normal("trend", 0, 0.1) * time_index
mu = baseline + trend + sum(contributions) + seasonality
sigma = pm.HalfNormal("sigma", 1)
pm.Normal("revenue", mu=mu, sigma=sigma, observed=y)
idata = pm.sample(2000, target_accept=0.95)
Three engineering notes that matter more than the model itself:
- Calibrate priors with experiments. Geo-lift results become informative priors on
channel_beta. An MMM uncalibrated by experiments is an opinion with credible intervals. - Hierarchical pooling across geos stabilizes small markets:
beta[c, geo] ~ Normal(beta_global[c], tau[c]). - Report distributions, not points. Budget decisions should be made against the posterior of marginal ROAS, not its mean.
In Papilon, this entire layer is packaged as a pipeline component — data validation, transform search, sampling, diagnostics (R-hat, ESS, posterior predictive checks), and a budget optimizer that maximizes expected incremental revenue under the full posterior, which naturally penalizes channels with high uncertainty:
from papilon.mmm import MediaMixModel
from papilon.optimize import BudgetAllocator
model = MediaMixModel.from_config("configs/mmm_us_dtc.yaml")
model.fit(df, calibration=geo_lift_results)
allocator = BudgetAllocator(model, objective="expected_incremental_revenue",
risk_aversion=0.3)
plan = allocator.solve(total_budget=2_400_000, constraints={
"paid_search": (0.10, 0.45), # share bounds
"ctv": (0.05, 0.30),
})
print(plan.summary()) # allocation + posterior lift intervals per channel3. Incrementality: Double ML and Heterogeneous Treatment Effects
MMM answers the strategic question. The tactical question — does this campaign move anyone who wouldn’t have converted anyway? — is a treatment-effect problem.
Double Machine Learning lets us use flexible learners for nuisance functions while retaining valid inference on the causal parameter:
from econml.dml import CausalForestDML
from lightgbm import LGBMRegressor, LGBMClassifier
est = CausalForestDML(
model_y=LGBMRegressor(n_estimators=500),
model_t=LGBMClassifier(n_estimators=500),
discrete_treatment=True,
n_estimators=2000,
min_samples_leaf=50,
)
est.fit(Y=revenue, T=exposed, X=hetero_features, W=confounders)
# Conditional average treatment effects — the persuadables surface here
cate = est.effect(X_customers)
ate_interval = est.ate_interval(X_customers)The operational payoff is uplift-based targeting: rank customers by estimated CATE, not by purchase propensity. Propensity targeting produces beautiful platform ROAS and near-zero incrementality, because you’re paying to reach people who were already coming. CATE targeting concentrates spend on customers whose behavior the treatment actually changes.
The failure mode to engineer against: unobserved confounding. This is where simulation earns its place.
4. Simulation-Based Causal Discovery with PyCausalSim
Observational marketing data cannot, by itself, distinguish between competing causal graphs. PyCausalSim, my framework for causal discovery through simulation, attacks this from the generative side: specify candidate structural causal models, simulate each under known interventions, and score them against observed intervention data (experiments, natural shocks, platform outages — which are gloriously clean natural experiments).
from pycausalsim import SCM, InterventionScorer
# Candidate graph A: brand search is a mediator of CTV
scm_a = SCM()
scm_a.add_equation("ctv_grps", exogenous=True)
scm_a.add_equation("brand_search", lambda ctv_grps, e: 0.4*ctv_grps + e)
scm_a.add_equation("conversions", lambda brand_search, ctv_grps, e:
0.6*brand_search + 0.1*ctv_grps + e)
# Candidate graph B: brand search is a confounder (demand drives both)
scm_b = SCM()
scm_b.add_equation("demand", exogenous=True)
scm_b.add_equation("ctv_grps", lambda demand, e: 0.5*demand + e) # budget rule
scm_b.add_equation("brand_search", lambda demand, e: 0.7*demand + e)
scm_b.add_equation("conversions", lambda brand_search, e: 0.6*brand_search + e)
scorer = InterventionScorer(observed_interventions=holdout_experiments)
results = scorer.compare([scm_a, scm_b], n_sims=10_000)
# results tells you which structure is consistent with experimental reality
Graphs A and B produce identical observational correlations and opposite budget recommendations. Under graph A, CTV drives conversions through search and deserves budget; under graph B, cutting CTV costs you nothing. Only intervention data separates them — and simulation lets you extract maximum information from the few interventions you can afford.
Daedalus sits above this layer as the causal and economic analysis platform: it maintains the organization’s causal graph as a versioned artifact, links each edge to its supporting evidence (experiment, DML estimate, or assumption), and runs counterfactual queries — “what does Q4 look like if we shift 15% of paid social into retail media?” — against the fitted structural model rather than a correlational one.
5. The Agentic Layer: Papilon’s Orchestrator
The newest layer of the stack is agentic. Papilon 2.0 includes an orchestration framework — BaseAgent, a ToolRegistry, pluggable LLM providers, and a memory subsystem — that turns the analytical components above into tools an autonomous agent can call under guardrails.
from papilon.orchestrator import BaseAgent, ToolRegistry, Tool, ToolParameter
from papilon.orchestrator import AnthropicProvider, ConversationMemory
registry = ToolRegistry()
registry.register(Tool(
name="run_mmm_refresh",
description="Refit the media mix model on the latest weekly data",
parameters=[ToolParameter("config_path", str)],
fn=lambda config_path: MediaMixModel.from_config(config_path).fit(latest()),
))
registry.register(Tool(
name="estimate_campaign_lift",
description="Run CausalForestDML on a completed campaign",
parameters=[ToolParameter("campaign_id", str)],
fn=estimate_lift,
))
registry.register(Tool(
name="propose_budget_shift",
description="Solve constrained allocation; returns plan for human approval",
parameters=[ToolParameter("total_budget", float)],
fn=propose_allocation, # never executes — proposes
))
agent = BaseAgent(
llm=AnthropicProvider(model="claude-sonnet-4-6"),
tools=registry,
memory=ConversationMemory(),
system_prompt=MARKETING_ANALYST_CHARTER, # objectives, constraints, ethics
)
response = agent.run(
"Weekly review: refresh the MMM, check whether the March CTV lift "
"held up in the DML estimates, and propose a reallocation if the "
"posterior marginal ROAS ordering has changed."
)Design principles that keep this safe and useful:
- Agents propose; humans dispose. Budget-moving tools return plans routed to approval, never executed autonomously. The agent’s autonomy is analytical, not fiscal.
- Tools are the causal stack. The agent doesn’t reason about marketing from parametric memory — it calls the MMM, the DML estimators, and Daedalus counterfactuals, and reasons over their outputs. LLM as orchestrator, causal models as ground truth.
- Every proposal carries provenance. Which model version, which posterior, which experiment calibrated it.
Memory: where MNE comes in
Marketing agents fail without longitudinal memory. “We tested creative fatigue caps in 2024 and the effect was null” is exactly the kind of institutional knowledge that a stateless agent re-litigates forever. My research on Memory-Node Encapsulation (MNE) — a data structure for artificial episodic memory — informs Papilon’s memory layer: analytical episodes (an experiment, a model refresh, a budget decision and its realized outcome) are encapsulated as retrievable nodes carrying context, outcome, and causal annotations. The agent retrieves episodes, not embeddings of documents, which means it can answer “have we tried this before, and what happened?” with the decision’s full evidentiary context. That episodic substrate is what turns an agent from a clever analyst into an accumulating one.
6. Evaluation: The Layer That Keeps Everyone Honest
Nothing above survives without an always-on evaluation loop:
- Rolling holdouts. A permanent 5–10% randomized control slice per addressable channel. Non-negotiable. This is the ground truth that calibrates everything else.
- Posterior predictive checks and drift monitors on the MMM; refit triggers on structural breaks (platform algorithm changes are the usual culprit).
- Incrementality audits. Quarterly reconciliation of platform-reported conversions vs. experiment-measured lift vs. MMM-attributed contribution. The three will disagree; the pattern of disagreement is diagnostic.
- Agent evals. Papilon’s agent layer ships with scenario-based evals — synthetic environments (via PyCausalSim, naturally) where the true causal structure is known, so you can score whether the agent’s proposals actually improve incremental revenue in-silico before it advises on real budgets.
That last point closes the loop elegantly: the simulation framework that stress-tests your causal assumptions also becomes the training and evaluation gym for your marketing agents.
7. Reference Architecture
Putting it together: five layers, each consuming the one below it, with an evaluation spine cutting across all of them.

Closing: Optimization Needs a World Model
The lesson from building these systems is that marketing ML fails not from weak models but from missing structure. Gradient boosting will happily learn that your budget optimizer’s policy is “the market.” Only by encoding the system — adstock, saturation, confounding, feedback — and disciplining it with experiments and simulation do you get models whose recommendations survive deployment.
That’s the thesis behind the Vector1 stack: Papilon to model and optimize the complex system, PyCausalSim to discover and stress-test its causal structure, Daedalus to maintain the organization’s causal world model, and MNE to give the agents operating on top of it a real memory. The machine learns the environment; the causal scaffold ensures it learned the right one.
About the Author
Brian Curry is a Kansas City–based AI and Machine Learning researcher, data scientist, and the founder of Vector1 Research, where he works on the intersection of cognitive AI architecture, causal inference, and economic systems. His research introduced Memory-Node Encapsulation (MNE), an original data structure for artificial episodic memory, and the NeoCortex-M architecture for memory-driven AI. He is the creator of Papilon, an open-source framework for complex system optimization with agentic orchestration, PyCausalSim, a Python framework for causal discovery through simulation, and Daedalus, a platform for causal and economic analysis.
His practitioner work focuses on building production intelligence systems — causal marketing analytics, agentic systems, and the serving infrastructure that makes them deployable inside enterprise environments. He writes about that work at vector1.ai and on Medium.


