by-framework 0.2.2.dev13__py3-none-any.whl → 0.2.2.dev15__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- by_framework/client/client.py +6 -0
- by_framework/metrics/catalog.py +54 -0
- by_framework/worker/_resume_metadata.py +65 -0
- by_framework/worker/processor.py +89 -22
- by_framework/worker/runner.py +29 -1
- by_framework/worker/worker.py +43 -0
- {by_framework-0.2.2.dev13.dist-info → by_framework-0.2.2.dev15.dist-info}/METADATA +1 -1
- {by_framework-0.2.2.dev13.dist-info → by_framework-0.2.2.dev15.dist-info}/RECORD +11 -10
- {by_framework-0.2.2.dev13.dist-info → by_framework-0.2.2.dev15.dist-info}/WHEEL +0 -0
- {by_framework-0.2.2.dev13.dist-info → by_framework-0.2.2.dev15.dist-info}/entry_points.txt +0 -0
- {by_framework-0.2.2.dev13.dist-info → by_framework-0.2.2.dev15.dist-info}/licenses/LICENSE +0 -0
by_framework/client/client.py
CHANGED
|
@@ -971,6 +971,12 @@ class GatewayClient:
|
|
|
971
971
|
"parent_message_id": params["parent_message_id"] or "",
|
|
972
972
|
"source_agent_type": CLIENT_SOURCE_AGENT_TYPE,
|
|
973
973
|
"target_agent_type": params["target_agent_type"],
|
|
974
|
+
# What this execution was dispatched with, so a worker
|
|
975
|
+
# can restore it after the execution suspends and
|
|
976
|
+
# resumes (see worker/_resume_metadata.py). Only the
|
|
977
|
+
# non-RESUME branch reaches here, so this never
|
|
978
|
+
# overwrites a suspended execution's original.
|
|
979
|
+
"metadata": dict(metadata or {}),
|
|
974
980
|
"stream_name": route.stream_name,
|
|
975
981
|
"status": "QUEUED",
|
|
976
982
|
"route_policy": route_policy,
|
by_framework/metrics/catalog.py
CHANGED
|
@@ -367,6 +367,60 @@ _CATALOG: dict[str, MetricDefinition] = {
|
|
|
367
367
|
"Instrumentation reliability signal pinpointing exporter failures."
|
|
368
368
|
),
|
|
369
369
|
),
|
|
370
|
+
# Self-monitoring for the background collector in metrics/collector.py.
|
|
371
|
+
# Exactly one process holds its Redis lock and writes history points, so
|
|
372
|
+
# these answer "is anyone still collecting?" — a gap here means the
|
|
373
|
+
# dashboards go stale silently rather than erroring.
|
|
374
|
+
"by_framework_metrics_collector_cycles_total": MetricDefinition(
|
|
375
|
+
name="by_framework_metrics_collector_cycles_total",
|
|
376
|
+
kind=MetricKind.COUNTER,
|
|
377
|
+
unit=MetricUnit.NONE,
|
|
378
|
+
labels=("result",),
|
|
379
|
+
description=(
|
|
380
|
+
"Metrics collector cycles by result "
|
|
381
|
+
"(success, snapshot_failed, lock_skipped, disabled)."
|
|
382
|
+
),
|
|
383
|
+
interpretation=(
|
|
384
|
+
"Collector health signal: only the lock holder reports success, so "
|
|
385
|
+
"lock_skipped dominating across a fleet is expected."
|
|
386
|
+
),
|
|
387
|
+
),
|
|
388
|
+
"by_framework_metrics_collector_snapshot_duration_ms": MetricDefinition(
|
|
389
|
+
name="by_framework_metrics_collector_snapshot_duration_ms",
|
|
390
|
+
kind=MetricKind.HISTOGRAM,
|
|
391
|
+
unit=MetricUnit.MILLISECONDS,
|
|
392
|
+
description=(
|
|
393
|
+
"Duration of one collector snapshot build plus history write."
|
|
394
|
+
),
|
|
395
|
+
interpretation=(
|
|
396
|
+
"Latency signal for collection itself; approaching the collection "
|
|
397
|
+
"interval means cycles are about to overlap."
|
|
398
|
+
),
|
|
399
|
+
),
|
|
400
|
+
"by_framework_metrics_collector_lock_held": MetricDefinition(
|
|
401
|
+
name="by_framework_metrics_collector_lock_held",
|
|
402
|
+
kind=MetricKind.GAUGE,
|
|
403
|
+
unit=MetricUnit.NONE,
|
|
404
|
+
description=(
|
|
405
|
+
"Whether this process currently holds the metrics collector lock."
|
|
406
|
+
),
|
|
407
|
+
interpretation=(
|
|
408
|
+
"Saturation signal: should sum to 1 across the fleet — 0 means "
|
|
409
|
+
"nobody is collecting, above 1 means the lock split."
|
|
410
|
+
),
|
|
411
|
+
),
|
|
412
|
+
"by_framework_metrics_collector_last_success_timestamp_ms": MetricDefinition(
|
|
413
|
+
name="by_framework_metrics_collector_last_success_timestamp_ms",
|
|
414
|
+
kind=MetricKind.GAUGE,
|
|
415
|
+
unit=MetricUnit.MILLISECONDS,
|
|
416
|
+
description=(
|
|
417
|
+
"Unix timestamp in milliseconds of the last successful collection."
|
|
418
|
+
),
|
|
419
|
+
interpretation=(
|
|
420
|
+
"Freshness signal: alert on now minus this value exceeding a few "
|
|
421
|
+
"collection intervals."
|
|
422
|
+
),
|
|
423
|
+
),
|
|
370
424
|
}
|
|
371
425
|
|
|
372
426
|
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""The inbound half of a resumed execution's metadata.
|
|
2
|
+
|
|
3
|
+
Two different directions restore metadata on a resume, and they are NOT the
|
|
4
|
+
same rule. Keep them apart:
|
|
5
|
+
|
|
6
|
+
- **Outbound** (``GatewayWorker._resolve_reply_command`` /
|
|
7
|
+
``GatewayProcessor._resolve_reply_header``): the header a resumed execution
|
|
8
|
+
*sends* to its caller. The stored dispatch metadata **replaces**
|
|
9
|
+
``header.metadata`` wholesale — the waking hop's data is plumbing the
|
|
10
|
+
original caller never asked for, and ``_enqueue_agent_return``'s
|
|
11
|
+
``{**header.metadata, **task_result.metadata}`` is the one sanctioned
|
|
12
|
+
channel for a handler to forward part of it.
|
|
13
|
+
- **Inbound** (this module): the header a resumed execution's own handler
|
|
14
|
+
*reads*. Here the waking message's metadata is legitimate payload — an
|
|
15
|
+
``ask_user`` answer's metadata was sent BY a client TO this agent — so it is
|
|
16
|
+
merged on top of the original dispatch metadata rather than discarded.
|
|
17
|
+
|
|
18
|
+
Without this, everything a handler was originally dispatched with disappears
|
|
19
|
+
the first time it suspends: it comes back seeing only whatever woke it up.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from typing import Any, Mapping, Optional
|
|
23
|
+
|
|
24
|
+
# Injected per dispatch by AgentContext._dispatch_single_task, not supplied by
|
|
25
|
+
# business code. A stored copy of them is stale by definition — it describes
|
|
26
|
+
# the hop that dispatched the execution, not the hop resuming it now — so they
|
|
27
|
+
# are dropped from the restored base and always come from the current message.
|
|
28
|
+
# Letting them through would surface a dispatch-time span id where the waking
|
|
29
|
+
# message has none, and AgentContext._resolve_call_langfuse_parent_id() reads
|
|
30
|
+
# exactly that as its last-resort fallback: a call made after a resume would
|
|
31
|
+
# parent to the original dispatch's observation.
|
|
32
|
+
FRAMEWORK_HOP_METADATA_KEYS = frozenset(
|
|
33
|
+
{
|
|
34
|
+
"trace_parent_span_id",
|
|
35
|
+
"framework_parent_span_id",
|
|
36
|
+
"langfuse_parent_observation_id",
|
|
37
|
+
}
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def merge_resume_metadata(
|
|
42
|
+
stored: Optional[Mapping[str, Any]],
|
|
43
|
+
incoming: Optional[Mapping[str, Any]],
|
|
44
|
+
) -> dict[str, Any]:
|
|
45
|
+
"""Merge an execution's original dispatch metadata under this hop's.
|
|
46
|
+
|
|
47
|
+
``stored`` is the ``metadata`` field of the execution record (what the
|
|
48
|
+
execution was originally dispatched with); ``incoming`` is the waking
|
|
49
|
+
message's own metadata. The waking message wins on key collisions: it is
|
|
50
|
+
the newer, more specific hop, and this keeps the property that every key a
|
|
51
|
+
handler can read today stays readable — the restore only ever adds keys.
|
|
52
|
+
|
|
53
|
+
A ``stored`` that is missing (an execution recorded before this field
|
|
54
|
+
existed, or one dispatched by an SDK that doesn't write it) degrades to
|
|
55
|
+
``incoming`` unchanged, i.e. exactly the pre-restore behaviour.
|
|
56
|
+
|
|
57
|
+
Neither argument is mutated.
|
|
58
|
+
"""
|
|
59
|
+
merged = {
|
|
60
|
+
key: value
|
|
61
|
+
for key, value in (stored or {}).items()
|
|
62
|
+
if key not in FRAMEWORK_HOP_METADATA_KEYS
|
|
63
|
+
}
|
|
64
|
+
merged.update(incoming or {})
|
|
65
|
+
return merged
|
by_framework/worker/processor.py
CHANGED
|
@@ -24,6 +24,7 @@ from by_framework.core.protocol.results import (
|
|
|
24
24
|
from by_framework.core.runtime.file_permissions import FilePermissionPolicy
|
|
25
25
|
from by_framework.core.wait_gate import consume_wait_entry, emit_orphaned_reply
|
|
26
26
|
from by_framework.core.wait_reply import flush_pending_group_replies
|
|
27
|
+
from by_framework.worker._resume_metadata import merge_resume_metadata
|
|
27
28
|
from by_framework.worker.context import AgentContext
|
|
28
29
|
|
|
29
30
|
ContextHandler = Callable[
|
|
@@ -99,8 +100,18 @@ class GatewayProcessor:
|
|
|
99
100
|
)
|
|
100
101
|
return None
|
|
101
102
|
|
|
102
|
-
|
|
103
|
+
# One lookup feeds both directions: the header this execution replies
|
|
104
|
+
# with, and the header its own handler reads. They are different
|
|
105
|
+
# rules over the same record — see _resolve_reply_header and
|
|
106
|
+
# _restore_inbound_metadata.
|
|
107
|
+
snapshot = (
|
|
108
|
+
await self._load_execution_snapshot(command) if is_agent_return else None
|
|
109
|
+
)
|
|
110
|
+
raw_command = command
|
|
111
|
+
reply_header = self._resolve_reply_header(command, snapshot)
|
|
103
112
|
has_source_agent = reply_header is not None
|
|
113
|
+
command = self._restore_inbound_metadata(command, is_agent_return, snapshot)
|
|
114
|
+
header = command.header
|
|
104
115
|
|
|
105
116
|
context = AgentContext(
|
|
106
117
|
session_id=header.session_id,
|
|
@@ -163,8 +174,10 @@ class GatewayProcessor:
|
|
|
163
174
|
getattr(context, "_is_suspended", False)
|
|
164
175
|
) and not is_terminal_state(task_result.status)
|
|
165
176
|
if has_source_agent and not is_suspended:
|
|
177
|
+
# raw_command, not the inbound-restored one: the reply is the
|
|
178
|
+
# outbound direction and must not inherit the inbound merge.
|
|
166
179
|
await self._enqueue_callback(
|
|
167
|
-
|
|
180
|
+
raw_command,
|
|
168
181
|
task_result.status,
|
|
169
182
|
task_result.reply_data,
|
|
170
183
|
content=task_result.content,
|
|
@@ -210,7 +223,7 @@ class GatewayProcessor:
|
|
|
210
223
|
|
|
211
224
|
if has_source_agent:
|
|
212
225
|
await self._enqueue_callback(
|
|
213
|
-
|
|
226
|
+
raw_command,
|
|
214
227
|
AgentState.FAILED.value,
|
|
215
228
|
{"error": str(e)},
|
|
216
229
|
reply_header=reply_header,
|
|
@@ -221,47 +234,101 @@ class GatewayProcessor:
|
|
|
221
234
|
)
|
|
222
235
|
raise
|
|
223
236
|
|
|
224
|
-
async def
|
|
237
|
+
async def _load_execution_snapshot(
|
|
225
238
|
self, command: GatewayCommand
|
|
226
|
-
) -> Optional[
|
|
227
|
-
"""
|
|
228
|
-
to, or None when nobody is waiting.
|
|
239
|
+
) -> Optional[dict[str, Any]]:
|
|
240
|
+
"""Read the execution record the original dispatch wrote.
|
|
229
241
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
for the same reason as there, ``CLIENT_SOURCE_AGENT_TYPE`` is not a
|
|
234
|
-
caller. It is what a client writes on a root execution's record, and
|
|
235
|
-
nothing consumes its control stream.
|
|
242
|
+
Fetched once per resume and shared by both restore directions.
|
|
243
|
+
Fail-soft: a registry error degrades to "no record", which each caller
|
|
244
|
+
then handles as its own no-op.
|
|
236
245
|
"""
|
|
237
246
|
header = command.header
|
|
238
|
-
if not isinstance(command, ResumeCommand):
|
|
239
|
-
return header if header.source_agent_type else None
|
|
240
|
-
|
|
241
|
-
execution: Optional[dict[str, Any]] = None
|
|
242
247
|
try:
|
|
243
248
|
from by_framework.core.registry import WorkerRegistry
|
|
244
249
|
|
|
245
|
-
|
|
250
|
+
return await WorkerRegistry(self.redis).get_execution_by_message_id(
|
|
246
251
|
header.message_id, session_id=header.session_id
|
|
247
252
|
)
|
|
248
253
|
except Exception as error: # pylint: disable=broad-exception-caught
|
|
249
254
|
self.logger.warning(
|
|
250
|
-
"[%s] Could not
|
|
255
|
+
"[%s] Could not load the execution record of resumed execution "
|
|
256
|
+
"%s: %s",
|
|
251
257
|
self.worker_id,
|
|
252
258
|
header.message_id,
|
|
253
259
|
error,
|
|
254
260
|
)
|
|
255
261
|
return None
|
|
256
262
|
|
|
257
|
-
|
|
263
|
+
def _resolve_reply_header(
|
|
264
|
+
self,
|
|
265
|
+
command: GatewayCommand,
|
|
266
|
+
snapshot: Optional[dict[str, Any]],
|
|
267
|
+
) -> Optional[MessageHeader]:
|
|
268
|
+
"""Return the header describing the caller this execution owes a reply
|
|
269
|
+
to, or None when nobody is waiting.
|
|
270
|
+
|
|
271
|
+
Mirrors ``GatewayWorker._resolve_reply_command``: a resume's header
|
|
272
|
+
describes the sub-agent that just finished, so the caller has to be
|
|
273
|
+
read back from the execution record the original dispatch wrote — and,
|
|
274
|
+
for the same reason as there, ``CLIENT_SOURCE_AGENT_TYPE`` is not a
|
|
275
|
+
caller. It is what a client writes on a root execution's record, and
|
|
276
|
+
nothing consumes its control stream.
|
|
277
|
+
|
|
278
|
+
Four fields come from that snapshot, not from the waking message:
|
|
279
|
+
``source_agent_type``, ``parent_message_id``, ``task_group_id`` and
|
|
280
|
+
``metadata``. The last one is restored as a full replacement rather
|
|
281
|
+
than a merge, exactly as ``GatewayWorker._resolve_reply_command``
|
|
282
|
+
does it: the waking message (an ``ask_user`` answer, or a sub-call's
|
|
283
|
+
reply) is transient plumbing for that one hop, not something the
|
|
284
|
+
caller ever sent. A snapshot missing the field (an execution recorded
|
|
285
|
+
before it existed) degrades to an empty dict rather than leaking the
|
|
286
|
+
waking message's metadata to the caller.
|
|
287
|
+
|
|
288
|
+
This is the OUTBOUND direction only. What the handler itself reads is
|
|
289
|
+
``_restore_inbound_metadata``, which merges rather than replaces —
|
|
290
|
+
and which must run even when this returns None, since a
|
|
291
|
+
client-dispatched root has no caller but still has its own metadata
|
|
292
|
+
to get back.
|
|
293
|
+
"""
|
|
294
|
+
header = command.header
|
|
295
|
+
if not isinstance(command, ResumeCommand):
|
|
296
|
+
return header if header.source_agent_type else None
|
|
297
|
+
|
|
298
|
+
caller_agent_type = str((snapshot or {}).get("source_agent_type", "") or "")
|
|
258
299
|
if not caller_agent_type or caller_agent_type == CLIENT_SOURCE_AGENT_TYPE:
|
|
259
300
|
return None
|
|
260
301
|
return replace(
|
|
261
302
|
header,
|
|
262
303
|
source_agent_type=caller_agent_type,
|
|
263
|
-
parent_message_id=str((
|
|
264
|
-
task_group_id=str((
|
|
304
|
+
parent_message_id=str((snapshot or {}).get("parent_message_id", "") or ""),
|
|
305
|
+
task_group_id=str((snapshot or {}).get("task_group_id", "") or ""),
|
|
306
|
+
metadata=dict((snapshot or {}).get("metadata") or {}),
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
@staticmethod
|
|
310
|
+
def _restore_inbound_metadata(
|
|
311
|
+
command: GatewayCommand,
|
|
312
|
+
is_agent_return: bool,
|
|
313
|
+
snapshot: Optional[dict[str, Any]],
|
|
314
|
+
) -> GatewayCommand:
|
|
315
|
+
"""Give a resumed handler its own dispatch metadata back.
|
|
316
|
+
|
|
317
|
+
Mirrors ``GatewayWorker._restore_inbound_metadata``, including its
|
|
318
|
+
merge-don't-replace rule and its no-mutation rule; see that docstring
|
|
319
|
+
for why the inbound direction differs from the outbound one.
|
|
320
|
+
"""
|
|
321
|
+
if not is_agent_return:
|
|
322
|
+
return command
|
|
323
|
+
return replace(
|
|
324
|
+
command,
|
|
325
|
+
header=replace(
|
|
326
|
+
command.header,
|
|
327
|
+
metadata=merge_resume_metadata(
|
|
328
|
+
(snapshot or {}).get("metadata"),
|
|
329
|
+
command.header.metadata,
|
|
330
|
+
),
|
|
331
|
+
),
|
|
265
332
|
)
|
|
266
333
|
|
|
267
334
|
async def _enqueue_callback(
|
by_framework/worker/runner.py
CHANGED
|
@@ -625,11 +625,33 @@ class WorkerRunner:
|
|
|
625
625
|
if registry:
|
|
626
626
|
if existing_execution:
|
|
627
627
|
if hasattr(registry, "update_execution_status"):
|
|
628
|
+
status_fields: dict[str, Any] = {
|
|
629
|
+
"worker_id": self.worker.worker_id
|
|
630
|
+
}
|
|
631
|
+
if not isinstance(command, ResumeCommand):
|
|
632
|
+
# The dispatch metadata, recorded by whoever is
|
|
633
|
+
# executing the message rather than by whoever
|
|
634
|
+
# sent it: header.metadata IS the dispatch
|
|
635
|
+
# metadata by definition, so this is correct for
|
|
636
|
+
# every dispatcher — including a client root
|
|
637
|
+
# dispatch and a TS/Java caller, neither of which
|
|
638
|
+
# writes the field (see context.py's
|
|
639
|
+
# initialize_execution, which does, and is kept
|
|
640
|
+
# because it is the only record that exists
|
|
641
|
+
# before a callee is ever picked up).
|
|
642
|
+
# _handle_message reads it back to restore what
|
|
643
|
+
# this execution was originally asked for once it
|
|
644
|
+
# resumes.
|
|
645
|
+
#
|
|
646
|
+
# A ResumeCommand must NOT write it: the waking
|
|
647
|
+
# message's metadata would overwrite the very
|
|
648
|
+
# original this exists to preserve.
|
|
649
|
+
status_fields["metadata"] = dict(header.metadata)
|
|
628
650
|
await registry.update_execution_status(
|
|
629
651
|
execution_id,
|
|
630
652
|
header.session_id,
|
|
631
653
|
"RUNNING",
|
|
632
|
-
|
|
654
|
+
**status_fields,
|
|
633
655
|
)
|
|
634
656
|
elif hasattr(registry, "save_execution"):
|
|
635
657
|
await registry.save_execution(
|
|
@@ -641,6 +663,12 @@ class WorkerRunner:
|
|
|
641
663
|
"parent_message_id": header.parent_message_id or "",
|
|
642
664
|
"worker_id": self.worker.worker_id,
|
|
643
665
|
"target_agent_type": header.target_agent_type,
|
|
666
|
+
# Same reason as the update branch above. This one
|
|
667
|
+
# is the no-existing-record fallback, so there is
|
|
668
|
+
# no stored original to protect and no ResumeCommand
|
|
669
|
+
# guard to make: whatever woke this is all this
|
|
670
|
+
# execution has ever been told.
|
|
671
|
+
"metadata": dict(header.metadata),
|
|
644
672
|
"stream_name": stream_name,
|
|
645
673
|
"redis_message_id": msg_id,
|
|
646
674
|
"status": "RUNNING",
|
by_framework/worker/worker.py
CHANGED
|
@@ -59,6 +59,7 @@ from by_framework.trace.span_recorder import TraceSpan, str_to_uint64
|
|
|
59
59
|
from by_framework.worker.context import AgentContext, current_agent_context_var
|
|
60
60
|
from by_framework.worker.heartbeat import WorkerHeartbeat
|
|
61
61
|
|
|
62
|
+
from ._resume_metadata import merge_resume_metadata
|
|
62
63
|
from .sandbox.hook_sandbox import active_workspace
|
|
63
64
|
|
|
64
65
|
|
|
@@ -377,6 +378,47 @@ class GatewayWorker(ABC):
|
|
|
377
378
|
),
|
|
378
379
|
)
|
|
379
380
|
|
|
381
|
+
@staticmethod
|
|
382
|
+
def _restore_inbound_metadata(
|
|
383
|
+
command: GatewayCommand,
|
|
384
|
+
is_agent_return: bool,
|
|
385
|
+
execution: Optional["RunningExecution"],
|
|
386
|
+
) -> GatewayCommand:
|
|
387
|
+
"""Give a resumed handler its own dispatch metadata back.
|
|
388
|
+
|
|
389
|
+
The mirror image of ``_resolve_reply_command``, and deliberately not
|
|
390
|
+
the same rule. That one rebuilds the header this execution *sends*;
|
|
391
|
+
this one rebuilds the header it *reads*. A resumed handler otherwise
|
|
392
|
+
sees only the metadata of whatever woke it up — an ``ask_user``
|
|
393
|
+
answer's, or a sub-call's reply — and everything the execution was
|
|
394
|
+
originally dispatched with is gone from the moment it first suspends.
|
|
395
|
+
|
|
396
|
+
Merged, not replaced (the opposite of the outbound direction): this
|
|
397
|
+
agent IS the addressee of the waking message, so its metadata is real
|
|
398
|
+
payload here rather than someone else's plumbing. Original dispatch
|
|
399
|
+
metadata is the base, the waking message wins on collisions. See
|
|
400
|
+
``_resume_metadata.py`` for why the framework's per-hop trace keys are
|
|
401
|
+
excluded from the base.
|
|
402
|
+
|
|
403
|
+
Returns a NEW command — never mutates. ``prepare_command_for_processing``
|
|
404
|
+
returns the *same* object for the base worker (only ``ByaiWorker``
|
|
405
|
+
rebuilds it), so mutating the header here would also rewrite
|
|
406
|
+
``raw_command``'s and leak this merge into the reply that goes out.
|
|
407
|
+
"""
|
|
408
|
+
if not is_agent_return:
|
|
409
|
+
return command
|
|
410
|
+
snapshot = getattr(execution, "existing_data", None) or {}
|
|
411
|
+
return replace(
|
|
412
|
+
command,
|
|
413
|
+
header=replace(
|
|
414
|
+
command.header,
|
|
415
|
+
metadata=merge_resume_metadata(
|
|
416
|
+
snapshot.get("metadata"),
|
|
417
|
+
command.header.metadata,
|
|
418
|
+
),
|
|
419
|
+
),
|
|
420
|
+
)
|
|
421
|
+
|
|
380
422
|
@staticmethod
|
|
381
423
|
def _apply_suspended_status(
|
|
382
424
|
task_result: AgentTaskResult,
|
|
@@ -682,6 +724,7 @@ class GatewayWorker(ABC):
|
|
|
682
724
|
is_agent_return = isinstance(raw_command, ResumeCommand)
|
|
683
725
|
reply_command = self._resolve_reply_command(raw_command, execution)
|
|
684
726
|
has_source_agent = reply_command is not None
|
|
727
|
+
command = self._restore_inbound_metadata(command, is_agent_return, execution)
|
|
685
728
|
|
|
686
729
|
# Get workspace dir from workspace_manager if available
|
|
687
730
|
# Note: We don't use hasattr check because it doesn't work well with mocks
|
|
@@ -5,7 +5,7 @@ by_framework/admin/cli.py,sha256=YW4tQxb0kBheRNQR53zOkoAlYHWP4bgycuZkaJIU3WA,119
|
|
|
5
5
|
by_framework/admin/worker_manager.py,sha256=ihXRMlFJAR74T2JSh3xTNCDjzAElWd0-_yVDAZwSTJU,5924
|
|
6
6
|
by_framework/client/__init__.py,sha256=kGP7uoXIlTJuiJ2Cgl1Q5wqYjnMA4SZqoa7Q7BhQ5JQ,272
|
|
7
7
|
by_framework/client/byai_client.py,sha256=bB1NyKLvVW82bXefD_3zymqZ1vk2O925CIbxOho9Qww,2426
|
|
8
|
-
by_framework/client/client.py,sha256=
|
|
8
|
+
by_framework/client/client.py,sha256=XcI29Jm9ktb74cq-VcUoMl0lyIfAvGrSRcS4CttATug,45666
|
|
9
9
|
by_framework/common/__init__.py,sha256=EkfIiVdmb8SCpi7OOUm6Hu84B7Wzekb-TG2hbvD_R1w,1685
|
|
10
10
|
by_framework/common/config.py,sha256=mTbCn3YspY8k00b4URSbi07MNC3Cfdj7RPtDLrXrJV8,5107
|
|
11
11
|
by_framework/common/constants.py,sha256=sZOUw5rlwszzsYO57yUHXI4zyrEuwka2cOyji_jblRE,34857
|
|
@@ -69,7 +69,7 @@ by_framework/errors/http.py,sha256=xtm4EKwbE1Z0n4yqWTjPOVbDd9fIPfmT8W4ou5aOLCQ,7
|
|
|
69
69
|
by_framework/errors/protocol.py,sha256=MWr15TFIHs8Ql67mgvU9mebGdE04yPxmg4AOEGbF9E4,1269
|
|
70
70
|
by_framework/errors/registry.py,sha256=LmcqSXiW8FSOghpLPHTllkbrQCnVcMYTLzO2cNU3wIw,842
|
|
71
71
|
by_framework/metrics/__init__.py,sha256=8AQpVnOwAmHoQsDhCfkNXSKUOX_ZCBF_QajBXmsNhgc,11353
|
|
72
|
-
by_framework/metrics/catalog.py,sha256=
|
|
72
|
+
by_framework/metrics/catalog.py,sha256=1yWxVCvvrbkT0Rdmi0x-rtRwtXFzQg6Dmuj75izN2X0,19684
|
|
73
73
|
by_framework/metrics/collector.py,sha256=uIO2TJfX4zQTBi5gWbMfgwtdMjrhmhzjzW36BcqxBKE,8533
|
|
74
74
|
by_framework/metrics/read_client.py,sha256=kcgHJlJh47eFPhlbH8QOyD-K6SpKbZHEX624XD6Mvxo,18511
|
|
75
75
|
by_framework/metrics/snapshot.py,sha256=eI4JVV3uma7fT6nvp2bliO2ZUBJhJT6bzTvQDIeC-wk,101784
|
|
@@ -86,19 +86,20 @@ by_framework/worker/__init__.py,sha256=GBkUWYnHc2j3_Wx2AC2P49DzFl0CSrl0SKhdRP5CT
|
|
|
86
86
|
by_framework/worker/_control_handling.py,sha256=0bL1Alx8_qaeXQV32Xv4ARSd9q77ySh9LMd7KtZZXu8,8111
|
|
87
87
|
by_framework/worker/_execution_tracking.py,sha256=AWahfDKTXGrx_JWSCNisjGKkGAQruu9vSlXisof0BYQ,2493
|
|
88
88
|
by_framework/worker/_message_processing.py,sha256=rgKzJTgPVDFp_HUfMcqJBwNdTCEfspuRAo5IM9VKsxs,2622
|
|
89
|
+
by_framework/worker/_resume_metadata.py,sha256=bo3OiO_E5e6PaGcKRzwDdEKjFjMTe2d26aRPCpFSSBg,2938
|
|
89
90
|
by_framework/worker/app.py,sha256=UHaQZzalqfPmHvfq-XL7vlPOJ8mTm5ULIU4JpeXA85g,16319
|
|
90
91
|
by_framework/worker/byai_context.py,sha256=DvKw-X8ePZr5c5osdapvzTvH4G23u0J80U-fgMLtge4,2388
|
|
91
92
|
by_framework/worker/byai_worker.py,sha256=0NoRqzuTG_MPMxVbZ1ueeOrqI0H8-Adxh7dM3vckjJo,1757
|
|
92
93
|
by_framework/worker/context.py,sha256=5As3GcnVrwIE1nCJDDYLt_Ke8-8nzh44CzQBvyncQsU,59833
|
|
93
94
|
by_framework/worker/health_server.py,sha256=4Jw3ltAD7Ow4dPHhwTUU_4JjKz-iDCNG7RJzD2VzPlM,6090
|
|
94
95
|
by_framework/worker/heartbeat.py,sha256=trHDS8FUALsA6jyzDPa087hpr8_2vY8ZUFn0ld1kI2o,15465
|
|
95
|
-
by_framework/worker/processor.py,sha256=
|
|
96
|
-
by_framework/worker/runner.py,sha256=
|
|
97
|
-
by_framework/worker/worker.py,sha256=
|
|
96
|
+
by_framework/worker/processor.py,sha256=UgC4_p-bg2KOHVTEILeMXF44rT9VLrRP5vompX0EeiQ,16243
|
|
97
|
+
by_framework/worker/runner.py,sha256=6_L_UUV1Kljys_4Eh_qnwMd7a-gGsikd4M_O5K879NA,51897
|
|
98
|
+
by_framework/worker/worker.py,sha256=IaPFLWCFo5U5Xo7PIQHtTt4JmtxZA1O_3nAuGl_g4N0,49527
|
|
98
99
|
by_framework/worker/sandbox/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
99
100
|
by_framework/worker/sandbox/hook_sandbox.py,sha256=XXSdH5YGX814LvV03vkq-zn5DVIBPYYt2NhRv1lT1Bk,2329
|
|
100
|
-
by_framework-0.2.2.
|
|
101
|
-
by_framework-0.2.2.
|
|
102
|
-
by_framework-0.2.2.
|
|
103
|
-
by_framework-0.2.2.
|
|
104
|
-
by_framework-0.2.2.
|
|
101
|
+
by_framework-0.2.2.dev15.dist-info/METADATA,sha256=l050msraVm4RdyM0rIA_GXOpYu5pWyuUTdw7e2IBdd4,28018
|
|
102
|
+
by_framework-0.2.2.dev15.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
|
|
103
|
+
by_framework-0.2.2.dev15.dist-info/entry_points.txt,sha256=5t6kgMjkR0UgJTpOsbuQDo0fOf7zeXjQW9DhkxfAGFc,56
|
|
104
|
+
by_framework-0.2.2.dev15.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
105
|
+
by_framework-0.2.2.dev15.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|