Skip to main content
This tutorial walks through DecimalAI’s end-to-end training workflow: trace → evaluate → build dataset → fine-tune. You start with an empty workspace and finish with a JSONL file of training rows on your disk. It isn’t a one-shot pipeline — it’s a flywheel. Each fine-tuned model you deploy produces new traces, which feed the next round of evaluation and training:

What you need

Three things, and no agent of your own — this page generates the traces it trains on. That is the whole list through step 5, where you get a dataset file. Two later steps need more, and each says so where it applies: the hosted fine-tune in step 6 needs an OpenAI, Together.AI, or Gemini key (and a paid plan — Free cannot launch one), and pushing to HuggingFace in step 7 needs pip install huggingface_hub datasets. You do not need a framework adapter, an LLM API key, litellm, or a running agent to complete steps 1–5.

The one rule that decides whether this works

A dataset row is assembled from a trace’s LLM calls — the rendered prompt that went to the model and the completion that came back. Spans, tool timings, and status are all recorded, but they are not what a training row is made of. So a trace with no LLM call contributes nothing. A build over nothing but such traces returns a 0-row version and an HTTP 200 — it does not raise. If a build ever comes back empty, this is the first thing to check.
Why this page emits its own traces instead of running decimalai demo regression. The demo seeds a realistic agent in under a second and it is genuinely useful — if what you want is a populated dashboard to click around in, run it and go look at Traces. It is the wrong opener here for three reasons. It hands you rows without ever naming the rule above, which is exactly the knowledge you need when your own agent’s build comes back empty. It seeds an agent called [Demo] support-agent, not support-agent. And it already builds a dataset for you, which deletes step 4 — the step this page exists to teach. Twenty lines that you run yourself buy the mental model; one command buys a screenshot.

1

Emit traces that contain LLM calls

Install the SDK and point it at your workspace:
Now generate a small support-agent corpus. Save this as seed_traces.py and run it:
In production you would not write log_llm_call by hand — decimalai.init(openai=True) or a framework adapter captures the same calls automatically. Writing them out once makes the mapping visible, and it is the mapping that decides what your training rows look like:
log_tool_call does not put tool messages in the row. A tool message is emitted only when the LLM call itself reports tool calls, which the framework adapters capture and the manual log_llm_call does not expose. The rows you build here are system / user / assistant; the tool still shows on the timeline and in the manifest. Instrument with an adapter if you want tool-calling trajectories.
You should now see: six traces in the workspace, all under the agent name support-agent. Confirm from the terminal before moving on — decimalai traces list --limit 6 — or on the Traces page. If decimalai.init() raised, your key is missing or wrong; if the script printed but nothing arrives, you dropped the decimalai.flush(). A 429 Too Many Requests straight after the send is the per-key rate limiter, not a failure — the ingest already retried and succeeded; wait a few seconds and list again.
2

See what the SDK registered for you

You never declared an agent, a model, or a tool list. The SDK derived all three from the calls you logged and registered a manifest — the versioned record of what your agent is, which later becomes the filter your dataset is built from.Open Manifests (or Agents → support-agent) and you’ll find one manifest, v1, marked active, with two components: the model gpt-4o-mini and the tool search_docs.
You should now see: exactly one manifest for support-agent. More than one means your traces disagreed about the model, the system prompt, or the tool list — each distinct combination registers its own version, and a dataset built from one version won’t contain traces from the others.
3

Score the traces

Attach a quality score to each trace. seed_traces.py already wrote them to trace_scores.json; push them:
Scores show up on each trace’s Evaluation Breakdown and on the Evaluate page. In a real pipeline the score comes from a judge rather than a literal — an LLM evaluator, a regex check, a thumbs-down from a user, a support ticket that got reopened. The shape is the same: a number between 0 and 1, attached to a trace id.
Scores pushed from the SDK are visible, not enforceable. decimalai.score() records an eval score against the trace, but it does not set the trace’s aggregate eval_score / eval_verdict columns — only a server-side Evaluator does that. A dataset build filtered on min_eval_score or on a verdict will therefore match nothing and return a 0-row version. Step 4 filters on the scores you already hold in Python, which works regardless.
4

