Skip to content

Commit ac147d0

Browse files
authored
Remove code_mode from AgentOperator in favor of the CodeMode capability (#74312)
code_mode=True only appended a bare CodeMode() to the agent's capabilities, which capabilities= now does directly. The flag hid CodeMode's own arguments, most importantly max_tool_calls, whose default of 100 nested calls per run_code snippet is too low for agents that fan out over a list.
1 parent e25f413 commit ac147d0

16 files changed

Lines changed: 78 additions & 182 deletions

File tree

‎providers/common/ai/docs/capabilities.rst‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -112,9 +112,9 @@ again. Whether a capability's work is replayed depends on where it runs:
112112
and capabilities from an agent spec file
113113
- Tools run again. Pass tools you need replayed in ``toolsets=`` instead.
114114
* - pydantic-ai-harness ``CodeMode``
115-
- Not allowed: the operator raises ``ValueError``, as it does for ``code_mode=True``. This
116-
includes a ``CodeMode`` inside a ``CombinedCapability`` or a wrapper such as
117-
``PrefixTools``, but not one a capability function builds when the run starts.
115+
- Not allowed: the operator raises ``ValueError``. This includes a ``CodeMode`` inside a
116+
``CombinedCapability`` or a wrapper such as ``PrefixTools``, but not one a capability
117+
function builds when the run starts.
118118

119119
See :doc:`durable_execution` for how the cache works.
120120

‎providers/common/ai/docs/changelog.rst‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,16 @@ Changelog
5151
result or ``result["sample_columns"]`` on a summary. A summary carries no ``columns`` key, so
5252
``result["columns"]`` raises ``KeyError`` on any table wide enough to be summarized.
5353

54+
.. note::
55+
``AgentOperator`` no longer accepts ``code_mode``. Pass the pydantic-ai-harness capability
56+
instead: replace ``code_mode=True`` with ``capabilities=[CodeMode()]``, using
57+
``from pydantic_ai_harness import CodeMode``. The capability takes its own arguments, such
58+
as ``max_tool_calls``. If the task also passes ``agent_params={"capabilities": [...]}``,
59+
move those into ``capabilities=`` too, since the two cannot be combined. The import runs
60+
when the Dag file is parsed, so the ``code-mode`` extra is now needed by the Dag processor
61+
as well as the workers. The extra's floor is now ``pydantic-ai-harness>=0.24.0``, the first
62+
release with ``max_tool_calls``. See :doc:`code_mode`.
63+
5464
0.10.0
5565
......
5666

‎providers/common/ai/docs/code_mode.rst‎

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,10 @@ Code mode
2525
Experimental: this can change or be removed in a minor release of this provider.
2626
See :ref:`howto/stability`.
2727

28-
Set ``code_mode=True`` to collapse the agent's tools into a single ``run_code``
29-
tool powered by the `Monty <https://github.com/pydantic/monty>`__ sandbox (via
30-
pydantic-ai-harness). Instead of one model round-trip per tool call, the model
28+
Pass pydantic-ai-harness's ``CodeMode`` capability in ``capabilities=`` (see
29+
:ref:`capabilities`) to collapse the
30+
agent's tools into a single ``run_code`` tool powered by the
31+
`Monty <https://github.com/pydantic/monty>`__ sandbox. Instead of one model round-trip per tool call, the model
3132
writes a single Python snippet that calls the tools as functions -- with loops,
3233
conditionals, and ``asyncio.gather`` -- in one turn. For multi-tool workflows
3334
this cuts round-trips and token use.
@@ -68,24 +69,39 @@ mode:
6869
genuinely required rather than by default. See :doc:`sandbox/index` for the
6970
backends and their limitations.
7071

71-
The two are not exclusive: ``code_mode=True`` and a ``SandboxToolset`` can be
72+
The two are not exclusive: ``CodeMode`` and a ``SandboxToolset`` can be
7273
enabled together, and the file tools fold into ``run_code`` while ``run_command``
7374
stays a tool of its own.
7475

7576
Requires the ``code-mode`` extra::
7677

7778
pip install "apache-airflow-providers-common-ai[code-mode]"
7879

80+
and the import ``from pydantic_ai_harness import CodeMode`` in the Dag file.
81+
7982
.. exampleinclude:: /../../ai/src/airflow/providers/common/ai/example_dags/example_agent.py
8083
:language: python
8184
:start-after: [START howto_operator_agent_code_mode]
8285
:end-before: [END howto_operator_agent_code_mode]
8386

