ms-tau-sdk 2.0.3.dev35__tar.gz → 2.0.3.dev37__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.dev35 → ms_tau_sdk-2.0.3.dev37}/CHANGELOG.md +53 -0
- {ms_tau_sdk-2.0.3.dev35 → ms_tau_sdk-2.0.3.dev37}/PKG-INFO +8 -2
- {ms_tau_sdk-2.0.3.dev35 → ms_tau_sdk-2.0.3.dev37}/README.md +7 -1
- {ms_tau_sdk-2.0.3.dev35 → ms_tau_sdk-2.0.3.dev37}/pyproject.toml +1 -1
- {ms_tau_sdk-2.0.3.dev35 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/__init__.py +3 -0
- {ms_tau_sdk-2.0.3.dev35 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/agent_skills/tau_a2a_runtime_adapter/SKILL.md +7 -2
- {ms_tau_sdk-2.0.3.dev35 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/agent_skills/tau_project_customization/SKILL.md +72 -1
- {ms_tau_sdk-2.0.3.dev35 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/agent_skills/tau_repository_integration/SKILL.md +3 -3
- {ms_tau_sdk-2.0.3.dev35 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/api/a2a.py +95 -2
- {ms_tau_sdk-2.0.3.dev35 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/api/chat.py +13 -3
- {ms_tau_sdk-2.0.3.dev35 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/api/request_identity.py +118 -17
- {ms_tau_sdk-2.0.3.dev35 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/backend/assertions.py +24 -1
- {ms_tau_sdk-2.0.3.dev35 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/backend/client.py +203 -6
- {ms_tau_sdk-2.0.3.dev35 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/backend/local.py +49 -2
- {ms_tau_sdk-2.0.3.dev35 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/backend/models.py +40 -0
- {ms_tau_sdk-2.0.3.dev35 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/backend/routes.py +10 -0
- {ms_tau_sdk-2.0.3.dev35 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/runtime/manager.py +110 -1
- ms_tau_sdk-2.0.3.dev37/src/ms_tau_sdk/runtime/requester.py +563 -0
- {ms_tau_sdk-2.0.3.dev35 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/runtime/session.py +2 -0
- {ms_tau_sdk-2.0.3.dev35 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/runtime/task_context.py +4 -0
- {ms_tau_sdk-2.0.3.dev35 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/sessions/storage.py +8 -0
- {ms_tau_sdk-2.0.3.dev35 → ms_tau_sdk-2.0.3.dev37}/.gitignore +0 -0
- {ms_tau_sdk-2.0.3.dev35 → ms_tau_sdk-2.0.3.dev37}/packages/tau-board/src/ms_tau_board/__init__.py +0 -0
- {ms_tau_sdk-2.0.3.dev35 → ms_tau_sdk-2.0.3.dev37}/packages/tau-board/src/ms_tau_board/app.py +0 -0
- {ms_tau_sdk-2.0.3.dev35 → ms_tau_sdk-2.0.3.dev37}/packages/tau-board/src/ms_tau_board/cli.py +0 -0
- {ms_tau_sdk-2.0.3.dev35 → ms_tau_sdk-2.0.3.dev37}/packages/tau-board/src/ms_tau_board/config.py +0 -0
- {ms_tau_sdk-2.0.3.dev35 → ms_tau_sdk-2.0.3.dev37}/packages/tau-board/src/ms_tau_board/env_file.py +0 -0
- {ms_tau_sdk-2.0.3.dev35 → ms_tau_sdk-2.0.3.dev37}/packages/tau-board/src/ms_tau_board/logs.py +0 -0
- {ms_tau_sdk-2.0.3.dev35 → ms_tau_sdk-2.0.3.dev37}/packages/tau-board/src/ms_tau_board/proxy.py +0 -0
- {ms_tau_sdk-2.0.3.dev35 → ms_tau_sdk-2.0.3.dev37}/packages/tau-board/src/ms_tau_board/state.py +0 -0
- {ms_tau_sdk-2.0.3.dev35 → ms_tau_sdk-2.0.3.dev37}/packages/tau-board/src/ms_tau_board/static/BULMA-LICENSE.txt +0 -0
- {ms_tau_sdk-2.0.3.dev35 → ms_tau_sdk-2.0.3.dev37}/packages/tau-board/src/ms_tau_board/static/app.js +0 -0
- {ms_tau_sdk-2.0.3.dev35 → ms_tau_sdk-2.0.3.dev37}/packages/tau-board/src/ms_tau_board/static/board.css +0 -0
- {ms_tau_sdk-2.0.3.dev35 → ms_tau_sdk-2.0.3.dev37}/packages/tau-board/src/ms_tau_board/static/bulma.min.css +0 -0
- {ms_tau_sdk-2.0.3.dev35 → ms_tau_sdk-2.0.3.dev37}/packages/tau-board/src/ms_tau_board/static/index.html +0 -0
- {ms_tau_sdk-2.0.3.dev35 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/agent_skills/tau_local_development/SKILL.md +0 -0
- {ms_tau_sdk-2.0.3.dev35 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/api/__init__.py +0 -0
- {ms_tau_sdk-2.0.3.dev35 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/api/conversations.py +0 -0
- {ms_tau_sdk-2.0.3.dev35 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/api/dependencies.py +0 -0
- {ms_tau_sdk-2.0.3.dev35 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/api/health.py +0 -0
- {ms_tau_sdk-2.0.3.dev35 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/api/inspection.py +0 -0
- {ms_tau_sdk-2.0.3.dev35 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/api/local_chat.py +0 -0
- {ms_tau_sdk-2.0.3.dev35 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/api/models.py +0 -0
- {ms_tau_sdk-2.0.3.dev35 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/api/sessions.py +0 -0
- {ms_tau_sdk-2.0.3.dev35 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/app.py +0 -0
- {ms_tau_sdk-2.0.3.dev35 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/application.py +0 -0
- {ms_tau_sdk-2.0.3.dev35 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/backend/__init__.py +0 -0
- {ms_tau_sdk-2.0.3.dev35 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/backend/auth.py +0 -0
- {ms_tau_sdk-2.0.3.dev35 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/backend/mcp.py +0 -0
- {ms_tau_sdk-2.0.3.dev35 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/cli.py +0 -0
- {ms_tau_sdk-2.0.3.dev35 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/errors.py +0 -0
- {ms_tau_sdk-2.0.3.dev35 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/logging.py +0 -0
- {ms_tau_sdk-2.0.3.dev35 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/protocols/__init__.py +0 -0
- {ms_tau_sdk-2.0.3.dev35 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/protocols/a2a_failure.py +0 -0
- {ms_tau_sdk-2.0.3.dev35 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/protocols/a2a_message.py +0 -0
- {ms_tau_sdk-2.0.3.dev35 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/protocols/a2a_roles.py +0 -0
- {ms_tau_sdk-2.0.3.dev35 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/protocols/assistant_ui.py +0 -0
- {ms_tau_sdk-2.0.3.dev35 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/protocols/chat_history.py +0 -0
- {ms_tau_sdk-2.0.3.dev35 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/protocols/strict_json.py +0 -0
- {ms_tau_sdk-2.0.3.dev35 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/providers/__init__.py +0 -0
- {ms_tau_sdk-2.0.3.dev35 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/providers/definitions.py +0 -0
- {ms_tau_sdk-2.0.3.dev35 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/providers/factory.py +0 -0
- {ms_tau_sdk-2.0.3.dev35 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/providers/tau_compat.py +0 -0
- {ms_tau_sdk-2.0.3.dev35 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/resources/SYSTEM.md +0 -0
- {ms_tau_sdk-2.0.3.dev35 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/resources/__init__.py +0 -0
- {ms_tau_sdk-2.0.3.dev35 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/resources/loader.py +0 -0
- {ms_tau_sdk-2.0.3.dev35 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/resources/prompts/review-code-repository.md +0 -0
- {ms_tau_sdk-2.0.3.dev35 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/runtime/__init__.py +0 -0
- {ms_tau_sdk-2.0.3.dev35 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/runtime/deployment_health.py +0 -0
- {ms_tau_sdk-2.0.3.dev35 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/runtime/events.py +0 -0
- {ms_tau_sdk-2.0.3.dev35 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/runtime/extensions.py +0 -0
- {ms_tau_sdk-2.0.3.dev35 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/runtime/failures.py +0 -0
- {ms_tau_sdk-2.0.3.dev35 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/runtime/live_turns.py +0 -0
- {ms_tau_sdk-2.0.3.dev35 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/runtime/observability.py +0 -0
- {ms_tau_sdk-2.0.3.dev35 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/runtime/provenance.py +0 -0
- {ms_tau_sdk-2.0.3.dev35 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/runtime/secret_entry.py +0 -0
- {ms_tau_sdk-2.0.3.dev35 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/runtime/snapshots.py +0 -0
- {ms_tau_sdk-2.0.3.dev35 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/sessions/__init__.py +0 -0
- {ms_tau_sdk-2.0.3.dev35 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/settings.py +0 -0
- {ms_tau_sdk-2.0.3.dev35 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/skills.py +0 -0
- {ms_tau_sdk-2.0.3.dev35 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/tools/__init__.py +0 -0
- {ms_tau_sdk-2.0.3.dev35 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/tools/mainsequence_mcp.py +0 -0
- {ms_tau_sdk-2.0.3.dev35 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/tools/secret_entry.py +0 -0
- {ms_tau_sdk-2.0.3.dev35 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/tools/skill_read.py +0 -0
- {ms_tau_sdk-2.0.3.dev35 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/tools/task_control.py +0 -0
|
@@ -11,6 +11,59 @@
|
|
|
11
11
|
and its result are unchanged. Direct-runtime clients that call the operation themselves are
|
|
12
12
|
unaffected. See the 2026-10-06 amendment of ADR 0002
|
|
13
13
|
([issue #65](https://github.com/mainsequence-sdk/ms-tau-sdk/issues/65)).
|
|
14
|
+
- Extension tools can read with the access of the person a turn serves, its requester, when an
|
|
15
|
+
Organization admin enabled the Agent for it. When a hosted runtime starts a chat or A2A Message
|
|
16
|
+
turn, it sends the verified caller assertion of the request in `X-MainSequence-Caller-Assertion`
|
|
17
|
+
with the `PATCH /api/v1/agent-sessions/<uid>/tau-runtime-activity/` transition that marks the
|
|
18
|
+
turn active. The platform answers with `requester_user_uid`, the person it recorded as the turn's
|
|
19
|
+
requester, or null. A hosted request that creates a Task (`POST /api/v1/agent-tasks/`) or
|
|
20
|
+
continues one (`POST /api/v1/agent-tasks/<uid>/continue/`) sends the same header with that call,
|
|
21
|
+
so that the platform records the Task's requester. A Task attempt that the request runs itself
|
|
22
|
+
serves the request's verified caller when the platform's Task answer names that User as
|
|
23
|
+
`requester_user_uid` with `requester_identity_type` `human`; a Task attempt started by the
|
|
24
|
+
platform's dispatch takes its requester from the dispatch's same two facts. Two new public
|
|
25
|
+
functions:
|
|
26
|
+
- `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
|
|
28
|
+
outside a turn;
|
|
29
|
+
- `requester_client()` returns a client bound to the turn. `request(method, path, ...)` calls a
|
|
30
|
+
platform API path with the runtime's credential and `X-MainSequence-Acting-For-Session`,
|
|
31
|
+
`X-MainSequence-Lease-Holder` and `X-MainSequence-Lease-Token`, sent only to the platform base
|
|
32
|
+
URL. `call_release(release_uid, method, path, ...)` obtains access through
|
|
33
|
+
`resolve-runtime-access` and calls the application with only its bearer token, kept for the
|
|
34
|
+
turn while it is valid. A 403 whose `code` is `requester_binding_invalid` or starts with
|
|
35
|
+
`runtime_lease_` raises a `PermissionError` with that code, and without a requester
|
|
36
|
+
`requester_client()` raises one at once.
|
|
37
|
+
|
|
38
|
+
The assertion stays in its request's scope and goes only to the turn start, Task creation or Task
|
|
39
|
+
continuation of that request. It is not sent once expired, never by the turn start of a Task
|
|
40
|
+
attempt, never on the platform's own calls, and never in local mode. The assertion, the lease
|
|
41
|
+
proof, the runtime credential and application tokens never reach a log line, a persisted entry,
|
|
42
|
+
model context, a tool result, the UI stream or history, and the binding ends with the turn. The
|
|
43
|
+
`tau-project-customization` skill teaches tools to use both functions, to return only business
|
|
44
|
+
results, and states what people are told about this access. Upgrade note: nothing changes for
|
|
45
|
+
an Agent that does not use them. Requester-bound calls need a platform that records turn and Task
|
|
46
|
+
requesters; until it does, `current_requester()` returns `None`. See the 2026-10-06 amendment of
|
|
47
|
+
ADR 0019 ([issue #66](https://github.com/mainsequence-sdk/ms-tau-sdk/issues/66)).
|
|
48
|
+
- `MainSequenceClient.list_model_providers(organization_environment_uid=...)` reads the
|
|
49
|
+
model-provider catalog of that Organization Environment. It raised `TypeError` instead, so
|
|
50
|
+
local-mode chat could not list the providers of a configured Environment through
|
|
51
|
+
`GET /api/chat/model-providers`. The Environment is now sent as the `organization_environment_uid`
|
|
52
|
+
query parameter, as the other list calls send theirs; the call without an Environment is
|
|
53
|
+
unchanged ([issue #68](https://github.com/mainsequence-sdk/ms-tau-sdk/issues/68)).
|
|
54
|
+
- A hosted runtime admits the Agent that delegated to a child session. When Agent A delegates to
|
|
55
|
+
Agent B through `a2a.send_message`, the platform creates B's child session for the person who
|
|
56
|
+
owns A's session, and A addresses it with its own credential, so the caller assertion names A's
|
|
57
|
+
workload User. B's runtime refused every such request with 403. It now also admits that caller
|
|
58
|
+
when the session's `parent_session_agent_uid` names an Agent and the platform's directory
|
|
59
|
+
(`GET /api/v1/users/<uid>/`, read with the runtime credential) shows the caller as a workload
|
|
60
|
+
User whose `agent_uid` is that Agent: Message send and stream, JSON-RPC, and Task continuation,
|
|
61
|
+
reads, list and cancel. The `X-Caller-*` headers never admit a caller. A session without a
|
|
62
|
+
parent, another Agent's workload, and a person who is not the owner still get 403, and a lookup
|
|
63
|
+
that fails refuses the request. One request looks a User up at most once, and nothing is kept
|
|
64
|
+
after it. The admitted Agent is never a requester: `current_requester()` stays `None` in its
|
|
65
|
+
turns and in the Task attempts its requests run. See the 2026-10-06 amendment of ADR 0019
|
|
66
|
+
([issue #67](https://github.com/mainsequence-sdk/ms-tau-sdk/issues/67)).
|
|
14
67
|
|
|
15
68
|
## 2.0.2 — 2026-10-06
|
|
16
69
|
|
|
@@ -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.dev37
|
|
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,12 +126,18 @@ 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
|
|
133
134
|
[request identity](docs/reference/runtime-contract.md#request-identity).
|
|
134
135
|
|
|
136
|
+
An Agent that an Organization admin enabled for it can read with the access of the person whose
|
|
137
|
+
request a turn is serving. Project tools read that person with `current_requester()` and call the
|
|
138
|
+
platform or another platform application for them with `requester_client()`; neither exposes a
|
|
139
|
+
proof or a token. See the [public API](docs/reference/public-api.md#current_requester-and-requester_client).
|
|
140
|
+
|
|
135
141
|
Job-hosted batch execution is an [accepted design](docs/adrs/0015-job-hosted-batch-execution.md)
|
|
136
142
|
with implementation pending. It will run this SDK's configured Tau composition for one assignment
|
|
137
143
|
inside a project Job, close it, and exit without starting Uvicorn. The Job continues to own its
|
|
@@ -93,12 +93,18 @@ 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
|
|
100
101
|
[request identity](docs/reference/runtime-contract.md#request-identity).
|
|
101
102
|
|
|
103
|
+
An Agent that an Organization admin enabled for it can read with the access of the person whose
|
|
104
|
+
request a turn is serving. Project tools read that person with `current_requester()` and call the
|
|
105
|
+
platform or another platform application for them with `requester_client()`; neither exposes a
|
|
106
|
+
proof or a token. See the [public API](docs/reference/public-api.md#current_requester-and-requester_client).
|
|
107
|
+
|
|
102
108
|
Job-hosted batch execution is an [accepted design](docs/adrs/0015-job-hosted-batch-execution.md)
|
|
103
109
|
with implementation pending. It will run this SDK's configured Tau composition for one assignment
|
|
104
110
|
inside a project Job, close it, and exit without starting Uvicorn. The Job continues to own its
|
|
@@ -9,6 +9,7 @@ from .runtime.deployment_health import (
|
|
|
9
9
|
RUNTIME_HEALTH_ABI_VERSION,
|
|
10
10
|
register_deployment_readiness_hook,
|
|
11
11
|
)
|
|
12
|
+
from .runtime.requester import current_requester, requester_client
|
|
12
13
|
from .settings import TauSDKSettings
|
|
13
14
|
|
|
14
15
|
__all__ = [
|
|
@@ -16,5 +17,7 @@ __all__ = [
|
|
|
16
17
|
"TauSDKSettings",
|
|
17
18
|
"__version__",
|
|
18
19
|
"create_app",
|
|
20
|
+
"current_requester",
|
|
19
21
|
"register_deployment_readiness_hook",
|
|
22
|
+
"requester_client",
|
|
20
23
|
]
|
|
@@ -26,8 +26,13 @@ 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;
|
|
32
|
+
- presenting the verified caller assertion of the request that starts a chat or A2A Message turn
|
|
33
|
+
only when it marks that turn active, so that the platform can record the turn's requester, and
|
|
34
|
+
giving extension tools that requester and requester-bound calls through `current_requester()`
|
|
35
|
+
and `requester_client()` without exposing the assertion, the lease proof, or any token;
|
|
31
36
|
- translating inbound A2A requests into the shared TAU runtime; and
|
|
32
37
|
- translating runtime events and results into validated A2A responses.
|
|
33
38
|
|
|
@@ -87,7 +87,7 @@ Extensions may change effective agent capabilities, but they must not replace or
|
|
|
87
87
|
|
|
88
88
|
- Main Sequence runtime or user authentication;
|
|
89
89
|
- provider-control evidence and credential hydration;
|
|
90
|
-
- caller identity and active-session proof;
|
|
90
|
+
- caller identity, the turn's requester, and active-session proof;
|
|
91
91
|
- persistence ordering, leases, cancellation, or task settlement;
|
|
92
92
|
- secret redaction and payload limits; or
|
|
93
93
|
- HTTP, SSE, Responses, and A2A wire validation.
|
|
@@ -95,6 +95,77 @@ Extensions may change effective agent capabilities, but they must not replace or
|
|
|
95
95
|
When Main Sequence MCP is enabled, use its canonical operations for live platform state. Project
|
|
96
96
|
tools do not grant new platform permissions merely because they run inside TAU.
|
|
97
97
|
|
|
98
|
+
## Acting for the person a turn serves
|
|
99
|
+
|
|
100
|
+
An Agent that an Organization admin enabled for it can read platform data, and ask other platform
|
|
101
|
+
applications, with the access of the person whose request a turn is serving: the requester. The
|
|
102
|
+
platform keeps that authority and finds the person in its own records; the runtime only proves
|
|
103
|
+
which of its sessions it is working on. Extension tools use two SDK functions and nothing else:
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
import json
|
|
107
|
+
|
|
108
|
+
from tau_agent.messages import TextContent
|
|
109
|
+
from tau_agent.tools import AgentToolResult
|
|
110
|
+
|
|
111
|
+
from ms_tau_sdk import current_requester, requester_client
|
|
112
|
+
|
|
113
|
+
ANALYST_DATA_RELEASE_UID = "..." # the release of the application to ask
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
async def revenue_by_region(tool_call_id, arguments, signal=None, on_update=None):
|
|
117
|
+
if current_requester() is None:
|
|
118
|
+
return AgentToolResult(content=[TextContent(text="Ask me from your own conversation.")])
|
|
119
|
+
try:
|
|
120
|
+
answer = await requester_client().call_release(
|
|
121
|
+
ANALYST_DATA_RELEASE_UID,
|
|
122
|
+
"POST",
|
|
123
|
+
"/query",
|
|
124
|
+
json={"question": "revenue by region"},
|
|
125
|
+
)
|
|
126
|
+
except PermissionError:
|
|
127
|
+
return AgentToolResult(content=[TextContent(text="Your access for this request ended.")])
|
|
128
|
+
return AgentToolResult(content=[TextContent(text=json.dumps(answer.json()["rows"]))])
|
|
129
|
+
```
|
|
130
|
+
|
|
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:
|
|
134
|
+
never take a person's UID from tool arguments, the prompt, history, or a header.
|
|
135
|
+
- `requester_client()` returns a client bound to the turn. `await client.request("GET",
|
|
136
|
+
"/api/v1/...")` reads a platform API path for the requester. `await client.call_release(
|
|
137
|
+
release_uid, method, path, ...)` asks another platform application, which answers as the
|
|
138
|
+
requester. Both return an `httpx.Response`.
|
|
139
|
+
- Without a requester, `requester_client()` raises a `PermissionError`. So does a call that the
|
|
140
|
+
platform refuses because the requester's access ended (`code` `requester_binding_invalid` or
|
|
141
|
+
`runtime_lease_*`): the turn is over, the access was removed, more than 24 hours passed, or the
|
|
142
|
+
Agent is not enabled. Catch it, say in plain words that the request cannot be served, and never
|
|
143
|
+
fall back to the Agent's own access.
|
|
144
|
+
|
|
145
|
+
Rules for tools that act for the requester:
|
|
146
|
+
|
|
147
|
+
- Never handle proofs or tokens. The SDK attaches the session, the lease proof, and the
|
|
148
|
+
credentials itself. A tool never reads, logs, stores, or forwards them, passes a path rather than
|
|
149
|
+
a URL, and never sets `Authorization` or an `X-MainSequence-*` header.
|
|
150
|
+
- Requester-bound calls are read-only. The platform refuses writes, sharing, and Secret values;
|
|
151
|
+
do not build tools that try them.
|
|
152
|
+
- Return to the model only business results, such as rows, numbers, and names. Never return the
|
|
153
|
+
response object, its headers, a token, a proof, or a raw error body.
|
|
154
|
+
- Keep nothing for another turn or another person. The binding ends with the turn, and a task the
|
|
155
|
+
tool leaves running has no access afterwards.
|
|
156
|
+
- What a tool reads for a person belongs to that person's conversation. Do not write it to shared
|
|
157
|
+
stores, other Agents, or external systems.
|
|
158
|
+
|
|
159
|
+
People who use such an Agent are told:
|
|
160
|
+
|
|
161
|
+
> **This Agent works with your identity, securely.** It reads only what you can already read, only
|
|
162
|
+
> to answer your own requests, and for at most 24 hours after you ask. It cannot act as anyone else,
|
|
163
|
+
> cannot change, share or delete anything, never sees your secret values, and stops the moment your
|
|
164
|
+
> access ends. Your Organization's administrator approved it to work this way.
|
|
165
|
+
|
|
166
|
+
The limit is plain: while it works on your request, the Agent's code can read what you can read,
|
|
167
|
+
which is why only administrators decide which Agents may work this way.
|
|
168
|
+
|
|
98
169
|
## Validation
|
|
99
170
|
|
|
100
171
|
Verify each project-owned capability through its observable interface and focused tests. Check
|
|
@@ -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;
|
|
@@ -20,6 +20,7 @@ from fastapi.responses import JSONResponse, StreamingResponse
|
|
|
20
20
|
from structlog.contextvars import bind_contextvars
|
|
21
21
|
from tau_agent.types import JSONValue
|
|
22
22
|
|
|
23
|
+
from ms_tau_sdk.backend.assertions import CallerAssertion
|
|
23
24
|
from ms_tau_sdk.backend.client import MainSequenceClient
|
|
24
25
|
from ms_tau_sdk.backend.models import (
|
|
25
26
|
AgentTask,
|
|
@@ -62,6 +63,7 @@ from ms_tau_sdk.runtime.provenance import (
|
|
|
62
63
|
TurnProvenance,
|
|
63
64
|
turn_provenance_from_request,
|
|
64
65
|
)
|
|
66
|
+
from ms_tau_sdk.runtime.requester import Requester, canonical_requester_uid
|
|
65
67
|
from ms_tau_sdk.runtime.task_context import (
|
|
66
68
|
TaskExecutionContext,
|
|
67
69
|
active_task_execution,
|
|
@@ -72,6 +74,7 @@ from ms_tau_sdk.settings import TauSDKSettings
|
|
|
72
74
|
from .dependencies import backend, runtime_manager, settings
|
|
73
75
|
from .request_identity import (
|
|
74
76
|
SessionAccessDeniedError,
|
|
77
|
+
request_caller_assertion,
|
|
75
78
|
require_session_access,
|
|
76
79
|
sessions_the_caller_may_address,
|
|
77
80
|
verified_user_uid,
|
|
@@ -740,11 +743,14 @@ async def _collect_turn(
|
|
|
740
743
|
max_output_bytes: int,
|
|
741
744
|
provenance: TurnProvenance,
|
|
742
745
|
turn_uid: str | None = None,
|
|
746
|
+
caller_assertion: CallerAssertion | None = None,
|
|
743
747
|
) -> str:
|
|
744
748
|
accumulator = _TurnAccumulator(max_output_bytes=max_output_bytes)
|
|
745
749
|
prompt_options: dict[str, Any] = {"provenance": provenance}
|
|
746
750
|
if turn_uid is not None:
|
|
747
751
|
prompt_options["turn_uid"] = turn_uid
|
|
752
|
+
if caller_assertion is not None:
|
|
753
|
+
prompt_options["caller_assertion"] = caller_assertion
|
|
748
754
|
async for event in manager.prompt(context_id, prompt, **prompt_options):
|
|
749
755
|
accumulator.consume(event)
|
|
750
756
|
context = active_task_execution()
|
|
@@ -772,6 +778,7 @@ async def _collect_validated_turn(
|
|
|
772
778
|
max_output_bytes: int,
|
|
773
779
|
provenance: TurnProvenance,
|
|
774
780
|
turn_uid: str | None = None,
|
|
781
|
+
caller_assertion: CallerAssertion | None = None,
|
|
775
782
|
) -> str:
|
|
776
783
|
text = await _collect_turn(
|
|
777
784
|
manager,
|
|
@@ -780,6 +787,7 @@ async def _collect_validated_turn(
|
|
|
780
787
|
max_output_bytes=max_output_bytes,
|
|
781
788
|
provenance=provenance,
|
|
782
789
|
turn_uid=turn_uid,
|
|
790
|
+
caller_assertion=caller_assertion,
|
|
783
791
|
)
|
|
784
792
|
if not contract.enabled:
|
|
785
793
|
return text
|
|
@@ -809,6 +817,7 @@ async def _collect_validated_turn(
|
|
|
809
817
|
),
|
|
810
818
|
max_output_bytes=max_output_bytes,
|
|
811
819
|
provenance=provenance,
|
|
820
|
+
caller_assertion=caller_assertion,
|
|
812
821
|
)
|
|
813
822
|
raise AssertionError("strict JSON repair loop did not terminate")
|
|
814
823
|
|
|
@@ -855,6 +864,7 @@ async def _create_backend_task(
|
|
|
855
864
|
local_mode: bool,
|
|
856
865
|
output_contract: StrictJsonContract | None = None,
|
|
857
866
|
provenance: TurnProvenance | None = None,
|
|
867
|
+
caller_assertion: CallerAssertion | None = None,
|
|
858
868
|
) -> AgentTaskCreateResult:
|
|
859
869
|
context_id = str(message["contextId"])
|
|
860
870
|
initial_message = _task_message_for_backend(
|
|
@@ -883,10 +893,51 @@ async def _create_backend_task(
|
|
|
883
893
|
"provenance": dict(provenance or {}),
|
|
884
894
|
},
|
|
885
895
|
},
|
|
886
|
-
}
|
|
896
|
+
},
|
|
897
|
+
**_presented_assertion(caller_assertion),
|
|
887
898
|
)
|
|
888
899
|
|
|
889
900
|
|
|
901
|
+
def _task_caller_assertion(
|
|
902
|
+
request: Request,
|
|
903
|
+
config: TauSDKSettings,
|
|
904
|
+
) -> CallerAssertion | None:
|
|
905
|
+
"""Return the request's verified caller assertion while it is valid, to present for a Task.
|
|
906
|
+
|
|
907
|
+
Presented beside the runtime's credential when the request creates or continues a Task, it
|
|
908
|
+
lets the platform record the request's person as the Task's requester. Local mode and the
|
|
909
|
+
platform's own calls have none.
|
|
910
|
+
"""
|
|
911
|
+
|
|
912
|
+
caller_assertion = request_caller_assertion(request)
|
|
913
|
+
if config.local_mode or caller_assertion is None:
|
|
914
|
+
return None
|
|
915
|
+
return caller_assertion if caller_assertion.unexpired_token() else None
|
|
916
|
+
|
|
917
|
+
|
|
918
|
+
def _presented_assertion(caller_assertion: CallerAssertion | None) -> dict[str, str]:
|
|
919
|
+
token = caller_assertion.unexpired_token() if caller_assertion is not None else None
|
|
920
|
+
return {"caller_assertion": token} if token else {}
|
|
921
|
+
|
|
922
|
+
|
|
923
|
+
def _requester_named_by_task(
|
|
924
|
+
task: AgentTask,
|
|
925
|
+
caller_assertion: CallerAssertion | None,
|
|
926
|
+
) -> Requester | None:
|
|
927
|
+
"""Return the request's verified caller when the platform recorded that person as requester.
|
|
928
|
+
|
|
929
|
+
This is the requester of a Task attempt that the request itself runs: the platform's Task
|
|
930
|
+
answer must name the same User, as a person, as ``requester_user_uid``.
|
|
931
|
+
"""
|
|
932
|
+
|
|
933
|
+
if caller_assertion is None or caller_assertion.caller_is_workload:
|
|
934
|
+
return None
|
|
935
|
+
caller = caller_assertion.caller
|
|
936
|
+
if task.requester_identity_type != "human" or task.requester_user_uid != caller.uid:
|
|
937
|
+
return None
|
|
938
|
+
return Requester(uid=caller.uid, team_uids=caller.team_uids)
|
|
939
|
+
|
|
940
|
+
|
|
890
941
|
async def _wait_for_task_return_state(
|
|
891
942
|
client: MainSequenceClient,
|
|
892
943
|
task: AgentTask,
|
|
@@ -1238,6 +1289,7 @@ async def _execute_task(
|
|
|
1238
1289
|
config: TauSDKSettings | None = None,
|
|
1239
1290
|
dispatch_uid: str | None = None,
|
|
1240
1291
|
claim: _ClaimedTask | None = None,
|
|
1292
|
+
requester: Requester | None = None,
|
|
1241
1293
|
) -> dict[str, Any]:
|
|
1242
1294
|
bind_contextvars(
|
|
1243
1295
|
a2a_task_id=task.task_id,
|
|
@@ -1270,6 +1322,7 @@ async def _execute_task(
|
|
|
1270
1322
|
attempt_uid=claim.attempt_uid,
|
|
1271
1323
|
holder_id=claim.holder_id,
|
|
1272
1324
|
lease_token=claim.lease_token,
|
|
1325
|
+
requester=requester,
|
|
1273
1326
|
)
|
|
1274
1327
|
try:
|
|
1275
1328
|
await writer.ensure_not_canceled(force=True)
|
|
@@ -1355,6 +1408,7 @@ async def _schedule_task_accelerator(
|
|
|
1355
1408
|
max_output_bytes: int,
|
|
1356
1409
|
provenance: TurnProvenance,
|
|
1357
1410
|
config: TauSDKSettings,
|
|
1411
|
+
requester: Requester | None = None,
|
|
1358
1412
|
) -> bool:
|
|
1359
1413
|
"""Best-effort local start after durable creation; dispatch remains recovery owner."""
|
|
1360
1414
|
|
|
@@ -1386,6 +1440,7 @@ async def _schedule_task_accelerator(
|
|
|
1386
1440
|
provenance=provenance,
|
|
1387
1441
|
config=config,
|
|
1388
1442
|
claim=claim,
|
|
1443
|
+
requester=requester,
|
|
1389
1444
|
)
|
|
1390
1445
|
try:
|
|
1391
1446
|
_background, scheduled = manager.create_a2a_task_execution(
|
|
@@ -1573,6 +1628,7 @@ async def _execute_message(
|
|
|
1573
1628
|
output_contract: StrictJsonContract,
|
|
1574
1629
|
max_output_bytes: int,
|
|
1575
1630
|
provenance: TurnProvenance,
|
|
1631
|
+
caller_assertion: CallerAssertion | None = None,
|
|
1576
1632
|
) -> dict[str, Any]:
|
|
1577
1633
|
text = await _collect_validated_turn(
|
|
1578
1634
|
manager,
|
|
@@ -1581,6 +1637,7 @@ async def _execute_message(
|
|
|
1581
1637
|
output_contract,
|
|
1582
1638
|
max_output_bytes=max_output_bytes,
|
|
1583
1639
|
provenance=provenance,
|
|
1640
|
+
caller_assertion=caller_assertion,
|
|
1584
1641
|
)
|
|
1585
1642
|
message = _agent_message(
|
|
1586
1643
|
context_id=context_id,
|
|
@@ -1623,6 +1680,7 @@ async def _stream_task_events(
|
|
|
1623
1680
|
provenance: TurnProvenance,
|
|
1624
1681
|
config: TauSDKSettings | None = None,
|
|
1625
1682
|
history_length: int = DEFAULT_TASK_HISTORY_LENGTH,
|
|
1683
|
+
requester: Requester | None = None,
|
|
1626
1684
|
) -> AsyncIterator[dict[str, Any]]:
|
|
1627
1685
|
claim = await _claim_backend_task(client, manager, task)
|
|
1628
1686
|
resolved = config or manager.settings
|
|
@@ -1642,6 +1700,7 @@ async def _stream_task_events(
|
|
|
1642
1700
|
attempt_uid=claim.attempt_uid,
|
|
1643
1701
|
holder_id=claim.holder_id,
|
|
1644
1702
|
lease_token=claim.lease_token,
|
|
1703
|
+
requester=requester,
|
|
1645
1704
|
)
|
|
1646
1705
|
working = await client.get_task(task.uid)
|
|
1647
1706
|
working = await _task_with_history(client, working, history_length=history_length)
|
|
@@ -1819,6 +1878,7 @@ def _message_stream_response(
|
|
|
1819
1878
|
history_length: int = DEFAULT_TASK_HISTORY_LENGTH,
|
|
1820
1879
|
json_rpc: bool = False,
|
|
1821
1880
|
request_id: object = None,
|
|
1881
|
+
requester: Requester | None = None,
|
|
1822
1882
|
) -> StreamingResponse:
|
|
1823
1883
|
async def stream() -> AsyncIterator[bytes]:
|
|
1824
1884
|
try:
|
|
@@ -1832,6 +1892,7 @@ def _message_stream_response(
|
|
|
1832
1892
|
provenance=provenance,
|
|
1833
1893
|
config=config,
|
|
1834
1894
|
history_length=history_length,
|
|
1895
|
+
requester=requester,
|
|
1835
1896
|
):
|
|
1836
1897
|
yield _sse(
|
|
1837
1898
|
_stream_payload(
|
|
@@ -2088,6 +2149,21 @@ def _durable_task_execution_input(
|
|
|
2088
2149
|
return prompt, contract, provenance
|
|
2089
2150
|
|
|
2090
2151
|
|
|
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.
|
|
2154
|
+
|
|
2155
|
+
Only a person is a requester: an Agent's workload User never is. A runtime that does not
|
|
2156
|
+
verify the platform's assertion on its internal routes takes no requester from them.
|
|
2157
|
+
"""
|
|
2158
|
+
|
|
2159
|
+
if config.local_mode or config.request_identity_mode != "assertion":
|
|
2160
|
+
return None
|
|
2161
|
+
if body.get("requester_identity_type") != "human":
|
|
2162
|
+
return None
|
|
2163
|
+
uid = canonical_requester_uid(body.get("requester_user_uid"))
|
|
2164
|
+
return Requester(uid=uid) if uid is not None else None
|
|
2165
|
+
|
|
2166
|
+
|
|
2091
2167
|
@router.post("/internal/a2a/task-dispatch", status_code=200)
|
|
2092
2168
|
async def task_dispatch_available(
|
|
2093
2169
|
body: dict[str, Any],
|
|
@@ -2123,6 +2199,7 @@ async def task_dispatch_available(
|
|
|
2123
2199
|
provenance=provenance,
|
|
2124
2200
|
config=config,
|
|
2125
2201
|
claim=claim,
|
|
2202
|
+
requester=_dispatched_requester(body, config),
|
|
2126
2203
|
),
|
|
2127
2204
|
name=f"a2a-task-{task.task_id}",
|
|
2128
2205
|
)
|
|
@@ -2273,7 +2350,12 @@ async def message_send(
|
|
|
2273
2350
|
task_id=existing_task.task_id,
|
|
2274
2351
|
local_mode=config.local_mode,
|
|
2275
2352
|
)
|
|
2276
|
-
|
|
2353
|
+
caller_assertion = _task_caller_assertion(request, config)
|
|
2354
|
+
task = await client.continue_task(
|
|
2355
|
+
existing_task.uid,
|
|
2356
|
+
continued_message,
|
|
2357
|
+
**_presented_assertion(caller_assertion),
|
|
2358
|
+
)
|
|
2277
2359
|
if config.local_mode:
|
|
2278
2360
|
task = await _persist_local_recovery_contract(
|
|
2279
2361
|
client,
|
|
@@ -2290,6 +2372,7 @@ async def message_send(
|
|
|
2290
2372
|
max_output_bytes=config.max_turn_output_bytes,
|
|
2291
2373
|
provenance=provenance,
|
|
2292
2374
|
config=config,
|
|
2375
|
+
requester=_requester_named_by_task(task, caller_assertion),
|
|
2293
2376
|
)
|
|
2294
2377
|
if return_immediately:
|
|
2295
2378
|
task = await _task_with_history(client, task, history_length=history_length)
|
|
@@ -2303,6 +2386,7 @@ async def message_send(
|
|
|
2303
2386
|
task = await _task_with_history(client, task, history_length=history_length)
|
|
2304
2387
|
return {"task": _task_payload(task, history_length=history_length)}
|
|
2305
2388
|
if response_kind is ResponseKind.TASK:
|
|
2389
|
+
caller_assertion = _task_caller_assertion(request, config)
|
|
2306
2390
|
creation = await _create_backend_task(
|
|
2307
2391
|
client,
|
|
2308
2392
|
message=message,
|
|
@@ -2310,6 +2394,7 @@ async def message_send(
|
|
|
2310
2394
|
local_mode=config.local_mode,
|
|
2311
2395
|
output_contract=output_contract,
|
|
2312
2396
|
provenance=provenance,
|
|
2397
|
+
caller_assertion=caller_assertion,
|
|
2313
2398
|
)
|
|
2314
2399
|
task = creation.task
|
|
2315
2400
|
if creation.created:
|
|
@@ -2322,6 +2407,7 @@ async def message_send(
|
|
|
2322
2407
|
max_output_bytes=config.max_turn_output_bytes,
|
|
2323
2408
|
provenance=provenance,
|
|
2324
2409
|
config=config,
|
|
2410
|
+
requester=_requester_named_by_task(task, caller_assertion),
|
|
2325
2411
|
)
|
|
2326
2412
|
if return_immediately:
|
|
2327
2413
|
task = await _task_with_history(client, task, history_length=history_length)
|
|
@@ -2371,6 +2457,7 @@ async def message_send(
|
|
|
2371
2457
|
output_contract=output_contract,
|
|
2372
2458
|
max_output_bytes=config.max_turn_output_bytes,
|
|
2373
2459
|
provenance=provenance,
|
|
2460
|
+
caller_assertion=request_caller_assertion(request),
|
|
2374
2461
|
)
|
|
2375
2462
|
if config.local_mode:
|
|
2376
2463
|
result = await client.complete_local_conversation_message(
|
|
@@ -2418,6 +2505,7 @@ async def message_stream(
|
|
|
2418
2505
|
output_contract = _output_contract(body)
|
|
2419
2506
|
if config.local_mode:
|
|
2420
2507
|
await manager.get(str(message["contextId"]))
|
|
2508
|
+
caller_assertion = _task_caller_assertion(request, config)
|
|
2421
2509
|
creation = await _create_backend_task(
|
|
2422
2510
|
client,
|
|
2423
2511
|
message=message,
|
|
@@ -2425,6 +2513,7 @@ async def message_stream(
|
|
|
2425
2513
|
local_mode=config.local_mode,
|
|
2426
2514
|
output_contract=output_contract,
|
|
2427
2515
|
provenance=provenance,
|
|
2516
|
+
caller_assertion=caller_assertion,
|
|
2428
2517
|
)
|
|
2429
2518
|
task = creation.task
|
|
2430
2519
|
if not creation.created:
|
|
@@ -2458,6 +2547,7 @@ async def message_stream(
|
|
|
2458
2547
|
provenance=provenance,
|
|
2459
2548
|
config=config,
|
|
2460
2549
|
history_length=history_length,
|
|
2550
|
+
requester=_requester_named_by_task(task, caller_assertion),
|
|
2461
2551
|
)
|
|
2462
2552
|
|
|
2463
2553
|
|
|
@@ -2662,6 +2752,7 @@ async def json_rpc(
|
|
|
2662
2752
|
output_contract = _output_contract(params)
|
|
2663
2753
|
if config.local_mode:
|
|
2664
2754
|
await manager.get(str(message["contextId"]))
|
|
2755
|
+
caller_assertion = _task_caller_assertion(request, config)
|
|
2665
2756
|
creation = await _create_backend_task(
|
|
2666
2757
|
client,
|
|
2667
2758
|
message=message,
|
|
@@ -2669,6 +2760,7 @@ async def json_rpc(
|
|
|
2669
2760
|
local_mode=config.local_mode,
|
|
2670
2761
|
output_contract=output_contract,
|
|
2671
2762
|
provenance=provenance,
|
|
2763
|
+
caller_assertion=caller_assertion,
|
|
2672
2764
|
)
|
|
2673
2765
|
task = creation.task
|
|
2674
2766
|
if not creation.created:
|
|
@@ -2707,6 +2799,7 @@ async def json_rpc(
|
|
|
2707
2799
|
history_length=history_length,
|
|
2708
2800
|
json_rpc=True,
|
|
2709
2801
|
request_id=request_id,
|
|
2802
|
+
requester=_requester_named_by_task(task, caller_assertion),
|
|
2710
2803
|
)
|
|
2711
2804
|
elif method in {"GetTask", "tasks/get"}:
|
|
2712
2805
|
result = await get_task(
|
|
@@ -4,7 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import asyncio
|
|
6
6
|
from collections.abc import AsyncIterator
|
|
7
|
-
from typing import Annotated
|
|
7
|
+
from typing import Annotated, Any
|
|
8
8
|
|
|
9
9
|
import structlog
|
|
10
10
|
from fastapi import APIRouter, Depends, HTTPException, Request
|
|
@@ -22,7 +22,11 @@ from ms_tau_sdk.runtime.provenance import (
|
|
|
22
22
|
|
|
23
23
|
from .dependencies import runtime_manager
|
|
24
24
|
from .models import ChatRequest
|
|
25
|
-
from .request_identity import
|
|
25
|
+
from .request_identity import (
|
|
26
|
+
request_caller_assertion,
|
|
27
|
+
require_session_access,
|
|
28
|
+
verified_user_uid,
|
|
29
|
+
)
|
|
26
30
|
|
|
27
31
|
router = APIRouter(prefix="/api")
|
|
28
32
|
RuntimeManagerDep = Annotated[SessionRuntimeManager, Depends(runtime_manager)]
|
|
@@ -110,10 +114,16 @@ async def chat(
|
|
|
110
114
|
if manager.settings.local_mode
|
|
111
115
|
else None
|
|
112
116
|
)
|
|
117
|
+
# A hosted turn presents its request's verified caller assertion when it starts, so that the
|
|
118
|
+
# platform can record who asked for it.
|
|
119
|
+
caller_assertion = None if local_turn is not None else request_caller_assertion(request)
|
|
120
|
+
turn_options: dict[str, Any] = {"provenance": provenance}
|
|
121
|
+
if caller_assertion is not None:
|
|
122
|
+
turn_options["caller_assertion"] = caller_assertion
|
|
113
123
|
events = (
|
|
114
124
|
local_turn.events()
|
|
115
125
|
if local_turn is not None
|
|
116
|
-
else manager.prompt(session_uid, prompt,
|
|
126
|
+
else manager.prompt(session_uid, prompt, **turn_options)
|
|
117
127
|
)
|
|
118
128
|
|
|
119
129
|
async def stream() -> AsyncIterator[bytes]:
|