harness-sdk-python 0.13.1__tar.gz → 0.13.3__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 (31) hide show
  1. {harness_sdk_python-0.13.1 → harness_sdk_python-0.13.3}/PKG-INFO +3 -3
  2. {harness_sdk_python-0.13.1 → harness_sdk_python-0.13.3}/examples/runs_app.py +7 -7
  3. {harness_sdk_python-0.13.1 → harness_sdk_python-0.13.3}/pyproject.toml +2 -2
  4. {harness_sdk_python-0.13.1 → harness_sdk_python-0.13.3}/src/harness_sdk/run_manager.py +55 -66
  5. {harness_sdk_python-0.13.1 → harness_sdk_python-0.13.3}/tests/run_helpers.py +9 -9
  6. harness_sdk_python-0.13.1/tests/test_ack_visibility.py → harness_sdk_python-0.13.3/tests/test_applied_visibility.py +11 -11
  7. {harness_sdk_python-0.13.1 → harness_sdk_python-0.13.3}/tests/test_batches.py +15 -21
  8. {harness_sdk_python-0.13.1 → harness_sdk_python-0.13.3}/tests/test_branch_anchor.py +10 -10
  9. {harness_sdk_python-0.13.1 → harness_sdk_python-0.13.3}/tests/test_dispatching.py +21 -21
  10. {harness_sdk_python-0.13.1 → harness_sdk_python-0.13.3}/tests/test_edit_dispatched.py +8 -8
  11. {harness_sdk_python-0.13.1 → harness_sdk_python-0.13.3}/tests/test_edit_reload.py +6 -6
  12. {harness_sdk_python-0.13.1 → harness_sdk_python-0.13.3}/tests/test_enqueue.py +18 -18
  13. {harness_sdk_python-0.13.1 → harness_sdk_python-0.13.3}/tests/test_input_required.py +20 -20
  14. {harness_sdk_python-0.13.1 → harness_sdk_python-0.13.3}/tests/test_meta.py +16 -16
  15. {harness_sdk_python-0.13.1 → harness_sdk_python-0.13.3}/tests/test_outcomes.py +9 -9
  16. {harness_sdk_python-0.13.1 → harness_sdk_python-0.13.3}/tests/test_placement.py +1 -1
  17. {harness_sdk_python-0.13.1 → harness_sdk_python-0.13.3}/tests/test_prepare_hooks.py +4 -4
  18. {harness_sdk_python-0.13.1 → harness_sdk_python-0.13.3}/tests/test_restore.py +4 -4
  19. {harness_sdk_python-0.13.1 → harness_sdk_python-0.13.3}/tests/test_rewind_during_run.py +6 -6
  20. {harness_sdk_python-0.13.1 → harness_sdk_python-0.13.3}/tests/test_settle.py +14 -14
  21. {harness_sdk_python-0.13.1 → harness_sdk_python-0.13.3}/tests/test_steer.py +11 -11
  22. {harness_sdk_python-0.13.1 → harness_sdk_python-0.13.3}/tests/test_stop_continue.py +22 -22
  23. {harness_sdk_python-0.13.1 → harness_sdk_python-0.13.3}/tests/test_stopping_window.py +22 -24
  24. {harness_sdk_python-0.13.1 → harness_sdk_python-0.13.3}/.gitignore +0 -0
  25. {harness_sdk_python-0.13.1 → harness_sdk_python-0.13.3}/README.md +0 -0
  26. {harness_sdk_python-0.13.1 → harness_sdk_python-0.13.3}/examples/__init__.py +0 -0
  27. {harness_sdk_python-0.13.1 → harness_sdk_python-0.13.3}/src/harness_sdk/__init__.py +0 -0
  28. {harness_sdk_python-0.13.1 → harness_sdk_python-0.13.3}/src/harness_sdk/fenced_postgres.py +0 -0
  29. {harness_sdk_python-0.13.1 → harness_sdk_python-0.13.3}/src/harness_sdk/linear_thread.py +0 -0
  30. {harness_sdk_python-0.13.1 → harness_sdk_python-0.13.3}/tests/test_fenced_postgres.py +0 -0
  31. {harness_sdk_python-0.13.1 → harness_sdk_python-0.13.3}/tests/test_linear_thread.py +0 -0
@@ -1,11 +1,11 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: harness-sdk-python
3
- Version: 0.13.1
3
+ Version: 0.13.3
4
4
  Summary: RunManager: the harness-sdk runs subsystem for Python Statewire hosts
5
5
  Project-URL: Repository, https://github.com/assistant-ui/harness-sdk
6
6
  License-Expression: MIT
