Skip to content

Configuration and Environment Variables

This page documents environment variables used in src/.

.env is read once per process

The toolkit loads .env on the first client initialization and never re-reads it. Editing .env — including rotating an API key — has no effect on an already-running process; restart it. To use a different key without restarting, pass api_key=... to Agent(...) or to initialize_clients / initialize_async_clients, which overrides the environment variable for that client. See Async Clients.

API and provider credentials

GOOGLE_GEMINI_API_KEY

  • Required: optional globally; required to initialize Google clients
  • Format: string
  • Example: AIza...
  • Controls: Google GenAI client initialization
  • Default behavior if missing: Google client is not initialized

ANTHROPIC_API_KEY

  • Required: optional globally; required to initialize Anthropic clients
  • Format: string
  • Example: sk-ant-...
  • Controls: Anthropic sync/async client initialization
  • Default behavior if missing: Anthropic client is not initialized

OPENAI_API_KEY

  • Required: optional globally; required to initialize OpenAI clients
  • Format: string
  • Example: sk-...
  • Controls: OpenAI sync/async client initialization
  • Default behavior if missing: OpenAI client is not initialized

FIREWORKS_API_KEY

  • Required: optional globally; required to initialize Fireworks clients
  • Format: string
  • Example: fwk-...
  • Controls: Fireworks sync/async client initialization
  • Default behavior if missing: Fireworks client is not initialized

LLMWHISPERER_API_KEY

  • Required: optional globally; required to initialize LLM Whisperer client
  • Format: string
  • Example: lw-...
  • Controls: OCR client initialization used by PDF processing utilities
  • Default behavior if missing: LLM Whisperer client is not initialized

LLMWHISPERER_BASE_URL_V2

  • Required: optional
  • Format: string URL
  • Example: https://llmwhisperer-api.us-central.unstract.com/api/v2
  • Controls: LLM Whisperer base endpoint
  • Default behavior if missing: defaults to US-Central endpoint

Agent runtime and logging

AGENT_LOG_LEVEL

  • Required: optional
  • Format: string enum (DEBUG|INFO|WARNING|ERROR|CRITICAL|NOTSET)
  • Example: INFO
  • Controls: default logger level in agent/core.py
  • Default behavior if missing: call-level log level argument is used

Where the default log file goes

With no logger= and no log_file=, an Agent writes to ./logs/<logger-name>_<timestamp>.log. The timestamp is resolved once per process, so building many Agents in one process appends to one file per logger name rather than opening a new file per Agent — tens of thousands had accumulated in long-lived working trees. A handler already open on that path is reused rather than closed and reopened, and a handler on a different path is still removed, so sub-agent logs cannot cross-contaminate.

Pass log_file= to choose the path, or pass your own logger= to take over logging entirely; in that case the toolkit never creates ./logs/ at all, and importing the package neither stamps a timestamp nor creates the directory. AGENT_LOG_LEVEL sets the level for Agent, and SUB_AGENT_LOG_LEVEL does the same for the sub-agents the agent-factory MCP server creates.

DPT_AGENT_LOG_TO_FILE

  • Required: optional
  • Format: 0, false, no or off (case-insensitive) to disable; 1, true, yes or on to enable
  • Example: 0
  • Default behavior if missing: on — the default log file described above is written
  • Controls: whether the logger an Agent builds for itself (no logger= passed), and the logger of each agent-factory sub-agent, writes a log file. Off gives a console-only logger: no file is opened, ./logs/ is never created, an explicit log_file= is ignored with a warning, and the agent-factory tools report log_file: null. Agent(log_to_file=False) does the same for one Agent. An unrecognised value is ignored with a single warning per process and leaves file logging on.
  • When to use it: server and container deployments where a library writing files into the working directory is unwanted, or where logs are collected from the console.

DEBUG_API_RESPONSES

  • Required: optional
  • Format: bool-like string ("true" to enable)
  • Example: true
  • Controls: verbose API response logging path in utils. It is read once, when doc_processing_toolkit.utils is first imported and before .env is loaded, so set it in the process environment rather than in .env.
  • Default behavior if missing: verbose response logging disabled

DEBUG_AGENT_STREAMING

  • Required: optional
  • Format: bool-like string ("true" to enable)
  • Example: true
  • Controls: grouped debug logging for Agent.stream() / AgentSession.stream() text and thinking deltas
  • Default behavior if missing: grouped stream debug logging disabled

DOC_TOOLKIT_SESSION_HISTORY_MODE

  • Required: optional
  • Format: string enum (full|messages_only|no_tool_results|no_reasoning|compact_tool_results|text_summary)
  • Example: no_reasoning
  • Controls: default session history mode, below per-session and Agent-level overrides (see Sessions)
  • Default behavior if missing: full
  • The value must be one of the six mode names exactly (lowercase). Any other value is ignored with one WARNING per process, and full applies (it used to be ignored silently).
  • Behaviour change: on Google, messages_only used to delete every message of a session, not only its tool traffic, so a Gemini session pinned to this mode through the environment variable ran with no memory at all. It now replays every earlier question and answer, which raises those sessions' input-token cost. See Sessions.

