power-loop 3.8.1__tar.gz → 3.10.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 (104) hide show
  1. {power_loop-3.8.1 → power_loop-3.10.0}/PKG-INFO +1 -1
  2. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/__init__.py +1 -1
  3. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/agent/stateful_loop.py +18 -4
  4. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/contracts/hook_contexts.py +9 -0
  5. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/core/pipeline.py +10 -0
  6. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop.egg-info/PKG-INFO +1 -1
  7. {power_loop-3.8.1 → power_loop-3.10.0}/LICENSE +0 -0
  8. {power_loop-3.8.1 → power_loop-3.10.0}/README.md +0 -0
  9. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/_vendor/__init__.py +0 -0
  10. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/_vendor/llm_client/__init__.py +0 -0
  11. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/_vendor/llm_client/anthropic_factory.py +0 -0
  12. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/_vendor/llm_client/capabilities.py +0 -0
  13. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/_vendor/llm_client/interface.py +0 -0
  14. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/_vendor/llm_client/llm_factory.py +0 -0
  15. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/_vendor/llm_client/llm_tooling.py +0 -0
  16. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/_vendor/llm_client/llm_utils.py +0 -0
  17. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/_vendor/llm_client/multimodal.py +0 -0
  18. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/agent/__init__.py +0 -0
  19. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/agent/follow_up.py +0 -0
  20. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/agent/sink.py +0 -0
  21. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/agent/system_prompt.py +0 -0
  22. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/agent/types.py +0 -0
  23. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/contracts/__init__.py +0 -0
  24. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/contracts/errors.py +0 -0
  25. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/contracts/event_payloads.py +0 -0
  26. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/contracts/events.py +0 -0
  27. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/contracts/handlers.py +0 -0
  28. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/contracts/hooks.py +0 -0
  29. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/contracts/messages.py +0 -0
  30. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/contracts/protocols.py +0 -0
  31. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/contracts/tools.py +0 -0
  32. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/contrib/__init__.py +0 -0
  33. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/contrib/_redact.py +0 -0
  34. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/contrib/jsonl_sink.py +0 -0
  35. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/contrib/logging_sink.py +0 -0
  36. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/contrib/mcp.py +0 -0
  37. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/contrib/metrics_sink.py +0 -0
  38. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/contrib/otel_sink.py +0 -0
  39. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/core/agent_context.py +0 -0
  40. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/core/events.py +0 -0
  41. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/core/hooks.py +0 -0
  42. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/core/phase.py +0 -0
  43. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/core/runner.py +0 -0
  44. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/core/state.py +0 -0
  45. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/py.typed +0 -0
  46. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/runtime/blackboard.py +0 -0
  47. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/runtime/budget.py +0 -0
  48. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/runtime/cancellation.py +0 -0
  49. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/runtime/compact.py +0 -0
  50. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/runtime/env.py +0 -0
  51. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/runtime/exec_backend.py +0 -0
  52. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/runtime/fold.py +0 -0
  53. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/runtime/fold_adapter.py +0 -0
  54. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/runtime/history_projector.py +0 -0
  55. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/runtime/history_sanitize.py +0 -0
  56. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/runtime/human_input.py +0 -0
  57. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/runtime/memory.py +0 -0
  58. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/runtime/notes.py +0 -0
  59. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/runtime/provider.py +0 -0
  60. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/runtime/representation.py +0 -0
  61. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/runtime/retry.py +0 -0
  62. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/runtime/runtime_state.py +0 -0
  63. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/runtime/session_store.py +0 -0
  64. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/runtime/skills.py +0 -0
  65. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/runtime/spec.py +0 -0
  66. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/runtime/store/__init__.py +0 -0
  67. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/runtime/store/backends/__init__.py +0 -0
  68. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/runtime/store/backends/mysql.py +0 -0
  69. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/runtime/store/backends/postgres.py +0 -0
  70. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/runtime/store/backends/sqlite.py +0 -0
  71. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/runtime/store/capabilities.py +0 -0
  72. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/runtime/store/db.py +0 -0
  73. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/runtime/store/dialect.py +0 -0
  74. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/runtime/store/factory.py +0 -0
  75. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/runtime/store/schema.py +0 -0
  76. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/runtime/store/store.py +0 -0
  77. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/runtime/store/types.py +0 -0
  78. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/runtime/structured.py +0 -0
  79. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/runtime/stub_provider.py +0 -0
  80. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/runtime/timers.py +0 -0
  81. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/tools/__init__.py +0 -0
  82. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/tools/blackboard.py +0 -0
  83. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/tools/default_manifest.py +0 -0
  84. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/tools/default_tools.py +0 -0
  85. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/tools/registry.py +0 -0
  86. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/tools/spawn_agent.py +0 -0
  87. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/workflow/__init__.py +0 -0
  88. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/workflow/api.py +0 -0
  89. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/workflow/engine.py +0 -0
  90. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/workflow/introspect.py +0 -0
  91. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/workflow/journal.py +0 -0
  92. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/workflow/result.py +0 -0
  93. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/workflow/resume.py +0 -0
  94. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/workflow/runner.py +0 -0
  95. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/workflow/spec.py +0 -0
  96. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/workflow/subprocess_executor.py +0 -0
  97. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/workflow/tool.py +0 -0
  98. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/workflow/worker.py +0 -0
  99. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop.egg-info/SOURCES.txt +0 -0
  100. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop.egg-info/dependency_links.txt +0 -0
  101. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop.egg-info/requires.txt +0 -0
  102. {power_loop-3.8.1 → power_loop-3.10.0}/power_loop.egg-info/top_level.txt +0 -0
  103. {power_loop-3.8.1 → power_loop-3.10.0}/pyproject.toml +0 -0
  104. {power_loop-3.8.1 → power_loop-3.10.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: power-loop
3
- Version: 3.8.1
3
+ Version: 3.10.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
@@ -15,7 +15,7 @@ Stability tiers
15
15
  无版本承诺,可随时变更或删除。
16
16
  """
17
17
 
18
- __version__ = "3.8.1"
18
+ __version__ = "3.10.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).
@@ -559,6 +559,7 @@ class StatefulAgentLoop:
559
559
  tools: Sequence[str] | ToolRegistry | None = None,
560
560
  system_prompt: str | None = None,
561
561
  heal_pending: bool = False,
562
+ max_rounds: int | None = None,
562
563
  ) -> StatefulResult:
563
564
  """Append one user input to the session and run the loop.
564
565
 
@@ -596,7 +597,8 @@ class StatefulAgentLoop:
596
597
  await self._raise_if_pending(sid)
597
598
  await self._persist_user_input(sid, user_input)
598
599
  return await self._run_loop(
599
- sid, stop_event=stop_event, tools=tools, system_prompt=system_prompt
600
+ sid, stop_event=stop_event, tools=tools, system_prompt=system_prompt,
601
+ max_rounds=max_rounds,
600
602
  )
601
603
 
602
604
  async def follow_up(
@@ -607,6 +609,7 @@ class StatefulAgentLoop:
607
609
  stop_event: CancellationLike = None,
608
610
  tools: Sequence[str] | ToolRegistry | None = None,
609
611
  system_prompt: str | None = None,
612
+ max_rounds: int | None = None,
610
613
  ) -> StatefulResult | FollowUpQueued:
611
614
  """Steer an in-flight loop, or fall back to :meth:`send`.
612
615
 
@@ -617,6 +620,10 @@ class StatefulAgentLoop:
617
620
  clears the drained items.
618
621
 
619
622
  When the session is idle (lock not held), behaves like :meth:`send`.
623
+
624
+ ``max_rounds`` (per-call, idle path only): run this continuation with a different round
625
+ budget than ``config.max_rounds`` — e.g. a short bounded "finalize" turn. Ignored on the
626
+ STEERED path (an in-flight loop's own budget governs the drained follow-up).
620
627
  """
621
628
  sid = session_id
622
629
  self._raise_if_closing()
@@ -627,7 +634,8 @@ class StatefulAgentLoop:
627
634
  depth = await self._enqueue_follow_up(sid, user_input)
628
635
  return FollowUpQueued(session_id=sid, queue_depth=depth)
629
636
  return await self.send(
630
- user_input, sid, stop_event=stop_event, tools=tools, system_prompt=system_prompt
637
+ user_input, sid, stop_event=stop_event, tools=tools, system_prompt=system_prompt,
638
+ max_rounds=max_rounds,
631
639
  )
632
640
 
633
641
  def _run_sync(self, coro: Coroutine[Any, Any, Any]) -> Any:
@@ -1202,6 +1210,7 @@ class StatefulAgentLoop:
1202
1210
  sink: SQLiteSink | None = None,
1203
1211
  tools: Sequence[str] | ToolRegistry | None = None,
1204
1212
  system_prompt: str | None = None,
1213
+ max_rounds: int | None = None,
1205
1214
  ) -> StatefulResult:
