penguiflow 2.0.0__py3-none-any.whl → 2.2.0__py3-none-any.whl

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.

Potentially problematic release.


This version of penguiflow might be problematic. Click here for more details.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: penguiflow
3
- Version: 2.0.0
3
+ Version: 2.2.0
4
4
  Summary: Async agent orchestration primitives.
5
5
  Author: PenguiFlow Team
6
6
  License: MIT License
@@ -36,7 +36,14 @@ Requires-Dist: pytest>=7.4; extra == "dev"
36
36
  Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
37
37
  Requires-Dist: pytest-cov>=4.0; extra == "dev"
38
38
  Requires-Dist: coverage[toml]>=7.0; extra == "dev"
39
+ Requires-Dist: hypothesis>=6.103; extra == "dev"
39
40
  Requires-Dist: ruff>=0.2; extra == "dev"
41
+ Requires-Dist: fastapi>=0.118; extra == "dev"
42
+ Requires-Dist: httpx>=0.27; extra == "dev"
43
+ Provides-Extra: a2a-server
44
+ Requires-Dist: fastapi>=0.118; extra == "a2a-server"
45
+ Provides-Extra: planner
46
+ Requires-Dist: litellm>=1.77.3; extra == "planner"
40
47
  Dynamic: license-file
41
48
 
42
49
  # PenguiFlow 🐧❄️
@@ -52,6 +59,9 @@ Dynamic: license-file
52
59
  <a href="https://github.com/penguiflow/penguiflow">
53
60
  <img src="https://img.shields.io/badge/coverage-85%25-brightgreen" alt="Coverage">
54
61
  </a>
62
+ <a href="https://nightly.link/penguiflow/penguiflow/workflows/benchmarks/main/benchmarks.json.zip">
63
+ <img src="https://img.shields.io/badge/benchmarks-latest-orange" alt="Benchmarks">
64
+ </a>
55
65
  <a href="https://pypi.org/project/penguiflow/">
56
66
  <img src="https://img.shields.io/pypi/v/penguiflow.svg" alt="PyPI version">
57
67
  </a>
@@ -77,10 +87,39 @@ It provides:
77
87
  * **Observability hooks** (`FlowEvent` callbacks for logging, MLflow, or custom metrics sinks)
78
88
  * **Policy-driven routing** (optional policies steer routers without breaking existing flows)
79
89
  * **Traceable exceptions** (`FlowError` captures node/trace metadata and optionally emits to Rookery)
90
+ * **Distribution hooks (opt-in)** — plug a `StateStore` to persist trace history and a
91
+ `MessageBus` to publish floe traffic for remote workers without changing existing flows.
92
+ * **Remote calls (opt-in)** — `RemoteNode` bridges the runtime to external agents through a
93
+ pluggable `RemoteTransport` interface (A2A-ready) while propagating streaming chunks and
94
+ cancellation.
95
+ * **A2A server adapter (opt-in)** — wrap a PenguiFlow graph in a FastAPI surface using
96
+ `penguiflow_a2a.A2AServerAdapter` so other agents can call `message/send`,
97
+ `message/stream`, and `tasks/cancel` while reusing the runtime's backpressure and
98
+ cancellation semantics.
99
+ * **Observability & ops polish** — remote calls emit structured metrics (latency, payload
100
+ sizes, cancel reasons) and the `penguiflow-admin` CLI replays trace history from any
101
+ configured `StateStore` for debugging.
80
102
 
81
103
  Built on pure `asyncio` (no threads), PenguiFlow is small, predictable, and repo-agnostic.
82
104
  Product repos only define **their models + node functions** — the core stays dependency-light.
83
105
 
