0.2.7
[0.2.7] - 2026-05-14
Added
-
LocalBackend.async_execute()— async, cancellable shell execution (#36, related to pydantic-deepagents#93) — usesasyncio.create_subprocess_execso that cancelling the calling task immediately kills the subprocess instead of waiting for the thread to finish. The console toolset'sexecutetool now prefersbackend.async_execute(...)when available and falls back toasyncio.to_thread(backend.execute, ...)for backends that don't expose the new method, so third-party backend implementations are unaffected.- On Unix, the subprocess is launched with
start_new_session=Trueand cancellation/timeout callsos.killpg(proc.pid, SIGKILL)so the entire process tree (including grandchildren the shell forked, e.g.sh -c "sleep 60") is reaped. Windows relies oncmd /clifecycle to terminate child processes. - Cleanup
await proc.communicate()afterkill()is wrapped inasyncio.shieldso a second cancellation can't leave subprocess pipes dangling. - Output is decoded with
errors="replace"to tolerate non-UTF-8 bytes.
- On Unix, the subprocess is launched with
-
Cross-platform shell selection in
LocalBackend(#36) — new static helperLocalBackend._shell_cmd(command)returns["cmd", "/c", command]on Windows and["sh", "-c", command]elsewhere. Bothexecute()andasync_execute()route through it.
Fixed
-
[WinError 2]crash on Windows when callingLocalBackend.execute()(#36) — the execute path hardcoded["sh", "-c", command], which is not available on Windows. Now routes through_shell_cmd()and usescmd /conwin32. -
Agent task cancellation didn't reach the running subprocess (#36) — previously,
execute()ran on a worker thread viaasyncio.to_thread, so cancelling the calling task only marked the future as cancelled while the subprocess kept running until completion or timeout. Withasync_execute(), cancellation propagates through toproc.kill()(orkillpgon Unix) immediately. -
timeout=0was silently rewritten to 120 seconds (#36) —execute()usedtimeout or 120, which treated0as falsy and substituted the default. Now uses an explicitNonecheck so0is honoured (will trigger immediate timeout).
Changed
- Extracted
MAX_EXECUTE_OUTPUT = 100_000constant inlocal.py, shared by bothexecute()andasync_execute()truncation paths.