agentflowkit 0.5.0__tar.gz → 0.6.1__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 (94) hide show
  1. {agentflowkit-0.5.0 → agentflowkit-0.6.1}/.github/workflows/ci.yml +1 -1
  2. agentflowkit-0.6.1/.gitignore +33 -0
  3. agentflowkit-0.6.1/CHANGELOG.md +240 -0
  4. agentflowkit-0.6.1/LEVEL_UP.md +314 -0
  5. agentflowkit-0.5.0/README.md → agentflowkit-0.6.1/PKG-INFO +502 -388
  6. agentflowkit-0.6.1/PUBLIC_API.md +78 -0
  7. agentflowkit-0.5.0/PKG-INFO → agentflowkit-0.6.1/README.md +454 -430
  8. agentflowkit-0.6.1/SECURITY.md +30 -0
  9. {agentflowkit-0.5.0 → agentflowkit-0.6.1}/docs/index.md +4 -4
  10. {agentflowkit-0.5.0 → agentflowkit-0.6.1}/examples/cpp_build_pipeline.py +14 -31
  11. {agentflowkit-0.5.0 → agentflowkit-0.6.1}/examples/drone_telemetry_agent.py +2 -1
  12. agentflowkit-0.6.1/examples/earnings_triage.py +183 -0
  13. {agentflowkit-0.5.0 → agentflowkit-0.6.1}/examples/market_analysis_crew.py +9 -5
  14. {agentflowkit-0.5.0 → agentflowkit-0.6.1}/examples/notebooks/parallel_execution_demo.ipynb +306 -292
  15. {agentflowkit-0.5.0 → agentflowkit-0.6.1}/examples/notebooks/quickstart.ipynb +18 -1
  16. {agentflowkit-0.5.0 → agentflowkit-0.6.1}/examples/research_react_agent.py +3 -4
  17. {agentflowkit-0.5.0 → agentflowkit-0.6.1}/examples/tool_agent.py +29 -2
  18. {agentflowkit-0.5.0 → agentflowkit-0.6.1}/pyproject.toml +8 -6
  19. {agentflowkit-0.5.0 → agentflowkit-0.6.1}/src/agentflow/__init__.py +25 -25
  20. {agentflowkit-0.5.0 → agentflowkit-0.6.1}/src/agentflow/agent.py +157 -48
  21. {agentflowkit-0.5.0 → agentflowkit-0.6.1}/src/agentflow/cache.py +24 -4
  22. agentflowkit-0.6.1/src/agentflow/contrib/__init__.py +1 -0
  23. agentflowkit-0.6.1/src/agentflow/contrib/otel.py +102 -0
  24. {agentflowkit-0.5.0 → agentflowkit-0.6.1}/src/agentflow/distillation.py +1 -1
  25. {agentflowkit-0.5.0 → agentflowkit-0.6.1}/src/agentflow/events.py +8 -3
  26. {agentflowkit-0.5.0 → agentflowkit-0.6.1}/src/agentflow/exceptions.py +16 -0
  27. {agentflowkit-0.5.0 → agentflowkit-0.6.1}/src/agentflow/llm.py +30 -23
  28. {agentflowkit-0.5.0 → agentflowkit-0.6.1}/src/agentflow/pipeline.py +288 -273
  29. {agentflowkit-0.5.0 → agentflowkit-0.6.1}/src/agentflow/rate_limiter.py +17 -23
  30. {agentflowkit-0.5.0 → agentflowkit-0.6.1}/src/agentflow/swarm.py +41 -28
  31. {agentflowkit-0.5.0 → agentflowkit-0.6.1}/src/agentflow/swarm_routing.py +6 -6
  32. agentflowkit-0.6.1/src/agentflow/types.py +136 -0
  33. agentflowkit-0.6.1/tests/test_agent.py +114 -0
  34. {agentflowkit-0.5.0 → agentflowkit-0.6.1}/tests/test_cache.py +19 -7
  35. {agentflowkit-0.5.0 → agentflowkit-0.6.1}/tests/test_conditional.py +1 -0
  36. {agentflowkit-0.5.0 → agentflowkit-0.6.1}/tests/test_hitl.py +99 -12
  37. agentflowkit-0.6.1/tests/test_llm.py +53 -0
  38. {agentflowkit-0.5.0 → agentflowkit-0.6.1}/tests/test_long_term_memory.py +1 -0
  39. {agentflowkit-0.5.0 → agentflowkit-0.6.1}/tests/test_memory.py +19 -17
  40. {agentflowkit-0.5.0 → agentflowkit-0.6.1}/tests/test_observability.py +1 -0
  41. {agentflowkit-0.5.0 → agentflowkit-0.6.1}/tests/test_parallel.py +1 -0
  42. {agentflowkit-0.5.0 → agentflowkit-0.6.1}/tests/test_pipeline.py +78 -0
  43. {agentflowkit-0.5.0 → agentflowkit-0.6.1}/tests/test_rate_limiter.py +1 -0
  44. {agentflowkit-0.5.0 → agentflowkit-0.6.1}/tests/test_sandbox.py +24 -24
  45. {agentflowkit-0.5.0 → agentflowkit-0.6.1}/tests/test_streaming.py +2 -1
  46. {agentflowkit-0.5.0 → agentflowkit-0.6.1}/tests/test_swarm.py +61 -17
  47. {agentflowkit-0.5.0 → agentflowkit-0.6.1}/tests/test_swarm_routing.py +14 -13
  48. {agentflowkit-0.5.0 → agentflowkit-0.6.1}/tests/test_tools.py +14 -13
  49. {agentflowkit-0.5.0 → agentflowkit-0.6.1}/tests/test_triggers.py +7 -2
  50. {agentflowkit-0.5.0 → agentflowkit-0.6.1}/uv.lock +8 -2
  51. agentflowkit-0.5.0/.coverage +0 -0
  52. agentflowkit-0.5.0/.gitignore +0 -21
  53. agentflowkit-0.5.0/AUDIT_REPORT.md +0 -137
  54. agentflowkit-0.5.0/CHANGELOG.md +0 -111
  55. agentflowkit-0.5.0/CMakeLists.txt +0 -30
  56. agentflowkit-0.5.0/PASS2_RESOLUTION_REPORT.md +0 -261
  57. agentflowkit-0.5.0/src/agentflow/cpp_core/bindings.cpp +0 -36
  58. agentflowkit-0.5.0/src/agentflow/cpp_core/dag_engine.cpp +0 -105
  59. agentflowkit-0.5.0/src/agentflow/cpp_core/dag_engine.h +0 -22
  60. agentflowkit-0.5.0/src/agentflow/types.py +0 -68
  61. agentflowkit-0.5.0/tests/test_agent.py +0 -54
  62. agentflowkit-0.5.0/tests/test_llm.py +0 -31
  63. {agentflowkit-0.5.0 → agentflowkit-0.6.1}/.gitattributes +0 -0
  64. {agentflowkit-0.5.0 → agentflowkit-0.6.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  65. {agentflowkit-0.5.0 → agentflowkit-0.6.1}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  66. {agentflowkit-0.5.0 → agentflowkit-0.6.1}/.github/workflows/docs.yml +0 -0
  67. {agentflowkit-0.5.0 → agentflowkit-0.6.1}/.github/workflows/publish.yml +0 -0
  68. {agentflowkit-0.5.0 → agentflowkit-0.6.1}/CONTRIBUTING.md +0 -0
  69. {agentflowkit-0.5.0 → agentflowkit-0.6.1}/LICENSE +0 -0
  70. {agentflowkit-0.5.0 → agentflowkit-0.6.1}/benchmarks/parallel_speedup.py +0 -0
  71. {agentflowkit-0.5.0 → agentflowkit-0.6.1}/docs/getting-started.md +0 -0
  72. {agentflowkit-0.5.0 → agentflowkit-0.6.1}/docs/guides/cost-streaming.md +0 -0
  73. {agentflowkit-0.5.0 → agentflowkit-0.6.1}/docs/guides/memory.md +0 -0
  74. {agentflowkit-0.5.0 → agentflowkit-0.6.1}/docs/guides/observability.md +0 -0
  75. {agentflowkit-0.5.0 → agentflowkit-0.6.1}/docs/guides/tools.md +0 -0
  76. {agentflowkit-0.5.0 → agentflowkit-0.6.1}/docs/reference.md +0 -0
  77. {agentflowkit-0.5.0 → agentflowkit-0.6.1}/examples/code_reviewer.py +0 -0
  78. {agentflowkit-0.5.0 → agentflowkit-0.6.1}/examples/memory_chat_agents.py +0 -0
  79. {agentflowkit-0.5.0 → agentflowkit-0.6.1}/examples/research_crew.py +0 -0
  80. {agentflowkit-0.5.0 → agentflowkit-0.6.1}/examples/robotics_mqtt_agent.py +0 -0
  81. {agentflowkit-0.5.0 → agentflowkit-0.6.1}/examples/streaming_and_cost.py +0 -0
  82. {agentflowkit-0.5.0 → agentflowkit-0.6.1}/mkdocs.yml +0 -0
  83. {agentflowkit-0.5.0 → agentflowkit-0.6.1}/src/agentflow/hitl.py +0 -0
  84. {agentflowkit-0.5.0 → agentflowkit-0.6.1}/src/agentflow/logging.py +0 -0
  85. {agentflowkit-0.5.0 → agentflowkit-0.6.1}/src/agentflow/memory.py +0 -0
  86. {agentflowkit-0.5.0 → agentflowkit-0.6.1}/src/agentflow/observability.py +0 -0
  87. {agentflowkit-0.5.0 → agentflowkit-0.6.1}/src/agentflow/pricing.py +0 -0
  88. {agentflowkit-0.5.0 → agentflowkit-0.6.1}/src/agentflow/py.typed +0 -0
  89. {agentflowkit-0.5.0 → agentflowkit-0.6.1}/src/agentflow/sandbox.py +0 -0
  90. {agentflowkit-0.5.0 → agentflowkit-0.6.1}/src/agentflow/tools.py +0 -0
  91. {agentflowkit-0.5.0 → agentflowkit-0.6.1}/src/agentflow/triggers.py +0 -0
  92. {agentflowkit-0.5.0 → agentflowkit-0.6.1}/tests/test_logging.py +0 -0
  93. {agentflowkit-0.5.0 → agentflowkit-0.6.1}/tests/test_pricing.py +0 -0
  94. {agentflowkit-0.5.0 → agentflowkit-0.6.1}/tests/test_retry.py +0 -0
@@ -33,7 +33,7 @@ jobs:
33
33
  strategy:
34
34
  fail-fast: false
35
35
  matrix:
36
- python-version: ["3.10", "3.11", "3.12"]
36
+ python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
37
37
 
38
38
  steps:
39
39
  - uses: actions/checkout@v4
@@ -0,0 +1,33 @@
1
+ __pycache__/
2
+ *.pyc
3
+ *.pyo
4
+ *.egg-info/
5
+ dist/
6
+ build/
7
+ .venv/
8
+ venv/
9
+ .env
10
+ .pytest_cache/
11
+ .mypy_cache/
12
+ .ruff_cache/
13
+ .coverage
14
+ *.egg
15
+ # AI assistants / agents
16
+ .claude/
17
+ CLAUDE.md
18
+ .cursor/
19
+ .cursorrules
20
+ .aider*
21
+ .github/copilot-instructions.md
22
+ AGENTS.md
23
+ chat.md
24
+ tmpclaude-*
25
+ # Session/report scaffolding — should never be committed
26
+ *_REPORT.md
27
+ PASS[0-9]*_*.md
28
+ commit_msg.txt
29
+ # Hackathon demo app — lives in its own repo, not in the library
30
+ nova_bridge/
31
+ run_nova_bridge.py
32
+ # Multi-agent orchestration workspace — untracked local state, never a package file
33
+ .bridgespace/
@@ -0,0 +1,240 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented here.
4
+
5
+ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
6
+ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ---
9
+
10
+ ## [0.6.1] — 2026-07-16
11
+
12
+ Packaging-hygiene release. **0.6.0 has been yanked from PyPI.** Its source
13
+ distribution (`.tar.gz`) accidentally bundled a local, untracked
14
+ `.bridgespace/` agent-workspace directory whose contents included a leaked
15
+ PyPI API token (since revoked by PyPI). The Python package code was
16
+ identical and unaffected; only the sdist contained the stray directory.
17
+
18
+ ### Fixed
19
+ - **Security/packaging**: `.bridgespace/` (a local multi-agent workspace, never
20
+ part of the library) was not excluded from the build and leaked into the
21
+ 0.6.0 sdist. It is now git-ignored and excluded from all distributions. The
22
+ wheel was never affected. If you installed 0.6.0, upgrade to 0.6.1.
23
+
24
+ ---
25
+
26
+ ## [0.6.0] — 2026-07-16
27
+
28
+ First release shipping a pure-Python `py3-none-any` wheel. The only wheel
29
+ published for 0.5.0 was a Windows/CPython-3.11 binary left over from the
30
+ (since removed) C++ extension era — every other platform fell back to an
31
+ sdist build. If 0.4.0/0.5.0 failed to install for you, this release is the fix.
32
+
33
+ ### Added
34
+ - **Typed data plane**: `LLM.generate()` returns a typed `LLMResponse` pydantic
35
+ model. Dict-style access is kept as a deprecated shim (see Deprecated).
36
+ - **Validated output flows downstream**: when an agent declares
37
+ `output_schema`, its validated output (`AgentResult.data`) is what downstream
38
+ agents receive in `context` — not the raw JSON string.
39
+ - **Cost budgets**: `Pipeline(budget_usd=0.25)` enforces a hard USD ceiling per
40
+ run, raising `BudgetExceededError` when exceeded (checked after each level).
41
+ - **OpenTelemetry adapter**: `agentflow.contrib.otel.OTelHooks` (install the
42
+ `otel` extra) — one import to spans.
43
+ - **`AgentSpec`** is the public name of the object `@Agent` returns (previously
44
+ the private `_DecoratorAgent`); `@Agent(system_prompt=...)` fully replaces
45
+ the default role-based system prompt.
46
+ - **`wall_time`** (elapsed) and **`agent_seconds`** (summed agent time) on
47
+ `PipelineResult`.
48
+ - **`PUBLIC_API.md`** stability contract and **`SECURITY.md`**.
49
+ - Showcase example `examples/earnings_triage.py`: diamond DAG with tools,
50
+ parallel analysts, typed output, budget, and cache — zero-key via Ollama/Groq.
51
+ - CI test matrix extended to Python 3.13 and 3.14.
52
+
53
+ ### Fixed
54
+ - `SupervisorAgent` crashed with `TypeError` when an upstream context value was
55
+ a validated-output dict.
56
+ - `SupervisorAgent` billing race: concurrent runs sharing one instance
57
+ corrupted each other's worker token/cost accounting (now a run-scoped ledger).
58
+ - A real agent failure alongside an HITL pause in the same DAG level was
59
+ silently swallowed; errors now take precedence and raise (the discarded
60
+ pause is logged).
61
+ - Resumed agents dropped their validated `output_schema` result;
62
+ `AgentResult.data` is now populated on the resume path too.
63
+ - `Pipeline.serve()` silently discarded the trigger's `context_data`; it is now
64
+ appended to the task prompt as a JSON block.
65
+ - `Pipeline.resume()` could raise a spurious `ValidationError` when the last
66
+ saved context value was a dict.
67
+ - Dependency-cycle errors now name the agents involved.
68
+
69
+ ### Changed
70
+ - `run()`, `resume()`, and `stream()` now share a single execution driver
71
+ (previously three divergent copies of the same loop — the source of the
72
+ pause/error bugs above). `resume()` and `stream()` fire the same
73
+ `on_agent_start`/`on_agent_end`/`on_agent_error` hooks as `run()`.
74
+ - `stream()` persists HITL pause state under a real `run_id` distinct from the
75
+ session id.
76
+ - `Event.type` and `EventEmitter.emit` are typed with the new `EventType`
77
+ Literal (now includes `pipeline_paused`); `PipelineResult.status` is
78
+ `Literal["completed", "paused"]`.
79
+ - `__version__` is single-sourced from package metadata (`importlib.metadata`).
80
+ - Sandbox and trigger names are no longer re-exported from the top-level
81
+ package — import via `agentflow.sandbox` / `agentflow.triggers` (best-effort
82
+ modules outside the semver contract).
83
+ - The C++ DAG engine and its build scaffolding were removed entirely: DAG
84
+ resolution is microseconds against multi-second LLM calls.
85
+
86
+ ### Deprecated (warn since 0.6, removal at 1.0)
87
+ - Dict-style access on `LLMResponse` (`response["content"]`, `.get(...)`) —
88
+ use attribute access.
89
+ - `set_session()` / `set_approval_policy()` — pass `session_id=` /
90
+ `approval_policy=` to `execute()`; mutating shared agent instances is unsafe
91
+ under concurrency.
92
+ - `PipelineResult.total_duration` — use `agent_seconds` or `wall_time`.
93
+ - The `_DecoratorAgent` name — use `AgentSpec`.
94
+
95
+ ---
96
+
97
+ ## [0.5.0] — 2026-07-03 (retroactive entry)
98
+
99
+ Written after the fact on 2026-07-16: 0.4.0 and 0.5.0 shipped without
100
+ changelog entries, violating this file's own policy. Reconstructed from git
101
+ history. **Packaging note:** the only 0.5.0 wheel on PyPI is
102
+ `cp311-cp311-win_amd64`; every other platform builds from the sdist. Prefer 0.6.0.
103
+
104
+ ### Added
105
+ - **Swarm routing**: `SupervisorAgent` delegates sub-tasks to worker agents via
106
+ a generated `delegate_task` tool; worker tokens/cost bubble up to the
107
+ supervisor's result. Unexported `swarm_routing.DynamicSupervisorAgent`
108
+ prototype with depth-capped dynamic agent creation.
109
+ - **Background memory distillation** (`agentflow.distillation`): compresses
110
+ long session memory with a version lock against concurrent writes.
111
+ - **Human-in-the-loop**: `ApprovalPolicy` + `PauseExecution` +
112
+ `Pipeline.resume()` — pause on blocked tool calls, persist state to memory,
113
+ resume with approve/reject.
114
+ - **Sandboxes** (`agentflow.sandbox`): Docker/subprocess code-execution tools.
115
+ - **MQTT triggers** (`agentflow.triggers`) and `Pipeline.serve()` daemon mode.
116
+
117
+ ### Changed
118
+ - The C++ DAG engine introduced in 0.4.0 was made optional (skipped when no
119
+ compiler is available) — and removed entirely in 0.6.0.
120
+
121
+ ---
122
+
123
+ ## [0.4.0] — 2026-07-02 (retroactive entry)
124
+
125
+ ### Added
126
+ - C++ DAG engine via pybind11 (Kahn's algorithm) — later judged unnecessary
127
+ and removed; see the 0.5.0/0.6.0 notes.
128
+ - Memory module (`BaseMemory`, `InMemoryContext`, `RedisContext`,
129
+ `VectorContext`) with per-session context injection into agent prompts.
130
+ - ReAct loop hardening: duplicate-call detection, tool-output truncation,
131
+ sliding message window, per-iteration LLM retry.
132
+
133
+ ### Note
134
+ - 0.2.0 and 0.3.0 were developed and documented below but never published to
135
+ PyPI, which jumped 0.1.0 → 0.4.0.
136
+
137
+ ---
138
+
139
+ ## [0.3.0]
140
+
141
+ ### Added
142
+ - **Tool / function calling** (`tools.py`): the `@tool` decorator turns any sync
143
+ or async Python function into an LLM-callable tool. Argument JSON schemas are
144
+ generated automatically from type hints via Pydantic. Agents given `tools=[...]`
145
+ run a bounded **ReAct loop** (call → execute tools → observe → repeat) up to
146
+ `max_tool_iterations` (default 6). Tool errors are fed back to the model for
147
+ recovery. New `ToolError` exception; tool-call traces recorded in
148
+ `AgentResult.metadata["tool_calls"]`.
149
+ - **Cost tracking** (`pricing.py`): built-in USD price tables for common OpenAI
150
+ and Anthropic models with longest-prefix matching. `LLM.generate()` returns a
151
+ `cost` (and `prompt_tokens`/`completion_tokens`); `AgentResult.cost` and
152
+ `PipelineResult.total_cost` aggregate spend. Cache hits bill `0.0`.
153
+ `register_price()` / `estimate_cost()` are public.
154
+ - **Token streaming**: `LLM.astream()` yields content deltas token-by-token for
155
+ interactive UIs (honours the rate limiter; no cache/retry mid-stream).
156
+ - **Observability hooks** (`observability.py`): `Hooks` base class + `LoggingHooks`
157
+ wire the previously-unused `PipelineLogger` into `Pipeline.run()` (which was
158
+ silent before). A raising hook is caught and warned, never crashing the run.
159
+ - **Production-grade retry**: unified exponential backoff with jitter and
160
+ `Retry-After` header support. New `LLM(retry_base_delay=, retry_jitter=)` args.
161
+ - **Documentation site**: MkDocs Material + mkdocstrings under `docs/`, deployed
162
+ via a new `docs.yml` workflow. New `docs` optional-dependency group.
163
+ - New examples: `tool_agent.py`, `streaming_and_cost.py`; and
164
+ `benchmarks/parallel_speedup.py`.
165
+
166
+ ### Fixed
167
+ - **`py.typed` marker** added — the package advertised `Typing :: Typed` but
168
+ shipped no marker, so downstream type-checkers saw no types.
169
+ - **Red CI made green**: resolved 1 `ruff` error (B904) and 7 `mypy --strict`
170
+ errors across `llm.py`, `cache.py`, `logging.py`, `events.py`, `pipeline.py`.
171
+
172
+ ### Changed
173
+ - `__version__` bumped to `0.3.0`.
174
+ - Test coverage raised to ~91%; `fail_under` tightened from 80 → 90.
175
+ - `Pipeline.__init__` gains a `hooks` parameter; `run()` now emits lifecycle
176
+ events and generates `run_id` up front.
177
+
178
+ ---
179
+
180
+ ## [0.2.0]
181
+
182
+ ### Added
183
+ - **Parallel execution**: Agents at the same DAG level now run concurrently via
184
+ `asyncio.gather()`. `_resolve_levels()` replaces `_resolve_order()` and uses
185
+ Kahn's algorithm to group independent agents.
186
+ - **Per-agent timeout**: `Pipeline.add(timeout=N)` wraps each agent coroutine with
187
+ `asyncio.wait_for`; raises `AgentTimeoutError` on expiry.
188
+ - **Conditional branching**: `Pipeline.add(condition=lambda ctx: ...)` allows
189
+ dynamic skipping of agents based on upstream outputs. Skipped agents emit
190
+ `agent_skipped` events in streaming mode.
191
+ - **Pipeline-level retry**: `Pipeline(retry_failed_agents=N)` retries failed
192
+ agents up to N times with exponential backoff (1s, 2s, 4s). Timeouts are
193
+ non-retriable.
194
+ - **LLM response caching** (`cache.py`): `ResponseCache` ABC with `InMemoryCache`
195
+ (SHA-256 key, lazy TTL eviction, max-size LRU) and `RedisCache` (optional dep).
196
+ Cache is wired into `LLM(cache=...)`.
197
+ - **Rate limiting** (`rate_limiter.py`): `RateLimiter(requests_per_minute, max_concurrent)`
198
+ using `asyncio.Semaphore` + sliding-window counter. Async context manager interface.
199
+ Wired into `LLM(rate_limiter=...)`.
200
+ - **Structured logging** (`logging.py`): `PipelineLogger` (LoggerAdapter with JSON
201
+ formatter) carrying `run_id` and `pipeline` through all log records.
202
+ - **Agent output validation**: `@Agent(output_schema=MyPydanticModel)` validates
203
+ LLM response JSON against a Pydantic v2 model; raises `AgentOutputValidationError`
204
+ on failure.
205
+ - New exception classes: `AgentTimeoutError`, `AgentOutputValidationError`.
206
+ - `AgentResult` gains: `cached`, `level`, `timestamp` fields.
207
+ - `PipelineResult` gains: `run_id`, `levels_executed`, `agents_with_cache_hits` fields.
208
+ - `Event.type` now documents all valid values including `"agent_skipped"`.
209
+ - GitHub Actions CI workflow (lint, test matrix py3.10-3.12, codecov, build check).
210
+ - GitHub Actions publish workflow (OIDC trusted publishing on version tags).
211
+ - Issue templates for bug reports and feature requests.
212
+ - `CONTRIBUTING.md` with development setup and PR checklist.
213
+ - `pythonpath = ["src"]` in pytest config to fix editable install on non-ASCII paths.
214
+
215
+ ### Changed
216
+ - `__version__` bumped to `0.2.0`.
217
+ - `pipeline.py`: `Pipeline.__init__` gains `retry_failed_agents` parameter.
218
+ - `pipeline.py`: `Pipeline.add` gains `timeout` and `condition` parameters.
219
+ - `llm.py`: `LLM.__init__` gains `cache` and `rate_limiter` parameters.
220
+ Return dict from `generate()` now includes `"cached"` key.
221
+ - `pyproject.toml`: classifier updated to Beta; dev extras expanded;
222
+ `ruff`, `mypy`, `coverage` tool config sections added.
223
+
224
+ ### Performance
225
+ - Two independent agents that each take 0.1s now complete in ~0.1s (parallel),
226
+ not ~0.2s (sequential).
227
+
228
+ ---
229
+
230
+ ## [0.1.0] — 2026-02-27
231
+
232
+ ### Added
233
+ - Initial release.
234
+ - `@Agent` decorator and `BaseAgent` ABC for defining agents.
235
+ - `Pipeline` with topological sort (`_resolve_order`) for dependency resolution.
236
+ - `LLM` provider abstraction with OpenAI-compatible API, retry logic.
237
+ - `EventEmitter` + `pipeline.stream()` for async event streaming.
238
+ - Pydantic v2 data models: `AgentResult`, `PipelineResult`, `Event`.
239
+ - Custom exception hierarchy: `AgentFlowError`, `AgentError`, `LLMError`, `PipelineError`.
240
+ - Published to PyPI as `agentflowkit`.
@@ -0,0 +1,314 @@
1
+ # agentflow — Level-Up Analysis
2
+
3
+ Reviewer stance: skeptical maintainer deciding whether a real team should depend on this.
4
+ Ordered by impact-per-effort. Verified against the code at v0.5.0 (working tree), not the README's claims.
5
+
6
+ ---
7
+
8
+ ## The 3 things that matter most
9
+
10
+ **1. The library has an identity crisis, and it's visible from the outside.**
11
+ The README says *"agentflow is a deliberately narrow library, not a framework"* — while
12
+ `__init__.py` exports `DockerSandbox`, `SubprocessSandbox`, `MQTTTrigger`, `SupervisorAgent`,
13
+ `VectorContext`, and an HITL pause/resume state machine. Git history shows a C++ DAG engine
14
+ added in one release ("pillar1") and deleted in the next, plus `swarm_routing.py` and
15
+ `distillation.py` that aren't even exported. A reviewer evaluating this for production reads
16
+ that history and concludes: *the maintainer doesn't know what this is yet, so I can't depend
17
+ on it.* Nothing else in this document matters until the scope is actually narrow, not
18
+ narrated as narrow.
19
+
20
+ **2. The string-only data plane is the one hard-to-reverse API mistake, and it's still reversible today.**
21
+ Agents communicate through `context: dict[str, str]`. `Pipeline.run()` takes a bare `str`.
22
+ `LLM.generate()` returns an untyped `dict[str, Any]`. Structured output (`output_schema`)
23
+ is validated and then **buried in `metadata["validated_output"]`** while downstream agents
24
+ receive the raw JSON string. For a library whose headline trust signal is "fully typed,
25
+ `mypy --strict`", the actual data flowing between agents is untyped strings. PydanticAI's
26
+ entire pitch is typed agent I/O — this is the exact axis you'll be compared on. Fix it
27
+ before 1.0 or never.
28
+
29
+ **3. Trust hygiene is broken in ways that take an hour to fix.**
30
+ `src/agentflow/__init__.py` says `__version__ = "0.3.0"`; `pyproject.toml` says `0.5.0`.
31
+ The import name is `agentflow` but the pip name is `agentflowkit` — every new user's first
32
+ five minutes includes "why doesn't `pip install agentflow` work". Commit messages like
33
+ "massive architecture upgrade" between minor versions signal churn. These are trivial fixes
34
+ that currently scream *hobby project* to anyone doing due diligence.
35
+
36
+ ---
37
+
38
+ ## Analysis 1 — Positioning & the "why not X" test
39
+
40
+ **The honest niche:** agentflow is a *readable, two-dependency, cost-aware parallel DAG
41
+ runner for OpenAI-compatible endpoints*. Its defensible claim is auditability: the core
42
+ (pipeline + agent + llm + tools + types, ~1,700 lines) can be read in an afternoon,
43
+ type-checks under `mypy --strict`, and has retries, timeouts, caching, cost, and events
44
+ built in rather than as plugins. That is a real niche — teams burned by LangChain's
45
+ dependency graph, running on Groq/Ollama/vLLM, who would otherwise hand-roll
46
+ `asyncio.gather` plus retry/cost boilerplate.
47
+
48
+ **The single sharpest wedge:** **cost-aware parallel orchestration on a core you can audit.**
49
+ Nobody else owns "every run tells you what it cost, per agent, with cache hits billing $0,
50
+ in a library small enough to read before deploying." LangGraph won't be small. CrewAI won't
51
+ be rigorous. PydanticAI won't center DAG parallelism. Own this one thing; everything else
52
+ (swarm, HITL, sandbox, MQTT) is secondary or actively dilutive.
53
+
54
+ **Who should NOT use agentflow (say this in the README):**
55
+ - Anyone already inside LangChain/LlamaIndex — the ecosystem gravity isn't worth fighting.
56
+ - Anyone needing durable workflows that survive process death (the HITL resume is
57
+ memory-backed state, not a workflow engine — don't pretend otherwise).
58
+ - Anyone needing native (non-OpenAI-compatible) SDKs: Bedrock, Vertex native.
59
+ - Anyone wanting RAG batteries: loaders, splitters, vector-store integrations.
60
+ - Anyone whose graph is dynamic per-run (agentflow's DAG is static once built).
61
+
62
+ ---
63
+
64
+ ## Analysis 2 — API design critique
65
+
66
+ ### 2a. Hard to reverse once adopted — fix NOW
67
+
68
+ **The untyped data plane** (the #2 issue above). Concretely:
69
+
70
+ ```python
71
+ # Before — llm.generate returns a dict users index blindly:
72
+ response = await llm.generate(messages)
73
+ content = response["content"] # typo = runtime KeyError
74
+ cost = response.get("cost", 0.0) # every caller re-invents the default
75
+
76
+ # After — a frozen model; attribute access, mypy-checked:
77
+ class LLMResponse(BaseModel):
78
+ content: str
79
+ tokens: TokenUsage
80
+ cost: float
81
+ model: str
82
+ cached: bool = False
83
+ tool_calls: list[ToolCall] | None = None
84
+ finish_reason: str | None = None
85
+
86
+ response = await llm.generate(messages)
87
+ response.content
88
+ ```
89
+
90
+ ```python
91
+ # Before — structured output validated, then thrown away for downstream agents:
92
+ context["analyst"] # raw JSON string
93
+ result.get("analyst").metadata["validated_output"] # the actual data, buried
94
+
95
+ # After — validated output IS the agent's output downstream:
96
+ @Agent(name="analyst", role="...", output_schema=Report)
97
+ ...
98
+ report: Report = context["analyst"] # context: dict[str, Any]
99
+ ```
100
+
101
+ `context: dict[str, str]` → `dict[str, Any]` is the enabling change. Do it while the user
102
+ count is small; after adoption it's a breaking change to every agent body ever written.
103
+
104
+ **Mutable shared agent state — this is a live bug, not just a smell.**
105
+ `@Agent` produces a module-level singleton. `Pipeline._execute_node()` calls
106
+ `agent.set_session(session_id)` and `agent.set_approval_policy(...)` — mutating that
107
+ singleton. Two pipelines (or two `serve()` requests — `serve` explicitly runs concurrent
108
+ `run()`s) sharing one agent will cross-contaminate sessions: pipeline A's agent reads and
109
+ writes pipeline B's memory. The fix is to stop mutating and pass run-scoped state through
110
+ the call:
111
+
112
+ ```python
113
+ # Before (pipeline.py:172-177) — mutate the shared instance:
114
+ agent.set_session(session_id)
115
+ agent.set_approval_policy(policy)
116
+ result = await agent.execute(task, context, self._llm)
117
+
118
+ # After — run-scoped context travels with the call:
119
+ result = await agent.execute(task, context, RunContext(llm=self._llm,
120
+ session_id=session_id,
121
+ approval_policy=policy))
122
+ ```
123
+
124
+ This also fixes `BaseAgent.execute(task, context, llm)`'s signature being frozen at three
125
+ positional params — `RunContext` is the extension point so the signature never breaks again.
126
+
127
+ **`Agent(...)` returning the private `_DecoratorAgent`.** The decorator's return type is a
128
+ private class users hold references to, pass to `Pipeline.add`, and will inevitably
129
+ introspect. Rename it public (`AgentSpec` or fold into `BaseAgent`) before someone
130
+ depends on the underscore name.
131
+
132
+ **`hasattr`-based duck typing** (`hasattr(agent, 'set_session')`,
133
+ `hasattr(node.agent, 'resume_execution')` in pipeline.py) — invisible contracts. A user
134
+ subclassing `BaseAgent` gets silently different behavior depending on which optional
135
+ methods they happened to define. Replace with an explicit `Protocol` or move the
136
+ capability into `BaseAgent` with default implementations.
137
+
138
+ ### 2b. Semantics that will surprise users
139
+
140
+ - **`PipelineResult.output` = "last agent's output"** — undefined when the final level has
141
+ multiple parallel agents; it's whichever ran last in list order. Either make it the
142
+ outputs of all sink nodes, or document that it's only meaningful for single-sink DAGs
143
+ and raise/warn otherwise.
144
+ - **`total_duration` sums per-agent durations** — in a *parallelism* library, the headline
145
+ duration metric double-counts parallel time. Rename to `agent_seconds` and add
146
+ `wall_time` measured around the run. This one is embarrassing given the pitch.
147
+ - **`condition` receives the full accumulated context; `execute` receives dep-scoped
148
+ context.** Two different context shapes for the same node. Pick one (dep-scoped is the
149
+ right one) — it changes what user lambdas can see, so it's a breaking change later.
150
+ - **The system prompt is hardcoded**: `f"You are a {role}. Provide clear, thorough,
151
+ well-structured responses."` — unoverridable trailing instruction injected into every
152
+ agent. Add `system_prompt=` to `@Agent`.
153
+ - **Memory injection silently truncates to 300 chars** (agent.py:110) — silent data loss
154
+ with no knob and no log line.
155
+ - **`status: str`** on `PipelineResult` → `Literal["completed", "paused"]`.
156
+ - **`depends_on` forbids forward references** (dep must be added first). Fine as a choice,
157
+ but it forces users to topologically sort their own code; validating at `run()` instead
158
+ would cost nothing.
159
+
160
+ ### 2c. What's genuinely good — keep and defend
161
+ Dep-scoped context (agents only see declared dependencies) is a *great* decision most
162
+ competitors don't make. Kahn's-levels parallelism is simple and explainable. The tool loop
163
+ (dedup, truncation, sliding window, concurrent tool calls) is careful. Exceptions carry
164
+ agent names. `stream()` cancels the background task when the consumer abandons the
165
+ generator. This core deserves the audit-friendly pitch — the periphery undermines it.
166
+
167
+ ---
168
+
169
+ ## Analysis 3 — The adoption gap (scored 1–5)
170
+
171
+ | Dimension | Score | Justification | Action to raise it |
172
+ |---|---|---|---|
173
+ | Documentation | 3 | mkdocs + guides exist; but exports like `SupervisorAgent`, `sandboxed_tool`, `MQTTTrigger` are thinly documented, no migration notes between 0.x releases | Rule: every name in `__all__` has a reference page and a runnable snippet, or it gets un-exported. Add UPGRADING.md per minor. |
174
+ | Reliability signals | 3 | CI matrix 3.10–3.12, 90% coverage gate, mypy strict — good. But `__version__` mismatch, deleted-feature churn, no 3.13 | Fix version (single-source from `importlib.metadata`), add 3.13, tag every release, keep CHANGELOG honest about removals. |
175
+ | Provider coverage | 2.5 | "OpenAI-compatible" is a fine stance but untested as a claim — nothing in CI exercises Groq/Ollama/OpenRouter quirks (e.g. tool-call format drift) | A recorded-response (VCR-style) provider matrix in CI + a documented compat table: provider / tools / streaming / tested version. |
176
+ | Observability & debuggability | 3 | Hooks + event stream are real; but no way to see *why* the DAG resolved the way it did, or why a condition skipped an agent | `pipe.explain()` — dry-run printing resolved levels, dependencies, and condition outcomes; include skip reasons in `agent_skipped` events. |
177
+ | Error messages | 4 | Exceptions are typed, carry agent/tool names, cycle detection exists | Cycle error should name the cycle members, not just "Cycle detected". |
178
+ | Ecosystem fit | 2 | README name-drops OpenTelemetry/Langfuse but ships no adapter; hooks are the right seam, unused | Ship `agentflow.contrib.otel.OTelHooks` (~50 lines) — one import to spans. Highest fit-per-line item on this list. |
179
+ | Trust signals | 2 | MIT clear, changelog exists — but version mismatch, pip-name/import-name split, sandbox-in-core security surface, feature churn | Fix version; add SECURITY.md; publish a stability policy (below); stop shipping code-execution sandboxes in the "minimal" core package. |
180
+
181
+ The sandbox deserves its own sentence: **a library advertising "minimal, auditable, two
182
+ dependencies" ships 461 lines of Docker/subprocess arbitrary-code execution in core.**
183
+ That's the single largest security surface in the package, in the component least related
184
+ to the wedge. Move it to a separate package or clearly-marked extra.
185
+
186
+ ---
187
+
188
+ ## Analysis 4 — The killer example
189
+
190
+ The wedge is *visible cost + visible parallelism*. Design the demo so both are undeniable
191
+ in one run, with zero API keys required to try it (Groq free tier or Ollama).
192
+
193
+ **"Earnings-call triage"** — realistic diamond DAG with genuine dependencies:
194
+
195
+ ```
196
+ ┌─ financials_analyst ──┐
197
+ transcript_fetcher ─┼─ sentiment_analyst ──┼─ risk_synthesizer ─ brief_writer
198
+ └─ competitor_scanner ──┘
199
+ ```
200
+
201
+ - `transcript_fetcher` uses **tools** (fetch + chunk) — shows the ReAct loop.
202
+ - The three middle analysts run **in parallel** — the event stream prints them starting
203
+ simultaneously with live `agent_complete` lines.
204
+ - `risk_synthesizer` has an `output_schema` — shows typed output.
205
+ - The script ends by printing: **wall time vs. sequential estimate** (e.g. "11.2s
206
+ parallel, ~29s sequential") and **per-agent + total cost**.
207
+ - Then it runs a second time with `InMemoryCache` and prints the delta:
208
+ "$0.0041 → $0.0007, 3 cache hits." That line is the whole pitch in one run.
209
+
210
+ Build it as `examples/earnings_triage.py`, make its *actual captured output* the first
211
+ code block in the README after install, and record a 20-second asciinema of it. Every
212
+ current README example is either a toy ("multiply two numbers") or unverifiable prose;
213
+ this replaces all of them as the centerpiece.
214
+
215
+ ---
216
+
217
+ ## Analysis 5 — Path to real dependents (6 months)
218
+
219
+ **The 3–5 guarantees worth depending on:**
220
+ 1. **Typed data plane end-to-end** (LLMResponse model, `Any` context, schema output flows
221
+ downstream) — the PydanticAI-parity feature.
222
+ 2. **A written stability contract**: `PUBLIC_API.md` listing exactly what's covered by
223
+ semver, a deprecation policy (warn one minor, remove next major), and explicit 1.0
224
+ criteria. Teams adopt contracts, not features.
225
+ 3. **Provider compat matrix tested in CI** — turns "OpenAI-compatible" from a hope into
226
+ a guarantee.
227
+ 4. **OTel hooks adapter** — makes it composable with whatever observability stack the
228
+ team already runs.
229
+ 5. **Cost budgets**: `Pipeline(budget_usd=0.50)` that aborts the run when exceeded.
230
+ Nobody else has this as a first-class primitive and it's ~40 lines on top of the
231
+ existing cost plumbing. This is the wedge feature.
232
+
233
+ **What hitting 1.0 requires:** the scope cut executed (below), the typed data plane done,
234
+ zero known breaking changes queued, three consecutive minors with no public-API breaks,
235
+ and the version/naming hygiene fixed. Realistically: 1.0 in month 5–6, not sooner.
236
+
237
+ **Where first users come from:** the cost-conscious OpenAI-compatible crowd — r/LocalLLaMA,
238
+ Groq and Ollama Discords, a Show HN. These communities *cannot* use most heavyweight
239
+ frameworks comfortably (local endpoints, tight budgets) and are exactly who the wedge
240
+ serves. What gets them to try it: the killer example running against Ollama with zero
241
+ API keys, and the cost-budget primitive.
242
+
243
+ **What to explicitly NOT build (scope creep already killing this):**
244
+ - No RAG: loaders, splitters, embeddings, vector stores. `VectorContext` is already over
245
+ the line — freeze it or extract it.
246
+ - No C++/Rust engine. The DAG resolution is microseconds against multi-second LLM calls;
247
+ it was rightly deleted, don't bring it back.
248
+ - No agent marketplace, personas, or prompt-template library.
249
+ - No general workflow engine (durable execution, cron, queues). `serve()` + MQTT is
250
+ already drifting there — extract triggers to `agentflowkit-triggers`.
251
+ - Sandbox → separate package (`agentflowkit-sandbox`) with its own security policy.
252
+ - Swarm/`SupervisorAgent`: keep only if it stays ≤ its current size; `swarm_routing.py`
253
+ and `distillation.py` are unexported speculative code — delete or ship, don't carry.
254
+
255
+ ---
256
+
257
+ ## Analysis 6 — Competitive teardown
258
+
259
+ **LangGraph** — Better: durable checkpointing/persistence, human-in-the-loop that survives
260
+ process death, LangSmith tracing, Studio, massive ecosystem and mindshare. Worse: dependency
261
+ weight, conceptual overhead (channels, reducers, compiled graphs) for what is often a
262
+ 5-node DAG, hard to audit. **Structural gap agentflow owns:** LangGraph can never be
263
+ small — its value *is* the ecosystem. "Read the whole orchestrator before prod" is
264
+ permanently unavailable to them.
265
+
266
+ **CrewAI** — Better: marketing, onboarding, templates/personas, huge top-of-funnel. Worse:
267
+ magic-heavy abstractions, weak typing, unpredictable token burn, hard to reason about
268
+ execution order. **Gap:** engineering rigor + cost transparency. CrewAI's audience is
269
+ prototypers; it structurally won't chase `mypy --strict` teams, and per-agent USD
270
+ accounting undercuts them where they're weakest.
271
+
272
+ **PydanticAI** — the dangerous one. Better: typed agent I/O done right, real multi-provider
273
+ abstraction (native Anthropic/Gemini/Bedrock), the Pydantic team's trust halo, growing
274
+ fast. Worse: orchestration is not its center of gravity — multi-agent parallel DAG
275
+ composition means bolting on pydantic-graph, which is heavier and less legible than
276
+ `pipe.add(x, depends_on=[...])`. **Gap:** declarative parallel DAG + cost/budget
277
+ primitives. But note: if agentflow doesn't fix its typed data plane, PydanticAI wins the
278
+ comparison on agentflow's own claimed strength. This is why Analysis 2a is urgent.
279
+
280
+ **Raw asyncio** — Better: zero dependencies, zero abstraction tax, infinitely flexible.
281
+ Worse: every team re-writes retries, backoff-with-Retry-After, cost tables, cache keys,
282
+ event streams, timeout handling — badly, under deadline. **Gap:** agentflow's honest
283
+ one-line pitch against DIY: *"the 1,700 lines you were going to write around
284
+ `asyncio.gather` anyway, already typed and tested."* This baseline, not LangGraph, is the
285
+ real competitor for the target user — position against it explicitly.
286
+
287
+ ---
288
+
289
+ ## Next 2 weeks — concrete actions
290
+
291
+ 1. **Fix version hygiene** (1 hr): single-source `__version__` from package metadata;
292
+ verify pyproject/`__init__`/git tag agree; add a CI check that fails on mismatch.
293
+ 2. **Execute the scope cut** (1–2 days): remove `sandbox`, `triggers`/`serve`,
294
+ `swarm_routing`, `distillation` from the core package (extras or delete); shrink
295
+ `__all__` to the narrow story; update README to match reality.
296
+ 3. **Fix the shared-agent mutation bug** (½ day): `RunContext` passed through `execute()`
297
+ replacing `set_session`/`set_approval_policy` mutation; regression test with two
298
+ concurrent `run()`s sharing an agent.
299
+ 4. **Type the data plane** (2–3 days): `LLMResponse` model, `context: dict[str, Any]`,
300
+ `output_schema` results flow to downstream agents as model instances; deprecation
301
+ shims for the dict access.
302
+ 5. **Rename `total_duration` → `agent_seconds`, add `wall_time`** (1 hr, with shim).
303
+ 6. **Build `examples/earnings_triage.py`** (1–2 days) and make its real output the README
304
+ centerpiece; record the cache-hit second run.
305
+ 7. **Write `PUBLIC_API.md`** (½ day): covered surface, deprecation policy, 1.0 criteria.
306
+ 8. **Ship `OTelHooks`** (½ day): one adapter class in `contrib/`, documented with a
307
+ Jaeger screenshot.
308
+
309
+ **The blunt bottom line:** the core four files are genuinely good — better engineered than
310
+ most 0.x agent libraries. The project's risk is not code quality; it's that it behaves
311
+ like four different projects sharing a repo, with trust-hygiene cracks a reviewer spots in
312
+ minutes. Cut to the wedge, type the data plane, fix the hour-long embarrassments, and
313
+ there is a real (if narrow) adoption path. Keep accreting pillars, and PydanticAI ends the
314
+ story by default.