harness-sdk-python 0.13.2__tar.gz → 0.14.0__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.2 → harness_sdk_python-0.14.0}/PKG-INFO +3 -3
  2. {harness_sdk_python-0.13.2 → harness_sdk_python-0.14.0}/examples/runs_app.py +7 -7
  3. {harness_sdk_python-0.13.2 → harness_sdk_python-0.14.0}/pyproject.toml +2 -2
  4. {harness_sdk_python-0.13.2 → harness_sdk_python-0.14.0}/src/harness_sdk/run_manager.py +69 -47
  5. {harness_sdk_python-0.13.2 → harness_sdk_python-0.14.0}/tests/run_helpers.py +9 -9
  6. harness_sdk_python-0.13.2/tests/test_ack_visibility.py → harness_sdk_python-0.14.0/tests/test_applied_visibility.py +13 -13
  7. {harness_sdk_python-0.13.2 → harness_sdk_python-0.14.0}/tests/test_batches.py +10 -10
  8. {harness_sdk_python-0.13.2 → harness_sdk_python-0.14.0}/tests/test_branch_anchor.py +10 -10
  9. {harness_sdk_python-0.13.2 → harness_sdk_python-0.14.0}/tests/test_dispatching.py +22 -22
  10. {harness_sdk_python-0.13.2 → harness_sdk_python-0.14.0}/tests/test_edit_dispatched.py +8 -8
  11. {harness_sdk_python-0.13.2 → harness_sdk_python-0.14.0}/tests/test_edit_reload.py +6 -6
  12. {harness_sdk_python-0.13.2 → harness_sdk_python-0.14.0}/tests/test_enqueue.py +18 -18
  13. {harness_sdk_python-0.13.2 → harness_sdk_python-0.14.0}/tests/test_input_required.py +20 -20
  14. {harness_sdk_python-0.13.2 → harness_sdk_python-0.14.0}/tests/test_meta.py +16 -16
  15. {harness_sdk_python-0.13.2 → harness_sdk_python-0.14.0}/tests/test_outcomes.py +9 -9
  16. {harness_sdk_python-0.13.2 → harness_sdk_python-0.14.0}/tests/test_placement.py +1 -1
  17. {harness_sdk_python-0.13.2 → harness_sdk_python-0.14.0}/tests/test_prepare_hooks.py +4 -4
  18. {harness_sdk_python-0.13.2 → harness_sdk_python-0.14.0}/tests/test_restore.py +4 -4
  19. {harness_sdk_python-0.13.2 → harness_sdk_python-0.14.0}/tests/test_rewind_during_run.py +6 -6
  20. {harness_sdk_python-0.13.2 → harness_sdk_python-0.14.0}/tests/test_settle.py +39 -34
  21. {harness_sdk_python-0.13.2 → harness_sdk_python-0.14.0}/tests/test_steer.py +11 -11
  22. {harness_sdk_python-0.13.2 → harness_sdk_python-0.14.0}/tests/test_stop_continue.py +64 -22
  23. {harness_sdk_python-0.13.2 → harness_sdk_python-0.14.0}/tests/test_stopping_window.py +16 -8
  24. {harness_sdk_python-0.13.2 → harness_sdk_python-0.14.0}/.gitignore +0 -0
  25. {harness_sdk_python-0.13.2 → harness_sdk_python-0.14.0}/README.md +0 -0
  26. {harness_sdk_python-0.13.2 → harness_sdk_python-0.14.0}/examples/__init__.py +0 -0
  27. {harness_sdk_python-0.13.2 → harness_sdk_python-0.14.0}/src/harness_sdk/__init__.py +0 -0
  28. {harness_sdk_python-0.13.2 → harness_sdk_python-0.14.0}/src/harness_sdk/fenced_postgres.py +0 -0
  29. {harness_sdk_python-0.13.2 → harness_sdk_python-0.14.0}/src/harness_sdk/linear_thread.py +0 -0
  30. {harness_sdk_python-0.13.2 → harness_sdk_python-0.14.0}/tests/test_fenced_postgres.py +0 -0
  31. {harness_sdk_python-0.13.2 → harness_sdk_python-0.14.0}/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.2
