Skip to main content

What you get

agent_config() gives a LangChain agent its prompt and skills as one middleware, which you pass to your own create_agent(..., middleware=[...]). It runs before you have a DecimalAI account. Nothing in LangChain is patched, so an agent gets a prompt and skills only when you give it the middleware, and other agents in the process are left alone.

Install

The extra installs langchain, where create_agent and its middleware live, and langchain-core. langchain brings langgraph with it. The chat model is a package of its own: langchain-openai for "openai:..." models, as here, or langchain-anthropic for "anthropic:...".

Run with no account

Save this as main.py:
Run it. With no DECIMAL_API_KEY, it says so once on stderr and sends nothing to DecimalAI. The project has no skills yet, so the model guesses the command.
  • middleware=[support.middleware] gives every model call a system message: your fallback= (later, the saved prompt), then the skill list and one sentence telling the model to load a skill before using it. It takes the place of create_agent(system_prompt=...): pass both and DecimalAI’s replaces yours, with one warning.
  • load_skill is the one tool the middleware adds. The model calls it with a skill’s name and gets the skill’s full text back.
  • No decimalai.init(). init() raises without a key, so a file that calls it can’t run keyless. With a key, agent_config() sets the SDK up from DECIMAL_API_KEY by itself.
To see where the list comes from, read Without a DecimalAI key.

Your own tools

Async

agent.ainvoke() and agent.astream() get the same system message and tool. On Python 3.10, see Troubleshooting.

A hand-built graph

A StateGraph of your own has no middleware to take. Give its model node the run’s system message, and bind the config’s tools:
system_message() picks the skills for the run’s latest user message on its first call, and later calls of the same run reuse the result. asystem_message() is the same on an event loop.

Older agent constructors

create_react_agent (from langgraph.prebuilt) and AgentExecutor (in langchain-classic) take no middleware. Both can be traced, and neither gets a prompt or skills from DecimalAI. For those, move to create_agent or build the graph yourself.

Add a registry skill

Run main.py again. The model loads the skill and gives the real command, playwright-cli attach --cdp=http://localhost:9222. The command wrote the skill’s files to .agents/skills/, listed it in .decimal/skills.toml, and recorded its exact bytes in .decimal/skills.lock. To find others, see Browse the registry. A skill you wrote goes in the same folder, and decimalai skills add <folder-name> lists it.

Add a key

Get one from Settings → API keys, and run main.py again. The code stays the same.
  • The first run connects the project. The agent is created in your workspace. In a git repository, your project’s skill list is copied onto it once, and .decimal/link.toml records this: commit it.
  • From now on its skills live on DecimalAI, not on disk. Each run’s list is picked from them for that run’s user message, and every model call in the run sees the same list.
  • Every invoke is one trace, filed under support.name. See Tracing.
  • Your fallback= keeps running until you save a prompt.
The detail is in Without a DecimalAI key. With a key, decimalai init <agent> --framework langchain writes a program like this for an agent that already exists.

Change the prompt without redeploying

Write a prompt on the agent’s Prompt & skills page and save it. agent_config() keeps a local copy of the prompt and re-reads it in the background at most once a minute, so the next run gets the saved prompt within a minute, in place of fallback=. An open thread keeps the prompt it started with. With a key, fallback= is what runs when the agent has no saved prompt or DecimalAI can’t be reached at startup. With a key and no fallback=, an unreachable DecimalAI raises rather than start an agent with no prompt. A saved prompt can hold variables, written {{name}}. LangChain fills them from the run’s context= (LangGraph’s runtime context), as attributes or dict keys, so {{customer.name}} reads context.customer.name or context["customer"]["name"]:
A hand-built graph passes the same values as support.system_message(messages, context=...). A {{name}} in your fallback= is filled the same way. A variable with no value stops the run before the model call with decimalai.MissingPromptVariable, which names it. DecimalAI fills only the prompt, never a skill’s text, and the trace keeps the placeholder names, not their values.

Conversations

A LangGraph conversation is a thread: a checkpointer on the agent, and one thread_id per chat. There is no support.conversation() here, because the middleware keeps what the thread needs in the thread’s private state, and the checkpointer carries it.
  • A thread keeps its first message’s system message and load_skill definition, which a chat on Claude requires, and which a provider’s prompt cache reuses. A newer saved prompt or skill list reaches the next thread.
  • A later message’s new skills arrive as a message. When the agent has more skills than one list holds, each message is ranked on its own text. The skills it picks that the thread has not shown go in as one human message after the user’s, tagged additional_kwargs["lc_source"] == "decimalai_skills". Earlier messages never change.
  • Variables keep the values of the thread’s first message, since they are part of its system message.
  • A hand-built graph keeps no thread record, so each run composes its system message afresh. On Claude with thinking, keep a thread’s first system message in your graph’s state and reuse it.

Skills the agent always uses

