mirrors-sdk 0.1.0__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.
@@ -0,0 +1,33 @@
1
+ """Mirrors: optional context and best-effort original trace capture."""
2
+
3
+ from .client import Mirrors
4
+ from .models import (
5
+ ContextFreshness,
6
+ ContextResult,
7
+ DeliveryReceipt,
8
+ DeliveryTicket,
9
+ FlushResult,
10
+ TurnTimer,
11
+ )
12
+ from .types import (
13
+ CanonicalMessage,
14
+ CanonicalTrace,
15
+ EndUser,
16
+ MessageBlock,
17
+ SourceCompleteness,
18
+ )
19
+
20
+ __all__ = [
21
+ "Mirrors",
22
+ "ContextFreshness",
23
+ "ContextResult",
24
+ "DeliveryReceipt",
25
+ "DeliveryTicket",
26
+ "FlushResult",
27
+ "TurnTimer",
28
+ "CanonicalMessage",
29
+ "CanonicalTrace",
30
+ "EndUser",
31
+ "MessageBlock",
32
+ "SourceCompleteness",
33
+ ]
mirrors_sdk/capture.py ADDED
@@ -0,0 +1,254 @@
1
+ """Ordered capture delivery with immutable reconciliation of uncertain writes."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import hashlib
7
+ import json
8
+ import time
9
+ import uuid
10
+ from collections import OrderedDict
11
+ from dataclasses import dataclass, field
12
+ from typing import Any
13
+
14
+ from .models import DeliveryReceipt
15
+ from .transport import TransportError
16
+ from .wire import Wire
17
+
18
+
19
+ @dataclass(frozen=True)
20
+ class _Pending:
21
+ digest: bytes
22
+ body: bytes
23
+ request: dict[str, Any]
24
+
25
+
26
+ @dataclass
27
+ class _Trace:
28
+ metadata: bytes
29
+ ordinal: int = 0
30
+ pending: _Pending | None = None
31
+ sealed: bool = False
32
+ retired: bool = False
33
+ lock: asyncio.Lock = field(default_factory=asyncio.Lock)
34
+ running: asyncio.Task[Any] | None = None
35
+ final_digest: bytes | None = None
36
+ final_owner: asyncio.Task[Any] | None = None
37
+
38
+
39
+ class CaptureRuntime:
40
+ """Loop-owned state; application threads communicate through the SDK worker."""
41
+
42
+ def __init__(self, transport: Any, config: Any) -> None:
43
+ self._transport = transport
44
+ self._config = config
45
+ self._active: dict[str, _Trace] = {}
46
+ self._terminal: dict[str, _Trace] = {}
47
+ self._recent: OrderedDict[str, tuple[bytes, DeliveryReceipt]] = OrderedDict()
48
+ self._closed = False
49
+
50
+ async def send(self, input: dict[str, Any], deadline: float) -> DeliveryReceipt:
51
+ """Send once; finals may retry once within the same absolute deadline."""
52
+ receipt = await self._send(input, deadline)
53
+ if input.get("final") is True:
54
+ trace_id = input.get("trace", {}).get("id")
55
+ # A rejected final still ends the host execution. Release any prefix state
56
+ # left behind by validation/capacity rejection, preserving admitted finals.
57
+ self.release(trace_id)
58
+ state = self._terminal.get(trace_id)
59
+ if state is not None and state.final_owner is asyncio.current_task():
60
+ # Settled uncertain finals do not consume scarce terminal capacity forever.
61
+ # Their immutable outcome is retained in the same bounded receipt cache.
62
+ self._terminal.pop(trace_id, None)
63
+ if state.final_digest is not None:
64
+ self._remember(trace_id, state.final_digest, receipt)
65
+ self._retire(state)
66
+ return receipt
67
+
68
+ async def _send(self, input: dict[str, Any], deadline: float) -> DeliveryReceipt:
69
+ if self._closed:
70
+ return DeliveryReceipt("dropped", "closed")
71
+ final = input.get("final") is True
72
+ timeout_ms = self._config.timeout_ms
73
+ deadline = min(
74
+ deadline,
75
+ time.monotonic() + (timeout_ms or (6000 if final else 1000)) / 1000,
76
+ )
77
+ try:
78
+ encoded = Wire.encode(input)
79
+ snapshot = json.loads(encoded)
80
+ trace_id = snapshot["trace"]["id"]
81
+ digest = hashlib.sha256(encoded).digest()
82
+ metadata = self._metadata(snapshot["trace"])
83
+ except (TypeError, ValueError, KeyError, RecursionError):
84
+ return DeliveryReceipt("dropped", "invalid_evidence")
85
+ previous = self._recent.get(trace_id)
86
+ if previous is not None:
87
+ return (
88
+ previous[1]
89
+ if previous[0] == digest
90
+ else DeliveryReceipt("dropped", "terminal_conflict")
91
+ )
92
+ state = self._terminal.get(trace_id) or self._active.get(trace_id)
93
+ if state is not None and state.metadata != metadata:
94
+ return DeliveryReceipt("dropped", "invalid_evidence")
95
+ if state is not None and state.sealed and not final:
96
+ return DeliveryReceipt("dropped", "terminal_conflict")
97
+ if state is not None and state.sealed and state.final_digest != digest:
98
+ return DeliveryReceipt("dropped", "terminal_conflict")
99
+ if state is None:
100
+ pool = self._terminal if final else self._active
101
+ if len(pool) >= (16 if final else 64):
102
+ return DeliveryReceipt("dropped", "capacity")
103
+ state = _Trace(metadata, sealed=final)
104
+ pool[trace_id] = state
105
+ if final:
106
+ state.final_digest = digest
107
+ state.final_owner = asyncio.current_task()
108
+ elif final and not state.sealed:
109
+ if len(self._terminal) >= 16:
110
+ return DeliveryReceipt("dropped", "capacity")
111
+ state.sealed = True
112
+ state.final_digest = digest
113
+ state.final_owner = asyncio.current_task()
114
+ self._active.pop(trace_id, None)
115
+ self._terminal[trace_id] = state
116
+ acquired = False
117
+ try:
118
+ remaining = deadline - time.monotonic()
119
+ if remaining <= 0:
120
+ return DeliveryReceipt("unconfirmed", "timeout")
121
+ await asyncio.wait_for(state.lock.acquire(), remaining)
122
+ acquired = True
123
+ state.running = asyncio.current_task()
124
+ if state.retired or self._closed:
125
+ return DeliveryReceipt("dropped", "closed")
126
+ if state.sealed and not final:
127
+ return DeliveryReceipt("dropped", "superseded")
128
+ # Another queued final may have completed while this call waited.
129
+ previous = self._recent.get(trace_id)
130
+ if previous is not None:
131
+ return (
132
+ previous[1]
133
+ if previous[0] == digest
134
+ else DeliveryReceipt("dropped", "terminal_conflict")
135
+ )
136
+ if state.pending is not None and state.pending.digest != digest:
137
+ # An uncertain terminal is immutable. A different final cannot replace it.
138
+ if state.pending.request["final"]:
139
+ return DeliveryReceipt("dropped", "terminal_conflict")
140
+ await self._deliver(state, trace_id, deadline, retry=final)
141
+ if state.pending is None:
142
+ if state.ordinal >= 10_000:
143
+ return DeliveryReceipt("dropped", "capacity")
144
+ request = {
145
+ **snapshot,
146
+ "protocol": 3,
147
+ "agentId": self._config.agent_id,
148
+ "requestId": str(uuid.uuid4()),
149
+ "ordinal": state.ordinal + 1,
150
+ }
151
+ state.pending = _Pending(
152
+ digest, Wire.encode(request, 1_048_576), request
153
+ )
154
+ return await self._deliver(state, trace_id, deadline, retry=final)
155
+ except asyncio.TimeoutError:
156
+ return DeliveryReceipt("unconfirmed", "timeout")
157
+ except asyncio.CancelledError:
158
+ return DeliveryReceipt("unconfirmed", "transport")
159
+ except TransportError as error:
160
+ return DeliveryReceipt(
161
+ "unconfirmed", "timeout" if error.reason == "timeout" else "transport"
162
+ )
163
+ except Exception:
164
+ # Optional capture must not leak protocol or transport failures into the host.
165
+ return DeliveryReceipt("unconfirmed", "transport")
166
+ finally:
167
+ if acquired:
168
+ state.running = None
169
+ state.lock.release()
170
+
171
+ async def _deliver(
172
+ self, state: _Trace, trace_id: str, deadline: float, *, retry: bool
173
+ ) -> DeliveryReceipt:
174
+ pending = state.pending
175
+ assert pending is not None
176
+ for attempt in range(2 if retry else 1):
177
+ if time.monotonic() >= deadline:
178
+ raise asyncio.TimeoutError
179
+ if state.retired or self._closed:
180
+ raise asyncio.CancelledError
181
+ try:
182
+ response = await self._transport.request(
183
+ "trace",
184
+ pending.body,
185
+ deadline,
186
+ lambda value: Wire.trace_response(value, pending.request),
187
+ final_recovery=retry,
188
+ )
189
+ break
190
+ except TransportError as error:
191
+ if (
192
+ error.permanent
193
+ or not retry
194
+ or attempt == 1
195
+ or time.monotonic() >= deadline
196
+ ):
197
+ raise
198
+ if state.retired or self._closed:
199
+ raise asyncio.CancelledError
200
+ if time.monotonic() >= deadline:
201
+ raise asyncio.TimeoutError
202
+ if not Wire.trace_response(response, pending.request):
203
+ raise ValueError("Invalid capture response")
204
+ state.ordinal = pending.request["ordinal"]
205
+ state.pending = None
206
+ receipt = DeliveryReceipt("accepted", ingestion=response["ingestion"])
207
+ if pending.request["final"]:
208
+ self._active.pop(trace_id, None)
209
+ self._terminal.pop(trace_id, None)
210
+ self._remember(trace_id, pending.digest, receipt)
211
+ return receipt
212
+
213
+ def _remember(self, trace_id: str, digest: bytes, receipt: DeliveryReceipt) -> None:
214
+ self._recent[trace_id] = (digest, receipt)
215
+ if len(self._recent) > 128:
216
+ self._recent.popitem(last=False)
217
+
218
+ def release(self, trace_id: str) -> None:
219
+ """Retire prefixes locally; an admitted final retains its delivery opportunity."""
220
+ state = self._active.pop(trace_id, None)
221
+ if state is not None:
222
+ self._retire(state)
223
+
224
+ def clear(self) -> None:
225
+ """Permanently stop capture and abandon all outstanding work."""
226
+ self._closed = True
227
+ for state in (*self._active.values(), *self._terminal.values()):
228
+ self._retire(state)
229
+ self._active.clear()
230
+ self._terminal.clear()
231
+ self._recent.clear()
232
+
233
+ @staticmethod
234
+ def _retire(state: _Trace) -> None:
235
+ state.retired = True
236
+ if state.running is not None:
237
+ state.running.cancel()
238
+
239
+ @staticmethod
240
+ def _metadata(trace: dict[str, Any]) -> bytes:
241
+ # End-user and measured duration intentionally belong to the final snapshot.
242
+ fields = (
243
+ "model",
244
+ "entryPoint",
245
+ "completeness",
246
+ "conversationId",
247
+ "conversationRevision",
248
+ )
249
+ return json.dumps(
250
+ {key: trace[key] for key in fields if key in trace},
251
+ sort_keys=True,
252
+ separators=(",", ":"),
253
+ ensure_ascii=False,
254
+ ).encode("utf-8")
mirrors_sdk/circuit.py ADDED
@@ -0,0 +1,47 @@
1
+ """An endpoint-local circuit, owned exclusively by the background event loop."""
2
+
3
+ from collections.abc import Callable
4
+ import time
5
+
6
+
7
+ class Circuit:
8
+ def __init__(self, threshold: int = 3, cooldown_ms: int = 30_000) -> None:
9
+ if type(threshold) is not int or not 1 <= threshold <= 100:
10
+ raise ValueError("Invalid circuit threshold")
11
+ if type(cooldown_ms) is not int or not 1 <= cooldown_ms <= 300_000:
12
+ raise ValueError("Invalid circuit cooldown")
13
+ self._threshold = threshold
14
+ self._cooldown = cooldown_ms / 1000
15
+ self._failures = 0
16
+ self._until = 0.0
17
+ self._probing = False
18
+ self._generation = 0
19
+
20
+ def acquire(self) -> Callable[[bool | None], None] | None:
21
+ if self._until and (time.monotonic() < self._until or self._probing):
22
+ return None
23
+ probe = self._until > 0
24
+ if probe:
25
+ self._probing = True
26
+ generation = self._generation
27
+ finished = False
28
+
29
+ def finish(success: bool | None) -> None:
30
+ nonlocal finished
31
+ if finished or generation != self._generation:
32
+ return
33
+ finished = True
34
+ if probe:
35
+ self._probing = False
36
+ if success is None:
37
+ return
38
+ if success:
39
+ self._failures = 0
40
+ self._until = 0
41
+ else:
42
+ self._failures += 1
43
+ if probe or self._failures >= self._threshold:
44
+ self._until = time.monotonic() + self._cooldown
45
+ self._generation += 1
46
+
47
+ return finish
mirrors_sdk/client.py ADDED
@@ -0,0 +1,339 @@
1
+ """Synchronous integration surface with optional async waits for async applications."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ from collections import OrderedDict
7
+ from concurrent.futures import TimeoutError
8
+ import hashlib
9
+ import os
10
+ import threading
11
+ import time
12
+
13
+ from .configuration import Config
14
+ from .context import ContextHost
15
+ from .delivery import Deliveries
16
+ from .models import DeliveryReceipt, FlushResult, TurnTimer
17
+ from .wire import Wire
18
+ from .worker import Worker
19
+
20
+
21
+ class Mirrors:
22
+ def __init__(
23
+ self,
24
+ *,
25
+ api_url: str,
26
+ api_key: str,
27
+ agent_id: str,
28
+ timeout_ms: int | None = None,
29
+ enabled: bool = True,
30
+ failure_threshold: int = 3,
31
+ cooldown_ms: int = 30_000,
32
+ heartbeat_interval_ms: int = 1000,
33
+ mode_lease_ms: int = 3000,
34
+ ):
35
+ self._lock = threading.RLock()
36
+ self._pid = os.getpid()
37
+ self._closed = False
38
+ self._failed = False
39
+ self._worker = None
40
+ self._deliveries = Deliveries()
41
+ self._traces = {}
42
+ self._recent = OrderedDict()
43
+ try:
44
+ self._config = Config(
45
+ api_url=api_url,
46
+ api_key=api_key,
47
+ agent_id=agent_id,
48
+ timeout_ms=timeout_ms,
49
+ enabled=enabled,
50
+ failure_threshold=failure_threshold,
51
+ cooldown_ms=cooldown_ms,
52
+ heartbeat_interval_ms=heartbeat_interval_ms,
53
+ mode_lease_ms=mode_lease_ms,
54
+ )
55
+ except Exception:
56
+ self._config = None
57
+ self._enabled = self._config is not None and self._config.enabled
58
+ self._context = ContextHost(self._config)
59
+ if self._config:
60
+ try:
61
+ self._worker = Worker(
62
+ self._config, self._context.observe_mode, self._worker_failed
63
+ )
64
+ self._context.worker = self._worker
65
+ except Exception:
66
+ self._worker_failed()
67
+
68
+ def _unavailable(self):
69
+ if os.getpid() != self._pid:
70
+ return "forked_process"
71
+ if self._config is None:
72
+ return "invalid_options"
73
+ if self._failed:
74
+ return "worker_failed"
75
+ if self._closed:
76
+ return "closed"
77
+ if not self._enabled:
78
+ return "disabled"
79
+ if self._worker is None:
80
+ return "worker_failed"
81
+ return None
82
+
83
+ def _remember(self, trace_id, value):
84
+ self._recent[trace_id] = value
85
+ self._recent.move_to_end(trace_id)
86
+ if len(self._recent) > 128:
87
+ self._recent.popitem(last=False)
88
+
89
+ def capture_trace(
90
+ self, trace: dict, *, final: bool = False, terminal: str | None = None
91
+ ):
92
+ """Copy and queue original evidence. Never wait for transport or a provider."""
93
+ started = time.monotonic()
94
+ if os.getpid() != self._pid:
95
+ # Never touch a lock inherited from another thread across fork.
96
+ from .models import DeliveryTicket
97
+
98
+ ticket = DeliveryTicket("forked")
99
+ ticket._future.set_result(DeliveryReceipt("dropped", "forked_process"))
100
+ return ticket
101
+ with self._lock:
102
+ reason = self._unavailable()
103
+ if reason:
104
+ return self._deliveries.drop(reason)
105
+ try:
106
+ snapshot = Wire.snapshot(trace, final=final, terminal=terminal)
107
+ encoded_trace = Wire.encode(snapshot)
108
+ value = {"trace": snapshot, "final": final, "captureOnly": True}
109
+ if terminal is not None:
110
+ value["terminal"] = terminal
111
+ digest = hashlib.sha256(Wire.encode(value)).digest()
112
+ except Exception:
113
+ if type(trace) is dict and type(trace.get("id")) is str:
114
+ self.release_trace(trace["id"])
115
+ return self._deliveries.drop("invalid_evidence")
116
+ trace_id = snapshot["id"]
117
+ with self._lock:
118
+ reason = self._unavailable()
119
+ if reason:
120
+ return self._deliveries.drop(reason)
121
+ if trace_id in self._recent:
122
+ recent = self._recent[trace_id]
123
+ return (
124
+ recent[1]
125
+ if recent and recent[0] == digest
126
+ else self._deliveries.drop("terminal_conflict")
127
+ )
128
+ if final:
129
+ self._context.release(trace_id)
130
+ else:
131
+ self._context.observe(trace_id, encoded_trace)
132
+ previous = self._traces.get(trace_id)
133
+ if (
134
+ previous
135
+ and previous[0] == digest
136
+ and previous[1].receipt.status == "pending"
137
+ ):
138
+ return previous[1]
139
+ if self._deliveries.full(final) or (
140
+ not final and not previous and len(self._traces) >= 64
141
+ ):
142
+ if final:
143
+ self.release_trace(trace_id)
144
+ return self._deliveries.drop("capacity")
145
+ ticket = self._deliveries.create(final, trace_id)
146
+ if final:
147
+ self._traces.pop(trace_id, None)
148
+ self._remember(trace_id, (digest, ticket))
149
+ else:
150
+ self._traces[trace_id] = (digest, ticket)
151
+ budget = self._config.timeout_ms or (6000 if final else 1000)
152
+ try:
153
+ job = self._worker.submit(
154
+ lambda worker: worker.capture.send(value, started + budget / 1000)
155
+ )
156
+ self._deliveries.attach(ticket, job)
157
+ job.add_done_callback(lambda done: self._capture_done(ticket, done))
158
+ except Exception:
159
+ self._deliveries.settle(
160
+ ticket, DeliveryReceipt("unconfirmed", "worker_failed")
161
+ )
162
+ return ticket
163
+
164
+ def _capture_done(self, ticket, job):
165
+ try:
166
+ receipt = job.result()
167
+ except Exception:
168
+ receipt = DeliveryReceipt("unconfirmed", "transport")
169
+ self._deliveries.settle(ticket, receipt)
170
+
171
+ def _begin_context(self, trace, timeout_ms, started, cancel):
172
+ with self._lock:
173
+ reason = self._unavailable()
174
+ if not reason and cancel is not None and cancel.is_set():
175
+ reason = "canceled"
176
+ if (
177
+ not reason
178
+ and type(trace) is dict
179
+ and type(trace.get("id")) is str
180
+ and trace["id"] in self._recent
181
+ ):
182
+ reason = "execution_closed"
183
+ if reason:
184
+ return self._context.empty(reason, started), None, started
185
+ future, deadline = self._context.start(trace, timeout_ms, started)
186
+ # Finalization/release may race with bounded preparation outside this lock.
187
+ with self._lock:
188
+ if (
189
+ type(trace) is dict
190
+ and type(trace.get("id")) is str
191
+ and trace["id"] in self._recent
192
+ ):
193
+ self._context.release(trace["id"])
194
+ return None, future, deadline
195
+
196
+ def get_context(
197
+ self,
198
+ trace: dict,
199
+ *,
200
+ timeout_ms: int = 1000,
201
+ cancel: threading.Event | None = None,
202
+ ):
203
+ """Return optional text within one total second; failures leave context empty."""
204
+ started = time.monotonic()
205
+ if os.getpid() != self._pid:
206
+ from .models import ContextResult
207
+
208
+ return ContextResult(reason="forked_process")
209
+ result, future, deadline = self._begin_context(
210
+ trace, timeout_ms, started, cancel
211
+ )
212
+ if result:
213
+ return result
214
+ while True:
215
+ if future.done():
216
+ return future.result()
217
+ self._context.tick(future, cancel is not None and cancel.is_set())
218
+ try:
219
+ return future.result(
220
+ timeout=max(0, min(0.005, deadline - time.monotonic()))
221
+ )
222
+ except TimeoutError:
223
+ if time.monotonic() >= deadline:
224
+ self._context.tick(future)
225
+ return (
226
+ future.result()
227
+ if future.done()
228
+ else self._context.empty("timeout", started)
229
+ )
230
+
231
+ async def aget_context(
232
+ self,
233
+ trace: dict,
234
+ *,
235
+ timeout_ms: int = 1000,
236
+ cancel: threading.Event | None = None,
237
+ ):
238
+ started = time.monotonic()
239
+ if os.getpid() != self._pid:
240
+ from .models import ContextResult
241
+
242
+ return ContextResult(reason="forked_process")
243
+ result, future, deadline = self._begin_context(
244
+ trace, timeout_ms, started, cancel
245
+ )
246
+ if result:
247
+ return result
248
+ try:
249
+ while not future.done():
250
+ self._context.tick(future, cancel is not None and cancel.is_set())
251
+ if future.done():
252
+ break
253
+ await asyncio.sleep(max(0, min(0.005, deadline - time.monotonic())))
254
+ return future.result()
255
+ except asyncio.CancelledError:
256
+ self._context.tick(future, True)
257
+ raise # Preserve application cancellation, never turn it into a model call.
258
+
259
+ def release_trace(self, trace_id: str):
260
+ if os.getpid() != self._pid or type(trace_id) is not str:
261
+ return
262
+ try:
263
+ Wire.id(trace_id)
264
+ except ValueError:
265
+ return
266
+ with self._lock:
267
+ self._context.release(trace_id)
268
+ self._traces.pop(trace_id, None)
269
+ if trace_id not in self._recent:
270
+ self._remember(trace_id, None)
271
+ self._deliveries.cancel("canceled", trace_id)
272
+ if self._worker:
273
+ self._worker.release(trace_id)
274
+
275
+ def set_enabled(self, enabled: bool):
276
+ if os.getpid() != self._pid or type(enabled) is not bool:
277
+ return
278
+ with self._lock:
279
+ if self._closed or self._enabled == enabled:
280
+ return
281
+ self._enabled = enabled
282
+ self._context.set_enabled(enabled)
283
+ if not enabled:
284
+ for trace_id in list(self._traces):
285
+ self.release_trace(trace_id)
286
+ self._deliveries.cancel("disabled")
287
+ if self._worker:
288
+ self._worker.set_enabled(enabled)
289
+
290
+ def flush(self, *, timeout_ms: int = 2000):
291
+ if os.getpid() != self._pid:
292
+ return FlushResult(unconfirmed=1)
293
+ return self._deliveries.flush(timeout_ms)
294
+
295
+ async def aflush(self, *, timeout_ms: int = 2000):
296
+ if os.getpid() != self._pid:
297
+ return FlushResult(unconfirmed=1)
298
+ return await self._deliveries.flush_async(timeout_ms)
299
+
300
+ def close(self, *, timeout_ms: int = 2000):
301
+ if os.getpid() != self._pid:
302
+ return FlushResult(unconfirmed=1)
303
+ with self._lock:
304
+ self._closed = True
305
+ self._context.stop()
306
+ result = self.flush(timeout_ms=timeout_ms)
307
+ self.dispose()
308
+ return result
309
+
310
+ def dispose(self):
311
+ if os.getpid() != self._pid:
312
+ return
313
+ with self._lock:
314
+ self._closed = True
315
+ self._context.stop()
316
+ self._traces.clear()
317
+ self._recent.clear()
318
+ self._deliveries.cancel("closed")
319
+ if self._worker:
320
+ self._worker.dispose()
321
+
322
+ def _worker_failed(self):
323
+ self._failed = True
324
+ self._context.stop("worker_failed")
325
+ self._deliveries.cancel("worker_failed")
326
+
327
+ def diagnostics(self):
328
+ if os.getpid() != self._pid:
329
+ return {"reason": "forked_process"}
330
+ with self._lock:
331
+ return {
332
+ **self._deliveries.diagnostics(),
333
+ "active_executions": len(self._traces),
334
+ "recent_executions": len(self._recent),
335
+ }
336
+
337
+ @staticmethod
338
+ def start_turn() -> TurnTimer:
339
+ return TurnTimer()