3
+ Version: 0.14.0
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.2"
3
+ version = "0.14.0"
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,16 +10,22 @@ 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:
13
+ initiator applies and settles accepted with its state-visible application:
14
14
  the verdict certifies the admission write — sends with their insert (lane
15
15
  or ``dispatching``), mid-run edit/reload with the stopping window,
16
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;
22
- steer entries still undelivered in the lane stay queued for the next run.
20
+ ``ctx.applied()`` confirms it; ``steering.take()`` may refill it — at most one
21
+ unconfirmed batch ever exists. Settle consumes a confirmed batch; a batch
22
+ still unconfirmed at settle returns to the front of its lanes. ``Complete``
23
+ with an unconfirmed batch settles as an error. Steer entries still
24
+ undelivered in the lane stay queued for the next run.
25
+
26
+ ``entry["epoch"]`` counts the entry's dispatches; a ``run/stop`` carrying an
27
+ ``epoch`` other than the live one rejects, so a replayed stop never ends a
28
+ later leg of the same ``runId``.
23
29
 
24
30
  A mid-run stop or rewind opens a stopping window on the entry:
25
31
  ``stopping: {reason}`` plus, for rewinds, the staged dispatch as
@@ -87,7 +93,7 @@ class _Send:
87
93
  message_id: str
88
94
  source_meta: dict[str, Any] | None
89
95
  meta: Any
90
- ack: Callable[[], None]
96
+ applied: Callable[[], None]
91
97
  run_id: str = ""
92
98
  anchor: Any = _ABSENT
93
99
  anchor_meta: dict[str, Any] | None = None
@@ -107,7 +113,7 @@ class _Edit:
107
113
  source_meta: dict[str, Any]
108
114
  message: dict[str, Any]
109
115
  meta: Any
110
- ack: Callable[[], None]
116
+ applied: Callable[[], None]
111
117
  run_id: str
112
118
  future: "asyncio.Future[Any]" = field(default_factory=_future)
113
119
 
@@ -116,7 +122,7 @@ class _Edit:
116
122
  class _Reload:
117
123
  source_meta: dict[str, Any]
118
124
  meta: Any
119
- ack: Callable[[], None]
125
+ applied: Callable[[], None]
120
126
  run_id: str
121
127
  future: "asyncio.Future[Any]" = field(default_factory=_future)
122
128
 
@@ -145,11 +151,11 @@ class _Rewind:
145
151
  type: str
146
152
  messages: list[dict[str, Any]]
147
153
  rollback_to: Any
148
- ack: Callable[[], None]
154
+ applied: Callable[[], None]
149
155
  future: "asyncio.Future[Any]"
150
156
  run_id: str
151
157
  root_meta: Any = None
152
- acked: bool = False
158
+ applied_sent: bool = False
153
159
 
154
160
 
155
161
  @dataclass
@@ -373,8 +379,8 @@ class RunManager:
373
379
  rollback_to=rewind.rollback_to,
374
380
  root_meta=rewind.root_meta,
375
381
  )
376
- if not rewind.acked:
377
- rewind.ack()
382
+ if not rewind.applied_sent:
383
+ rewind.applied()
378
384
  elif outcome is not None:
379
385
  self._settle_outcome(outcome)
380
386
  else:
@@ -384,8 +390,8 @@ class RunManager:
384
390
  ):
385
391
  assert self._ctx is not None
386
392
  self._ctx.stop_requested.set()
387
- for future, ack, result in fx.staged_sends:
388
- ack()
393
+ for future, applied, result in fx.staged_sends:
394
+ applied()
389
395
  if not future.done():
390
396
  future.set_result(result)
391
397
  for future in fx.continues:
@@ -534,6 +540,7 @@ class RunManager:
534
540
  self._dispatch_record = dict(record)