Build a dataset

Go to Datasets in the sidebar and click + New Dataset:
  1. Dataset Namesupport-agent SFT
  2. Dataset TypeFine-tuning (SFT)
  3. Target Agentsupport-agent
  4. Target Manifest — pick the v1 (active) row; it shows its trace count
  5. Click Create & Build
The manifest is the filter. Selecting one includes every trace recorded under it, plus any older trace a compatibility run marked keep or repair. There is no eval-verdict picker in this dialog, by design — compatibility and quality are separate axes, and this dialog only sets the first one.You land on the dataset detail page with version 1 built:
DecimalAI converts each trace into one chat-completion example:
The tools block comes from the manifest, so the fine-tuned model learns the tool surface it will be given at inference time. Its parameters are a placeholder here — a manual log_tool_call carries a name but no JSON Schema. Drop the tools key if your trainer validates it.Keep only your best traces. All six rows are in there, including the answer you scored 0.20. Quality is the second axis, and the filter that works with SDK-pushed scores is an explicit trace list — you already have the scores in trace_scores.json:
The dashboard’s own quality knob is Min Eval Score, on the dataset detail page’s Build new version form — not a verdict picker in the create dialog. It reads the trace’s aggregate eval_score, which is written by a server-side Evaluator and not by decimalai.score(). Configure an evaluator first, or filter by trace id as above.
You should now see: a version with a non-zero row count. A 0-row build is reported as version_status: "failed" on the API response and as a failed version in the UI — it is never a silent success. The three causes, in the order they bite: the traces carry no LLM calls; the agent name on the build doesn’t match the agent name on the traces; or a quality filter is gating on an eval_score nothing wrote.
5

Pull the data

Download the version you just built:
The SDK equivalent, if you’d rather stay in Python:
Don’t reach for decimalai datasets build --verdict. The flag is accepted and then ignored — it sends a filter key the build endpoint does not read, so you get an unfiltered build that looks like it worked. Use the trace-id build from step 4.
You should now see: a non-empty training_data.jsonl, one JSON object per line, each with a messages array. wc -l training_data.jsonl should print the row count the build reported.
6

Launch a hosted fine-tune

From the dataset detail page, click Train:
  1. Select provider: OpenAI, Together.AI, or Gemini
  2. Enter credentials: an API key for that provider — this is the first step that needs one
  3. Configure: base model, training epochs
  4. Launch
The platform submits the job and polls for completion. Training metrics (loss, validation) are stored for review.
Plan limits: hosted fine-tune launches are metered per plan — Free 0 / Core 2 / Pro 5 / Enterprise custom (see Pricing). An over-quota launch returns 429; a launch only counts once it succeeds. On the Free tier the hosted launcher is unavailable — skip to the next step and train on the file you pulled.
7

Or train it yourself

You already have training_data.jsonl, which is enough for any trainer that reads chat-format JSONL. To go through HuggingFace Hub instead:
Then use it in any framework:
8

Deploy and iterate

Point your agent at the fine-tuned model. Because the model name is part of the manifest, DecimalAI will:
  1. Detect the manifest change and register a new version
  2. Generate a compatibility report for existing traces
  3. Keep evaluating traces from the fine-tuned model
  4. Let you build the next dataset from the improved outputs
That is the loop: better model → better traces → better training data → better model.

If a build came back empty