7
7
  Requires-Python: <4.0,>=3.11
8
- Requires-Dist: statewire<0.7,>=0.6.0
8
+ Requires-Dist: statewire<0.8,>=0.7.0
9
9
  Provides-Extra: deepagents
10
10
  Requires-Dist: deepagents>=0.6.12; extra == 'deepagents'
11
11
  Requires-Dist: langchain-core>=0.3; extra == 'deepagents'
@@ -107,7 +107,7 @@ class RunsHost(Statewire):
107
107
  "content": user_text(message),
108
108
  }
109
109
  )
110
- ctx.ack()
110
+ ctx.applied()
111
111
  outcome = await self.stream_turn(ctx)
112
112
  if outcome is not None:
113
113
  return outcome
@@ -158,11 +158,11 @@ class RunsHost(Statewire):
158
158
 
159
159
  @command("run/enqueue")
160
160
  async def run_enqueue(self, params, *, ctx):
161
- return await self.runs.enqueue(params, ack=ctx.ack)
161
+ return await self.runs.enqueue(params, applied=ctx.applied)
162
162
 
163
163
  @command("run/steer")
164
164
  async def run_steer(self, params, *, ctx):
165
- return await self.runs.steer(params, ack=ctx.ack)
165
+ return await self.runs.steer(params, applied=ctx.applied)
166
166
 
167
167
  @command("run/dequeue")
168
168
  async def run_dequeue(self, params):
@@ -170,19 +170,19 @@ class RunsHost(Statewire):
170
170
 
171
171
  @command("run/edit")
172
172
  async def run_edit(self, params, *, ctx):
173
- return await self.runs.edit(params, ack=ctx.ack)
173
+ return await self.runs.edit(params, applied=ctx.applied)
174
174
 
175
175
  @command("run/reload")
176
176
  async def run_reload(self, params, *, ctx):
177
- return await self.runs.reload(params, ack=ctx.ack)
177
+ return await self.runs.reload(params, applied=ctx.applied)
178
178
 
179
179
  @command("run/stop")
180
180
  async def run_stop(self, params=None, *, ctx):
181
- return await self.runs.stop(params, ack=ctx.ack)
181
+ return await self.runs.stop(params, applied=ctx.applied)
182
182
 
183
183
  @command("run/continue")
184
184
  async def run_continue(self, params=None, *, ctx):
185
- return await self.runs.continue_run(ack=ctx.ack)
185
+ return await self.runs.continue_run(applied=ctx.applied)
186
186
 
187
187
  @command("run/input")
188
188
  async def run_input(self, params):
@@ -1,11 +1,11 @@
1
1
  [project]
2
2
  name = "harness-sdk-python"
3
- version = "0.13.1"
3
+ version = "0.13.3"
4
4
  description = "RunManager: the harness-sdk runs subsystem for Python Statewire hosts"
5
5
  readme = "README.md"
6
6
  license = "MIT"
7
7
  requires-python = ">=3.11,<4.0"
8
- dependencies = ["statewire>=0.6.0,<0.7"]
8
+ dependencies = ["statewire>=0.7.0,<0.8"]
9
9
 
10
10
  [project.optional-dependencies]
11
11
  postgres = ["langgraph-checkpoint-postgres>=2.0.0"]
@@ -10,23 +10,23 @@ dispatchable send appends straight into ``dispatching`` — it never transits a
10
10
  queue lane — and everything staged there by the end of the drain dispatches
11
11
  as one run; in error/stop the dispatch is the continuation
12
12
  (``error-continue`` / ``stop-continue``) carrying the send's messages. Every
13
- initiator acks and settles accepted with its state-visible application
14
- sends with their insert (lane or ``dispatching``), edit/reload/continue at
15
- their dispatch; ``run/stop`` awaits a future the drain resolves once the
16
- in-flight run has ended.
13
+ initiator applies and settles accepted with its state-visible application:
14
+ the verdict certifies the admission write — sends with their insert (lane
15
+ or ``dispatching``), mid-run edit/reload with the stopping window,
16
+ ``run/stop`` with ``stopping`` — never future execution.
17
17
 
