Skip to main content

Generations

One model loop inside a session — the record of an execution, not its live state.

Overview

A generation is created either directly (POST /v1/projects/{project_id}/agents/{agent_id}/generations) or as the effect of POST /v1/projects/{project_id}/agents/{agent_id}/sessions/{session_id}/generate (see Sessions). It carries lifecycle status, timing, the structured error when it fails, and links to the Trace that records every tool call it made.

See the OpenAPI spec for the full endpoint and schema reference, or browse it rendered under API Reference → Generations.

Data Model

Generation

FieldTypeDescription
idstringPublic generation ID.
project_idstringThe owning project.
agent_idstringThe agent that ran this generation.
trace_idstringThe trace recording this generation's tool calls.
initiator_generation_idstring, nullableThe generation that triggered this one, for sub-agent chains.
statusstringLifecycle status — see GenerationStatus.
stop_reasonstring, nullableWhy the model loop stopped.
errorobject, nullableStructured error when the generation failed.
action_idstring, nullableThe action_id label you sent when running the generation, echoed back for spend rollups.
trigger_idstring, nullableThe trigger that started this generation, when applicable.
extractionobject, nullableMemory-extraction summary (candidates, created, updated, skipped), when the agent's knowledge config produced one for this turn.
metadataobject, nullableCaller-supplied opaque metadata — nothing else. Server-owned state has its own top-level fields (action_id, trigger_id, extraction) and is never merged in here.
content_redacted_atstring (date-time), nullableWhen this generation's content was purged — see Purging generation content. Null when nothing has been purged.
started_atstring (date-time), nullable
completed_atstring (date-time), nullable
last_activity_atstring (date-time), nullable
created_atstring (date-time)
updated_atstring (date-time)

Key Concepts

Run : Trace : Generation

A generation is the innermost of three layers: a Trace is what one agent did inside an execution — a tree whose children are the traces its sub-agent calls started — and a generation is one model loop inside a trace. For a plain chat turn all three collapse to 1:1:1.

Structured output

Running a generation returns a GenerationResult, whose object is populated when — and only when — the agent has an output_schema. The model's output is constrained to that schema and the parsed value is handed back alongside the raw text:

{ "status": "completed", "text": "{\"approved\":true}", "object": { "approved": true } }

An agent with no schema always reports object: null. Streaming generations (stream: true) are never constrained, so they have no object either — see Agents → Structured output.

Background by default

A model loop can outlast the request that starts it, so POST /v1/projects/{project_id}/agents/{agent_id}/generations does not hold one open. It answers 202 straight away with a handle:

{
"status": "accepted",
"generation_id": "gen_V1StGXR8Z5jdHi6B",
"agent_id": "agent_V1StGXR8Z5jdHi6B",
"trace_id": "trace_V1StGXR8Z5jdHi6B"
}

status is always accepted — it reports that the turn was queued, not how it went. Read the outcome from GET /v1/projects/{project_id}/generations/{generation_id}, polling until status leaves in_progress.

Pass wait=true to block instead and get the turn itself — the text, the object, or the pending tool_calls — in one call:

naturali create-generation \
--project-id proj_V1StGXR8Z5jdHi6B \
--agent-id agent_V1StGXR8Z5jdHi6B \
--wait \
--messages '[{"role":"user","content":"What is the capital of France?"}]'

Waiting is the right call for a short turn a person is sitting in front of. Prefer the default for anything long, anything with tools in the loop, or anything started from a request you do not want to keep open.

Two things do not change with the mode. Every reason to reject the request — authentication, ownership, a malformed body — is still answered before the 202, so a bad request is never something you discover by polling. And stream: true always waits, since a stream holds the request open by definition; combining it with wait=false is a 400 rather than a guess.

Cost is per generation, and per project

Two grains, deliberately:

The project meter cannot substitute for the receipt. Its finest bucket is a run, a run can hold more than one generation — and a plain generation belongs to no orchestration run at all, so group_by=run buckets it under a null key.

In both places cost_usd: null means nothing was priced — never that the work was free. Only naturali-managed providers are priced; a BYOK generation runs on your own provider account, so its tokens are reported without a cost. Labelling a run with action_id when you start it is what lets spend roll up per operating action later.

Tutorials → Your first agent generation walks the whole path, from provider to priced generation.

Purging generation content

DELETE /v1/projects/{project_id}/generations/{generation_id}/content clears one generation's content — metadata, error, extraction and the internal recovery state of a paused run — and stamps content_redacted_at as verifiable proof the content is gone.

What survives is the billing and audit skeleton: ids, timestamps, status, stop reason and the attribution fields (action_id, trigger_id) the usage ledger reads. A purged generation still reads back with GET /v1/projects/{project_id}/generations/{generation_id}; a 404 there would be indistinguishable from an id that never existed, and would prove nothing about what was erased. That is also why content_redacted_at matters on its own: without it you could not tell a purged generation from one that simply never carried content.

The call is idempotent — purging an already-purged generation succeeds and leaves the original content_redacted_at in place.

Two grains of erasure

This is the narrow one, scoped to a single model turn. It does not delete the parent trace's step payload, which holds this generation's content alongside its siblings'. To erase a whole execution's content, purge the trace with DELETE /v1/projects/{project_id}/traces/{trace_id}/content, which cascades to every descendant trace and all of their generations — see Purging trace content.

Reach for the generation purge when an erasure request names one turn; reach for the trace purge when it names a run.

Examples

naturali get-generation \
--project-id proj_V1StGXR8Z5jdHi6B \
--generation-id gen_V1StGXR8Z5jdHi6B

And what that generation cost:

naturali get-generation-usage \
--project-id proj_V1StGXR8Z5jdHi6B \
--generation-id gen_V1StGXR8Z5jdHi6B

And erasing its content, keeping the billing skeleton:

naturali purge-generation-content \
--project-id proj_V1StGXR8Z5jdHi6B \
--generation-id gen_V1StGXR8Z5jdHi6B