Conversation
7c823df to
2760d7d
Compare
teknium1
left a comment
There was a problem hiding this comment.
Thanks for tracing the post-stream task identity through the gateway; that path matches a real current-main limitation (gateway/run.py:18393-18394).
Problems
- Blocking: the cron path still loses container-local media before the new task-bound lookup runs. At
cron/scheduler.py:1525-1527,_deliver_result()extracts media, uses unscoped translation, and filters it. Under the multi-environment case described by this PR, that filter drops the unchanged container path. The later task-bound translation atcron/scheduler.py:1342-1344therefore receives no media.
Suggested changes
- Thread
str(job["id"])into the translation atcron/scheduler.py:1526before the first filter, or move filtering to the task-bound helper. Add a two-active-Docker-environment regression proving a container-only cron attachment reaches the adapter.
Automated hermes-sweeper review.
| # instead of degrading to mount-table-only / single-Docker-environment-only | ||
| # translation. | ||
| media_files = BasePlatformAdapter.translate_docker_media_paths( | ||
| media_files, task_id=str(job.get("id") or "") or None |
There was a problem hiding this comment.
This task-bound translation is too late for cron delivery: _deliver_result() already calls task-id-less translation and filter_media_delivery_paths() at lines 1525-1527. In the multiple-environment case, that earlier filter drops the container path, so this helper receives an empty list. Pass the job task_id into the earlier translation (or defer filtering) and cover that ordering with a regression test.
48e81ec to
20ac63f
Compare
20ac63f to
d1578c1
Compare
d1578c1 to
c258c24
Compare
|
Re-scoped this PR following @teknium1's guidance on #94441 (thanks for flagging it there rather than letting it re-derive independently). What changed and why: The original approach here (thread What's kept — a still-open gap, independently re-confirmed against current
Separately, I traced the actual call sites and found two in This PR now:
All changes are additive (new optional params, defaults preserve existing behavior for every other caller) — squashed to one fresh commit against current Open question I didn't want to resolve unilaterally: is ambient |
…DIA delivery Re-scopes NousResearch#74066 following maintainer guidance on NousResearch#94441: NousResearch#94560 (merged) fixed the root cause behind the original bug report by making persistent Docker profile-scoped instead of per-session, which supersedes this PR's original task_id-based sandbox resolution entirely. That mechanism is dropped. What survives is a still-live, independently-confirmed gap: NousResearch#94560's own _docker_sandbox_dir_candidates() takes session_key explicitly (its own docstring cites NousResearch#93950 - ambient lookups run after the turn's profile scope has often already exited) but still resolves its PRIMARY profile candidate via ambient get_active_profile_name(), not the explicit session_key. Delivery call sites inherit that same ambient-lookup risk. This adds an explicit `profile` override (alongside the existing session_key) to _docker_sandbox_dir_candidates and every function in its call chain, and threads SessionSource.profile/session_key explicitly through the two gateway/run.py call sites that had NO identity threading at all (_deliver_media_from_response's post-stream rescan, and the delegate-task background-result delivery path) plus cron's own _send_media_via_adapter/ _deliver_result (job id as session_key, matching the PR's original identifier construction). All changes are additive (new optional params, existing callers unaffected).
c258c24 to
b6bff38
Compare
…DIA delivery Re-derived against the 2026-09-03/04 simplify(compat) refactor, which split cron/scheduler.py's delivery helpers into cron/scheduler_delivery.py, consolidated _default_docker_workspace_host_roots/_docker_persistent_home_host_roots into gateway/platforms/base.py's shared _docker_persistent_sandbox_roots(), and split gateway/run.py's post-stream/background media delivery into gateway/run_notifications.py and gateway/run_turn.py.
b6bff38 to
dd84684
Compare
What does this PR do?
Stacked on #47716 — this branch is built on top of that PR's, so the diff
below includes its 5 commits until #47716 merges. Only the last commit
(
fix(gateway): thread a real task_id through post-stream media delivery and cron tasks) is this PR's own; the file-level review should focus there.Follow-up to #47716. That PR added Docker container→host path translation for
MEDIA:file delivery, but several call sites have notask_idavailable, sotranslate_docker_media_paths()degrades to mount-table-only translation andonly proceeds when exactly one Docker terminal environment is active
gateway-wide — with zero or multiple concurrently active environments (as on
any busy multi-session deployment) it no-ops and the file is dropped, even
though the run that actually produced it is still alive and its environment
still resolvable by its own
task_id.Reproduced in production: a Telegram guest-mode reply's buffered "OPC"
flush (guest mode buffers the whole reply and delivers it as a single edit at
turn end, not via live streaming) routes through
gateway/run.py::_deliver_media_from_response— one of the task-id-lesspaths. Across three separate turns in the same guest chat, the agent
correctly emitted
MEDIA:/home/.../cache/videos/....mp4, and each time thegateway log showed
Skipping unsafe MEDIA directive path— the video wassilently dropped, with only the text reply delivered.
This PR threads a real
task_idthrough the two paths where the producingtask genuinely is identifiable: the post-stream media rescan, and cron jobs.
Related Issue
Addresses #64889 for the two paths listed below.
Not filing as
Fixes #64889— the issue's third path(
gateway/platforms/base.py's generichandle_message()/weixin.py'ssend()) is intentionally left open; see "Deliberately out of scope."Type of Change
Changes Made
run_agent.py:AIAgent.run_conversation()already computes its owneffective_task_idbefore calling intoagent.conversation_loop.run_conversation(). It now surfaces that value asresult["task_id"]on every dict return (the exception path re-raises andnever returns a dict, so no other branch needs touching) — any caller
holding the turn's result dict can recover the exact
task_idthatproduced it, the same way
already_sent/failedalready work.gateway/run.py:_deliver_media_from_response()takes an optionaltask_idparameter and forwards it intotranslate_docker_media_paths().Its one caller now passes
agent_result.get("task_id").cron/scheduler.py:run_job()now passestask_id=str(job_id)intoagent.run_conversation(), so a cron job's own terminal environment isdeterministically identifiable by its own job id instead of an unrelated
random UUID generated fresh per run.
_send_media_via_adapter()alreadyreceives the full
jobdict — it now readsjob["id"]back as that sametask_id(previously the function's own comment noted this was not thereal task_id; it now is).
Deliberately out of scope
gateway/platforms/base.py's generichandle_message()andweixin.py'ssend()still can't identify the producing task without a larger contractchange: both receive their text from
self._message_handler(event), andMessageHandleris typed to return a plainOptional[str]— the fullagent_resultdict (and its newtask_idkey) never reaches this layer.Threading it through would mean changing that protocol's return type across
every platform adapter that registers a handler, which is exactly the kind of
"bigger than a bug fix" shared-contract change #47716 itself declined to
bundle (see that PR's "Why this isn't in #47716" note). Left degraded,
unchanged from before this PR — still tracked by #64889.
How to Test
scripts/run_tests.sh tests/run_agent/test_run_agent.py tests/gateway/test_post_stream_media_delivery.py tests/cron/test_scheduler.py tests/agent/test_subagent_lifecycle.py—all pass, including four new tests covering explicit/auto-generated
task_idsurfacing, forwarding intotranslate_docker_media_paths, andthe cron job-id threading.
a Telegram guest-mode chat, have the agent create a video file and reply
with a
MEDIA:tag while at least one other, unrelated terminalenvironment is concurrently active elsewhere on the gateway. Before this
fix: video silently dropped (
Skipping unsafe MEDIA directive pathin thelog) whenever zero or 2+ environments are active gateway-wide at flush
time. After: delivered, because translation now resolves this turn's own
environment directly by its own
task_idregardless of what else isrunning.
Checklist
Code
pytest tests/ -qand all tests passDocumentation & Housekeeping
Screenshots / Logs
Live log excerpt from the reproduced failure, before this fix:
turn_media=Falseon every flush confirms the guest-mode media stagingfunction was never reached — the file never survived path translation to get
there.