Commit 032d43b
fix: run sync sandbox tools off the event loop so traced tools work in agent code mode (#1523)
## Problem
With `Agent(code_mode=True)`, live runs showed **every** tool call
failing with:
```
Deadlock detected: blocking call used in syncify thread flyte_syncify when calling
function <function _fetch_action_outputs ...>, use .aio() if in an async call.
```
Root cause: when the agent is entered through a syncified call (e.g. a
sync `agent.run()`), the whole Monty sandbox loop runs on the
`flyte_syncify` background loop. `ExternalFunctionBridge.execute_monty`
invoked external functions **inline**, so a sync `@flyte.trace` tool's
wrapper made its blocking syncify call (`_fetch_action_outputs`) from
the syncify thread itself — a guaranteed deadlock that the SDK detects
and aborts. The JSON tool-calling path was unaffected because
`_make_callable_tool` already dispatches sync tools via
`asyncio.to_thread`.
Downstream workaround that motivated this fix: unionai/unionai-agents#86
(had to disable code mode entirely).
## Fix
`ExternalFunctionBridge` now routes every non-coroutine-function
external through `asyncio.to_thread` (new `_call_external` helper, used
by both `execute_monty` and `_map_callable`):
- `asyncio.to_thread` copies contextvars, so the Flyte task context —
and therefore tracing — works exactly as it does on the JSON tool path.
- The blocking syncify calls inside sync trace wrappers now happen on a
worker thread while the syncify loop stays free to serve them.
- Side benefit: slow sync tools no longer stall the event loop driving
the sandbox (previously they blocked it outright), including under
`flyte_map`.
- Coroutine functions (`TaskTemplate.aio`, async tools, call-handler
wrappers) keep their inline path, and the existing unwrap loop still
drains coroutines returned by non-coroutine callables (local-mode
`TaskTemplate.aio()`).
## Also in this PR: remove a flaky Python 3.14 test
CI on this PR surfaced a pre-existing flaky failure, unrelated to the
sandbox change:
`test_task_call_sequence.py::test_interleaved_allocations_prevent_id_reuse`
asserted that CPython never hands a freed override object's address to a
later allocation when a same-size "noise" object is allocated in
between. That's unspecified allocator behavior, and the 3.14 runner
reused an address once (`seqs [1,1,1,1,1,1,1,2,1,1]`).
The `id()`-keyed sequencing that test demonstrates is also historical —
the remote controller has keyed sequences on a call key (task identity +
inputs hash + group) via `TaskCallSequencer` since #607, with
deterministic coverage in `test_action_name_stability_gaps.py`. The test
is removed and the file's docstring now notes that the remaining
demonstrations (which hold every object alive, so ids are guaranteed
distinct) are deterministic and describe pre-#607 behavior.
## Verification
- New regression tests drive `orchestrate_local` on the syncify loop
with a sync tool that makes a blocking syncify call (direct call and via
`flyte_map`) — they reproduce the exact production error before the fix
and pass after.
- Repro script confirmed: unfixed bridge → `Deadlock detected: blocking
call used in syncify thread flyte_syncify ...`; fixed bridge → returns
the tool result.
- `tests/flyte/sandbox/` + `tests/flyte/ai/`: 694 passed; sequencing
suites (`test_task_call_sequence.py`,
`test_action_name_stability_gaps.py`): 22 passed. Lint/mypy/ty clean.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01TqG84zzd1AFjBXPrMnWDxh
---------
Signed-off-by: Niels Bantilan <niels.bantilan@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>1 parent 1fc0a47 commit 032d43b
3 files changed
Lines changed: 145 additions & 33 deletions
File tree
- src/flyte/sandbox
- tests/flyte
- internal/runtime
- sandbox
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
29 | 29 | | |
30 | 30 | | |
31 | 31 | | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
32 | 56 | | |
33 | 57 | | |
34 | 58 | | |
| |||
140 | 164 | | |
141 | 165 | | |
142 | 166 | | |
143 | | - | |
144 | | - | |
145 | | - | |
146 | | - | |
| 167 | + | |
147 | 168 | | |
148 | 169 | | |
149 | 170 | | |
| |||
202 | 223 | | |
203 | 224 | | |
204 | 225 | | |
205 | | - | |
206 | | - | |
207 | | - | |
208 | | - | |
209 | | - | |
210 | | - | |
| 226 | + | |
211 | 227 | | |
212 | 228 | | |
213 | 229 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
10 | 10 | | |
11 | 11 | | |
12 | 12 | | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
13 | 21 | | |
14 | 22 | | |
15 | 23 | | |
| |||
135 | 143 | | |
136 | 144 | | |
137 | 145 | | |
138 | | - | |
139 | | - | |
140 | | - | |
141 | | - | |
142 | | - | |
143 | | - | |
144 | | - | |
145 | | - | |
146 | | - | |
147 | | - | |
148 | | - | |
149 | | - | |
150 | | - | |
151 | | - | |
152 | | - | |
153 | | - | |
154 | | - | |
155 | | - | |
156 | | - | |
157 | | - | |
158 | | - | |
159 | | - | |
160 | 146 | | |
161 | 147 | | |
162 | 148 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | | - | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
4 | 11 | | |
5 | 12 | | |
6 | 13 | | |
| |||
28 | 35 | | |
29 | 36 | | |
30 | 37 | | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
0 commit comments