18
- ``entry["dispatching"]`` is the dispatch record (absent between an ack and
18
+ ``entry["dispatching"]`` is the dispatch record (absent between an applied and
19
19
  the next dispatch or take): the payload the run callback receives.
20
- ``ctx.ack()`` clears it; ``steering.take()`` may refill it — at most one
21
- unacked batch ever exists. Settle consumes the delivered batch, acked or not;
20
+ ``ctx.applied()`` clears it; ``steering.take()`` may refill it — at most one
21
+ unapplied batch ever exists. Settle consumes the delivered batch, applied or not;
22
22
  steer entries still undelivered in the lane stay queued for the next run.
23
23
 
24
24
  A mid-run stop or rewind opens a stopping window on the entry:
25
25
  ``stopping: {reason}`` plus, for rewinds, the staged dispatch as
26
26
  ``nextDispatch`` — written in the same envelope as the opener's pending
27
27
  answer. On run end one envelope promotes ``nextDispatch`` into
28
- ``dispatching`` and clears the window; a later stop/rewind supersedes the
29
- staged one, which settles rejected (``superseded``).
28
+ ``dispatching`` and clears the window; a later stop/rewind replaces the
29
+ staged dispatch, whose command already settled at its admission write.
30
30
  """
31
31
 
32
32
  import asyncio
@@ -87,7 +87,7 @@ class _Send:
87
87
  message_id: str
88
88
  source_meta: dict[str, Any] | None
89
89
  meta: Any
90
- ack: Callable[[], None]
90
+ applied: Callable[[], None]
91
91
  run_id: str = ""
92
92
  anchor: Any = _ABSENT
93
93
  anchor_meta: dict[str, Any] | None = None
@@ -107,7 +107,7 @@ class _Edit:
107
107
  source_meta: dict[str, Any]
108
108
  message: dict[str, Any]
109
109
  meta: Any
110
- ack: Callable[[], None]
110
+ applied: Callable[[], None]
111
111
  run_id: str
112
112
  future: "asyncio.Future[Any]" = field(default_factory=_future)
113
113
 
@@ -116,7 +116,7 @@ class _Edit:
116
116
  class _Reload:
117
117
  source_meta: dict[str, Any]
118
118
  meta: Any
119
- ack: Callable[[], None]
119
+ applied: Callable[[], None]
120
120
  run_id: str
121
121
  future: "asyncio.Future[Any]" = field(default_factory=_future)
122
122
 
@@ -145,11 +145,11 @@ class _Rewind:
145
145
  type: str
146
146
  messages: list[dict[str, Any]]
147
147
  rollback_to: Any
148
- ack: Callable[[], None]
148
+ applied: Callable[[], None]
149
149
  future: "asyncio.Future[Any]"
150
150
  run_id: str
151
151
  root_meta: Any = None
152
- acked: bool = False
152
+ applied_sent: bool = False
153
153
 
154
154
 
155
155
  @dataclass
@@ -209,7 +209,7 @@ class RunManager:
209
209
  self._dispatch_record: dict[str, Any] | None = None
210
210
  self._intake: list[Any] = []
211
211
  self._outcome: Any = None
212
- self._staged_stops: list["asyncio.Future[Any]"] = []
212
+ self._stop_staged = False
213
213
  self._stop_reason: str | None = None
214
214
  self._staged_rewind: _Rewind | None = None
215
215
  self._dispatching: list[tuple[str, dict[str, Any]]] = []
@@ -354,10 +354,7 @@ class RunManager:
354
354
  for entry in entries:
355
355
  self._apply(entry, fx)
356
356
  if self._task is None:
357
- for stop in self._staged_stops:
358
- if not stop.done():
359
- stop.set_result(None)
360
- self._staged_stops.clear()
357
+ self._stop_staged = False
361
358
  if isinstance(outcome, RunManager.Complete):
362
359
  self._dispatched_ids = ()
363
360
  if self._staged_rewind is not None:
@@ -376,21 +373,19 @@ class RunManager:
376
373
  rollback_to=rewind.rollback_to,
377
374
  root_meta=rewind.root_meta,
378
375
  )
379
- if not rewind.acked:
380
- rewind.ack()
381
- if not rewind.future.done():
382
- rewind.future.set_result(None)
376
+ if not rewind.applied_sent:
377
+ rewind.applied()
383
378
  elif outcome is not None:
384
379
  self._settle_outcome(outcome)
385
380
  else:
386
381
  self._idle_action(fx)
387
382
  if self._task is not None and (
388
- self._staged_stops or self._staged_rewind is not None
383
+ self._stop_staged or self._staged_rewind is not None
389
384
  ):
390
385
  assert self._ctx is not None
391
386
  self._ctx.stop_requested.set()
392
- for future, ack, result in fx.staged_sends:
393
- ack()
387
+ for future, applied, result in fx.staged_sends:
388
+ applied()
394
389
  if not future.done():
395
390
  future.set_result(result)
396
391
  for future in fx.continues:
@@ -401,12 +396,14 @@ class RunManager:
401
396
 
402
397
  def _apply(self, entry: Any, fx: _Effects) -> None:
403
398
  if isinstance(entry, _Stop):
404
- self._staged_stops.append(entry.future)
405
399
  if self._task is not None:
400
+ self._stop_staged = True
406
401
  if self._staged_rewind is not None:
407
- self._supersede()
402
+ self._staged_rewind = None
408
403
  self._entry().pop("nextDispatch", None)
409
404
  self._entry()["stopping"] = {"reason": "stop"}
405
+ if not entry.future.done():
406
+ entry.future.set_result(None)
410
407
  return
411
408
  if isinstance(entry, _Continue):
412
409
  if not fx.continue_requested:
@@ -604,11 +601,11 @@ class RunManager:
604
601
  entry.pop("dispatching", None)
605
602
  entry.pop("stopping", None)
606
603
 
607
- def _ack(self) -> None:
604
+ def _applied(self) -> None:
608
605
  entry = self._entry()
609
606
  record = plain(entry.get("dispatching"))
610
607
  if record is None:
611
- raise RuntimeError("ack() with no unacked batch")
608
+ raise RuntimeError("applied() with no unapplied batch")
612
609
  self._dispatching = []
613
610
  entry.pop("dispatching", None)
614
611
 
@@ -683,17 +680,7 @@ class RunManager:
683
680
  return True
684
681
  return False
685
682
 
686
- def _supersede(self) -> None:
687
- rewind, self._staged_rewind = self._staged_rewind, None
688
- assert rewind is not None
689
- if not rewind.future.done():
690
- rewind.future.set_exception(
691
- _reject("superseded", "a later stop/rewind replaced this command")
692
- )
693
-
694
683
  def _stage_rewind(self, rewind: _Rewind) -> None:
695
- if self._staged_rewind is not None:
696
- self._supersede()
697
684
  self._staged_rewind = rewind
698
685
  if self._task is not None:
699
686
  entry = self._entry()
@@ -704,8 +691,10 @@ class RunManager:
704
691
  rollback_to=rewind.rollback_to,
705
692
  root_meta=rewind.root_meta,
706
693
  )
707
- rewind.ack()
708
- rewind.acked = True
694
+ rewind.applied()
695
+ rewind.applied_sent = True
696
+ if not rewind.future.done():
697
+ rewind.future.set_result(None)
709
698
 
710
699
  def _freeze(self, message: str, payload: Any = None) -> None:
711
700
  self._set_status("error")
@@ -952,7 +941,7 @@ class RunManager:
952
941
  if live_run_id is not None and e.anchor is not _ABSENT
953
942
  else None
954
943
  )
955
- fx.staged_sends.append((e.future, e.ack, result))
944
+ fx.staged_sends.append((e.future, e.applied, result))
956
945
  return _PARKED
957
946
 
958
947
  def _apply_move(self, e: _Send, fx: _Effects) -> Any:
@@ -992,7 +981,7 @@ class RunManager:
992
981
  "message-edit",
993
982
  [self._stamped(e.message, e.meta)],
994
983
  e.source_meta["parentId"],
995
- e.ack,
984
+ e.applied,
996
985
  e.future,
997
986
  e.run_id,
998
987
  )
@@ -1047,7 +1036,7 @@ class RunManager:
1047
1036
  "message-edit",
1048
1037
  [self._stamped(e.message, e.meta)],
1049
1038
  e.source_meta["parentId"],
1050
- e.ack,
1039
+ e.applied,
1051
1040
  e.future,
1052
1041
  e.run_id,
1053
1042
  )
@@ -1060,7 +1049,7 @@ class RunManager:
1060
1049
  "message-reload",
1061
1050
  [],
1062
1051
  e.source_meta["parentId"],
1063
- e.ack,
1052
+ e.applied,
1064
1053
  e.future,
1065
1054
  e.run_id,
1066
1055
  root_meta=e.meta,
@@ -1075,7 +1064,7 @@ class RunManager:
1075
1064
  lane: str,
1076
1065
  params: Any,
1077
1066
  meta: Any,
1078
- ack: Callable[[], None],
1067
+ applied: Callable[[], None],
1079
1068
  ) -> Any:
1080
1069
  if not isinstance(params, dict):
1081
1070
  raise _reject("invalid-message", "params must be an object")
@@ -1111,7 +1100,7 @@ class RunManager:
1111
1100
  message_id,
1112
1101
  None,
1113
1102
  None,
1114
- ack,
1103
+ applied,
1115
1104
  run_id=run_id,
1116
1105
  anchor=anchor,
1117
1106
  anchor_meta=anchor_meta,
@@ -1128,7 +1117,7 @@ class RunManager:
1128
1117
  message["id"],
1129
1118
  source_meta,
1130
1119
  meta,
1131
- ack,
1120
+ applied,
1132
1121
  run_id=run_id,
1133
1122
  anchor=anchor,
1134
1123
  anchor_meta=anchor_meta,
@@ -1137,14 +1126,14 @@ class RunManager:
1137
1126
  )
1138
1127
 
1139
1128
  async def enqueue(
1140
- self, params: Any, *, meta: Any = None, ack: Callable[[], None]
1129
+ self, params: Any, *, meta: Any = None, applied: Callable[[], None]
1141
1130
  ) -> Any:
1142
- return await self._send("queue", params, meta, ack)
1131
+ return await self._send("queue", params, meta, applied)
1143
1132
 
1144
1133
  async def steer(
1145
- self, params: Any, *, meta: Any = None, ack: Callable[[], None]
1134
+ self, params: Any, *, meta: Any = None, applied: Callable[[], None]
1146
1135
  ) -> Any:
1147
- return await self._send("steerQueue", params, meta, ack)
1136
+ return await self._send("steerQueue", params, meta, applied)
1148
1137
 
1149
1138
  async def dequeue(self, params: Any) -> None:
1150
1139
  message_id = params.get("messageId") if isinstance(params, dict) else None
@@ -1163,7 +1152,7 @@ class RunManager:
1163
1152
  raise _reject("capability-missing", "the rewind capability is not enabled")
1164
1153
 
1165
1154
  async def edit(
1166
- self, params: Any, *, meta: Any = None, ack: Callable[[], None]
1155
+ self, params: Any, *, meta: Any = None, applied: Callable[[], None]
1167
1156
  ) -> Any:
1168
1157
  self._check_rewind_gate("run/edit")
1169
1158
  run_id = _run_id_of(params)
@@ -1187,11 +1176,11 @@ class RunManager:
1187
1176
  ):
1188
1177
  raise _reject("duplicate-id", f"message id {message['id']} is already used")
1189
1178
  return await self._stage(
1190
- _Edit(source_id, source_meta, message, meta, ack, run_id)
1179
+ _Edit(source_id, source_meta, message, meta, applied, run_id)
1191
1180
  )
1192
1181
 
1193
1182
  async def reload(
1194
- self, params: Any, *, meta: Any = None, ack: Callable[[], None]
1183
+ self, params: Any, *, meta: Any = None, applied: Callable[[], None]
1195
1184
  ) -> Any:
1196
1185
  self._check_rewind_gate("run/reload")
1197
1186
  run_id = _run_id_of(params)
@@ -1215,9 +1204,9 @@ class RunManager:
1215
1204
  "capability-missing",
1216
1205
  "the assistant-continuation capability is not enabled",
1217
1206
  )
1218
- return await self._stage(_Reload(source_meta, meta, ack, run_id))
1207
+ return await self._stage(_Reload(source_meta, meta, applied, run_id))
1219
1208
 
1220
- async def stop(self, params: Any = None, *, ack: Callable[[], None]) -> Any:
1209
+ async def stop(self, params: Any = None, *, applied: Callable[[], None]) -> Any:
1221
1210
  if params is not None and not isinstance(params, dict):
1222
1211
  raise _reject("invalid-message", "params must be an object")
1223
1212
  reason = (params or {}).get("reason", _ABSENT)
@@ -1235,7 +1224,7 @@ class RunManager:
1235
1224
  self._stop_reason = reason
1236
1225
  entry = _Stop()
1237
1226
  self._intake.append(entry)
1238
- ack()
1227
+ applied()
1239
1228
  self._schedule(self._drain)
1240
1229
  return await entry.future
1241
1230
 
@@ -1285,7 +1274,7 @@ class RunManager:
1285
1274
  response = self._validated_response(request["type"], params["response"])
1286
1275
  return await self._stage(_Input(request_id, response, meta))
1287
1276
 
1288
- async def continue_run(self, *, meta: Any = None, ack: Callable[[], None]) -> Any:
1277
+ async def continue_run(self, *, meta: Any = None, applied: Callable[[], None]) -> Any:
1289
1278
  status = self._status()
1290
1279
  if status not in ("error", "stopped"):
1291
1280
  raise _reject("wrong-state", f"run/continue is rejected in {status}")
@@ -1298,7 +1287,7 @@ class RunManager:
1298
1287
  )
1299
1288
  entry = _Continue(meta)
1300
1289
  self._intake.append(entry)
1301
- ack()
1290
+ applied()
1302
1291
  self._schedule(self._drain)
1303
1292
  return await entry.future
1304
1293
 
@@ -1351,12 +1340,12 @@ class RunManager:
1351
1340
  _ctx: "RunManager.RunContext"
1352
1341
 
1353
1342
  def take(self) -> tuple[dict[str, Any], ...]:
1354
- """Drain the steer lane into a fresh dispatch record; requires the current batch to be acked."""
1343
+ """Drain the steer lane into a fresh dispatch record; requires the current batch to be applied."""
1355
1344
  self._ctx._ensure_active()
1356
1345
  manager = self._ctx._manager
1357
1346
  entry = manager._entry()
1358
1347
  if plain(entry.get("dispatching")) is not None:
1359
- raise RuntimeError("take before the current batch is acked")
1348
+ raise RuntimeError("take before the current batch is applied")
1360
1349
  items = manager._lane_items("steerQueue")
1361
1350
  if not items:
1362
1351
  return ()
@@ -1421,10 +1410,10 @@ class RunManager:
1421
1410
  if self._manager._ctx is not self:
1422
1411
  raise RuntimeError("this run has already settled")
1423
1412
 
1424
- def ack(self) -> None:
1425
- """Ack the current batch: clears the dispatch record."""
1413
+ def applied(self) -> None:
1414
+ """Applied the current batch: clears the dispatch record."""
1426
1415
  self._ensure_active()
1427
- self._manager._ack()
1416
+ self._manager._applied()
1428
1417
 
1429
1418
  def set_recovery_state(self, value: Any) -> None:
1430
1419
  self._ensure_active()
@@ -16,8 +16,8 @@ class Call:
16
16
  ctx: RunManager.RunContext
17
17
  outcome: "asyncio.Future[Any]"
18
18
 
19
- def ack(self) -> None:
20
- self.ctx.ack()
19
+ def applied(self) -> None:
20
+ self.ctx.applied()
21
21
 
22
22
  def finish(self, outcome: Any) -> None:
23
23
  self.outcome.set_result(outcome)
@@ -87,11 +87,11 @@ def make_host(
87
87
 
88
88
  @command("run/enqueue")
89
89
  async def run_enqueue(self, params, *, ctx):
90
- return await self.runs.enqueue(params, meta=_meta(params), ack=ctx.ack)
90
+ return await self.runs.enqueue(params, meta=_meta(params), applied=ctx.applied)
91
91
 
92
92
  @command("run/steer")
93
93
  async def run_steer(self, params, *, ctx):
94
- return await self.runs.steer(params, meta=_meta(params), ack=ctx.ack)
94
+ return await self.runs.steer(params, meta=_meta(params), applied=ctx.applied)
95
95
 
96
96
  @command("run/dequeue")
97
97
  async def run_dequeue(self, params):
@@ -99,19 +99,19 @@ def make_host(
99
99
 
100
100
  @command("run/edit")
101
101
  async def run_edit(self, params, *, ctx):
102
- return await self.runs.edit(params, meta=_meta(params), ack=ctx.ack)
102
+ return await self.runs.edit(params, meta=_meta(params), applied=ctx.applied)
103
103
 
104
104
  @command("run/reload")
105
105
  async def run_reload(self, params, *, ctx):
106
- return await self.runs.reload(params, meta=_meta(params), ack=ctx.ack)
106
+ return await self.runs.reload(params, meta=_meta(params), applied=ctx.applied)
107
107
 
108
108
  @command("run/stop")
109
109
  async def run_stop(self, params=None, *, ctx):
110
- return await self.runs.stop(params, ack=ctx.ack)
110
+ return await self.runs.stop(params, applied=ctx.applied)
111
111
 
112
112
  @command("run/continue")
113
113
  async def run_continue(self, params=None, *, ctx):
114
- return await self.runs.continue_run(meta=_meta(params), ack=ctx.ack)
114
+ return await self.runs.continue_run(meta=_meta(params), applied=ctx.applied)
115
115
 
116
116
  @command("run/input")
117
117
  async def run_input(self, params):
@@ -205,7 +205,7 @@ class RunDriver:
205
205
  return await self.res(self._seq)
206
206
 
207
207
  async def res(self, seq: int, *, terminal: bool = True) -> dict[str, Any]:
208
- # A payload-less accept rides only the ack; rejections and late
208
+ # A payload-less accept rides only the legacy ack; rejections and late
209
209
  # settles ride res.
210
210
  while True:
211
211
  entries = self._res.get(seq, [])
@@ -1,8 +1,8 @@
1
- """Contract: acks ride visibility. A send's covering ack only rides an
1
+ """Contract: applied rides visibility. A send's covering applied only rides an
2
2
  envelope whose state carries the command's effect — the item in a published
3
3
  queue lane or in ``dispatching``. An idle send's insert lands in
4
- ``dispatching`` directly, so its ack rides the dispatch envelope and the queue
5
- lane never holds the item; a rewind's ack rides its dispatch the same way."""
4
+ ``dispatching`` directly, so its applied rides the dispatch envelope and the queue
5
+ lane never holds the item; a rewind's applied rides its dispatch the same way."""
6
6
 
7
7
  import copy
8
8
 
@@ -35,7 +35,7 @@ def assert_never_queued(drv, message_id):
35
35
  assert message_id not in queue_ids(state, "queue")
36
36
 
37
37
 
38
- async def test_idle_send_acks_with_its_dispatching_insert():
38
+ async def test_idle_send_applies_with_its_dispatching_insert():
39
39
  script = Script()
40
40
  async with run_host(script) as (drv, host):
41
41
  seq = drv.post("run/enqueue", add("m1"))
@@ -44,23 +44,23 @@ async def test_idle_send_acks_with_its_dispatching_insert():
44
44
  assert dispatching_ids(covered_state(drv, seq)) == ["m1"]
45
45
  assert_never_queued(drv, "m1")
46
46
  host.live.state["messages"] = [msg("m1")]
47
- call.ack()
47
+ call.applied()
48
48
  call.finish(RunManager.Complete())
49
49
 
50
50
 
51
- async def test_parked_send_acks_with_its_queue_insert():
51
+ async def test_parked_send_applies_with_its_queue_insert():
52
52
  script = Script()
53
53
  async with run_host(script) as (drv, host):
54
54
  drv.post("run/enqueue", add("m1"))
55
55
  call = await script.next_call()
56
- call.ack()
56
+ call.applied()
57
57
  res = await drv.command("run/enqueue", add("m2"))
58
58
  assert res["type"] == "accepted"
59
59
  assert "m2" in queue_ids(covered_state(drv, res["seq"]), "queue")
60
60
  call.finish(RunManager.Complete())
61
61
 
62
62
 
63
- async def test_unacked_run_end_consumes_the_direct_dispatch():
63
+ async def test_unapplied_run_end_consumes_the_direct_dispatch():
64
64
  script = Script()
65
65
  async with run_host(script) as (drv, host):
66
66
  seq = drv.post("run/enqueue", add("m1"))
@@ -72,7 +72,7 @@ async def test_unacked_run_end_consumes_the_direct_dispatch():
72
72
  assert queue_ids(drv.replica, "queue") == []
73
73
 
74
74
 
75
- async def test_unacked_run_end_consumes_the_direct_steer_send():
75
+ async def test_unapplied_run_end_consumes_the_direct_steer_send():
76
76
  script = Script()
77
77
  async with run_host(script) as (drv, host):
78
78
  seq = drv.post("run/steer", add("s1"))
@@ -85,7 +85,7 @@ async def test_unacked_run_end_consumes_the_direct_steer_send():
85
85
  assert queue_ids(drv.replica, "queue") == []
86
86
 
87
87
 
88
- async def test_rewind_edit_acks_with_its_dispatch():
88
+ async def test_rewind_edit_applies_with_its_dispatch():
89
89
  script = Script()
90
90
  script.thread["u1"] = {"parentId": None, "role": "user", "isLeaf": True}
91
91
  async with run_host(script, capabilities=("rewind",)) as (drv, host):
@@ -95,6 +95,6 @@ async def test_rewind_edit_acks_with_its_dispatch():
95
95
  await drv.wait_status("running")
96
96
  assert dispatching_ids(covered_state(drv, seq)) == ["u2"]
97
97
  host.live.state["messages"] = [msg("u2")]
98
- rerun.ack()
98
+ rerun.applied()
99
99
  rerun.finish(RunManager.Complete())
100
100
  assert (await drv.res(seq))["type"] == "accepted"
@@ -25,7 +25,7 @@ async def test_multi_steer_batch_places_all_and_dispatches_once():
25
25
  assert [m["id"] for m in call.ctx.messages] == ["s1", "s2", "s3"]
26
26
  script.no_call()
27
27
  assert queue_ids(drv.replica, "steerQueue") == []
28
- call.ack()
28
+ call.applied()
29
29
  call.finish(RunManager.Complete())
30
30
  for offset in range(3):
31
31
  assert (await drv.res(first + offset))["type"] == "accepted"
@@ -45,7 +45,7 @@ async def test_same_tick_sends_dispatch_as_one_run():
45
45
  merged = await drv.res(first + 1)
46
46
  assert merged["type"] == "accepted"
47
47
  assert merged["payload"] == {"runId": mint["runId"]}
48
- call.ack()
48
+ call.applied()
49
49
  call.finish(RunManager.Complete())
50
50
 
51
51
 
@@ -54,35 +54,29 @@ async def test_steer_and_stop_in_one_batch_nets_to_stop():
54
54
  async with run_host(script) as (drv, host):
55
55
  drv.post("run/enqueue", add("m1"))
56
56
  call = await script.next_call()
57
- call.ack()
57
+ call.applied()
58
58
  first = drv.batch(
59
59
  [("run/steer", add("s1")), ("run/stop", {"runId": await drv.run_id()})]
60
60
  )
61
61
  assert (await drv.res(first))["type"] == "accepted"
62
- assert (await drv.res(first + 1, terminal=False))["type"] == "pending"
62
+ assert (await drv.res(first + 1))["type"] == "accepted"
63
63
  await asyncio.wait_for(call.ctx.stop_requested.wait(), 5)
64
64
  call.finish(RunManager.Stop(dispatch_queue=False))
65
- assert (await drv.res(first + 1))["type"] == "accepted"
66
65
  await drv.wait_status("stopped")
67
66
  assert queue_ids(drv.replica, "steerQueue") == ["s1"]
68
67
  script.no_call()
69
68
 
70
69
 
71
- async def test_stop_settles_only_after_the_run_task_ends():
70
+ async def test_stop_settles_at_the_stopping_write():
72
71
  script = Script()
73
72
  async with run_host(script) as (drv, host):
74
73
  drv.post("run/enqueue", add("m1"))
75
74
  call = await script.next_call()
76
- call.ack()
77
- pending = await drv.stop(terminal=False)
78
- assert pending["type"] == "pending"
79
- for _ in range(5):
80
- await asyncio.sleep(0)
81
- assert all(
82
- rsp["type"] == "pending" for rsp in drv._res.get(pending["seq"], [])
83
- )
75
+ call.applied()
76
+ settled = await drv.stop()
77
+ assert settled["type"] == "accepted"
78
+ assert drv.replica["status"] == "running"
84
79
  call.finish(RunManager.Stop(dispatch_queue=False))
85
- assert (await drv.res(pending["seq"]))["type"] == "accepted"
86
80
  await drv.wait_status("stopped")
87
81
 
88
82
 
@@ -110,10 +104,10 @@ async def test_handler_tail_staging_decides_in_its_own_envelope():
110
104
  )