DPTTOOLS_SKILLS_DIR

  • Required: optional
  • Format: filesystem path
  • Example: /path/to/.agent_skills
  • Controls: the directory the skills MCP server reads skill definitions from (see Skills). It is read by the server, in the server's own process: set it in that process's environment — for an mcp_command / mcp_script server through the config's env (a subprocess does not inherit this process's variables), for a server you start yourself through its StdioServerParameters(env=...), for an in-process server in this process. Every DPTTOOLS_* variable is documented on the std-tools configuration page.
  • Default behavior if missing: the server uses its own default (./.agent_skills in its working directory) and reports an empty catalog when that does not exist; the toolkit then hides the activation tools. Since 0.5.3 the toolkit itself does not read this variable.

Provider behaviour kill switches

These switches exist so a behaviour change that landed in this release can be turned off without a downgrade. The three Google switches accept on (the default, also for an unset or empty variable) and off (case-insensitive, surrounding whitespace ignored). DPT_GOOGLE_EMPTY_TURN_RETRY additionally accepts identical. DPT_ANTHROPIC_BLOCK_BINDING takes three named values; DPT_ANTHROPIC_STRICT_BUDGET and DPT_COMPACTION_PROTECT_CURRENT_TURN take on or off; and DPT_SDK_RETRIES takes default or an integer. Every switch in this section, and the opt-in DPT_PREFIX_AUDIT flag, logs one warning per distinct value it does not recognise and then applies its default (DPT_PREFIX_AUDIT stays off).

DPT_GOOGLE_RETAIN_FINAL_TURN

  • Required: optional
  • Format: off to disable; anything else (or unset) enables
  • Default behavior if missing: on
  • Controls: whether a Gemini session stores the final answer as the model response object the API returned, so its text thought_signature survives into the next turn. off stores the answer as plain text instead, which drops the signature — the behaviour before this release.
  • When to use it: leaving it on adds roughly 200–1,700 input tokens per stored turn when thinking is enabled, because each stored answer replays its thought summary, and that compounds across a session and brings compaction forward. Turn it off if that cost outweighs the reasoning quality Google's guidance attributes to returning the signature. See Sessions.

DPT_ANTHROPIC_RETAIN_FINAL_TURN

  • Required: optional
  • Format: on or off (case-insensitive); any other value is ignored with one WARNING and leaves the default
  • Default behavior if missing: on
  • Controls: whether an Anthropic session stores the final answer of each turn as the block list the model returned — its signed thinking / redacted_thinking blocks plus the text — so the next request replays that thinking. It applies only on the rows whose registry entry sets produces_preserved_thinking_blocks (claude-fable-5-1, claude-opus-5-5, claude-sonnet-5-5, claude-haiku-5-5) and only to a turn that carried a thinking block; every other row stores the answer as plain text exactly as before. It covers the tool loop and the no-tools path through AgentSession.run(), and a streamed turn with output_schema (stored exactly as run() stores it); a plain-text AgentSession.stream() turn keeps its plain-text record. off stores the answer as plain text on the gated rows too — the behaviour before this release.
  • When to use it: leaving it on raises input tokens on the gated rows by the replayed thinking of every stored answer, which compounds across a session and brings compaction forward. Turn it off if that cost outweighs the reasoning continuity Anthropic's preserved-thinking guidance attributes to sending the thinking back. A session later moved to a model that cannot read the blocks still replays them; the API drops them. See Extended Thinking.

DPT_GOOGLE_EMPTY_TURN_RETRY

  • Required: optional
  • Format: off to disable, identical for the byte-for-byte re-send; anything else (or unset) gives the default nudge behaviour
  • Default behavior if missing: on (nudge)
  • Controls: the single bounded re-request after an empty Google model turn. Gemini occasionally returns a model turn with finish_reason=STOP, no usable content and zero output tokens after a tool result. The toolkit re-requests once — that request is billed and appears as its own cost record — and fails the run with an explicit "Empty model response" error if the turn is still empty. The re-request appends one short synthetic user turn (providers.google.GOOGLE_EMPTY_TURN_NUDGE_TEXT, "Use the tool results above to answer the question.") that is sent on the wire only and never stored in history — not even when compaction fires later in the same run, which used to copy it into the session's stored messages through the post-compaction resync: live, an identical re-send came back empty again in 8 of 14 occurrences, while the nudged form recovered 44 of 44.
  • When to use it: identical keeps the re-request byte-for-byte identical to the original — choose it if a synthetic turn would disturb your own transcript accounting, accepting that it is a re-roll rather than a recovery. off restores the earlier behaviour, in which such a run returned an empty answer with no error at all; choose it only if an empty answer is more useful to your application than a loud failure. A final turn carrying only a thought summary is never treated as empty. See Providers.

