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.
- {power_loop-3.8.1 → power_loop-3.10.0}/PKG-INFO +1 -1
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/__init__.py +1 -1
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/agent/stateful_loop.py +18 -4
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/contracts/hook_contexts.py +9 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/core/pipeline.py +10 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop.egg-info/PKG-INFO +1 -1
- {power_loop-3.8.1 → power_loop-3.10.0}/LICENSE +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/README.md +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/_vendor/__init__.py +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/_vendor/llm_client/__init__.py +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/_vendor/llm_client/anthropic_factory.py +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/_vendor/llm_client/capabilities.py +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/_vendor/llm_client/interface.py +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/_vendor/llm_client/llm_factory.py +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/_vendor/llm_client/llm_tooling.py +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/_vendor/llm_client/llm_utils.py +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/_vendor/llm_client/multimodal.py +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/agent/__init__.py +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/agent/follow_up.py +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/agent/sink.py +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/agent/system_prompt.py +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/agent/types.py +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/contracts/__init__.py +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/contracts/errors.py +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/contracts/event_payloads.py +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/contracts/events.py +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/contracts/handlers.py +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/contracts/hooks.py +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/contracts/messages.py +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/contracts/protocols.py +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/contracts/tools.py +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/contrib/__init__.py +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/contrib/_redact.py +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/contrib/jsonl_sink.py +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/contrib/logging_sink.py +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/contrib/mcp.py +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/contrib/metrics_sink.py +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/contrib/otel_sink.py +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/core/agent_context.py +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/core/events.py +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/core/hooks.py +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/core/phase.py +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/core/runner.py +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/core/state.py +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/py.typed +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/runtime/blackboard.py +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/runtime/budget.py +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/runtime/cancellation.py +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/runtime/compact.py +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/runtime/env.py +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/runtime/exec_backend.py +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/runtime/fold.py +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/runtime/fold_adapter.py +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/runtime/history_projector.py +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/runtime/history_sanitize.py +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/runtime/human_input.py +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/runtime/memory.py +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/runtime/notes.py +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/runtime/provider.py +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/runtime/representation.py +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/runtime/retry.py +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/runtime/runtime_state.py +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/runtime/session_store.py +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/runtime/skills.py +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/runtime/spec.py +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/runtime/store/__init__.py +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/runtime/store/backends/__init__.py +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/runtime/store/backends/mysql.py +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/runtime/store/backends/postgres.py +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/runtime/store/backends/sqlite.py +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/runtime/store/capabilities.py +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/runtime/store/db.py +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/runtime/store/dialect.py +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/runtime/store/factory.py +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/runtime/store/schema.py +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/runtime/store/store.py +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/runtime/store/types.py +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/runtime/structured.py +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/runtime/stub_provider.py +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/runtime/timers.py +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/tools/__init__.py +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/tools/blackboard.py +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/tools/default_manifest.py +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/tools/default_tools.py +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/tools/registry.py +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/tools/spawn_agent.py +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/workflow/__init__.py +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/workflow/api.py +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/workflow/engine.py +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/workflow/introspect.py +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/workflow/journal.py +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/workflow/result.py +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/workflow/resume.py +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/workflow/runner.py +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/workflow/spec.py +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/workflow/subprocess_executor.py +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/workflow/tool.py +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop/workflow/worker.py +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop.egg-info/SOURCES.txt +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop.egg-info/dependency_links.txt +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop.egg-info/requires.txt +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/power_loop.egg-info/top_level.txt +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/pyproject.toml +0 -0
- {power_loop-3.8.1 → power_loop-3.10.0}/setup.cfg +0 -0
|
@@ -15,7 +15,7 @@ Stability tiers
|
|
|
15
15
|
无版本承诺,可随时变更或删除。
|
|
16
16
|
"""
|
|
17
17
|
|
|
18
|
-
__version__ = "3.
|
|
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
|
-
|
|
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
|
-
|
|
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):
|
|
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
|