agent_config() reads an agent’s prompt and skills from DecimalAI and hands them over per user message, as a run: run.system (the saved prompt, then the agent’s skill list), run.message (the user turn) and run.tools (a load_skill tool), which you pass to your own Chat Completions or Responses API loop. Each run is one trace, filed under the DecimalAI agent. Nothing in the OpenAI SDK is patched, so only the requests you give these pieces to get DecimalAI’s prompt and skills. decimalai.openai needs SDK 0.25 or later; through 0.24, code that called the OpenAI SDK itself used decimalai.agent_config() (see Moving from decimalai.agent_config()).
Building on the OpenAI Agents SDK (from agents import Agent) instead? See OpenAI Agents.
Install
openai and openinference-instrumentation-openai, which agent_config() uses to trace its runs.
Use
agent_config("support-agent")reads the agent’s saved prompt when it is called. It initializes the SDK fromDECIMAL_API_KEYwhendecimalai.init()has not been called, and a name your workspace has no agent by is created, so a misspelled name makes a new agent rather than an error.fallback=is what runs when the agent has no saved prompt yet, when no DecimalAI key is set, or when DecimalAI cannot be reached at startup; with a key and nofallback=, an unreachable DecimalAI raises instead of starting an agent with no prompt. With no key at all, the agent runs without a DecimalAI key: the skills your project lists in.decimal/skills.toml, with nothing sent.support.run(question)routes the agent’s skill list onquestionand opens the run’s trace. It calls no model: make the model calls inside thewithblock.run.systemis the system message’s text: the saved prompt, then the agent’s skill list with one sentence telling the model to load a skill before using it.run.messageis the user turn,{"role": "user", "content": question}. Send it instead of your own user dict: in a chat it can carry more.run.toolsholds theload_skilltool in Chat Completions’ shape, limited to the agent’s skills. The model calls it with a skill’s name and gets the skill’s full text back, which counts as delivered and activated on the usage ladder. The list is empty when the run has no skills to offer.run.tool_result(call)answers aload_skillcall with itstoolmessage, and returnsNonefor any other tool, soorhands the call to yours. Each load names the run’s routing decision, and the platform decides what it serves: a skill your org owns, has installed or has assigned to the agent loads whether or not this run’s list showed it, while one reachable only through the public registry loads only when this run was offered it. A refusal (an unknown name, an outage) comes back as a sentence the model can read.run.load_skill(name)returns the same text alone, for a dispatcher of your own.- No
decimalai.init(). With a key,agent_config()initializes the SDK fromDECIMAL_API_KEYand turns its tracing on itself.init()refuses to start without a key, so a file that calls it cannot run keyless.
decimalai init <agent> --framework openai writes this loop as a runnable file, with an example tool.
The Responses API
The same run, onclient.responses.create(): the system text goes in instructions, the user turn in input, and the tool from run.response_tools (the same load_skill, in the Responses API’s flat shape, strict). run.tool_result(call) answers a function_call item with its function_call_output:
previous_response_id does not carry the earlier request’s instructions over, so send run.system with every request.
Async, and other clients
AsyncOpenAI: open the run withasync with support.run(question) as run:, and answer each call withawait run.atool_result(call)(await run.aload_skill(name)for the text alone), which keep the load off the event loop.- OpenAI-compatible endpoints: the tools are plain dicts and
run.tool_result()reads a call as the SDK returns it or as a dict, so anOpenAI(base_url=...)client for Azure OpenAI, a local server or another provider’s OpenAI-compatible endpoint takes the same run, and its calls are traced. - LiteLLM:
litellm.completion()takesrun.system,run.messageandrun.toolsthe same way, andrun.tool_result()answers its tool calls. Calls made through a library other thanopenaiare not traced; the run’s record is.
Chats
A chat keeps one conversation, and a conversation’s system text and tool stay what its first message got, so every request in the chat starts with the same prefix (which OpenAI’s prompt cache reuses). A newly saved prompt or a newly assigned skill reaches the next conversation, not an open one.run.messageis the user turn to send: the question, plus, for an agent whose skill list is cut to fit, the skills routed for this message that the chat has not shown yet, after the question. Send it instead of your own user dict, or the model is not shown what this message’s routing picked.chat.stateis the conversation as JSON-ready data. A server that keeps no chat in memory stores it beside the chat’s history, andsupport.conversation(state=saved)resumes it in another process, with the same system text and tool. A state for another agent, orstate=together withvariables=, raisesValueError.- Variables are the conversation’s: they are filled once, at its first message. Put facts that change per message in the user message.
Change the prompt without redeploying
agent_config() keeps a local copy of the prompt and re-reads it in the background at most once a minute, so a version saved in the dashboard’s Prompt tab reaches the next run within a minute, with no redeploy (an open conversation keeps its first message’s). Pinning an older version there, or serving the latest again, works the same way.
A saved prompt can hold variables, written {{name}}, filled from variables= as attributes or dict keys ({{customer.name}} reads variables["customer"]["name"]):
MissingPromptVariable error (decimalai.MissingPromptVariable) that names it. DecimalAI fills only the prompt, never a skill’s text, and the trace keeps the placeholder names, not their values.
How each run’s skills are picked
Each run’s skill list is routed on that run’s own message.- The first run is routed before it opens.
- Later runs show the local copy of the list, and their own routing decision is recorded in the background, so no run waits on it. Closing the run does not wait either: the trace is sent once the decision lands.
- An agent with more skills than the list holds is routed before every run, so the list is picked for the message.
- A later message of a conversation shows the conversation’s list. When the list was cut to fit, the message is still routed on its own text, and the skills it picks that the chat has not shown go into
run.message.
load_skill takes only the names on it, and another name is answered with the list and loads nothing; when the list was cut to fit, it takes any name, and the platform serves a skill of the agent’s that the cap left off.
When an agent’s skills are switched off
- The tool’s kill switch. With
DECIMALAI_LOAD_SKILL_TOOL=0ordecimalai.init(load_skill_tool=False), every run is routed and the text of the skills picked for it goes intorun.systeminstead (inject_skill_body, which defaults to on once the tool is off), andrun.toolsis empty. Withinject_skill_body=Falseas well, the agent gets no skills, and one warning says so. decimalai.init(enabled=False)afteragent_config()stops routing and skills for that config; the prompt it read keeps running, and an open conversation keeps its system text. Anagent_config()called while it is off asks DecimalAI nothing: the agent runs on itsfallback=text, and without oneagent_config()raisesRuntimeError.on_unavailable="fail". By default ("continue") a run whose skill list never came back answers without skills, and its trace says so. Withagent_config(..., on_unavailable="fail")opening the run raises instead.
load_skill answers with a sentence the model can read.
Tracing
agent_config() turns on OpenAI tracing (OpenInference’s instrumentor, which wraps the SDK’s classes for the process) on the same pipeline as decimalai.init(openai=True), and sends only its runs:
- Each run is one trace, filed under
support.name, with its model calls (Chat Completions and Responses API alike), the prompt version, the variable names it filled, and the skills offered, loaded and delivered. A conversation’s runs share its id as the trace’ssession_id. A run opened inside your own span (a web server’s request span) is a trace of its own, linked to that span. - Nothing outside a run is sent. An OpenAI call made outside one, in any client of the process, is held back, and so are the app’s own spans.
decimalai.init(openai=True)sends every OpenAI call;decimalai.init(otel=True), the app’s spans. - Set up tracing first. Your own OpenTelemetry tracer provider, and
decimalai.init(otel=True), go before the firstagent_config(): OpenTelemetry sets the global provider once. - Another tool’s tracing. When the instrumentor was turned on outside DecimalAI (Phoenix, Langfuse, your own setup), DecimalAI does not turn it on again; its runs are traced as long as that tracing sends its spans to the global tracer provider.
- Reading each trace.
agent_config(..., on_trace=fn)callsfnonce per run with a copy of the finished trace before it is sent. A run made insidedecimalai.start_trace()is linked to it.
with block logs one warning that no OpenAI request was traced inside it.
What gets captured
- One trace per run: each model request with its input and output, and each
load_skillresult as a tool message in the next request’s input - The prompt version, the variable names it filled, any skill outage, and the skills offered, loaded and delivered, each delivery with the exact version the model received
- An auto-detected manifest: the model and each tool, by name. Not the system text: a run’s prompt is the saved one, whose version each trace names.
- Token counts, from each response’s usage
Checkpoint: run the agent from Use once with a question that matches one of your skills, then
print(support). It names the prompt version the agent ran and the skills on its list, for example OpenAISDKAgentConfig(name='support-agent', prompt_version=3, skills=['refund-policy']), and the run appears on the Traces page under that name. No trace? Check that the model calls were made inside the with block. An empty skill list means the router had none to offer: check that your workspace has the skill installed or forked, and that agent_config() names the agent it is assigned to (see the silent no-ops list in the Quickstart).Moving from decimalai.agent_config()
Through 0.24, OpenAI code used the top-level decimalai.agent_config(): it wrapped run.tool_spec in OpenAI’s tool shape itself, answered load_skill with run.load_skill(name), and needed decimalai.init(openai=True), agent_run() and the instrumentor package for a run to reach a trace. That still works. decimalai.openai does the same in OpenAI’s shapes, and traces each run with no other line:
What’s next
Skills
Skill list vs body delivery, the load_skill tool, and the usage ladder.
OpenAI Agents
The same prompt and skills for an agent built on the OpenAI Agents SDK.