DPT_GOOGLE_BILL_TOOL_USE_TOKENS

  • Required: optional
  • Format: on or off (case-insensitive, surrounding whitespace ignored)
  • Default behavior if missing: on
  • Controls: whether a Gemini request's usage_metadata.tool_use_prompt_token_count is billed as input (0.5.1). Gemini reports the server-side tool output it feeds the model there, outside prompt_token_count: the pages a URL-context call retrieves, and code execution's generated code and results. Google bills both as input, and total_token_count includes them. When on, both Google paths (the tool loop and the structured / text call) add them to input_tokens and to the input modality breakdown on a response that shows URL context or code execution ran, so each request's cost record and the run's cost include them. A search-only response is unchanged (search is priced per query), and the in-loop compaction check still reads the prompt alone. off restores the earlier billing exactly: only prompt_token_count was billed, so a run with URL context or code execution was under-reported (one URL: 17 prompt tokens against 1,323 tool-use tokens, measured 2026-09-28). Google Search reports no tool-use tokens; it is priced per query, as before.
  • When to use it: leave it on. Turn it off only to compare a run's cost with a figure computed before 0.5.1. See Cost Tracking.

DPT_ANTHROPIC_REFUSAL_BILLING

  • Required: optional
  • Format: category or legacy (case-insensitive, surrounding whitespace ignored); any other value is ignored with one WARNING and leaves the default
  • Default behavior if missing: category
  • Controls: how a Claude refusal that arrives before any output is priced (0.5.2). With category, such a refusal is billed only when its category is one Anthropic bills (bio, frontier_llm, reasoning_extraction today, read from pricing.anthropic.refusal_billing in the registry), and the same rule applies to each attempt of a server-side fallback: a declined attempt in a billed category is added to cost at the declining model's rates, and to the request's token counts. legacy restores the pricing before 0.5.2 exactly: a fallback attempt that declined before any output is never billed, an output-less last step of an attempt that declined partway is not billed either, a refusal with no fallback is always billed from its usage, whatever its category, and a request's tokens are the top-level usage alone.
  • When to use it: leave it on category, which follows Anthropic's billing. Use legacy only to compare a run's cost with a figure computed before 0.5.2. See Cost Tracking.

DPT_ANTHROPIC_BLOCK_BINDING

  • Required: optional
  • Format: off, drop_block or error (case-insensitive)
  • Default behavior if missing: the model's registry row decides. Four rows opt in today (claude-fable-5-1, claude-opus-5-5, claude-sonnet-5-5 and claude-haiku-5-5, which set enforces_thinking_prefix_check): every thinking request on them carries drop_block and the beta header. Every other row sends nothing.
  • Controls: whether an Anthropic request carries thinking.block_binding.prefix_mismatch_behavior and the thinking-binding-controls-2026-08-01 beta header. off suppresses both on every model, including one whose registry row enables them — it is the kill switch. drop_block and error opt in process-wide. An unrecognised value is ignored with a single warning per process.
  • When to use it: off if your account or gateway rejects the beta header; error in CI, where a replayed thinking block that no longer matches its conversation prefix should fail loudly rather than be dropped. Precedence is a thinking={"block_binding": ...} you pass yourself, then Agent(anthropic_block_binding=...), then Agent(strict_prefix_binding=True), then this variable, then the registry. See Thinking.
  • With thinking turned off: an off value cannot carry the field, so nothing is sent whatever this variable says. error still has one effect there: on a model that checks the prefix, a prefix-mismatch 400 is raised with its cause instead of being retried with the thinking blocks removed. See Thinking off on a model that checks the thinking prefix.

DPT_ANTHROPIC_STRICT_BUDGET

  • Required: optional
  • Format: on or off (case-insensitive, surrounding whitespace ignored)
  • Default behavior if missing: on
  • Controls: how strict_tools=True (the Agent default) picks the Anthropic tools it makes strict. Anthropic refuses a request whose strict schemas exceed any of three limits in total: 20 strict tools, 24 optional properties and 16 union-typed parameters. The counts cover every strict tool, nested objects included, and a structured run's response schema counts toward the property and union limits. The 400 reads, for example, "Schemas contains too many optional parameters (40) ... (limit: 24)". When on (0.5.1), the tools with the fewest optional properties are made strict first (then the fewest union-typed parameters, then by name) while all three budgets last, the response schema's share reserved first. A tool that does not fit is sent without strict mode, one WARNING names every such tool, and every tool is still offered. off restores the earlier check, which looked only at each tool's own optional properties (a tool over 24 is sent without strict mode). Under that check nine small tools (the agent-factory server's, 40 optional properties in all) drew the 400, and so did any agent with more than 20 tools. The optional-property limit is SCHEMA_OPTIONAL_COUNT_ANTHROPIC.
  • When to use it: leave it on. Turn it off only if a gateway in front of Anthropic accepts more and you want strict mode on every tool that passes the per-tool check. See Strict tool schemas on Anthropic.