535
541
  self._stop_reason = None
536
542
  entry = self._ensure_entry()
543
+ entry["epoch"] = (plain(entry.get("epoch")) or 0) + 1
537
544
  entry["dispatching"] = record
538
545
  entry.pop("error", None)
539
546
  if messages:
@@ -570,6 +577,8 @@ class RunManager:
570
577
  "run must return a RunManager outcome, got "
571
578
  f"{type(outcome).__name__}"
572
579
  )
580
+ if isinstance(outcome, RunManager.Complete) and self._dispatching:
581
+ raise RuntimeError("run returned Complete with an unconfirmed batch")
573
582
  except asyncio.CancelledError:
574
583
  self._settle(ctx)
575
584
  self._set_status("stopped")
@@ -593,19 +602,25 @@ class RunManager:
593
602
  self._ctx = None
594
603
  self._task = None
595
604
  self._dispatch_record = None
596
- self._dispatching = []
597
605
  entry = self._ensure_entry()
598
- record = plain(entry.get("dispatching"))
599
- if record is not None and record["trigger"] != "steer" and record["messages"]:
600
- self._dispatched_ids = ()
601
- entry.pop("dispatching", None)
606
+ if self._dispatching:
607
+ self._unstage_dispatching()
608
+ else:
609
+ record = plain(entry.get("dispatching"))
610
+ if (
611
+ record is not None
612
+ and record["trigger"] != "steer"
613
+ and record["messages"]
614
+ ):
615
+ self._dispatched_ids = ()
616
+ entry.pop("dispatching", None)
602
617
  entry.pop("stopping", None)
603
618
 
604
- def _ack(self) -> None:
619
+ def _applied(self) -> None:
605
620
  entry = self._entry()
606
621
  record = plain(entry.get("dispatching"))
607
622
  if record is None:
608
- raise RuntimeError("ack() with no unacked batch")
623
+ raise RuntimeError("applied() with no unapplied batch")
609
624
  self._dispatching = []
610
625
  entry.pop("dispatching", None)
611
626
 
@@ -691,8 +706,8 @@ class RunManager:
691
706
  rollback_to=rewind.rollback_to,
692
707
  root_meta=rewind.root_meta,
693
708
  )
694
- rewind.ack()
695
- rewind.acked = True
709
+ rewind.applied()
710
+ rewind.applied_sent = True
696
711
  if not rewind.future.done():
697
712
  rewind.future.set_result(None)
698
713
 
@@ -941,7 +956,7 @@ class RunManager:
941
956
  if live_run_id is not None and e.anchor is not _ABSENT
942
957
  else None
943
958
  )
944
- fx.staged_sends.append((e.future, e.ack, result))
959
+ fx.staged_sends.append((e.future, e.applied, result))
945
960
  return _PARKED
946
961
 
947
962
  def _apply_move(self, e: _Send, fx: _Effects) -> Any:
@@ -981,7 +996,7 @@ class RunManager:
981
996
  "message-edit",
982
997
  [self._stamped(e.message, e.meta)],
983
998
  e.source_meta["parentId"],
984
- e.ack,
999
+ e.applied,
985
1000
  e.future,
986
1001
  e.run_id,
987
1002
  )
@@ -1036,7 +1051,7 @@ class RunManager:
1036
1051
  "message-edit",
1037
1052
  [self._stamped(e.message, e.meta)],
1038
1053
  e.source_meta["parentId"],
1039
- e.ack,
1054
+ e.applied,
1040
1055
  e.future,
1041
1056
  e.run_id,
1042
1057
  )
@@ -1049,7 +1064,7 @@ class RunManager:
1049
1064
  "message-reload",
1050
1065
  [],
1051
1066
  e.source_meta["parentId"],
1052
- e.ack,
1067
+ e.applied,
1053
1068
  e.future,
1054
1069
  e.run_id,
1055
1070
  root_meta=e.meta,
