soothe-client-python 1.0.4__tar.gz → 1.0.7__tar.gz

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.
Files changed (34) hide show
  1. {soothe_client_python-1.0.4 → soothe_client_python-1.0.7}/PKG-INFO +1 -1
  2. {soothe_client_python-1.0.4 → soothe_client_python-1.0.7}/src/soothe_client/__init__.py +2 -2
  3. {soothe_client_python-1.0.4 → soothe_client_python-1.0.7}/src/soothe_client/appkit/__init__.py +11 -4
  4. soothe_client_python-1.0.7/src/soothe_client/appkit/card_projection.py +10 -0
  5. {soothe_client_python-1.0.4 → soothe_client_python-1.0.7}/src/soothe_client/appkit/daemon_session.py +47 -59
  6. soothe_client_python-1.0.4/src/soothe_client/appkit/session_store.py → soothe_client_python-1.0.7/src/soothe_client/appkit/loop_session_store.py +4 -4
  7. {soothe_client_python-1.0.4 → soothe_client_python-1.0.7}/src/soothe_client/appkit/pool.py +3 -3
  8. {soothe_client_python-1.0.4 → soothe_client_python-1.0.7}/src/soothe_client/appkit/turn_runner.py +2 -2
  9. {soothe_client_python-1.0.4 → soothe_client_python-1.0.7}/src/soothe_client/helpers.py +4 -4
  10. {soothe_client_python-1.0.4 → soothe_client_python-1.0.7}/src/soothe_client/intent_hints.py +0 -2
  11. {soothe_client_python-1.0.4 → soothe_client_python-1.0.7}/src/soothe_client/protocol_params.py +6 -5
  12. {soothe_client_python-1.0.4 → soothe_client_python-1.0.7}/src/soothe_client/stream_terminal.py +11 -3
  13. {soothe_client_python-1.0.4 → soothe_client_python-1.0.7}/src/soothe_client/websocket.py +14 -8
  14. {soothe_client_python-1.0.4 → soothe_client_python-1.0.7}/.gitignore +0 -0
  15. {soothe_client_python-1.0.4 → soothe_client_python-1.0.7}/LICENSE +0 -0
  16. {soothe_client_python-1.0.4 → soothe_client_python-1.0.7}/README.md +0 -0
  17. {soothe_client_python-1.0.4 → soothe_client_python-1.0.7}/pyproject.toml +0 -0
  18. {soothe_client_python-1.0.4 → soothe_client_python-1.0.7}/src/soothe_client/appkit/attachments.py +0 -0
  19. {soothe_client_python-1.0.4 → soothe_client_python-1.0.7}/src/soothe_client/appkit/broadcaster.py +0 -0
  20. {soothe_client_python-1.0.4 → soothe_client_python-1.0.7}/src/soothe_client/appkit/chunk_filter.py +0 -0
  21. {soothe_client_python-1.0.4 → soothe_client_python-1.0.7}/src/soothe_client/appkit/classifier.py +0 -0
  22. {soothe_client_python-1.0.4 → soothe_client_python-1.0.7}/src/soothe_client/appkit/events.py +0 -0
  23. {soothe_client_python-1.0.4 → soothe_client_python-1.0.7}/src/soothe_client/appkit/managed_client.py +0 -0
  24. {soothe_client_python-1.0.4 → soothe_client_python-1.0.7}/src/soothe_client/appkit/observability.py +0 -0
  25. {soothe_client_python-1.0.4 → soothe_client_python-1.0.7}/src/soothe_client/appkit/query_gate.py +0 -0
  26. {soothe_client_python-1.0.4 → soothe_client_python-1.0.7}/src/soothe_client/appkit/thinking_step.py +0 -0
  27. {soothe_client_python-1.0.4 → soothe_client_python-1.0.7}/src/soothe_client/appkit/turn.py +0 -0
  28. {soothe_client_python-1.0.4 → soothe_client_python-1.0.7}/src/soothe_client/appkit/turn_boundary.py +0 -0
  29. {soothe_client_python-1.0.4 → soothe_client_python-1.0.7}/src/soothe_client/command_client.py +0 -0
  30. {soothe_client_python-1.0.4 → soothe_client_python-1.0.7}/src/soothe_client/errors.py +0 -0
  31. {soothe_client_python-1.0.4 → soothe_client_python-1.0.7}/src/soothe_client/py.typed +0 -0
  32. {soothe_client_python-1.0.4 → soothe_client_python-1.0.7}/src/soothe_client/schemas.py +0 -0
  33. {soothe_client_python-1.0.4 → soothe_client_python-1.0.7}/src/soothe_client/session.py +0 -0
  34. {soothe_client_python-1.0.4 → soothe_client_python-1.0.7}/src/soothe_client/turn_boundary.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: soothe-client-python