1206
1215
  store = await self._ensure_store()
1207
1216
  # 3.0: projection-style representation drives the derived-layer path; verbatim → None
@@ -1442,9 +1451,14 @@ class StatefulAgentLoop:
1442
1451
  effective_sp = system_prompt
1443
1452
  if effective_sp is None and session_row is not None and session_row.system_prompt:
1444
1453
  effective_sp = session_row.system_prompt
1445
- runtime_config = self.config
1454
+ # Per-call config overrides (system_prompt, max_rounds) → a per-run copy; never mutate
1455
+ # self.config (shared across concurrent sessions).
1456
+ _overrides: dict[str, Any] = {}
1446
1457
  if effective_sp is not None and effective_sp != self.config.system_prompt:
1447
- runtime_config = replace(self.config, system_prompt=effective_sp)
1458
+ _overrides["system_prompt"] = effective_sp
1459
+ if max_rounds is not None and int(max_rounds) != self.config.max_rounds:
1460
+ _overrides["max_rounds"] = max(1, int(max_rounds))
1461
+ runtime_config = replace(self.config, **_overrides) if _overrides else self.config
1448
1462
  effective_registry = self._resolve_registry(tools)
1449
1463
 
1450
1464
  async with self._runner.session_async(session_id=sid):
@@ -125,6 +125,13 @@ class LlmBeforeCtx(BaseHookCtx):
125
125
  Handler may modify any input field. ``messages`` is the fresh per-call list
