ms-tau-sdk 2.0.3.dev37__tar.gz → 2.0.3.dev38__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 (85) hide show
  1. {ms_tau_sdk-2.0.3.dev37 → ms_tau_sdk-2.0.3.dev38}/CHANGELOG.md +10 -1
  2. {ms_tau_sdk-2.0.3.dev37 → ms_tau_sdk-2.0.3.dev38}/PKG-INFO +1 -1
  3. {ms_tau_sdk-2.0.3.dev37 → ms_tau_sdk-2.0.3.dev38}/pyproject.toml +1 -1
  4. {ms_tau_sdk-2.0.3.dev37 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/agent_skills/tau_project_customization/SKILL.md +3 -2
  5. {ms_tau_sdk-2.0.3.dev37 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/api/a2a.py +11 -2
  6. {ms_tau_sdk-2.0.3.dev37 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/backend/models.py +5 -1
  7. {ms_tau_sdk-2.0.3.dev37 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/runtime/manager.py +36 -4
  8. {ms_tau_sdk-2.0.3.dev37 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/runtime/requester.py +14 -0
  9. {ms_tau_sdk-2.0.3.dev37 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/sessions/storage.py +5 -0
  10. {ms_tau_sdk-2.0.3.dev37 → ms_tau_sdk-2.0.3.dev38}/.gitignore +0 -0
  11. {ms_tau_sdk-2.0.3.dev37 → ms_tau_sdk-2.0.3.dev38}/README.md +0 -0
  12. {ms_tau_sdk-2.0.3.dev37 → ms_tau_sdk-2.0.3.dev38}/packages/tau-board/src/ms_tau_board/__init__.py +0 -0
  13. {ms_tau_sdk-2.0.3.dev37 → ms_tau_sdk-2.0.3.dev38}/packages/tau-board/src/ms_tau_board/app.py +0 -0
  14. {ms_tau_sdk-2.0.3.dev37 → ms_tau_sdk-2.0.3.dev38}/packages/tau-board/src/ms_tau_board/cli.py +0 -0
  15. {ms_tau_sdk-2.0.3.dev37 → ms_tau_sdk-2.0.3.dev38}/packages/tau-board/src/ms_tau_board/config.py +0 -0
  16. {ms_tau_sdk-2.0.3.dev37 → ms_tau_sdk-2.0.3.dev38}/packages/tau-board/src/ms_tau_board/env_file.py +0 -0
  17. {ms_tau_sdk-2.0.3.dev37 → ms_tau_sdk-2.0.3.dev38}/packages/tau-board/src/ms_tau_board/logs.py +0 -0
  18. {ms_tau_sdk-2.0.3.dev37 → ms_tau_sdk-2.0.3.dev38}/packages/tau-board/src/ms_tau_board/proxy.py +0 -0
  19. {ms_tau_sdk-2.0.3.dev37 → ms_tau_sdk-2.0.3.dev38}/packages/tau-board/src/ms_tau_board/state.py +0 -0
  20. {ms_tau_sdk-2.0.3.dev37 → ms_tau_sdk-2.0.3.dev38}/packages/tau-board/src/ms_tau_board/static/BULMA-LICENSE.txt +0 -0
  21. {ms_tau_sdk-2.0.3.dev37 → ms_tau_sdk-2.0.3.dev38}/packages/tau-board/src/ms_tau_board/static/app.js +0 -0
  22. {ms_tau_sdk-2.0.3.dev37 → ms_tau_sdk-2.0.3.dev38}/packages/tau-board/src/ms_tau_board/static/board.css +0 -0
  23. {ms_tau_sdk-2.0.3.dev37 → ms_tau_sdk-2.0.3.dev38}/packages/tau-board/src/ms_tau_board/static/bulma.min.css +0 -0
  24. {ms_tau_sdk-2.0.3.dev37 → ms_tau_sdk-2.0.3.dev38}/packages/tau-board/src/ms_tau_board/static/index.html +0 -0
  25. {ms_tau_sdk-2.0.3.dev37 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/__init__.py +0 -0
  26. {ms_tau_sdk-2.0.3.dev37 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/agent_skills/tau_a2a_runtime_adapter/SKILL.md +0 -0
  27. {ms_tau_sdk-2.0.3.dev37 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/agent_skills/tau_local_development/SKILL.md +0 -0
  28. {ms_tau_sdk-2.0.3.dev37 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/agent_skills/tau_repository_integration/SKILL.md +0 -0
  29. {ms_tau_sdk-2.0.3.dev37 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/api/__init__.py +0 -0
  30. {ms_tau_sdk-2.0.3.dev37 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/api/chat.py +0 -0
  31. {ms_tau_sdk-2.0.3.dev37 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/api/conversations.py +0 -0
  32. {ms_tau_sdk-2.0.3.dev37 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/api/dependencies.py +0 -0
  33. {ms_tau_sdk-2.0.3.dev37 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/api/health.py +0 -0
  34. {ms_tau_sdk-2.0.3.dev37 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/api/inspection.py +0 -0
  35. {ms_tau_sdk-2.0.3.dev37 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/api/local_chat.py +0 -0
  36. {ms_tau_sdk-2.0.3.dev37 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/api/models.py +0 -0
  37. {ms_tau_sdk-2.0.3.dev37 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/api/request_identity.py +0 -0
  38. {ms_tau_sdk-2.0.3.dev37 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/api/sessions.py +0 -0
  39. {ms_tau_sdk-2.0.3.dev37 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/app.py +0 -0
  40. {ms_tau_sdk-2.0.3.dev37 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/application.py +0 -0
  41. {ms_tau_sdk-2.0.3.dev37 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/backend/__init__.py +0 -0
  42. {ms_tau_sdk-2.0.3.dev37 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/backend/assertions.py +0 -0
  43. {ms_tau_sdk-2.0.3.dev37 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/backend/auth.py +0 -0
  44. {ms_tau_sdk-2.0.3.dev37 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/backend/client.py +0 -0
  45. {ms_tau_sdk-2.0.3.dev37 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/backend/local.py +0 -0
  46. {ms_tau_sdk-2.0.3.dev37 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/backend/mcp.py +0 -0
  47. {ms_tau_sdk-2.0.3.dev37 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/backend/routes.py +0 -0
  48. {ms_tau_sdk-2.0.3.dev37 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/cli.py +0 -0
  49. {ms_tau_sdk-2.0.3.dev37 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/errors.py +0 -0
  50. {ms_tau_sdk-2.0.3.dev37 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/logging.py +0 -0
  51. {ms_tau_sdk-2.0.3.dev37 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/protocols/__init__.py +0 -0
  52. {ms_tau_sdk-2.0.3.dev37 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/protocols/a2a_failure.py +0 -0
  53. {ms_tau_sdk-2.0.3.dev37 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/protocols/a2a_message.py +0 -0
  54. {ms_tau_sdk-2.0.3.dev37 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/protocols/a2a_roles.py +0 -0
  55. {ms_tau_sdk-2.0.3.dev37 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/protocols/assistant_ui.py +0 -0
  56. {ms_tau_sdk-2.0.3.dev37 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/protocols/chat_history.py +0 -0
  57. {ms_tau_sdk-2.0.3.dev37 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/protocols/strict_json.py +0 -0
  58. {ms_tau_sdk-2.0.3.dev37 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/providers/__init__.py +0 -0
  59. {ms_tau_sdk-2.0.3.dev37 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/providers/definitions.py +0 -0
  60. {ms_tau_sdk-2.0.3.dev37 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/providers/factory.py +0 -0
  61. {ms_tau_sdk-2.0.3.dev37 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/providers/tau_compat.py +0 -0
  62. {ms_tau_sdk-2.0.3.dev37 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/resources/SYSTEM.md +0 -0
  63. {ms_tau_sdk-2.0.3.dev37 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/resources/__init__.py +0 -0
  64. {ms_tau_sdk-2.0.3.dev37 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/resources/loader.py +0 -0
  65. {ms_tau_sdk-2.0.3.dev37 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/resources/prompts/review-code-repository.md +0 -0
  66. {ms_tau_sdk-2.0.3.dev37 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/runtime/__init__.py +0 -0
  67. {ms_tau_sdk-2.0.3.dev37 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/runtime/deployment_health.py +0 -0
  68. {ms_tau_sdk-2.0.3.dev37 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/runtime/events.py +0 -0
  69. {ms_tau_sdk-2.0.3.dev37 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/runtime/extensions.py +0 -0
  70. {ms_tau_sdk-2.0.3.dev37 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/runtime/failures.py +0 -0
  71. {ms_tau_sdk-2.0.3.dev37 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/runtime/live_turns.py +0 -0
  72. {ms_tau_sdk-2.0.3.dev37 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/runtime/observability.py +0 -0
  73. {ms_tau_sdk-2.0.3.dev37 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/runtime/provenance.py +0 -0
  74. {ms_tau_sdk-2.0.3.dev37 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/runtime/secret_entry.py +0 -0
  75. {ms_tau_sdk-2.0.3.dev37 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/runtime/session.py +0 -0
  76. {ms_tau_sdk-2.0.3.dev37 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/runtime/snapshots.py +0 -0
  77. {ms_tau_sdk-2.0.3.dev37 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/runtime/task_context.py +0 -0
  78. {ms_tau_sdk-2.0.3.dev37 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/sessions/__init__.py +0 -0
  79. {ms_tau_sdk-2.0.3.dev37 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/settings.py +0 -0
  80. {ms_tau_sdk-2.0.3.dev37 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/skills.py +0 -0
  81. {ms_tau_sdk-2.0.3.dev37 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/tools/__init__.py +0 -0
  82. {ms_tau_sdk-2.0.3.dev37 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/tools/mainsequence_mcp.py +0 -0
  83. {ms_tau_sdk-2.0.3.dev37 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/tools/secret_entry.py +0 -0
  84. {ms_tau_sdk-2.0.3.dev37 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/tools/skill_read.py +0 -0
  85. {ms_tau_sdk-2.0.3.dev37 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/tools/task_control.py +0 -0
@@ -2,6 +2,15 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ - A turn that a caller delivery resumes serves the person who asked for the delegated work. When
6
+ `POST /internal/a2a/task-caller-delivery` resumes a hosted session, the turn presents no
7
+ assertion. It names the delivery in `caller_delivery_uid` on the
8
+ `PATCH /api/v1/agent-sessions/<uid>/tau-runtime-activity/` transition that marks it active, and
9
+ the platform answers with the requester it recorded for the turn: the person who asked for the
10
+ turn that delegated with `resume_caller`. `current_requester()` returns that person, with empty
11
+ `team_uids`, and `requester_client()` reads for them, only when the delivery signal names the
12
+ same User with `requester_identity_type` `human`. Local mode and a runtime that is not hosted
13
+ name no delivery.
5
14
  - Main Sequence MCP no longer offers `agent_session.resolve_runtime_access` to the model. Its
6
15
  result carries a short-lived runtime token for direct-runtime clients, and Tau's generic MCP
7
16
  projection copied that token into model-visible tool content and into tool details, which reach
@@ -24,7 +33,7 @@
24
33
  platform's dispatch takes its requester from the dispatch's same two facts. Two new public
25
34
  functions:
26
35
  - `current_requester()` returns that person inside the turn (`uid`, `team_uids`), or `None` for
27
- Agent callers, the platform's own calls, local mode, a runtime that is not hosted, and code
36
+ Agent callers, the platform's other calls, local mode, a runtime that is not hosted, and code
28
37
  outside a turn;
29
38
  - `requester_client()` returns a client bound to the turn. `request(method, path, ...)` calls a
30
39
  platform API path with the runtime's credential and `X-MainSequence-Acting-For-Session`,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: ms-tau-sdk
3
- Version: 2.0.3.dev37
3
+ Version: 2.0.3.dev38
4
4
  Summary: Workspace-bound Tau application primitives for Main Sequence projects
5
5
  Project-URL: Changelog, https://github.com/mainsequence-sdk/ms-tau-sdk/blob/development/CHANGELOG.md
6
6
  Project-URL: Documentation, https://github.com/mainsequence-sdk/ms-tau-sdk/tree/development/docs
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "ms-tau-sdk"
7
- version = "2.0.3.dev37"
7
+ version = "2.0.3.dev38"
8
8
  description = "Workspace-bound Tau application primitives for Main Sequence projects"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.13"
@@ -129,8 +129,9 @@ async def revenue_by_region(tool_call_id, arguments, signal=None, on_update=None
129
129
  ```
130
130
 
131
131
  - `current_requester()` returns the turn's verified requester, with `uid` and `team_uids`, or
132
- `None`. It is `None` for Agent callers, the platform's own calls, local mode, and code outside a
133
- turn. Only it names the requester:
132
+ `None`. It is `None` for Agent callers, the platform's own calls other than a caller delivery
133
+ (which resumes delegated work for the person who asked), local mode, and code outside a turn.
134
+ Only it names the requester:
134
135
  never take a person's UID from tool arguments, the prompt, history, or a header.
135
136
  - `requester_client()` returns a client bound to the turn. `await client.request("GET",
136
137
  "/api/v1/...")` reads a platform API path for the requester. `await client.call_release(
@@ -63,7 +63,7 @@ from ms_tau_sdk.runtime.provenance import (
63
63
  TurnProvenance,
64
64
  turn_provenance_from_request,
65
65
  )
66
- from ms_tau_sdk.runtime.requester import Requester, canonical_requester_uid
66
+ from ms_tau_sdk.runtime.requester import CallerDelivery, Requester, canonical_requester_uid
67
67
  from ms_tau_sdk.runtime.task_context import (
68
68
  TaskExecutionContext,
69
69
  active_task_execution,
@@ -2150,7 +2150,7 @@ def _durable_task_execution_input(
2150
2150
 
2151
2151
 
2152
2152
  def _dispatched_requester(body: dict[str, Any], config: TauSDKSettings) -> Requester | None:
2153
- """Return the Task's requester as the platform's verified dispatch names it.
2153
+ """Return the requester that the platform's verified dispatch or caller delivery names.
2154
2154
 
2155
2155
  Only a person is a requester: an Agent's workload User never is. A runtime that does not
2156
2156
  verify the platform's assertion on its internal routes takes no requester from them.
@@ -2213,6 +2213,7 @@ async def _resume_caller_delivery(
2213
2213
  task_id: str,
2214
2214
  task_status: str,
2215
2215
  caller_agent_session_uid: str,
2216
+ delivery: CallerDelivery,
2216
2217
  ) -> None:
2217
2218
  prompt = (
2218
2219
  "A delegated asynchronous A2A Task has new actionable state. "
@@ -2229,6 +2230,9 @@ async def _resume_caller_delivery(
2229
2230
  "actorKind": "platform",
2230
2231
  "actorUid": "mainsequence",
2231
2232
  },
2233
+ # The turn names its delivery, so that the platform records the requester of the
2234
+ # delegated work for it.
2235
+ caller_delivery=delivery,
2232
2236
  ):
2233
2237
  pass
2234
2238
  manager.mark_response_delivered(caller_agent_session_uid)
@@ -2238,6 +2242,7 @@ async def _resume_caller_delivery(
2238
2242
  async def task_caller_delivery_available(
2239
2243
  body: dict[str, Any],
2240
2244
  manager: RuntimeManagerDep,
2245
+ config: SettingsDep,
2241
2246
  ) -> dict[str, Any]:
2242
2247
  delivery_uid = str(body.get("delivery_uid") or "").strip()
2243
2248
  task_uid = str(body.get("task_uid") or "").strip()
@@ -2284,6 +2289,10 @@ async def task_caller_delivery_available(
2284
2289
  task_id=task_id,
2285
2290
  task_status=task_status,
2286
2291
  caller_agent_session_uid=caller_agent_session_uid,
2292
+ delivery=CallerDelivery(
2293
+ uid=delivery_uid,
2294
+ requester=_dispatched_requester(body, config),
2295
+ ),
2287
2296
  ),
2288
2297
  name=f"a2a-caller-delivery-{delivery_uid}",
2289
2298
  )
@@ -295,7 +295,8 @@ class RuntimeState(BackendModel):
295
295
  cancel_requested: bool = False
296
296
  cancellation_id: str | None = None
297
297
  # The person the platform recorded as the active turn's requester, from the caller assertion
298
- # the runtime presented when it marked the turn active. None when it recorded nobody.
298
+ # the runtime presented, or the caller delivery it named, when it marked the turn active.
299
+ # None when it recorded nobody.
299
300
  requester_user_uid: str | None = None
300
301
 
301
302
  @model_validator(mode="after")
@@ -322,6 +323,9 @@ class RuntimeActivityPatch(BackendRequestModel):
322
323
  activity_sequence: int | None = Field(default=None, ge=1)
323
324
  runtime_activity: AgentRuntimeActivity
324
325
  active_turn_uid: str | None = None
326
+ # The caller delivery a turn the platform starts resumes the session for, sent only with the
327
+ # transition that marks that turn active, and never together with a caller assertion.
328
+ caller_delivery_uid: str | None = None
325
329
 
326
330
  @model_validator(mode="after")
327
331
  def validate_concurrency_mode(self) -> RuntimeActivityPatch:
@@ -65,6 +65,7 @@ from ms_tau_sdk.runtime.live_turns import LiveTurn, LocalChatTurn
65
65
  from ms_tau_sdk.runtime.observability import TauTurnObserver
66
66
  from ms_tau_sdk.runtime.provenance import TurnProvenance
67
67
  from ms_tau_sdk.runtime.requester import (
68
+ CallerDelivery,
68
69
  Requester,
69
70
  TurnRequesterBinding,
70
71
  bind_turn_requester,
@@ -1323,11 +1324,14 @@ class SessionRuntimeManager:
1323
1324
  platform_event: PlatformEvent | None = None,
1324
1325
  turn_uid: str | None = None,
1325
1326
  caller_assertion: CallerAssertion | None = None,
1327
+ caller_delivery: CallerDelivery | None = None,
1326
1328
  ) -> AsyncIterator[TauRuntimeEvent]:
1327
1329
  """Run one turn in a session.
1328
1330
 
1329
1331
  ``caller_assertion`` is the verified caller assertion of the hosted chat or A2A Message
1330
1332
  request that starts the turn. It is presented only when the turn is marked active.
1333
+ ``caller_delivery`` is, instead, the caller delivery that a turn the platform starts
1334
+ resumes the session for. It is named only when the turn is marked active.
1331
1335
  """
1332
1336
 
1333
1337
  agent_run_uid = str(uuid.uuid4())
@@ -1345,6 +1349,7 @@ class SessionRuntimeManager:
1345
1349
  provenance=provenance,
1346
1350
  platform_event=platform_event,
1347
1351
  caller_assertion=caller_assertion,
1352
+ caller_delivery=caller_delivery,
1348
1353
  ):
1349
1354
  yield event
1350
1355
 
@@ -1356,6 +1361,7 @@ class SessionRuntimeManager:
1356
1361
  provenance: TurnProvenance | None = None,
1357
1362
  platform_event: PlatformEvent | None = None,
1358
1363
  caller_assertion: CallerAssertion | None = None,
1364
+ caller_delivery: CallerDelivery | None = None,
1359
1365
  ) -> AsyncIterator[TauRuntimeEvent]:
1360
1366
  live_turn = self._open_live_turn(session_uid, content, provenance)
1361
1367
  try:
@@ -1365,6 +1371,7 @@ class SessionRuntimeManager:
1365
1371
  provenance=provenance,
1366
1372
  platform_event=platform_event,
1367
1373
  caller_assertion=caller_assertion,
1374
+ caller_delivery=caller_delivery,
1368
1375
  ):
1369
1376
  if live_turn is not None:
1370
1377
  live_turn.observe(event)
@@ -1497,6 +1504,7 @@ class SessionRuntimeManager:
1497
1504
  provenance: TurnProvenance | None = None,
1498
1505
  platform_event: PlatformEvent | None = None,
1499
1506
  caller_assertion: CallerAssertion | None = None,
1507
+ caller_delivery: CallerDelivery | None = None,
1500
1508
  ) -> AsyncIterator[TauRuntimeEvent]:
1501
1509
  logger.info(
1502
1510
  "agent.run.accepted",
@@ -1522,18 +1530,25 @@ class SessionRuntimeManager:
1522
1530
  context = get_contextvars()
1523
1531
  turn_uid = str(context.get("turn_uid") or uuid.uuid4())
1524
1532
  task_context = active_task_execution()
1525
- # Only a hosted chat or A2A Message turn presents its request's caller assertion. A Task
1533
+ # Only a hosted chat or A2A Message turn presents its request's caller assertion, and only
1534
+ # a hosted turn the platform starts for a caller delivery names that delivery. A Task
1526
1535
  # attempt takes its requester from the platform's dispatch instead.
1527
1536
  presented = (
1528
1537
  caller_assertion
1529
1538
  if task_context is None and self._requester_identity_verified()
1530
1539
  else None
1531
1540
  )
1541
+ delivery = (
1542
+ caller_delivery
1543
+ if presented is None and task_context is None and self._requester_identity_verified()
1544
+ else None
1545
+ )
1532
1546
  runtime = await self._begin_turn_with_one_reload(
1533
1547
  session_uid=session_uid,
1534
1548
  runtime=runtime,
1535
1549
  turn_uid=turn_uid,
1536
1550
  caller_assertion=presented,
1551
+ caller_delivery_uid=delivery.uid if delivery is not None else None,
1537
1552
  )
1538
1553
  requester_binding = TurnRequesterBinding(
1539
1554
  session_uid=session_uid,
@@ -1541,6 +1556,7 @@ class SessionRuntimeManager:
1541
1556
  runtime,
1542
1557
  caller_assertion=presented,
1543
1558
  task_context=task_context,
1559
+ caller_delivery=delivery,
1544
1560
  ),
1545
1561
  lease_proof=lambda: LeaseProof(
1546
1562
  session_uid=session_uid,
@@ -1677,16 +1693,24 @@ class SessionRuntimeManager:
1677
1693
  runtime: ActiveSessionRuntime,
1678
1694
  turn_uid: str,
1679
1695
  caller_assertion: CallerAssertion | None = None,
1696
+ caller_delivery_uid: str | None = None,
1680
1697
  ) -> ActiveSessionRuntime:
1681
1698
  for begin_attempt in range(2):
1682
1699
  runtime.activity_sequence += 1
1683
1700
  # An expired assertion is not presented, and the platform then records nobody.
1684
1701
  presented = caller_assertion.unexpired_token() if caller_assertion else None
1702
+ named: dict[str, str] = (
1703
+ {"caller_assertion": presented}
1704
+ if presented
1705
+ else {"caller_delivery_uid": caller_delivery_uid}
1706
+ if caller_delivery_uid
1707
+ else {}
1708
+ )
1685
1709
  try:
1686
1710
  state = await runtime.storage.begin_turn(
1687
1711
  turn_uid=turn_uid,
1688
1712
  activity_sequence=runtime.activity_sequence,
1689
- **({"caller_assertion": presented} if presented else {}),
1713
+ **named,
1690
1714
  )
1691
1715
  except BackendConflictError:
1692
1716
  if begin_attempt:
@@ -1700,7 +1724,7 @@ class SessionRuntimeManager:
1700
1724
  runtime.activity_sequence = state.activity_sequence or runtime.activity_sequence
1701
1725
  requester_user_uid = state.requester_user_uid
1702
1726
  runtime.requester_user_uid = (
1703
- requester_user_uid if presented and isinstance(requester_user_uid, str) else None
1727
+ requester_user_uid if named and isinstance(requester_user_uid, str) else None
1704
1728
  )
1705
1729
  return runtime
1706
1730
  raise AssertionError("Tau turn reload loop did not terminate")
@@ -1716,19 +1740,27 @@ class SessionRuntimeManager:
1716
1740
  *,
1717
1741
  caller_assertion: CallerAssertion | None,
1718
1742
  task_context: TaskExecutionContext | None,
1743
+ caller_delivery: CallerDelivery | None = None,
1719
1744
  ) -> Requester | None:
1720
1745
  """Name the verified person a turn serves, or None.
1721
1746
 
1722
1747
  A Task turn serves the person its execution context names, as the platform recorded the
1723
1748
  Task's requester. A chat or A2A Message turn serves its verified caller only when the
1724
1749
  platform recorded that same person as the turn's requester, which it does for a person who
1725
- owns the session and never for an Agent caller.
1750
+ owns the session and never for an Agent caller. A turn the platform starts for a caller
1751
+ delivery serves the requester the delivery names, only when the platform recorded that
1752
+ same person for the turn.
1726
1753
  """
1727
1754
 
1728
1755
  if not self._requester_identity_verified():
1729
1756
  return None
1730
1757
  if task_context is not None:
1731
1758
  return task_context.requester
1759
+ if caller_delivery is not None:
1760
+ requester = caller_delivery.requester
1761
+ if requester is None or runtime.requester_user_uid != requester.uid:
1762
+ return None
1763
+ return requester
1732
1764
  if caller_assertion is None or caller_assertion.caller_is_workload:
1733
1765
  return None
1734
1766
  caller = caller_assertion.caller
@@ -58,6 +58,19 @@ class Requester:
58
58
  team_uids: tuple[str, ...] = ()
59
59
 
60
60
 
61
+ @dataclass(frozen=True, slots=True)
62
+ class CallerDelivery:
63
+ """The caller delivery that a turn the platform starts resumes its session for.
64
+
65
+ ``requester`` is the person the platform's verified signal names as the requester of the
66
+ delegated work, or None. The turn serves that person only when the platform records the same
67
+ person for the turn it starts.
68
+ """
69
+
70
+ uid: str
71
+ requester: Requester | None = None
72
+
73
+
61
74
  class RequesterBindingError(TauSDKError, PermissionError):
62
75
  """The turn has no verified requester, or the platform ended the requester's binding.
63
76
 
@@ -551,6 +564,7 @@ def _token_access(answer: ReleaseRuntimeAccess) -> _ReleaseAccess:
551
564
 
552
565
 
553
566
  __all__ = [
567
+ "CallerDelivery",
554
568
  "Requester",
555
569
  "RequesterBindingError",
556
570
  "RequesterClient",
@@ -68,11 +68,15 @@ class BackendSessionStorage(SessionStorage):
68
68
  turn_uid: str,
69
69
  activity_sequence: int,
70
70
  caller_assertion: str | None = None,
71
+ caller_delivery_uid: str | None = None,
71
72
  ) -> RuntimeState:
72
73
  """Mark ``turn_uid`` active.
73
74
 
74
75
  ``caller_assertion`` is the verified caller assertion of the request that started the
75
76
  turn. It goes only to this transition, so that the platform can record who asked.
77
+ ``caller_delivery_uid`` names, instead, the caller delivery that a turn the platform
78
+ starts resumes the session for; the platform then records the requester of the delegated
79
+ work.
76
80
  """
77
81
 
78
82
  async with self._state_lock:
@@ -89,6 +93,7 @@ class BackendSessionStorage(SessionStorage):
89
93
  activity_sequence=activity_sequence,
90
94
  runtime_activity="working",
91
95
  active_turn_uid=turn_uid,
96
+ caller_delivery_uid=caller_delivery_uid,
92
97
  ),
93
98
  **({"caller_assertion": caller_assertion} if caller_assertion else {}),
94
99
  )