DPT_PREFIX_AUDIT

  • Required: optional
  • Format: 1, true, yes or on to enable (case-insensitive); any other value leaves the audit off, without a warning
  • Default behavior if missing: off
  • Controls: the shadow prefix audit for every Anthropic request in the process. It fingerprints the system prompt, the tools array and each message before a request and logs what changed since the previous one as a prefix_drift record, without altering a single request byte — proved byte-identical across every compaction strategy and history mode in the golden harness.
  • When to use it: to find out what is moving your cached or bound prefix — a rebuilt instruction string, an MCP server whose tool list changed mid-session, a compaction strategy rewriting more than you expected. It works on every model and needs no beta header. Per agent instead of per process: Agent(prefix_audit=True).

DPT_SDK_RETRIES

  • Required: optional
  • Format: default, or a non-negative integer
  • Example: default
  • Default behavior if missing: the Anthropic and OpenAI SDK clients are built with max_retries=0
  • Controls: the SDKs' own retry layer underneath tenacity. By default tenacity is the only retry layer: each of its five attempts is exactly one HTTP request, and every retry is logged and billed in retry_count / retry_cost. default restores the SDKs' own default of two retries per attempt — the behaviour before this release, worst case 15 requests with 10 of them invisible. An integer is passed through as max_retries. Anything else is ignored with a single warning per process (clients keep max_retries=0). Read when a client is built; clients are process-cached, so set it before the first request. Fireworks clients always use max_retries=0 and google-genai has no SDK retry layer, so neither is affected.
  • When to use it: default if you relied on the SDKs' fast (0.5 s) first retry; with the SDK layer off, a transient failure waits tenacity's back-off (a server retry-after, else 4 s). See Cost Tracking.

DPT_OPENAI_REALTIME_REASONING

  • Required: optional
  • Format: on or off (case-insensitive); any other value is ignored with one WARNING per process and leaves the default
  • Default behavior if missing: on
  • Controls: whether thinking_level= on an OpenAI realtime session is sent as session.reasoning.effort. It applies only to the rows whose registry entry publishes supported_thinking_levels (gpt-realtime-2, gpt-realtime-2.1, gpt-realtime-2.1-mini: minimal, low, medium, high, xhigh); on every other OpenAI realtime row the value is dropped with a WARNING, as before. off drops it with a WARNING on the reasoning rows too — the behaviour before this release.
  • When to use it: higher reasoning effort can raise latency and output tokens on a voice turn. Turn it off if a deployed voice agent already passes thinking_level (it used to be ignored) and you need the old latency back before you can change the call. See the OpenAI provider notes on Realtime Voice.

DPT_MERGE_REASONING_INTO_SCHEMA

  • Required: optional
  • Format: on / off (also 1 / 0, true / false, yes / no; case-insensitive, surrounding whitespace ignored); any other value is ignored with one WARNING per distinct value and leaves the default
  • Default behavior if missing: on
  • Controls: whether the model's reasoning text is appended to a schema field named internal_reasoning, thoughts or reasoning (case-insensitive) of a structured answer, under a ***Internal Reasoning:*** heading. On is today's behaviour on all four providers; off leaves the field exactly as the model wrote it. The reasoning is on AgentResult.reasoning_content either way — that is the canonical location. One shared predicate (utils.merge_reasoning_into_schema_enabled) gates every merge site, and it reads the variable on each structured answer, so a change takes effect without a restart.
  • When to use it: off if your schema has such a field for the model's own words and the appended copy duplicates reasoning_content. The merge is scheduled to be retired in 0.5 (the default becomes off, as a breaking change). See Agent.

DPT_OPENAI_WEB_SEARCH_SOURCES

  • Required: optional
  • Format: on or off (0, false, no also mean off)
  • Default behavior if missing: on
  • Controls: whether OpenAI requests that carry web_search also send include=["web_search_call.action.sources"] (merged into any include already present) and whether distinct_urls adds the URLs each search visited to the ones the answer cites — which is what fills the field on JSON-mode and output_schema answers, where no citation annotations exist. off restores the previous request (no include entry) and the citations-only list. Any other value is ignored with one warning per process.
  • When to use it: if a proxy rejects the include field, or if you depend on distinct_urls listing only cited pages. See Built-in tools recipe.