3
- Version: 1.0.4
3
+ Version: 1.0.7
4
4
  Summary: WebSocket client + appkit for soothe-daemon (Python)
5
5
  Project-URL: Homepage, https://github.com/mirasoth/soothe-client-python
6
6
  Project-URL: Documentation, https://soothe.readthedocs.io
@@ -33,7 +33,7 @@ from soothe_client.helpers import (
33
33
  check_daemon_status,
34
34
  connected_websocket,
35
35
  fetch_config_section,
36
- fetch_loop_cards,
36
+ fetch_execution_state,
37
37
  fetch_loop_history,
38
38
  fetch_loop_messages,
39
39
  fetch_skills_catalog,
@@ -91,8 +91,8 @@ __all__ = [
91
91
  "request_daemon_shutdown",
92
92
  "fetch_skills_catalog",
93
93
  "fetch_config_section",
94
+ "fetch_execution_state",
94
95
  "fetch_loop_history",
95
- "fetch_loop_cards",
96
96
  "fetch_loop_messages",
97
97
  # Intent hints
98
98
  "TEXT_COMPLETION",
@@ -7,7 +7,7 @@ Primary building blocks for agent UIs and backends:
7
7
  - ``QueryGate`` — one-in-flight query per session
8
8
  - ``EventClassifier`` — stream → deliverable mapping
9
9
  - ``SSEBroadcaster`` — drop-on-full fan-out to subscribers
10
- - ``SessionStore`` — persistence seam (Protocol)
10
+ - ``LoopSessionStore`` — persistence seam (Protocol)
11
11
 
12
12
  Advanced stream/pipeline plumbing lives in submodules
13
13
  (``events``, ``chunk_filter``, ``turn``, ``managed_client``).
@@ -21,6 +21,7 @@ from soothe_client.appkit.attachments import (
21
21
  compact_image_attachment,
22
22
  )
23
23
  from soothe_client.appkit.broadcaster import SSEBroadcaster, SSEEvent
24
+ from soothe_client.appkit.card_projection import CardProjection, parse_card_custom_payload
24
25
  from soothe_client.appkit.classifier import (
25
26
  EVENT_FINAL_REPORT,
26
27
  ChatEventResult,
@@ -29,6 +30,11 @@ from soothe_client.appkit.classifier import (
29
30
  EventClassifier,
30
31
  )
31
32
  from soothe_client.appkit.daemon_session import DaemonSession
33
+ from soothe_client.appkit.loop_session_store import (
34
+ LoopSessionEntry,
35
+ LoopSessionStore,
36
+ SessionMessage,
37
+ )
32
38
  from soothe_client.appkit.pool import (
33
39
  ConnectionPool,
34
40
  ErrPoolExhausted,
@@ -37,7 +43,6 @@ from soothe_client.appkit.pool import (
37
43
  default_pool_config,
38
44
  )
39
45
  from soothe_client.appkit.query_gate import ErrQueryBusy, QueryGate
40
- from soothe_client.appkit.session_store import SessionEntry, SessionMessage, SessionStore
41
46
  from soothe_client.appkit.thinking_step import (
42
47
  DEFAULT_THINKING_STEP_EVENTS,
43
48
  extract_thinking_step,
@@ -72,6 +77,7 @@ __all__ = [
72
77
  "STREAM_CLOSE_FAIL",
73
78
  "STREAM_CLOSE_SOFT_COMPLETE",
74
79
  "Attachment",
80
+ "CardProjection",
75
81
  "ChatEventResult",
76
82
  "ChatEventTerminal",
77
83
  "ClassifierConfig",
@@ -85,6 +91,8 @@ __all__ = [
85
91
  "ErrQueryTimeout",
86
92
  "EventClassifier",
87
93
  "InputOpts",
94
+ "LoopSessionEntry",
95
+ "LoopSessionStore",
88
96
  "OnComplete",
89
97
  "OnError",
90
98
  "PoolConfig",
@@ -92,9 +100,7 @@ __all__ = [
92
100
  "QueryGate",
93
101
  "SSEBroadcaster",
94
102
  "SSEEvent",
95
- "SessionEntry",
96
103
  "SessionMessage",
97
- "SessionStore",
98
104
  "StreamClosePolicy",
99
105
  "TURN_END_IDLE",
100
106
  "TURN_END_STOPPED",
@@ -111,4 +117,5 @@ __all__ = [
111
117
  "extract_thinking_step",
112
118
  "idle_timeout_for_turn",
113
119
  "input_message_for_loop",
120
+ "parse_card_custom_payload",
114
121
  ]
@@ -0,0 +1,10 @@
1
+ """In-memory apply helpers for daemon ``soothe.card.*`` frames."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from soothe_sdk.display.card_wire import CardProjection, parse_card_custom_payload
6
+
7
+ __all__ = [
8
+ "CardProjection",
9
+ "parse_card_custom_payload",
10
+ ]
@@ -431,7 +431,7 @@ class DaemonSession:
431
431
  ):
432
432
  continue
433
433
 
434
- # IG-659 phase 5: drop frames at or before prior turn end seq.
434
+ # IG-616 phase 5: drop frames at or before prior turn end seq.
435
435
  ev_seq = frame_seq(event)
436
436
  if ev_seq is not None and last_end_seq > 0 and ev_seq <= last_end_seq:
437
437
  continue
@@ -526,7 +526,7 @@ class DaemonSession:
526
526
  self.turn_event_stats.filtered_early += 1
527
527
  continue
528
528
 
529
- # Prefer turn_id match when present; else IG-658 progress gate.
529
+ # Prefer turn_id match when present; else IG-615 progress gate.
530
530
  if mode == "custom" and is_turn_end_custom_data(data):
531
531
  data_turn = (
532
532
  frame_turn_id(data if isinstance(data, dict) else None) or ev_turn_id
@@ -623,40 +623,6 @@ class DaemonSession:
623
623
  await self._rpc_client.wait_for_connection_ack(ack_timeout_s=_RPC_HANDSHAKE_TIMEOUT_S)
624
624
  self._rpc_connected = True
625
625
 
626
- async def fetch_loop_cards(self, loop_id: str) -> SimpleNamespace:
627
- """Fetch bound display-card snapshot for a loop."""
628
- lid = str(loop_id or "").strip()
629
- if not lid:
630
- return SimpleNamespace(cards=[], seq=0, success=False)
631
-
632
- async with self._rpc_lock:
633
- await self._ensure_rpc_connected()
634
- try:
635
- resp = await self._rpc_client.request(
636
- "loop_cards_fetch",
637
- {"loop_id": lid},
638
- timeout=30.0,
639
- )
640
- except Exception:
641
- logger.warning("loop_cards_fetch failed for loop %s", lid[:16], exc_info=True)
642
- return SimpleNamespace(cards=[], seq=0, success=False)
643
-
644
- raw_cards = resp.get("cards")
645
- cards = list(raw_cards) if isinstance(raw_cards, list) else []
646
- seq = int(resp.get("seq") or 0)
647
- context_tokens_raw = resp.get("context_tokens")
648
- context_tokens = (
649
- context_tokens_raw
650
- if isinstance(context_tokens_raw, int) and context_tokens_raw >= 0
651
- else 0
652
- )
653
- return SimpleNamespace(
654
- cards=cards,
655
- seq=seq,
656
- context_tokens=context_tokens,
657
- success=True,
658
- )
659
-
660
626
  async def fetch_loop_history(self, loop_id: str) -> SimpleNamespace:
661
627
  """Fetch goal display snapshots plus live card tail."""
662
628
  lid = str(loop_id or "").strip()
@@ -731,34 +697,56 @@ class DaemonSession:
731
697
  values: dict[str, Any] = dict(raw) if isinstance(raw, dict) else {}
732
698
  return SimpleNamespace(values=values)
733
699
 
734
- async def aupdate_loop_state(
735
- self,
736
- loop_id: str,
737
- values: dict[str, Any],
738
- *,
739
- timeout: float = 10.0,
740
- as_node: str | None = None,
741
- ) -> None:
742
- """Merge partial state into the loop on the daemon host."""
700
+ async def fetch_execution_state(self, loop_id: str) -> SimpleNamespace:
701
+ """Fetch execution-progress snapshot (plan, step_index, iteration, status).
702
+
703
+ Returns a namespace with ``plan``, ``step_index``, ``iteration``,
704
+ ``status`` for the loop's bound checkpoint thread. On RPC failure the
705
+ fields degrade gracefully (``step_index=0``, ``iteration=0``,
706
+ ``status=None``, ``plan=None``) so a resume gate can still proceed.
707
+ """
743
708
  lid = str(loop_id or "").strip()
709
+ empty = SimpleNamespace(
710
+ plan=None,
711
+ step_index=0,
712
+ iteration=0,
713
+ status=None,
714
+ loop_id=lid or None,
715
+ success=False,
716
+ )
744
717
  if not lid:
745
- return
718
+ return empty
746
719
 
747
720
  async with self._rpc_lock:
748
721
  await self._ensure_rpc_connected()
749
- from soothe_sdk.wire.protocol import _serialize_for_json
750
-
751
- payload_values = _serialize_for_json(values)
752
- if not isinstance(payload_values, dict):
753
- return
754
- params: dict[str, Any] = {"loop_id": lid, "values": payload_values}
755
- if as_node:
756
- params["as_node"] = as_node
757
- await self._rpc_client.request(
758
- "loop_state_update",
759
- params,
760
- timeout=timeout,
761
- )
722
+ try:
723
+ resp = await self._rpc_client.request(
724
+ "loop_execution_state_fetch",
725
+ {"loop_id": lid},
726
+ timeout=30.0,
727
+ )
728
+ except Exception:
729
+ logger.warning(
730
+ "loop_execution_state_fetch failed for loop %s", lid[:16], exc_info=True
731
+ )
732
+ return empty
733
+
734
+ plan = resp.get("plan")
735
+ step_index_raw = resp.get("step_index")
736
+ step_index = step_index_raw if isinstance(step_index_raw, int) else 0
737
+ iteration_raw = resp.get("iteration")
738
+ iteration = iteration_raw if isinstance(iteration_raw, int) else 0
739
+ status_raw = resp.get("status")
740
+ status = status_raw if isinstance(status_raw, str) else None
741
+ success = bool(resp.get("success", True))
742
+ return SimpleNamespace(
743
+ plan=plan,
744
+ step_index=step_index,
745
+ iteration=iteration,
746
+ status=status,
747
+ loop_id=resp.get("loop_id", lid),
748
+ success=success,
749
+ )
762
750
 
763
751
  async def fetch_conversation_log(
764
752
  self,
@@ -5,7 +5,7 @@ from __future__ import annotations
5
5
  from typing import Any, Protocol, runtime_checkable
6
6
 
7
7
 
8
- class SessionEntry:
8
+ class LoopSessionEntry:
9
9
  """Persisted mapping between an application session id and a daemon loop id."""
10
10
 
11
11
  __slots__ = (
@@ -63,10 +63,10 @@ class SessionMessage:
63
63
 
64
64
 
65
65
  @runtime_checkable
66
- class SessionStore(Protocol):
66
+ class LoopSessionStore(Protocol):
67
67
  """Persistence seam between appkit and the application's storage backend."""
68
68
 
69
- async def get_session(self, session_id: str) -> SessionEntry | None:
69
+ async def get_session(self, session_id: str) -> LoopSessionEntry | None:
70
70
  """Return the persisted entry for ``session_id``, or None."""
71
71
  ...
72
72
 
@@ -97,4 +97,4 @@ class SessionStore(Protocol):
97
97
  ...
98
98
 
99
99
 
100
- __all__ = ["SessionEntry", "SessionMessage", "SessionStore"]
100
+ __all__ = ["LoopSessionEntry", "SessionMessage", "LoopSessionStore"]
@@ -3,7 +3,7 @@
3
3
  Manages a pool of daemon connections, one active per session. Reuses an
4
4
  active connection when still live, otherwise bootstraps a fresh loop or
5
5
  reattaches an existing one. Persistence of session↔loop mappings is
6
- abstracted behind ``SessionStore``.
6
+ abstracted behind ``LoopSessionStore``.
7
7
  """
8
8
 
9
9
  from __future__ import annotations
@@ -15,6 +15,7 @@ from collections.abc import AsyncIterator
15
15
  from dataclasses import dataclass
16
16
  from typing import Any
17
17
 
18
+ from soothe_client.appkit.loop_session_store import LoopSessionStore
18
19
  from soothe_client.appkit.managed_client import (
19
20
  BootstrapFunc,
20
21
  ClientFactory,
@@ -22,7 +23,6 @@ from soothe_client.appkit.managed_client import (
22
23
  default_bootstrap_func,
23
24
  default_client_factory,
24
25
  )
25
- from soothe_client.appkit.session_store import SessionStore
26
26
  from soothe_client.errors import StaleLoopError
27
27
 
28
28
 
@@ -96,7 +96,7 @@ class ConnectionPool:
96
96
  def __init__(
97
97
  self,
98
98
  url: str,
99
- store: SessionStore,
99
+ store: LoopSessionStore,
100
100
  cfg: PoolConfig | None = None,
101
101
  factory: ClientFactory | None = None,
102
102
  ) -> None:
@@ -21,9 +21,9 @@ from typing import Any
21
21
  from soothe_client.appkit.attachments import CompactImageOptions, compact_attachments
22
22
  from soothe_client.appkit.broadcaster import SSEBroadcaster, SSEEvent
23
23
  from soothe_client.appkit.classifier import ChatEventTerminal, EventClassifier
24
+ from soothe_client.appkit.loop_session_store import LoopSessionStore, SessionMessage
24
25
  from soothe_client.appkit.pool import ConnectionPool, PooledConn
25
26
  from soothe_client.appkit.query_gate import QueryGate
26
- from soothe_client.appkit.session_store import SessionMessage, SessionStore
27
27
  from soothe_client.appkit.turn_boundary import TurnBoundary, is_daemon_turn_end_event
28
28
  from soothe_client.intent_hints import validate_loop_input_intent_hint
29
29
 
@@ -135,7 +135,7 @@ class TurnRunner:
135
135
  pool: ConnectionPool,
136
136
  gate: QueryGate,
137
137
  classifier: EventClassifier,
138
- store: SessionStore,
138
+ store: LoopSessionStore,
139
139
  broadcaster: SSEBroadcaster | None,
140
140
  cfg: TurnConfig | None = None,
141
141
  ) -> None:
@@ -428,18 +428,18 @@ async def fetch_loop_history(
428
428
  return await client.loop_history_fetch(lid, timeout=timeout)
429
429
 
430
430
 
431
- async def fetch_loop_cards(
431
+ async def fetch_execution_state(
432
432
  client: WebSocketClient,
433
433
  loop_id: str,
434
434
  *,
435
435
  timeout: float = 30.0,
436
436
  ) -> dict[str, Any]:
437
- """Fetch the daemon's bound display-card snapshot for a loop."""
437
+ """Fetch execution-progress snapshot (plan, step_index, iteration, status)."""
438
438
  lid = str(loop_id or "").strip()
439
439
  if not lid:
440
440
  raise ValueError("loop_id is required")
441
441
  await _ensure_handshake(client, timeout=timeout)
442
- return await client.loop_cards_fetch(lid, timeout=timeout)
442
+ return await client.loop_execution_state_fetch(lid, timeout=timeout)
443
443
 
444
444
 
445
445
  async def fetch_loop_messages(
@@ -480,6 +480,6 @@ __all__ = [
480
480
  "fetch_skills_catalog",
481
481
  "fetch_config_section",
482
482
  "fetch_loop_history",
483
- "fetch_loop_cards",
484
483
  "fetch_loop_messages",
484
+ "fetch_execution_state",
485
485
  ]
@@ -9,8 +9,6 @@ IMAGE_TO_TEXT: Final = "image_to_text"
9
9
  OCR: Final = "ocr"
10
10
  EMBED: Final = "embed"
11
11
 
12
- REMOVED_INTENT_HINTS: frozenset[str] = frozenset({"direct_llm", "quiz", "direct_model"})
13
-
14
12
  # Default deliverable phases for turn-ending replies (excludes plan_direct narration).
15
13
  DEFAULT_DELIVERABLE_PHASES: frozenset[str] = frozenset(
16
14
  {
@@ -22,7 +22,7 @@ __all__ = [
22
22
  "LoopMessagesParams",
23
23
  "LoopStateGetParams",
24
24
  "LoopStateUpdateParams",
25
- "LoopCardsFetchParams",
25
+ "LoopExecutionStateFetchParams",
26
26
  "LoopDetachParams",
27
27
  # Subscription params
28
28
  "SubscribeParams",
@@ -269,16 +269,17 @@ class LoopStateUpdateParams(ParamsBase):
269
269
  as_node: str | None = None
270
270
 
271
271
 
272
- class LoopCardsFetchParams(ParamsBase):
273
- """Params for ``method=loop_cards_fetch``.
272
+ class LoopExecutionStateFetchParams(ParamsBase):
273
+ """Params for ``method=loop_execution_state_fetch``.
274
+
275
+ Returns a focused execution-progress snapshot (plan, step_index,
276
+ iteration, status) for the loop's bound checkpoint thread.
274
277
 
275
278
  Attributes:
276
279
  loop_id: Loop identifier (required).
277
- since: Fetch cards after this sequence.
278
280
  """
279
281
 
280
282
  loop_id: str = Field(..., min_length=1)
281
- since: str | None = None
282
283
 
283
284
 
284
285
  class LoopHistoryFetchParams(ParamsBase):
@@ -9,6 +9,11 @@ from __future__ import annotations
9
9
  from typing import Any, TypeGuard
10
10
 
11
11
  from soothe_sdk.core.events import (
12
+ CARD_CREATED,
13
+ CARD_FINALIZED,
14
+ CARD_REPLAY_BEGIN,
15
+ CARD_REPLAY_END,
16
+ CARD_UPDATED,
12
17
  PLAN_CREATED,
13
18
  STRANGE_LOOP_COMPLETED,
14
19
  STRANGE_LOOP_STEP_COMPLETED,
@@ -28,6 +33,9 @@ _TURN_PROGRESS_CUSTOM_TYPES = frozenset(
28
33
  STRANGE_LOOP_STEP_STARTED,
29
34
  STRANGE_LOOP_STEP_QUEUED,
30
35
  STRANGE_LOOP_STEP_COMPLETED,
36
+ CARD_CREATED,
37
+ CARD_UPDATED,
38
+ CARD_FINALIZED,
31
39
  }
32
40
  )
33
41
 
@@ -35,9 +43,9 @@ _TURN_PROGRESS_CUSTOM_TYPES = frozenset(
35
43
  STALE_TURN_PENDING_TYPES = frozenset(
36
44
  {
37
45
  "connection_ack",
38
- "card.replay_begin",
39
- "card.replay_end",
40
- "card.created",
46
+ CARD_REPLAY_BEGIN,
47
+ CARD_REPLAY_END,
48
+ CARD_CREATED,
41
49
  "complete",
42
50
  }
43
51
  )
@@ -1066,14 +1066,6 @@ class WebSocketClient:
1066
1066
  timeout=timeout,
1067
1067
  )
1068
1068
 
1069
- async def loop_cards_fetch(self, loop_id: str, *, timeout: float = 30.0) -> dict[str, Any]:
1070
- """Fetch bound display-card snapshot (``loop_cards_fetch``)."""
1071
- return await self.request(
1072
- "loop_cards_fetch",
1073
- {"loop_id": loop_id},
1074
- timeout=timeout,
1075
- )
1076
-
1077
1069
  async def loop_messages(
1078
1070
  self,
1079
1071
  loop_id: str,
@@ -1103,6 +1095,20 @@ class WebSocketClient:
1103
1095
  timeout=timeout,
1104
1096
  )
1105
1097
 
1098
+ async def loop_execution_state_fetch(
1099
+ self, loop_id: str, *, timeout: float = 30.0
1100
+ ) -> dict[str, Any]:
1101
+ """Fetch execution-progress snapshot (``loop_execution_state_fetch``).
1102
+
1103
+ Returns ``{plan, step_index, iteration, status}`` for the loop's bound
1104
+ checkpoint thread.
1105
+ """
1106
+ return await self.request(
1107
+ "loop_execution_state_fetch",
1108
+ {"loop_id": loop_id},
1109
+ timeout=timeout,
1110
+ )
1111
+
1106
1112
  async def loop_state_update(
1107
1113
  self,
1108
1114
  loop_id: str,