agentic_function

August 20, 2026 · View on GitHub

Source: openprogram/agentic_programming/function.py

@agentic_function turns an ordinary Python function into an Agentic Function: each call is recorded as a code node in the session DAG, and the llm() calls inside the function body are recorded as llm nodes.

This document defines the decorator and authoring conventions for agentic functions.

Usage

from openprogram import agentic_function
from openprogram.agentic_programming import llm

@agentic_function
def f(x: str, runtime) -> str:
    """One-line summary of what f does."""
    return llm([{"type": "text", "text": f"...{x}..."}])

You can use bare @agentic_function or the parameterized form @agentic_function(...).

Decorator parameters

Agentic-specific parameters

ParameterTypeDefaultDescription
exposestr"io"Outward-facing: what others can see about me when they render the DAG. "io" = only the function's name and return value are visible externally, while its internals (LLM exchanges, sub-calls) are hidden; "llm" = the reverse, exposing only the internal LLM exchanges and hiding the function's own name/return value and nested code sub-calls; "full" = everything visible (docstring + params + output + LLM replies + internals); "hidden" = no DAG nodes are written at all. Any other value raises ValueError at decoration time
render_rangedictNoneInward-facing: how many history nodes to read from the DAG when this function's internal llm() call assembles its prompt. Shape {"callers": N, "subcalls": M}, where both numbers are node counts (sliced by seq):
callers — nodes written before this function's frame started; take the most recent N (None default = unlimited, 0 = a full wall)
subcalls — nodes already written after this function's frame started; take the most recent N (-1 default = unlimited, so the frame naturally sees its own progress; N>=0 = set explicitly when you want to truncate the prompt; 0 = wall off in-frame entirely)
{"callers":0,"subcalls":0} = cut off from both the outside world and your own frame
inputdictNonePer-parameter UI metadata; the WebUI renders the input form from it. Supported fields per parameter: description (label next to the name), placeholder (example text), multiline (True = textarea), options (list of allowed values, rendered as a dropdown and emitted as a JSON-schema enum), hidden (True = exclude from the form and from the LLM tool schema)
systemstrNoneThe system prompt for this function's LLM calls (applied over the injected runtime for the duration of the call, then restored afterward)

Tool-registration parameters

Every @agentic_function is also registered as an LLM-callable tool in the shared registry (openprogram.programs), alongside @function-decorated tools. These parameters control that registration and share their names and semantics with @function:

ParameterTypeDefaultDescription
as_toolboolTrueRegister this function as an LLM-callable tool. False = Python-direct-invoke only
namestrNoneTool name override. Default: the function's __name__
descriptionstrNoneTool description override. Default: the function's docstring
parametersdictNoneJSON-schema parameter override. Default: auto-generated from the signature's type hints plus input metadata (runtime-injected and hidden parameters excluded)
labelstrNoneHuman-readable label shown in tool UIs
toolsettuple()Toolset names this tool belongs to (used by exec(toolset=...) presets)
unsafe_intuple()Channel sources in which the tool is considered unsafe and filtered out
check_fnCallableNonePer-call gate: called before dispatch; a falsy result blocks the call
requires_envtuple()Environment variable names that must be set for the tool to be offered
can_useCallableNoneDynamic availability predicate evaluated at tool-resolution time
max_result_charsintNoneTruncation cap for the tool result fed back to the model. None = the registry default DEFAULT_MAX_RESULT_CHARS (30,000 chars)
persist_fullboolFalsePersist the untruncated result to disk so the agent can read it back
head_ratiofloatNoneWhen truncating, the fraction kept from the head, rest from the tail. None = the registry default DEFAULT_HEAD_RATIO (0.7)
requires_approvalNoneApproval requirement forwarded to the tool registry (same shape as @function)
cacheboolFalseMemoize results on (name, args) for tool-dispatched calls
cache_ttlfloat300.0Cache lifetime in seconds when cache=True
timeoutfloatNoneHard wall-clock kill for a tool-dispatched call, in seconds; on expiry the model receives an error result
available_ifCallableNoneImport-time gate: if it returns falsy (or raises), the decorator is skipped entirely and the module-level name stays a plain function — no wrapper, no registration
deferboolFalseRegister as a deferred tool (schema loaded on demand instead of shipped with every call)
register_globallyboolTrueFalse = build the tool but keep it out of the global registry

The function name, parameter names / types / defaults, and the one-line summary are all read automatically from the function signature and docstring, not repeated in the decorator (see SKILL.md §3).

Runtime injection

Parameters named runtime, exec_runtime, or review_runtime are auto-injected: if the caller passes none (or None), the runtime is taken from the current call chain, or — for an entry-point call — created via create_runtime() (auto-detection) and closed again when the function returns. A function may declare more than one runtime parameter; all of them are filled with the same runtime. These parameters never appear in the LLM tool schema or the WebUI form.

Introspection and safety

  • fn.spec — the auto-generated JSON-schema tool spec ({"name", "description", "parameters"}); fn.execute(**kwargs) invokes the wrapper with LLM-provided kwargs.
  • Self-recursion backstop: a function that re-enters itself more than 5 levels deep raises RecursionError (the model is also steered away from self-calls by an injected situational prompt).
  • Pre-invocation hooks (add_pre_invocation_hook / remove_pre_invocation_hook) run at the top of every call and may raise CancelledError to abort it (this is how the WebUI stop button works).

Recording to the DAG

  • Entering the function: write a code node (output=None, status="running"), and store the function docstring into that node's metadata.doc, which is prepended to function_name(args) when rendering context.
  • llm() inside the function body: each call writes an llm node.
  • Exiting the function: backfill the same code node's output / status.

When expose="hidden", no nodes are written. In standalone runs (with no DAG store installed), all recording is a no-op and the function executes as usual.