capability-runtime 0.1.0__tar.gz → 0.1.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/PKG-INFO +2 -2
  2. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/pyproject.toml +2 -2
  3. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/__init__.py +3 -2
  4. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/adapters/agent_adapter.py +294 -5
  5. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/protocol/__init__.py +2 -1
  6. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/protocol/agent.py +8 -1
  7. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/runtime.py +16 -3
  8. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/sdk_lifecycle.py +100 -4
  9. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/services.py +10 -2
  10. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime.egg-info/PKG-INFO +2 -2
  11. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime.egg-info/SOURCES.txt +1 -0
  12. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime.egg-info/requires.txt +1 -1
  13. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_dependency_pins.py +2 -1
  14. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_per_capability_llm_config_model_routing.py +131 -1
  15. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_public_api_exports.py +1 -0
  16. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_release_tag_version_guardrail.py +2 -2
  17. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_runtime_initial_history_and_meta.py +47 -0
  18. capability_runtime-0.1.1/tests/test_upstream_prompt_profile_contract.py +101 -0
  19. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/README.md +0 -0
  20. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/setup.cfg +0 -0
  21. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/adapters/__init__.py +0 -0
  22. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/adapters/agently_backend.py +0 -0
  23. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/adapters/triggerflow_workflow_engine.py +0 -0
  24. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/adapters/workflow_engine.py +0 -0
  25. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/config.py +0 -0
  26. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/errors.py +0 -0
  27. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/guards.py +0 -0
  28. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/host_protocol.py +0 -0
  29. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/host_toolkit/__init__.py +0 -0
  30. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/host_toolkit/approvals_profiles.py +0 -0
  31. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/host_toolkit/evidence_hooks.py +0 -0
  32. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/host_toolkit/history.py +0 -0
  33. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/host_toolkit/invoke_capability.py +0 -0
  34. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/host_toolkit/resume.py +0 -0
  35. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/host_toolkit/system_prompt.py +0 -0
  36. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/host_toolkit/turn_delta.py +0 -0
  37. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/logging_utils.py +0 -0
  38. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/manifest.py +0 -0
  39. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/output_validator.py +0 -0
  40. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/protocol/capability.py +0 -0
  41. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/protocol/chat_backend.py +0 -0
  42. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/protocol/context.py +0 -0
  43. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/protocol/workflow.py +0 -0
  44. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/registry.py +0 -0
  45. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/reporting/__init__.py +0 -0
  46. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/reporting/node_report.py +0 -0
  47. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/runtime_ui_events_mixin.py +0 -0
  48. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/service_facade.py +0 -0
  49. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/structured_output.py +0 -0
  50. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/structured_stream.py +0 -0
  51. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/types.py +0 -0
  52. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/ui_events/__init__.py +0 -0
  53. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/ui_events/projector.py +0 -0
  54. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/ui_events/session.py +0 -0
  55. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/ui_events/store.py +0 -0
  56. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/ui_events/transport.py +0 -0
  57. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/ui_events/v1.py +0 -0
  58. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/upstream_compat.py +0 -0
  59. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/utils/__init__.py +0 -0
  60. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/utils/usage.py +0 -0
  61. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/workflow_runtime.py +0 -0
  62. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime.egg-info/dependency_links.txt +0 -0
  63. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime.egg-info/top_level.txt +0 -0
  64. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_agently_backend.py +0 -0
  65. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_agently_backend_replay.py +0 -0
  66. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_backend_mode.py +0 -0
  67. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_bilingual_docs_surface.py +0 -0
  68. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_bridge_artifacts_passthrough.py +0 -0
  69. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_bridge_register_tool_public_api.py +0 -0
  70. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_coding_agent_examples_atomic.py +0 -0
  71. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_coding_agent_examples_recipes.py +0 -0
  72. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_config_glue.py +0 -0
  73. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_docs_pinned_dependency_versions.py +0 -0
  74. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_docs_scheme2_no_skilladapter_residue.py +0 -0
  75. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_error_observability.py +0 -0
  76. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_examples_alignment_fixes_l1.py +0 -0
  77. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_examples_no_agent_sdk_imports.py +0 -0
  78. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_examples_real_evidence_strict_integration.py +0 -0
  79. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_examples_real_integration.py +0 -0
  80. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_examples_smoke.py +0 -0
  81. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_examples_ui_events_showcase_offline.py +0 -0
  82. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_examples_ui_events_showcase_real_fallback.py +0 -0
  83. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_examples_ui_events_showcase_real_integration.py +0 -0
  84. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_examples_ui_events_showcase_ui_contract.py +0 -0
  85. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_examples_workflow_skills_first_smoke.py +0 -0
  86. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_guards.py +0 -0
  87. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_host_toolkit_approvals_profiles.py +0 -0
  88. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_host_toolkit_history_assembler.py +0 -0
  89. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_host_toolkit_invoke_capability.py +0 -0
  90. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_host_toolkit_invoke_capability_shared_runtime.py +0 -0
  91. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_host_toolkit_resume_helper.py +0 -0
  92. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_host_toolkit_system_prompt_evidence.py +0 -0
  93. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_host_toolkit_turn_delta.py +0 -0
  94. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_integration_agently_requester_smoke.py +0 -0
  95. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_integration_approval_event_shape.py +0 -0
  96. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_integration_sources_redis_pgsql_smoke.py +0 -0
  97. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_loop.py +0 -0
  98. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_node_report_builder.py +0 -0
  99. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_node_report_contract_v1.py +0 -0
  100. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_node_report_engine_identity_contract.py +0 -0
  101. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_offline_backend_injection_evidence.py +0 -0
  102. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_preflight_gate.py +0 -0
  103. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_project_identity_naming_matrix.py +0 -0
  104. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_public_repo_hygiene.py +0 -0
  105. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_qa_agent.py +0 -0
  106. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_registry.py +0 -0
  107. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_replay_tool_calls_alignment.py +0 -0
  108. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_repo_hygiene_no_tracked_env_or_pyc.py +0 -0
  109. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_repo_no_deep_imports_in_user_facing_docs.py +0 -0
  110. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_review_followups_module_contracts.py +0 -0
  111. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_runtime_bridge_fake_backend.py +0 -0
  112. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_runtime_concurrency.py +0 -0
  113. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_runtime_engine.py +0 -0
  114. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_runtime_hitl_host_protocol.py +0 -0
  115. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_runtime_hooks_and_schema_gate.py +0 -0
  116. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_runtime_manifest.py +0 -0
  117. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_runtime_run_stream_semantics_v1.py +0 -0
  118. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_runtime_service_facade_rpc_mapping.py +0 -0
  119. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_runtime_service_session_bridge.py +0 -0
  120. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_runtime_status_mapping.py +0 -0
  121. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_runtime_structured_output_bridge.py +0 -0
  122. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_runtime_structured_stream.py +0 -0
  123. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_sandbox_permissions_passthrough_contract.py +0 -0
  124. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_services_call_callback.py +0 -0
  125. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_services_map_node_status.py +0 -0
  126. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_skills_conformance_smoke.py +0 -0
  127. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_upstream_adapter_tool_registration.py +0 -0
  128. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_upstream_chat_backend_protocol_contract.py +0 -0
  129. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_upstream_chat_sse_usage_contract.py +0 -0
  130. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_upstream_compat_spaces_schema.py +0 -0
  131. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_upstream_runtime_client_server_contract.py +0 -0
  132. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_upstream_sandbox_profile_precedence_contract.py +0 -0
  133. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_upstream_skills_bundles_contract.py +0 -0
  134. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_upstream_tool_descriptor_compat.py +0 -0
  135. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_upstream_verification.py +0 -0
  136. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_versioning_strategy_guard.py +0 -0
  137. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_wal_backend_injection_contract.py +0 -0
  138. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_wal_locator_resolution_contract.py +0 -0
  139. {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_workflow_host_runtime_surface.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: capability-runtime
3
- Version: 0.1.0
3
+ Version: 0.1.1
4
4
  Summary: Bridge/glue layer that composes Agently (LLM/TriggerFlow) with skills-runtime-sdk (skills/tools/WAL/events).
5
5
  License-Expression: MIT
6
6
  Requires-Python: >=3.10
@@ -8,7 +8,7 @@ Description-Content-Type: text/markdown
8
8
  Requires-Dist: PyYAML>=6
9
9
  Requires-Dist: pydantic<3,>=2
10
10
  Requires-Dist: agently==4.0.8
11
- Requires-Dist: skills-runtime-sdk==0.1.11
11
+ Requires-Dist: skills-runtime-sdk==0.1.12
12
12
  Provides-Extra: dev
13
13
  Requires-Dist: pytest>=7; extra == "dev"
14
14
  Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "capability-runtime"
3
- version = "0.1.0"
3
+ version = "0.1.1"
4
4
  description = "Bridge/glue layer that composes Agently (LLM/TriggerFlow) with skills-runtime-sdk (skills/tools/WAL/events)."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -9,7 +9,7 @@ dependencies = [
9
9
  "PyYAML>=6",
10
10
  "pydantic>=2,<3",
11
11
  "agently==4.0.8",
12
- "skills-runtime-sdk==0.1.11",
12
+ "skills-runtime-sdk==0.1.12",
13
13
  ]
14
14
 
15
15
  [project.optional-dependencies]
@@ -1,7 +1,7 @@
1
1
  """capability-runtime:统一 Runtime 入口(能力协议 + 执行 + 报告)。"""
2
2
  from __future__ import annotations
3
3
 
4
- __version__ = "0.1.0"
4
+ __version__ = "0.1.1"
5
5
 
6
6
  # === 统一入口 ===
7
7
  from .config import CustomTool, RuntimeConfig
@@ -18,7 +18,7 @@ from .host_protocol import ApprovalTicket, HostRunSnapshot, HostRunStatus, Resum
18
18
  from .manifest import CapabilityDescriptor, CapabilityManifestEntry, CapabilityVisibility
19
19
 
20
20
  # === Protocol 导出 ===
21
- from .protocol.agent import AgentIOSchema, AgentSpec
21
+ from .protocol.agent import AgentIOSchema, AgentSpec, PromptRenderMode
22
22
  from .protocol.capability import (
23
23
  CapabilityKind,
24
24
  CapabilityRef,
@@ -76,6 +76,7 @@ __all__ = [
76
76
  "RuntimeServiceFacade",
77
77
  "AgentSpec",
78
78
  "AgentIOSchema",
79
+ "PromptRenderMode",
79
80
  "WorkflowSpec",
80
81
  "Step",
81
82
  "LoopStep",
@@ -10,8 +10,11 @@ AgentAdapter:AgentSpec 的执行适配器(统一 mock/bridge/sdk_native)
10
10
  from __future__ import annotations
11
11
 
12
12
  import asyncio
13
+ import hashlib
13
14
  import inspect
14
15
  import json
16
+ import re
17
+ from dataclasses import dataclass
15
18
  from typing import Any, AsyncIterator, Dict, List, Optional, Union
16
19
 
17
20
  from skills_runtime.core.contracts import AgentEvent
@@ -30,6 +33,34 @@ _SECTION_TASK = "## 任务"
30
33
  _SECTION_INPUT = "## 输入"
31
34
  _SECTION_OUTPUT_PREFIX = "## 输出要求"
32
35
  _SECTION_SKILLS = "## 使用以下 Skills"
36
+ _RUNTIME_PROMPT_KEY = "_runtime_prompt"
37
+ _PROMPT_HASH_RE = re.compile(r"^sha256:[0-9a-f]{64}$")
38
+ _ALLOWED_PROMPT_ROLES = {"system", "developer", "user", "assistant", "tool"}
39
+ _ALLOWED_PROMPT_PROFILES = {"default_agent", "generation_direct", "structured_transform"}
40
+
41
+
42
+ class _InvalidPromptMessages(ValueError):
43
+ """prompt control envelope 非法。"""
44
+
45
+
46
+ @dataclass(frozen=True)
47
+ class _PromptRenderPlan:
48
+ """
49
+ AgentAdapter 内部 prompt 渲染计划。
50
+
51
+ 字段:
52
+ - mode:三种 prompt rendering strategy 之一;
53
+ - task:传给 SDK Agent.run_stream_async 的 task 文本;
54
+ - prompt_profile:透传给上游 SDK 的 prompt profile;
55
+ - precomposed_messages:最终 provider messages 覆写;
56
+ - evidence:写入 NodeReport.meta 的最小披露摘要。
57
+ """
58
+
59
+ mode: str
60
+ task: str
61
+ prompt_profile: Optional[str]
62
+ precomposed_messages: Optional[List[Dict[str, Any]]]
63
+ evidence: Dict[str, Any]
33
64
 
34
65
 
35
66
  class AgentAdapter:
@@ -168,6 +199,29 @@ class AgentAdapter:
168
199
  真实执行(bridge/sdk_native):驱动 SDK Agent.run_stream_async 并聚合 NodeReport。
169
200
  """
170
201
 
202
+ try:
203
+ prompt_plan = self._resolve_prompt_render_plan(spec=spec, input=input)
204
+ except _InvalidPromptMessages as exc:
205
+ report = self._services.build_fail_closed_report(
206
+ run_id=context.run_id,
207
+ status="failed",
208
+ reason="invalid_prompt_messages",
209
+ completion_reason="invalid_prompt_messages",
210
+ meta={
211
+ "capability_id": spec.base.id,
212
+ "source": "prompt_rendering",
213
+ "prompt_error": str(exc),
214
+ },
215
+ )
216
+ yield CapabilityResult(
217
+ status=CapabilityStatus.FAILED,
218
+ error=str(exc),
219
+ error_code="INVALID_PROMPT_MESSAGES",
220
+ report=report,
221
+ node_report=report,
222
+ )
223
+ return
224
+
171
225
  # preflight gate(生产默认 fail-closed)
172
226
  issues: List[FrameworkIssue] = []
173
227
  if getattr(self._services.config, "preflight_mode", "error") != "off":
@@ -184,6 +238,7 @@ class AgentAdapter:
184
238
  "code": "SKILL_PREFLIGHT_FAILED",
185
239
  "details": {"issues": [self._services.redact_issue(i) for i in issues]},
186
240
  },
241
+ **prompt_plan.evidence,
187
242
  },
188
243
  )
189
244
  yield CapabilityResult(
@@ -196,8 +251,34 @@ class AgentAdapter:
196
251
  )
197
252
  return
198
253
 
199
- task = self._build_task(spec=spec, input=input)
200
- agent = self._services.create_sdk_agent(llm_config=spec.llm_config)
254
+ task = prompt_plan.task
255
+ create_agent_kwargs: Dict[str, Any] = {"llm_config": spec.llm_config}
256
+ if prompt_plan.prompt_profile is not None:
257
+ create_agent_kwargs["prompt_profile"] = prompt_plan.prompt_profile
258
+ if prompt_plan.precomposed_messages is not None:
259
+ create_agent_kwargs["precomposed_messages"] = prompt_plan.precomposed_messages
260
+ try:
261
+ agent = self._services.create_sdk_agent(**create_agent_kwargs)
262
+ except Exception as exc:
263
+ report = self._services.build_fail_closed_report(
264
+ run_id=context.run_id,
265
+ status="failed",
266
+ reason="engine_error",
267
+ completion_reason="engine_exception",
268
+ meta={
269
+ "source": "sdk_agent_create",
270
+ "engine_exception": type(exc).__name__,
271
+ **prompt_plan.evidence,
272
+ },
273
+ )
274
+ yield CapabilityResult(
275
+ status=CapabilityStatus.FAILED,
276
+ error=str(exc),
277
+ error_code="ENGINE_ERROR",
278
+ report=report,
279
+ node_report=report,
280
+ )
281
+ return
201
282
 
202
283
  events: List[AgentEvent] = []
203
284
  host_meta = self._services.get_host_meta(context=context)
@@ -241,6 +322,7 @@ class AgentAdapter:
241
322
  completion_reason="run_cancelled",
242
323
  meta={"capability_id": spec.base.id, "source": "sdk_agent"},
243
324
  )
325
+ self._apply_prompt_evidence(report=report, evidence=prompt_plan.evidence)
244
326
  yield CapabilityResult(
245
327
  status=CapabilityStatus.CANCELLED,
246
328
  error="execution cancelled",
@@ -255,7 +337,7 @@ class AgentAdapter:
255
337
  status="failed",
256
338
  reason="engine_error",
257
339
  completion_reason="engine_exception",
258
- meta={"engine_exception": type(exc).__name__},
340
+ meta={"engine_exception": type(exc).__name__, **prompt_plan.evidence},
259
341
  )
260
342
  yield CapabilityResult(
261
343
  status=CapabilityStatus.FAILED,
@@ -267,6 +349,7 @@ class AgentAdapter:
267
349
  return
268
350
 
269
351
  report = NodeReportBuilder().build(events=events)
352
+ self._apply_prompt_evidence(report=report, evidence=prompt_plan.evidence)
270
353
  if issues and getattr(self._services.config, "preflight_mode", "error") == "warn":
271
354
  report.meta["preflight_mode"] = "warn"
272
355
  report.meta["preflight_issues"] = [self._services.redact_issue(i) for i in issues]
@@ -306,6 +389,166 @@ class AgentAdapter:
306
389
  artifacts=list(report.artifacts),
307
390
  )
308
391
 
392
+ def _resolve_prompt_render_plan(self, *, spec: AgentSpec, input: Dict[str, Any]) -> _PromptRenderPlan:
393
+ """
394
+ 解析 AgentSpec + run-level `_runtime_prompt`,生成 prompt 渲染计划。
395
+
396
+ 参数:
397
+ - spec:Agent 声明
398
+ - input:run 输入,其中 `_runtime_prompt` 为保留控制面
399
+
400
+ 返回:
401
+ - `_PromptRenderPlan`
402
+
403
+ 异常:
404
+ - `_InvalidPromptMessages`:控制面非法,调用方应 fail-fast。
405
+ """
406
+
407
+ envelope_raw = input.get(_RUNTIME_PROMPT_KEY)
408
+ if envelope_raw is None:
409
+ envelope: Dict[str, Any] = {}
410
+ elif isinstance(envelope_raw, dict):
411
+ envelope = dict(envelope_raw)
412
+ else:
413
+ raise _InvalidPromptMessages("_runtime_prompt must be a dict")
414
+
415
+ raw_mode = envelope.get("mode") if "mode" in envelope else getattr(spec, "prompt_render_mode", "structured_task")
416
+ if raw_mode is None:
417
+ raw_mode = "structured_task"
418
+ if not isinstance(raw_mode, str) or not raw_mode.strip():
419
+ raise _InvalidPromptMessages("prompt render mode must be a non-empty string")
420
+ mode = raw_mode.strip()
421
+ if mode not in {"structured_task", "direct_task_text", "precomposed_messages"}:
422
+ raise _InvalidPromptMessages(
423
+ "unsupported prompt render mode; allowed: direct_task_text, precomposed_messages, structured_task"
424
+ )
425
+
426
+ prompt_profile = envelope.get("profile")
427
+ if prompt_profile is None:
428
+ prompt_profile = getattr(spec, "prompt_profile", None)
429
+ if prompt_profile is not None:
430
+ if not isinstance(prompt_profile, str) or not prompt_profile.strip():
431
+ raise _InvalidPromptMessages("prompt profile must be a non-empty string")
432
+ prompt_profile = prompt_profile.strip()
433
+ if prompt_profile not in _ALLOWED_PROMPT_PROFILES:
434
+ allowed = ", ".join(sorted(_ALLOWED_PROMPT_PROFILES))
435
+ raise _InvalidPromptMessages(f"unsupported prompt profile; allowed: {allowed}")
436
+
437
+ trace = envelope.get("trace") if "trace" in envelope else {}
438
+ if trace is None:
439
+ trace = {}
440
+ if not isinstance(trace, dict):
441
+ raise _InvalidPromptMessages("_runtime_prompt.trace must be a dict")
442
+ prompt_hash = trace.get("prompt_hash")
443
+ if prompt_hash is not None:
444
+ if not isinstance(prompt_hash, str) or _PROMPT_HASH_RE.fullmatch(prompt_hash.strip()) is None:
445
+ raise _InvalidPromptMessages("trace.prompt_hash must match sha256:<64 lowercase hex>")
446
+ prompt_hash = prompt_hash.strip()
447
+ composer_version = trace.get("composer_version")
448
+ if composer_version is not None and not isinstance(composer_version, str):
449
+ raise _InvalidPromptMessages("trace.composer_version must be a string")
450
+
451
+ if mode == "structured_task":
452
+ business_input = self._strip_runtime_prompt(input)
453
+ task = self._build_task(spec=spec, input=business_input)
454
+ evidence = self._build_prompt_evidence(
455
+ mode=mode,
456
+ prompt_profile=prompt_profile,
457
+ prompt_hash=prompt_hash or _hash_text(task),
458
+ composer_version=composer_version,
459
+ messages=None,
460
+ )
461
+ return _PromptRenderPlan(
462
+ mode=mode,
463
+ task=task,
464
+ prompt_profile=prompt_profile,
465
+ precomposed_messages=None,
466
+ evidence=evidence,
467
+ )
468
+
469
+ if mode == "direct_task_text":
470
+ task_text = envelope.get("task_text")
471
+ if not isinstance(task_text, str) or not task_text.strip():
472
+ raise _InvalidPromptMessages("_runtime_prompt.task_text must be a non-empty string")
473
+ task = task_text
474
+ evidence = self._build_prompt_evidence(
475
+ mode=mode,
476
+ prompt_profile=prompt_profile,
477
+ prompt_hash=prompt_hash or _hash_text(task),
478
+ composer_version=composer_version,
479
+ messages=None,
480
+ )
481
+ return _PromptRenderPlan(
482
+ mode=mode,
483
+ task=task,
484
+ prompt_profile=prompt_profile,
485
+ precomposed_messages=None,
486
+ evidence=evidence,
487
+ )
488
+
489
+ messages = _validate_precomposed_messages(envelope.get("messages"))
490
+ evidence = self._build_prompt_evidence(
491
+ mode=mode,
492
+ prompt_profile=prompt_profile,
493
+ prompt_hash=prompt_hash or _hash_messages(messages),
494
+ composer_version=composer_version,
495
+ messages=messages,
496
+ )
497
+ return _PromptRenderPlan(
498
+ mode=mode,
499
+ task="",
500
+ prompt_profile=prompt_profile,
501
+ precomposed_messages=messages,
502
+ evidence=evidence,
503
+ )
504
+
505
+ def _build_prompt_evidence(
506
+ self,
507
+ *,
508
+ mode: str,
509
+ prompt_profile: Optional[str],
510
+ prompt_hash: str,
511
+ composer_version: Any,
512
+ messages: Optional[List[Dict[str, Any]]],
513
+ ) -> Dict[str, Any]:
514
+ """
515
+ 构造 NodeReport.meta 的 prompt 最小披露摘要。
516
+
517
+ 参数:
518
+ - mode:prompt render mode
519
+ - prompt_profile:可选 SDK prompt profile
520
+ - prompt_hash:`sha256:<hex>` 摘要
521
+ - composer_version:可选 composer 版本
522
+ - messages:precomposed messages;仅用于 count/roles 摘要
523
+ """
524
+
525
+ evidence: Dict[str, Any] = {
526
+ "prompt_render_mode": mode,
527
+ "prompt_hash": prompt_hash,
528
+ }
529
+ if prompt_profile:
530
+ evidence["prompt_profile"] = prompt_profile
531
+ if isinstance(composer_version, str) and composer_version:
532
+ evidence["prompt_composer_version"] = composer_version
533
+ if messages is not None:
534
+ evidence["prompt_messages_count"] = len(messages)
535
+ evidence["prompt_message_roles"] = [str(item["role"]) for item in messages]
536
+ return evidence
537
+
538
+ def _strip_runtime_prompt(self, input: Dict[str, Any]) -> Dict[str, Any]:
539
+ """从业务 input 中剥离 runtime prompt 控制面。"""
540
+
541
+ if _RUNTIME_PROMPT_KEY not in input:
542
+ return input
543
+ return {k: v for k, v in input.items() if k != _RUNTIME_PROMPT_KEY}
544
+
545
+ def _apply_prompt_evidence(self, *, report: Any, evidence: Dict[str, Any]) -> None:
546
+ """把 prompt evidence 写入 NodeReport.meta;兼容测试中的 dict fake report。"""
547
+
548
+ meta = getattr(report, "meta", None)
549
+ if isinstance(meta, dict):
550
+ meta.update(evidence)
551
+
309
552
  def _build_task(self, *, spec: AgentSpec, input: Dict[str, Any]) -> str:
310
553
  """
311
554
  将 AgentSpec + input 转换为 SDK Agent 的 task 文本(结构化拼接)。
@@ -323,9 +566,10 @@ class AgentAdapter:
323
566
  if spec.base.description:
324
567
  parts.append(f"{_SECTION_TASK}\n{spec.base.description}")
325
568
 
326
- if input:
569
+ business_input = self._strip_runtime_prompt(input)
570
+ if business_input:
327
571
  lines: List[str] = []
328
- for k, v in input.items():
572
+ for k, v in business_input.items():
329
573
  if isinstance(v, str):
330
574
  lines.append(f"- {k}: {v}")
331
575
  else:
@@ -437,3 +681,48 @@ class AgentAdapter:
437
681
  if account and domain:
438
682
  return f"[{account}:{domain}]"
439
683
  return None
684
+
685
+
686
+ def _hash_text(text: str) -> str:
687
+ """生成 prompt 文本的 sha256 摘要。"""
688
+
689
+ return "sha256:" + hashlib.sha256(text.encode("utf-8")).hexdigest()
690
+
691
+
692
+ def _hash_messages(messages: List[Dict[str, Any]]) -> str:
693
+ """对 provider messages 做稳定 JSON canonicalization 后生成摘要。"""
694
+
695
+ canonical = json.dumps(messages, ensure_ascii=False, sort_keys=True, separators=(",", ":"))
696
+ return _hash_text(canonical)
697
+
698
+
699
+ def _validate_precomposed_messages(raw: Any) -> List[Dict[str, Any]]:
700
+ """
701
+ 校验 host 提供的最终 provider messages。
702
+
703
+ 约束:
704
+ - 只接受非空 list[dict];
705
+ - 每条消息必须有合法 role 与字符串 content;
706
+ - 返回浅拷贝,避免后续执行链路修改调用方输入。
707
+ """
708
+
709
+ if not isinstance(raw, list):
710
+ raise _InvalidPromptMessages("_runtime_prompt.messages must be a list")
711
+ if not raw:
712
+ raise _InvalidPromptMessages("_runtime_prompt.messages must not be empty")
713
+
714
+ out: List[Dict[str, Any]] = []
715
+ for idx, item in enumerate(raw):
716
+ if not isinstance(item, dict):
717
+ raise _InvalidPromptMessages(f"_runtime_prompt.messages[{idx}] must be a dict")
718
+ role = item.get("role")
719
+ content = item.get("content")
720
+ if not isinstance(role, str) or role not in _ALLOWED_PROMPT_ROLES:
721
+ raise _InvalidPromptMessages(f"_runtime_prompt.messages[{idx}].role is invalid")
722
+ if not isinstance(content, str):
723
+ raise _InvalidPromptMessages(f"_runtime_prompt.messages[{idx}].content must be a string")
724
+ copied = dict(item)
725
+ copied["role"] = role
726
+ copied["content"] = content
727
+ out.append(copied)
728
+ return out
@@ -2,7 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  """Protocol 层:纯能力类型定义,不依赖任何上游模块。"""
4
4
 
5
- from .agent import AgentIOSchema, AgentSpec
5
+ from .agent import AgentIOSchema, AgentSpec, PromptRenderMode
6
6
  from .capability import (
7
7
  CapabilityKind,
8
8
  CapabilityRef,
@@ -30,6 +30,7 @@ __all__ = [
30
30
  "CapabilityResult",
31
31
  "AgentSpec",
32
32
  "AgentIOSchema",
33
+ "PromptRenderMode",
33
34
  "ChatBackendProtocol",
34
35
  "WorkflowSpec",
35
36
  "Step",
@@ -3,11 +3,14 @@ from __future__ import annotations
3
3
  """Agent 元能力声明。"""
4
4
 
5
5
  from dataclasses import dataclass, field
6
- from typing import Any, Dict, List, Optional
6
+ from typing import Any, Dict, List, Literal, Optional
7
7
 
8
8
  from .capability import CapabilityRef, CapabilitySpec
9
9
 
10
10
 
11
+ PromptRenderMode = Literal["structured_task", "direct_task_text", "precomposed_messages"]
12
+
13
+
11
14
  @dataclass(frozen=True)
12
15
  class AgentIOSchema:
13
16
  """
@@ -36,6 +39,8 @@ class AgentSpec:
36
39
  - output_schema: 输出 schema(可选)
37
40
  - loop_compatible: 是否可被 LoopStep 循环调用
38
41
  - llm_config: LLM 覆盖配置
42
+ - prompt_render_mode: prompt 渲染模式(默认 structured_task,保持兼容)
43
+ - prompt_profile: 可选上游 SDK prompt profile(如 generation_direct)
39
44
  - prompt_template: 可选的 prompt 模板(支持 {field} 占位符)
40
45
  - system_prompt: 可选的“Agent 级 system message”(用于该 Agent 的提示词组织)
41
46
 
@@ -58,5 +63,7 @@ class AgentSpec:
58
63
  output_schema: Optional[AgentIOSchema] = None
59
64
  loop_compatible: bool = False
60
65
  llm_config: Optional[Dict[str, Any]] = None
66
+ prompt_render_mode: PromptRenderMode = "structured_task"
67
+ prompt_profile: Optional[str] = None
61
68
  prompt_template: Optional[str] = None
62
69
  system_prompt: Optional[str] = None
@@ -914,17 +914,30 @@ class Runtime(RuntimeUIEventsMixin):
914
914
  return []
915
915
  return self._sdk.preflight()
916
916
 
917
- def create_sdk_agent(self, *, llm_config: Optional[Dict[str, Any]] = None) -> Any:
917
+ def create_sdk_agent(
918
+ self,
919
+ *,
920
+ llm_config: Optional[Dict[str, Any]] = None,
921
+ prompt_profile: Optional[str] = None,
922
+ precomposed_messages: Optional[List[Dict[str, Any]]] = None,
923
+ ) -> Any:
918
924
  """
919
925
  RuntimeServices 协议方法:创建 per-run SDK Agent。
920
926
 
921
927
  参数:
922
- - llm_config:可选 LLM 覆写配置(当前仅支持 `model` 字段覆写)
928
+ - llm_config:可选 LLM 覆写配置
929
+ - prompt_profile:可选 SDK prompt profile
930
+ - precomposed_messages:可选最终 provider messages 覆写
923
931
  """
924
932
 
925
933
  if self._sdk is None:
926
934
  raise RuntimeError("SDK lifecycle is not initialized")
927
- return self._sdk.create_agent(custom_tools=list(self._config.custom_tools), llm_config=llm_config)
935
+ return self._sdk.create_agent(
936
+ custom_tools=list(self._config.custom_tools),
937
+ llm_config=llm_config,
938
+ prompt_profile=prompt_profile,
939
+ precomposed_messages=precomposed_messages,
940
+ )
928
941
 
929
942
 
930
943
  __all__ = ["Runtime"]
@@ -2,9 +2,11 @@ from __future__ import annotations
2
2
 
3
3
  """SDK 生命周期组件:初始化、预检与 per-run Agent 创建。"""
4
4
 
5
+ import hashlib
5
6
  import inspect
6
- from datetime import datetime, timezone
7
+ import uuid
7
8
  from dataclasses import dataclass
9
+ from datetime import datetime, timezone
8
10
  from pathlib import Path
9
11
  from typing import Any, AsyncGenerator, AsyncIterator, Dict, List, Optional
10
12
 
@@ -83,13 +85,22 @@ class SdkLifecycle:
83
85
  )
84
86
  ]
85
87
 
86
- def create_agent(self, *, custom_tools: List[CustomTool], llm_config: Optional[Dict[str, Any]] = None) -> Any:
88
+ def create_agent(
89
+ self,
90
+ *,
91
+ custom_tools: List[CustomTool],
92
+ llm_config: Optional[Dict[str, Any]] = None,
93
+ prompt_profile: Optional[str] = None,
94
+ precomposed_messages: Optional[List[Dict[str, Any]]] = None,
95
+ ) -> Any:
87
96
  """
88
97
  创建 per-run SDK Agent 实例(避免跨 run 共享可变状态)。
89
98
 
90
99
  参数:
91
100
  - custom_tools:每次创建 Agent 时要注册的自定义工具列表
92
101
  - llm_config:可选 LLM 覆写配置(当前支持 `model`、`tool_choice` 与 `response_format` 字段覆写)
102
+ - prompt_profile:可选 SDK prompt profile,通过 per-run config overlay 透传给上游
103
+ - precomposed_messages:可选最终 provider messages 覆写
93
104
  """
94
105
 
95
106
  from skills_runtime.core.agent import Agent
@@ -106,12 +117,23 @@ class SdkLifecycle:
106
117
  override_response_format = _extract_response_format_override(llm_config)
107
118
  if override_response_format is not None:
108
119
  backend = _ResponseFormatOverrideBackend(backend=backend, response_format=override_response_format)
120
+ if precomposed_messages is not None:
121
+ backend = _PrecomposedMessagesBackend(backend=backend, messages=precomposed_messages)
109
122
  usage_bridge_backend = _UsageTapBackend(backend=backend)
110
123
  backend = usage_bridge_backend
111
124
 
125
+ config_paths = list(self._state.config_paths)
126
+ prompt_profile_overlay: Optional[Path] = None
127
+ if prompt_profile is not None:
128
+ prompt_profile_overlay = _write_prompt_profile_overlay(
129
+ workspace_root=self._state.workspace_root,
130
+ prompt_profile=prompt_profile,
131
+ )
132
+ config_paths.append(prompt_profile_overlay)
133
+
112
134
  kwargs: Dict[str, Any] = {
113
135
  "workspace_root": self._state.workspace_root,
114
- "config_paths": list(self._state.config_paths),
136
+ "config_paths": config_paths,
115
137
  "env_vars": dict(self._config.env_vars),
116
138
  "backend": backend,
117
139
  "human_io": self._config.human_io,
@@ -124,7 +146,18 @@ class SdkLifecycle:
124
146
  "wal_backend": self._config.wal_backend,
125
147
  }
126
148
 
127
- agent = Agent(**kwargs)
149
+ try:
150
+ agent = Agent(**kwargs)
151
+ finally:
152
+ if prompt_profile_overlay is not None:
153
+ try:
154
+ prompt_profile_overlay.unlink(missing_ok=True)
155
+ except Exception as exc:
156
+ log_suppressed_exception(
157
+ context="prompt_profile_overlay_cleanup",
158
+ exc=exc,
159
+ extra={"path": str(prompt_profile_overlay)},
160
+ )
128
161
  diagnostics: Dict[str, Dict[str, bool]] = {}
129
162
  for t in custom_tools:
130
163
  diagnostics[t.spec.name] = _register_custom_tool_compat(agent, t)
@@ -558,6 +591,39 @@ class _ResponseFormatOverrideBackend:
558
591
  yield ev
559
592
 
560
593
 
594
+ class _PrecomposedMessagesBackend:
595
+ """
596
+ ChatBackend 薄代理:仅覆写 request.messages,然后委托给底层 backend。
597
+
598
+ 说明:
599
+ - 该包装不绕过 SDK Agent / WAL / events,只在 provider request 出口替换 messages;
600
+ - messages 做浅拷贝转发,避免下游 backend 修改 host 输入。
601
+ """
602
+
603
+ def __init__(self, *, backend: ChatBackendProtocol, messages: List[Dict[str, Any]]) -> None:
604
+ self._backend: ChatBackendProtocol = backend
605
+ self._messages: List[Dict[str, Any]] = [dict(item) for item in messages]
606
+
607
+ async def stream_chat(self, request: ChatRequest) -> AsyncGenerator[ChatStreamEvent, None]:
608
+ """
609
+ 覆写 request.messages 并转发 `stream_chat`。
610
+
611
+ 参数:
612
+ - request:上游 SDK 生成的 ChatRequest(或兼容对象)
613
+ """
614
+
615
+ forwarded = _clone_request_with_field_update(
616
+ request,
617
+ field_name="messages",
618
+ value=[dict(item) for item in self._messages],
619
+ dataclasses_context="precomposed_messages_dataclasses_replace",
620
+ clone_context="precomposed_messages_override",
621
+ )
622
+
623
+ async for ev in self._backend.stream_chat(forwarded):
624
+ yield ev
625
+
626
+
561
627
  class _UsageTapBackend:
562
628
  """
563
629
  ChatBackend 薄代理:通过 request.extra 注入 usage sink,best-effort 收集 bridge usage。
@@ -737,6 +803,36 @@ def _clone_request_with_extra(request: Any, update_extra: Any) -> Any:
737
803
  return forwarded
738
804
 
739
805
 
806
+ def _write_prompt_profile_overlay(*, workspace_root: Path, prompt_profile: str) -> Path:
807
+ """
808
+ 写入 per-run prompt profile SDK overlay,并返回 overlay 路径。
809
+
810
+ 说明:
811
+ - 只写 profile 名称,不写 prompt 明文;
812
+ - 文件名使用 profile 摘要,避免 profile 字符串直接进入路径。
813
+ """
814
+
815
+ import yaml
816
+
817
+ profile = str(prompt_profile).strip()
818
+ if not profile:
819
+ raise ValueError("prompt_profile must be a non-empty string")
820
+
821
+ digest = hashlib.sha256(profile.encode("utf-8")).hexdigest()[:16]
822
+ overlay_dir = Path(workspace_root) / ".skills_runtime_sdk"
823
+ overlay_dir.mkdir(parents=True, exist_ok=True)
824
+ overlay_path = overlay_dir / f"caprt_prompt_profile_{digest}_{uuid.uuid4().hex}.yaml"
825
+ overlay_path.write_text(
826
+ yaml.safe_dump(
827
+ {"prompt": {"profile": profile}},
828
+ allow_unicode=True,
829
+ sort_keys=True,
830
+ ),
831
+ encoding="utf-8",
832
+ )
833
+ return overlay_path
834
+
835
+
740
836
  def _load_yaml_dict(path: Path) -> Dict[str, Any]:
741
837
  """
742
838
  读取 YAML 文件并返回 dict。