# Design philosophy > OpenProgram is the productized implementation of the Agentic Programming paradigm. > This document is about the paradigm itself: what problem it solves, why it inverts control, and what its core primitives are. > The paradigm is described in the paper *LLM-as-Code: Agentic Programming for Agent Harness* ([arXiv:2606.15874](https://arxiv.org/abs/2606.15874)), accepted at the KDD 2026 AgenticSE workshop. ## The Problem Every current LLM Agent framework hands control to the model: - **What to do** is decided by the LLM (a planner plans first, then the agent executes) - **When to do it** is decided by the LLM (a while loop runs until the agent says "I'm done") - **How to do it** is decided by the LLM (tool calls, arguments, ordering) The cost: - **Unpredictable execution** — the same input produces a different trajectory every time - **Context explosion** — every step crams the history back into the model - **No output guarantees** — no one can say "this task is guaranteed to run to completion" - **Debugging hell** — when something breaks, you can't tell whether it's a prompt problem, a tool problem, or a model hallucination The root cause: **using a black-box probabilistic system to do work that could have been done with deterministic code in the first place**. ## The Inversion: Python Controls Flow, the LLM Reasons Agentic Programming gives control back to the programmer: | Dimension | Traditional Agent | Agentic Programming | |------|-----------|---------------------| | Flow | LLM plans | Python code | | Decisions | LLM judges at every step | Python decides whether to call the LLM | | State | Crammed into the context | Function variables, return values | | Testability | Prompt regression | Unit tests | Decompose a complex task into a function call graph. For each node on the graph, you decide: - **Doesn't need reasoning** — use a plain Python function - **Needs understanding / generation / judgment** — decorate it with `@agentic_function`, and call `llm(...)` inside the function body The LLM becomes a tool that you call, constrain, and compose. ## The Three Primitives The entire paradigm is just three things: ### 1. `@agentic_function` A decorator. For a function it decorates, the docstring travels with the call as descriptive context, and `llm(...)` inside the body triggers one model call. The decorator supplies the ambient runtime. ```python from openprogram import agentic_function from openprogram.agentic_programming import llm @agentic_function def summarize(text: str, runtime=None) -> str: """Summarize a text in one sentence, preserving the core point.""" return llm([{"type": "text", "text": ( f"Summarize in one sentence, preserving the core point:\n\n{text}" )}]) ``` External callers can't tell the difference — `summarize(article)` looks just like any Python function. ### 2. `Runtime` The runtime abstraction for LLM calls. It is responsible for: - Packaging the current conversation history - Calling the underlying provider (Anthropic / OpenAI / Claude Code / ...) - Writing the result back into the context User orchestration calls `llm()` for a single model request. `Runtime.exec()` remains the lower-level embedding and infrastructure API used underneath it. ### 3. `Context` The automatic record of execution. Every user turn, every LLM call, and every function call is one node on a single **flat DAG**; the edges are `caller` (which function invoked this node) and `reads` (which nodes an LLM call saw in its prompt). Each node records inputs, outputs, token usage, elapsed time, and failure reason. The DAG is not just a trace — it is also **where each LLM call's history comes from**: `llm()` renders its message history from the DAG through the ambient runtime. Two decorator knobs shape that flow per function: - `expose` — what a completed call reveals to its parent (`"io"` by default: name + input + output, internals hidden). - `render_range` — how much history the function's own `exec` pulls in. `render_range={"callers": 0}` gives an isolated scratch context that sees none of the prior conversation. So context management stops being prompt plumbing and becomes a property you declare on the function. The same DAG doubles as your debugging view: visualization, token accounting, replaying failure paths. ## Derived Concepts ### The LLM Writes Code Too The LLM isn't just the runtime's reasoning engine; it can also **write code** — generating, modifying, and fixing `@agentic_function`s that conform to the spec. This needs no dedicated `create()` / `fix()` framework functions; the agent authors new functions with ordinary file edits, following the [`agentic-programming` skill](https://github.com/Fzkuji/OpenProgram/blob/main/skills/agentic-programming/SKILL.md) as the spec — where files go, decorator metadata, the division of labor between the docstring and `content`, and the validation checklist. A background watcher rescans `programs/agentic_functions/` and hot-loads new modules: their `@agentic_function` decorators fire on import and self-register, so a freshly written function is callable without a restart. Code is data, the LLM is the compiler, and functions are the product — the loop closes. ### Dual Mode Agentic Programming is at the same time: - **A library** — you write `@agentic_function`s and wire up the pipeline by hand - **A running product** — chat in the CLI or WebUI and ask the agent to write the function for you; the generated file lands in `programs/agentic_functions/` and hot-loads Beginners start by asking, and what they get is a complete, readable Python file. Those who want to dig deeper can then import and hand-write. This is a tool that **can be understood incrementally**. ## Comparison with Traditional Agent Frameworks | Scenario | LangChain / AutoGPT | Agentic Programming | |------|---------------------|---------------------| | "Fetch 10 pages and generate a summary for each" | The agent decides ordering and parallelism itself | Python writes `for url in urls: summarize(fetch(url))` | | "Remember context across 3 consecutive conversations" | Stuff the conversation into a memory store and query it each time | It's just a local variable in a Python function | | "Let the LLM decide which tool to call" | function calling + agent loop | `runtime.exec(tools=[...])` or `decision.make(prompt, options)` | | "Retry on error" | The agent decides itself | `try / except` + code gates: an invalid pick is caught by validation and the model is asked to re-decide | This isn't to say agent frameworks are wrong; they suit a class of tasks (fully open-ended, with fuzzy goals). But most of what you want to do can actually be done more reliably with Agentic Programming. ## OpenProgram = the Productized Paradigm The `agentic_programming/` subpackage is the paradigm's engine code. `context/` implements the flat-DAG context model. `providers/` adapts the various LLMs. `programs/agentic_functions/` holds the functions and applications already written under this paradigm. `webui/` lets beginners run things without writing code. The paradigm comes first; the product exists to use it. --- Further reading: - [Getting Started](../../start/GETTING_STARTED.md) - [API Reference](../../reference/api/) - [Design Details](../../reference/design/)