DPT_OPENAI_RETAIN_ENCRYPTED_REASONING

  • Required: optional
  • Format: on or off (case-insensitive); any other value is ignored with one WARNING and leaves the default
  • Default behavior if missing: on
  • Controls: whether OpenAI requests on a reasoning row (a registry entry with supported_reasoning_values) send include=["reasoning.encrypted_content"] (merged into any include already present) and whether an AgentSession keeps the returned encrypted_content on the reasoning items it stores. With both, a stored OpenAI conversation replays its reasoning on its own: it no longer depends on OpenAI's 30-day response store and works under zero-data retention. off restores the previous request (no include entry on a reasoning row) and the previous storage (the payload stripped; replay by rs_ id through the store). The status field is dropped from stored reasoning items either way.
  • When to use it: leave it on. Turn it off if a proxy rejects the include field, or if envelope size matters more than store-independent replay (an encrypted payload is a few KB per reasoning item). See History and Checkpointing.

DPT_COMPACTION_PROTECT_CURRENT_TURN

  • Required: optional
  • Format: off to disable; on, empty or unset enables. Case-insensitive, surrounding whitespace ignored. Any other value is ignored with a single warning per process, and the default applies.
  • Default behavior if missing: on
  • Controls: whether in-loop (Level 2) compaction protects the turn in progress, on all four providers. When it is on, summarize, the summarise step of combined and sliding_window compact only the messages before the current turn's user message. The request, the draft the model has already written and the open tool round are kept verbatim. When nothing precedes the turn, the in-loop compaction falls back to tool_result_clearing on the turn. off restores the earlier behaviour byte for byte: the turn is folded into the summary, followed by the synthetic "Continue the task" turn. Pre-call (Level 1) compaction and tool_result_clearing on its own are not affected either way.
  • When to use it: leave it on. Folding the turn is what made models answer "the previous task has already been completed" instead of finishing the task. Turn it off only if you depend on the exact pre-change request shape, or if a single turn's own tool loop regularly fills the context window and you prefer the old aggressive summary to a larger context. See Compaction.

OpenAI transport

DPT_OPENAI_TRANSPORT

  • Required: optional
  • Format: http or websocket (case-insensitive, surrounding whitespace ignored); any other value is ignored with one WARNING and leaves the default
  • Default behavior if missing: http
  • Controls: the process default for the transport of the OpenAI tool loop (0.5.2). With websocket, a run with local tools sends its requests over one WebSocket to the Responses API instead of one HTTP request each. Agent(openai_transport=...) wins over it for that agent. Read on every run, so it takes effect without a restart. If the websockets package cannot be imported, the run logs one WARNING and uses HTTP.
  • When to use it: for agents that call tools in a loop, where each request saves connection overhead; leave it unset for single-request workloads, which are slower over a socket. Results, billing and retries are the same on both transports. See OpenAI WebSocket Transport.

Session kill switches

DPT_SESSION_INTERRUPTED_TURN

  • Required: optional
  • Format: on or off (case-insensitive); any other value is ignored with one WARNING and leaves the default
  • Default behavior if missing: on
  • Controls: what an AgentSession keeps of a turn that did not complete. On, a turn that raises, or returns an error-shaped result with no transcript and no answer, has its user message removed from the stored history and recorded on metadata["interrupted_turn"] (message, files_attached, error, error_type, failed_at, run, undelivered_injections), which survives the envelope and is cleared when the next turn starts; session.interrupted_turn reads it. off restores the previous behaviour exactly: the unanswered question stays in messages as a dangling user turn and nothing is recorded. See History and Checkpointing.
  • When to use it: leave it on. Turn it off only if a host parses the stored history for the failed question itself and cannot yet read the record.

DPT_GOOGLE_MERGE_TEXT_PARTS

  • Required: optional
  • Format: on or off (case-insensitive); any other value is ignored with one WARNING and leaves the default
  • Default behavior if missing: on
  • Controls: how an AgentSession stores a Gemini answer that arrived as several plain text parts (one per streamed chunk). On, adjacent plain text parts of a stored Google message are merged into one part when the turn is stored, so the history replays as one text block per run of text; a part carrying a thought_signature, a thought, a function call or media is never merged and stays exactly as returned. off restores the previous storage: one part per chunk.
  • When to use it: leave it on. Turn it off only if a host reads the stored parts and depends on the per-chunk split.

DPT_SESSION_CROSS_PROVIDER_CONVERSION

  • Required: optional
  • Format: on or off (case-insensitive); any other value is ignored with one WARNING and leaves the default
  • Default behavior if missing: on
  • Controls: what AgentSession.from_dict / from_json / rebind do with a stored history written in one provider's dialect when the agent is on another provider. On, the history is converted through the neutral representation — tool rounds and attachments cross, what cannot cross is dropped and recorded on conversion_report — and only a history that cannot be converted raises SessionCompatibilityError. off restores the previous behaviour: every provider-native history is refused with SessionCompatibilityError, and the host restores it under the provider that wrote it or truncates it to its plain-text turns first. See Continuing on another provider.
  • When to use it: leave it on. Turn it off only if a host must never continue a conversation on another provider without its own review of what a conversion would drop.