126
126
  actually sent to the LLM — mutating it (e.g. appending an ephemeral memory
127
127
  block) never touches the loop's persisted history.
128
+
129
+ ``persist_messages`` is the durable counterpart: any message a handler appends
130
+ here becomes a REAL history/store row (stamped with the round's send_index, via
131
+ the same append path as the loop's own turns) AND is added to this round's
132
+ request. Use it for injected turns that must survive the send — e.g. a periodic
133
+ "you haven't called X in N rounds" reminder — as opposed to the ephemeral,
134
+ request-only edits to ``messages``. Appended in order, at the tail of the request.
128
135
  """
129
136
 
130
137
  messages: list[LoopMessage] = field(default_factory=list)
@@ -133,6 +140,8 @@ class LlmBeforeCtx(BaseHookCtx):
133
140
  max_tokens: int = 8000
134
141
  temperature: float = 0.0
135
142
  session_id: str | None = None
143
+ # Durable injections: persisted as real turns after LLM_BEFORE, then added to the request.
144
+ persist_messages: list[LoopMessage] = field(default_factory=list)
136
145
  # Handler output (for SHORT_CIRCUIT)
137
146
  output: LLMResponse | None = None
138
147
 
@@ -944,6 +944,16 @@ class AgentPipeline:
944
944
  llm_before.messages, _pre_hook_ids, _hook_audit_mode
945
945
  )
946
946
 
947
+ # Durable LLM_BEFORE injections (persist_messages): unlike the ephemeral, request-only
948
+ # edits to `messages` (captured by hook_audit above), each of these becomes a REAL turn —
949
+ # persisted to history + store with the round's send_index via the loop's own append path
950
+ # — and is added to this round's request tail. Used for injected turns that must survive
951
+ # the send (e.g. a periodic "you haven't called X in N rounds" reminder). Computed AFTER
952
+ # hook_audit so these durable rows don't get counted as ephemeral injections.
953
+ for _pm in llm_before.persist_messages:
954
+ await self._append_message(_pm, round_index=round_idx)
955
+ llm_before.messages.append(dict(_pm))
956
+
947
957
  if llm_before.directive == HookDirective.SHORT_CIRCUIT:
948
958
  response = llm_before.output
949
959
  if not isinstance(response, LLMResponse):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: power-loop
3
- Version: 3.8.1
3
+ Version: 3.10.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
File without changes
File without changes
File without changes
File without changes