agent-lifecycle-kit 1.50.1__py3-none-any.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.
Files changed (227) hide show
  1. agent_lifecycle/__init__.py +5 -0
  2. agent_lifecycle/__main__.py +5 -0
  3. agent_lifecycle/_version.py +3 -0
  4. agent_lifecycle/adapter_sessions/__init__.py +41 -0
  5. agent_lifecycle/adapter_sessions/contracts.py +133 -0
  6. agent_lifecycle/adapter_sessions/env.py +44 -0
  7. agent_lifecycle/adapter_sessions/launcher.py +101 -0
  8. agent_lifecycle/adapter_sessions/process.py +39 -0
  9. agent_lifecycle/adapter_sessions/redaction.py +30 -0
  10. agent_lifecycle/adapter_sessions/session_store.py +64 -0
  11. agent_lifecycle/adapter_sessions/task_intake.py +450 -0
  12. agent_lifecycle/adapter_sessions/workflow_bridge.py +154 -0
  13. agent_lifecycle/audit/__init__.py +67 -0
  14. agent_lifecycle/audit/bug_forensics.py +94 -0
  15. agent_lifecycle/audit/implementation.py +516 -0
  16. agent_lifecycle/audit/ownership.py +119 -0
  17. agent_lifecycle/audit/proof_integrity.py +814 -0
  18. agent_lifecycle/audit/review_verdict.py +116 -0
  19. agent_lifecycle/changesets/__init__.py +5 -0
  20. agent_lifecycle/changesets/git.py +35 -0
  21. agent_lifecycle/cli/__init__.py +5 -0
  22. agent_lifecycle/cli/adapter.py +360 -0
  23. agent_lifecycle/cli/dispatch.py +1220 -0
  24. agent_lifecycle/cli/followup.py +89 -0
  25. agent_lifecycle/cli/main.py +41 -0
  26. agent_lifecycle/cli/metrics_parser.py +49 -0
  27. agent_lifecycle/cli/parsers.py +618 -0
  28. agent_lifecycle/cli/policy.py +101 -0
  29. agent_lifecycle/cli/progress_hooks.py +117 -0
  30. agent_lifecycle/cli/worktree.py +74 -0
  31. agent_lifecycle/compiler/__init__.py +17 -0
  32. agent_lifecycle/compiler/small_model_packets.py +322 -0
  33. agent_lifecycle/compiler/task_packets.py +229 -0
  34. agent_lifecycle/context/__init__.py +20 -0
  35. agent_lifecycle/context/episode_retrieval.py +30 -0
  36. agent_lifecycle/context/external_memory.py +71 -0
  37. agent_lifecycle/context/profiles.py +104 -0
  38. agent_lifecycle/context/rendering.py +226 -0
  39. agent_lifecycle/contracts/__init__.py +25 -0
  40. agent_lifecycle/contracts/adapter_contract_schemas.py +130 -0
  41. agent_lifecycle/contracts/adapter_event_schemas.py +118 -0
  42. agent_lifecycle/contracts/adapter_session_schemas.py +130 -0
  43. agent_lifecycle/contracts/adapter_task_schemas.py +90 -0
  44. agent_lifecycle/contracts/audit_schemas.py +133 -0
  45. agent_lifecycle/contracts/bug_forensics_schemas.py +400 -0
  46. agent_lifecycle/contracts/canonical.py +55 -0
  47. agent_lifecycle/contracts/compatibility.py +242 -0
  48. agent_lifecycle/contracts/context_model_schemas.py +265 -0
  49. agent_lifecycle/contracts/core_schemas.py +252 -0
  50. agent_lifecycle/contracts/cross_check_schemas.py +121 -0
  51. agent_lifecycle/contracts/errors.py +26 -0
  52. agent_lifecycle/contracts/evidence_import_schemas.py +152 -0
  53. agent_lifecycle/contracts/host_capability_schemas.py +64 -0
  54. agent_lifecycle/contracts/import_dialect_schemas.py +166 -0
  55. agent_lifecycle/contracts/metric_schemas.py +315 -0
  56. agent_lifecycle/contracts/paths.py +31 -0
  57. agent_lifecycle/contracts/plan_contract_schemas.py +148 -0
  58. agent_lifecycle/contracts/policy_schemas.py +350 -0
  59. agent_lifecycle/contracts/progress_bridge_schemas.py +114 -0
  60. agent_lifecycle/contracts/progress_hook_schemas.py +119 -0
  61. agent_lifecycle/contracts/proof_integrity_schemas.py +240 -0
  62. agent_lifecycle/contracts/release_contract_schemas.py +476 -0
  63. agent_lifecycle/contracts/review_mesh_recommendation_schemas.py +64 -0
  64. agent_lifecycle/contracts/review_mesh_schemas.py +222 -0
  65. agent_lifecycle/contracts/review_quality_schemas.py +109 -0
  66. agent_lifecycle/contracts/runner_schemas.py +91 -0
  67. agent_lifecycle/contracts/runner_worktree_schemas.py +389 -0
  68. agent_lifecycle/contracts/sandbox_schemas.py +159 -0
  69. agent_lifecycle/contracts/schema_builders.py +23 -0
  70. agent_lifecycle/contracts/schemas.py +93 -0
  71. agent_lifecycle/contracts/status_goal_schemas.py +469 -0
  72. agent_lifecycle/contracts/task_template_schemas.py +96 -0
  73. agent_lifecycle/contracts/usage_export_schemas.py +68 -0
  74. agent_lifecycle/diagnostics/__init__.py +17 -0
  75. agent_lifecycle/diagnostics/bundles.py +124 -0
  76. agent_lifecycle/diagnostics/readiness.py +733 -0
  77. agent_lifecycle/evidence_index/__init__.py +39 -0
  78. agent_lifecycle/evidence_index/core.py +272 -0
  79. agent_lifecycle/evidence_index/episode_index.py +329 -0
  80. agent_lifecycle/evidence_index/external_context.py +276 -0
  81. agent_lifecycle/followup/__init__.py +23 -0
  82. agent_lifecycle/followup/records.py +404 -0
  83. agent_lifecycle/freeze/__init__.py +5 -0
  84. agent_lifecycle/freeze/locks.py +23 -0
  85. agent_lifecycle/goal/__init__.py +15 -0
  86. agent_lifecycle/goal/records.py +244 -0
  87. agent_lifecycle/goal/view.py +109 -0
  88. agent_lifecycle/host_protocol/__init__.py +65 -0
  89. agent_lifecycle/host_protocol/acp_capability.py +195 -0
  90. agent_lifecycle/host_protocol/capabilities.py +268 -0
  91. agent_lifecycle/host_protocol/contracts.py +214 -0
  92. agent_lifecycle/host_protocol/event_capture.py +390 -0
  93. agent_lifecycle/host_protocol/events.py +196 -0
  94. agent_lifecycle/host_protocol/inspection.py +194 -0
  95. agent_lifecycle/host_protocol/inspection_common.py +310 -0
  96. agent_lifecycle/host_protocol/inspection_cursor.py +164 -0
  97. agent_lifecycle/host_protocol/inspection_gemini_cli.py +163 -0
  98. agent_lifecycle/host_protocol/inspection_hermes.py +161 -0
  99. agent_lifecycle/host_protocol/inspection_kimi_code.py +169 -0
  100. agent_lifecycle/host_protocol/inspection_opencode.py +147 -0
  101. agent_lifecycle/host_protocol/inspection_qwen_code.py +143 -0
  102. agent_lifecycle/host_protocol/receipts.py +58 -0
  103. agent_lifecycle/host_protocol/scaffold.py +329 -0
  104. agent_lifecycle/host_protocol/validation.py +229 -0
  105. agent_lifecycle/imports/__init__.py +76 -0
  106. agent_lifecycle/imports/agentskills_profile.py +54 -0
  107. agent_lifecycle/imports/bmad_profile.py +49 -0
  108. agent_lifecycle/imports/constitution_adr.py +91 -0
  109. agent_lifecycle/imports/dialect_profiles.py +121 -0
  110. agent_lifecycle/imports/external_agent.py +28 -0
  111. agent_lifecycle/imports/external_dialects.py +486 -0
  112. agent_lifecycle/imports/external_workflow.py +28 -0
  113. agent_lifecycle/imports/multi_markdown.py +111 -0
  114. agent_lifecycle/imports/openspec_profile.py +49 -0
  115. agent_lifecycle/imports/planning.py +536 -0
  116. agent_lifecycle/imports/spec_kit_profile.py +49 -0
  117. agent_lifecycle/imports/spec_kitty_profile.py +49 -0
  118. agent_lifecycle/metrics/__init__.py +60 -0
  119. agent_lifecycle/metrics/cost_collection.py +315 -0
  120. agent_lifecycle/metrics/costs.py +203 -0
  121. agent_lifecycle/metrics/outcome_index.py +330 -0
  122. agent_lifecycle/metrics/phase_resources.py +208 -0
  123. agent_lifecycle/metrics/recommendations.py +507 -0
  124. agent_lifecycle/metrics/regression_signals.py +64 -0
  125. agent_lifecycle/metrics/usage_export.py +390 -0
  126. agent_lifecycle/model_routing/__init__.py +19 -0
  127. agent_lifecycle/model_routing/profiles.py +323 -0
  128. agent_lifecycle/model_routing/receipts.py +140 -0
  129. agent_lifecycle/model_routing/resolver.py +566 -0
  130. agent_lifecycle/neutrality/__init__.py +12 -0
  131. agent_lifecycle/neutrality/__main__.py +6 -0
  132. agent_lifecycle/neutrality/authority.py +265 -0
  133. agent_lifecycle/neutrality/canonical.py +28 -0
  134. agent_lifecycle/neutrality/cli.py +294 -0
  135. agent_lifecycle/neutrality/ed25519.py +124 -0
  136. agent_lifecycle/neutrality/errors.py +89 -0
  137. agent_lifecycle/neutrality/gate.py +269 -0
  138. agent_lifecycle/neutrality/paths.py +92 -0
  139. agent_lifecycle/neutrality/policy.py +76 -0
  140. agent_lifecycle/neutrality/receipt.py +135 -0
  141. agent_lifecycle/neutrality/scanner.py +310 -0
  142. agent_lifecycle/neutrality/test_runner.py +116 -0
  143. agent_lifecycle/planning/__init__.py +47 -0
  144. agent_lifecycle/planning/acceptance_markdown.py +159 -0
  145. agent_lifecycle/planning/completeness.py +362 -0
  146. agent_lifecycle/planning/continuity.py +255 -0
  147. agent_lifecycle/planning/templates.py +294 -0
  148. agent_lifecycle/planning/tiering.py +87 -0
  149. agent_lifecycle/planning/validation.py +72 -0
  150. agent_lifecycle/policy/__init__.py +37 -0
  151. agent_lifecycle/policy/adaptive_lifecycle.py +416 -0
  152. agent_lifecycle/policy/apply.py +44 -0
  153. agent_lifecycle/policy/proposals.py +189 -0
  154. agent_lifecycle/policy/quality_floor.py +144 -0
  155. agent_lifecycle/policy/runtime_receipts.py +160 -0
  156. agent_lifecycle/py.typed +1 -0
  157. agent_lifecycle/quality/__init__.py +89 -0
  158. agent_lifecycle/quality/bug_forensics.py +727 -0
  159. agent_lifecycle/quality/bug_forensics_advisor.py +88 -0
  160. agent_lifecycle/quality/bug_forensics_recipes.py +239 -0
  161. agent_lifecycle/quality/cross_check.py +437 -0
  162. agent_lifecycle/quality/failure_classification.py +318 -0
  163. agent_lifecycle/quality/packs.py +265 -0
  164. agent_lifecycle/reporting/__init__.py +37 -0
  165. agent_lifecycle/reporting/change_summary.py +156 -0
  166. agent_lifecycle/reporting/event_feed.py +101 -0
  167. agent_lifecycle/reporting/progress_bridge.py +153 -0
  168. agent_lifecycle/reporting/progress_hooks.py +161 -0
  169. agent_lifecycle/reporting/progress_terminal.py +95 -0
  170. agent_lifecycle/reporting/progress_view.py +284 -0
  171. agent_lifecycle/reporting/status_view.py +125 -0
  172. agent_lifecycle/reporting/usage_export.py +59 -0
  173. agent_lifecycle/review/__init__.py +5 -0
  174. agent_lifecycle/review/validation.py +29 -0
  175. agent_lifecycle/review_mesh/__init__.py +83 -0
  176. agent_lifecycle/review_mesh/assignments.py +160 -0
  177. agent_lifecycle/review_mesh/contracts.py +685 -0
  178. agent_lifecycle/review_mesh/operator_templates.py +320 -0
  179. agent_lifecycle/review_mesh/quorum.py +38 -0
  180. agent_lifecycle/review_mesh/recommendation.py +487 -0
  181. agent_lifecycle/review_mesh/results.py +98 -0
  182. agent_lifecycle/review_mesh/synthesis.py +102 -0
  183. agent_lifecycle/runner/__init__.py +47 -0
  184. agent_lifecycle/runner/attempt_snapshots.py +244 -0
  185. agent_lifecycle/runner/core.py +653 -0
  186. agent_lifecycle/runner/sandbox_receipts.py +628 -0
  187. agent_lifecycle/specification/__init__.py +23 -0
  188. agent_lifecycle/specification/completion_check.py +135 -0
  189. agent_lifecycle/specification/completion_gate.py +501 -0
  190. agent_lifecycle/specification/completion_signal.py +75 -0
  191. agent_lifecycle/specification/validation.py +48 -0
  192. agent_lifecycle/workflow/__init__.py +79 -0
  193. agent_lifecycle/workflow/artifacts.py +74 -0
  194. agent_lifecycle/workflow/budget_decisions.py +370 -0
  195. agent_lifecycle/workflow/budget_policy.py +140 -0
  196. agent_lifecycle/workflow/budget_receipts.py +70 -0
  197. agent_lifecycle/workflow/bug_forensics_gates.py +284 -0
  198. agent_lifecycle/workflow/controller.py +45 -0
  199. agent_lifecycle/workflow/events.py +50 -0
  200. agent_lifecycle/workflow/final_proof_integrity.py +74 -0
  201. agent_lifecycle/workflow/finalization.py +422 -0
  202. agent_lifecycle/workflow/gates.py +185 -0
  203. agent_lifecycle/workflow/implementation_audit_gate.py +126 -0
  204. agent_lifecycle/workflow/leases.py +220 -0
  205. agent_lifecycle/workflow/lineage.py +108 -0
  206. agent_lifecycle/workflow/managed_runner.py +167 -0
  207. agent_lifecycle/workflow/model_usage.py +106 -0
  208. agent_lifecycle/workflow/next_action.py +86 -0
  209. agent_lifecycle/workflow/operation_kernel.py +98 -0
  210. agent_lifecycle/workflow/plan_adoption.py +439 -0
  211. agent_lifecycle/workflow/query.py +52 -0
  212. agent_lifecycle/workflow/review_mesh_gate.py +93 -0
  213. agent_lifecycle/workflow/reviews.py +168 -0
  214. agent_lifecycle/workflow/run_transitions.py +163 -0
  215. agent_lifecycle/workflow/sandbox_policy.py +214 -0
  216. agent_lifecycle/workflow/selectors.py +57 -0
  217. agent_lifecycle/workflow/state.py +130 -0
  218. agent_lifecycle/workflow/task_transitions.py +421 -0
  219. agent_lifecycle/worktree/__init__.py +17 -0
  220. agent_lifecycle/worktree/isolation.py +407 -0
  221. agent_lifecycle_kit-1.50.1.dist-info/METADATA +201 -0
  222. agent_lifecycle_kit-1.50.1.dist-info/RECORD +227 -0
  223. agent_lifecycle_kit-1.50.1.dist-info/WHEEL +5 -0
  224. agent_lifecycle_kit-1.50.1.dist-info/entry_points.txt +3 -0
  225. agent_lifecycle_kit-1.50.1.dist-info/licenses/LICENSE +201 -0
  226. agent_lifecycle_kit-1.50.1.dist-info/licenses/NOTICE +5 -0
  227. agent_lifecycle_kit-1.50.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,450 @@
