“We already run 12-factor agents,” a team told us recently, meaning they had a tidy prompt-orchestration checklist. Then they tried to ship the same agent to iOS, Android, and a desktop build, and it fell apart at the first runtime boundary. The prompts were consistent. The behaviour was not. That gap is what the 12-factor idea is actually about, and most teams read only half of it. The naive reading treats “12-factor agents” as an application-layer manifesto — structure your prompts, own your tools, keep your context tidy — disconnected from where the model actually runs. The expert reading recognises that several of the factors are the same portability discipline that lets a single inference artifact ship across CoreML, ONNX Runtime, and a desktop target without divergent per-platform pipelines. Stateless reducers, a unified execution context, and owning your own control flow are not just tidiness rules. They are the constraints that decide whether an agent can be exported once and run everywhere, or whether it fractures into three subtly different agents you now have to maintain in parallel. What does “12-factor agents” mean in practice? The name borrows from the twelve-factor app methodology that shaped how web services were built to be portable across hosting environments. Applied to LLM agents, the idea is a set of design principles that keep an agent’s behaviour predictable, testable, and deployable rather than fused to one framework or one hosted API. In practice it covers things like treating your prompts as versioned artifacts, keeping tool calls explicit, externalising state, and refusing to hide control flow inside a framework you do not own. The trap is reading all twelve as an application-layer concern. Some of them are. But a subset — the ones that touch state and execution — are really a deployment contract. They decide whether the agent you validated on your laptop behaves the same way when the underlying inference target changes per device. When an agent pins its state to a single runtime or embeds platform-specific control flow, it cannot be exported once and run everywhere. It has to be re-validated on each platform, which is exactly the cost the discipline is supposed to remove. This is the same tension we describe when a single model has to run across several targets in our work on cross-platform ONNX and CoreML inference and what compiler flags actually do. The runtime changes underneath you; the behaviour must not. Which of the 12 factors matter for cross-runtime deployment? Not all twelve carry equal weight when your problem is “ship this agent to CoreML on iOS, ONNX Runtime on Android, and a native desktop build.” A handful of them are portability-critical; the rest are good hygiene that does not directly break at the runtime boundary. The distinction matters because teams over-invest in prompt structure and under-invest in the two or three factors that actually determine whether the export-once pipeline holds. The following table maps the portability-relevant factors to the concrete deployment decision each one governs. This is a design rubric, not a compliance scorecard — the point is to know which factor you are trading against when you make a runtime choice. Portability-relevant factors mapped to deployment decisions Factor (portability lens) What it governs at the runtime boundary Failure if ignored Stateless reducer Agent state lives outside the runtime, passed in and out as data State pins to one runtime; agent cannot be exported and re-hydrated on another device Unified execution context One representation of context regardless of host runtime Context assembled differently per platform; behaviour diverges silently Own your control flow Agent decides what happens next, not a framework hidden per platform Platform-specific branches multiply; each device becomes a separate maintenance target Explicit tool contracts Tools invoked through a defined interface, not runtime-native calls Tool binding differs per runtime; the same agent step produces different results Compact, error-in-context handling Errors returned into the context loop, not swallowed by the host One platform recovers from a failed step, another aborts — drift you only see in production The pattern across these rows is consistent: the factors that break portability are the ones where the agent quietly borrows something from its host runtime. State, context assembly, control flow, tool binding, error handling — each is a place where “let the platform handle it” feels convenient at build time and turns into a divergent pipeline in production. In configurations we have worked through, the factors above are where near-identical agents start behaving differently across devices; the prompt-layer factors rarely are (observed pattern across our on-device deployment work, not a benchmarked ranking). Why does treating state as a stateless reducer improve portability? This is the factor that carries the most portability weight, so it is worth being precise about the mechanism. A stateless reducer means the agent does not hold state internally between steps. Instead, the full state is passed in as input, the step produces a new state as output, and nothing about the transition depends on where it ran. State becomes plain data — serialisable, inspectable, and runtime-agnostic. The reason this improves cross-platform portability is structural, not stylistic. If an agent keeps its state inside the runtime — a live object graph in a Python process, say, or a session handle tied to a hosted API — then moving that agent to CoreML or ONNX Runtime means reconstructing something the new runtime does not have a native equivalent for. You end up writing per-platform glue to marshal state, and every piece of that glue is a place behaviour can diverge. When state is externalised as data, the runtime is interchangeable. You export the model once, and the reducer that drives it is the same code path everywhere because it never touched the runtime in the first place. There is a direct analogy in inference deployment. The distillation-versus-quantisation choice — do you shrink the model by training a smaller one, or by reducing numerical precision — is a decision about how to keep behaviour consistent when the execution target changes. The stateless-reducer discipline is the same decision at the orchestration layer: keep the thing that varies (the runtime) cleanly separated from the thing that must not vary (the agent’s behaviour). We treat this as the design-time counterpart to the export-once, run-everywhere pipeline, and it connects to the broader cross-platform inference-optimisation methodology in the GPU and inference family. How do 12-factor principles map onto an export-once pipeline? An export-once, run-everywhere pipeline has a clear shape: you define the model and its behaviour once, export a portable artifact, and run it across CoreML, ONNX Runtime, and desktop targets without re-authoring the pipeline per platform. The 12-factor principles are the design-time contract that makes that pipeline possible. Without them, “export once” only covers the model weights, and the agent logic around the model still forks per platform. The mapping is straightforward once you separate the two halves. The model export handles the tensor math being portable — the domain of graph compilation and runtime backends. The portability-relevant factors handle the behaviour around the model being portable: the same context assembly, the same control-flow decisions, the same tool contracts, the same state representation, regardless of which runtime the exported model happens to sit on. When both halves hold, changing the inference target is a configuration decision, not a re-engineering effort. This is the boundary telecom and media teams building client-side ML features hit constantly, which is why it sits inside our work on the media and telecom industry practice, where the same feature has to ship to a wide fleet of devices with consistent behaviour. Where does platform-specific control flow break behaviour consistency? Control flow is the quiet failure point. An agent that embeds if iOS do X, else do Y — or worse, lets a per-platform framework decide the next step — has already lost behaviour consistency, even if every individual branch is correct. The problem is not that the branches are wrong. It is that you now have three code paths to reason about, and the number of interaction states you have to test grows with every branch. The early warning sign is subtle: the agent works on every platform in isolation, and the team declares victory. The drift shows up later, in an edge case that only one platform’s branch handles differently — a tool that times out, a context that overflows, an error the desktop build retries but the iOS build surfaces to the user. Because the branches were written per platform, nobody validated the same failure across all three. Owning your control flow — keeping the “what happens next” decision inside the agent’s own logic rather than delegating it to the host runtime — is what collapses those three paths back into one. When the same reducer decides the next step everywhere, a fix on one platform is a fix everywhere, and behaviour drift at the state-management boundary drops to near zero. This connects to a broader reliability concern we cover in 12-factor agent design principles for client-side ML inference: the failures that hurt most are the ones that only appear when the runtime, not the logic, changes. How do you QA a 12-factor agent across runtimes without re-validating each platform? If the portability-relevant factors are applied properly, the QA strategy changes shape. You do not re-run the full behavioural test suite against each platform from scratch. Instead, you test the agent logic once against the reducer — because the reducer is runtime-agnostic — and then run a much smaller conformance suite per platform that confirms the exported model produces the same outputs and the runtime honours the same tool contracts. The following checklist is what we look for before declaring an agent portable across runtimes. It is diagnostic: if any item fails, the export-once claim is not yet true. Cross-runtime portability checklist State is serialisable and runtime-free. You can dump the full agent state to data, load it in a different runtime, and resume with identical behaviour. Control flow lives in the agent, not the host. No if platform branches decide agent steps; the reducer is the single source of “what next.” Tool contracts are explicit and identical across runtimes. The same tool call binds the same way on CoreML, ONNX Runtime, and desktop. Context assembly is unified. The context handed to the model is built by one code path, not reconstructed per platform. Errors return into the context loop. Failure handling is part of the reducer, so recovery behaviour is consistent everywhere. A per-platform conformance suite exists and is small. It confirms numerical parity and tool binding, not full behavioural coverage — because behaviour was validated once against the reducer. When those hold, per-platform validation is a bounded conformance check rather than a full re-validation cycle. That is the measurable outcome: the same “ship once, run everywhere within latency targets” result, with behaviour drift between iOS, Android, and desktop reduced to near zero at the state-management boundary (project-specific outcome from the parent on-device deployment case study, not a general benchmark). FAQ How does 12-factor agents work? It is a set of design principles that keep an LLM agent portable, testable, and deployable rather than fused to one framework or hosted API. In practice it means versioned prompts, explicit tool contracts, externalised state, and owned control flow. The portability-relevant subset — statelessness, unified context, own-your-control-flow — is really a deployment contract that decides whether the agent behaves the same when the runtime changes underneath it. Which of the 12 factors actually matter for running an agent across multiple runtimes like CoreML and ONNX? The portability-critical factors are the ones that touch state and execution: stateless reducer, unified execution context, own-your-control-flow, explicit tool contracts, and error-in-context handling. These are the places where an agent quietly borrows something from its host runtime, which is exactly where near-identical agents start diverging across devices. The prompt-structure factors are good hygiene but rarely break at the runtime boundary. Why does treating agent state as an externalised, stateless reducer improve cross-platform portability? When state lives inside the runtime, moving the agent to a new target means reconstructing something that runtime has no native equivalent for, which forces per-platform glue where behaviour can diverge. A stateless reducer passes state in and out as plain serialisable data, so the runtime becomes interchangeable. The reducer never touched the runtime, so the same code path drives the exported model everywhere. How do 12-factor principles map onto an export-once, run-everywhere deployment pipeline? Model export makes the tensor math portable; the portability-relevant factors make the behaviour around the model portable — same context assembly, control flow, tool contracts, and state representation regardless of runtime. When both halves hold, changing the inference target is a configuration decision, not a re-engineering effort. Where does platform-specific control flow break agent behaviour consistency across iOS, Android, and desktop? It breaks the moment the “what happens next” decision is delegated to a per-platform branch or host framework instead of the agent’s own logic. Each branch may be individually correct, but you now have three code paths, and drift surfaces later in an edge case only one branch handles differently. Owning control flow collapses those paths into one reducer, so a fix on one platform is a fix everywhere. How do I QA a 12-factor agent across runtimes without re-validating each platform from scratch? Test the agent logic once against the runtime-agnostic reducer, then run a small per-platform conformance suite that confirms numerical parity and identical tool binding. Full behavioural coverage happens once; each platform gets a bounded check rather than a complete re-validation cycle. What does a production-ready 12-factor agent look like measured in behaviour drift and per-platform maintenance cost? It ships from a single agent definition across CoreML, ONNX Runtime, and desktop with behaviour drift at the state-management boundary reduced to near zero and no divergent control-flow branches to maintain per platform. The signal that you are there is that per-platform validation has collapsed to a small conformance suite instead of a full re-run. The unresolved question for most teams is not which of the twelve factors to adopt — it is which ones they can afford to treat as optional. The prompt-layer factors are forgiving. The state and control-flow factors are not: skip them and the runtime, not your logic, decides how your agent behaves, one device at a time. That is the failure class the portability discipline exists to prevent, and it is the design-time counterpart to the export-once, run-everywhere pipeline every cross-platform deployment eventually depends on.