power-loop 6.2.0__tar.gz → 6.3.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.
- {power_loop-6.2.0 → power_loop-6.3.0}/PKG-INFO +1 -1
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/__init__.py +1 -1
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/core/pipeline.py +12 -6
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/runtime/image_recall.py +33 -11
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop.egg-info/PKG-INFO +1 -1
- {power_loop-6.2.0 → power_loop-6.3.0}/LICENSE +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/README.md +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/_vendor/__init__.py +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/_vendor/llm_client/__init__.py +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/_vendor/llm_client/anthropic_factory.py +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/_vendor/llm_client/capabilities.py +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/_vendor/llm_client/interface.py +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/_vendor/llm_client/llm_factory.py +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/_vendor/llm_client/llm_tooling.py +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/_vendor/llm_client/llm_utils.py +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/_vendor/llm_client/multimodal.py +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/agent/__init__.py +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/agent/follow_up.py +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/agent/sink.py +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/agent/stateful_loop.py +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/agent/system_prompt.py +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/agent/types.py +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/contracts/__init__.py +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/contracts/errors.py +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/contracts/event_payloads.py +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/contracts/events.py +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/contracts/handlers.py +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/contracts/hook_contexts.py +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/contracts/hooks.py +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/contracts/messages.py +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/contracts/protocols.py +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/contracts/tools.py +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/contrib/__init__.py +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/contrib/_redact.py +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/contrib/jsonl_sink.py +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/contrib/logging_sink.py +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/contrib/mcp.py +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/contrib/metrics_sink.py +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/contrib/otel_sink.py +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/core/agent_context.py +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/core/events.py +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/core/hooks.py +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/core/phase.py +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/core/runner.py +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/core/state.py +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/py.typed +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/runtime/blackboard.py +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/runtime/budget.py +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/runtime/cancellation.py +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/runtime/compact.py +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/runtime/env.py +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/runtime/exec_backend.py +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/runtime/fold.py +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/runtime/fold_adapter.py +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/runtime/history_projector.py +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/runtime/history_sanitize.py +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/runtime/human_input.py +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/runtime/memory.py +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/runtime/notes.py +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/runtime/provider.py +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/runtime/representation.py +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/runtime/retry.py +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/runtime/runtime_state.py +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/runtime/session_store.py +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/runtime/skills.py +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/runtime/spec.py +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/runtime/store/__init__.py +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/runtime/store/backends/__init__.py +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/runtime/store/backends/mysql.py +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/runtime/store/backends/postgres.py +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/runtime/store/backends/sqlite.py +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/runtime/store/capabilities.py +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/runtime/store/db.py +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/runtime/store/dialect.py +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/runtime/store/factory.py +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/runtime/store/schema.py +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/runtime/store/store.py +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/runtime/store/types.py +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/runtime/structured.py +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/runtime/stub_provider.py +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/runtime/timers.py +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/tools/__init__.py +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/tools/blackboard.py +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/tools/command_policy.py +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/tools/default_manifest.py +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/tools/default_tools.py +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/tools/registry.py +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/tools/spawn_agent.py +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/workflow/__init__.py +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/workflow/api.py +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/workflow/engine.py +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/workflow/introspect.py +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/workflow/journal.py +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/workflow/result.py +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/workflow/resume.py +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/workflow/runner.py +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/workflow/spec.py +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/workflow/subprocess_executor.py +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/workflow/tool.py +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop/workflow/worker.py +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop.egg-info/SOURCES.txt +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop.egg-info/dependency_links.txt +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop.egg-info/requires.txt +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/power_loop.egg-info/top_level.txt +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/pyproject.toml +0 -0
- {power_loop-6.2.0 → power_loop-6.3.0}/setup.cfg +0 -0
|
@@ -15,7 +15,7 @@ Stability tiers
|
|
|
15
15
|
无版本承诺,可随时变更或删除。
|
|
16
16
|
"""
|
|
17
17
|
|
|
18
|
-
__version__ = "6.
|
|
18
|
+
__version__ = "6.3.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).
|
|
@@ -1044,12 +1044,18 @@ class AgentPipeline:
|
|
|
1044
1044
|
await self._append_message(_pm, round_index=round_idx)
|
|
1045
1045
|
llm_before.messages.append(dict(_pm))
|
|
1046
1046
|
|
|
1047
|
-
#
|
|
1048
|
-
#
|
|
1049
|
-
#
|
|
1050
|
-
#
|
|
1051
|
-
#
|
|
1052
|
-
|
|
1047
|
+
# Image recall: a tool (see_image / recall_send / an image generator) can put
|
|
1048
|
+
# pictures in front of the model. DURABLE ones become real `user` rows — the image
|
|
1049
|
+
# stays in view for the rest of the send (cheap: it sits in the provider's cached
|
|
1050
|
+
# prefix) and is distilled to `[image: … · file_uuid=…]` across sends, so nothing
|
|
1051
|
+
# accumulates without bound. EPHEMERAL ones go into this request only.
|
|
1052
|
+
# Appended AFTER the hook's own persist_messages so a tool's picture lands below
|
|
1053
|
+
# the tool result that announced it — which is exactly where the model expects it.
|
|
1054
|
+
_durable_imgs, _ephemeral_imgs = drain_queued_images(self.session_id)
|
|
1055
|
+
for _img in _durable_imgs:
|
|
1056
|
+
await self._append_message(_img, round_index=round_idx)
|
|
1057
|
+
llm_before.messages.append(dict(_img))
|
|
1058
|
+
for _img in _ephemeral_imgs:
|
|
1053
1059
|
llm_before.messages.append(_img)
|
|
1054
1060
|
|
|
1055
1061
|
if llm_before.directive == HookDirective.SHORT_CIRCUIT:
|
|
@@ -13,10 +13,16 @@ on which vendor the definition happens to point at, and definitions switch model
|
|
|
13
13
|
So the image travels as its own **user** message instead, and the tool returns only a line
|
|
14
14
|
saying it is there.
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
DURABLE by default, and that default was reversed on purpose. The first cut was ephemeral —
|
|
17
|
+
request-only, gone next round — out of a fear that "recall three times and you carry three
|
|
18
|
+
images forever". Measurement killed that fear: the provider's prefix cache serves a stable
|
|
19
|
+
history at ~99% hit rate, so an image sitting in the prefix costs about a tenth of its face
|
|
20
|
+
value every later round. Meanwhile the SEMANTICS clearly favour durable: an agent that looked
|
|
21
|
+
at a UI screenshot and then spends fifteen rounds coding against it should still have the
|
|
22
|
+
picture in front of it, not have to ask for it again. Across sends the projection distils the
|
|
23
|
+
row down to `[image: shot.png · file_uuid=…]`, so nothing accumulates without bound.
|
|
24
|
+
|
|
25
|
+
Pass ``durable=False`` for the look-once case (request-only, never stored).
|
|
20
26
|
"""
|
|
21
27
|
|
|
22
28
|
from __future__ import annotations
|
|
@@ -38,9 +44,14 @@ MAX_PENDING_PER_SESSION = 8
|
|
|
38
44
|
|
|
39
45
|
|
|
40
46
|
def queue_image_for_next_round(
|
|
41
|
-
session_id: str | None, *, path: str, note: str = "", ref: str = ""
|
|
47
|
+
session_id: str | None, *, path: str, note: str = "", ref: str = "",
|
|
48
|
+
durable: bool = True,
|
|
42
49
|
) -> bool:
|
|
43
|
-
"""Queue one image (by local path) to be shown to the model
|
|
50
|
+
"""Queue one image (by local path) to be shown to the model from the next round on.
|
|
51
|
+
|
|
52
|
+
``durable=True`` (default) stores it as a real ``user`` row, so it stays in front of the
|
|
53
|
+
model for the rest of the send and is distilled to a text reference across sends.
|
|
54
|
+
``durable=False`` puts it in the request only — visible for exactly one round.
|
|
44
55
|
|
|
45
56
|
Returns False when there is no session to queue against, or the queue is full — the caller
|
|
46
57
|
should then say so in its own return value rather than pretend the image was delivered.
|
|
@@ -62,16 +73,27 @@ def queue_image_for_next_round(
|
|
|
62
73
|
queue = _pending.setdefault(session_id, [])
|
|
63
74
|
if len(queue) >= MAX_PENDING_PER_SESSION:
|
|
64
75
|
return False
|
|
65
|
-
queue.append({"role": "user", "content": blocks})
|
|
76
|
+
queue.append({"role": "user", "content": blocks, "__durable__": durable})
|
|
66
77
|
return True
|
|
67
78
|
|
|
68
79
|
|
|
69
|
-
def drain_queued_images(session_id: str | None) -> list[LoopMessage]:
|
|
70
|
-
"""Take everything queued for this session (and clear it).
|
|
80
|
+
def drain_queued_images(session_id: str | None) -> tuple[list[LoopMessage], list[LoopMessage]]:
|
|
81
|
+
"""Take everything queued for this session (and clear it).
|
|
82
|
+
|
|
83
|
+
Returns ``(durable, ephemeral)`` — the caller persists the first group and appends the
|
|
84
|
+
second to this round's request only. The marker key is stripped so neither ever reaches
|
|
85
|
+
a provider payload.
|
|
86
|
+
"""
|
|
71
87
|
if not session_id:
|
|
72
|
-
return []
|
|
88
|
+
return [], []
|
|
73
89
|
with _lock:
|
|
74
|
-
|
|
90
|
+
queued = _pending.pop(session_id, [])
|
|
91
|
+
durable: list[LoopMessage] = []
|
|
92
|
+
ephemeral: list[LoopMessage] = []
|
|
93
|
+
for item in queued:
|
|
94
|
+
keep = bool(item.pop("__durable__", True))
|
|
95
|
+
(durable if keep else ephemeral).append(item)
|
|
96
|
+
return durable, ephemeral
|
|
75
97
|
|
|
76
98
|
|
|
77
99
|
def discard_queued_images(session_id: str | None) -> None:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|