1
+ """Adapter task intake classifier and managed-run delegator."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+ from typing import Any
7
+
8
+ from agent_lifecycle.adapter_sessions.workflow_bridge import managed_adapter_run
9
+ from agent_lifecycle.contracts import (
10
+ LifecycleError,
11
+ canonical_digest,
12
+ load_json_object,
13
+ sha256_hex,
14
+ write_json_create,
15
+ )
16
+ from agent_lifecycle.imports import import_planning_input, import_planning_text
17
+ from agent_lifecycle.quality.bug_forensics_advisor import (
18
+ bug_forensics_recommended,
19
+ build_bug_forensics_advisory,
20
+ )
21
+ from agent_lifecycle.review_mesh.recommendation import recommend_review_mesh_for_text
22
+
23
+ ADAPTER_TASK_START_RECEIPT_SCHEMA = "agent-adapter-task-start-receipt.v1"
24
+ ADAPTER_TASK_RUN_REQUEST_SCHEMA = "agent-adapter-task-run-request.v1"
25
+
26
+ _ANALYSIS_FIRST_MARKERS = (
27
+ "analyze code",
28
+ "analyse code",
29
+ "analysis before",
30
+ "inspect before",
31
+ "investigate before",
32
+ "review code before",
33
+ "risk scan",
34
+ "проанализ",
35
+ "анализ кода",
36
+ "перед внедрением",
37
+ "перед реализацией",
38
+ "сначала анализ",
39
+ "сначала проверь",
40
+ "исследуй",
41
+ )
42
+
43
+
44
+ def start_adapter_task(
45
+ *,
46
+ adapter_id: str,
47
+ task_file: Path | None = None,
48
+ task_text: str | None = None,
49
+ candidate_out: Path | None = None,
50
+ descriptor_path: Path | None = None,
51
+ session_root: Path | None = None,
52
+ state_path: Path | None = None,
53
+ lock_path: Path | None = None,
54
+ task_id: str | None = None,
55
+ operation_id: str | None = None,
56
+ expected_revision: int | None = None,
57
+ source_revision: str | None = None,
58
+ max_input_bytes: int = 32768,
59
+ target_tokens: int = 4096,
60
+ package_id: str = "adapter-task-intake",
61
+ ) -> dict[str, Any]:
62
+ """Classify adapter task input without turning raw text into authority."""
63
+
64
+ source, source_blockers = _load_source(task_file=task_file, task_text=task_text)
65
+ if source is None:
66
+ return _receipt(
67
+ adapter_id=adapter_id,
68
+ source=_empty_source(),
69
+ action="BLOCKED",
70
+ status="BLOCKED",
71
+ blockers=source_blockers,
72
+ )
73
+ parsed = _parse_json(source["data"])
74
+ if parsed is not None and parsed.get("schemaVersion") == ADAPTER_TASK_RUN_REQUEST_SCHEMA:
75
+ return _from_run_request(
76
+ adapter_id=adapter_id,
77
+ request=parsed,
78
+ source=_source_summary(source),
79
+ descriptor_path=descriptor_path,
80
+ session_root=session_root,
81
+ )
82
+ if parsed is not None and parsed.get("schemaVersion") == "agent-plan-manifest.v1":
83
+ if parsed.get("status") == "FROZEN":
84
+ if source.get("path") is None:
85
+ return _receipt(
86
+ adapter_id=adapter_id,
87
+ source=_source_summary(source),
88
+ action="BLOCKED",
89
+ status="BLOCKED",
90
+ blockers=[{"code": "adapter-task-frozen-manifest-file-required"}],
91
+ )
92
+ return _from_frozen_manifest(
93
+ adapter_id=adapter_id,
94
+ manifest_path=Path(source["path"]),
95
+ source=_source_summary(source),
96
+ descriptor_path=descriptor_path,
97
+ session_root=session_root,
98
+ state_path=state_path,
99
+ lock_path=lock_path,
100
+ task_id=task_id,
101
+ operation_id=operation_id,
102
+ expected_revision=expected_revision,
103
+ source_revision=source_revision,
104
+ )
105
+ return _planning_intake(
106
+ adapter_id=adapter_id,
107
+ source=source,
108
+ candidate_out=candidate_out,
109
+ max_input_bytes=max_input_bytes,
110
+ target_tokens=target_tokens,
111
+ package_id=package_id,
112
+ )
113
+
114
+
115
+ def _load_source(*, task_file: Path | None, task_text: str | None) -> tuple[dict[str, Any] | None, list[dict[str, Any]]]:
116
+ if (task_file is None and task_text is None) or (task_file is not None and task_text is not None):
117
+ return None, [{"code": "adapter-task-source-invalid", "message": "exactly one of --file/--task-file or --text/--task-text is required"}]
118
+ if task_text is not None:
119
+ data = task_text.encode("utf-8")
120
+ return {"kind": "TEXT", "label": "inline-task", "data": data, "text": task_text, "path": None}, []
121
+ assert task_file is not None
122
+ if not task_file.is_file():
123
+ return None, [{"code": "adapter-task-file-missing", "sourceLabel": task_file.name}]
124
+ data = task_file.read_bytes()
125
+ try:
126
+ text = data.decode("utf-8")
127
+ except UnicodeDecodeError:
128
+ text = ""
129
+ return {"kind": "FILE", "label": task_file.name, "data": data, "text": text, "path": task_file.as_posix()}, []
130
+
131
+
132
+ def _planning_intake(
133
+ *,
134
+ adapter_id: str,
135
+ source: dict[str, Any],
136
+ candidate_out: Path | None,
137
+ max_input_bytes: int,
138
+ target_tokens: int,
139
+ package_id: str,
140
+ ) -> dict[str, Any]:
141
+ if source["kind"] == "TEXT":
142
+ planning = import_planning_text(
143
+ source["text"],
144
+ source_label=source["label"],
145
+ package_id=package_id,
146
+ max_input_bytes=max_input_bytes,
147
+ target_tokens=target_tokens,
148
+ )
149
+ else:
150
+ planning = import_planning_input(
151
+ Path(source["path"]),
152
+ package_id=package_id,
153
+ max_input_bytes=max_input_bytes,
154
+ target_tokens=target_tokens,
155
+ )
156
+ if candidate_out is not None:
157
+ write_json_create(candidate_out, planning)
158
+ shape = _classify_task(source.get("text", ""))
159
+ review_mesh_recommendation = recommend_review_mesh_for_text(
160
+ source.get("text", ""),
161
+ source_label=source["label"],
162
+ )
163
+ blockers = [{"code": item.get("code", "planning-import-blocker"), **({"source": "planning-import"} if "code" in item else {})} for item in planning.get("blockers", [])]
164
+ status = "BLOCKED" if planning.get("status") != "PASS" else "REVIEW_REQUIRED"
165
+ action = "BLOCKED" if status == "BLOCKED" else ("DRAFT_PLAN_REVIEW" if _parse_json(source["data"]) else "DRAFT_INTAKE")
166
+ return _receipt(
167
+ adapter_id=adapter_id,
168
+ source=_source_summary(source),
169
+ action=action,
170
+ status=status,
171
+ blockers=blockers,
172
+ planning_import=_planning_summary(planning),
173
+ detected_task_shape=shape["taskShape"],
174
+ recommended_quality_profiles=shape["recommendedQualityProfiles"],
175
+ bug_forensics_advisory=shape["bugForensicsAdvisory"],
176
+ pre_implementation_analysis=shape["preImplementationAnalysis"],
177
+ review_mesh_recommendation=review_mesh_recommendation,
178
+ requires_review=True,
179
+ audit_required=True,
180
+ freeze_blocked=True,
181
+ )
182
+
183
+
184
+ def _from_run_request(
185
+ *,
186
+ adapter_id: str,
187
+ request: dict[str, Any],
188
+ source: dict[str, Any],
189
+ descriptor_path: Path | None,
190
+ session_root: Path | None,
191
+ ) -> dict[str, Any]:
192
+ blockers = _run_request_blockers(adapter_id, request)
193
+ if blockers:
194
+ return _receipt(adapter_id=adapter_id, source=source, action="BLOCKED", status="BLOCKED", blockers=blockers)
195
+ return _managed_run_receipt(
196
+ adapter_id=adapter_id,
197
+ source=source,
198
+ descriptor_path=descriptor_path,
199
+ session_root=session_root,
200
+ state_path=Path(str(request["state"])),
201
+ manifest_path=Path(str(request["manifest"])),
202
+ lock_path=Path(str(request["lock"])) if request.get("lock") else None,
203
+ task_id=str(request["task"]),
204
+ operation_id=str(request["operationId"]),
205
+ expected_revision=int(request["expectedRevision"]),
206
+ source_revision=str(request["sourceRevision"]),
207
+ )
208
+
209
+
210
+ def _from_frozen_manifest(
211
+ *,
212
+ adapter_id: str,
213
+ manifest_path: Path,
214
+ source: dict[str, Any],
215
+ descriptor_path: Path | None,
216
+ session_root: Path | None,
217
+ state_path: Path | None,
218
+ lock_path: Path | None,
219
+ task_id: str | None,
220
+ operation_id: str | None,
221
+ expected_revision: int | None,
222
+ source_revision: str | None,
223
+ ) -> dict[str, Any]:
224
+ missing = [
225
+ name
226
+ for name, value in {
227
+ "state": state_path,
228
+ "lock": lock_path,
229
+ "task": task_id,
230
+ "operationId": operation_id,
231
+ "expectedRevision": expected_revision,
232
+ "sourceRevision": source_revision,
233
+ }.items()
234
+ if value in {None, ""}
235
+ ]
236
+ if missing:
237
+ return _receipt(
238
+ adapter_id=adapter_id,
239
+ source=source,
240
+ action="BLOCKED",
241
+ status="BLOCKED",
242
+ blockers=[{"code": "adapter-task-frozen-binding-missing", "fields": missing}],
243
+ )
244
+ return _managed_run_receipt(
245
+ adapter_id=adapter_id,
246
+ source=source,
247
+ descriptor_path=descriptor_path,
248
+ session_root=session_root,
249
+ state_path=state_path or Path(""),
250
+ manifest_path=manifest_path,
251
+ lock_path=lock_path,
252
+ task_id=str(task_id),
253
+ operation_id=str(operation_id),
254
+ expected_revision=int(expected_revision or 0),
255
+ source_revision=str(source_revision),
256
+ )
257
+
258
+
259
+ def _managed_run_receipt(
260
+ *,
261
+ adapter_id: str,
262
+ source: dict[str, Any],
263
+ descriptor_path: Path | None,
264
+ session_root: Path | None,
265
+ state_path: Path,
266
+ manifest_path: Path,
267
+ lock_path: Path | None,
268
+ task_id: str,
269
+ operation_id: str,
270
+ expected_revision: int,
271
+ source_revision: str,
272
+ ) -> dict[str, Any]:
273
+ try:
274
+ session_receipt = managed_adapter_run(
275
+ adapter_id=adapter_id,
276
+ descriptor_path=descriptor_path,
277
+ session_root=session_root,
278
+ state_path=state_path,
279
+ manifest_path=manifest_path,
280
+ lock_path=lock_path,
281
+ task_id=task_id,
282
+ operation_id=operation_id,
283
+ expected_revision=expected_revision,
284
+ source_revision=source_revision,
285
+ )
286
+ except LifecycleError as exc:
287
+ return _receipt(
288
+ adapter_id=adapter_id,
289
+ source=source,
290
+ action="BLOCKED",
291
+ status="BLOCKED",
292
+ blockers=[{"code": exc.code, "message": exc.message, "context": exc.details}],
293
+ )
294
+ blockers = list(session_receipt.get("blockers", []))
295
+ status = "BLOCKED" if session_receipt.get("status") == "BLOCKED" or blockers else "READY"
296
+ return _receipt(
297
+ adapter_id=adapter_id,
298
+ source=source,
299
+ action="MANAGED_RUN" if status == "READY" else "BLOCKED",
300
+ status=status,
301
+ blockers=blockers,
302
+ adapter_session_receipt=session_receipt,
303
+ execution_started=status == "READY",
304
+ lifecycle_coverage_claimed=bool(session_receipt.get("lifecycleCoverageClaimed")) and status == "READY",
305
+ requires_review=False,
306
+ audit_required=False,
307
+ freeze_blocked=False,
308
+ workflow_binding={
309
+ "state": state_path.as_posix(),
310
+ "manifest": manifest_path.as_posix(),
311
+ "lock": lock_path.as_posix() if lock_path else None,
312
+ "task": task_id,
313
+ "operationId": operation_id,
314
+ "expectedRevision": expected_revision,
315
+ "sourceRevision": source_revision,
316
+ },
317
+ )
318
+
319
+
320
+ def _run_request_blockers(adapter_id: str, request: dict[str, Any]) -> list[dict[str, Any]]:
321
+ blockers: list[dict[str, Any]] = []
322
+ for field in ("state", "manifest", "task", "operationId", "expectedRevision", "sourceRevision"):
323
+ value = request.get(field)
324
+ if value in {None, ""}:
325
+ blockers.append({"code": "adapter-task-run-request-field-missing", "field": field})
326
+ expected_revision = request.get("expectedRevision")
327
+ if not isinstance(expected_revision, int) or isinstance(expected_revision, bool) or expected_revision < 1:
328
+ blockers.append({"code": "adapter-task-run-request-revision-invalid"})
329
+ request_adapter = request.get("adapterId") or request.get("adapter")
330
+ if isinstance(request_adapter, str) and request_adapter and request_adapter != adapter_id:
331
+ blockers.append({"code": "adapter-task-run-request-adapter-mismatch", "expected": adapter_id, "actual": request_adapter})
332
+ if request.get("productionPromotionClaimed") is not False and "productionPromotionClaimed" in request:
333
+ blockers.append({"code": "adapter-task-run-request-production-claim"})
334
+ digest = request.get("requestDigest")
335
+ if digest is not None:
336
+ expected = canonical_digest({key: value for key, value in request.items() if key != "requestDigest"})
337
+ if digest != expected:
338
+ blockers.append({"code": "adapter-task-run-request-digest-mismatch"})
339
+ return blockers
340
+
341
+
342
+ def _classify_task(text: str) -> dict[str, Any]:
343
+ lowered = text.lower()
344
+ advisory = build_bug_forensics_advisory(text)
345
+ defect = bug_forensics_recommended(advisory)
346
+ analysis = any(marker in lowered for marker in _ANALYSIS_FIRST_MARKERS)
347
+ profiles = list(advisory["recommendedQualityProfiles"]) if defect else []
348
+ task_shape = "bugfix" if defect else ("analysis-first" if analysis else ("feature" if text.strip() else "unknown"))
349
+ purpose = None
350
+ if analysis:
351
+ purpose = "bug-investigation" if defect else "feature-discovery"
352
+ return {
353
+ "taskShape": task_shape,
354
+ "recommendedQualityProfiles": profiles,
355
+ "bugForensicsAdvisory": advisory,
356
+ "preImplementationAnalysis": {
357
+ "required": analysis,
358
+ "purpose": purpose,
359
+ "activeWorkflowGateClaimed": False,
360
+ },
361
+ }
362
+
363
+
364
+ def _planning_summary(planning: dict[str, Any]) -> dict[str, Any]:
365
+ candidate = planning.get("candidatePlan")
366
+ return {
367
+ "schemaVersion": planning.get("schemaVersion"),
368
+ "status": planning.get("status"),
369
+ "candidateLifecycleStatus": planning.get("candidateLifecycleStatus"),
370
+ "requiresReview": planning.get("requiresReview"),
371
+ "auditRequired": planning.get("auditRequired"),
372
+ "freezeBlocked": planning.get("freezeBlocked"),
373
+ "source": planning.get("source"),
374
+ "candidateDigest": canonical_digest(candidate) if isinstance(candidate, dict) else None,
375
+ "importDigest": planning.get("importDigest"),
376
+ "blockerCodes": [item.get("code") for item in planning.get("blockers", []) if isinstance(item, dict)],
377
+ }
378
+
379
+
380
+ def _source_summary(source: dict[str, Any]) -> dict[str, Any]:
381
+ return {
382
+ "type": source["kind"],
383
+ "label": source["label"],
384
+ "digest": sha256_hex(source["data"]),
385
+ "byteCount": len(source["data"]),
386
+ "rawTextStored": False,
387
+ }
388
+
389
+
390
+ def _empty_source() -> dict[str, Any]:
391
+ return {"type": "NONE", "label": None, "digest": sha256_hex(b""), "byteCount": 0, "rawTextStored": False}
392
+
393
+
394
+ def _parse_json(data: bytes) -> dict[str, Any] | None:
395
+ try:
396
+ return load_json_object(data, label="adapter task input")
397
+ except LifecycleError:
398
+ return None
399
+
400
+
401
+ def _receipt(
402
+ *,
403
+ adapter_id: str,
404
+ source: dict[str, Any],
405
+ action: str,
406
+ status: str,
407
+ blockers: list[dict[str, Any]] | None = None,
408
+ planning_import: dict[str, Any] | None = None,
409
+ adapter_session_receipt: dict[str, Any] | None = None,
410
+ detected_task_shape: str = "unknown",
411
+ recommended_quality_profiles: list[str] | None = None,
412
+ pre_implementation_analysis: dict[str, Any] | None = None,
413
+ execution_started: bool = False,
414
+ lifecycle_coverage_claimed: bool = False,
415
+ requires_review: bool = False,
416
+ audit_required: bool = False,
417
+ freeze_blocked: bool = False,
418
+ workflow_binding: dict[str, Any] | None = None,
419
+ review_mesh_recommendation: dict[str, Any] | None = None,
420
+ bug_forensics_advisory: dict[str, Any] | None = None,
421
+ ) -> dict[str, Any]:
422
+ body = {
423
+ "schemaVersion": ADAPTER_TASK_START_RECEIPT_SCHEMA,
424
+ "status": status,
425
+ "adapterId": adapter_id,
426
+ "input": source,
427
+ "action": action,
428
+ "detectedTaskShape": detected_task_shape,
429
+ "recommendedQualityProfiles": recommended_quality_profiles or [],
430
+ "preImplementationAnalysis": pre_implementation_analysis
431
+ or {"required": False, "purpose": None, "activeWorkflowGateClaimed": False},
432
+ "executionStarted": execution_started,
433
+ "lifecycleCoverageClaimed": lifecycle_coverage_claimed,
434
+ "requiresReview": requires_review,
435
+ "auditRequired": audit_required,
436
+ "freezeBlocked": freeze_blocked,
437
+ "reviewBlockers": blockers or [],
438
+ "planningImport": planning_import,
439
+ "adapterSessionReceipt": adapter_session_receipt,
440
+ "workflowBinding": workflow_binding,
441
+ "reviewMeshRecommendation": review_mesh_recommendation,
442
+ "bugForensicsAdvisory": bug_forensics_advisory,
443
+ "modelCallsStarted": False,
444
+ "hostLaunchStarted": bool(adapter_session_receipt.get("hostLaunchStarted")) if isinstance(adapter_session_receipt, dict) else False,
445
+ "secretsWritten": False,
446
+ "nativeConfigWritten": False,
447
+ "rawTaskTextStored": False,
448
+ "productionPromotionClaimed": False,
449
+ }
450
+ return {**body, "receiptDigest": canonical_digest(body)}
@@ -0,0 +1,154 @@
1
+ """Composition between managed adapter sessions and ALK workflow state."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+ from typing import Any
7
+
8
+ from agent_lifecycle.adapter_sessions.contracts import build_adapter_session_receipt, build_resume_receipt
9
+ from agent_lifecycle.adapter_sessions.launcher import load_adapter_descriptor, managed_launch_profile
10
+ from agent_lifecycle.adapter_sessions.session_store import create_session, load_session, update_session
11
+ from agent_lifecycle.contracts import read_json_object
12
+ from agent_lifecycle.workflow import run_managed_lifecycle_step
13
+
14
+
15
+ def managed_adapter_run(
16
+ *,
17
+ adapter_id: str,
18
+ state_path: Path,
19
+ manifest_path: Path,
20
+ lock_path: Path | None,
21
+ task_id: str,
22
+ operation_id: str,
23
+ expected_revision: int,
24
+ source_revision: str,
25
+ descriptor_path: Path | None = None,
26
+ session_root: Path | None = None,
27
+ ) -> dict[str, Any]:
28
+ _descriptor_path, descriptor = load_adapter_descriptor(adapter_id, descriptor_path)
29
+ profile = managed_launch_profile(descriptor)
30
+ runner_receipt = run_managed_lifecycle_step(
31
+ state_path=state_path,
32
+ manifest_path=manifest_path,
33
+ lock_path=lock_path,
34
+ operation_id=operation_id,
35
+ expected_revision=expected_revision,
36
+ source_revision=source_revision,
37
+ reason=f"managed adapter run for {task_id}",
38
+ )
39
+ state_identity = {**_state_identity(state_path), "taskId": task_id}
40
+ proof = _managed_proof("adapter run", adapter_id=adapter_id, task_id=task_id, state_identity=state_identity)
41
+ session = create_session(
42
+ adapter_id=adapter_id,
43
+ mode="MANAGED_TASK",
44
+ status="READY" if runner_receipt["status"] == "PASS" else "BLOCKED",
45
+ launch_profile=profile,
46
+ session_root=session_root,
47
+ state_identity=state_identity,
48
+ managed_workflow_proof=proof,
49
+ )
50
+ return build_adapter_session_receipt(
51
+ status=session["status"],
52
+ session_id=session["sessionId"],
53
+ adapter_id=adapter_id,
54
+ mode="MANAGED_TASK",
55
+ launch_profile=profile,
56
+ state_identity=state_identity,
57
+ managed_workflow_proof=proof,
58
+ progress_hook_default="stderr",
59
+ host_launch_started=False,
60
+ blockers=runner_receipt.get("blockers", []),
61
+ next_action=runner_receipt.get("nextAction"),
62
+ )
63
+
64
+
65
+ def promote_session_to_workflow(
66
+ *,
67
+ session_id: str,
68
+ state_path: Path,
69
+ task_id: str,
70
+ adapter_id: str | None = None,
71
+ session_root: Path | None = None,
72
+ ) -> dict[str, Any]:
73
+ session = load_session(session_id, session_root=session_root)
74
+ if adapter_id and session.get("adapterId") != adapter_id:
75
+ return build_resume_receipt(
76
+ session_id=session_id,
77
+ adapter_id=adapter_id,
78
+ expected_identity={"adapterId": adapter_id},
79
+ actual_identity={"adapterId": session.get("adapterId")},
80
+ blockers=[{"code": "adapter-session-adapter-mismatch"}],
81
+ )
82
+ identity = {**_state_identity(state_path), "taskId": task_id}
83
+ proof = _managed_proof("adapter session promote", adapter_id=session["adapterId"], task_id=task_id, state_identity=identity)
84
+ session["mode"] = "PROMOTED"
85
+ session["status"] = "READY"
86
+ session["stateIdentity"] = identity
87
+ session["managedWorkflowProof"] = proof
88
+ update_session(session, session_root=session_root)
89
+ return build_adapter_session_receipt(
90
+ status="READY",
91
+ session_id=session_id,
92
+ adapter_id=session["adapterId"],
93
+ mode="PROMOTED",
94
+ launch_profile=session.get("launchProfile", {}),
95
+ state_identity=identity,
96
+ managed_workflow_proof=proof,
97
+ progress_hook_default="stderr",
98
+ )
99
+
100
+
101
+ def resume_adapter_session(
102
+ *,
103
+ session_id: str,
104
+ session_root: Path | None = None,
105
+ adapter_id: str | None = None,
106
+ state_path: Path | None = None,
107
+ task_id: str | None = None,
108
+ ) -> dict[str, Any]:
109
+ session = load_session(session_id, session_root=session_root)
110
+ expected: dict[str, Any] = {}
111
+ blockers: list[dict[str, Any]] = []
112
+ if adapter_id:
113
+ if session.get("adapterId") != adapter_id:
114
+ blockers.append({"code": "adapter-session-adapter-mismatch", "expected": adapter_id, "actual": session.get("adapterId")})
115
+ if task_id:
116
+ expected["taskId"] = task_id
117
+ actual = session.get("stateIdentity") if isinstance(session.get("stateIdentity"), dict) else {}
118
+ if state_path is not None:
119
+ expected.update(_state_identity(state_path))
120
+ for key, expected_value in expected.items():
121
+ if actual.get(key) != expected_value:
122
+ blockers.append({"code": "adapter-session-lineage-mismatch", "field": key, "expected": expected_value, "actual": actual.get(key)})
123
+ return build_resume_receipt(
124
+ session_id=session_id,
125
+ adapter_id=session.get("adapterId", adapter_id or ""),
126
+ expected_identity=expected,
127
+ actual_identity=actual,
128
+ blockers=blockers,
129
+ )
130
+
131
+
132
+ def _state_identity(state_path: Path) -> dict[str, Any]:
133
+ state = read_json_object(state_path, label="workflow state")
134
+ return {
135
+ "statePath": state_path.as_posix(),
136
+ "runId": state.get("runId"),
137
+ "packageId": state.get("packageId"),
138
+ "planRevision": state.get("planRevision"),
139
+ "planDigest": state.get("planDigest"),
140
+ "sourceRevision": state.get("sourceRevision"),
141
+ "stateRevision": state.get("stateRevision"),
142
+ "phase": state.get("phase"),
143
+ }
144
+
145
+
146
+ def _managed_proof(command: str, *, adapter_id: str, task_id: str, state_identity: dict[str, Any]) -> dict[str, Any]:
147
+ return {
148
+ "kind": "alk-managed-adapter-session",
149
+ "status": "PASS",
150
+ "command": command,
151
+ "adapterId": adapter_id,
152
+ "taskId": task_id,
153
+ "stateIdentity": state_identity,
154
+ }
@@ -0,0 +1,67 @@
1
+ """Audit helpers for frozen lifecycle plans."""
2
+
3
+ from agent_lifecycle.audit.ownership import build_ownership_report
4
+ from agent_lifecycle.audit.proof_integrity import (
5
+ build_finding_identity,
6
+ build_fix_impact_receipt,
7
+ build_hash_chain_migration_policy,
8
+ build_proof_integrity_receipt,
9
+ build_receipt_hash_chain,
10
+ build_root_cause_evidence,
11
+ finding_identity_fields,
12
+ require_proof_integrity_pass,
13
+ stable_finding_id,
14
+ validate_finding_identity,
15
+ validate_fix_impact_receipt,
16
+ validate_hash_chain_migration_policy,
17
+ validate_proof_integrity_receipt,
18
+ validate_receipt_hash_chain,
19
+ validate_root_cause_evidence,
20
+ )
21
+ from agent_lifecycle.audit.review_verdict import (
22
+ compact_review_routing,
23
+ require_review_verdict_pass,
24
+ validate_review_verdict,
25
+ )
26
+ from agent_lifecycle.audit.bug_forensics import (
27
+ build_bug_forensics_audit,
28
+ require_bug_forensics_audit_pass,
29
+ validate_bug_forensics_audit,
30
+ )
31
+ from agent_lifecycle.audit.implementation import (
32
+ build_final_implementation_audit,
33
+ build_implementation_audit_report,
34
+ require_implementation_audit_accepted,
35
+ validate_final_implementation_audit,
36
+ validate_implementation_audit_report,
37
+ )
38
+
39
+ __all__ = [
40
+ "build_bug_forensics_audit",
41
+ "build_ownership_report",
42
+ "build_final_implementation_audit",
43
+ "build_implementation_audit_report",
44
+ "build_finding_identity",
45
+ "build_fix_impact_receipt",
46
+ "build_hash_chain_migration_policy",
47
+ "build_proof_integrity_receipt",
48
+ "build_receipt_hash_chain",
49
+ "build_root_cause_evidence",
50
+ "compact_review_routing",
51
+ "finding_identity_fields",
52
+ "require_proof_integrity_pass",
53
+ "require_bug_forensics_audit_pass",
54
+ "require_review_verdict_pass",
55
+ "require_implementation_audit_accepted",
56
+ "stable_finding_id",
57
+ "validate_finding_identity",
58
+ "validate_bug_forensics_audit",
59
+ "validate_final_implementation_audit",
60
+ "validate_implementation_audit_report",
61
+ "validate_fix_impact_receipt",
62
+ "validate_hash_chain_migration_policy",
63
+ "validate_proof_integrity_receipt",
64
+ "validate_receipt_hash_chain",
65
+ "validate_root_cause_evidence",
66
+ "validate_review_verdict",
67
+ ]