ms-tau-sdk 2.0.3.dev36__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.
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev38}/CHANGELOG.md +23 -1
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev38}/PKG-INFO +3 -2
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev38}/README.md +2 -1
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev38}/pyproject.toml +1 -1
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/agent_skills/tau_a2a_runtime_adapter/SKILL.md +3 -2
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/agent_skills/tau_project_customization/SKILL.md +3 -2
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/agent_skills/tau_repository_integration/SKILL.md +3 -3
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/api/a2a.py +12 -3
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/api/request_identity.py +91 -18
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/backend/assertions.py +3 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/backend/client.py +8 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/backend/models.py +20 -1
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/backend/routes.py +5 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/runtime/manager.py +37 -5
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/runtime/requester.py +14 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/sessions/storage.py +5 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev38}/.gitignore +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev38}/packages/tau-board/src/ms_tau_board/__init__.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev38}/packages/tau-board/src/ms_tau_board/app.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev38}/packages/tau-board/src/ms_tau_board/cli.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev38}/packages/tau-board/src/ms_tau_board/config.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev38}/packages/tau-board/src/ms_tau_board/env_file.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev38}/packages/tau-board/src/ms_tau_board/logs.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev38}/packages/tau-board/src/ms_tau_board/proxy.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev38}/packages/tau-board/src/ms_tau_board/state.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev38}/packages/tau-board/src/ms_tau_board/static/BULMA-LICENSE.txt +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev38}/packages/tau-board/src/ms_tau_board/static/app.js +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev38}/packages/tau-board/src/ms_tau_board/static/board.css +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev38}/packages/tau-board/src/ms_tau_board/static/bulma.min.css +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev38}/packages/tau-board/src/ms_tau_board/static/index.html +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/__init__.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/agent_skills/tau_local_development/SKILL.md +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/api/__init__.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/api/chat.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/api/conversations.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/api/dependencies.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/api/health.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/api/inspection.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/api/local_chat.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/api/models.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/api/sessions.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/app.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/application.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/backend/__init__.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/backend/auth.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/backend/local.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/backend/mcp.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/cli.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/errors.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/logging.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/protocols/__init__.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/protocols/a2a_failure.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/protocols/a2a_message.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/protocols/a2a_roles.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/protocols/assistant_ui.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/protocols/chat_history.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/protocols/strict_json.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/providers/__init__.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/providers/definitions.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/providers/factory.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/providers/tau_compat.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/resources/SYSTEM.md +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/resources/__init__.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/resources/loader.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/resources/prompts/review-code-repository.md +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/runtime/__init__.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/runtime/deployment_health.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/runtime/events.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/runtime/extensions.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/runtime/failures.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/runtime/live_turns.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/runtime/observability.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/runtime/provenance.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/runtime/secret_entry.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/runtime/session.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/runtime/snapshots.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/runtime/task_context.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/sessions/__init__.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/settings.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/skills.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/tools/__init__.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/tools/mainsequence_mcp.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/tools/secret_entry.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/tools/skill_read.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → 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
|
|
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`,
|
|
@@ -51,6 +60,19 @@
|
|
|
51
60
|
`GET /api/chat/model-providers`. The Environment is now sent as the `organization_environment_uid`
|
|
52
61
|
query parameter, as the other list calls send theirs; the call without an Environment is
|
|
53
62
|
unchanged ([issue #68](https://github.com/mainsequence-sdk/ms-tau-sdk/issues/68)).
|
|
63
|
+
- A hosted runtime admits the Agent that delegated to a child session. When Agent A delegates to
|
|
64
|
+
Agent B through `a2a.send_message`, the platform creates B's child session for the person who
|
|
65
|
+
owns A's session, and A addresses it with its own credential, so the caller assertion names A's
|
|
66
|
+
workload User. B's runtime refused every such request with 403. It now also admits that caller
|
|
67
|
+
when the session's `parent_session_agent_uid` names an Agent and the platform's directory
|
|
68
|
+
(`GET /api/v1/users/<uid>/`, read with the runtime credential) shows the caller as a workload
|
|
69
|
+
User whose `agent_uid` is that Agent: Message send and stream, JSON-RPC, and Task continuation,
|
|
70
|
+
reads, list and cancel. The `X-Caller-*` headers never admit a caller. A session without a
|
|
71
|
+
parent, another Agent's workload, and a person who is not the owner still get 403, and a lookup
|
|
72
|
+
that fails refuses the request. One request looks a User up at most once, and nothing is kept
|
|
73
|
+
after it. The admitted Agent is never a requester: `current_requester()` stays `None` in its
|
|
74
|
+
turns and in the Task attempts its requests run. See the 2026-10-06 amendment of ADR 0019
|
|
75
|
+
([issue #67](https://github.com/mainsequence-sdk/ms-tau-sdk/issues/67)).
|
|
54
76
|
|
|
55
77
|
## 2.0.2 — 2026-10-06
|
|
56
78
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: ms-tau-sdk
|
|
3
|
-
Version: 2.0.3.
|
|
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
|
|
@@ -126,7 +126,8 @@ app = create_app()
|
|
|
126
126
|
|
|
127
127
|
When Main Sequence hosts the runtime, `create_app()` admits a request only with the platform's
|
|
128
128
|
signed assertion in `X-MainSequence-Caller-Assertion`, never on gateway identity headers, and lets
|
|
129
|
-
only a session's owner
|
|
129
|
+
only a session's owner, an Organization admin, or the Agent that delegated to it address that
|
|
130
|
+
session. It declares this request
|
|
130
131
|
identity to the platform launcher in `app.state.mainsequence_request_identity`; the launcher serves
|
|
131
132
|
only an application that declares it, so serve the application `create_app()` returns. Outside
|
|
132
133
|
hosting, requests are handled as before. See
|
|
@@ -93,7 +93,8 @@ app = create_app()
|
|
|
93
93
|
|
|
94
94
|
When Main Sequence hosts the runtime, `create_app()` admits a request only with the platform's
|
|
95
95
|
signed assertion in `X-MainSequence-Caller-Assertion`, never on gateway identity headers, and lets
|
|
96
|
-
only a session's owner
|
|
96
|
+
only a session's owner, an Organization admin, or the Agent that delegated to it address that
|
|
97
|
+
session. It declares this request
|
|
97
98
|
identity to the platform launcher in `app.state.mainsequence_request_identity`; the launcher serves
|
|
98
99
|
only an application that declares it, so serve the application `create_app()` returns. Outside
|
|
99
100
|
hosting, requests are handled as before. See
|
|
@@ -26,8 +26,9 @@ The SDK owns:
|
|
|
26
26
|
- privately attaching active caller-session proof when a tool advertises
|
|
27
27
|
`mainsequence.ai/requires-caller-session-proof/v1: true`;
|
|
28
28
|
- verifying the platform's signed caller or platform assertion on every inbound request of a
|
|
29
|
-
hosted runtime, and admitting a request to a session or its Tasks only for the session's owner
|
|
30
|
-
|
|
29
|
+
hosted runtime, and admitting a request to a session or its Tasks only for the session's owner,
|
|
30
|
+
an Organization admin, or the workload User of the Agent that delegated to that child session,
|
|
31
|
+
which the platform's own records establish, never the `X-Caller-*` headers;
|
|
31
32
|
- presenting the verified caller assertion of the request that starts a chat or A2A Message turn
|
|
32
33
|
only when it marks that turn active, so that the platform can record the turn's requester, and
|
|
33
34
|
giving extension tools that requester and requester-bound calls through `current_requester()`
|
|
@@ -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
|
|
133
|
-
|
|
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(
|
|
@@ -46,9 +46,9 @@ TAU image, overlay, or SDK-owned deployment.
|
|
|
46
46
|
|
|
47
47
|
`create_app()` installs the runtime's request identity. When Main Sequence hosts the runtime, the
|
|
48
48
|
application admits only requests that carry the platform's signed assertion, lets only a session's
|
|
49
|
-
owner
|
|
50
|
-
`app.state.mainsequence_request_identity`. The platform launcher serves an application
|
|
51
|
-
that declaration, read from the object it serves. Therefore:
|
|
49
|
+
owner, an Organization admin, or the Agent that delegated to it address that session, and declares
|
|
50
|
+
all this in `app.state.mainsequence_request_identity`. The platform launcher serves an application
|
|
51
|
+
only with that declaration, read from the object it serves. Therefore:
|
|
52
52
|
|
|
53
53
|
- export the object `create_app()` returns as the module's `app`. An application that mounts it
|
|
54
54
|
inside another one declares nothing, and the launcher refuses it;
|
|
@@ -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,
|
|
@@ -930,7 +930,7 @@ def _requester_named_by_task(
|
|
|
930
930
|
answer must name the same User, as a person, as ``requester_user_uid``.
|
|
931
931
|
"""
|
|
932
932
|
|
|
933
|
-
if caller_assertion is None:
|
|
933
|
+
if caller_assertion is None or caller_assertion.caller_is_workload:
|
|
934
934
|
return None
|
|
935
935
|
caller = caller_assertion.caller
|
|
936
936
|
if task.requester_identity_type != "human" or task.requester_user_uid != caller.uid:
|
|
@@ -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
|
|
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
|
)
|
|
@@ -8,8 +8,9 @@ the kind its route requires: a platform assertion on ``/internal/*``, a caller a
|
|
|
8
8
|
other route. Handlers read the verified caller with ``current_caller()``, or from
|
|
9
9
|
``request.state.user`` and ``request.state.user_uid``.
|
|
10
10
|
|
|
11
|
-
A request that addresses an existing session must also come from the session's owner
|
|
12
|
-
Organization admin
|
|
11
|
+
A request that addresses an existing session must also come from the session's owner, from an
|
|
12
|
+
Organization admin, or, for a delegated child session, from the workload User of the Agent that
|
|
13
|
+
delegated to it; see ``require_session_access()``. Outside hosted mode nothing here changes how
|
|
13
14
|
requests are handled.
|
|
14
15
|
|
|
15
16
|
The raw caller assertion stays private to the request: ``request_caller_assertion()`` hands it
|
|
@@ -18,11 +19,13 @@ only to the turn that request starts, which presents it when it marks the turn a
|
|
|
18
19
|
|
|
19
20
|
from __future__ import annotations
|
|
20
21
|
|
|
22
|
+
import dataclasses
|
|
21
23
|
from collections.abc import Iterable
|
|
22
24
|
from contextvars import ContextVar
|
|
23
25
|
|
|
24
26
|
import structlog
|
|
25
27
|
from fastapi import FastAPI, HTTPException, Request
|
|
28
|
+
from pydantic import ValidationError
|
|
26
29
|
from starlette.responses import JSONResponse
|
|
27
30
|
from starlette.types import ASGIApp, Receive, Scope, Send
|
|
28
31
|
from starlette.websockets import WebSocketClose
|
|
@@ -37,8 +40,8 @@ from ms_tau_sdk.backend.assertions import (
|
|
|
37
40
|
VerifiedCaller,
|
|
38
41
|
)
|
|
39
42
|
from ms_tau_sdk.backend.client import MainSequenceClient
|
|
40
|
-
from ms_tau_sdk.backend.models import AgentSession
|
|
41
|
-
from ms_tau_sdk.errors import SessionNotFoundError
|
|
43
|
+
from ms_tau_sdk.backend.models import AgentSession, DirectoryUser
|
|
44
|
+
from ms_tau_sdk.errors import SessionNotFoundError, TauSDKError
|
|
42
45
|
from ms_tau_sdk.logging import bind_request_identity_log_fields
|
|
43
46
|
from ms_tau_sdk.runtime.provenance import CallerIdentityError
|
|
44
47
|
from ms_tau_sdk.settings import TauSDKSettings
|
|
@@ -66,6 +69,12 @@ _current_caller: ContextVar[VerifiedCaller | None] = ContextVar(
|
|
|
66
69
|
# The verified raw caller assertion is kept in the request's own scope, not in request.state and
|
|
67
70
|
# not in a context variable, so that only the code that starts the request's turn reads it.
|
|
68
71
|
_CALLER_ASSERTION_SCOPE_KEY = "ms_tau_sdk.caller_assertion"
|
|
72
|
+
# The workload Users that one request looked up, by User UID: the workload, or None for a User
|
|
73
|
+
# that is not a workload or whose lookup failed. It lives only as long as its request.
|
|
74
|
+
_workload_lookups: ContextVar[dict[str, DirectoryUser | None] | None] = ContextVar(
|
|
75
|
+
"ms_tau_sdk_workload_lookups",
|
|
76
|
+
default=None,
|
|
77
|
+
)
|
|
69
78
|
|
|
70
79
|
|
|
71
80
|
class SessionAccessDeniedError(HTTPException):
|
|
@@ -85,15 +94,24 @@ def current_caller() -> VerifiedCaller | None:
|
|
|
85
94
|
|
|
86
95
|
|
|
87
96
|
def request_caller_assertion(request: Request) -> CallerAssertion | None:
|
|
88
|
-
"""Return the verified caller assertion of ``request`` for the turn it starts.
|
|
97
|
+
"""Return the verified caller assertion of ``request`` for the turn or Task it starts.
|
|
89
98
|
|
|
90
|
-
It is None outside hosted mode and on the platform's own calls. Only the chat and A2A
|
|
91
|
-
|
|
92
|
-
or passed to the model, a tool, a stream, or history.
|
|
99
|
+
It is None outside hosted mode and on the platform's own calls. Only the chat and A2A routes
|
|
100
|
+
read it, to present it when they mark their turn active or create or continue a Task; it is
|
|
101
|
+
never logged, persisted, or passed to the model, a tool, a stream, or history. A caller this
|
|
102
|
+
request admitted as the Agent that delegated to the session comes back marked as a workload,
|
|
103
|
+
which is never a requester.
|
|
93
104
|
"""
|
|
94
105
|
|
|
95
106
|
value = request.scope.get(_CALLER_ASSERTION_SCOPE_KEY)
|
|
96
|
-
|
|
107
|
+
if not isinstance(value, CallerAssertion):
|
|
108
|
+
return None
|
|
109
|
+
lookups = _workload_lookups.get()
|
|
110
|
+
if lookups is not None and lookups.get(value.caller.uid) is not None:
|
|
111
|
+
# The request was admitted as the Agent that delegated to the session: its assertion names
|
|
112
|
+
# a workload, never a requester.
|
|
113
|
+
return dataclasses.replace(value, caller_is_workload=True)
|
|
114
|
+
return value
|
|
97
115
|
|
|
98
116
|
|
|
99
117
|
def verified_user_uid(settings: TauSDKSettings) -> str | None:
|
|
@@ -111,15 +129,63 @@ def verified_user_uid(settings: TauSDKSettings) -> str | None:
|
|
|
111
129
|
return caller.uid
|
|
112
130
|
|
|
113
131
|
|
|
132
|
+
async def _workload_of(client: MainSequenceClient, user_uid: str) -> DirectoryUser | None:
|
|
133
|
+
"""Return the workload User ``user_uid`` names, as the platform's directory shows it, or None.
|
|
134
|
+
|
|
135
|
+
A User that is not a workload, and a lookup that fails, give None, so the check fails closed.
|
|
136
|
+
The answer is kept for the rest of the request only.
|
|
137
|
+
"""
|
|
138
|
+
|
|
139
|
+
lookups = _workload_lookups.get()
|
|
140
|
+
if lookups is not None and user_uid in lookups:
|
|
141
|
+
return lookups[user_uid]
|
|
142
|
+
workload: DirectoryUser | None = None
|
|
143
|
+
try:
|
|
144
|
+
found = await client.get_user(user_uid)
|
|
145
|
+
except (TauSDKError, ValidationError) as error:
|
|
146
|
+
logger.warning(
|
|
147
|
+
"request_identity.caller_lookup.failed",
|
|
148
|
+
message="The caller's User could not be looked up; the session is refused",
|
|
149
|
+
error_type=type(error).__name__,
|
|
150
|
+
)
|
|
151
|
+
else:
|
|
152
|
+
if found.uid == user_uid and found.identity_type == "workload" and found.agent_uid:
|
|
153
|
+
workload = found
|
|
154
|
+
if lookups is not None:
|
|
155
|
+
lookups[user_uid] = workload
|
|
156
|
+
return workload
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
async def _delegated_to_caller(
|
|
160
|
+
client: MainSequenceClient,
|
|
161
|
+
session: AgentSession,
|
|
162
|
+
caller: VerifiedCaller,
|
|
163
|
+
) -> bool:
|
|
164
|
+
"""Whether ``session`` is a child session that the caller's own Agent delegated to.
|
|
165
|
+
|
|
166
|
+
The session must name its parent session's Agent, the caller must be a workload User as the
|
|
167
|
+
platform's directory shows it, and that workload must serve the same Agent. No request header
|
|
168
|
+
is read.
|
|
169
|
+
"""
|
|
170
|
+
|
|
171
|
+
parent_agent_uid = session.parent_session_agent_uid
|
|
172
|
+
if not parent_agent_uid:
|
|
173
|
+
return False
|
|
174
|
+
workload = await _workload_of(client, caller.uid)
|
|
175
|
+
return workload is not None and workload.agent_uid == parent_agent_uid
|
|
176
|
+
|
|
177
|
+
|
|
114
178
|
async def require_session_access(
|
|
115
179
|
client: MainSequenceClient,
|
|
116
180
|
session_uid: str,
|
|
117
181
|
) -> AgentSession | None:
|
|
118
|
-
"""Admit the current request to ``session_uid`` only for
|
|
182
|
+
"""Admit the current request to ``session_uid`` only for a caller allowed to address it.
|
|
119
183
|
|
|
120
|
-
|
|
121
|
-
``created_by_user_uid
|
|
122
|
-
|
|
184
|
+
That is the session's owner, the User the platform recorded as its creator
|
|
185
|
+
(``created_by_user_uid``); an Organization admin; or, for a delegated child session, the
|
|
186
|
+
workload User of the Agent that delegated to it (``parent_session_agent_uid``). The check
|
|
187
|
+
applies in hosted mode only and raises ``SessionAccessDeniedError`` (403). It returns the
|
|
188
|
+
session when it had to read it.
|
|
123
189
|
"""
|
|
124
190
|
|
|
125
191
|
if client.settings.request_identity_mode != "assertion":
|
|
@@ -130,16 +196,19 @@ async def require_session_access(
|
|
|
130
196
|
if caller.is_organization_admin:
|
|
131
197
|
return None
|
|
132
198
|
session = await client.get_session(session_uid)
|
|
133
|
-
if session.created_by_user_uid
|
|
134
|
-
|
|
135
|
-
|
|
199
|
+
if session.created_by_user_uid == caller.uid:
|
|
200
|
+
return session
|
|
201
|
+
if await _delegated_to_caller(client, session, caller):
|
|
202
|
+
return session
|
|
203
|
+
raise SessionAccessDeniedError()
|
|
136
204
|
|
|
137
205
|
|
|
138
206
|
async def sessions_the_caller_may_address(
|
|
139
207
|
client: MainSequenceClient,
|
|
140
208
|
session_uids: Iterable[str],
|
|
141
209
|
) -> set[str] | None:
|
|
142
|
-
"""Return which of ``session_uids`` the current caller may address
|
|
210
|
+
"""Return which of ``session_uids`` the current caller may address, by the rule of
|
|
211
|
+
``require_session_access()``.
|
|
143
212
|
|
|
144
213
|
None means every session: outside hosted mode, or for an Organization admin. A session that
|
|
145
214
|
no longer exists is left out.
|
|
@@ -160,7 +229,9 @@ async def sessions_the_caller_may_address(
|
|
|
160
229
|
session = await client.get_session(session_uid)
|
|
161
230
|
except SessionNotFoundError:
|
|
162
231
|
continue
|
|
163
|
-
if session.created_by_user_uid == caller.uid
|
|
232
|
+
if session.created_by_user_uid == caller.uid or await _delegated_to_caller(
|
|
233
|
+
client, session, caller
|
|
234
|
+
):
|
|
164
235
|
allowed.add(session_uid)
|
|
165
236
|
return allowed
|
|
166
237
|
|
|
@@ -240,9 +311,11 @@ class RequestIdentityMiddleware:
|
|
|
240
311
|
token=token,
|
|
241
312
|
)
|
|
242
313
|
caller_token = _current_caller.set(caller)
|
|
314
|
+
lookups_token = _workload_lookups.set({})
|
|
243
315
|
try:
|
|
244
316
|
await self.app(scope, receive, send)
|
|
245
317
|
finally:
|
|
318
|
+
_workload_lookups.reset(lookups_token)
|
|
246
319
|
_current_caller.reset(caller_token)
|
|
247
320
|
scope.pop(_CALLER_ASSERTION_SCOPE_KEY, None)
|
|
248
321
|
|
|
@@ -113,6 +113,9 @@ class CallerAssertion:
|
|
|
113
113
|
caller: VerifiedCaller
|
|
114
114
|
expires_at: int
|
|
115
115
|
token: str = field(repr=False)
|
|
116
|
+
# True when the platform's directory showed the caller to be a workload User. A workload is
|
|
117
|
+
# never a turn's or a Task's requester.
|
|
118
|
+
caller_is_workload: bool = False
|
|
116
119
|
|
|
117
120
|
def unexpired_token(self, *, now: float | None = None) -> str | None:
|
|
118
121
|
"""Return the raw assertion while it is valid, and None once it has expired."""
|
|
@@ -35,6 +35,7 @@ from .models import (
|
|
|
35
35
|
AgentTaskEventPage,
|
|
36
36
|
AgentTaskExecutionAttempt,
|
|
37
37
|
AgentTaskSnapshot,
|
|
38
|
+
DirectoryUser,
|
|
38
39
|
LocalChatSessionPage,
|
|
39
40
|
LocalChatTranscript,
|
|
40
41
|
LocalConversationMessagePage,
|
|
@@ -77,6 +78,7 @@ from .routes import (
|
|
|
77
78
|
agent_task_operation,
|
|
78
79
|
model_provider_credentials,
|
|
79
80
|
resource_release_runtime_access,
|
|
81
|
+
user,
|
|
80
82
|
)
|
|
81
83
|
|
|
82
84
|
T = TypeVar("T")
|
|
@@ -404,6 +406,12 @@ class MainSequenceClient:
|
|
|
404
406
|
detail=error.errors(include_input=False),
|
|
405
407
|
) from error
|
|
406
408
|
|
|
409
|
+
async def get_user(self, user_uid: str) -> DirectoryUser:
|
|
410
|
+
"""Look a User of the Organization up, as the platform's directory shows it."""
|
|
411
|
+
|
|
412
|
+
data = await self._request("GET", user(user_uid), idempotent=True)
|
|
413
|
+
return DirectoryUser.model_validate(data)
|
|
414
|
+
|
|
407
415
|
async def list_model_providers(
|
|
408
416
|
self, *, organization_environment_uid: str | None = None
|
|
409
417
|
) -> dict[str, Any]:
|
|
@@ -74,6 +74,9 @@ class AgentSession(BackendModel):
|
|
|
74
74
|
)
|
|
75
75
|
status: str | None = None
|
|
76
76
|
created_by_user_uid: str | None = None
|
|
77
|
+
# The Agent of the parent session, or None for a session without a parent. The Agent that
|
|
78
|
+
# delegated to a child session addresses it with its own workload User.
|
|
79
|
+
parent_session_agent_uid: str | None = None
|
|
77
80
|
runtime_capabilities: dict[str, str] = Field(default_factory=dict)
|
|
78
81
|
|
|
79
82
|
@model_validator(mode="after")
|
|
@@ -87,6 +90,18 @@ class AgentSession(BackendModel):
|
|
|
87
90
|
return self
|
|
88
91
|
|
|
89
92
|
|
|
93
|
+
class DirectoryUser(BackendModel):
|
|
94
|
+
"""A User of the Organization as the platform's directory shows it to the runtime.
|
|
95
|
+
|
|
96
|
+
A workload User's row carries ``identity_type`` ``workload`` and, for the workload of an
|
|
97
|
+
Agent's release, ``agent_uid``.
|
|
98
|
+
"""
|
|
99
|
+
|
|
100
|
+
uid: str
|
|
101
|
+
identity_type: str | None = None
|
|
102
|
+
agent_uid: str | None = None
|
|
103
|
+
|
|
104
|
+
|
|
90
105
|
class AgentCardEnvelope(BackendModel):
|
|
91
106
|
agent_session_uid: str
|
|
92
107
|
agent_uid: str
|
|
@@ -280,7 +295,8 @@ class RuntimeState(BackendModel):
|
|
|
280
295
|
cancel_requested: bool = False
|
|
281
296
|
cancellation_id: str | None = None
|
|
282
297
|
# The person the platform recorded as the active turn's requester, from the caller assertion
|
|
283
|
-
# the runtime presented when it marked the turn active.
|
|
298
|
+
# the runtime presented, or the caller delivery it named, when it marked the turn active.
|
|
299
|
+
# None when it recorded nobody.
|
|
284
300
|
requester_user_uid: str | None = None
|
|
285
301
|
|
|
286
302
|
@model_validator(mode="after")
|
|
@@ -307,6 +323,9 @@ class RuntimeActivityPatch(BackendRequestModel):
|
|
|
307
323
|
activity_sequence: int | None = Field(default=None, ge=1)
|
|
308
324
|
runtime_activity: AgentRuntimeActivity
|
|
309
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
|
|
310
329
|
|
|
311
330
|
@model_validator(mode="after")
|
|
312
331
|
def validate_concurrency_mode(self) -> RuntimeActivityPatch:
|
|
@@ -12,6 +12,7 @@ MODEL_PROVIDER_CREDENTIALS = f"{API_V1_PREFIX}/model-provider-credentials/"
|
|
|
12
12
|
MODEL_PROVIDERS = f"{API_V1_PREFIX}/model-providers/"
|
|
13
13
|
AGENT_TASKS = f"{API_V1_PREFIX}/agent-tasks/"
|
|
14
14
|
RESOURCE_RELEASES = f"{API_V1_PREFIX}/resource-releases/"
|
|
15
|
+
USERS = f"{API_V1_PREFIX}/users/"
|
|
15
16
|
|
|
16
17
|
type RuntimeLeaseOperation = Literal["acquire", "renew", "release"]
|
|
17
18
|
type ModelProviderCredentialOperation = Literal["hydrate", "status", "flush", "revoke"]
|
|
@@ -92,3 +93,7 @@ def agent_task_operation(task_uid: str, operation: AgentTaskOperation) -> str:
|
|
|
92
93
|
|
|
93
94
|
def resource_release_runtime_access(release_uid: str) -> str:
|
|
94
95
|
return f"{RESOURCE_RELEASES}{release_uid}/resolve-runtime-access/"
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def user(user_uid: str) -> str:
|
|
99
|
+
return f"{USERS}{user_uid}/"
|
|
@@ -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
|
|
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
|
-
**
|
|
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
|
|
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,20 +1740,28 @@ 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
|
|
1732
|
-
if
|
|
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
|
|
1764
|
+
if caller_assertion is None or caller_assertion.caller_is_workload:
|
|
1733
1765
|
return None
|
|
1734
1766
|
caller = caller_assertion.caller
|
|
1735
1767
|
if runtime.requester_user_uid != caller.uid:
|
|
@@ -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
|
)
|
|
File without changes
|
{ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev38}/packages/tau-board/src/ms_tau_board/__init__.py
RENAMED
|
File without changes
|
{ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev38}/packages/tau-board/src/ms_tau_board/app.py
RENAMED
|
File without changes
|
{ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev38}/packages/tau-board/src/ms_tau_board/cli.py
RENAMED
|
File without changes
|
{ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev38}/packages/tau-board/src/ms_tau_board/config.py
RENAMED
|
File without changes
|
{ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev38}/packages/tau-board/src/ms_tau_board/env_file.py
RENAMED
|
File without changes
|
{ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev38}/packages/tau-board/src/ms_tau_board/logs.py
RENAMED
|
File without changes
|
{ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev38}/packages/tau-board/src/ms_tau_board/proxy.py
RENAMED
|
File without changes
|
{ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev38}/packages/tau-board/src/ms_tau_board/state.py
RENAMED
|
File without changes
|
|
File without changes
|
{ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev38}/packages/tau-board/src/ms_tau_board/static/app.js
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev38}/src/ms_tau_sdk/runtime/deployment_health.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|