managed-deepagents 0.6.2.dev7__py3-none-win_amd64.whl → 0.6.2.dev8__py3-none-win_amd64.whl

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.
@@ -9,19 +9,12 @@ This package provides two things:
9
9
 
10
10
  Resources use ``define_*`` factories while provider collections use namespaces:
11
11
  ``define_sandbox()``, ``channels.slack(...)``, ``connectors.mcp(...)``,
12
- ``credentials.get(...)``, and ``auth.supabase(...)``. Contributing a *new*
12
+ and ``auth.supabase(...)``. Contributing a *new*
13
13
  primitive is not part of the public surface.
14
14
  """
15
15
 
16
16
  from __future__ import annotations
17
17
 
18
- from managed_deepagents._credentials import (
19
- CredentialAuthorizationRequiredError,
20
- CredentialNotFoundError,
21
- CredentialOwner,
22
- CredentialStoreError,
23
- credentials,
24
- )
25
18
  from managed_deepagents._managed_tools import (
26
19
  ManagedDeepAgentRuntime,
27
20
  )
@@ -38,10 +31,6 @@ from managed_deepagents.sandbox import SandboxDefinition
38
31
  from managed_deepagents.schedules import define_schedule
39
32
 
40
33
  __all__ = [
41
- "CredentialAuthorizationRequiredError",
42
- "CredentialNotFoundError",
43
- "CredentialOwner",
44
- "CredentialStoreError",
45
34
  "DeepAgentDefinition",
46
35
  "ManagedDeepAgentRuntime",
47
36
  "MemoryDefinition",
@@ -49,7 +38,6 @@ __all__ = [
49
38
  "auth",
50
39
  "channels",
51
40
  "connectors",
52
- "credentials",
53
41
  "define_deep_agent",
54
42
  "define_identity",
55
43
  "define_memory",
Binary file
@@ -2,30 +2,31 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- import logging
6
5
  import os
6
+ import uuid
7
7
  from collections.abc import Awaitable, Callable, Mapping
8
8
  from importlib import import_module
9
9
  from typing import TYPE_CHECKING, Any, Literal, TypedDict, cast
10
10
 
11
11
  from managed_deepagents._channels.assistant_text import extract_assistant_text
12
12
  from managed_deepagents._channels.binding import SLACK_BOT_TOKEN_ENV
13
+ from managed_deepagents._channels.trigger.client import create_trigger_client
13
14
  from managed_deepagents._identity_runtime import (
14
15
  _ambient_configurable,
15
16
  _configurable_from_runtime,
16
17
  )
18
+ from managed_deepagents._log import logger
17
19
  from managed_deepagents._memory.async_util import await_if_needed
18
20
 
19
21
  if TYPE_CHECKING:
20
22
  from langchain.agents.middleware.types import AgentMiddleware
21
23
 
24
+ from managed_deepagents._channels.trigger.types import TriggerChannelActionRequest
22
25
  from managed_deepagents._channels.types import ChannelAddress
23
26
  else:
24
27
  # Authoring-light installs may lack langchain; annotations stay deferred.
25
28
  AgentMiddleware = object
26
29
 
27
- _LOG = logging.getLogger("mda")
28
-
29
30
  _schedule_metadata_for_tests: Callable[[], Mapping[str, object] | None] | None = None
30
31
 
31
32
 
@@ -58,6 +59,16 @@ class NormalizedScheduleDeliverTo(TypedDict):
58
59
  auto_post: bool
59
60
 
60
61
 
62
+ def get_deployment_id(env: Mapping[str, str] | None = None) -> str | None:
63
+ """Return the deployment ID supplied by the managed runtime."""
64
+ resolved_env = os.environ if env is None else env
65
+ for key in ("LANGSMITH_HOST_PROJECT_ID", "LANGGRAPH_CLOUD_ID"):
66
+ value = (resolved_env.get(key) or "").strip()
67
+ if value:
68
+ return value
69
+ return None
70
+
71
+
61
72
  def read_schedule_deliver_to( # noqa: PLR0911 - validation short-circuits
62
73
  configurable: Mapping[str, object] | None,
63
74
  ) -> NormalizedScheduleDeliverTo | None:
@@ -122,7 +133,7 @@ def address_from_deliver_to(to: _ProviderConversation | _ProviderThread) -> Chan
122
133
  return {"conversation_id": to["conversation_id"]}
123
134
 
124
135
 
125
- async def deliver_schedule_result( # noqa: PLR0911 - soft-fail outcomes
136
+ async def deliver_schedule_result( # noqa: C901, PLR0911 - soft-fail outcomes
126
137
  *,
127
138
  deliver_to: NormalizedScheduleDeliverTo,
128
139
  reply_text: str,
@@ -134,9 +145,9 @@ async def deliver_schedule_result( # noqa: PLR0911 - soft-fail outcomes
134
145
  if not text or not deliver_to["auto_post"]:
135
146
  return "skipped"
136
147
  if deliver_to["channel"] != "slack":
137
- _LOG.error(
138
- "schedule deliver_to channel %r is not supported yet",
139
- deliver_to["channel"],
148
+ logger.error(
149
+ 'schedule deliver_to channel "%s" is not supported yet',
150
+ logger.log_identifier(deliver_to["channel"]),
140
151
  )
141
152
  return "failed"
142
153
 
@@ -151,15 +162,47 @@ async def deliver_schedule_result( # noqa: PLR0911 - soft-fail outcomes
151
162
  if hasattr(result, "__await__"):
152
163
  await cast("Awaitable[object]", result)
153
164
  return "posted"
154
- except Exception: # noqa: BLE001 - soft-fail schedule delivery
155
- _LOG.exception("schedule Slack delivery failed")
165
+ except Exception as error: # noqa: BLE001 - soft-fail schedule delivery
166
+ logger.error(
167
+ "schedule Slack delivery failed (%s)",
168
+ logger.safe_error_label(error),
169
+ )
170
+ return "failed"
171
+
172
+ deployment_id = get_deployment_id()
173
+ try:
174
+ client = create_trigger_client()
175
+ except ValueError:
176
+ client = None
177
+ if client is not None and deployment_id:
178
+ action: TriggerChannelActionRequest = {
179
+ "type": "post",
180
+ "action_id": str(uuid.uuid4()),
181
+ "address": dict(address),
182
+ "message": {"text": text},
183
+ }
184
+ try:
185
+ await client.post_deployment_action(
186
+ deployment_id,
187
+ deliver_to["channel"],
188
+ action,
189
+ )
190
+ return "posted"
191
+ except Exception as error: # noqa: BLE001 - soft-fail schedule delivery
192
+ logger.error(
193
+ "schedule Trigger channel delivery failed (%s)",
194
+ logger.safe_error_label(error),
195
+ )
156
196
  return "failed"
157
197
 
158
198
  token = (
159
199
  bot_token if bot_token is not None else os.environ.get(SLACK_BOT_TOKEN_ENV) or ""
160
200
  ).strip()
161
201
  if not token:
162
- _LOG.error("schedule deliver_to requires SLACK_BOT_TOKEN; skipping auto-post")
202
+ logger.error(
203
+ "schedule deliver_to requires Trigger channel configuration or "
204
+ "SLACK_BOT_TOKEN; skipping auto-post"
205
+ )
163
206
  return "failed"
164
207
 
165
208
  try:
@@ -176,8 +219,11 @@ async def deliver_schedule_result( # noqa: PLR0911 - soft-fail outcomes
176
219
  if hasattr(posted, "__await__"):
177
220
  await cast("Awaitable[object]", posted)
178
221
  return "posted"
179
- except Exception: # noqa: BLE001 - soft-fail schedule delivery
180
- _LOG.exception("schedule Slack delivery failed")
222
+ except Exception as error: # noqa: BLE001 - soft-fail schedule delivery
223
+ logger.error(
224
+ "schedule Slack delivery failed (%s)",
225
+ logger.safe_error_label(error),
226
+ )
181
227
  return "failed"
182
228
 
183
229
 
@@ -297,6 +343,7 @@ __all__ = [
297
343
  "address_from_deliver_to",
298
344
  "create_schedule_delivery_middleware",
299
345
  "deliver_schedule_result",
346
+ "get_deployment_id",
300
347
  "is_trusted_schedule_delivery_run",
301
348
  "read_schedule_deliver_to",
302
349
  ]
@@ -93,18 +93,41 @@ class TriggerClient(TriggerClientLike):
93
93
  channel_id: str,
94
94
  body: TriggerChannelActionRequest,
95
95
  ) -> TriggerChannelActionResponse:
96
- response = await self._post("actions", channel_id, body)
97
- response_id = response.get("id") if isinstance(response, dict) else None
98
- if not isinstance(response_id, str) or not response_id.strip():
99
- raise TriggerClientError("actions")
100
- return {"id": response_id}
96
+ encoded_channel_id = urllib.parse.quote(channel_id, safe="")
97
+ response = await self._post(
98
+ "actions",
99
+ f"{self._api_base}/channels/{encoded_channel_id}/actions",
100
+ body,
101
+ )
102
+ return _parse_action_response(response)
103
+
104
+ async def post_deployment_action(
105
+ self,
106
+ deployment_id: str,
107
+ channel_name: str,
108
+ body: TriggerChannelActionRequest,
109
+ ) -> TriggerChannelActionResponse:
110
+ query = urllib.parse.urlencode(
111
+ {"deployment_id": deployment_id, "channel_name": channel_name}
112
+ )
113
+ response = await self._post(
114
+ "actions",
115
+ f"{self._api_base}/channels/actions?{query}",
116
+ body,
117
+ )
118
+ return _parse_action_response(response)
101
119
 
102
120
  async def post_runtime_event(
103
121
  self,
104
122
  channel_id: str,
105
123
  body: TriggerChannelRuntimeEventRequest,
106
124
  ) -> TriggerChannelRuntimeEventResponse:
107
- response = await self._post("events", channel_id, body)
125
+ encoded_channel_id = urllib.parse.quote(channel_id, safe="")
126
+ response = await self._post(
127
+ "events",
128
+ f"{self._api_base}/channels/{encoded_channel_id}/events",
129
+ body,
130
+ )
108
131
  status = response.get("status") if isinstance(response, dict) else None
109
132
  if not isinstance(status, str):
110
133
  raise TriggerClientError("events")
@@ -113,11 +136,9 @@ class TriggerClient(TriggerClientLike):
113
136
  async def _post(
114
137
  self,
115
138
  operation: TriggerClientOperation,
116
- channel_id: str,
139
+ url: str,
117
140
  body: TriggerChannelActionRequest | TriggerChannelRuntimeEventRequest,
118
141
  ) -> object:
119
- encoded_channel_id = urllib.parse.quote(channel_id, safe="")
120
- url = f"{self._api_base}/channels/{encoded_channel_id}/{operation}"
121
142
  headers = {
122
143
  "content-type": "application/json",
123
144
  "x-api-key": self._api_key,
@@ -144,6 +165,13 @@ class TriggerClient(TriggerClientLike):
144
165
  raise TriggerClientError(operation) from None
145
166
 
146
167
 
168
+ def _parse_action_response(response: object) -> TriggerChannelActionResponse:
169
+ response_id = response.get("id") if isinstance(response, dict) else None
170
+ if not isinstance(response_id, str) or not response_id.strip():
171
+ raise TriggerClientError("actions")
172
+ return {"id": response_id}
173
+
174
+
147
175
  def create_trigger_client(
148
176
  *,
149
177
  user_id: str | None = None,
@@ -66,14 +66,17 @@ class TriggerChannelActionMessage(TypedDict):
66
66
  text: str
67
67
 
68
68
 
69
- class TriggerChannelActionRequest(TypedDict):
69
+ class TriggerChannelActionRequestRequired(TypedDict):
70
70
  type: Literal["post"]
71
71
  action_id: str
72
72
  address: TriggerChannelAddress
73
- source_thread_key: str
74
73
  message: TriggerChannelActionMessage
75
74
 
76
75
 
76
+ class TriggerChannelActionRequest(TriggerChannelActionRequestRequired, total=False):
77
+ source_thread_key: str
78
+
79
+
77
80
  class TriggerChannelActionResponse(TypedDict):
78
81
  id: str
79
82
 
@@ -9,7 +9,7 @@ name, before any managed ``{server}__`` prefix is applied.
9
9
  Example::
10
10
 
11
11
  # connectors/mcp.py
12
- from managed_deepagents import connectors, credentials
12
+ from managed_deepagents import connectors
13
13
 
14
14
  connector = connectors.mcp(
15
15
  use_standard_content_blocks=True,
@@ -21,11 +21,6 @@ Example::
21
21
  "url": "https://docs.example.com/mcp",
22
22
  "include_tools": ["search", "fetch"],
23
23
  },
24
- "notion": {
25
- "transport": "http",
26
- "url": "https://mcp.notion.com/mcp",
27
- "credential": credentials.get("notion", {"type": "user"}),
28
- },
29
24
  },
30
25
  )
31
26
  """