84-
``code_mode=True`` is the same as adding ``CodeMode()`` at the end of ``capabilities=`` (see
85-
:ref:`capabilities`), except that the operator builds the capability when the task runs, so the
86-
Dag file does not import ``pydantic_ai_harness``.
87+
``CodeMode`` takes its own arguments; see the
88+
`pydantic-ai-harness code mode docs <https://ai.pydantic.dev/harness/code-mode/>`__. The one
89+
you are most likely to need is ``max_tool_calls``: each ``run_code`` snippet may make at most
90+
that many nested tool calls, 100 by default. A snippet that asks for more fails, the calls it
91+
already made are not undone, and the model has to split the work across several snippets. If the
92+
agent fans out over a list, for example one tool call per row or per file, set it above the
93+
largest list you expect.
94+
95+
Code mode cannot be combined with ``durable=True`` (see :doc:`capabilities` for which
96+
capabilities durable replay covers), and it turns off :doc:`tool_approval`. A tool that needs
97+
approval and is called from ``run_code`` does not run: the model gets an error back and may
98+
carry on without it, so the task can still succeed. To keep such a tool out of ``run_code``,
99+
list the other tools in ``CodeMode(tools=[...])``; called directly, it fails the task.
100+
101+
Importing ``CodeMode`` in the Dag file loads pydantic-ai-harness and Monty each time the file is
102+
parsed. Keep code mode agents in their own Dag file if the rest of the file does not need them.
87103

88104
.. note::
89105

90-
Monty is pre-1.0. The ``code-mode`` extra is opt-in so its dependency churn
106+
pydantic-ai-harness is pre-1.0. The ``code-mode`` extra is opt-in so its dependency churn
91107
never affects the base provider install.

‎providers/common/ai/docs/features.rst‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ you use. Each is a parameter on the operator or decorator.
2828
- :doc:`message_history`: ``message_history`` carries a conversation across agent runs.
2929
- :doc:`capabilities`: ``capabilities=`` adds pydantic-ai capabilities such as ``Thinking`` and
3030
``WebSearch``, and ``pydantic-ai-shields`` guardrails, to an agent.
31-
- :doc:`code_mode`: ``code_mode=True`` lets the model call several tools from one Python
32-
snippet instead of one round trip per call.
31+
- :doc:`code_mode`: the ``CodeMode`` capability lets the model call several tools from one
32+
Python snippet instead of one round trip per call.
3333
- :doc:`approval_gates`: ``require_approval=True`` pauses an LLM operator until a person
3434
approves, edits or rejects the output.
3535
- :doc:`hitl_review`: ``enable_hitl_review=True`` opens an iterative review loop on an agent,

‎providers/common/ai/docs/index.rst‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ Extra Dependencies
227227
``mcp`` ``pydantic-ai-slim[mcp]>=2.33.0``
228228
``modal`` ``apache-airflow-providers-modal``, ``modal>=1.5.2``
229229
``opensandbox`` ``opensandbox>=1.1.0``
230-
``code-mode`` ``pydantic-ai-harness[codemode]>=0.3.0``
230+
``code-mode`` ``pydantic-ai-harness[codemode]>=0.24.0``
231231
``shields`` ``pydantic-ai-shields>=0.3.4``
232232
``skills`` ``apache-airflow-providers-git>=0.4.0``, ``pydantic-ai-skills>=1.2.0``
233233
``avro`` ``fastavro>=1.10.0; python_version < "3.14"``, ``fastavro>=1.12.1; python_version >= "3.14"``

‎providers/common/ai/docs/operators/agent.rst‎

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -251,8 +251,8 @@ Five features have pages of their own:
251251
on retry instead of paying for them again.
252252
- :doc:`../capabilities`: pass pydantic-ai capabilities and ``pydantic-ai-shields`` guardrails
253253
with ``capabilities=``.
254-
- :doc:`../code_mode`: set ``code_mode=True`` to collapse the agent's tools into a single
255-
``run_code`` tool the model drives by writing Python.
254+
- :doc:`../code_mode`: pass the ``CodeMode`` capability to collapse the agent's tools into a
255+
single ``run_code`` tool the model drives by writing Python.
256256
- :doc:`../tool_approval`: mark tools that need a person's approval, and the task pauses before
257257
a marked call runs.
258258

@@ -435,9 +435,6 @@ Parameters
435435
when the steps it needs are cached. Clearing a failed task instance starts
436436
a fresh budget but keeps the durable cache its attempts left behind, so what
437437
the rerun replays from that cache is free there too.
438-
- ``code_mode``: When ``True``, wraps the agent's tools in a single ``run_code``
439-
tool that the model drives by writing Python, executed in the Monty sandbox.
440-
Requires the ``code-mode`` extra. Default ``False``. See :ref:`code-mode`.
441438
- ``cache_prompt``: Ask the provider to cache the tool definitions, system prompt and
442439
conversation so later requests read them back at a discount. Default ``True``; a no-op for
443440
providers that cache on their own. See :ref:`agent-prompt-caching`.

‎providers/common/ai/docs/sandbox/index.rst‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -359,7 +359,7 @@ somewhere to work". This page has the worked scenarios above and
359359
the limitations below to read before designing a Dag around it.
360360

