Skip to main content

Run an agent that records nothing

By the end of this tutorial you will have an agent that answers normally and writes none of its content to disk — no prompts, no tool arguments, no results, no error payloads — while it still shows up in your audit trail and still prices on your bill.

This is a different claim from deleting. Erasing a person's data says "we deleted it", which is only as good as your ability to find every copy. Zero-retention says the content was never written, so there is nothing for a sweep to miss and nothing for a backup to keep. When content must not reach disk at all, that is the setting you want.

Three steps:

  1. Put the agent into zero-retention.
  2. Run it on something sensitive.
  3. Confirm nothing was stored.

Each step is one API call, shown for all three clients. The ids in the responses are examples — copy the ones your own calls return.

Prerequisites

  1. A credential. A nat_sk_… API key (or a session JWT from Auth) exported as NATURALI_TOKEN, and your client set up — the CLI, the SDK or plain curl against https://api.naturali.ai/v1:

    export NATURALI_TOKEN=nat_sk_...
    export NATURALI_API=https://api.naturali.ai/v1 # curl examples only
  2. A working agent. That is what Your first agent generation builds — do it first if you haven't. Arrive here with both ids exported:

    export PROJECT=proj_V1StGXR8Z5jdHi6B
    export AGENT=agent_V1StGXR8Z5jdHi6B
    export NATURALI_PROJECT=$PROJECT # lets the CLI omit --project-id

1. Put the agent into zero-retention

trace_content_mode is agent configuration, set once. none means this agent's content is never written; null — the default — inherits the project's setting.

naturali update-agent \
--agent-id agent_V1StGXR8Z5jdHi6B \
--trace-content-mode none
{
"id": "agent_V1StGXR8Z5jdHi6B",
"project_id": "proj_V1StGXR8Z5jdHi6B",
"name": "intake",
"max_steps": 20,
"trace_content_mode": "none",
"status": "active",
"updated_at": "2026-07-20T15:09:48.868Z"
}

That is the whole configuration. Everything else about the agent — its model, its tools, its instructions — is unchanged, and it will keep answering exactly as it did.

You are trading away your own observability

This is not free. With none set, traces stop carrying the step payload, so when this agent does something wrong you cannot look at what it did — no tool arguments, no results, no error text. Debugging becomes guesswork.

Turn it on for the agent that handles material you must not store, not for the whole fleet by reflex. Per-agent is the grain for exactly this reason: the sensitive agent goes dark while the rest stay debuggable.

2. Run it on something sensitive

Run a generation the way you normally would. Nothing about the call changes — that is the point.

naturali create-generation \
--agent-id agent_V1StGXR8Z5jdHi6B \
--wait \
--messages '[{"role":"user","content":"Patient reports chest pain. Summarise in one line."}]' \
--action-id intake.triage \
--metadata '{"case_ref":"MRN-88421"}'
{
"id": "gen_V1StGXR8Z5jdHi6B",
"agent_id": "agent_V1StGXR8Z5jdHi6B",
"status": "completed",
"text": "The patient is experiencing chest pain.",
"object": null,
"tool_calls": null
}

The answer came back. Zero-retention governs what is written, not what is returned — your caller gets the reply exactly as before. Note that the prompt named a patient and the call attached case_ref: MRN-88421; the next step is where you check what became of them.

export GENERATION=gen_V1StGXR8Z5jdHi6B

3. Confirm nothing was stored

Read the generation record back.

naturali get-generation \
--generation-id gen_V1StGXR8Z5jdHi6B
{
"id": "gen_V1StGXR8Z5jdHi6B",
"trace_id": "trace_V1StGXR8Z5jdHi6B",
"status": "completed",
"stop_reason": "stop",
"error": null,
"action_id": "intake.triage",
"extraction": null,
"metadata": null,
"content_redacted_at": "2026-07-20T15:09:59.330Z",
"started_at": "2026-07-20T15:09:59.338Z",
"completed_at": "2026-07-20T15:09:59.783Z"
}

Three things to read here:

  • metadata is null. You sent { "case_ref": "MRN-88421" } and the record kept nothing. error and extraction are empty for the same reason.
  • content_redacted_at is stamped, and it is earlier than started_at. That ordering is the proof of the stronger claim: the marker went on when the record was created, before the turn ran. Nothing was written and then cleared — there was never anything to clear.
  • The audit and billing skeleton survives. status, stop_reason, the timestamps and the action_id you labelled the spend with are all intact, so the run is still attributable and still priced. Check it with GET /v1/projects/{project_id}/generations/{generation_id}/usage — see Your first agent generation.

The trace tells the same story: step_count still counts the steps, has_steps is false, and content_redacted_at is stamped. The record that work happened survives; what the work contained never existed.

Reading the steps of a zero-retention trace

GET /v1/projects/{project_id}/traces/{trace_id}/steps answers 404 steps_redacted here, with content_redacted_at in details.

That is a different code from steps_not_available, which is the brief window after a normal run where the payload really is still being written. Retrying that one works; retrying this one never will. If you have a client that polls for steps, branch on the code rather than waiting it out.

What's next

  • Make it the rule, not the exception. Setting trace_content_mode: none on the project applies it to every agent in it, and becomes a floor: an agent may tighten to none but can no longer loosen to full, so the mandate cannot be escaped by creating a new agent.
  • Deal with what you already stored. Turning this on stops future writes and changes nothing about the past. Erase the backlog on demand with Erase a person's data, or give the project a trace_content_retention_days window and let the daily sweep do it.
  • Remember what this does not cover. These settings govern what this platform stores. The model still receives the prompt, and what the provider does with it is governed by your agreement with that provider — directly with them, for a BYOK provider.