@@ -47,7 +42,6 @@ from pydantic import (
47
42
 
48
43
  from managed_deepagents._connector import Connector
49
44
  from managed_deepagents._connectors.mcp.load import load_managed_mcp_tools
50
- from managed_deepagents._credentials import CredentialReference # noqa: TC001
51
45
  from managed_deepagents._extension import define_extension
52
46
 
53
47
 
@@ -59,9 +53,6 @@ class McpToolSelectionConfig(TypedDict, total=False):
59
53
  class RemoteMcpServerBaseConfig(McpToolSelectionConfig, total=False):
60
54
  url: str
61
55
  headers: dict[str, str]
62
- #: Bearer credential: an agent owner is a deployment-wide secret read once
63
- #: at load, a user owner is the caller's OAuth grant loaded after the gate.
64
- credential: CredentialReference
65
56
  #: Default timeout in seconds for tools loaded from this server.
66
57
  default_tool_timeout: float
67
58
 
@@ -151,7 +142,7 @@ class _RemoteMcpServer(BaseModel):
151
142
  ``default_tool_timeout``) are stripped before the client is constructed.
152
143
  """
153
144
 
154
- model_config = ConfigDict(extra="allow", validate_default=True, arbitrary_types_allowed=True)
145
+ model_config = ConfigDict(extra="allow", validate_default=True)
155
146
 
156
147
  # Both default to a value the validators below reject, so a missing field
157
148
  # reports the same message a malformed one does — as it does in Zod.
@@ -163,7 +154,6 @@ class _RemoteMcpServer(BaseModel):
163
154
  default_tool_timeout: float | None = None
164
155
  include_tools: list[_ToolName] | None = None
165
156
  exclude_tools: list[_ToolName] | None = None
166
- credential: CredentialReference | None = None
167
157
 
168
158
  @field_validator("default_tool_timeout")
169
159
  @classmethod
@@ -10,13 +10,10 @@ from __future__ import annotations
10
10
  import asyncio
11
11
  import math
12
12
  import warnings
13
- from collections.abc import Awaitable, Callable, Iterable, Mapping, Sequence
14
- from functools import partial
13
+ from collections.abc import Awaitable, Callable, Iterable, Mapping
15
14
  from typing import TYPE_CHECKING, Protocol, cast
16
15
 
17
16
  from managed_deepagents._connectors.tool_selection import select_tools
18
- from managed_deepagents._credential_gate import CredentialConsumer
19
- from managed_deepagents._credentials import CredentialReference, credentials
20
17
  from managed_deepagents._memory.async_util import await_if_needed
21
18
  from managed_deepagents._memory.deps import (
22
19
  optional_runtime_symbols,
@@ -53,10 +50,6 @@ class _TimedTool(Protocol):
53
50
  #: tools loaded once per declaration, and every subsequent run reuses them.
54
51
  #: Multiple ``connectors.mcp(...)`` modules each get their own entry (keyed by
55
52
  #: object identity).
56
- #:
57
- #: An agent-owned credential is read into this cache once, so rotating that
58
- #: secret takes a redeploy. Servers with a user-owned credential are omitted
59
- #: entirely; the credential gate loads those per caller.
60
53
  _managed_mcp_tools: dict[int, list[object]] = {}
61
54
 
62
55
 
@@ -80,83 +73,6 @@ def load_managed_mcp_tools(connectors: McpServersDefinition) -> list[object]:
80
73
  return tools
81
74
 
82
75
 
83
- def mcp_credential_consumers(
84
- connectors: Sequence[object] | None,
85
- ) -> list[CredentialConsumer]:
86
- """The credential gate's view of the declared MCP connectors.
87
-
88
- One consumer per declaration that names a user-owned credential; a
89
- declaration that needs no grant never reaches the gate.
90
- """
91
- out: list[CredentialConsumer] = []
92
- for connector in connectors or []:
93
- if getattr(connector, "kind", None) != "mcp_servers":
94
- continue
95
- definition = cast("McpServersDefinition", connector)
96
- references = _user_credential_references(definition)
97
- if not references:
98
- continue
99
- out.append(
100
- CredentialConsumer(
101
- declaration=definition,
102
- credentials=references,
103
- load=partial(load_user_credential_mcp_tools, definition),
104
- )
105
- )
106
- return out
107
-
108
-
109
- def _user_credential_references(
110
- connectors: McpServersDefinition,
111
- ) -> tuple[CredentialReference, ...]:
112
- """The declared references themselves: the gate checks their owner."""
113
- config = getattr(connectors, "config", None)
114
- servers = config.get("mcp_servers") if isinstance(config, Mapping) else None
115
- if not isinstance(servers, Mapping):
116
- return ()
117
- by_slug: dict[str, CredentialReference] = {}
118
- for server_config in servers.values():
119
- if _uses_user_credential(server_config):
120
- credential = cast("CredentialReference", server_config["credential"])
121
- by_slug.setdefault(credential.slug, credential)
122
- return tuple(by_slug.values())
123
-
124
-
125
- def load_user_credential_mcp_tools(
126
- connectors: McpServersDefinition,
127
- access_tokens: Mapping[str, str],
128
- ) -> list[object]:
129
- """Load tools for user-credential servers, once the gate confirms grants.
130
-
131
- Not process-cached here: the Authorization header is per caller, so the gate
132
- owns the cache. Python's ``MultiServerMCPClient`` opens a session per call
133
- and holds no transport, so there is nothing to close afterwards.
134
- """
135
- config = connectors.config
136
- servers = config["mcp_servers"]
137
- _eager, user_credential = _partition_mcp_servers(servers)
138
- if not user_credential:
139
- return []
140
-
141
- connectable: dict[str, RemoteMcpServerConfig] = {}
142
- for server_name in user_credential:
143
- server_config = servers.get(server_name)
144
- if not isinstance(server_config, dict):
145
- continue
146
- credential = server_config.get("credential")
147
- if not isinstance(credential, CredentialReference):
148
- continue
149
- access_token = access_tokens.get(credential.slug)
150
- if not access_token:
151
- raise RuntimeError(f'MCP user credential "{credential.slug}" has no access token')
152
- connectable[server_name] = _with_bearer(server_config, access_token)
153
-
154
- if not connectable:
155
- return []
156
-
157
- return _load_tools_from_servers(connectors, connectable)
158
-
159
-
160
76
  def clear_managed_mcp_tools_cache_for_tests(
161
77
  connectors: McpServersDefinition | None = None,
162
78
  ) -> None:
@@ -167,92 +83,14 @@ def clear_managed_mcp_tools_cache_for_tests(
167
83
  _managed_mcp_tools.clear()
168
84
 
169
85
 
170
- def _uses_user_credential(server_config: RemoteMcpServerConfig | dict[str, object]) -> bool:
171
- if isinstance(server_config, dict):
172
- credential = server_config.get("credential")
173
- else:
174
- credential = getattr(server_config, "credential", None)
175
- return isinstance(credential, CredentialReference) and credential.owner["type"] == "user"
176
-
177
-
178
- def _partition_mcp_servers(
179
- servers: dict[str, RemoteMcpServerConfig],
180
- ) -> tuple[dict[str, RemoteMcpServerConfig], list[str]]:
181
- eager: dict[str, RemoteMcpServerConfig] = {}
182
- user_credential: list[str] = []
183
- for server_name, server_config in servers.items():
184
- if _uses_user_credential(server_config):
185
- user_credential.append(server_name)
186
- else:
187
- eager[server_name] = server_config
188
- return eager, user_credential
189
-
190
-
191
- def _warn_deferred_user_credential_servers(server_names: list[str]) -> None:
192
- if not server_names:
193
- return
194
- warnings.warn(
195
- f"MCP server(s) {', '.join(server_names)} declare a user credential; "
196
- "tools load after the pre-run credential gate confirms grants for the caller.",
197
- RuntimeWarning,
198
- stacklevel=3,
199
- )
200
-
201
-
202
86
  def _create_client_and_load_tools(connectors: McpServersDefinition) -> list[object]:
203
- """Create the ``langchain-mcp-adapters`` client and load its eager tools.
87
+ """Create the ``langchain-mcp-adapters`` client and load its tools.
204
88
 
205
89
  Python's ``MultiServerMCPClient`` does not prefix tool names itself, so when
206
90
  ``prefix_tool_name_with_server_name`` is set (the default) MDA loads each
207
91
  server's tools and namespaces them as ``{server}__{tool}``, matching the
208
92
  TypeScript adapter's format.
209
-
210
- Servers with user credential references are omitted; the gate loads them
211
- per caller after OAuth grants exist.
212
93
  """