111
105
  )
112
106
  # The decide ran outside the batch segment: its ops flush in an
113
- # envelope of their own, carrying the send's covering ack.
107
+ # envelope of their own, carrying the send's covering applied.
114
108
  assert dispatch_env.get("ack", 0) >= seq
115
109
  assert (await drv.res(seq))["type"] == "accepted"
116
- call.ack()
110
+ call.applied()
117
111
 
118
112
 
119
113
  async def test_rewind_outranks_send_and_continue_in_one_batch():
@@ -135,7 +129,7 @@ async def test_rewind_outranks_send_and_continue_in_one_batch():
135
129
  "isLeaf": True,
136
130
  "onActiveBranch": True,
137
131
  }
138
- call.ack()
132
+ call.applied()
139
133
  call.fail(RuntimeError("boom"))
140
134
  await drv.wait_status("error")
141
135
  first = drv.batch(
@@ -153,7 +147,7 @@ async def test_rewind_outranks_send_and_continue_in_one_batch():
153
147
  assert (await drv.res(first + 1))["type"] == "accepted"
154
148
  assert (await drv.res(first + 2))["type"] == "accepted"
155
149
  assert queue_ids(drv.replica, "queue") == ["q1"]
156
- rerun.ack()
150
+ rerun.applied()
157
151
  rerun.finish(RunManager.Complete())
158
152
 
159
153
 
@@ -162,7 +156,7 @@ async def test_send_outranks_the_completing_input_answer():
162
156
  async with run_host(script) as (drv, host):
163
157
  drv.post("run/enqueue", add("m1"))
164
158
  call = await script.next_call()
165
- call.ack()
159
+ call.applied()
166
160
  request = {"type": "tool-call", "id": "r1", "toolCallId": "tc1"}
167
161
  call.finish(RunManager.InputRequired((request,)))
168
162
  await drv.wait_status("input-required")
@@ -179,6 +173,6 @@ async def test_send_outranks_the_completing_input_answer():
179
173
  script.no_call()
180
174
  assert (await drv.res(first))["type"] == "accepted"
181
175
  assert (await drv.res(first + 1))["type"] == "accepted"
182
- run.ack()
176
+ run.applied()
183
177
  run.finish(RunManager.Complete())
184
178
  await drv.wait_status("ready")