@@ -1064,7 +1079,7 @@ class RunManager:
1064
1079
  lane: str,
1065
1080
  params: Any,
1066
1081
  meta: Any,
1067
- ack: Callable[[], None],
1082
+ applied: Callable[[], None],
1068
1083
  ) -> Any:
1069
1084
  if not isinstance(params, dict):
1070
1085
  raise _reject("invalid-message", "params must be an object")
@@ -1100,7 +1115,7 @@ class RunManager:
1100
1115
  message_id,
1101
1116
  None,
1102
1117
  None,
1103
- ack,
1118
+ applied,
1104
1119
  run_id=run_id,
1105
1120
  anchor=anchor,
1106
1121
  anchor_meta=anchor_meta,
@@ -1117,7 +1132,7 @@ class RunManager:
1117
1132
  message["id"],
1118
1133
  source_meta,
1119
1134
  meta,
1120
- ack,
1135
+ applied,
1121
1136
  run_id=run_id,
1122
1137
  anchor=anchor,
1123
1138
  anchor_meta=anchor_meta,
@@ -1126,14 +1141,14 @@ class RunManager:
1126
1141
  )
1127
1142
 
1128
1143
  async def enqueue(
1129
- self, params: Any, *, meta: Any = None, ack: Callable[[], None]
1144
+ self, params: Any, *, meta: Any = None, applied: Callable[[], None]
1130
1145
  ) -> Any:
1131
- return await self._send("queue", params, meta, ack)
1146
+ return await self._send("queue", params, meta, applied)
1132
1147
 
1133
1148
  async def steer(
1134
- self, params: Any, *, meta: Any = None, ack: Callable[[], None]
1149
+ self, params: Any, *, meta: Any = None, applied: Callable[[], None]
1135
1150
  ) -> Any:
1136
- return await self._send("steerQueue", params, meta, ack)
1151
+ return await self._send("steerQueue", params, meta, applied)
1137
1152
 
1138
1153
  async def dequeue(self, params: Any) -> None:
1139
1154
  message_id = params.get("messageId") if isinstance(params, dict) else None
@@ -1152,7 +1167,7 @@ class RunManager:
1152
1167
  raise _reject("capability-missing", "the rewind capability is not enabled")
1153
1168
 
1154
1169
  async def edit(
1155
- self, params: Any, *, meta: Any = None, ack: Callable[[], None]
1170
+ self, params: Any, *, meta: Any = None, applied: Callable[[], None]
1156
1171
  ) -> Any:
1157
1172
  self._check_rewind_gate("run/edit")
1158
1173
  run_id = _run_id_of(params)
@@ -1176,11 +1191,11 @@ class RunManager:
1176
1191
  ):
1177
1192
  raise _reject("duplicate-id", f"message id {message['id']} is already used")
1178
1193
  return await self._stage(
1179
- _Edit(source_id, source_meta, message, meta, ack, run_id)
1194
+ _Edit(source_id, source_meta, message, meta, applied, run_id)
1180
1195
  )
1181
1196
 
1182
1197
  async def reload(
1183
- self, params: Any, *, meta: Any = None, ack: Callable[[], None]
1198
+ self, params: Any, *, meta: Any = None, applied: Callable[[], None]
1184
1199
  ) -> Any:
1185
1200
  self._check_rewind_gate("run/reload")
1186
1201
  run_id = _run_id_of(params)
@@ -1204,14 +1219,17 @@ class RunManager:
1204
1219
  "capability-missing",
1205
1220
  "the assistant-continuation capability is not enabled",
1206
1221
  )
1207
- return await self._stage(_Reload(source_meta, meta, ack, run_id))
1222
+ return await self._stage(_Reload(source_meta, meta, applied, run_id))
1208
1223
 
1209
- async def stop(self, params: Any = None, *, ack: Callable[[], None]) -> Any:
1224
+ async def stop(self, params: Any = None, *, applied: Callable[[], None]) -> Any:
1210
1225
  if params is not None and not isinstance(params, dict):