Streaming kill switches

Two switches cover the Agent.stream() changes described in Stopping a stream early. Both accept on or off (case-insensitive, surrounding whitespace ignored). Unset means on. off restores the previous behaviour exactly. Any other value is ignored with one warning per process and leaves the behaviour on.

DPT_STREAM_CANCEL_ON_CLOSE

  • Required: optional
  • Format: on or off
  • Default behavior if missing: on
  • Controls: what happens when the consumer closes Agent.stream() or AgentSession.stream() before the complete event, whether through aclose(), contextlib.aclosing or the interpreter finalizing an abandoned generator. When on, the run is cancelled and the toolkit waits at most STREAM_CANCEL_WAIT_SECONDS (5 s) for it to stop. It then reports what the run had already spent as a complete-style record with metadata["abandoned"] = True. The record goes to Agent.last_abandoned_stream, to the session's totals and last_turn_results, and to hooks. AgentSession.stream() also closes its inner generators at once, so a consumer that stops at the complete event still has that turn banked. off restores the previous handler: an unbounded wait for the cancelled run, and no record.
  • When to use it: turn it off only if a hook or a session-metadata consumer cannot handle the extra complete event or the extra banked run.

DPT_STREAM_STRUCTURED_OFFLOOP

  • Required: optional
  • Format: on or off
  • Default behavior if missing: on
  • Controls: where a streamed run without local tools makes its provider call. Such a run takes the provider's synchronous structured-output function. When on, that function runs in a worker thread (asyncio.to_thread) and every delta is handed back to the event loop as it arrives, so the stream is real-time and other tasks on the loop keep running. A closed stream also stops the request at the next chunk. off calls the function on the event-loop thread again: the loop is blocked for the whole request, and every delta arrives after the response has returned. Agent.run() is not affected either way.
  • When to use it: turn it off if your own code relies on the provider call running on the event-loop thread, for example a logging handler or a patched SDK client that is not thread-safe.

In-process tool kill switches

Two switches cover the 0.5.3 changes to how a tool of an in-process FastMCP server (tool format 4 — the SDK-bundled mcp.server.fastmcp.FastMCP or standalone fastmcp<3) is called. Neither touches fastmcp>=3 servers, which are called through their own public call_tool. Each takes on (the default, also for an unset or empty variable) or off; an unrecognised value is ignored with one warning per distinct unrecognised value and on applies.

DPT_FASTMCP_VALIDATED_CALL

  • Required: optional
  • Format: on or off
  • Default behavior if missing: on
  • Controls: whether a registry tool is called through its own run (since 0.5.3) rather than as fn(**arguments) with the arguments as the model sent them. On the SDK-bundled FastMCP, run validates and coerces the arguments as the server does over stdio: an unknown argument is dropped (before, it raised), a string is coerced to an annotated int, a tool that asks for a Context receives one bound to the server (only ctx.fastmcp works; its log and progress methods raise and the call becomes an error result) — and, which a tool written against the old direct call can notice, a pydantic-model parameter arrives as the model instance rather than a dict, date / Path / Enum parameters arrive converted, and a value the schema rejects (2.5 for an int, null for a parameter that is not Optional) is an error result instead of being passed through. A fastmcp<3 tool's run validates by its own rules (it rejects an unknown argument, as fastmcp>=3 does) and gets no toolkit-made context. off restores the direct call exactly.
  • When to use it: turn it off if an in-process tool relied on receiving raw values — a dict where a model is annotated, a string where a date is — until the tool is adjusted.

