The “12 Factor Agents” framework distills practical principles for building LLM-powered software that is robust enough for production customers. It rejects the naïve “here’s your prompt, bag of tools, loop until done” pattern in favour of deterministic orchestration via micro agents — small, focused agent loops embedded in a broader directed acyclic graph (DAG). The agent loop itself (LLM chooses a step, deterministic code executes it, results accumulate in the context) is kept short (3–10 steps) to avoid context-window spirals. Each factor is a concrete engineering practice: owning prompts as code, building custom context engineering, defining tools as structured outputs, unifying execution and business state, exposing simple APIs for launch/pause/resume, using structured outputs for human-in-the-loop, compacting errors back into the context, and triggering agents from any channel. The result is a system that is testable, resumable, auditable, and capable of handling human feedback and high-stakes operations.
Key Points
Production agents are primarily deterministic software with LLM steps sprinkled at well-scoped points, not a free-form loop that decides everything.
The agent loop (LLM steps → deterministic tool execution → context accumulation) breaks down after 10–20 turns; keep it small by using micro agents.
Own your prompts and context window: treat them as first‑class code, not framework abstractions.
Unify execution state (current step, waiting, retries) with business state (messages, tool results) in a single serializable thread.
Expose simple APIs to launch, query, pause, resume, or stop an agent; enable resumption via webhooks after human input.
Always output JSON from the LLM and use explicit tokens like request_human_input or done_for_now to control flow.
Compact error messages and stack traces back into the context so the LLM can self‑heal, but limit consecutive retries.
Build small, focused agents that each handle 3–10 steps; let the outer deterministic DAG compose them.
Trigger agents from Slack, email, SMS, etc. to meet users where they are and enable high‑stakes operations with quick human escalation.
Concepts
– The core cycle: LLM outputs a structured tool call, deterministic code executes the tool, the result is appended to a context window, and the LLM is asked again for the next step until a “done” signal is emitted.
Agent loop
Micro agent – A small, domain‑specific agent (3–10 steps) used as a component inside a larger deterministic DAG. The LLM’s role is limited to parsing plaintext feedback and choosing among a few well‑defined transitions.
Context engineering – The deliberate construction of the LLM’s input (instructions, history, tool results, retrieved documents) to maximise token efficiency and attention, often by packing everything into a single user message with XML‑style tags.
Structured tool calls – The LLM outputs a JSON object describing the next action (e.g., {"intent": "create_payment_link", "params": {...}}), which a switch statement dispatches to deterministic functions.
Stateless reducer – A design pattern mentioned but not elaborated; the agent processes events and produces a new state without side effects, enabling easy serialization and recovery.
Details
The article identifies twelve factors that together enable production‑grade AI agents. Each factor is presented as an independent practice, but they complement one another.
Factor 1: Natural Language to Tool Calls
The primary interface between the user and the system is a natural‑language request that the LLM converts into a structured JSON object describing an API call. For example, “create a payment link for $750 to Terri for sponsoring the February AI Tinkerers meetup” becomes create_payment_link(customer_id, product_id, price_id). The LLM may first call helper tools (list customers, products, prices) to build the correct payload. Deterministic code parses the JSON and dispatches via a switch statement. The final human‑readable response is generated in a separate loop.
Factor 2: Own Your Prompts
Frameworks that offer black‑box abstractions like Agent(role, goal, personality, tools) are good for prototyping but hard to tune. Treat prompts as first‑class code — e.g., a typed function DetermineNextStep(thread: string) -> DoneForNow | ListGitTags | DeployBackend | ... that constructs the prompt explicitly. Benefits include full control, testability (evals), rapid iteration, transparency, and the ability to exploit non‑standard API capabilities (e.g., the now‑deprecated OpenAI completions endpoint for “model gaslighting”).
Factor 3: Own Your Context Engineering
The LLM in an agent always receives “here’s what’s happened so far, what’s the next step?”. Great context includes instructions, retrieved documents, past state / tool calls / history, memory from separate conversations, and structured‑output instructions. To maximise token and attention efficiency, build a custom format. Example: pack everything into a single user message with XML tags like <slack_message>, <list_git_tags>, <list_git_tags_result>, then ask “what’s the next step?”. A Thread class (list of Event types) with an event_to_prompt function produces this format. Benefits: higher information density, hiding resolved errors, filtering sensitive data, adapting format per use case, and token efficiency.
Factor 4: Own Your Tools
Tools are simply structured outputs from the LLM that trigger deterministic code. Define classes like CreateIssue (with intent: "create_issue" and an Issue sub‑object) and SearchIssues (with intent: "search_issues" and a query). The LLM outputs JSON; deterministic code parses the JSON and executes the appropriate action (e.g., calling an external API). Results are captured and appended to the context.
Factor 5: Unify Execution State and Business State
Where feasible, infer execution state (current step, next step, waiting status, retry counts) from the same context window that holds business state (list of messages, tool calls, results). Rather than managing separate state machines, treat the thread of events as the single source of truth. Benefits: simplicity, easy serialization, debugging, flexibility (add state by introducing a new event type), recovery (resume from any point by loading the thread), forking, and converting to human‑readable formats.
Factor 6: Expose Simple APIs for Agent Lifecycle
Agents should expose minimal APIs for launch, query, resume, and stop. Pausing during long‑running operations (e.g., waiting for human approval) is expected. External triggers (e.g., a webhook after a human responds) should let the agent resume without deep orchestrator integration. This factor ties closely to factors 5 and 8 but can be implemented independently.
Factor 7: Always Output JSON
Always have the LLM output JSON, and use tokens like request_human_input or done_for_now instead of relying on the first token to choose between plaintext and structured output. The source provides a code example: an Options class with urgency, format, and choices; a RequestHumanInput tool with intent: "request_human_input", question, context, options. In the agent loop, when the LLM emits request_human_input, the thread records the request, saves state, notifies the human, and breaks the loop. A later webhook loads the thread, appends the human’s response, determines the next step, and continues. Benefits: clear instructions for the LLM, agent‑to‑human initiation (outer loop), tracking multiple human inputs, agent‑to‑agent requests, and durable multi‑player workflows (when combined with factor 6).
Factor 8: Own Your Control Flow
Custom control flow enables interruption and resumption, especially between tool selection and tool invocation — the #1 requested feature missing from many AI frameworks. Three patterns in a handle_next_step loop:
request_clarification – breaks and awaits human input (factor 7).
fetch_open_issues – fetches data, appends to the thread, and continues the loop.
create_issue – breaks for human approval before executing.
This granularity avoids forcing the agent to either pause in memory (and restart if interrupted), restrict to low‑stakes calls, or hope it doesn’t screw up.
Factor 9: Compact Errors into Context Window
For short tasks, the LLM can read an error message or stack trace and change its next tool call. Most frameworks implement this, but you can do just this without the other 11 factors. Example loop:
thread = {"events": [initial_message]}
while True:
next_step = await determine_next_step(thread_to_prompt(thread))
thread["events"].append({"type": next_step.intent, "data": next_step})
try:
result = await handle_next_step(thread, next_step)
except Exception as e:
thread["events"].append({"type": 'error', "data": format_error(e)})
# loop to try again
Implement an errorCounter for a specific tool call, limiting to about 3 attempts. If consecutive errors exceed a threshold, escalate to a human (factor 7) or break. Benefits: self‑healing and durability. The #1 way to prevent error spin‑outs is factor 10.
Factor 10: Small, Focused Agents
Build small agents that do one thing well, as a building block in a larger mostly deterministic system. Keep agents focused on specific domains with 3–10 steps (maybe 20 at most) to keep context windows manageable and LLM performance high. Benefits: manageable context, clear responsibilities, better reliability, easier testing, and improved debugging. As the NotebookLM team put it: “the most magical moments out of AI building come about for me when I'm really, really, really just close to the edge of the model capability.” Finding that boundary and getting it right consistently builds magical experiences.
Factor 11: Trigger from Anywhere
If already implementing factor 6 (simple APIs) and factor 7 (contact humans), enable users to trigger agents from Slack, email, SMS, or other channels, and have agents respond via the same channels. Benefits: meet users where they are, enabling outer‑loop agents that work for 5–90 minutes and contact a human when critical, and supporting high‑stakes tools (sending external emails, updating production data) because a human can be quickly looped in. Maintaining clear standards gives auditability and confidence.
Factor 12: Make Your Agent a Stateless Reducer
Mentioned briefly “for fun” with no further elaboration; the intention is to treat the agent as a function that receives the current thread and returns a new thread, making it easy to test, replay, and recover.
Example: Deploybot Micro Agent
A concrete illustration of several factors combined:
Human merges a PR.
Deterministic code deploys to staging and runs e2e tests.
The micro agent is handed control with initial context: “deploy SHA 4af9ec0 to production”.
Human rejects with “can you deploy the backend first?” → agent calls deploy_backend_to_prod → human approves → backend deployed.
Agent calls deploy_frontend_to_prod → human approves → frontend deployed.
Agent emits done → deterministic code runs e2e on production.
On failure, a rollback agent is invoked (factor 10).
The LLM’s primary value here is parsing plaintext human feedback and proposing an updated course of action, within a tiny 5–10 step workflow.
Rendering diagram…
The agent loop (LLM → deterministic tool → context → LLM) is isolated to this small scope, while the outer orchestration remains a deterministic DAG. This pattern makes it easy to incorporate live human feedback without context‑error loops.