106
+ ## Gold Standard Scorecard
107
+
108
+ | Area | Metric | Target | Current |
109
+ | --- | --- | --- | --- |
110
+ | Hop overhead | µs per hop | ≤ 500 | 398 |
111
+ | Streaming order | gaps/dupes | 0 | 0 |
112
+ | Cancel leakage | orphan tasks | 0 | 0 |
113
+ | Coverage | lines | ≥85% | 87% |
114
+ | Deps | count | ≤2 | 2 |
115
+ | Import time | ms | ≤220 | 203 |
116
+
117
+ ## 📑 Core Behavior Spec
118
+
119
+ * [Core Behavior Spec](docs/core_behavior_spec.md) — single-page rundown of ordering,
120
+ streaming, cancellation, deadline, and fan-in invariants with pointers to regression
121
+ tests.
122
+
84
123
  ---
85
124
 
86
125
  ## ✨ Why PenguiFlow?
@@ -168,6 +207,10 @@ print(out.payload) # PackOut(...)
168
207
  await flow.stop()
169
208
  ```
170
209
 
210
+ > **Opt-in distribution:** pass `state_store=` and/or `message_bus=` when calling
211
+ > `penguiflow.core.create(...)` to persist trace history and publish floe traffic
212
+ > without changing node logic.
213
+
171
214
  ---
172
215
 
173
216
  ## 🧭 Design Principles
@@ -222,6 +265,60 @@ sacrificing backpressure or ordering guarantees. The helper wraps the payload i
222
265
  increments per-stream sequence numbers. See `tests/test_streaming.py` and
223
266
  `examples/streaming_llm/` for an end-to-end walk-through.
224
267
 
268
+ ### Remote orchestration
269
+
270
+ Phase 2 introduces `RemoteNode` and the `RemoteTransport` protocol so flows can delegate
271
+ work to remote agents (e.g., the A2A JSON-RPC/SSE ecosystem) without changing existing
272
+ nodes. The helper records remote bindings via the `StateStore`, mirrors streaming
273
+ partials back into the graph, and propagates per-trace cancellation to remote tasks via
274
+ `RemoteTransport.cancel`. See `tests/test_remote.py` for reference in-memory transports.
275
+
276
+ ### Exposing a flow over A2A
277
+
278
+ Install the optional extra to expose PenguiFlow as an A2A-compatible FastAPI service:
279
+
280
+ ```bash
281
+ pip install "penguiflow[a2a-server]"
282
+ ```
283
+
284
+ Create the adapter and mount the routes:
285
+
286
+ ```python
287
+ from penguiflow import Message, Node, create
288
+ from penguiflow_a2a import A2AAgentCard, A2AServerAdapter, A2ASkill, create_a2a_app
289
+
290
+ async def orchestrate(message: Message, ctx):
291
+ await ctx.emit_chunk(parent=message, text="thinking...")
292
+ return {"result": "done"}
293
+
294
+ node = Node(orchestrate, name="main")
295
+ flow = create(node.to())
296
+
297
+ card = A2AAgentCard(
298
+ name="Main Agent",
299
+ description="Primary entrypoint for orchestration",
300
+ version="2.1.0",
301
+ skills=[A2ASkill(name="orchestrate", description="Handles orchestration")],
302
+ )
303
+
304
+ adapter = A2AServerAdapter(
305
+ flow,
306
+ agent_card=card,
307
+ agent_url="https://agent.example",
308
+ )
309
+ app = create_a2a_app(adapter)
310
+ ```
311
+
312
+ The generated FastAPI app implements:
313
+
314
+ * `GET /agent` for discovery (Agent Card)
315
+ * `POST /message/send` for unary execution
316
+ * `POST /message/stream` for SSE streaming
317
+ * `POST /tasks/cancel` to mirror cancellation into PenguiFlow traces
318
+
319
+ `A2AServerAdapter` reuses the runtime's `StateStore` hooks, so bindings between trace IDs
320
+ and external `taskId`/`contextId` pairs are persisted automatically.
321
+
225
322
  ### Reliability & guardrails
226
323
 
227
324
  PenguiFlow enforces reliability boundaries out of the box:
@@ -272,6 +369,70 @@ The new `penguiflow.testkit` module keeps unit tests tiny:
272
369
  The harness is covered by `tests/test_testkit.py` and demonstrated in
273
370
  `examples/testkit_demo/`.
274
371
 
372
+ ### JSON-only ReAct planner (Phase A)
373
+
374
+ Phase A introduces a lightweight planner loop that keeps PenguiFlow typed and
375
+ deterministic:
376
+
377
+ * `penguiflow.catalog.NodeSpec` + `build_catalog` turn registered nodes into
378
+ tool descriptors with JSON Schemas derived from your Pydantic models.
379
+ * `penguiflow.planner.ReactPlanner` drives a JSON-only ReAct loop over those
380
+ descriptors, validating every LLM action with Pydantic and replaying invalid
381
+ steps to request corrections.
382
+ * LiteLLM stays optional—install `penguiflow[planner]` or inject a custom
383
+ `llm_client` for deterministic/offline runs.
384
+
385
+ See `examples/react_minimal/` for a stubbed end-to-end run.
386
+
387
+ ### Trajectory summarisation & pause/resume (Phase B)
388
+
389
+ Phase B adds the tools you need for longer-running, approval-driven flows:
390
+
391
+ * **Token-aware summaries** — `Trajectory.compress()` keeps a compact state and
392
+ the planner can route summaries through a cheaper `summarizer_llm` before
393
+ asking for the next action.
394
+ * **`PlannerPause` contract** — nodes can call `await ctx.pause(...)` to return a
395
+ typed pause payload. Resume the run later with `ReactPlanner.resume(token, user_input=...)`.
396
+ * **Developer hints** — pass `planning_hints={...}` to enforce disallowed tools,
397
+ preferred ordering, or parallelism ceilings.
398
+
399
+ All three features are exercised in `examples/react_pause_resume/`, which runs
400
+ entirely offline with stubbed LLM responses.
401
+
402
+ ### Adaptive re-planning & budgets (Phase C)
403
+
404
+ Phase C closes the loop when things go sideways:
405
+
406
+ * **Structured failure feedback** — if a tool raises after exhausting its retries,
407
+ the planner records `{failure: {node, args, error_code, suggestion}}` and feeds
408
+ it back to the LLM, prompting a constrained re-plan instead of aborting.
409
+ * **Hard guardrails** — configure wall-clock deadlines and hop budgets directly
410
+ on `ReactPlanner`; attempts beyond the allotted hops surface deterministic
411
+ violations and ultimately finish with `reason="budget_exhausted"` alongside a
412
+ constraint snapshot.
413
+ * **Typed exit reasons** — runs now finish with one of
414
+ `answer_complete`, `no_path`, or `budget_exhausted`, keeping downstream code
415
+ simple and machine-checkable.
416
+
417
+ The new `examples/react_replan/` sample shows a retrieval timeout automatically
418
+ recover via a cached index without leaving the JSON-only contract.
419
+
420
+ ### Parallel fan-out & joins (Phase D)
421
+
422
+ Phase D lets the planner propose sets of independent tool calls and join them
423
+ without leaving the typed surface area:
424
+
425
+ * **Parallel `plan` blocks** — the LLM can return `{"plan": [...]}` actions
426
+ where each branch is validated against the catalog and executed concurrently.
427
+ * **Typed joins** — provide a `{"join": {"node": ...}}` descriptor and the
428
+ planner will aggregate results, auto-populate fields like `expect`, `results`,
429
+ or `failures`, and feed branch metadata through `ctx.meta` for the join node.
430
+ * **Deterministic telemetry** — branch errors, pauses, and joins are recorded as
431
+ structured observations so follow-up actions can re-plan or finish cleanly.
432
+
433
+ See `examples/react_parallel/` for a shard fan-out that merges responses in one
434
+ round-trip.
435
+
275
436
 
276
437
  ## 🧭 Repo Structure
277
438
 
@@ -478,9 +639,15 @@ docs or diagramming pipelines.
478
639
  * **Structured `FlowEvent`s**: every node event carries `{ts, trace_id, node_name, event,
479
640
  latency_ms, q_depth_in, q_depth_out, attempt}` plus a mutable `extra` map for custom
480
641
  annotations.
642
+ * **Remote call telemetry**: `RemoteNode` executions emit extra metrics (latency, request
643
+ and response bytes, context/task identifiers, cancel reasons) so remote hops can be
644
+ traced end-to-end.
481
645
  * **Middleware hooks**: subscribe observers (e.g., MLflow) to the structured `FlowEvent`
482
646
  stream. See `examples/mlflow_metrics/` for an MLflow integration and
483
647
  `examples/reliability_middleware/` for a concrete timeout + retry walkthrough.
648
+ * **`penguiflow-admin` CLI**: inspect or replay stored trace history from any configured
649
+ `StateStore` (`penguiflow-admin history <trace>` or `penguiflow-admin replay <trace>`)
650
+ when debugging distributed runs.
484
651
 
485
652
  ---
486
653
 
@@ -488,9 +655,9 @@ docs or diagramming pipelines.
488
655
 
489
656
  - **In-process runtime**: there is no built-in distribution layer yet. Long-running CPU work should be delegated to your own pools or services.
490
657
  - **Registry-driven typing**: nodes default to validation. Provide a `ModelRegistry` when calling `flow.run(...)` or set `validate="none"` explicitly for untyped hops.
491
- - **Observability**: structured `FlowEvent` callbacks power logs/metrics; integrations with
492
- third-party stacks (OTel, Prometheus, Datadog) remain DIY. See the MLflow middleware
493
- example for a lightweight pattern.
658
+ - **Observability**: structured `FlowEvent` callbacks and the `penguiflow-admin` CLI power
659
+ local debugging; integrations with third-party stacks (OTel, Prometheus, Datadog) remain
660
+ DIY. See the MLflow middleware example for a lightweight pattern.
494
661
  - **Roadmap**: follow-up releases focus on optional distributed backends, deeper observability integrations, and additional playbook patterns. Contributions and proposals are welcome!
495
662
 
496
663
  ---
@@ -546,6 +713,8 @@ pytest -q
546
713
  * `examples/streaming_llm/`: mock LLM emitting streaming chunks to an SSE sink.
547
714
  * `examples/metadata_propagation/`: attaching and consuming `Message.meta` context.
548
715
  * `examples/visualizer/`: exports Mermaid + DOT diagrams with loop/subflow annotations.
716
+ * `examples/react_minimal/`: JSON-only ReactPlanner loop with a stubbed LLM.
717
+ * `examples/react_pause_resume/`: Phase B planner features with pause/resume and developer hints.
549
718
 
550
719
  ---
551
720
 
@@ -0,0 +1,27 @@
1
+ penguiflow/__init__.py,sha256=Hsoc0UtuilT77kVd0VKPlgnliaRlHgkA3XYd_PvQOYw,2435
2
+ penguiflow/admin.py,sha256=093xFkE4bM_2ZhLrzhrEUKtmKHi_yVfMPyaGfwi1rcA,5382
3
+ penguiflow/bus.py,sha256=mb29509_n97A6zwC-6EDpYorfAWFSpwqsMu_WeZhLE8,732
4
+ penguiflow/catalog.py,sha256=z-Drf6PbEkvd65PcBvsVJZBBnM9GwT8ctcMdiIoQ5HY,4673
5
+ penguiflow/core.py,sha256=7w3fbyfQspt0aRt5nfDcr2kzPzX7Tf7O83v-U64DfHI,53960
6
+ penguiflow/debug.py,sha256=KPdpWbascsi1ghu-2HPqRORPM2iqkuV6qWyPc0mAalY,945
7
+ penguiflow/errors.py,sha256=mXpCqZ3zdvz7J7Dck_kcw2BGTIm9yrJAjxp_L8KMY7o,3419
8
+ penguiflow/metrics.py,sha256=KsxH9tUqrYfs3EyccLcM0-haYySAByq7RMnK7q61eRA,3989
9
+ penguiflow/middlewares.py,sha256=cB4SrRciNcKHyLanaMVsfMElt3El0LNCj_3dyik07x4,2864
10
+ penguiflow/node.py,sha256=0NOs3rU6t1tHNNwwJopqzM2ufGcp82JpzhckynWBRqs,3563
11
+ penguiflow/patterns.py,sha256=qtzRSNRKxV5_qEPXhffd15PuCZs0YnoGF80nNUsrcxw,5512
12
+ penguiflow/policies.py,sha256=3w8ionnpTyuA0ZCc3jPpB011L7_i1qlbiO6escY024s,4385
13
+ penguiflow/registry.py,sha256=1nR3J1A6jzuevH8EMn83vCkSnnNKgE28CCO6fXMA3wE,2001
14
+ penguiflow/remote.py,sha256=0-2aW48P8OB8KLEC_7_F_RHtzVJk3huyAMBGdXjmWeA,16426
15
+ penguiflow/state.py,sha256=fBY5d_48hR4XHWVG08FraaQ7u4IVPJwooewfVLmzu1Q,1773
16
+ penguiflow/streaming.py,sha256=RKMm4VfaDA2ceEM_pB2Cuhmpwtdcjj7og-kjXQQDcbc,3863
17
+ penguiflow/testkit.py,sha256=pIFYpu1RfJnW2mbGvUkPhMpL-xDAw0E959oTMxLkLh8,11806
18
+ penguiflow/types.py,sha256=Fl56-b7OwIEUbPMDD1CY09nbOG_tmBw3FUhioojeG5M,1503
19
+ penguiflow/viz.py,sha256=KbBb9kKoL223vj0NgJV_jo5ny-0RTc2gcSBACm0jG8w,5508
20
+ penguiflow-2.2.0.dist-info/licenses/LICENSE,sha256=JSvodvLXxSct_kI9IBsZOBpVKoESQTB_AGbkClwZ7HI,1065
21
+ penguiflow_a2a/__init__.py,sha256=JuK_ov06yS2H97D2OVXhgX8LcgdOqE3EujUPaDKaduc,342
22
+ penguiflow_a2a/server.py,sha256=VMBO-oGjB6Z9mtRBU0z7ZFGprDUC_kihZJukh3budbs,25932
23
+ penguiflow-2.2.0.dist-info/METADATA,sha256=Rp0MN4Yovee2HoJOKpio9PE8A2UBTNUsECAsXBt6lLk,28872
24
+ penguiflow-2.2.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
25
+ penguiflow-2.2.0.dist-info/entry_points.txt,sha256=F2KxANLEVGRbpWLmcHcvYrTVLWbKWdmk3VOe98a7t9I,59
26
+ penguiflow-2.2.0.dist-info/top_level.txt,sha256=K-fTwLA14n0u_LDxDBCV7FmeBnJffhTOtUbTtOymQns,26
27
+ penguiflow-2.2.0.dist-info/RECORD,,
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ penguiflow-admin = penguiflow.admin:main
@@ -0,0 +1,2 @@
1
+ penguiflow
2
+ penguiflow_a2a
@@ -0,0 +1,19 @@
1
+ """Optional A2A adapters for PenguiFlow."""
2
+
3
+ from .server import (
4
+ A2AAgentCard,
5
+ A2AMessagePayload,
6
+ A2AServerAdapter,
7
+ A2ASkill,
8
+ A2ATaskCancelRequest,
9
+ create_a2a_app,
10
+ )
11
+
12
+ __all__ = [
13
+ "A2AAgentCard",
14
+ "A2ASkill",
15
+ "A2AMessagePayload",
16
+ "A2ATaskCancelRequest",
17
+ "A2AServerAdapter",
18
+ "create_a2a_app",
19
+ ]