DPT_FASTMCP_SYNC_TOOLS_OFFLOOP

  • Required: optional
  • Format: on or off
  • Default behavior if missing: on
  • Controls: where a synchronous tool function of an in-process FastMCP server runs. When on, it runs on a worker thread (anyio.to_thread.run_sync), so the event loop keeps serving stream events, hooks and its other tasks (a concurrent run, another agent's turn) while the tool works (since 0.5.3). It does not run a round's tool calls in parallel: every provider awaits them one after another. off calls the function on the event-loop thread again: one slow tool stalls everything else on the loop for its duration — the behaviour before 0.5.3. Async tools, and a tool whose run wraps no function of its own (a fastmcp<3 proxy or OpenAPI tool), stay on the loop either way.
  • When to use it: turn it off if a synchronous tool touches thread-affine state — an SQLite connection opened on the loop thread, a client that is not thread-safe. See MCP.

Text-to-speech kill switches

Two switches cover the 0.5.1 changes to synthesize_speech described in Multimedia. Both accept on or off (case-insensitive, surrounding whitespace ignored). Unset means on. off restores the behaviour before 0.5.1 exactly. Any other value is ignored with one warning per process and leaves the behaviour on.

DPT_GOOGLE_TTS_RAW_PCM

  • Required: optional
  • Format: on or off
  • Default behavior if missing: on
  • Controls: what output_format="pcm" returns on a Google TTS row. When on, the raw 16-bit samples, with mime_type audio/l16;rate=24000. off returns the WAV container instead, which is what "pcm" returned before 0.5.1. "wav" and the default are unaffected either way, and so are the streaming functions, which always stream raw PCM on Google. The reader is multimedia.google_tts_raw_pcm_enabled().
  • When to use it: turn it off if your code passed output_format="pcm" to a Google row and relies on getting a WAV back.

DPT_OPENAI_TTS_SSE_USAGE

  • Required: optional
  • Format: on or off
  • Default behavior if missing: on
  • Controls: how synthesize_speech calls an OpenAI TTS row with supports_sse_usage: true (gpt-4o-mini-tts). When on, it asks for the SSE form of the response, assembles the audio from its delta events, and bills the provider's own input_tokens and output_tokens, so cost_estimated is False. off sends the raw-audio request used before 0.5.1 and estimates the cost from the text and the clip's duration. The streaming functions (synthesize_speech_stream, asynthesize_speech_stream) follow the same switch: on, they stream the SSE form and bill its usage; off, they stream raw audio and estimate. The reader is multimedia.openai_tts_sse_usage_enabled().
  • When to use it: turn it off if a proxy between you and OpenAI cannot pass an event stream, or if you need the exact request used before 0.5.1. A single call can keep the raw request with provider_options={"stream_format": "audio"}.

Tool-reported spend

DPT_AGENT_TOOL_REPORTED_COST

  • Required: optional
  • Format: on or off (case-insensitive, surrounding whitespace ignored). Any other value logs one WARNING and is treated as on.
  • Default behavior if missing: on
  • Controls: whether an Agent adds the spend its tools report (the _dpt_spend marker the multimedia MCP tools, the agent factory's create_agent and suggest_agent_config, and your own tools can return) to its cost, tool_reported_cost and tool_reported_costs. off restores the totals from before 0.5.1 exactly: nothing is read, the new fields stay at 0.0 and [], and tool_call_result events carry no tool_reported_cost. utils.tool_reported_cost_enabled() reads the variable on every call.
  • When to use it: turn it off if your app already adds these tools' costs to the agent's cost itself, until it stops doing so. See Cost Tracking.

Multimedia MCP server output

DPT_MULTIMEDIA_OUTPUT_DIR

  • Required: optional
  • Format: a folder path (surrounding whitespace ignored; blank is the same as unset)
  • Default behavior if missing: <system temp>/doc_processing_toolkit/multimedia, created on first use. The system temp folder follows TMPDIR / TEMP / TMP, so inside a sandbox it is the sandbox's own. On Linux and macOS, where /tmp is shared, the folder is doc_processing_toolkit-<uid>/multimedia, created private to the user (mode 700). Each of its two components is refused when it exists as a symlink, belongs to another user, or can be written to by the group or other users, so nobody else can redirect the files.
  • Controls: where the multimedia MCP tools (synthesize_speech, generate_image, edit_image, and the transcript copy transcribe_audio saves) write their files when the call gives no output_dir. The tools never return base64 (0.5.1), so the file is the output: a call's output_dir wins, then this variable, then the default. A folder that cannot be created or written to is an error result naming the folder and this variable, before any provider call is billed. The toolkit never deletes these files, and never overwrites one: a clashing name gains a _2 suffix.
  • When to use it: when the app reads the files from another container (point it at a shared volume), when the process cannot write to its temp folder, or to keep the files somewhere you clean up. See MCP Integration.

Google Files API upload wait

A Google request carrying a file uploads it through the Files API and waits for it to reach ACTIVE. These three variables set the process-wide default for that wait; each is overridden per call by the matching Agent(...) provider keyword (file_active_timeout_s, file_poll_interval_s, file_poll_backoff). Pass nothing and set nothing and the wait behaves exactly as it did before this release. An out-of-range parameter raises before the first state check, because clamping would hide the typo the caller was trying to escape; an out-of-range or unparseable environment value logs one warning and falls back to the built-in default, because an operator typo must not brick a process. Empty or whitespace-only counts as unset, not as bad.

DPT_GOOGLE_FILE_ACTIVE_TIMEOUT_S

  • Required: optional
  • Format: number of seconds, 1–3600
  • Default behavior if missing: 60 s for images and 120 s for video and audio. A document waits 60 s on the synchronous path and 120 s on the async path.
  • Controls: how long a request waits for an uploaded file to become ACTIVE. A long video exceeds the built-in ceiling and used to fail with a timeout a caller had no way to raise.
  • When to use it: raise it for long video or audio. The timeout error names the file, the state it was last seen in, how many checks were made and which knob to turn.

DPT_GOOGLE_FILE_POLL_INTERVAL_S

  • Required: optional
  • Format: number of seconds, 0.05–60; must not exceed the timeout
  • Default behavior if missing: 1 s for images, 2 s otherwise
  • Controls: the gap between file-state checks while waiting.

DPT_GOOGLE_FILE_POLL_BACKOFF

  • Required: optional
  • Format: multiplier, 1–10
  • Default behavior if missing: 1.0 — the fixed cadence used before this release
  • Controls: the factor the poll interval is multiplied by after each check. The interval is clamped to the remaining budget on the last sleep, so a backoff can never overrun its own timeout.

Audio conversion

doc_processing_toolkit.audio_convert transcodes a recording whose container the target speech-to-text model does not accept. The converter is discovered, never required — see Multimedia — and with none installed an unaccepted container raises a named error instead of reaching the provider as a 400.

DPT_FFMPEG_PATH

  • Required: optional
  • Format: filesystem path to an ffmpeg executable
  • Default behavior if missing: ffmpeg on PATH, then the binary imageio-ffmpeg bundles, then PyAV, then no converter
  • Controls: which ffmpeg the converter uses. Checked first, so it always wins. A path that is not a file logs one warning and discovery continues with PATH.
  • When to use it: a pinned or sandboxed ffmpeg build, or a machine where PATH is not the one the process inherits. The discovery verdict is cached for the process; audio_convert.reset_converter_cache() clears it in a test.

DPT_AUDIO_CONVERT_TIMEOUT

  • Required: optional
  • Format: number of seconds, greater than 0. A value that is not a positive number logs a warning and the default applies.
  • Default behavior if missing: 60
  • Controls: the wall clock allowed for one ffmpeg subprocess. A transcode that outruns it is killed and raises AudioConversionError.

DPT_AUDIO_CONVERT_MAX_MB

  • Required: optional
  • Format: number of megabytes, greater than 0. A value that is not a positive number logs a warning and the default applies.
  • Default behavior if missing: 200
  • Controls: the largest input accepted for conversion, so a multi-gigabyte recording is refused locally rather than handed to a subprocess and buffered in memory.

Agent factory server env vars

SUB_AGENT_LOG_LEVEL

  • Required: optional
  • Format: string log level
  • Example: DEBUG
  • Controls: sub-agent logger level in agent factory flows
  • Default behavior if missing: DEBUG

SUB_AGENT_DEFAULT_TOOLS

  • Required: optional
  • Format: JSON list string
  • Example: ["tools/my_tools.py", {"type":"mcp", "server_url":"https://..."}]
  • Controls: fallback tool list when no explicit tools are passed to factory flows
  • Default behavior if missing: no default tools injected

AGENT_FACTORY_SUGGESTION_MODEL

  • Required: optional
  • Format: string model name
  • Example: claude-sonnet-4-5
  • Controls: model used by suggestion sub-agent in suggest_agent_config
  • Default behavior if missing: claude-sonnet-4-5
  • Any provider works, Gemini included. recommended_config, each alternative's config and estimated_cost are typed objects (SuggestedAgentConfig, CostRange). They were property-less {"type": "object"} fields, which gave a constrained decoder nothing to emit: on gemini-3-flash-preview the sub-agent emitted whitespace until its 65k-token limit, twice per call. The sub-agent also runs with an output ceiling (max_tokens=16384, clamped to the model's own maximum), so a runaway is a fast parse failure handled by the existing single retry rather than a $0.20, 12-minute one. Optional parameters the model leaves unset are removed from the returned dicts, so the result shape is unchanged: only what differs from the create_agent defaults.
  • The catalogue of registry rows this sub-agent is shown has editor-facing provenance keys stripped out first, so a _source or _verified note can never reach the suggestion prompt.
  • Required: optional
  • Format: bool-like string ("true" / "false")
  • Example: true
  • Controls: whether suggestion sub-agent enables web search
  • Default behavior if missing: false

Provider tuning env vars

SCHEMA_OPTIONAL_COUNT_ANTHROPIC

  • Required: optional
  • Format: int
  • Example: 24
  • Controls: the most optional properties Anthropic's strict mode accepts. A tool with more is sent without strict mode; since 0.5.1 the same number is also the budget for all the strict schemas of one request (see DPT_ANTHROPIC_STRICT_BUDGET)
  • Default behavior if missing: 24

SCHEMA_OPTIONAL_COUNT_OPENAI

  • Required: optional
  • Format: int
  • Example: 24
  • Controls: OpenAI schema optional field behavior in provider implementation
  • Default behavior if missing: 24

Notes

  • Variables are read via os.getenv(...) and os.environ.get(...) in source files.
  • For local development, define values in .env at repository root.
  • For CI, prefer secret-injected environment variables over committed files.
  • A missing provider key is reported by name: client initialization raises RuntimeError: <provider> API key not configured (<ENV_VAR> is unset). Any other construction failure chains its real cause instead of blaming the key. See Async Clients.