1211
1226
  raise _reject("invalid-message", "params must be an object")
1212
1227
  reason = (params or {}).get("reason", _ABSENT)
1213
1228
  if reason is not _ABSENT and not isinstance(reason, str):
1214
1229
  raise _reject("invalid-message", "reason must be a string")
1230
+ epoch = (params or {}).get("epoch", _ABSENT)
1231
+ if epoch is not _ABSENT and (isinstance(epoch, bool) or not isinstance(epoch, int)):
1232
+ raise _reject("invalid-message", "epoch must be an integer")
1215
1233
  status = self._status()
1216
1234
  if status != "running":
1217
1235
  raise _reject("wrong-state", f"run/stop is rejected in {status}")
@@ -1220,11 +1238,15 @@ class RunManager:
1220
1238
  raise _reject(
1221
1239
  "wrong-state", f"runId {run_id!r} does not name the live run"
1222
1240
  )
1241
+ if epoch is not _ABSENT and epoch != plain(self._entry()["epoch"]):
1242
+ raise _reject(
1243
+ "wrong-state", f"epoch {epoch} does not name the live dispatch"
1244
+ )
1223
1245
  if reason is not _ABSENT:
1224
1246
  self._stop_reason = reason
1225
1247
  entry = _Stop()
1226
1248
  self._intake.append(entry)
1227
- ack()
1249
+ applied()
1228
1250
  self._schedule(self._drain)
1229
1251
  return await entry.future
1230
1252
 
@@ -1274,7 +1296,7 @@ class RunManager:
1274
1296
  response = self._validated_response(request["type"], params["response"])
1275
1297
  return await self._stage(_Input(request_id, response, meta))
1276
1298
 
1277
- async def continue_run(self, *, meta: Any = None, ack: Callable[[], None]) -> Any:
1299
+ async def continue_run(self, *, meta: Any = None, applied: Callable[[], None]) -> Any:
1278
1300
  status = self._status()
1279
1301
  if status not in ("error", "stopped"):
1280
1302
  raise _reject("wrong-state", f"run/continue is rejected in {status}")
@@ -1287,7 +1309,7 @@ class RunManager:
1287
1309
  )
1288
1310
  entry = _Continue(meta)
1289
1311
  self._intake.append(entry)
1290
- ack()
1312
+ applied()
1291
1313
  self._schedule(self._drain)
1292
1314
  return await entry.future
1293
1315
 
@@ -1340,12 +1362,12 @@ class RunManager:
1340
1362
  _ctx: "RunManager.RunContext"
1341
1363
 
1342
1364
  def take(self) -> tuple[dict[str, Any], ...]:
1343
- """Drain the steer lane into a fresh dispatch record; requires the current batch to be acked."""
1365
+ """Drain the steer lane into a fresh dispatch record; requires the current batch to be applied."""
1344
1366
  self._ctx._ensure_active()
1345
1367
  manager = self._ctx._manager
1346
1368
  entry = manager._entry()
1347
1369
  if plain(entry.get("dispatching")) is not None:
1348
- raise RuntimeError("take before the current batch is acked")
1370
+ raise RuntimeError("take before the current batch is applied")
1349
1371
  items = manager._lane_items("steerQueue")
1350
1372
  if not items:
1351
1373
  return ()
@@ -1410,10 +1432,10 @@ class RunManager:
1410
1432
  if self._manager._ctx is not self:
1411
1433
  raise RuntimeError("this run has already settled")
1412
1434
 
1413
- def ack(self) -> None:
1414
- """Ack the current batch: clears the dispatch record."""
1435
+ def applied(self) -> None:
1436
+ """Confirms the current batch durable: clears the dispatch record so settle consumes it."""
1415
1437
  self._ensure_active()
1416
- self._manager._ack()
1438
+ self._manager._applied()
1417
1439
 
1418
1440
  def set_recovery_state(self, value: Any) -> None:
1419
1441
  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_unconfirmed_run_end_requeues_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"))
