power-loop 7.3.1__tar.gz → 9.0.0__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 (226) hide show
  1. {power_loop-7.3.1 → power_loop-9.0.0}/PKG-INFO +47 -4
  2. {power_loop-7.3.1 → power_loop-9.0.0}/README.md +43 -2
  3. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/__init__.py +16 -2
  4. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/_vendor/llm_client/llm_factory.py +2 -1
  5. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/_vendor/llm_client/multimodal.py +3 -3
  6. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/agent/sink.py +33 -10
  7. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/agent/stateful_loop.py +164 -75
  8. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/agent/system_prompt.py +50 -55
  9. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/agent/types.py +6 -1
  10. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/contracts/context_policy.py +3 -0
  11. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/contracts/errors.py +10 -0
  12. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/contracts/event_payloads.py +42 -0
  13. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/contracts/events.py +4 -0
  14. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/contracts/tools.py +34 -2
  15. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/contrib/mcp.py +29 -8
  16. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/core/pass_turn.py +13 -5
  17. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/core/pipeline.py +274 -114
  18. power_loop-9.0.0/power_loop/runtime/_tagged_processes.py +129 -0
  19. power_loop-9.0.0/power_loop/runtime/abort_outcomes.py +481 -0
  20. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/runtime/background.py +1 -1
  21. power_loop-9.0.0/power_loop/runtime/budget_contracts.py +279 -0
  22. power_loop-9.0.0/power_loop/runtime/budget_revisions.py +221 -0
  23. power_loop-9.0.0/power_loop/runtime/budget_scopes.py +207 -0
  24. power_loop-9.0.0/power_loop/runtime/completion.py +250 -0
  25. power_loop-9.0.0/power_loop/runtime/completion_windows.py +119 -0
  26. power_loop-9.0.0/power_loop/runtime/context_window_probe.py +112 -0
  27. power_loop-9.0.0/power_loop/runtime/controller_handoff.py +241 -0
  28. power_loop-9.0.0/power_loop/runtime/controller_recovery.py +397 -0
  29. power_loop-9.0.0/power_loop/runtime/controller_retirement.py +127 -0
  30. power_loop-9.0.0/power_loop/runtime/controller_task_exit.py +181 -0
  31. power_loop-9.0.0/power_loop/runtime/delegation.py +605 -0
  32. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/runtime/exec_backend.py +7 -0
  33. power_loop-9.0.0/power_loop/runtime/execution_membership.py +235 -0
  34. power_loop-9.0.0/power_loop/runtime/execution_roles.py +84 -0
  35. power_loop-9.0.0/power_loop/runtime/executor_lifecycle.py +384 -0
  36. power_loop-9.0.0/power_loop/runtime/executor_registry.py +364 -0
  37. power_loop-9.0.0/power_loop/runtime/finalization.py +516 -0
  38. power_loop-9.0.0/power_loop/runtime/fold_calls.py +7 -0
  39. power_loop-9.0.0/power_loop/runtime/host_send_steps.py +213 -0
  40. power_loop-9.0.0/power_loop/runtime/input_dispositions.py +180 -0
  41. power_loop-9.0.0/power_loop/runtime/invocation.py +24 -0
  42. power_loop-9.0.0/power_loop/runtime/jobs.py +1028 -0
  43. power_loop-9.0.0/power_loop/runtime/managed_claim.py +116 -0
  44. power_loop-9.0.0/power_loop/runtime/managed_completion.py +310 -0
  45. power_loop-9.0.0/power_loop/runtime/managed_delivery.py +405 -0
  46. power_loop-9.0.0/power_loop/runtime/managed_effects.py +1401 -0
  47. power_loop-9.0.0/power_loop/runtime/message_ref.py +51 -0
  48. power_loop-9.0.0/power_loop/runtime/model_attempt_query.py +83 -0
  49. power_loop-9.0.0/power_loop/runtime/model_attempts.py +692 -0
  50. power_loop-9.0.0/power_loop/runtime/model_budgets.py +1019 -0
  51. power_loop-9.0.0/power_loop/runtime/model_calls.py +165 -0
  52. power_loop-9.0.0/power_loop/runtime/model_exit.py +72 -0
  53. power_loop-9.0.0/power_loop/runtime/native_returns.py +298 -0
  54. power_loop-9.0.0/power_loop/runtime/nested_tools.py +188 -0
  55. power_loop-9.0.0/power_loop/runtime/node_inputs.py +240 -0
  56. power_loop-9.0.0/power_loop/runtime/node_resume.py +330 -0
  57. power_loop-9.0.0/power_loop/runtime/nudges.py +155 -0
  58. power_loop-9.0.0/power_loop/runtime/original_owner_review.py +312 -0
  59. power_loop-9.0.0/power_loop/runtime/owner_waits.py +556 -0
  60. power_loop-9.0.0/power_loop/runtime/pending_calls.py +429 -0
  61. power_loop-9.0.0/power_loop/runtime/pending_native.py +170 -0
  62. power_loop-9.0.0/power_loop/runtime/pending_owner.py +97 -0
  63. power_loop-9.0.0/power_loop/runtime/pricing.py +201 -0
  64. power_loop-9.0.0/power_loop/runtime/process_control.py +194 -0
  65. power_loop-9.0.0/power_loop/runtime/processing.py +379 -0
  66. power_loop-9.0.0/power_loop/runtime/recall.py +237 -0
  67. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/runtime/representation.py +7 -2
  68. power_loop-9.0.0/power_loop/runtime/response_capture.py +157 -0
  69. power_loop-9.0.0/power_loop/runtime/result_payloads.py +36 -0
  70. power_loop-9.0.0/power_loop/runtime/result_proof.py +14 -0
  71. power_loop-9.0.0/power_loop/runtime/result_revisions.py +41 -0
  72. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/runtime/retry.py +2 -0
  73. power_loop-9.0.0/power_loop/runtime/review_tools.py +53 -0
  74. power_loop-9.0.0/power_loop/runtime/root_context.py +178 -0
  75. power_loop-9.0.0/power_loop/runtime/root_control.py +163 -0
  76. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/runtime/runtime_state.py +8 -2
  77. power_loop-9.0.0/power_loop/runtime/send_receipt.py +143 -0
  78. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/runtime/session_store.py +15 -12
  79. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/runtime/skills.py +4 -2
  80. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/runtime/spec.py +121 -39
  81. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/runtime/store/backends/sqlite.py +101 -45
  82. power_loop-9.0.0/power_loop/runtime/store/context_cache.py +41 -0
  83. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/runtime/store/dialect.py +6 -3
  84. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/runtime/store/factory.py +12 -3
  85. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/runtime/store/schema.py +17 -0
  86. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/runtime/store/store.py +181 -39
  87. power_loop-9.0.0/power_loop/runtime/store/task_schema.py +453 -0
  88. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/runtime/store/types.py +2 -0
  89. power_loop-9.0.0/power_loop/runtime/subagent_output.py +45 -0
  90. power_loop-9.0.0/power_loop/runtime/subagent_resume.py +254 -0
  91. power_loop-9.0.0/power_loop/runtime/task_control.py +284 -0
  92. power_loop-9.0.0/power_loop/runtime/task_events.py +249 -0
  93. power_loop-9.0.0/power_loop/runtime/task_history.py +390 -0
  94. power_loop-9.0.0/power_loop/runtime/task_inspection.py +170 -0
  95. power_loop-9.0.0/power_loop/runtime/task_query.py +298 -0
  96. power_loop-9.0.0/power_loop/runtime/task_runtime.py +797 -0
  97. power_loop-9.0.0/power_loop/runtime/task_sessions.py +137 -0
  98. power_loop-9.0.0/power_loop/runtime/timer_backend.py +60 -0
  99. power_loop-9.0.0/power_loop/runtime/timer_waits.py +339 -0
  100. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/runtime/timers.py +9 -0
  101. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/runtime/tool_archive.py +41 -43
  102. power_loop-9.0.0/power_loop/runtime/tool_attempts.py +407 -0
  103. power_loop-9.0.0/power_loop/runtime/tool_billing.py +141 -0
  104. power_loop-9.0.0/power_loop/runtime/tool_call_abort.py +174 -0
  105. power_loop-9.0.0/power_loop/runtime/tool_call_plans.py +428 -0
  106. power_loop-9.0.0/power_loop/runtime/tool_context.py +274 -0
  107. power_loop-9.0.0/power_loop/runtime/tool_contracts.py +312 -0
  108. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/runtime/tool_truncation.py +1 -2
  109. power_loop-9.0.0/power_loop/runtime/wait_decisions.py +228 -0
  110. power_loop-9.0.0/power_loop/runtime/wait_dispositions.py +205 -0
  111. power_loop-9.0.0/power_loop/runtime/wait_effects.py +299 -0
  112. power_loop-9.0.0/power_loop/runtime/wait_notifications.py +240 -0
  113. power_loop-9.0.0/power_loop/runtime/wait_reviews.py +193 -0
  114. power_loop-9.0.0/power_loop/runtime/wait_terminal.py +271 -0
  115. power_loop-9.0.0/power_loop/runtime/workflow_controller.py +196 -0
  116. power_loop-9.0.0/power_loop/runtime/workflow_profile.py +164 -0
  117. power_loop-9.0.0/power_loop/runtime/workflow_resources.py +279 -0
  118. power_loop-9.0.0/power_loop/runtime/workflow_restore.py +249 -0
  119. power_loop-9.0.0/power_loop/runtime/workflow_restore_execution.py +367 -0
  120. power_loop-9.0.0/power_loop/runtime/workflow_restore_resources.py +168 -0
  121. power_loop-9.0.0/power_loop/runtime/workflow_restore_runner.py +246 -0
  122. power_loop-9.0.0/power_loop/runtime/workflow_results.py +304 -0
  123. power_loop-9.0.0/power_loop/runtime/workflow_resume.py +262 -0
  124. power_loop-9.0.0/power_loop/runtime/workflow_tasks.py +488 -0
  125. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/tools/__init__.py +5 -2
  126. power_loop-9.0.0/power_loop/tools/background_contract.py +71 -0
  127. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/tools/blackboard.py +2 -1
  128. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/tools/default_manifest.py +68 -36
  129. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/tools/default_tools.py +310 -123
  130. power_loop-9.0.0/power_loop/tools/jobs.py +238 -0
  131. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/tools/registry.py +76 -46
  132. power_loop-9.0.0/power_loop/tools/shell_contract.py +46 -0
  133. power_loop-9.0.0/power_loop/tools/skill_contract.py +22 -0
  134. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/tools/spawn_agent.py +94 -30
  135. power_loop-9.0.0/power_loop/tools/wait_handoff.py +81 -0
  136. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/workflow/__init__.py +4 -0
  137. power_loop-9.0.0/power_loop/workflow/_process_guard.py +81 -0
  138. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/workflow/admission.py +7 -1
  139. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/workflow/api.py +22 -1
  140. power_loop-9.0.0/power_loop/workflow/continuation.py +167 -0
  141. power_loop-9.0.0/power_loop/workflow/control.py +149 -0
  142. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/workflow/engine.py +346 -38
  143. power_loop-9.0.0/power_loop/workflow/facade.py +527 -0
  144. power_loop-9.0.0/power_loop/workflow/legacy_history.py +555 -0
  145. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/workflow/result.py +6 -0
  146. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/workflow/resume.py +18 -44
  147. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/workflow/runner.py +302 -22
  148. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/workflow/spec.py +11 -0
  149. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/workflow/subprocess_executor.py +73 -74
  150. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/workflow/tool.py +57 -140
  151. power_loop-9.0.0/power_loop/workflow/yaml.py +1513 -0
  152. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop.egg-info/PKG-INFO +47 -4
  153. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop.egg-info/SOURCES.txt +100 -2
  154. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop.egg-info/requires.txt +3 -0
  155. {power_loop-7.3.1 → power_loop-9.0.0}/pyproject.toml +3 -2
  156. power_loop-7.3.1/power_loop/runtime/context_worker.py +0 -121
  157. power_loop-7.3.1/power_loop/runtime/store/context_cache.py +0 -223
  158. power_loop-7.3.1/power_loop/runtime/tool_context.py +0 -416
  159. {power_loop-7.3.1 → power_loop-9.0.0}/LICENSE +0 -0
  160. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/_vendor/__init__.py +0 -0
  161. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/_vendor/llm_client/__init__.py +0 -0
  162. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/_vendor/llm_client/anthropic_factory.py +0 -0
  163. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/_vendor/llm_client/attempt_scope.py +0 -0
  164. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/_vendor/llm_client/capabilities.py +0 -0
  165. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/_vendor/llm_client/interface.py +0 -0
  166. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/_vendor/llm_client/llm_tooling.py +0 -0
  167. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/_vendor/llm_client/llm_utils.py +0 -0
  168. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/_vendor/llm_client/proxy_fallback.py +0 -0
  169. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/agent/__init__.py +0 -0
  170. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/agent/follow_up.py +0 -0
  171. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/contracts/__init__.py +0 -0
  172. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/contracts/handlers.py +0 -0
  173. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/contracts/hook_contexts.py +0 -0
  174. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/contracts/hooks.py +0 -0
  175. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/contracts/messages.py +0 -0
  176. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/contracts/protocols.py +0 -0
  177. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/contrib/__init__.py +0 -0
  178. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/contrib/_redact.py +0 -0
  179. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/contrib/jsonl_sink.py +0 -0
  180. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/contrib/logging_sink.py +0 -0
  181. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/contrib/metrics_sink.py +0 -0
  182. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/contrib/otel_sink.py +0 -0
  183. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/core/agent_context.py +0 -0
  184. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/core/events.py +0 -0
  185. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/core/hooks.py +0 -0
  186. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/core/phase.py +0 -0
  187. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/core/runner.py +0 -0
  188. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/core/state.py +0 -0
  189. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/py.typed +0 -0
  190. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/runtime/bash_capture.py +0 -0
  191. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/runtime/blackboard.py +0 -0
  192. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/runtime/budget.py +0 -0
  193. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/runtime/cancellation.py +0 -0
  194. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/runtime/capability_probe.py +0 -0
  195. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/runtime/compact.py +0 -0
  196. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/runtime/env.py +0 -0
  197. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/runtime/fold.py +0 -0
  198. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/runtime/fold_adapter.py +0 -0
  199. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/runtime/history_projector.py +0 -0
  200. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/runtime/history_sanitize.py +0 -0
  201. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/runtime/human_input.py +0 -0
  202. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/runtime/image_recall.py +0 -0
  203. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/runtime/lineage.py +0 -0
  204. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/runtime/memory.py +0 -0
  205. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/runtime/notes.py +0 -0
  206. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/runtime/provider.py +0 -0
  207. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/runtime/steer.py +0 -0
  208. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/runtime/stop_policy.py +0 -0
  209. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/runtime/store/__init__.py +0 -0
  210. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/runtime/store/backends/__init__.py +0 -0
  211. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/runtime/store/backends/mysql.py +0 -0
  212. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/runtime/store/backends/postgres.py +0 -0
  213. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/runtime/store/capabilities.py +0 -0
  214. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/runtime/store/db.py +0 -0
  215. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/runtime/structured.py +0 -0
  216. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/runtime/stub_provider.py +0 -0
  217. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/runtime/todos.py +0 -0
  218. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/runtime/usage_estimate.py +0 -0
  219. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/tools/command_policy.py +0 -0
  220. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/tools/command_rules.py +0 -0
  221. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/workflow/introspect.py +0 -0
  222. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/workflow/journal.py +0 -0
  223. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop/workflow/worker.py +0 -0
  224. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop.egg-info/dependency_links.txt +0 -0
  225. {power_loop-7.3.1 → power_loop-9.0.0}/power_loop.egg-info/top_level.txt +0 -0
  226. {power_loop-7.3.1 → power_loop-9.0.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: power-loop
3
- Version: 7.3.1
3
+ Version: 9.0.0
4
4
  Summary: Embeddable agent execution kernel — LLM loop, hooks, events, tools, dynamic sub-agents.
5
5
  Author-email: zhangran <zhangran24@126.com>
6
6
  License: MIT
@@ -18,7 +18,7 @@ Classifier: Programming Language :: Python :: 3.10
18
18
  Classifier: Programming Language :: Python :: 3.11
19
19
  Classifier: Programming Language :: Python :: 3.12
20
20
  Classifier: Topic :: Software Development :: Libraries
21
- Requires-Python: >=3.10
21
+ Requires-Python: >=3.11
22
22
  Description-Content-Type: text/markdown
23
23
  License-File: LICENSE
24
24
  Provides-Extra: anthropic
@@ -27,6 +27,8 @@ Provides-Extra: openai
27
27
  Requires-Dist: openai>=1.52.0; extra == "openai"
28
28
  Provides-Extra: skills
29
29
  Requires-Dist: pyyaml>=6.0; extra == "skills"
30
+ Provides-Extra: workflow
31
+ Requires-Dist: pyyaml>=6.0; extra == "workflow"
30
32
  Provides-Extra: pdf
31
33
  Requires-Dist: pypdf>=5.3.0; extra == "pdf"
32
34
  Provides-Extra: images
@@ -303,7 +305,8 @@ Every event carries a process-wide `seq` and a monotonic clock, so streams total
303
305
  from power_loop.contrib.mcp import StdioMCPClient, register_mcp_tools # power-loop[mcp]
304
306
 
305
307
  client = await StdioMCPClient("npx", ["-y", "@modelcontextprotocol/server-filesystem", "/data"]).connect()
306
- await register_mcp_tools(registry, client, prefix="fs.") # MCP tools → power-loop ToolDefinitions
308
+ from power_loop.runtime.pricing import Free
309
+ await register_mcp_tools(registry, client, pricing=Free("local_filesystem"), prefix="fs.") # MCP tools → power-loop ToolDefinitions
307
310
  ```
308
311
 
309
312
  The seam is a tiny `MCPToolSource` Protocol, so the `mcp` SDK is optional and any client works.
@@ -342,7 +345,7 @@ POWER_LOOP_API_KEY=sk-...
342
345
  POWER_LOOP_MODEL=gpt-4o-mini
343
346
  ```
344
347
 
345
- Python 3.10+. See [Getting Started](docs/en/getting-started.md). Optional extras: `postgres`, `mysql`, `skills`, `pdf`, `prometheus`, `statsd`, `otel`, `mcp`.
348
+ Python 3.11+. See [Getting Started](docs/en/getting-started.md). Optional extras: `postgres`, `mysql`, `skills`, `pdf`, `prometheus`, `statsd`, `otel`, `mcp`.
346
349
 
347
350
  ---
348
351
 
@@ -375,3 +378,43 @@ power-loop **orchestrates; it does not, by itself, isolate.** The built-in `bash
375
378
  - **Used by:** DeepTalk — the agent runtime for a 1-on-1 relationship-IM product's in-conversation agents. *(Using it in production? PR a line here.)*
376
379
  - **Develop:** `pip install -e ".[dev]"` · `ruff check .` · `pytest -q --no-real` (drop `--no-real` for the live-LLM suite; set a `POWER_LOOP_TEST_PG_DSN` / `POWER_LOOP_TEST_MYSQL_DSN` to run the server-backend conformance suites).
377
380
  - [Docs](docs/en/index.md) · [Architecture](docs/en/architecture.md) · [Storage backends](docs/en/user-guide/storage-backends.md) · [Changelog](CHANGELOG.md) · [Contributing](CONTRIBUTING.md) · [Security](SECURITY.md) · [License](LICENSE)
381
+
382
+ Trusted host tools declare roles, admission and background behavior through the
383
+ [RuntimeTool interface](docs/runtime-tool-contracts.md); names do not grant capabilities.
384
+
385
+
386
+ ### Explicit tool pricing
387
+
388
+ Every registered tool definition must provide `pricing`, including local and
389
+ control tools: `ToolDefinition(name="local", description="...", pricing=Free("local"))`.
390
+ Import `Free` or implement `PricingSpec` from `power_loop.runtime.pricing`.
391
+ Registration rejects a missing declaration. The rule version is retained with the
392
+ registration and pending call identity; changing it requires a new registration,
393
+ and cannot silently reprice a previously planned call. Pricing metadata is not
394
+ sent to the model as part of the tool schema.
395
+
396
+ MCP registration requires either one explicit server pricing rule or a mapping
397
+ from remote tool names to rules. Missing entries reject the batch before any tool
398
+ is registered. An external service is not assumed free.
399
+
400
+ Native callbacks can return `ToolResult(content, billing_receipt=receipt)`; only
401
+ `content` is shown to the model. Async callbacks can instead
402
+ `await record_billing_receipt(receipt)` immediately after receiving provider billing
403
+ facts, before subsequent response processing might fail. Both helpers are
404
+ provisional top-level exports; `BillingReceipt` is in `power_loop.runtime.pricing`.
405
+ The awaited capture is bound to the original callback task and persisted call,
406
+ and repeating the same receipt is idempotent. A different receipt is rejected.
407
+ Receipt capture requires the native callback checkpoint path; it does not yet
408
+ support managed recovery adapters or legacy invocation. Persisting a receipt
409
+ does not acknowledge successful execution or settle a charge. Host budget and
410
+ settlement wiring is still required before paid tools can be enabled.
411
+
412
+ An ordinary native callback can invoke a priced internal tool with the provisional
413
+ `await invoke_priced_tool(definition, arguments, handler)` export. Each invocation
414
+ gets a separate budget attempt linked to its original source callback. Its price
415
+ must already be bound, and provider facts use the same `record_billing_receipt` API.
416
+ Internal concurrent tasks are retained and joined before the source callback ends.
417
+ After the exact charge is locally durable, API delivery failure leaves a pending
418
+ budget submission and preserves the real tool result. Reconciliation never calls
419
+ the handler again or reads a replacement price. Managed recovery sources without
420
+ an ordinary callback checkpoint still need a separate adapter.
@@ -227,7 +227,8 @@ Every event carries a process-wide `seq` and a monotonic clock, so streams total
227
227
  from power_loop.contrib.mcp import StdioMCPClient, register_mcp_tools # power-loop[mcp]
228
228
 
229
229
  client = await StdioMCPClient("npx", ["-y", "@modelcontextprotocol/server-filesystem", "/data"]).connect()
230
- await register_mcp_tools(registry, client, prefix="fs.") # MCP tools → power-loop ToolDefinitions
230
+ from power_loop.runtime.pricing import Free
231
+ await register_mcp_tools(registry, client, pricing=Free("local_filesystem"), prefix="fs.") # MCP tools → power-loop ToolDefinitions
231
232
  ```
232
233
 
233
234
  The seam is a tiny `MCPToolSource` Protocol, so the `mcp` SDK is optional and any client works.
@@ -266,7 +267,7 @@ POWER_LOOP_API_KEY=sk-...
266
267
  POWER_LOOP_MODEL=gpt-4o-mini
267
268
  ```
268
269
 
269
- Python 3.10+. See [Getting Started](docs/en/getting-started.md). Optional extras: `postgres`, `mysql`, `skills`, `pdf`, `prometheus`, `statsd`, `otel`, `mcp`.
270
+ Python 3.11+. See [Getting Started](docs/en/getting-started.md). Optional extras: `postgres`, `mysql`, `skills`, `pdf`, `prometheus`, `statsd`, `otel`, `mcp`.
270
271
 
271
272
  ---
272
273
 
@@ -299,3 +300,43 @@ power-loop **orchestrates; it does not, by itself, isolate.** The built-in `bash
299
300
  - **Used by:** DeepTalk — the agent runtime for a 1-on-1 relationship-IM product's in-conversation agents. *(Using it in production? PR a line here.)*
300
301
  - **Develop:** `pip install -e ".[dev]"` · `ruff check .` · `pytest -q --no-real` (drop `--no-real` for the live-LLM suite; set a `POWER_LOOP_TEST_PG_DSN` / `POWER_LOOP_TEST_MYSQL_DSN` to run the server-backend conformance suites).
301
302
  - [Docs](docs/en/index.md) · [Architecture](docs/en/architecture.md) · [Storage backends](docs/en/user-guide/storage-backends.md) · [Changelog](CHANGELOG.md) · [Contributing](CONTRIBUTING.md) · [Security](SECURITY.md) · [License](LICENSE)
303
+
304
+ Trusted host tools declare roles, admission and background behavior through the
305
+ [RuntimeTool interface](docs/runtime-tool-contracts.md); names do not grant capabilities.
306
+
307
+
308
+ ### Explicit tool pricing
309
+
310
+ Every registered tool definition must provide `pricing`, including local and
311
+ control tools: `ToolDefinition(name="local", description="...", pricing=Free("local"))`.
312
+ Import `Free` or implement `PricingSpec` from `power_loop.runtime.pricing`.
313
+ Registration rejects a missing declaration. The rule version is retained with the
314
+ registration and pending call identity; changing it requires a new registration,
315
+ and cannot silently reprice a previously planned call. Pricing metadata is not
316
+ sent to the model as part of the tool schema.
317
+
318
+ MCP registration requires either one explicit server pricing rule or a mapping
319
+ from remote tool names to rules. Missing entries reject the batch before any tool
320
+ is registered. An external service is not assumed free.
321
+
322
+ Native callbacks can return `ToolResult(content, billing_receipt=receipt)`; only
323
+ `content` is shown to the model. Async callbacks can instead
324
+ `await record_billing_receipt(receipt)` immediately after receiving provider billing
325
+ facts, before subsequent response processing might fail. Both helpers are
326
+ provisional top-level exports; `BillingReceipt` is in `power_loop.runtime.pricing`.
327
+ The awaited capture is bound to the original callback task and persisted call,
328
+ and repeating the same receipt is idempotent. A different receipt is rejected.
329
+ Receipt capture requires the native callback checkpoint path; it does not yet
330
+ support managed recovery adapters or legacy invocation. Persisting a receipt
331
+ does not acknowledge successful execution or settle a charge. Host budget and
332
+ settlement wiring is still required before paid tools can be enabled.
333
+
334
+ An ordinary native callback can invoke a priced internal tool with the provisional
335
+ `await invoke_priced_tool(definition, arguments, handler)` export. Each invocation
336
+ gets a separate budget attempt linked to its original source callback. Its price
337
+ must already be bound, and provider facts use the same `record_billing_receipt` API.
338
+ Internal concurrent tasks are retained and joined before the source callback ends.
339
+ After the exact charge is locally durable, API delivery failure leaves a pending
340
+ budget submission and preserves the real tool result. Reconciliation never calls
341
+ the handler again or reads a replacement price. Managed recovery sources without
342
+ an ordinary callback checkpoint still need a separate adapter.
@@ -15,7 +15,7 @@ Stability tiers
15
15
  无版本承诺,可随时变更或删除。
16
16
  """
17
17
 
18
- __version__ = "7.3.1"
18
+ __version__ = "9.0.0"
19
19
 
20
20
  # Public LLM contract (SDK-free) re-exported so callers (e.g. writing llm.* hooks or
21
21
  # a custom LLMService) don't reach into the internal vendored transport package (H3.4).
@@ -76,6 +76,7 @@ from power_loop.contracts.event_payloads import (
76
76
  LlmCallCompletedPayload,
77
77
  LlmCallStartedPayload,
78
78
  LlmDegradedPayload,
79
+ LlmResponseReceivedPayload,
79
80
  LlmRetryAttemptedPayload,
80
81
  LoopCancelledPayload,
81
82
  MemoryFailedPayload,
@@ -97,6 +98,7 @@ from power_loop.contracts.event_payloads import (
97
98
  SubagentTaskStartPayload,
98
99
  SubagentTextPayload,
99
100
  SystemLogPayload,
101
+ TaskSupervisionClosedPayload,
100
102
  TimerFiredPayload,
101
103
  TodoUpdatedPayload,
102
104
  ToolCallCompletedPayload,
@@ -136,7 +138,7 @@ from power_loop.contracts.protocols import (
136
138
  HookManagerProtocol,
137
139
  ToolArgsValidator,
138
140
  )
139
- from power_loop.contracts.tools import ToolDefinition, validate_tool_args
141
+ from power_loop.contracts.tools import ToolDefinition, ToolResult, validate_tool_args
140
142
  from power_loop.core.agent_context import get_effective_tools
141
143
  from power_loop.core.events import AgentEventBus
142
144
  from power_loop.core.hooks import AgentHooks
@@ -174,6 +176,7 @@ from power_loop.runtime.memory import (
174
176
  MemorySnapshot,
175
177
  tag_as_memory,
176
178
  )
179
+ from power_loop.runtime.nested_tools import invoke_priced_tool
177
180
  from power_loop.runtime.notes import (
178
181
  DEFAULT_NOTES_POLICY,
179
182
  NoteMemory,
@@ -239,6 +242,7 @@ from power_loop.runtime.structured import (
239
242
  parse_structured,
240
243
  )
241
244
  from power_loop.runtime.timers import TimerDelivery, TimerRunner
245
+ from power_loop.runtime.tool_billing import record_billing_receipt, tool_billing_active, tool_billing_managed
242
246
  from power_loop.runtime.tool_context import ToolContextConfig
243
247
  from power_loop.tools import (
244
248
  DEFAULT_TOOL_HANDLERS,
@@ -254,6 +258,7 @@ from power_loop.tools.default_manifest import (
254
258
  TOOL_PRESETS,
255
259
  get_tool_definitions,
256
260
  )
261
+ from power_loop.tools.jobs import JOBS_DEFINITION, register_jobs_tools
257
262
  from power_loop.tools.registry import AsyncToolInSyncContext
258
263
  from power_loop.tools.spawn_agent import (
259
264
  SPAWN_AGENT_DEFINITION,
@@ -397,6 +402,7 @@ __all__ = [
397
402
  "request_user_input",
398
403
  "LlmCallStartedPayload",
399
404
  "LlmCallCompletedPayload",
405
+ "LlmResponseReceivedPayload",
400
406
  "LlmRetryAttemptedPayload",
401
407
  "SteerInterruptedPayload",
402
408
  "LlmDegradedPayload",
@@ -489,6 +495,7 @@ __all__ = [
489
495
  "SubagentTextPayload",
490
496
  "SubagentLimitPayload",
491
497
  "SubagentCompletedPayload",
498
+ "TaskSupervisionClosedPayload",
492
499
  "EventHandler",
493
500
  "HookHandler",
494
501
  "ToolHandler",
@@ -499,6 +506,11 @@ __all__ = [
499
506
  "ChildRunGuard",
500
507
  "get_effective_tools",
501
508
  "ToolDefinition",
509
+ "ToolResult",
510
+ "record_billing_receipt",
511
+ "tool_billing_managed",
512
+ "tool_billing_active",
513
+ "invoke_priced_tool",
502
514
  "validate_tool_args",
503
515
  "ToolRegistry",
504
516
  "AsyncToolInSyncContext",
@@ -523,6 +535,8 @@ __all__ = [
523
535
  "build_explore_subagent_system_prompt",
524
536
  "format_tool_catalog",
525
537
  "register_spawn_agent",
538
+ "register_jobs_tools",
539
+ "JOBS_DEFINITION",
526
540
  "SPAWN_AGENT_DEFINITION",
527
541
  "LOAD_SKILL_DEFINITION",
528
542
  "SkillLoader",
@@ -634,7 +634,8 @@ class OpenAICompatibleChatLLMService(LLMService):
634
634
  current_request = request
635
635
  restart_count = 0
636
636
  max_restarts = max(0, int(self._cfg.stream_max_restarts or 0))
637
- resume_enabled = bool(self._cfg.stream_resume_on_error)
637
+ from .attempt_scope import single_attempt
638
+ resume_enabled = bool(self._cfg.stream_resume_on_error) and not single_attempt.get()
638
639
 
639
640
  def _as_dict(obj: Any) -> Any:
640
641
  if obj is None:
@@ -27,7 +27,7 @@ class AttachmentRef:
27
27
  mime_type: str
28
28
  kind: str
29
29
  #: 宿主给的「怎么把这张图找回来」坐标,原样带进蒸馏与降级文案(DeepTalk 放
30
- #: ``file_uuid=…``,模型可直接拿去 see_image)。空字符串 = 没有。
30
+ #: 宿主提供的不透明资源引用)。空字符串 = 没有。
31
31
  ref: str = ""
32
32
 
33
33
 
@@ -192,7 +192,7 @@ def _render_image_attachment(ref: AttachmentRef, path: Path, capabilities: Model
192
192
  为什么降级不等于回到老毛病:老实现塞的是一句含糊的
193
193
  "The current model does not support image input",混在附件描述里,模型读过去照样按
194
194
  「我看过这张图」的语气编答案。这里的占位必须做到两件事——**说清楚模型没有看到**,并且
195
- **给出把图找回来的坐标**(``ref``,DeepTalk 放 file_uuid,可直接喂给 see_image)。
195
+ **给出把图找回来的坐标**(``ref``,由宿主解释和回取原件)。
196
196
  调用方想要「发图给看不了图的模型就报错」,用 ``capabilities.require_image_input()`` 自查。
197
197
  """
198
198
  if capabilities.supports_image_input is True:
@@ -226,7 +226,7 @@ def _render_image_attachment(ref: AttachmentRef, path: Path, capabilities: Model
226
226
  )
227
227
  text = (
228
228
  f"[图片 {describe_attachment_ref(ref)}——**当前模型看不了图片,你没有看到它的内容**。"
229
- "不要凭空描述这张图;需要看就用 see_image 之类的视觉工具,按上面的坐标取它。]"
229
+ "不要凭空描述这张图;需要时由可看图的工具或模型按上面的坐标回取原图。]"
230
230
  )
231
231
  return PreparedAttachment(
232
232
  ref=ref,
@@ -17,6 +17,8 @@ import logging
17
17
  from typing import Any, Protocol, runtime_checkable
18
18
 
19
19
  from power_loop.agent.types import LoopMessage
20
+ from power_loop.contracts.errors import InboxClaimChanged
21
+ from power_loop.core.agent_context import get_current_loop
20
22
  from power_loop.runtime.store.store import SessionStore
21
23
  from power_loop.runtime.store.types import (
22
24
  CONTENT_ENCODING_JSON,
@@ -149,6 +151,11 @@ class SQLiteSink:
149
151
 
150
152
  # ── messages ───────────────────────────────────────────────
151
153
 
154
+ @property
155
+ def persisted_assistant_seq(self) -> int | None:
156
+ """Actual DB seq, never the index of an in-memory/projected history row."""
157
+ return self._assistant_seq
158
+
152
159
  async def on_round_started(self, round_index: int) -> None:
153
160
  await self.store.set_round_index(self.session_id, round_index)
154
161
 
@@ -171,6 +178,8 @@ class SQLiteSink:
171
178
  )
172
179
  self._history_seqs.append(seq)
173
180
  self._history_ord.append(seq)
181
+ from power_loop.runtime.task_runtime import bind_tool_receipt
182
+ await bind_tool_receipt(self.store, self.session_id, self._assistant_seq, tool_call_id, seq)
174
183
  # Auto-resolve pending: when the matching tool message lands,
175
184
  # drop it from the unresolved set and clear pending once empty.
176
185
  if tool_call_id and tool_call_id in self._unresolved:
@@ -204,6 +213,9 @@ class SQLiteSink:
204
213
  if role == "assistant":
205
214
  tool_calls = message.get("tool_calls")
206
215
  text, structured = _encode_content(message.get("content"))
216
+ from power_loop.runtime.task_runtime import atomic_pending_required
217
+ options = ({"pending_tool_calls": True}
218
+ if tool_calls and atomic_pending_required(self.store, self.session_id) else {})
207
219
  seq = await self.store.append_message(
208
220
  self.session_id,
209
221
  role="assistant",
@@ -213,6 +225,7 @@ class SQLiteSink:
213
225
  meta=_meta_with_content_encoding(message.get("meta"), structured=structured),
214
226
  send_index=message.get("send_index"),
215
227
  hook_injected=message.get("hook_injected"),
228
+ **options,
216
229
  )
217
230
  self._history_seqs.append(seq)
218
231
  self._history_ord.append(seq)
@@ -227,16 +240,26 @@ class SQLiteSink:
227
240
  # Which inbox items this row carries (dedupe/audit; design/124 §6).
228
241
  meta = {**(meta or {}), "inbox": {k: inbox[k] for k in ("item_ids", "kinds")
229
242
  if k in inbox}}
230
- seq = await self.store.append_message(
231
- self.session_id,
232
- role=str(role or "user"),
233
- content=text,
234
- name=message.get("name"),
235
- round_index=round_index,
236
- meta=meta,
237
- send_index=message.get("send_index"),
238
- inbox=inbox,
239
- )
243
+ try:
244
+ seq = await self.store.append_message(
245
+ self.session_id,
246
+ role=str(role or "user"),
247
+ content=text,
248
+ name=message.get("name"),
249
+ round_index=round_index,
250
+ meta=meta,
251
+ send_index=message.get("send_index"),
252
+ inbox=inbox,
253
+ )
254
+ except InboxClaimChanged:
255
+ # The batch was rolled back. Release only its surviving claim rows;
256
+ # the pipeline will render a fresh batch, never reuse the old text.
257
+ await self.store.inbox_release(self.session_id, inbox["claim_token"])
258
+ pending = (await self.store.inbox_counts(self.session_id))["pending"]
259
+ loop = get_current_loop()
260
+ if pending and loop is not None:
261
+ loop._note_committed_inbox(self.session_id)
262
+ raise
240
263
  self._history_seqs.append(seq)
241
264
  self._history_ord.append(seq)
242
265