361361
Before reaching for it, check whether the actual need is narrower than that.
362-
``code_mode=True`` is a flag on ``AgentOperator``. It changes how the
362+
:ref:`Code mode <code-mode>` is a capability on ``AgentOperator``. It changes how the
363363
model invokes the tools it already has, letting it write code that calls several
364364
of them instead of emitting one call per step. It does not give the agent somewhere
365365
to run arbitrary code of its own. Because it needs no backend, it avoids the

‎providers/common/ai/docs/stability.rst‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ Pydantic AI toolsets, but the Pydantic AI class they inherit from can change.
5151
* - ``@task.agent`` and :class:`~airflow.providers.common.ai.operators.agent.AgentOperator`
5252
- Runs an agent with the model from ``llm_conn_id`` and the given toolsets, and
5353
returns its output under the same rules as ``@task.llm``. ``durable``,
54-
``code_mode`` and per-tool approval are experimental; see below.
54+
code mode and per-tool approval are experimental; see below.
5555
* - ``message_history`` on ``AgentOperator``
5656
- Seeds the run with the given conversation, as a list of messages or its JSON form,
5757
and publishes the finished conversation under the ``message_history`` XCom key so a
@@ -131,7 +131,7 @@ Everything this provider ships that is not in the table above is experimental.
131131
``on_tool_approval_timeout`` and ``tool_approval_assigned_users``;
132132
:doc:`tool_approval`)
133133
- New, and needs Airflow 3.3. How a paused run resumes may change.
134-
* - ``code_mode`` (:doc:`code_mode`), the Agent Skills toolset
134+
* - Code mode (:doc:`code_mode`), the Agent Skills toolset
135135
(:doc:`toolsets/skills`) and the ``shields`` extra (used in :doc:`capabilities`)
136136
- Thin integrations of packages outside this provider whose APIs are still
137137
changing: ``pydantic-ai-harness``, ``pydantic-ai-skills`` and

‎providers/common/ai/docs/tool_approval.rst‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -127,11 +127,13 @@ Requirements and limits
127127

128128
- Airflow 3.3 or later. On older versions a tool marked for approval fails the
129129
task, as it did before.
130-
- Not together with ``durable=True``, ``enable_hitl_review=True``, code mode
131-
(``code_mode=True`` or a ``CodeMode`` capability), or a ``SandboxToolset`` that
130+
- Not together with ``durable=True``, ``enable_hitl_review=True``, a ``CodeMode``
131+
capability, or a ``SandboxToolset`` that
132132
provisions its own sandbox. Each
133133
assumes the run finishes in one go; that sandbox, for one, is destroyed when the
134-
run pauses. With any of them, a marked tool fails the task. A ``SandboxToolset``
134+
run pauses. With any of them, a marked tool fails the task, except under code mode,
135+
where a marked tool called from ``run_code`` does not run and the model may carry on
136+
without it (see :doc:`code_mode`). A ``SandboxToolset``
135137
attached to a sandbox another task owns keeps its files through the pause, so it
136138
is allowed; the wait spends that sandbox's lifetime
137139
(:ref:`sandbox-attach`).

‎providers/common/ai/docs/troubleshooting.rst‎

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,9 @@ Model and connection errors
4444
version suffixes, for example), use the matching vendor connection type instead of
4545
the generic one.
4646

47-
An ``ImportError`` for ``pydantic_ai.models.<vendor>`` or the vendor SDK
48-
The provider is installed without the extra for that vendor. Install it, quoting the
47+
An ``ImportError`` for ``pydantic_ai.models.<vendor>``, the vendor SDK, or ``pydantic_ai_harness``
48+
The provider is installed without the extra for that vendor, or without ``code-mode``
49+
for ``pydantic_ai_harness``. Install it, quoting the
4950
package name so the brackets survive the shell:
5051

5152
.. code-block:: bash
@@ -102,17 +103,14 @@ as a task failure.
102103
or later. Upgrade Airflow, or use ``on_uncertain="fail"`` and drop the review flags
103104
on an older Airflow version. See :doc:`approval_gates` and :doc:`hitl_review`.
104105

105-
``durable=True and enable_hitl_review=True cannot be used together`` / ``durable=True and code_mode=True cannot be used together``
106+
``durable=True and enable_hitl_review=True cannot be used together`` / ``durable=True cannot be used with a CodeMode capability``
106107
Durable replay assumes a stable step order across attempts, which neither a human
107108
review loop nor code mode provides. Pick one. See :doc:`durable_execution`.
108109

109110
``message_history and enable_hitl_review=True cannot be used together``
110111
The post-review transcript is not recoverable today, so the operator refuses rather
111112
than silently dropping the reviewed turns. See :doc:`message_history`.
112113

113-
``code_mode=True requires the 'code-mode' extra``
114-
Install ``apache-airflow-providers-common-ai[code-mode]``. See :doc:`code_mode`.
115-
116114
``... does not support decision_policy yet``
117115
Only ``LLMOperator`` and ``LLMBranchOperator`` honor a ``DecisionPolicy`` with a
118116
confidence bar. The SQL, schema-compare and file-analysis operators run their own

0 commit comments

Comments
 (0)