Skip to main content

Your first agent generation

By the end of this tutorial you will have an agent that answers — created, run once, and read back step by step.

Three steps:

  1. Create an agent on the provider you already have.
  2. Run a generation and poll it to completion.
  3. Read the run back — the transcript of what the agent actually did.

Prerequisites​

  • A nat_sk_… API key exported as NATURALI_TOKEN.
  • A project id exported as PROJECT and a working AI provider id exported as PROVIDER — from either Enable naturali models (no credential of your own) or Create a provider (bring your own key).
  • A client set up as in that tutorial's Prerequisites.
export NATURALI_TOKEN=nat_sk_...
export PROJECT=proj_V1StGXR8Z5jdHi6B
export PROVIDER=aip_V1StGXR8Z5jdHi6B

1. Create an agent​

An agent is a configuration: where the model comes from, what to tell it, and how far it may go. Everything except the model source is optional.

naturali create-agent \
--project-id "$PROJECT" \
--ai-provider-id "$PROVIDER" \
--name refund-explainer \
--instructions 'Answer in one sentence. If you are unsure, say so.'
{
"id": "agent_wq7H4Ka1eDFVsBXM",
"project_id": "proj_cT9LACJi0WypPf5U",
"ai_provider_id": "aip_NzjzJDzoId8cm1Hu",
"name": "refund-explainer",
"model": null,
"instructions": "Answer in one sentence. If you are unsure, say so.",
"max_steps": 20,
"version": 1
}

version is 1 because creating the agent archived its configuration. Every later write that changes the config archives another one — which is what makes staged rollouts possible later.

model is null, so the agent generates on the provider's default_model.

export AGENT=agent_wq7H4Ka1eDFVsBXM

2. Run a generation​

Generation is background by default: the call returns immediately with a generation id, and the work continues.

naturali create-agent-generation \
--project-id "$PROJECT" \
--agent-id "$AGENT" \
--messages '[{"role":"user","content":"What is our refund window?"}]'

The call answers 202 Accepted:

{
"status": "accepted",
"generation_id": "gen_GvfSi82dWIHZ2QuJ",
"trace_id": "trace_YWhw2oowz6wCVN7N"
}
export GENERATION=gen_GvfSi82dWIHZ2QuJ

Poll it until the status leaves in_progress:

naturali get-generation \
--project-id "$PROJECT" \
--generation-id "$GENERATION"
{
"id": "gen_GvfSi82dWIHZ2QuJ",
"agent_id": "agent_wq7H4Ka1eDFVsBXM",
"status": "completed",
"stop_reason": "stop",
"error": null,
"agent_version": 1,
"usage": {
"cost_usd": 0.00000738,
"input_tokens": 19,
"output_tokens": 26
}
}

A failed status carries a structured error with at least a message — the usual causes are a credential the vendor rejected and a model the credential cannot reach.

Don't want to poll?

Add ?wait=true (--wait true on the CLI, query: { wait: true } in the SDK) and the call holds the request open and returns the finished result instead. It is the right choice for a script and the wrong one for a web request that must answer in milliseconds.

3. Read the run back​

The transcript is the run as a sequence of steps — what the model was asked, what it called, and how it ended.

naturali get-generation-transcript \
--project-id "$PROJECT" \
--generation-id "$GENERATION"
{
"generation_id": "gen_GvfSi82dWIHZ2QuJ",
"status": "completed",
"step_count": 1,
"input": [{ "role": "user", "content": "What is our refund window?" }],
"steps": [
{
"index": 0,
"text": "I am unsure of our refund window; this information may vary depending on the specific policies of the company or service in question.",
"finish_reason": "stop",
"tool_calls": [],
"tool_results": [],
"usage": { "input_tokens": 19, "output_tokens": 26 }
}
],
"output": {
"content": "I am unsure of our refund window; this information may vary depending on the specific policies of the company or service in question.",
"finish_reason": "stop"
}
}

The agent knows nothing about your policy yet, and its instructions tell it to say so rather than guess.

One step with no tool calls is what a plain question looks like. Bind a tool to the agent and the same transcript grows a step per call, with the arguments the model chose and what came back — which is how you answer "why did it say that" without guessing.

What's next​