power-loop 3.5.0__tar.gz → 3.7.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.5.0 → power_loop-3.7.0}/PKG-INFO +1 -1
  2. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop/__init__.py +1 -1
  3. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop/runtime/history_projector.py +5 -4
  4. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop/runtime/representation.py +6 -4
  5. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop/tools/default_manifest.py +23 -8
  6. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop/tools/default_tools.py +18 -0
  7. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop.egg-info/PKG-INFO +1 -1
  8. {power_loop-3.5.0 → power_loop-3.7.0}/LICENSE +0 -0
  9. {power_loop-3.5.0 → power_loop-3.7.0}/README.md +0 -0
  10. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop/_vendor/__init__.py +0 -0
  11. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop/_vendor/llm_client/__init__.py +0 -0
  12. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop/_vendor/llm_client/anthropic_factory.py +0 -0
  13. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop/_vendor/llm_client/capabilities.py +0 -0
  14. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop/_vendor/llm_client/interface.py +0 -0
  15. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop/_vendor/llm_client/llm_factory.py +0 -0
  16. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop/_vendor/llm_client/llm_tooling.py +0 -0
  17. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop/_vendor/llm_client/llm_utils.py +0 -0
  18. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop/_vendor/llm_client/multimodal.py +0 -0
  19. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop/agent/__init__.py +0 -0
  20. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop/agent/follow_up.py +0 -0
  21. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop/agent/sink.py +0 -0
  22. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop/agent/stateful_loop.py +0 -0
  23. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop/agent/system_prompt.py +0 -0
  24. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop/agent/types.py +0 -0
  25. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop/contracts/__init__.py +0 -0
  26. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop/contracts/errors.py +0 -0
  27. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop/contracts/event_payloads.py +0 -0
  28. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop/contracts/events.py +0 -0
  29. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop/contracts/handlers.py +0 -0
  30. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop/contracts/hook_contexts.py +0 -0
  31. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop/contracts/hooks.py +0 -0
  32. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop/contracts/messages.py +0 -0
  33. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop/contracts/protocols.py +0 -0
  34. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop/contracts/tools.py +0 -0
  35. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop/contrib/__init__.py +0 -0
  36. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop/contrib/_redact.py +0 -0
  37. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop/contrib/jsonl_sink.py +0 -0
  38. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop/contrib/logging_sink.py +0 -0
  39. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop/contrib/mcp.py +0 -0
  40. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop/contrib/metrics_sink.py +0 -0
  41. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop/contrib/otel_sink.py +0 -0
  42. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop/core/agent_context.py +0 -0
  43. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop/core/events.py +0 -0
  44. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop/core/hooks.py +0 -0
  45. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop/core/phase.py +0 -0
  46. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop/core/pipeline.py +0 -0
  47. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop/core/runner.py +0 -0
  48. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop/core/state.py +0 -0
  49. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop/py.typed +0 -0
  50. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop/runtime/blackboard.py +0 -0
  51. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop/runtime/budget.py +0 -0
  52. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop/runtime/cancellation.py +0 -0
  53. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop/runtime/compact.py +0 -0
  54. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop/runtime/env.py +0 -0
  55. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop/runtime/exec_backend.py +0 -0
  56. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop/runtime/fold.py +0 -0
  57. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop/runtime/fold_adapter.py +0 -0
  58. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop/runtime/history_sanitize.py +0 -0
  59. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop/runtime/human_input.py +0 -0
  60. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop/runtime/memory.py +0 -0
  61. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop/runtime/notes.py +0 -0
  62. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop/runtime/provider.py +0 -0
  63. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop/runtime/retry.py +0 -0
  64. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop/runtime/runtime_state.py +0 -0
  65. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop/runtime/session_store.py +0 -0
  66. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop/runtime/skills.py +0 -0
  67. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop/runtime/spec.py +0 -0
  68. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop/runtime/store/__init__.py +0 -0
  69. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop/runtime/store/backends/__init__.py +0 -0
  70. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop/runtime/store/backends/mysql.py +0 -0
  71. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop/runtime/store/backends/postgres.py +0 -0
  72. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop/runtime/store/backends/sqlite.py +0 -0
  73. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop/runtime/store/capabilities.py +0 -0
  74. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop/runtime/store/db.py +0 -0
  75. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop/runtime/store/dialect.py +0 -0
  76. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop/runtime/store/factory.py +0 -0
  77. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop/runtime/store/schema.py +0 -0
  78. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop/runtime/store/store.py +0 -0
  79. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop/runtime/store/types.py +0 -0
  80. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop/runtime/structured.py +0 -0
  81. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop/runtime/stub_provider.py +0 -0
  82. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop/runtime/timers.py +0 -0
  83. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop/tools/__init__.py +0 -0
  84. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop/tools/blackboard.py +0 -0
  85. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop/tools/registry.py +0 -0
  86. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop/tools/spawn_agent.py +0 -0
  87. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop/workflow/__init__.py +0 -0
  88. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop/workflow/api.py +0 -0
  89. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop/workflow/engine.py +0 -0
  90. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop/workflow/introspect.py +0 -0
  91. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop/workflow/journal.py +0 -0
  92. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop/workflow/result.py +0 -0
  93. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop/workflow/resume.py +0 -0
  94. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop/workflow/runner.py +0 -0
  95. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop/workflow/spec.py +0 -0
  96. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop/workflow/subprocess_executor.py +0 -0
  97. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop/workflow/tool.py +0 -0
  98. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop/workflow/worker.py +0 -0
  99. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop.egg-info/SOURCES.txt +0 -0
  100. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop.egg-info/dependency_links.txt +0 -0
  101. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop.egg-info/requires.txt +0 -0
  102. {power_loop-3.5.0 → power_loop-3.7.0}/power_loop.egg-info/top_level.txt +0 -0
  103. {power_loop-3.5.0 → power_loop-3.7.0}/pyproject.toml +0 -0
  104. {power_loop-3.5.0 → power_loop-3.7.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: power-loop
3
- Version: 3.5.0
3
+ Version: 3.7.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.5.0"
18
+ __version__ = "3.7.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).
@@ -117,7 +117,9 @@ class HistoryProjector(Protocol):
117
117
 
118
118
  def _truncate(s: Any, max_chars: int) -> str:
119
119
  t = "" if s is None else str(s)
120
- return t if len(t) <= max_chars else t[:max_chars] + "…"
120
+ if max_chars <= 0 or len(t) <= max_chars: # max_chars <= 0 → no truncation (unlimited)
121
+ return t
122
+ return t[:max_chars] + "…"
121
123
 
122
124
 
123
125
  def _parse_args(raw: Any) -> Any:
@@ -225,7 +227,7 @@ class DefaultDeterministicProjector:
225
227
  terse plain text with NO tool-protocol structure."""
226
228
 
227
229
  version: int = 1
228
- max_chars: int = 300 # per-field truncation budget
230
+ max_chars: int = 300 # per-field truncation budget; <= 0 = no truncation (unlimited)
229
231
  keep_last_sends: int = 4 # most-recent sends ALWAYS kept individually (never folded)
230
232
  #: Fold older sends into a compact row once the rendered projected prefix reaches
231
233
  #: ``loop max_tokens × trigger_ratio`` (mirrors DefaultCompactor's policy) — token-driven,
@@ -244,8 +246,7 @@ class DefaultDeterministicProjector:
244
246
  keep_last_sends=self.keep_last_sends,
245
247
  trigger_ratio=self.trigger_ratio,
246
248
  )
247
- if self.max_chars <= 0:
248
- raise ValueError(f"max_chars must be > 0; got {self.max_chars!r}")
249
+ # max_chars <= 0 → no truncation (unlimited); any int is accepted.
249
250
  if self.max_compact_chars < 0:
250
251
  raise ValueError(
251
252
  f"max_compact_chars must be >= 0 (0 = unbounded); got {self.max_compact_chars!r}"
@@ -93,7 +93,9 @@ class Representation(Protocol):
93
93
 
94
94
  def _truncate(s: Any, max_chars: int) -> str:
95
95
  t = "" if s is None else str(s)
96
- return t if len(t) <= max_chars else t[:max_chars] + "…"
96
+ if max_chars <= 0 or len(t) <= max_chars: # max_chars <= 0 → no truncation (unlimited)
97
+ return t
98
+ return t[:max_chars] + "…"
97
99
 
98
100
 
99
101
  def _parse_args(raw: Any) -> Any:
@@ -143,8 +145,8 @@ def _validate_representation_params(*, version: int, max_chars: int | None = Non
143
145
  raise ValueError(
144
146
  f"representation version must be >= 1 (the compatibility key); got {version!r}"
145
147
  )
146
- if max_chars is not None and max_chars <= 0:
147
- raise ValueError(f"max_chars must be > 0; got {max_chars!r}")
148
+ # max_chars <= 0 is now VALID and means "no truncation" (unlimited) — let a host disable the
149
+ # library's per-field cap and do its own limiting. Any int is accepted.
148
150
 
149
151
 
150
152
  def _render_compact_row(row: ProjectMessageRow) -> LoopMessage:
@@ -249,7 +251,7 @@ class ProjectedRepresentation:
249
251
 
250
252
  kind: str = "projection"
251
253
  version: int = 1
252
- max_chars: int = 300 # per-field truncation budget
254
+ max_chars: int = 300 # per-field truncation budget; <= 0 = no truncation (unlimited)
253
255
  #: Format knobs for render() — see :class:`ProjectionRenderConfig`. A plain dict is coerced (so a
254
256
  #: host can pass JSON config straight through). Subclasses can ALSO override the render_* methods.
255
257
  render_config: ProjectionRenderConfig = field(default_factory=ProjectionRenderConfig)
@@ -188,11 +188,11 @@ DEFAULT_TOOL_DEFINITIONS: list[ToolDefinition] = [
188
188
  ToolDefinition(
189
189
  name="note_add",
190
190
  description=(
191
- "Save a short persistent note to your own memory. Notes survive context compaction and are "
192
- "shown back to you at the start of every turn. Use for durable facts, preferences, ongoing "
193
- "task state not for transcripts or long content (put those in files). When notes are full "
194
- "you must delete or merge old ones first. Set pinned=true for notes that must never be "
195
- "hidden or auto-evicted."
191
+ "Save a short persistent note to your own memory. Notes survive context compaction; read "
192
+ "them back with note_list (and, when the memory-recall hook is enabled, they are also shown "
193
+ "at the start of each turn). Use for durable facts, preferences, ongoing task state not "
194
+ "for transcripts or long content (put those in files). When notes are full you must delete "
195
+ "or merge old ones first. Set pinned=true for notes that must never be hidden or auto-evicted."
196
196
  ),
197
197
  input_schema={
198
198
  "type": "object",
@@ -213,7 +213,7 @@ DEFAULT_TOOL_DEFINITIONS: list[ToolDefinition] = [
213
213
  input_schema={
214
214
  "type": "object",
215
215
  "properties": {
216
- "note_id": {"type": "integer", "description": "The #id shown in YOUR NOTES."},
216
+ "note_id": {"type": "integer", "description": "The #id from note_list (or YOUR NOTES)."},
217
217
  "content": {"type": "string", "description": "New text replacing the old content."},
218
218
  "pinned": {"type": "boolean", "description": "Set or clear the pinned flag."},
219
219
  },
@@ -223,16 +223,31 @@ DEFAULT_TOOL_DEFINITIONS: list[ToolDefinition] = [
223
223
  ),
224
224
  ToolDefinition(
225
225
  name="note_delete",
226
- description="Delete one of your notes by its #id when it is stale or no longer worth remembering.",
226
+ description=(
227
+ "Delete one of your notes by its #id (from note_list) when it is stale or no longer "
228
+ "worth remembering."
229
+ ),
227
230
  input_schema={
228
231
  "type": "object",
229
232
  "properties": {
230
- "note_id": {"type": "integer", "description": "The #id shown in YOUR NOTES."},
233
+ "note_id": {"type": "integer", "description": "The #id from note_list (or YOUR NOTES)."},
231
234
  },
232
235
  "required": ["note_id"],
233
236
  },
234
237
  required_params=("note_id",),
235
238
  ),
239
+ ToolDefinition(
240
+ name="note_list",
241
+ description=(
242
+ "List your persistent notes — each note's #id, pinned flag, and content — so you can "
243
+ "read back what you remember and get the #id needed to note_update / note_delete. These "
244
+ "are the same notes you maintain with note_add / note_update / note_delete; they survive "
245
+ "context compaction. Call this when you want to check your memory (e.g. at the start of a "
246
+ "task) rather than relying on it being auto-shown."
247
+ ),
248
+ input_schema={"type": "object", "properties": {}},
249
+ required_params=(),
250
+ ),
236
251
  ToolDefinition(
237
252
  name="schedule_wakeup",
238
253
  description=(
@@ -1471,6 +1471,19 @@ async def run_note_delete(note_id: int) -> str:
1471
1471
  return f"note #{note_id} deleted ({await store.count_notes(sid)} remaining)"
1472
1472
 
1473
1473
 
1474
+ async def run_note_list() -> str:
1475
+ """Read back the agent's persistent notes (rendered with #id, pinned flag, content), so the
1476
+ agent can inspect its memory and get the #ids for note_update / note_delete WITHOUT relying on
1477
+ the (optional) memory-recall hook auto-injecting them."""
1478
+ from power_loop.runtime.notes import render_notes
1479
+
1480
+ store, sid = _notes_store_and_session()
1481
+ notes = await store.list_notes(sid)
1482
+ if not notes:
1483
+ return "(no notes yet — use note_add to remember durable facts, preferences, or task state)"
1484
+ return render_notes(notes, policy=_notes_policy())
1485
+
1486
+
1474
1487
  def _timers_store_and_session() -> tuple[Any, str]:
1475
1488
  store, sid = _current_store_and_session()
1476
1489
  if store is None or sid is None:
@@ -1684,6 +1697,10 @@ async def _h_note_delete(**kw: Any) -> Any:
1684
1697
  return await run_note_delete(kw["note_id"])
1685
1698
 
1686
1699
 
1700
+ async def _h_note_list(**kw: Any) -> Any:
1701
+ return await run_note_list()
1702
+
1703
+
1687
1704
  async def _h_schedule_wakeup(**kw: Any) -> Any:
1688
1705
  return await run_schedule_wakeup(kw["delay_seconds"], kw["note"], kw.get("every_seconds"))
1689
1706
 
@@ -1740,6 +1757,7 @@ DEFAULT_TOOL_HANDLERS: dict[str, Any] = {
1740
1757
  "note_add": _h_note_add,
1741
1758
  "note_update": _h_note_update,
1742
1759
  "note_delete": _h_note_delete,
1760
+ "note_list": _h_note_list,
1743
1761
  "schedule_wakeup": _h_schedule_wakeup,
1744
1762
  "list_wakeups": _h_list_wakeups,
1745
1763
  "cancel_wakeup": _h_cancel_wakeup,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: power-loop
3
- Version: 3.5.0
3
+ Version: 3.7.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