A skill set to Full text in every run, in its panel on the agent’s page, goes into every thread in full. It arrives as one human message at the thread’s first message, tagged additional_kwargs["lc_source"] == "decimalai_always_include", and as one more if the agent gains another mid-thread, so the system message stays the thread’s. load_skill answers for it that it is already loaded. In a hand-built graph, the text ends the system message instead.

Its own skill loader

LangChain and LangGraph read no skill folders themselves. Deep Agents (deepagents), LangChain’s ready-made agent built on LangGraph, does, and decimalai.deepagents.skills() points it at the skills decimalai skills add installed:
  • The helper returns two arguments: skills=[<your skills folder>] and a backend that can see it. The default backend reads files and has no shell, so a skill’s scripts can’t run. To run them, pass one: decimalai.deepagents.skills(backend=LocalShellBackend(root_dir=project, virtual_mode=False)), with LocalShellBackend from deepagents.backends. It runs the model’s commands on this machine.
  • Each read is recorded. init(langchain=True) traces the agent. Each SKILL.md the model reads with read_file counts as Loaded, with the version of the bytes it read, and the skills the middleware lists count as Offered. A read that stops short of the whole file carries no version.
  • Don’t add agent_config()’s middleware as well. The model would see two skill lists, and the middleware’s system message replaces the one Deep Agents builds. The SDK warns once.
The full setup is in Native skill loaders.

Tracing

With a key, agent_config() turns LangChain tracing on for the process, the same install as decimalai.init(langchain=True). Each invoke is one trace, filed under the DecimalAI agent whatever the graph is called. With no key, nothing is traced. A trace holds:
  • Chain and agent steps. Each LangGraph node is a span, with its model and tool calls nested under it.
  • Each model call, with prompt templates filled in, and each tool call, with the text the tool returned.
  • The agent’s manifest (the record of its tools, model and prompts), detected from tool schemas and model config.
  • With agent_config(): the prompt version, the variable names it filled, the skills offered, delivered and loaded, and the ranking decision.
The trace page hides LangGraph’s own scaffolding (call_model, should_continue, RunnableSequence). Tick Framework internals to see it.

Tracing only

To trace with no prompt or skills from DecimalAI, install the callback handler for the process:
Every chain invoke, LangGraph node and tool call in the process is traced from then on, and none of them gets a prompt or skills. skills=False says you trace only on purpose: without it, the first traced run with no middleware says so, once, with a warning. decimalai.langchain.instrument(agent_name="my-chain") is the same install, and takes more:
  • on_trace=fn calls fn with a copy of each finished trace before it is sent.
  • prompts={"system": "..."} names a prompt your code builds at runtime, for tracing without agent_config().
  • disk_sync=True or skill_dirs=[...] reads the project’s SKILL.md files and uploads the ones you wrote. Without them, or init(skill_authority="harness"), it reads none.
There is one install per process. A later instrument() is ignored, with a warning naming what it dropped, so call yours before agent_config() or init(langchain=True). DECIMAL_AUTO_TRACE=langchain installs it at import, ahead of any call.

Pass a handler per call

Construct the handler yourself and hand it to a single run, when two runs share a process and their traces must stay separate:
A per-call handler wins over the process’s, so a run is never traced twice. The same config= works on a compiled graph. A trace is sent when the outermost chain, agent or graph run ends, and a bare llm.invoke("Hello!") gets a trace of its own.

Multi-agent runs

Link a sub-agent’s trace to its orchestrator. See Multi-agent for what this shows.

Troubleshooting

  • this agent already has a tool named load_skill. DecimalAI’s tool is left out, so the skill list can’t be loaded from. Rename yours.
  • this agent also loads skills with Deep Agents' SkillsMiddleware. The model is shown two skill lists. Use one.
  • create_agent(system_prompt=...) is replaced by the agent's prompt. Put your code’s prompt in agent_config(..., fallback=...).
  • this LangChain run's model calls got no DecimalAI prompt or skills. init(langchain=True) traces only. Give the agent middleware=[support.middleware], or pass skills=False if you meant it.
  • GraphRecursionError after adding skills. Loading a skill costs a model call: the model asks in one call and uses the skill in the next, two more steps of the graph. Leave room under recursion_limit.
  • An async run’s trace has no model calls, on Python 3.10. asyncio on 3.10 can’t hand a run’s config to an async LangGraph node, and the SDK logs a warning the first time. The prompt and skills still reach the model. Use Python 3.11+, or .invoke().
  • DecimalAI went down during a run. That isn’t an error. The run keeps the last skill list it got, and load_skill answers with a sentence the model can read.
  • The model never loads a skill. After a run, print(support) names the prompt version and the skills on its list. With no key, an empty list means .decimal/skills.toml lists nothing. With a key, agent_config() reads no local SKILL.md files, so check that the agent gets the skill.
  • No load_skill tool on the model call. With DECIMALAI_LOAD_SKILL_TOOL=0 or decimalai.init(load_skill_tool=False), the text of the skills picked for each run goes into the system message instead. With inject_skill_body=False as well, the agent gets no skills, and one warning says DecimalAI skills are off for this agent. The other switches are in Turn it off.