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,116 @@
1
+ """Structured review verdicts for targeted remediation routing."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from agent_lifecycle.contracts import LifecycleError, canonical_digest
8
+
9
+ REVIEW_VERDICT_SCHEMA = "agent-review-verdict.v1"
10
+ REVIEW_VERDICT_VALIDATION_SCHEMA = "agent-review-verdict-validation.v1"
11
+ DIMENSIONS = ("requirementFit", "implementationQuality", "evidenceQuality", "residualRisk")
12
+ DIMENSION_STATUSES = {"PASS", "WARN", "FAIL"}
13
+ OVERALL_VERDICTS = {"ACCEPTED", "REWORK", "CONTRACT_CHANGE", "BLOCKED"}
14
+ NEXT_ACTIONS = {"accept", "fix-implementation", "strengthen-evidence", "reopen-contract", "block-external"}
15
+
16
+
17
+ def validate_review_verdict(verdict: dict[str, Any], *, findings: list[dict[str, Any]] | None = None) -> dict[str, Any]:
18
+ if not isinstance(verdict, dict):
19
+ raise LifecycleError("invalid-review-verdict", "review verdict must be an object")
20
+ blockers: list[dict[str, Any]] = []
21
+ if verdict.get("schemaVersion") != REVIEW_VERDICT_SCHEMA:
22
+ blockers.append({"code": "invalid-review-verdict-schema", "message": "unsupported review verdict schemaVersion"})
23
+ overall = verdict.get("overall")
24
+ if overall not in OVERALL_VERDICTS:
25
+ blockers.append({"code": "invalid-review-verdict-overall", "message": "overall verdict is unsupported"})
26
+ dimensions = verdict.get("dimensions")
27
+ if not isinstance(dimensions, dict):
28
+ blockers.append({"code": "invalid-review-verdict-dimensions", "message": "dimensions must be an object"})
29
+ dimensions = {}
30
+ missing = sorted(set(DIMENSIONS).difference(dimensions))
31
+ if missing:
32
+ blockers.append({"code": "review-verdict-dimension-missing", "dimensions": missing})
33
+ failing_dimensions: list[str] = []
34
+ warning_dimensions: list[str] = []
35
+ for name in DIMENSIONS:
36
+ dimension = dimensions.get(name)
37
+ if not isinstance(dimension, dict):
38
+ continue
39
+ status = dimension.get("status")
40
+ if status not in DIMENSION_STATUSES:
41
+ blockers.append({"code": "review-verdict-dimension-status", "dimension": name, "status": status})
42
+ continue
43
+ reason = dimension.get("reasonCode")
44
+ if not isinstance(reason, str) or not reason:
45
+ blockers.append({"code": "review-verdict-dimension-reason-missing", "dimension": name})
46
+ summary = dimension.get("summary")
47
+ if not isinstance(summary, str) or not summary:
48
+ blockers.append({"code": "review-verdict-dimension-summary-missing", "dimension": name})
49
+ if status == "FAIL":
50
+ failing_dimensions.append(name)
51
+ elif status == "WARN":
52
+ warning_dimensions.append(name)
53
+ routing = verdict.get("routing")
54
+ if not isinstance(routing, dict):
55
+ blockers.append({"code": "invalid-review-verdict-routing", "message": "routing must be an object"})
56
+ routing = {}
57
+ next_action = routing.get("nextAction")
58
+ if next_action not in NEXT_ACTIONS:
59
+ blockers.append({"code": "review-verdict-routing-action", "nextAction": next_action})
60
+ target = routing.get("target")
61
+ if target is not None and (not isinstance(target, str) or not target):
62
+ blockers.append({"code": "review-verdict-routing-target", "message": "routing target must be a non-empty string"})
63
+ open_medium_plus = _open_medium_plus(findings or [])
64
+ if open_medium_plus:
65
+ blockers.append({"code": "review-verdict-open-findings", "findings": open_medium_plus})
66
+ if overall == "ACCEPTED" and (failing_dimensions or open_medium_plus):
67
+ blockers.append({"code": "review-verdict-accepted-with-blockers", "dimensions": failing_dimensions, "findings": open_medium_plus})
68
+ if overall == "ACCEPTED" and next_action != "accept":
69
+ blockers.append({"code": "review-verdict-routing-mismatch", "overall": overall, "nextAction": next_action})
70
+ if overall != "ACCEPTED" and next_action == "accept":
71
+ blockers.append({"code": "review-verdict-routing-mismatch", "overall": overall, "nextAction": next_action})
72
+ body = {
73
+ "schemaVersion": REVIEW_VERDICT_VALIDATION_SCHEMA,
74
+ "status": "PASS" if not blockers else "FAIL",
75
+ "overall": overall,
76
+ "failingDimensions": failing_dimensions,
77
+ "warningDimensions": warning_dimensions,
78
+ "nextAction": next_action,
79
+ "blockers": blockers,
80
+ }
81
+ return {**body, "verdictDigest": canonical_digest(verdict), "validationDigest": canonical_digest(body)}
82
+
83
+
84
+ def require_review_verdict_pass(payload: dict[str, Any]) -> dict[str, Any]:
85
+ if payload.get("status") == "FAIL":
86
+ raise LifecycleError("review-verdict-validation-failed", "review verdict validation failed", {"validation": payload})
87
+ return payload
88
+
89
+
90
+ def compact_review_routing(verdict: dict[str, Any]) -> dict[str, Any]:
91
+ validation = validate_review_verdict(verdict)
92
+ dimensions = verdict.get("dimensions", {})
93
+ return {
94
+ "schemaVersion": "agent-review-routing-summary.v1",
95
+ "status": validation["status"],
96
+ "overall": validation["overall"],
97
+ "nextAction": validation["nextAction"],
98
+ "failingDimensions": validation["failingDimensions"],
99
+ "warningDimensions": validation["warningDimensions"],
100
+ "dimensionStatus": {
101
+ key: dimensions.get(key, {}).get("status")
102
+ for key in DIMENSIONS
103
+ if isinstance(dimensions.get(key), dict)
104
+ },
105
+ "verdictDigest": validation["verdictDigest"],
106
+ }
107
+
108
+
109
+ def _open_medium_plus(findings: list[dict[str, Any]]) -> list[str]:
110
+ return [
111
+ str(finding.get("id") or "<unknown>")
112
+ for finding in findings
113
+ if isinstance(finding, dict)
114
+ and finding.get("status") == "open"
115
+ and finding.get("severity") in {"BLOCKER", "HIGH", "MEDIUM"}
116
+ ]
@@ -0,0 +1,5 @@
1
+ """Changed-file discovery helpers."""
2
+
3
+ from agent_lifecycle.changesets.git import changed_files
4
+
5
+ __all__ = ["changed_files"]
@@ -0,0 +1,35 @@
1
+ """Git-backed changed-file discovery."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import subprocess
6
+ from pathlib import Path
7
+
8
+ from agent_lifecycle.contracts import LifecycleError
9
+ from agent_lifecycle.contracts.paths import normalize_repo_path
10
+
11
+
12
+ def changed_files(cwd: Path, *, base: str | None = None) -> list[str]:
13
+ paths: set[str] = set()
14
+ if base:
15
+ paths.update(_git_lines(cwd, ["git", "diff", "--name-only", base, "--"]))
16
+ else:
17
+ paths.update(_git_lines(cwd, ["git", "diff", "--name-only", "--"]))
18
+ paths.update(_git_lines(cwd, ["git", "diff", "--name-only", "--cached", "--"]))
19
+ paths.update(_git_lines(cwd, ["git", "ls-files", "--others", "--exclude-standard"]))
20
+ return sorted(normalize_repo_path(path) for path in paths if path)
21
+
22
+
23
+ def _git_lines(cwd: Path, argv: list[str]) -> list[str]:
24
+ try:
25
+ completed = subprocess.run(
26
+ argv,
27
+ cwd=cwd,
28
+ check=True,
29
+ stdout=subprocess.PIPE,
30
+ stderr=subprocess.PIPE,
31
+ text=True,
32
+ )
33
+ except (OSError, subprocess.CalledProcessError) as exc:
34
+ raise LifecycleError("git-command-failed", f"git command failed: {' '.join(argv)}") from exc
35
+ return [line.strip() for line in completed.stdout.splitlines() if line.strip()]
@@ -0,0 +1,5 @@
1
+ """Compact JSON CLI entry point."""
2
+
3
+ from agent_lifecycle.cli.main import main
4
+
5
+ __all__ = ["main"]
@@ -0,0 +1,360 @@
1
+ """Adapter CLI parser and dispatch."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ from pathlib import Path
7
+ from typing import Any
8
+
9
+ from agent_lifecycle.contracts import LifecycleError, read_json_object
10
+ from agent_lifecycle.contracts import write_json_create
11
+ from agent_lifecycle.adapter_sessions import (
12
+ build_adapter_session_receipt,
13
+ create_session,
14
+ launch_from_descriptor,
15
+ load_session,
16
+ managed_adapter_run,
17
+ promote_session_to_workflow,
18
+ resume_adapter_session,
19
+ start_adapter_task,
20
+ update_session,
21
+ )
22
+ from agent_lifecycle.adapter_sessions.launcher import load_adapter_descriptor, managed_launch_profile
23
+ from agent_lifecycle.reporting.progress_hooks import build_progress_hook_receipt
24
+ from agent_lifecycle.diagnostics import build_adapter_install_plan
25
+ from agent_lifecycle.host_protocol import (
26
+ inspect_adapter_descriptor,
27
+ require_adapter_event_stream_pass,
28
+ require_event_capture_pass,
29
+ require_adapter_inspection_pass,
30
+ require_adapter_validation_pass,
31
+ scaffold_adapter,
32
+ validate_adapter_descriptor,
33
+ validate_adapter_event_stream,
34
+ validate_event_capture_conformance,
35
+ )
36
+
37
+
38
+ def add_adapter_parser(subparsers: argparse._SubParsersAction) -> None:
39
+ adapter = subparsers.add_parser("adapter", help="adapter commands")
40
+ adapter_sub = adapter.add_subparsers(dest="adapter_command")
41
+ adapter_validate = adapter_sub.add_parser("validate")
42
+ adapter_validate.add_argument("--descriptor", required=True)
43
+ adapter_validate.add_argument("--baseline")
44
+ adapter_validate.add_argument("--request", action="append", default=[])
45
+ adapter_validate.add_argument("--receipt", action="append", default=[])
46
+ adapter_inspect = adapter_sub.add_parser("inspect")
47
+ adapter_inspect.add_argument("--descriptor", required=True)
48
+ adapter_inspect.add_argument("--host-bin")
49
+ adapter_inspect.add_argument("--project-root", default=".")
50
+ adapter_inspect.add_argument("--skip-host-commands", action="store_true")
51
+ adapter_inspect.add_argument("--timeout-seconds", type=float, default=10.0)
52
+ adapter_install_plan = adapter_sub.add_parser("install-plan")
53
+ adapter_install_plan.add_argument("--descriptor", required=True)
54
+ adapter_install_plan.add_argument("--project-root", default=".")
55
+ adapter_event = adapter_sub.add_parser("event-check")
56
+ adapter_event.add_argument("--event", action="append", required=True)
57
+ adapter_event_capture = adapter_sub.add_parser("event-capture-check")
58
+ adapter_event_capture.add_argument("--descriptor", required=True)
59
+ adapter_event_capture.add_argument("--projection")
60
+ adapter_event_capture.add_argument("--capability-manifest")
61
+ adapter_event_capture.add_argument("--event", action="append", required=True)
62
+ adapter_event_capture.add_argument("--receipt", required=True)
63
+ adapter_scaffold = adapter_sub.add_parser("scaffold")
64
+ adapter_scaffold.add_argument("--host", required=True)
65
+ adapter_scaffold.add_argument("--target", required=True)
66
+ adapter_scaffold.add_argument("--maturity", default="EXPERIMENTAL")
67
+ adapter_scaffold.add_argument("--dry-run", action="store_true")
68
+ adapter_session = adapter_sub.add_parser("session")
69
+ session_sub = adapter_session.add_subparsers(dest="adapter_session_command", required=True)
70
+ session_start = session_sub.add_parser("start")
71
+ session_start.add_argument("--adapter", required=True)
72
+ session_start.add_argument("--descriptor")
73
+ session_start.add_argument("--session-root")
74
+ session_start.add_argument("--launch", action="store_true")
75
+ session_start.add_argument("--env-policy")
76
+ session_start.add_argument("--out")
77
+ session_resume = session_sub.add_parser("resume")
78
+ session_resume.add_argument("--session", required=True)
79
+ session_resume.add_argument("--session-root")
80
+ session_resume.add_argument("--adapter")
81
+ session_resume.add_argument("--state")
82
+ session_resume.add_argument("--task")
83
+ session_resume.add_argument("--out")
84
+ session_status = session_sub.add_parser("status")
85
+ session_status.add_argument("--session", required=True)
86
+ session_status.add_argument("--session-root")
87
+ session_status.add_argument("--out")
88
+ session_promote = session_sub.add_parser("promote")
89
+ session_promote.add_argument("--session", required=True)
90
+ session_promote.add_argument("--session-root")
91
+ session_promote.add_argument("--adapter")
92
+ session_promote.add_argument("--state", required=True)
93
+ session_promote.add_argument("--task", required=True)
94
+ session_promote.add_argument("--progress-hook", choices=["stderr", "receipt", "off"], default="stderr")
95
+ session_promote.add_argument("--progress-receipt")
96
+ session_promote.add_argument("--out")
97
+ adapter_task = adapter_sub.add_parser("task")
98
+ task_sub = adapter_task.add_subparsers(dest="adapter_task_command", required=True)
99
+ task_start = task_sub.add_parser("start")
100
+ task_start.add_argument("--adapter", required=True)
101
+ source_group = task_start.add_mutually_exclusive_group(required=True)
102
+ source_group.add_argument("--file", "--task-file", dest="task_file")
103
+ source_group.add_argument("--text", "--task-text", dest="task_text")
104
+ task_start.add_argument("--descriptor")
105
+ task_start.add_argument("--session-root")
106
+ task_start.add_argument("--state")
107
+ task_start.add_argument("--lock")
108
+ task_start.add_argument("--task")
109
+ task_start.add_argument("--operation-id")
110
+ task_start.add_argument("--expected-revision", type=int)
111
+ task_start.add_argument("--source-revision")
112
+ task_start.add_argument("--candidate-out")
113
+ task_start.add_argument("--package-id", default="adapter-task-intake")
114
+ task_start.add_argument("--max-input-bytes", type=int, default=32768)
115
+ task_start.add_argument("--target-tokens", type=int, default=4096)
116
+ task_start.add_argument("--progress-hook", choices=["stderr", "receipt", "off"], default="stderr")
117
+ task_start.add_argument("--progress-receipt")
118
+ task_start.add_argument("--out")
119
+ adapter_run = adapter_sub.add_parser("run")
120
+ adapter_run.add_argument("--adapter", required=True)
121
+ adapter_run.add_argument("--descriptor")
122
+ adapter_run.add_argument("--session-root")
123
+ adapter_run.add_argument("--state", required=True)
124
+ adapter_run.add_argument("--manifest", required=True)
125
+ adapter_run.add_argument("--lock")
126
+ adapter_run.add_argument("--task", required=True)
127
+ adapter_run.add_argument("--operation-id", required=True)
128
+ adapter_run.add_argument("--expected-revision", required=True, type=int)
129
+ adapter_run.add_argument("--source-revision", required=True)
130
+ adapter_run.add_argument("--progress-hook", choices=["stderr", "receipt", "off"], default="stderr")
131
+ adapter_run.add_argument("--progress-receipt")
132
+ adapter_run.add_argument("--out")
133
+
134
+
135
+ def dispatch_adapter(args: argparse.Namespace) -> dict[str, Any]:
136
+ if args.adapter_command == "validate":
137
+ descriptor = read_json_object(Path(args.descriptor), label="adapter descriptor")
138
+ baseline = read_json_object(Path(args.baseline), label="adapter baseline") if args.baseline else None
139
+ requests = [read_json_object(Path(path), label="host operation request") for path in args.request]
140
+ receipts = [read_json_object(Path(path), label="host operation receipt") for path in args.receipt]
141
+ return require_adapter_validation_pass(
142
+ validate_adapter_descriptor(
143
+ descriptor,
144
+ baseline=baseline,
145
+ requests=requests,
146
+ receipts=receipts,
147
+ )
148
+ )
149
+ if args.adapter_command == "inspect":
150
+ descriptor_path = Path(args.descriptor)
151
+ descriptor = read_json_object(descriptor_path, label="adapter descriptor")
152
+ return require_adapter_inspection_pass(
153
+ inspect_adapter_descriptor(
154
+ descriptor,
155
+ descriptor_path=descriptor_path,
156
+ host_bin=args.host_bin,
157
+ project_root=Path(args.project_root),
158
+ skip_host_commands=args.skip_host_commands,
159
+ timeout_seconds=args.timeout_seconds,
160
+ )
161
+ )
162
+ if args.adapter_command == "install-plan":
163
+ return build_adapter_install_plan(
164
+ project_root=Path(args.project_root),
165
+ descriptor_path=Path(args.descriptor),
166
+ )
167
+ if args.adapter_command == "event-check":
168
+ events = [read_json_object(Path(path), label="adapter event") for path in args.event]
169
+ return require_adapter_event_stream_pass(validate_adapter_event_stream(events))
170
+ if args.adapter_command == "event-capture-check":
171
+ descriptor = read_json_object(Path(args.descriptor), label="adapter descriptor")
172
+ projection = read_json_object(Path(args.projection), label="adapter projection") if args.projection else None
173
+ capability_manifest = read_json_object(Path(args.capability_manifest), label="capability manifest") if args.capability_manifest else None
174
+ receipt = read_json_object(Path(args.receipt), label="adapter event stream receipt")
175
+ events = [read_json_object(Path(path), label="adapter event") for path in args.event]
176
+ return require_event_capture_pass(
177
+ validate_event_capture_conformance(
178
+ descriptor=descriptor,
179
+ projection=projection,
180
+ capability_manifest=capability_manifest,
181
+ events=events,
182
+ receipt=receipt,
183
+ )
184
+ )
185
+ if args.adapter_command == "scaffold":
186
+ return scaffold_adapter(
187
+ host=args.host,
188
+ target=Path(args.target),
189
+ maturity=args.maturity,
190
+ dry_run=args.dry_run,
191
+ )
192
+ if args.adapter_command == "session":
193
+ return _dispatch_adapter_session(args)
194
+ if args.adapter_command == "task":
195
+ return _dispatch_adapter_task(args)
196
+ if args.adapter_command == "run":
197
+ _validate_adapter_progress_args(args)
198
+ payload = managed_adapter_run(
199
+ adapter_id=args.adapter,
200
+ descriptor_path=Path(args.descriptor) if args.descriptor else None,
201
+ session_root=Path(args.session_root) if args.session_root else None,
202
+ state_path=Path(args.state),
203
+ manifest_path=Path(args.manifest),
204
+ lock_path=Path(args.lock) if args.lock else None,
205
+ task_id=args.task,
206
+ operation_id=args.operation_id,
207
+ expected_revision=args.expected_revision,
208
+ source_revision=args.source_revision,
209
+ )
210
+ _emit_adapter_progress(args, adapter_id=args.adapter, state_path=Path(args.state), task_id=args.task)
211
+ if args.out:
212
+ write_json_create(Path(args.out), payload)
213
+ return payload
214
+ raise LifecycleError("command-not-implemented", "adapter command is not implemented")
215
+
216
+
217
+ def _dispatch_adapter_task(args: argparse.Namespace) -> dict[str, Any]:
218
+ if args.adapter_task_command != "start":
219
+ raise LifecycleError("command-not-implemented", "adapter task command is not implemented")
220
+ payload = start_adapter_task(
221
+ adapter_id=args.adapter,
222
+ task_file=Path(args.task_file) if args.task_file else None,
223
+ task_text=args.task_text,
224
+ candidate_out=Path(args.candidate_out) if args.candidate_out else None,
225
+ descriptor_path=Path(args.descriptor) if args.descriptor else None,
226
+ session_root=Path(args.session_root) if args.session_root else None,
227
+ state_path=Path(args.state) if args.state else None,
228
+ lock_path=Path(args.lock) if args.lock else None,
229
+ task_id=args.task,
230
+ operation_id=args.operation_id,
231
+ expected_revision=args.expected_revision,
232
+ source_revision=args.source_revision,
233
+ max_input_bytes=args.max_input_bytes,
234
+ target_tokens=args.target_tokens,
235
+ package_id=args.package_id,
236
+ )
237
+ binding = payload.get("workflowBinding") if isinstance(payload.get("workflowBinding"), dict) else {}
238
+ if payload.get("executionStarted") and binding.get("state") and binding.get("task"):
239
+ _emit_adapter_progress(args, adapter_id=args.adapter, state_path=Path(binding["state"]), task_id=str(binding["task"]))
240
+ if args.out:
241
+ write_json_create(Path(args.out), payload)
242
+ return payload
243
+
244
+
245
+ def _dispatch_adapter_session(args: argparse.Namespace) -> dict[str, Any]:
246
+ if args.adapter_session_command == "start":
247
+ _descriptor_path, descriptor = load_adapter_descriptor(args.adapter, Path(args.descriptor) if args.descriptor else None)
248
+ profile = managed_launch_profile(descriptor)
249
+ session = create_session(
250
+ adapter_id=args.adapter,
251
+ mode="INTERACTIVE",
252
+ status="WAITING_FOR_TASK",
253
+ launch_profile=profile,
254
+ session_root=Path(args.session_root) if args.session_root else None,
255
+ )
256
+ launch_receipt = None
257
+ status = "WAITING_FOR_TASK"
258
+ host_launch_started = False
259
+ blockers: list[dict[str, Any]] = []
260
+ if args.launch:
261
+ launch_receipt = launch_from_descriptor(
262
+ descriptor=descriptor,
263
+ session_id=session["sessionId"],
264
+ launch_mode="interactive",
265
+ policy_path=Path(args.env_policy) if args.env_policy else None,
266
+ )
267
+ host_launch_started = launch_receipt["hostLaunchStarted"]
268
+ status = "LAUNCHED" if launch_receipt["status"] == "PASS" else "BLOCKED"
269
+ blockers = launch_receipt.get("blockers", [])
270
+ session["status"] = status
271
+ update_session(session, session_root=Path(args.session_root) if args.session_root else None)
272
+ payload = build_adapter_session_receipt(
273
+ status=status,
274
+ session_id=session["sessionId"],
275
+ adapter_id=args.adapter,
276
+ mode="INTERACTIVE",
277
+ launch_profile=profile,
278
+ progress_hook_default="off",
279
+ host_launch_started=host_launch_started,
280
+ blockers=blockers,
281
+ launch_receipt=launch_receipt,
282
+ )
283
+ if args.out:
284
+ write_json_create(Path(args.out), payload)
285
+ return payload
286
+ if args.adapter_session_command == "status":
287
+ session = load_session(args.session, session_root=Path(args.session_root) if args.session_root else None)
288
+ payload = build_adapter_session_receipt(
289
+ status=session.get("status", "UNMANAGED"),
290
+ session_id=session["sessionId"],
291
+ adapter_id=session["adapterId"],
292
+ mode=session.get("mode", "INTERACTIVE"),
293
+ launch_profile=session.get("launchProfile", {}),
294
+ state_identity=session.get("stateIdentity"),
295
+ managed_workflow_proof=session.get("managedWorkflowProof"),
296
+ progress_hook_default="off",
297
+ state_written=False,
298
+ )
299
+ if args.out:
300
+ write_json_create(Path(args.out), payload)
301
+ return payload
302
+ if args.adapter_session_command == "resume":
303
+ payload = resume_adapter_session(
304
+ session_id=args.session,
305
+ session_root=Path(args.session_root) if args.session_root else None,
306
+ adapter_id=args.adapter,
307
+ state_path=Path(args.state) if args.state else None,
308
+ task_id=args.task,
309
+ )
310
+ if args.out:
311
+ write_json_create(Path(args.out), payload)
312
+ return payload
313
+ if args.adapter_session_command == "promote":
314
+ _validate_adapter_progress_args(args)
315
+ payload = promote_session_to_workflow(
316
+ session_id=args.session,
317
+ session_root=Path(args.session_root) if args.session_root else None,
318
+ adapter_id=args.adapter,
319
+ state_path=Path(args.state),
320
+ task_id=args.task,
321
+ )
322
+ if payload.get("status") != "BLOCKED":
323
+ _emit_adapter_progress(args, adapter_id=payload["adapterId"], state_path=Path(args.state), task_id=args.task)
324
+ if args.out:
325
+ write_json_create(Path(args.out), payload)
326
+ return payload
327
+ raise LifecycleError("command-not-implemented", "adapter session command is not implemented")
328
+
329
+
330
+ def _emit_adapter_progress(args: argparse.Namespace, *, adapter_id: str, state_path: Path, task_id: str) -> None:
331
+ mode = getattr(args, "progress_hook", "off")
332
+ if mode == "off":
333
+ return
334
+ _validate_adapter_progress_args(args)
335
+ receipt = build_progress_hook_receipt(
336
+ adapter_id=adapter_id,
337
+ support_level="AUTO",
338
+ command="workflow run",
339
+ hook_point="after-workflow-run",
340
+ hook_mode=mode,
341
+ state_path=state_path,
342
+ managed_workflow_proof={
343
+ "kind": "alk-managed-workflow-command",
344
+ "status": "PASS",
345
+ "command": "workflow run",
346
+ "adapterCommand": args.adapter_command,
347
+ "taskId": task_id,
348
+ },
349
+ )
350
+ if mode == "receipt":
351
+ write_json_create(Path(args.progress_receipt), receipt)
352
+ else:
353
+ import sys
354
+
355
+ sys.stderr.write(receipt["terminalText"].rstrip("\n") + "\n")
356
+
357
+
358
+ def _validate_adapter_progress_args(args: argparse.Namespace) -> None:
359
+ if getattr(args, "progress_hook", "off") == "receipt" and not getattr(args, "progress_receipt", None):
360
+ raise LifecycleError("adapter-progress-receipt-path-missing", "--progress-hook receipt requires --progress-receipt")