An empty build is reported (version_status: "failed", 0 rows) but it does not raise, so it is easy to walk past. Three causes account for nearly all of them:
A training row is built from a trace’s LLM calls. A trace that recorded only spans, only tool timings, or only a status contributes zero rows. Open one trace and check that its LLM Calls section is non-empty; if it isn’t, the instrumentation never captured a model call — see the silent no-ops below.
The build filters on the manifest’s agent name and, on the by-agent path, on agent_name directly. support-agent, Support Agent, and [Demo] support-agent are three different agents, and a mismatch matches nothing without erroring. Run decimalai traces list and use the name exactly as it prints.
min_eval_score and verdict filters read the trace’s aggregate eval_score / eval_verdict, which are written by server-side Evaluators. Scores pushed with decimalai.score() are recorded and displayed but do not set them, so a build gated that way returns 0 rows. Filter by explicit trace_ids instead, or configure an evaluator first.
The SDK is fail-open in the same way further upstream — here are the six places it bites: The SDK is deliberately fail-open: a misconfiguration degrades quietly instead of crashing your agent. These are the six places that bites, in the order people hit them.
Browsing or previewing a skill on the public registry doesn’t make it routable — you have to adopt it into your org first. Either way works — Install (the Install button, or router.use("name") in the SDK) creates a linked pointer that tracks the author’s updates, and Fork (the Fork a copy button, or router.fork("name"), or router.install("name") to also write it to disk) creates an editable copy you own. The Skill Router offers both. If you use per-agent assignments, also assign it to the requesting agent. Until you install or fork it, the skill never appears in your agent’s menu, with no warning anywhere.
By default build_prompt_fragment injects one-line menu rows (name + description). The skill’s actual instructions reach the model only if you pass inject_body=True to the SkillRouter (smart-routed queries), or enable the load_skill tool so the model can pull bodies on demand. Menu rows count as offered — usage panels show rung-labeled counts for them, and activation isn’t measurable for bare prompt-injection usage. If a skill “isn’t working,” check whether its body ever actually reached the model.
The on-demand body loader is opt-in: pass enable_skill_loader=True to instrument() on the openai_agents or pydantic_ai adapters. On the anthropic adapter there is no tool loop — enable_skill_loader=True there injects the skill menu into system (the offered rung only); full bodies are injected by default once the loader is on (0.12.0+; init(inject_skill_body=False) turns that off), and note enable_load_skill_tool is accepted but dormant. Kill-switch: init(load_skill_tool=False) or DECIMALAI_LOAD_SKILL_TOOL=0.
decimalai.init(langchain=True) (and every other framework flag) logs a warning and continues untraced when the framework package isn’t importable — your agent runs, nothing is recorded. The same applies to DECIMAL_AUTO_TRACE (auto-init warns and skips) and to the raw-provider flags (openai=True etc.), which soft-skip when the matching OpenInference instrumentor is absent. Fix: for a framework flag, install the matching extra, e.g. pip install "decimalai[langchain]"; for a raw-provider flag, install the instrumentor package the startup warning names, e.g. pip install openinference-instrumentation-openai (the [openai] extra covers only the provider SDK, not the instrumentor). Then re-check startup logs.
Adapters only inject skills into prompt shapes they can safely rewrite. On openai_agents, an agent whose instructions is a user-supplied callable is left untouched; on langchain, a prebuilt PromptValue (or any unrecognized prompt shape) passes through unchanged. The agent runs normally — with zero skills injected and no error raised.
The regression check needs two things before it can say anything real: a baseline manifest (recorded automatically on the Action’s first run, or by decimalai.init() running in production) and ingested traces to measure blast radius against. A fresh workspace legitimately reports “first run — no baseline” and then near-zero affected traces. That’s honesty, not breakage — reports gain weight over days as production trace volume accumulates.

You’ve done it

Emitted traces carrying real LLM calls, with no agent and no LLM key
Watched the SDK register the agent’s manifest from those calls
Scored the traces and understood what a score does and does not gate
Built a versioned SFT dataset, then rebuilt it filtered to the good traces
Pulled a non-empty JSONL file ready for any trainer
Closed the loop — a deployed model produces traces for the next iteration

What makes this different

Most platforms stop at evaluation. DecimalAI connects:
  • Manifest compatibility — training data matches a specific, versioned agent config
  • Automatic format conversion — multi-turn, tool-using traces become chat rows, tool calls and results intact
  • HuggingFace Hub integration — one step to every open-source trainer
  • The loop repeats — each fine-tuned model feeds the next iteration

Next steps

Datasets Guide

Filter strategies, version pinning, and export formats in depth.

Replay Guide

Regenerate training data by replaying historical inputs against the new model.

Evaluations

Configure server-side evaluators so quality filters actually gate a build.

Manifests

How compatibility is computed when you change the agent.