@@ -69,10 +69,10 @@ async def test_unacked_run_end_consumes_the_direct_dispatch():
69
69
  assert dispatching_ids(covered_state(drv, seq)) == ["m1"]
70
70
  call.fail(RuntimeError("boom"))
71
71
  await drv.wait_status("error")
72
- assert queue_ids(drv.replica, "queue") == []
72
+ assert queue_ids(drv.replica, "queue") == ["m1"]
73
73
 
74
74
 
75
- async def test_unacked_run_end_consumes_the_direct_steer_send():
75
+ async def test_unconfirmed_run_end_requeues_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"))
@@ -81,11 +81,11 @@ async def test_unacked_run_end_consumes_the_direct_steer_send():
81
81
  assert dispatching_ids(covered_state(drv, seq)) == ["s1"]
82
82
  call.fail(RuntimeError("boom"))
83
83
  await drv.wait_status("error")
84
- assert queue_ids(drv.replica, "steerQueue") == []
84
+ assert queue_ids(drv.replica, "steerQueue") == ["s1"]
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,7 +54,7 @@ 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
  )
@@ -72,7 +72,7 @@ async def test_stop_settles_at_the_stopping_write():
72
72
  async with run_host(script) as (drv, host):
73
73
  drv.post("run/enqueue", add("m1"))
74
74
  call = await script.next_call()
75
- call.ack()
75
+ call.applied()
76
76
  settled = await drv.stop()
77
77
  assert settled["type"] == "accepted"
78
78
  assert drv.replica["status"] == "running"
@@ -104,10 +104,10 @@ async def test_handler_tail_staging_decides_in_its_own_envelope():
104
104
  )
105
105
  )
106
106
  # The decide ran outside the batch segment: its ops flush in an
107
- # envelope of their own, carrying the send's covering ack.
107
+ # envelope of their own, carrying the send's covering applied.
108
108
  assert dispatch_env.get("ack", 0) >= seq
109
109
  assert (await drv.res(seq))["type"] == "accepted"
110
- call.ack()
110
+ call.applied()
111
111
 
112
112
 
113
113
  async def test_rewind_outranks_send_and_continue_in_one_batch():
@@ -129,7 +129,7 @@ async def test_rewind_outranks_send_and_continue_in_one_batch():
129
129
  "isLeaf": True,
130
130
  "onActiveBranch": True,
131
131
  }
132
- call.ack()
132
+ call.applied()
133
133
  call.fail(RuntimeError("boom"))
134
134
  await drv.wait_status("error")
135
135
  first = drv.batch(
@@ -147,7 +147,7 @@ async def test_rewind_outranks_send_and_continue_in_one_batch():
147
147
  assert (await drv.res(first + 1))["type"] == "accepted"
148
148
  assert (await drv.res(first + 2))["type"] == "accepted"
149
149
  assert queue_ids(drv.replica, "queue") == ["q1"]
150
- rerun.ack()
150
+ rerun.applied()
151
151
  rerun.finish(RunManager.Complete())
152
152
 
153
153
 
@@ -156,7 +156,7 @@ async def test_send_outranks_the_completing_input_answer():
156
156
  async with run_host(script) as (drv, host):
157
157
  drv.post("run/enqueue", add("m1"))
158
158
  call = await script.next_call()
159
- call.ack()
159
+ call.applied()
160
160
  request = {"type": "tool-call", "id": "r1", "toolCallId": "tc1"}
161
161
  call.finish(RunManager.InputRequired((request,)))
162
162
  await drv.wait_status("input-required")
@@ -173,6 +173,6 @@ async def test_send_outranks_the_completing_input_answer():
173
173
  script.no_call()
174
174
  assert (await drv.res(first))["type"] == "accepted"
175
175
  assert (await drv.res(first + 1))["type"] == "accepted"
176
- run.ack()
176
+ run.applied()
177
177
  run.finish(RunManager.Complete())
178
178
  await drv.wait_status("ready")