213
- config = connectors.config
214
- servers = config["mcp_servers"]
215
- eager, user_credential = _partition_mcp_servers(servers)
216
- if user_credential:
217
- _warn_deferred_user_credential_servers(user_credential)
218
- if not eager:
219
- return []
220
- return _load_tools_from_servers(connectors, _with_agent_credentials(eager))
221
-
222
-
223
- def _with_agent_credentials(
224
- servers: dict[str, RemoteMcpServerConfig],
225
- ) -> dict[str, RemoteMcpServerConfig]:
226
- """Resolve each agent-owned credential into a static bearer header."""
227
- out: dict[str, RemoteMcpServerConfig] = {}
228
- for server_name, server_config in servers.items():
229
- credential = server_config.get("credential") if isinstance(server_config, dict) else None
230
- if not isinstance(credential, CredentialReference):
231
- out[server_name] = server_config
232
- continue
233
- secret = await_if_needed(credentials.resolve_deployment_secret(credential.slug))
234
- out[server_name] = _with_bearer(server_config, cast("str", secret))
235
- return out
236
-
237
-
238
- def _with_bearer(
239
- server_config: RemoteMcpServerConfig,
240
- token: str,
241
- ) -> RemoteMcpServerConfig:
242
- merged = dict(cast("Mapping[str, object]", server_config))
243
- raw_headers = merged.get("headers")
244
- headers: dict[str, str] = (
245
- dict(cast("Mapping[str, str]", raw_headers)) if isinstance(raw_headers, Mapping) else {}
246
- )
247
- headers["Authorization"] = f"Bearer {token}"
248
- merged["headers"] = headers
249
- return cast("RemoteMcpServerConfig", merged)
250
-
251
-
252
- def _load_tools_from_servers(
253
- connectors: McpServersDefinition,
254
- connectable: dict[str, RemoteMcpServerConfig],
255
- ) -> list[object]:
256
94
  client_cls = cast(
257
95
  "type",
258
96
  require_runtime_dependency(
@@ -263,13 +101,14 @@ def _load_tools_from_servers(
263
101
  )
264
102
 
265
103
  config = connectors.config
104
+ servers = config["mcp_servers"]
266
105
  prefix = config.get("prefix_tool_name_with_server_name", True)
267
106
  throw_on_load_error = config.get("throw_on_load_error", True)
268
107
 
269
- client = client_cls(_strip_managed_tool_selection(connectable))
108
+ client = client_cls(_strip_managed_tool_selection(servers))
270
109
 
271
110
  tools: list[object] = []
272
- for server_name, server_config in connectable.items():
111
+ for server_name, server_config in servers.items():
273
112
  try:
274
113
  server_tools = await_if_needed(client.get_tools(server_name=server_name))
275
114
  except Exception as exc:
@@ -304,12 +143,7 @@ def _load_tools_from_servers(
304
143
 
305
144
  #: Fields MDA owns. ``langchain-mcp-adapters`` unpacks the remaining dict into
306
145
  #: the transport session constructor, which does not accept these keys.
307
- _MDA_OWNED_SERVER_KEYS = (
308
- "include_tools",
309
- "exclude_tools",
310
- "default_tool_timeout",
311
- "credential",
312
- )
146
+ _MDA_OWNED_SERVER_KEYS = ("include_tools", "exclude_tools", "default_tool_timeout")
313
147
 
314
148
 
315
149
  def _strip_managed_tool_selection(
@@ -274,7 +274,7 @@ async def _authenticate(
274
274
  async def resolve_request_identity(
275
275
  cfg: IdentityConfig, request: HttpRequestLike
276
276
  ) -> RuntimeIdentity:
277
- """Resolve the caller's frozen identity envelope from a raw HTTP request.
277
+ """Resolve the caller's frozen ``runtime.identity`` from a raw HTTP request.
278
278
 
279
279
  Uses the same ingress logic (``trusted_backend`` headers / ``validated_token``
280
280
  verification) as the managed auth handler, then builds the frozen identity