agentx-dev 3.3.0__tar.gz → 3.4.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 (73) hide show
  1. {agentx_dev-3.3.0 → agentx_dev-3.4.0}/CHANGELOG.md +709 -566
  2. {agentx_dev-3.3.0/agentx_dev.egg-info → agentx_dev-3.4.0}/PKG-INFO +96 -3
  3. agentx_dev-3.3.0/PKG-INFO → agentx_dev-3.4.0/README.md +1156 -1127
  4. {agentx_dev-3.3.0 → agentx_dev-3.4.0}/agentx_dev/Agents/__init__.py +10 -10
  5. {agentx_dev-3.3.0 → agentx_dev-3.4.0}/agentx_dev/AsyncTools.py +160 -160
  6. {agentx_dev-3.3.0 → agentx_dev-3.4.0}/agentx_dev/AutoSetup.py +164 -164
  7. {agentx_dev-3.3.0 → agentx_dev-3.4.0}/agentx_dev/Cache.py +471 -471
  8. {agentx_dev-3.3.0 → agentx_dev-3.4.0}/agentx_dev/ChatModel.py +1910 -1583
  9. {agentx_dev-3.3.0 → agentx_dev-3.4.0}/agentx_dev/Config.py +144 -144
  10. {agentx_dev-3.3.0 → agentx_dev-3.4.0}/agentx_dev/DefaultTools.py +2053 -2026
  11. {agentx_dev-3.3.0 → agentx_dev-3.4.0}/agentx_dev/Embeddings.py +681 -681
  12. {agentx_dev-3.3.0 → agentx_dev-3.4.0}/agentx_dev/Evals.py +514 -514
  13. {agentx_dev-3.3.0 → agentx_dev-3.4.0}/agentx_dev/Handoffs.py +420 -420
  14. agentx_dev-3.4.0/agentx_dev/Media.py +500 -0
  15. {agentx_dev-3.3.0 → agentx_dev-3.4.0}/agentx_dev/Memory.py +474 -474
  16. {agentx_dev-3.3.0 → agentx_dev-3.4.0}/agentx_dev/Observability.py +494 -494
  17. {agentx_dev-3.3.0 → agentx_dev-3.4.0}/agentx_dev/Runner/AgentRun.py +2870 -2683
  18. {agentx_dev-3.3.0 → agentx_dev-3.4.0}/agentx_dev/Runner/AsyncAgentRun.py +1061 -1008
  19. {agentx_dev-3.3.0 → agentx_dev-3.4.0}/agentx_dev/Runner/__init__.py +13 -13
  20. {agentx_dev-3.3.0 → agentx_dev-3.4.0}/agentx_dev/Runner/promptTemplate.yaml +139 -139
  21. {agentx_dev-3.3.0 → agentx_dev-3.4.0}/agentx_dev/Streaming.py +297 -297
  22. {agentx_dev-3.3.0 → agentx_dev-3.4.0}/agentx_dev/Supervisor.py +1937 -1922
  23. {agentx_dev-3.3.0 → agentx_dev-3.4.0}/agentx_dev/Tools.py +246 -246
  24. {agentx_dev-3.3.0 → agentx_dev-3.4.0}/agentx_dev/VectorStores/qdrant_store.py +239 -239
  25. {agentx_dev-3.3.0 → agentx_dev-3.4.0}/agentx_dev/WebTools.py +501 -501
  26. {agentx_dev-3.3.0 → agentx_dev-3.4.0}/agentx_dev/__init__.py +4 -0
  27. {agentx_dev-3.3.0 → agentx_dev-3.4.0}/agentx_dev/resources/__init__.py +3 -3
  28. agentx_dev-3.3.0/README.md → agentx_dev-3.4.0/agentx_dev.egg-info/PKG-INFO +159 -2
  29. {agentx_dev-3.3.0 → agentx_dev-3.4.0}/agentx_dev.egg-info/SOURCES.txt +1 -0
  30. {agentx_dev-3.3.0 → agentx_dev-3.4.0}/pyproject.toml +70 -70
  31. {agentx_dev-3.3.0 → agentx_dev-3.4.0}/AGENTX.md +0 -0
  32. {agentx_dev-3.3.0 → agentx_dev-3.4.0}/CONTRIBUTING.md +0 -0
  33. {agentx_dev-3.3.0 → agentx_dev-3.4.0}/LICENSE +0 -0
  34. {agentx_dev-3.3.0 → agentx_dev-3.4.0}/MANIFEST.in +0 -0
  35. {agentx_dev-3.3.0 → agentx_dev-3.4.0}/agentx_dev/Agents/Agent.py +0 -0
  36. {agentx_dev-3.3.0 → agentx_dev-3.4.0}/agentx_dev/Compiler.py +0 -0
  37. {agentx_dev-3.3.0 → agentx_dev-3.4.0}/agentx_dev/Loader.py +0 -0
  38. {agentx_dev-3.3.0 → agentx_dev-3.4.0}/agentx_dev/MCP.py +0 -0
  39. {agentx_dev-3.3.0 → agentx_dev-3.4.0}/agentx_dev/Planner.py +0 -0
  40. {agentx_dev-3.3.0 → agentx_dev-3.4.0}/agentx_dev/Session.py +0 -0
  41. {agentx_dev-3.3.0 → agentx_dev-3.4.0}/agentx_dev/Splitters.py +0 -0
  42. {agentx_dev-3.3.0 → agentx_dev-3.4.0}/agentx_dev/VectorStores/__init__.py +0 -0
  43. {agentx_dev-3.3.0 → agentx_dev-3.4.0}/agentx_dev/VectorStores/chroma_store.py +0 -0
  44. {agentx_dev-3.3.0 → agentx_dev-3.4.0}/agentx_dev/VectorStores/pg_store.py +0 -0
  45. {agentx_dev-3.3.0 → agentx_dev-3.4.0}/agentx_dev/resources/promptTemplate.yaml +0 -0
  46. {agentx_dev-3.3.0 → agentx_dev-3.4.0}/agentx_dev.egg-info/dependency_links.txt +0 -0
  47. {agentx_dev-3.3.0 → agentx_dev-3.4.0}/agentx_dev.egg-info/requires.txt +0 -0
  48. {agentx_dev-3.3.0 → agentx_dev-3.4.0}/agentx_dev.egg-info/top_level.txt +0 -0
  49. {agentx_dev-3.3.0 → agentx_dev-3.4.0}/examples/README.md +0 -0
  50. {agentx_dev-3.3.0 → agentx_dev-3.4.0}/examples/agentic_rag_demo.py +0 -0
  51. {agentx_dev-3.3.0 → agentx_dev-3.4.0}/examples/async_example.py +0 -0
  52. {agentx_dev-3.3.0 → agentx_dev-3.4.0}/examples/async_quickstart.py +0 -0
  53. {agentx_dev-3.3.0 → agentx_dev-3.4.0}/examples/auto_features_example.py +0 -0
  54. {agentx_dev-3.3.0 → agentx_dev-3.4.0}/examples/caching_example.py +0 -0
  55. {agentx_dev-3.3.0 → agentx_dev-3.4.0}/examples/chatbot_example.py +0 -0
  56. {agentx_dev-3.3.0 → agentx_dev-3.4.0}/examples/complete_example.py +0 -0
  57. {agentx_dev-3.3.0 → agentx_dev-3.4.0}/examples/concurrent_example.py +0 -0
  58. {agentx_dev-3.3.0 → agentx_dev-3.4.0}/examples/concurrent_tool_example.py +0 -0
  59. {agentx_dev-3.3.0 → agentx_dev-3.4.0}/examples/file_agent_demo.py +0 -0
  60. {agentx_dev-3.3.0 → agentx_dev-3.4.0}/examples/function_calling_demo.py +0 -0
  61. {agentx_dev-3.3.0 → agentx_dev-3.4.0}/examples/mcp_demo.py +0 -0
  62. {agentx_dev-3.3.0 → agentx_dev-3.4.0}/examples/mcp_github_triage_demo.py +0 -0
  63. {agentx_dev-3.3.0 → agentx_dev-3.4.0}/examples/observability_example.py +0 -0
  64. {agentx_dev-3.3.0 → agentx_dev-3.4.0}/examples/orchestration_demo.py +0 -0
  65. {agentx_dev-3.3.0 → agentx_dev-3.4.0}/examples/planner_example.py +0 -0
  66. {agentx_dev-3.3.0 → agentx_dev-3.4.0}/examples/robust_link_scraper.py +0 -0
  67. {agentx_dev-3.3.0 → agentx_dev-3.4.0}/examples/supervisor_codebase_analysis_demo.py +0 -0
  68. {agentx_dev-3.3.0 → agentx_dev-3.4.0}/examples/supervisor_example.py +0 -0
  69. {agentx_dev-3.3.0 → agentx_dev-3.4.0}/examples/sync_quickstart.py +0 -0
  70. {agentx_dev-3.3.0 → agentx_dev-3.4.0}/examples/v3_1_1_features_demo.py +0 -0
  71. {agentx_dev-3.3.0 → agentx_dev-3.4.0}/examples/v3_1_comprehensive_demo.py +0 -0
  72. {agentx_dev-3.3.0 → agentx_dev-3.4.0}/examples/v3_1_features_demo.py +0 -0
  73. {agentx_dev-3.3.0 → agentx_dev-3.4.0}/setup.cfg +0 -0
@@ -1,566 +1,709 @@
1
- # Changelog
2
-
3
- All notable changes to `agentx-dev` are documented here. Format loosely
4
- follows [Keep a Changelog](https://keepachangelog.com/); versioning is
5
- [Semver](https://semver.org/).
6
-
7
- ## [3.3.0] - 2026-08-19
8
-
9
- Dependency DAGs for the Supervisor. Plans declare which steps consume
10
- which, and the executor derives ordering, parallelism, AND context
11
- routing from those edges — unifying the old split where sequential
12
- mode had threading but no parallelism and concurrent mode had
13
- parallelism but no threading. Design: docs/design/3.3-depends-on-dag.md.
14
-
15
- ### Added
16
-
17
- - **`depends_on` plan steps.** Every plan step now carries an `id`;
18
- a step that consumes an earlier step's output lists that id in
19
- `depends_on`. Sync `Supervisor` executes in stable topological
20
- order; `AsyncSupervisor` runs a completion-driven scheduler where a
21
- step starts the MOMENT its dependencies finish (not on wave
22
- barriers) and independent steps run concurrently. In DAG mode each
23
- step is threaded ONLY its direct dependencies' results — explicit
24
- routing instead of "everything prior", which also stops the
25
- per-entry context budget shrinking as plans grow.
26
-
27
- - **Plan sanitization that never fails a run.** Missing/duplicate ids
28
- auto-assigned, unknown dependencies dropped (a planner typo degrades
29
- to a root step, not a dead run), self-deps dropped, cycles broken
30
- deterministically (back-edge in plan order), spawn steps cannot be
31
- depended on. If repairs were needed, the plan is re-requested once
32
- (`max_plan_retries`, default 1) with the repair warnings appended;
33
- the sanitized original is kept when the retry is no better.
34
-
35
- - **Failure cascade + `skipped` flag.** A step whose dependency failed
36
- (after retries / success-check) is skipped, transitively, with
37
- `SubtaskResult.skipped=True` and an error naming the failed dep.
38
- Independent branches keep running; synthesis runs over what
39
- succeeded. Skipped steps never dispatch — no tokens burned
40
- downstream of garbage.
41
-
42
- - **`skip_when` conditional execution.** A step may declare
43
- `{"step": <direct dep id>, "field": <typed output field>, "is": <value>}`;
44
- evaluated in Python (no LLM call) against the dependency's 3.2
45
- structured output, dotted paths supported, strictly FAIL-OPEN (any
46
- doubt → the step runs). Condition-skips do NOT cascade — dependents
47
- treat them as empty successes ("retrieval unnecessary" is not
48
- "answering impossible").
49
-
50
- - **`Specialist` registry entries.** `agents={}` now also accepts
51
- `Specialist(description, runner, depends_on=[...names...],
52
- output_schema=..., when_to_use=...)`. The extras render into the
53
- planning catalog (`typically after:` / `returns: Schema(fields)` /
54
- `use when:`) so the planner can write real dependency graphs and
55
- `skip_when` conditions against actual field names. `depends_on`
56
- here is a planner HINT, never an execution constraint (the same
57
- specialist can appear twice in one plan; step-ids disambiguate).
58
- Classic `(description, runner)` tuples keep working — they are
59
- wrapped internally, and `Specialist` tuple-unpacks for older code.
60
-
61
- - **`AsyncSupervisor(max_parallel=N)`.** Caps concurrent sub-tasks for
62
- rate-limited deployments. `sequential=True` is now sugar for
63
- `max_parallel=1` with all-prior threading.
64
-
65
- - **`SubtaskResult.step_id` / `.depends_on` / `.skipped`** and a
66
- `step_id` field on `dispatch` / `subtask_result` stream events.
67
-
68
- ### Backward compatibility
69
-
70
- A plan where NO step declares `depends_on` runs with byte-identical
71
- legacy semantics: sync + async-sequential thread all prior results in
72
- plan order; async-concurrent runs everything at once with no
73
- threading. Verified by regression tests against the 3.2 behaviour.
74
-
75
- ## [3.2.0] - 2026-08-13
76
-
77
- Typed multi-agent pipelines. Specialists can now declare a Pydantic
78
- output schema once and pass validated instances to each other through
79
- the Supervisor, instead of downstream agents re-parsing prose.
80
-
81
- ### Added
82
-
83
- - **`output_schema` on the `AgentRunner` / `AsyncAgentRunner`
84
- constructor.** Declare the runner's output shape once
85
- (`AgentRunner(..., output_schema=QueryIntent)`) instead of passing it
86
- on every call or describing JSON in the prompt. A per-call
87
- `output_schema=` still wins when both are set. `None` keeps the exact
88
- pre-3.2 behaviour: no coercion, `completion.output` stays `None`.
89
-
90
- - **Schema coercion via forced native function calling.** When a schema
91
- is in play, the final answer is converted by forcing a provider-native
92
- tool call against the schema (constrained decoding), not by regexing
93
- JSON out of prose. The ReAct loop itself is untouched: tool selection
94
- and intermediate reasoning run exactly as before, and the coercion
95
- happens once, after the loop finishes. Models without a
96
- `call_with_tools` implementation fall back to the previous text-JSON
97
- parsing, so custom `BaseChatModel` subclasses keep working.
98
- `completion.content` keeps the human-readable answer alongside
99
- `completion.output` in every case.
100
-
101
- - **`SubtaskResult.output`.** The Supervisor now preserves each
102
- specialist's validated Pydantic instance next to its `content` text.
103
- Consumers that only read `content` are unaffected.
104
-
105
- - **Structured specialist-to-specialist handoff.** When an earlier step
106
- produced typed output, `_build_augmented_query` serializes it into the
107
- next specialist's context as a labelled JSON block
108
- (`STRUCTURED OUTPUT (QueryIntent): {...}`) followed by the summary
109
- text, so downstream steps parse fields rather than interpreting
110
- sentences like `INTENT: ... SEARCH_QUERY: ...`.
111
-
112
- - **`vector_search_tool` pipeline options.** New kwargs:
113
- `max_text_chars` (default 500; pass `0` for full untruncated passages,
114
- which a reranker judging evidence actually needs) and
115
- `structured_output` (default False; when True the tool returns a JSON
116
- array of `{id, text, vector_score, metadata}` instead of the
117
- human-formatted list). Defaults preserve existing behaviour byte-for-
118
- byte.
119
-
120
- ### Fixed
121
-
122
- - **Supervisor planning prompt contradicted the execution engine.** The
123
- planner rule said sub-agents "do NOT see previous steps' output" and
124
- discouraged dependency chains, but the dispatcher has threaded prior
125
- findings into every step since `_build_augmented_query` shipped.
126
- The rule now tells the planner that sequential steps receive earlier
127
- results (structured when available) and that chains like
128
- intent -> retrieval -> reranking are a good plan shape, while still
129
- requiring same-specialist steps to merge and banning report-only steps.
130
-
131
- ## [3.1.7] — 2026-07-27
132
-
133
- ### Changed
134
-
135
- - **`use_function_calling` default flipped to auto-detect** on
136
- `AgentRunner` / `AsyncAgentRunner`. The parameter's default type is
137
- now `Optional[bool] = None`; `None` resolves to `True` when the
138
- model class overrides `BaseChatModel.call_with_tools` (both `GPT`
139
- and `Claude` do) and to `False` when it doesn't (or when
140
- `bind_tools_natively=True`). Callers passing `True`/`False`
141
- explicitly are unaffected. Rationale: text-mode ReAct requires the
142
- model to emit strict JSON with any long `action_input` string
143
- properly escaped — a 1200-word markdown draft with unescaped
144
- newlines or quotes reliably breaks `json.loads` and killed the run.
145
- Function-calling mode routes the parser through the SDK's typed
146
- channel so escaping is handled automatically. The historical
147
- default (`False`) was the fragile option; the new default matches
148
- what most users actually want.
149
-
150
- ### Fixed
151
-
152
- - **Malformed parser JSON no longer crashes the run.** When the
153
- text-mode assistant response failed `json.loads` (typically because
154
- a long `action_input` string had unescaped `"`, `\n`, or backticks),
155
- the framework used to raise `JSONDecodeError` and unwind the whole
156
- invocation. The runner now (1) tries a regex-based salvage that
157
- extracts `{Thought, action, action_input}` from the raw text
158
- covering the common "outer envelope valid, inner string broke
159
- escaping" failure, and (2) if salvage fails, feeds a targeted fix
160
- hint back to the model (`"your last response was not valid JSON;
161
- emit …, escape newlines as \n"`) and continues the loop bounded
162
- by `max_iterations`. Exhaustion returns a clear framework message
163
- rather than an uncaught exception. Applied to both sync and async
164
- runners via a shared `_salvage_react_json` helper.
165
- The salvager's action-name regex is intentionally strict
166
- (`[A-Za-z_][A-Za-z0-9_.\- ]{0,79}`) so it can't hallucinate an
167
- "action" out of an unrelated `"key":"value"` pair inside malformed
168
- JSON.
169
-
170
- - **Verbose trace in `bind_tools_natively` mode now prints tool
171
- name + args + response.** Previously native runs showed blank
172
- `[tool.call.start]` / `[tool.call.complete]` pairs (the
173
- observability layer fires them without the trace context), so you
174
- couldn't tell which tool the model actually invoked or what came
175
- back. The runner now prints `[tool] Invoking '<name>' with args:
176
- <input>` and `[tool] Response: <preview>` (or `[tool] Error: ...`
177
- when the dispatch raised) in the post-dispatch loop, matching the
178
- format text-mode and function-calling mode use. Mirrored to the
179
- async runner.
180
-
181
- - **`web_fetch_tool(vector_store=...)` auto-ingests fetched pages into
182
- a vector store** instead of dumping raw HTML into the model's
183
- context. Fixes the TPM-limit trap: when a research agent fetches
184
- four articles in parallel (via ``multi_tool_use.parallel`` or
185
- native binding), the combined bodies can easily exceed 40k tokens
186
- and blow past a 30k TPM ceiling on the very next model call.
187
- New parameters on ``web_fetch_tool``:
188
-
189
- | Kwarg | Default | Effect |
190
- |---|---|---|
191
- | ``vector_store`` | ``None`` | When set, each fetch is HTML-stripped, chunked with ``TextSplitter``, embedded via the store's embeddings, and added with ``{src: url, chunk_index, total_chunks}`` metadata. The tool response becomes a compact summary (URL, byte count, chunk count, 240-char preview) — NOT the raw body. The model then calls ``vector_search`` / ``Rag`` to pull only the passages it needs. |
192
- | ``chunk_size`` | ``1500`` | Characters per chunk when ``vector_store`` is set. Ignored otherwise. |
193
- | ``chunk_overlap`` | ``200`` | Overlap between adjacent chunks so a fact spanning a boundary is still retrievable. Ignored otherwise. |
194
-
195
- Backwards-compatible: the positional ``cache_dir`` signature keeps
196
- working; `web_fetch_tool()` with no ``vector_store`` returns raw
197
- body as before. Ingest and cache_dir compose — enable both and get
198
- disk-cached full bodies AND searchable chunks. HTML stripping is
199
- minimal and dependency-free (regex-based: script/style blocks
200
- dropped whole, then tags stripped, whitespace collapsed) so the
201
- ingest path adds no new install dependency. On JSON/plain-text
202
- responses the stripper is a near no-op.
203
-
204
- The observation returned to the model shows topical coverage --
205
- first, middle, and last chunk previews (up to 3 samples,
206
- deduplicated for short pages) -- so the model can tell what
207
- topics the page actually covers, not just the intro paragraph.
208
- Without this the model would only see the page's opening and
209
- wouldn't know to query for topics discussed later in the same
210
- page. Explicit instruction in the observation ("query with
211
- SPECIFIC keywords from the topics above; do NOT re-fetch; do
212
- NOT ask for the full body") steers the model toward the RAG path
213
- on follow-up turns.
214
-
215
- - **`multi_tool_use.parallel` now reaches its dispatch path.**
216
- When GPT wanted to batch several tool calls into one turn (fetch N
217
- URLs concurrently, run M searches at once), it emitted OpenAI's
218
- synthetic `multi_tool_use.parallel` meta-tool. The registry's
219
- `_dispatch_multi_parallel` / `_adispatch_multi_parallel` handlers
220
- already knew how to unpack it, but the runner loop's known-tools
221
- guardrail rejected the name FIRST as unregistered — dumping the
222
- raw `{"tool_uses": [...]}` payload into the user-facing "final
223
- answer" and never invoking any of the nested calls. Added
224
- `multi_tool_use.parallel` to the recognized action set in both
225
- sync and async runners so the meta-tool flows through to dispatch
226
- and the existing unpackers run. Nested calls with the `functions.`
227
- prefix are normalized before dispatch (same as top-level FC
228
- calls), so the model can emit either shape.
229
-
230
- - **`Permissions.full_access` / `read_only` auto-wrap a bare string.**
231
- Passing `full_access("./workspace")` used to iterate the string
232
- into 11 single-character "subtrees" (Python's `list("./workspace")`)
233
- — every path check silently rejected because no real path could
234
- ever match a `"."` or `"/"` "allowed subtree". The classmethod
235
- now detects a bare string and treats it as `[allowed_paths]`, so
236
- `full_access("./workspace")` does the intuitive thing (equivalent
237
- to `full_access(["./workspace"])` and auto-infers the workspace).
238
- Same fix on `read_only`. List inputs are unchanged.
239
-
240
- - **`Permissions.full_access` now accepts (and auto-infers)
241
- `workspace`.** The classmethod set `allowed_paths` but not
242
- `workspace`, so short paths like `write_file(path="report.md")`
243
- resolved to CWD (outside the sandbox) and raised
244
- `PermissionError: access denied` — a landmine that every caller of
245
- `Permissions.full_access(["./workspace"])` hit sooner or later.
246
- New signature: `full_access(allowed_paths, *, workspace=None)`.
247
- When `workspace` isn't passed AND `allowed_paths` has exactly one
248
- entry, that path is auto-set as the workspace (the "project-scoped
249
- agent whose one allowed subtree IS its workspace" case, which is
250
- 99% of use). Two or more paths stay ambiguous and require an
251
- explicit `workspace=` if short-path resolution is wanted. Pass an
252
- explicit `workspace=` string to override the auto-choice.
253
- Backwards-compatible on the positional signature; adds a keyword
254
- argument that existing callers didn't use.
255
-
256
- ## [3.1.5] — 2026-07-26
257
-
258
- ### Fixed
259
-
260
- - **Text-mode tool results no longer use `role: "function"`.** In text
261
- mode (the default — no `use_function_calling`) the runner fed each tool
262
- observation back to the model as a `role: "function"` message. Newer
263
- OpenAI models reject that role outright (`400 … 'messages[N].role' does
264
- not support 'function' with this model`, e.g. gpt-5.x), and Anthropic
265
- never accepted it — text-mode multi-tool runs on Claude were latently
266
- broken too; older GPT models simply still tolerated the legacy role.
267
- Tool observations now go back as a plain `role: "user"` turn framed as
268
- `Observation: …`, which every provider and model generation accepts and
269
- which matches the ReAct template's own few-shot convention.
270
- Function-calling mode is unchanged (native `role: "tool"` +
271
- `tool_call_id`). The async runner was additionally emitting `function`
272
- unconditionally (even in FC mode); it now uses the same shared helper.
273
-
274
- ### Added
275
-
276
- - **`subtask_success_check` on `Supervisor` / `AsyncSupervisor`.** Opt-in
277
- predicate `(SubtaskResult) -> bool | str` that decides whether a
278
- *returned* (non-raised) sub-task result is actually acceptable — the
279
- "ran fine but produced nothing useful" case a plain retry can't catch
280
- (a scraper that saved 0 links, an extractor that found nothing). Return
281
- `True` to accept, or `False`/a `str` reason to reject; a rejected
282
- result is retried like a raised error, with the reason fed back into the
283
- query, bounded by `max_subtask_retries`. After retries are exhausted the
284
- last result is returned with its `error` set (content preserved). A
285
- check that itself raises is treated as "accept" so a buggy predicate
286
- can't wedge the run. Default `None` keeps the exceptions-only behavior.
287
- New example `examples/robust_link_scraper.py` wires it together with a
288
- scraping `system_addendum` (parse relative+absolute hrefs, fall back to
289
- `sitemap.xml` on JS-rendered sites).
290
-
291
- ## [3.1.4] — 2026-07-26
292
-
293
- ### Fixed
294
-
295
- - **A malformed-JSON tool argument no longer crashes the whole agent
296
- run.** When a model emitted a Python snippet or a Windows path as a
297
- tool-call argument — `re.findall(r'\d+')`, `C:\Users` — the `\d` / `\U`
298
- are illegal JSON escapes, and the OpenAI adapter's eager
299
- `json.loads(call.function.arguments)` raised `JSONDecodeError` and
300
- `raise`d it, unwinding the entire ReAct loop before the agent's own
301
- retry machinery could act. Under a Supervisor this surfaced as a bare
302
- `ERROR: Invalid \escape: line 1 column 598` and the sub-task was
303
- abandoned. Now:
304
- - `_parse_tool_arguments` repairs the common case (backslashes that
305
- don't begin a valid JSON escape are doubled), recovering `\d`,
306
- `\w`, `\s`, etc. with zero extra round-trips. A backslash before a
307
- valid-escape letter (`\b`, `\n`, …) remains ambiguous and is left
308
- as the escape — a documented limit.
309
- - When repair fails, `call_with_tools` returns a dedicated
310
- `invalid_tool_args` result and the loop feeds the error back as a
311
- retryable observation ("your arguments weren't valid JSON — escape
312
- backslashes and resend"), bounded by `max_iterations`, in all
313
- three modes across both `AgentRunner` and `AsyncAgentRunner`.
314
- `Claude` was already immune (its tool inputs arrive pre-parsed).
315
-
316
- - **A tool-call preamble is no longer returned as the final answer.**
317
- Models routinely end a turn with an announcement instead of an action
318
- — "I'll look up your recent scores to get a clear view of your
319
- communication skills. Just a second!" — and every "no tool call
320
- found" branch in both runners was coded as *this text is the answer,
321
- break*. The loop terminated on iteration 1 and the caller got a
322
- promise instead of a result. Three sites per runner were affected:
323
- the native-binding path (`type != "tool_use"`), the
324
- `use_function_calling` path (parser unresolved), and the JSON-text
325
- path (response didn't parse). `max_iterations` never helped, because
326
- the break happened before any iteration was spent.
327
-
328
- The runner now feeds the model one corrective nudge — "your last turn
329
- had no action, so nothing happened; do it, don't announce it" — and
330
- continues the loop. Verified against both `AgentRunner` and
331
- `AsyncAgentRunner` in all three modes.
332
-
333
- ### Added
334
-
335
- - **Proactive "act, don't announce" system-prompt clause.** The reactive
336
- `text_turn_nudges` fix corrects an agent *after* it narrates instead of
337
- acting; this clause heads it off. When (and only when) an agent has
338
- tools, its system prompt now tells it to call the tool rather than
339
- reply "I'll do X / just a second" and stop — and to report what it DID
340
- in past tense. Injected in all three modes across both runners; skipped
341
- for tool-less chat agents, where prose is the correct answer. Sits
342
- before any `system_addendum` so a caller's role instructions still win.
343
-
344
- - **`max_subtask_retries` on `Supervisor` / `AsyncSupervisor`** (default
345
- `1`). A sub-task that raised used to be recorded as an error and the
346
- Supervisor moved straight to synthesis — no second attempt. Now a
347
- failed sub-task is re-dispatched up to this many times, with the prior
348
- error appended to the query so the specialist knows what to fix
349
- ("your previous attempt failed with X — diagnose and try again").
350
- Bounded and informed: only raised exceptions trigger a retry (a
351
- sub-task that returns content is accepted as-is, since the Supervisor
352
- can't tell "terse but correct" from "wrong"), and the error text is
353
- fed back rather than blindly re-running. Set to `0` for the old
354
- quit-on-first-failure behavior. Applies in both sequential and
355
- concurrent async modes.
356
-
357
- - **`text_turn_nudges` on `AgentRunner` / `AsyncAgentRunner`** (default
358
- `1`). Caps the re-prompts described above at one extra LLM call per
359
- run; after the budget is spent the model's text stands as the answer.
360
- Set to `0` for the previous behavior. Automatically skipped when no
361
- tools are registered, since a runner with no tools is a plain chat
362
- call and prose genuinely is the answer there.
363
-
364
- ## [3.1.3] — 2026-07-22
365
-
366
- Docs-only patch. No code changes since 3.1.2. Users on 3.1.2 don't
367
- need to upgrade for functionality; upgrade to pick up the improved
368
- onboarding docs bundled in the sdist.
369
-
370
- ### Documentation
371
-
372
- - **Tools doc rewritten to answer "how do I actually use these?"**
373
- Added §0 `How each built-in tool is registered` as the entry
374
- section. Two registration paths — auto vs manual — laid out in a
375
- table on the first screen. Six runnable subsections covering every
376
- combination:
377
- - §0.1 DefaultTools via `Permissions(...)` (auto)
378
- - §0.2 WebTools via `tools=[web_search_tool(), web_fetch_tool()]`
379
- - §0.3 RAG via `TextSplitter` -> `VectorStore.add_documents` ->
380
- `vector_search_tool(store)`
381
- - §0.4 Handoffs via `handoff_tool` + `HandoffCoordinator`
382
- - §0.5 Custom `StructuredTool` from scratch
383
- - §0.6 Fully-loaded runner combining all of the above
384
- - §0.7 Rules on name collisions, invisible-denied-capabilities,
385
- async-tool behavior
386
- The existing inventory + wrapper / controls / cheat-sheet sections
387
- are unchanged; they now sit after the "how to use them" primer
388
- instead of before it.
389
-
390
- ## [3.1.2] — 2026-07-22
391
-
392
- Patch release. Two independent fixes.
393
-
394
- ### Fixed
395
-
396
- - **`llm_judge` correctly parses YES/NO across providers.** The judge
397
- parser was comparing the reply's first word to the literal string
398
- `"YES"`. GPT-4o answers `"YES,"` (comma-suffixed), which failed the
399
- equality check and marked every genuine PASS as FAIL. Claude replies
400
- `"YES"` without punctuation so the bug hid during local development.
401
- Fixed by matching `\b(YES|NO)\b` (word-boundary regex, case-
402
- insensitive) at the start of the reply. Handles every real shape:
403
- `YES`, `YES.`, `YES!`, `YES, exactly right`, `Yes.`, `yes -- reason`.
404
- Ambiguous replies (`Maybe`, empty string) still fail closed.
405
- - Regression test `test_llm_judge_parses_various_verdict_shapes`
406
- covers 8 YES shapes, 5 NO shapes, and 4 ambiguous replies.
407
-
408
- ### Added
409
-
410
- - **`agentx_dev.Tools` is a one-stop tools namespace.** Users no
411
- longer need to remember which module each tool lives in:
412
-
413
- ```python
414
- from agentx_dev.Tools import (
415
- StandardTool, StructuredTool,
416
- AsyncStandardTool, AsyncStructuredTool,
417
- web_search_tool, web_fetch_tool,
418
- vector_search_tool, handoff_tool,
419
- DefaultTools, Permissions,
420
- )
421
- ```
422
-
423
- Both this form and the pre-existing `from agentx_dev import X`
424
- form coexist. Implementation uses PEP 562 module `__getattr__`
425
- and `__dir__` so re-exports are lazy (no import cost for modules
426
- the caller doesn't touch) and show up in IDE autocomplete +
427
- `dir(agentx_dev.Tools)`.
428
-
429
- ## [3.1.1] — 2026-07-21
430
-
431
- Second batch of 3.1 features + a full docs + brand pass.
432
-
433
- ### Added
434
-
435
- **Streaming through orchestration**
436
- - `Supervisor.stream()` / `AsyncSupervisor.astream()` emit
437
- `plan_start` / `plan` / `dispatch` / `subtask_result` /
438
- `synthesize_start` / `final` / `completion` events.
439
- - `HandoffCoordinator.stream()` / `.astream()` emit `invoke` /
440
- `completion` / `handoff` / `final` / `result` events per hop.
441
- - Legacy `.run()` / `.arun()` refactored to consume the streams (no
442
- code duplication).
443
-
444
- **Prompt optimization — `Compiled`**
445
- - New `agentx_dev.Compiler` module.
446
- - `Compiled(runner_factory, trainset, ...)` iteratively refines a
447
- runner's `system_addendum` against the eval harness. Half of
448
- DSPy's power at a tenth of the surface.
449
-
450
- **Anthropic Batch API**
451
- - `Claude.batch(requests)` submits many prompts at Anthropic's 50%-off
452
- batch rate, polls to completion, returns results in submission order.
453
- - Per-request error dicts on failure; token usage funneled into
454
- `TokenUsage` so cost tracking stays a single source of truth.
455
-
456
- **Vector store adapters — `agentx_dev.VectorStores`**
457
- - `ChromaVectorStore`, `QdrantVectorStore`, `PgVectorStore` — same
458
- public shape as the in-memory `VectorStore` (`add` / `search` /
459
- `delete` / `clear` / `__len__` / `embeddings`).
460
- - `vector_search_tool()` and `SemanticMemory` accept any of them.
461
- - SDK imports lazy; friendly `ImportError` when the underlying SDK
462
- is missing.
463
-
464
- **Trace viewer (`viewer/`)**
465
- - Self-hosted single-page app that reads `FileHook` JSONL and renders
466
- a timeline with type/text filters, summary sidebar, JSON drill-down.
467
- - Works from `file://`, no server required.
468
-
469
- **Docs site (`host/`)**
470
- - Full editorial dark-first design system (JetBrains Mono headings,
471
- Inter body, `#B8FF3E` electric-lime accent).
472
- - Command palette (`Cmd+K`) with keyboard navigation and live search.
473
- - Hero code snippet with hand-tinted syntax highlighting.
474
- - Reading progress bar, breadcrumbs, header anchor links.
475
- - Sidebar sliding active marker, collapsible groups.
476
- - Code copy buttons, language labels.
477
- - Right-rail auto-TOC with `IntersectionObserver` scrollspy.
478
- - Dark/light theme toggle, persisted.
479
- - Cache-busted assets so edits land on refresh without hard-reload.
480
-
481
- **Brand identity (`brand/`)**
482
- - Full brand kit: 5 SVG assets (`mark`, `mono`, `wordmark`, `logo-full`,
483
- `app-icon`), `BRAND.md` strategy doc, rendered brand-kit HTML deck.
484
- - Copy audit dropped "small" (weak) and "LangChain" references from
485
- all marketing surfaces.
486
- - Favicon wired into docs + trace viewer.
487
-
488
- **Test suite (`tests/`)**
489
- - Restored + expanded pytest suite: 127 tests passing (3 skipped for
490
- absent optional SDKs).
491
- - Coverage: parser + all `AgentType` variants, `ToolRegistry`
492
- (dispatch / dup-guard / circuit-breaker / timeout), Permissions
493
- (capability gating + sandbox + traversal), budgets (cost / rate /
494
- retry / non-retryable HTTP), runner loop (streaming + output_schema
495
- + chat history), embeddings + `VectorStore` + `SemanticMemory`,
496
- handoffs (bounded hops + history sanitization), evals harness
497
- (all assertion helpers + JSON case loaders), vector-store adapter
498
- shape conformance.
499
-
500
- **Docs (`docs/`)**
501
- - Full docs tree (34 pages), including new pages for:
502
- vector store adapters, prompt optimization, batch API, trace viewer,
503
- and a **use-cases** landing (13 concrete scenarios with runnable code).
504
- - Rewrote **Tools** page to enumerate every built-in tool with args,
505
- return shape, capability flag, and use-case guidance.
506
- - Rewrote **Agents** page to cover all four orchestration
507
- architectures (Solo / Supervisor / Handoffs / Compiled) with
508
- decision trees, worked examples, and cheat sheet.
509
- - **Agentic RAG chatbot** as use case §13 — multi-query decomposition,
510
- parallel retrieval, self-critique, citations, user memory.
511
-
512
- **Examples**
513
- - `examples/agentic_rag_demo.py` — the runnable version of the
514
- agentic RAG use case. Auto-seeds a KB if none exists, `--demo` flag
515
- runs a 3-turn scripted session proving user-notes recall works.
516
-
517
- **Package**
518
- - `[chroma]`, `[qdrant]`, `[pgvector]`, `[dev]` extras added.
519
- - `[anthropic]` bumped to `>=0.36` (Batch API + prompt cache).
520
-
521
- ### Fixed
522
-
523
- - `AgentRunner._iter_run` in `bind_tools_natively=True` mode uses a
524
- minimal system prompt instead of the AgentType template so the
525
- ReAct `action/action_input` scaffold no longer fights the native
526
- tool interface. Previously produced JSON-blob answers under GPT.
527
- - `HandoffCoordinator._sanitize_history_for_next_agent` strips tool
528
- and function role messages between hops so tool_call_ids from a
529
- previous agent don't leak into the next model's call (OpenAI 400).
530
- - Docs site marker positioning uses double-`requestAnimationFrame` +
531
- `document.fonts.ready` so the sidebar accent bar lands on the
532
- correct row even on a cold font cache.
533
- - Primary hero CTA color uses `#doc .hero-cta a.primary` selector to
534
- outrank `#doc a` link styling (previously rendered lime-on-lime
535
- and was invisible).
536
-
537
- ### Notes
538
-
539
- - Package version bumped from `3.0.6` to `3.1.1`. The 3.1.0 release
540
- did not ship publicly — 3.1.1 is the first 3.1-tagged PyPI release
541
- and includes both batches of features.
542
-
543
- ## [3.1.0] — internal only (commits 52840e7)
544
-
545
- First batch of 3.1 features. Committed but not released to PyPI.
546
- Merged into 3.1.1 for the public release.
547
-
548
- ### Added
549
- - Anthropic prompt caching (`Claude(enable_prompt_cache=True)`).
550
- - Parallel per-turn tool dispatch in `AgentRunner`
551
- (`bind_tools_natively=True`, `parallel_tool_workers`).
552
- - Semantic memory (`SemanticMemory`, embeddings-backed retrieval).
553
- - RAG core (`Embeddings`, `HashEmbeddings`, `OpenAIEmbeddings`,
554
- `VectorStore`, `VectorHit`, `vector_search_tool()`).
555
- - Agent-to-agent handoffs (`HandoffRequest`, `handoff_tool`,
556
- `HandoffCoordinator`, `HandoffResult`).
557
- - Evals harness (`EvalCase`, `EvalRunner`, `EvalReport`, 7 assertion
558
- helpers, JSON case loader, `python -m agentx_dev.Evals run` CLI).
559
- - `TokenUsage.cache_hit_ratio` property.
560
-
561
- ## [3.0.6] — 2026-03 (baseline)
562
-
563
- Security hardening baseline (SSRF guard on `web_fetch`, HMAC-signed
564
- persistent state, scrubbed subprocess env, path sanitizer,
565
- `permissions.json` mode 0o600, ReDoS guard on `grep`,
566
- `invoke`/`ainvoke` accept bare strings and message lists).
1
+ # Changelog
2
+
3
+ All notable changes to `agentx-dev` are documented here. Format loosely
4
+ follows [Keep a Changelog](https://keepachangelog.com/); versioning is
5
+ [Semver](https://semver.org/).
6
+
7
+ ## [3.4.0] - 2026-09-18
8
+
9
+ Media input for GPT and Claude, models that adapt to each generation's
10
+ parameter support, and workspace-rooted paths. Non-breaking; two
11
+ defaults changed -- see docs/guides/upgrading.md.
12
+
13
+ ### Added
14
+
15
+ - **Media input for GPT and Claude.** New `Media` type
16
+ (`Media.image()`, `.document()`, `.audio()`, `.from_path()`,
17
+ `.from_url()`, `.from_bytes()`) and a `media=[...]` argument on
18
+ `AgentRunner.invoke` / `stream` and `AsyncAgentRunner.ainvoke` /
19
+ `astream`. Items can be paths, URLs, `Media`, or content-part dicts
20
+ in either provider's native shape; each model renders them in its
21
+ own wire format (OpenAI `image_url` / `file` / `input_audio`,
22
+ Anthropic `image` / `document`). Unsupported combinations -- audio to
23
+ Claude, a document URL to GPT -- raise `ValueError` before any
24
+ request. Media is stored as plain JSON dicts, so `completion.history`
25
+ and `Session.save()` keep working. Guide: docs/guides/media.md.
26
+
27
+ - **Models adapt to per-generation parameter support.** `GPT` and
28
+ `Claude` read a parameter-compatibility 400 (which names the
29
+ parameter and, for enums, the allowed values), make the smallest
30
+ change it asks for, retry, and remember it per model: an unsupported
31
+ `reasoning_effort` moves to the nearest supported value, `max_tokens`
32
+ becomes `max_completion_tokens` on reasoning models (up front for
33
+ known families), parameters a model lacks are dropped, and Claude's
34
+ `max_tokens` is clamped to the model's cap. Logged at WARNING; other
35
+ 400s raise unchanged. `adapt_params=False` opts out.
36
+
37
+ - **`Claude` gains `top_p`, `top_k`, `thinking`, `stop_sequences`.**
38
+
39
+ ### Changed
40
+
41
+ - `Claude(temperature=)` defaults to `None` (not sent; the API default is
42
+ the old 1.0). Always sending it conflicted with `top_p` on newer models
43
+ and with extended thinking.
44
+ - `GPT(reasoning_effort=)` accepts any string, not just
45
+ `none | low | medium | high`, so `minimal` and `xhigh` are expressible.
46
+
47
+ ### Fixed
48
+
49
+ - **`reasoning_effort="none"` crashed on models that don't take it**
50
+ ("Unsupported value: 'reasoning_effort' does not support 'none' with
51
+ this model"). The models docs even recommended `"none"` as a fix for
52
+ tool-calling conflicts; that advice is gone.
53
+ - **Workspace-rooted paths resolved to the drive root.** With a workspace
54
+ set, `/bruce.jpeg` became `C:\bruce.jpeg` on Windows (a leading slash
55
+ with no drive means the current drive's root) and the sandbox rejected
56
+ it. A leading slash now means the workspace root; the re-rooted path
57
+ is still sandbox-checked, so traversal is rejected as before.
58
+ - **`run_python` ran in the host process's directory, not the
59
+ workspace.** It passed no `cwd`, so `open("bruce.jpeg")` looked
60
+ wherever the program was launched from. It now starts in the workspace,
61
+ matching `run_shell`.
62
+ - **Message-list input stringified media into the prompt.** A user turn
63
+ whose content was a list (text + image) went through `str()`, so an
64
+ attached image reached the model as base64 text. Text and media are
65
+ now split, and earlier turns keep their list content.
66
+ - **Claude returned the wrong block with extended thinking.** It read
67
+ `response.content[0].text`; with thinking the first block is a
68
+ `thinking` block. Text blocks are now joined.
69
+ - `GPT.Initialize` / `stream_text` / `astream_text` now run messages
70
+ through the same OpenAI translator as `call_with_tools`.
71
+
72
+ ## [3.3.1] - 2026-09-09
73
+
74
+ Correctness fixes in the agent loop, found by driving it with a
75
+ scripted model rather than reading it. No API changes; every fix
76
+ replaces a silent wrong answer or a runaway cost with correct
77
+ behaviour. Nine regression tests ship with them
78
+ (`tests/test_agent_loop_flaws.py`).
79
+
80
+ ### Fixed
81
+
82
+ - **Native binding had no loop-level spiral guard.** The repeat breaker
83
+ sat after the native branch's `continue`, so `bind_tools_natively`
84
+ runs never reached it: a model stuck re-issuing one call burned the
85
+ full `max_iterations` (20 LLM turns where text mode stopped at 3) and
86
+ returned the "hit max_iterations" recap quoting the dup-guard's own
87
+ warning text instead of the data.
88
+
89
+ - **`respond` batched with real tool calls dropped them.** A model that
90
+ emitted "do X, and here is my answer" in one turn had X silently
91
+ discarded -- never dispatched, never in `completion.tool_calls`, no
92
+ error. The batch now runs and the answer defers one turn.
93
+
94
+ - **`completion.history` could not be replayed as `chat_history`.** The
95
+ copy filter kept only truthy `{role, content}`, which dropped every
96
+ tool-calling assistant turn (`content=""`), stripped `tool_call_id`
97
+ off `role="tool"` messages, and replayed a stored system prompt on top
98
+ of the fresh one -- so a follow-up call sent two system messages, zero
99
+ assistant turns, and an orphaned tool message providers reject. Fixed
100
+ in both runners.
101
+
102
+ - **A sync `AgentRunner` silently ignored async tools.** `known_tools`
103
+ unioned only the two sync registry tables while the registry accepted,
104
+ listed and prompt-advertised async ones, so calling one fell through
105
+ to implicit-final and returned `action_input` as the answer with an
106
+ empty `tool_calls` list. Now unions all four, matching the async
107
+ runner; the sync dispatcher already returns a clear `ToolError`, so
108
+ the silent wrong answer became an actionable one.
109
+
110
+ - **`AsyncAgentRunner` regressed the function-calling message shape.**
111
+ It appended its FC turn as raw JSON text with no `tool_calls` block
112
+ and never set `_last_function_call_id`, so observations went back as
113
+ `role="user"` -- text-mode shape while in FC mode, losing provider
114
+ correlation and the cached prefix.
115
+
116
+ - **A `BaseException` from a specialist orphaned its siblings.**
117
+ `CancelledError` and `KeyboardInterrupt` are not `Exception`, so
118
+ `_run_subtask`'s handler never saw them; `t.result()` re-raised and
119
+ left sibling tasks running unowned, with in-flight LLM calls still
120
+ billing. The scheduler now owns its tasks in a `try`/`finally`, which
121
+ also covers a consumer that stops iterating `astream` early.
122
+
123
+ - **The tool cache collided on tool name.** It is a process-wide
124
+ singleton keyed on `(tool_name, args)` with no record of which
125
+ implementation ran, so two runners whose tools merely share a name --
126
+ `search`, `fetch`, `query`, routine across Supervisor specialists --
127
+ served each other's results and the second function never ran. Keys
128
+ now fold in the callable's `module.qualname`, so same-name /
129
+ different-implementation misses while genuinely identical tools still
130
+ share, including across processes for the disk-backed `FileCache`.
131
+
132
+ ### Docs
133
+
134
+ - Streaming documentation described parameters the API rejects.
135
+ `AsyncAgentRunner.astream()` and `HandoffCoordinator.stream()` take no
136
+ `stream_tokens` (only the sync `AgentRunner.stream()` does), and every
137
+ `text_delta` example was built on a default model where
138
+ `use_function_calling` auto-detects to `True` and yields zero deltas.
139
+ `simple_stream` was documented with the wrong signature entirely.
140
+ Added a table naming exactly which stream methods accept what.
141
+
142
+ ### Tests
143
+
144
+ - Repaired 20 stale text-mode tests. `MockModel` defined
145
+ `call_with_tools` unconditionally, so 3.1.7's auto-detect routed every
146
+ text script down the function-calling path and the runner returned
147
+ `""`. The mock now advertises the capability only when scripted for
148
+ it. Suite: 206 passed, 3 skipped.
149
+
150
+ ## [3.3.0] - 2026-08-19
151
+
152
+ Dependency DAGs for the Supervisor. Plans declare which steps consume
153
+ which, and the executor derives ordering, parallelism, AND context
154
+ routing from those edges — unifying the old split where sequential
155
+ mode had threading but no parallelism and concurrent mode had
156
+ parallelism but no threading. Design: docs/design/3.3-depends-on-dag.md.
157
+
158
+ ### Added
159
+
160
+ - **`depends_on` plan steps.** Every plan step now carries an `id`;
161
+ a step that consumes an earlier step's output lists that id in
162
+ `depends_on`. Sync `Supervisor` executes in stable topological
163
+ order; `AsyncSupervisor` runs a completion-driven scheduler where a
164
+ step starts the MOMENT its dependencies finish (not on wave
165
+ barriers) and independent steps run concurrently. In DAG mode each
166
+ step is threaded ONLY its direct dependencies' results — explicit
167
+ routing instead of "everything prior", which also stops the
168
+ per-entry context budget shrinking as plans grow.
169
+
170
+ - **Plan sanitization that never fails a run.** Missing/duplicate ids
171
+ auto-assigned, unknown dependencies dropped (a planner typo degrades
172
+ to a root step, not a dead run), self-deps dropped, cycles broken
173
+ deterministically (back-edge in plan order), spawn steps cannot be
174
+ depended on. If repairs were needed, the plan is re-requested once
175
+ (`max_plan_retries`, default 1) with the repair warnings appended;
176
+ the sanitized original is kept when the retry is no better.
177
+
178
+ - **Failure cascade + `skipped` flag.** A step whose dependency failed
179
+ (after retries / success-check) is skipped, transitively, with
180
+ `SubtaskResult.skipped=True` and an error naming the failed dep.
181
+ Independent branches keep running; synthesis runs over what
182
+ succeeded. Skipped steps never dispatch — no tokens burned
183
+ downstream of garbage.
184
+
185
+ - **`skip_when` conditional execution.** A step may declare
186
+ `{"step": <direct dep id>, "field": <typed output field>, "is": <value>}`;
187
+ evaluated in Python (no LLM call) against the dependency's 3.2
188
+ structured output, dotted paths supported, strictly FAIL-OPEN (any
189
+ doubt → the step runs). Condition-skips do NOT cascade — dependents
190
+ treat them as empty successes ("retrieval unnecessary" is not
191
+ "answering impossible").
192
+
193
+ - **`Specialist` registry entries.** `agents={}` now also accepts
194
+ `Specialist(description, runner, depends_on=[...names...],
195
+ output_schema=..., when_to_use=...)`. The extras render into the
196
+ planning catalog (`typically after:` / `returns: Schema(fields)` /
197
+ `use when:`) so the planner can write real dependency graphs and
198
+ `skip_when` conditions against actual field names. `depends_on`
199
+ here is a planner HINT, never an execution constraint (the same
200
+ specialist can appear twice in one plan; step-ids disambiguate).
201
+ Classic `(description, runner)` tuples keep working — they are
202
+ wrapped internally, and `Specialist` tuple-unpacks for older code.
203
+
204
+ - **`AsyncSupervisor(max_parallel=N)`.** Caps concurrent sub-tasks for
205
+ rate-limited deployments. `sequential=True` is now sugar for
206
+ `max_parallel=1` with all-prior threading.
207
+
208
+ - **`SubtaskResult.step_id` / `.depends_on` / `.skipped`** and a
209
+ `step_id` field on `dispatch` / `subtask_result` stream events.
210
+
211
+ ### Backward compatibility
212
+
213
+ A plan where NO step declares `depends_on` runs with byte-identical
214
+ legacy semantics: sync + async-sequential thread all prior results in
215
+ plan order; async-concurrent runs everything at once with no
216
+ threading. Verified by regression tests against the 3.2 behaviour.
217
+
218
+ ## [3.2.0] - 2026-08-13
219
+
220
+ Typed multi-agent pipelines. Specialists can now declare a Pydantic
221
+ output schema once and pass validated instances to each other through
222
+ the Supervisor, instead of downstream agents re-parsing prose.
223
+
224
+ ### Added
225
+
226
+ - **`output_schema` on the `AgentRunner` / `AsyncAgentRunner`
227
+ constructor.** Declare the runner's output shape once
228
+ (`AgentRunner(..., output_schema=QueryIntent)`) instead of passing it
229
+ on every call or describing JSON in the prompt. A per-call
230
+ `output_schema=` still wins when both are set. `None` keeps the exact
231
+ pre-3.2 behaviour: no coercion, `completion.output` stays `None`.
232
+
233
+ - **Schema coercion via forced native function calling.** When a schema
234
+ is in play, the final answer is converted by forcing a provider-native
235
+ tool call against the schema (constrained decoding), not by regexing
236
+ JSON out of prose. The ReAct loop itself is untouched: tool selection
237
+ and intermediate reasoning run exactly as before, and the coercion
238
+ happens once, after the loop finishes. Models without a
239
+ `call_with_tools` implementation fall back to the previous text-JSON
240
+ parsing, so custom `BaseChatModel` subclasses keep working.
241
+ `completion.content` keeps the human-readable answer alongside
242
+ `completion.output` in every case.
243
+
244
+ - **`SubtaskResult.output`.** The Supervisor now preserves each
245
+ specialist's validated Pydantic instance next to its `content` text.
246
+ Consumers that only read `content` are unaffected.
247
+
248
+ - **Structured specialist-to-specialist handoff.** When an earlier step
249
+ produced typed output, `_build_augmented_query` serializes it into the
250
+ next specialist's context as a labelled JSON block
251
+ (`STRUCTURED OUTPUT (QueryIntent): {...}`) followed by the summary
252
+ text, so downstream steps parse fields rather than interpreting
253
+ sentences like `INTENT: ... SEARCH_QUERY: ...`.
254
+
255
+ - **`vector_search_tool` pipeline options.** New kwargs:
256
+ `max_text_chars` (default 500; pass `0` for full untruncated passages,
257
+ which a reranker judging evidence actually needs) and
258
+ `structured_output` (default False; when True the tool returns a JSON
259
+ array of `{id, text, vector_score, metadata}` instead of the
260
+ human-formatted list). Defaults preserve existing behaviour byte-for-
261
+ byte.
262
+
263
+ ### Fixed
264
+
265
+ - **Supervisor planning prompt contradicted the execution engine.** The
266
+ planner rule said sub-agents "do NOT see previous steps' output" and
267
+ discouraged dependency chains, but the dispatcher has threaded prior
268
+ findings into every step since `_build_augmented_query` shipped.
269
+ The rule now tells the planner that sequential steps receive earlier
270
+ results (structured when available) and that chains like
271
+ intent -> retrieval -> reranking are a good plan shape, while still
272
+ requiring same-specialist steps to merge and banning report-only steps.
273
+
274
+ ## [3.1.7] — 2026-07-27
275
+
276
+ ### Changed
277
+
278
+ - **`use_function_calling` default flipped to auto-detect** on
279
+ `AgentRunner` / `AsyncAgentRunner`. The parameter's default type is
280
+ now `Optional[bool] = None`; `None` resolves to `True` when the
281
+ model class overrides `BaseChatModel.call_with_tools` (both `GPT`
282
+ and `Claude` do) and to `False` when it doesn't (or when
283
+ `bind_tools_natively=True`). Callers passing `True`/`False`
284
+ explicitly are unaffected. Rationale: text-mode ReAct requires the
285
+ model to emit strict JSON with any long `action_input` string
286
+ properly escaped — a 1200-word markdown draft with unescaped
287
+ newlines or quotes reliably breaks `json.loads` and killed the run.
288
+ Function-calling mode routes the parser through the SDK's typed
289
+ channel so escaping is handled automatically. The historical
290
+ default (`False`) was the fragile option; the new default matches
291
+ what most users actually want.
292
+
293
+ ### Fixed
294
+
295
+ - **Malformed parser JSON no longer crashes the run.** When the
296
+ text-mode assistant response failed `json.loads` (typically because
297
+ a long `action_input` string had unescaped `"`, `\n`, or backticks),
298
+ the framework used to raise `JSONDecodeError` and unwind the whole
299
+ invocation. The runner now (1) tries a regex-based salvage that
300
+ extracts `{Thought, action, action_input}` from the raw text
301
+ covering the common "outer envelope valid, inner string broke
302
+ escaping" failure, and (2) if salvage fails, feeds a targeted fix
303
+ hint back to the model (`"your last response was not valid JSON;
304
+ emit …, escape newlines as \n"`) and continues the loop bounded
305
+ by `max_iterations`. Exhaustion returns a clear framework message
306
+ rather than an uncaught exception. Applied to both sync and async
307
+ runners via a shared `_salvage_react_json` helper.
308
+ The salvager's action-name regex is intentionally strict
309
+ (`[A-Za-z_][A-Za-z0-9_.\- ]{0,79}`) so it can't hallucinate an
310
+ "action" out of an unrelated `"key":"value"` pair inside malformed
311
+ JSON.
312
+
313
+ - **Verbose trace in `bind_tools_natively` mode now prints tool
314
+ name + args + response.** Previously native runs showed blank
315
+ `[tool.call.start]` / `[tool.call.complete]` pairs (the
316
+ observability layer fires them without the trace context), so you
317
+ couldn't tell which tool the model actually invoked or what came
318
+ back. The runner now prints `[tool] Invoking '<name>' with args:
319
+ <input>` and `[tool] Response: <preview>` (or `[tool] Error: ...`
320
+ when the dispatch raised) in the post-dispatch loop, matching the
321
+ format text-mode and function-calling mode use. Mirrored to the
322
+ async runner.
323
+
324
+ - **`web_fetch_tool(vector_store=...)` auto-ingests fetched pages into
325
+ a vector store** instead of dumping raw HTML into the model's
326
+ context. Fixes the TPM-limit trap: when a research agent fetches
327
+ four articles in parallel (via ``multi_tool_use.parallel`` or
328
+ native binding), the combined bodies can easily exceed 40k tokens
329
+ and blow past a 30k TPM ceiling on the very next model call.
330
+ New parameters on ``web_fetch_tool``:
331
+
332
+ | Kwarg | Default | Effect |
333
+ |---|---|---|
334
+ | ``vector_store`` | ``None`` | When set, each fetch is HTML-stripped, chunked with ``TextSplitter``, embedded via the store's embeddings, and added with ``{src: url, chunk_index, total_chunks}`` metadata. The tool response becomes a compact summary (URL, byte count, chunk count, 240-char preview) — NOT the raw body. The model then calls ``vector_search`` / ``Rag`` to pull only the passages it needs. |
335
+ | ``chunk_size`` | ``1500`` | Characters per chunk when ``vector_store`` is set. Ignored otherwise. |
336
+ | ``chunk_overlap`` | ``200`` | Overlap between adjacent chunks so a fact spanning a boundary is still retrievable. Ignored otherwise. |
337
+
338
+ Backwards-compatible: the positional ``cache_dir`` signature keeps
339
+ working; `web_fetch_tool()` with no ``vector_store`` returns raw
340
+ body as before. Ingest and cache_dir compose — enable both and get
341
+ disk-cached full bodies AND searchable chunks. HTML stripping is
342
+ minimal and dependency-free (regex-based: script/style blocks
343
+ dropped whole, then tags stripped, whitespace collapsed) so the
344
+ ingest path adds no new install dependency. On JSON/plain-text
345
+ responses the stripper is a near no-op.
346
+
347
+ The observation returned to the model shows topical coverage --
348
+ first, middle, and last chunk previews (up to 3 samples,
349
+ deduplicated for short pages) -- so the model can tell what
350
+ topics the page actually covers, not just the intro paragraph.
351
+ Without this the model would only see the page's opening and
352
+ wouldn't know to query for topics discussed later in the same
353
+ page. Explicit instruction in the observation ("query with
354
+ SPECIFIC keywords from the topics above; do NOT re-fetch; do
355
+ NOT ask for the full body") steers the model toward the RAG path
356
+ on follow-up turns.
357
+
358
+ - **`multi_tool_use.parallel` now reaches its dispatch path.**
359
+ When GPT wanted to batch several tool calls into one turn (fetch N
360
+ URLs concurrently, run M searches at once), it emitted OpenAI's
361
+ synthetic `multi_tool_use.parallel` meta-tool. The registry's
362
+ `_dispatch_multi_parallel` / `_adispatch_multi_parallel` handlers
363
+ already knew how to unpack it, but the runner loop's known-tools
364
+ guardrail rejected the name FIRST as unregistered — dumping the
365
+ raw `{"tool_uses": [...]}` payload into the user-facing "final
366
+ answer" and never invoking any of the nested calls. Added
367
+ `multi_tool_use.parallel` to the recognized action set in both
368
+ sync and async runners so the meta-tool flows through to dispatch
369
+ and the existing unpackers run. Nested calls with the `functions.`
370
+ prefix are normalized before dispatch (same as top-level FC
371
+ calls), so the model can emit either shape.
372
+
373
+ - **`Permissions.full_access` / `read_only` auto-wrap a bare string.**
374
+ Passing `full_access("./workspace")` used to iterate the string
375
+ into 11 single-character "subtrees" (Python's `list("./workspace")`)
376
+ — every path check silently rejected because no real path could
377
+ ever match a `"."` or `"/"` "allowed subtree". The classmethod
378
+ now detects a bare string and treats it as `[allowed_paths]`, so
379
+ `full_access("./workspace")` does the intuitive thing (equivalent
380
+ to `full_access(["./workspace"])` and auto-infers the workspace).
381
+ Same fix on `read_only`. List inputs are unchanged.
382
+
383
+ - **`Permissions.full_access` now accepts (and auto-infers)
384
+ `workspace`.** The classmethod set `allowed_paths` but not
385
+ `workspace`, so short paths like `write_file(path="report.md")`
386
+ resolved to CWD (outside the sandbox) and raised
387
+ `PermissionError: access denied` — a landmine that every caller of
388
+ `Permissions.full_access(["./workspace"])` hit sooner or later.
389
+ New signature: `full_access(allowed_paths, *, workspace=None)`.
390
+ When `workspace` isn't passed AND `allowed_paths` has exactly one
391
+ entry, that path is auto-set as the workspace (the "project-scoped
392
+ agent whose one allowed subtree IS its workspace" case, which is
393
+ 99% of use). Two or more paths stay ambiguous and require an
394
+ explicit `workspace=` if short-path resolution is wanted. Pass an
395
+ explicit `workspace=` string to override the auto-choice.
396
+ Backwards-compatible on the positional signature; adds a keyword
397
+ argument that existing callers didn't use.
398
+
399
+ ## [3.1.5] — 2026-07-26
400
+
401
+ ### Fixed
402
+
403
+ - **Text-mode tool results no longer use `role: "function"`.** In text
404
+ mode (the default — no `use_function_calling`) the runner fed each tool
405
+ observation back to the model as a `role: "function"` message. Newer
406
+ OpenAI models reject that role outright (`400 … 'messages[N].role' does
407
+ not support 'function' with this model`, e.g. gpt-5.x), and Anthropic
408
+ never accepted it — text-mode multi-tool runs on Claude were latently
409
+ broken too; older GPT models simply still tolerated the legacy role.
410
+ Tool observations now go back as a plain `role: "user"` turn framed as
411
+ `Observation: …`, which every provider and model generation accepts and
412
+ which matches the ReAct template's own few-shot convention.
413
+ Function-calling mode is unchanged (native `role: "tool"` +
414
+ `tool_call_id`). The async runner was additionally emitting `function`
415
+ unconditionally (even in FC mode); it now uses the same shared helper.
416
+
417
+ ### Added
418
+
419
+ - **`subtask_success_check` on `Supervisor` / `AsyncSupervisor`.** Opt-in
420
+ predicate `(SubtaskResult) -> bool | str` that decides whether a
421
+ *returned* (non-raised) sub-task result is actually acceptable — the
422
+ "ran fine but produced nothing useful" case a plain retry can't catch
423
+ (a scraper that saved 0 links, an extractor that found nothing). Return
424
+ `True` to accept, or `False`/a `str` reason to reject; a rejected
425
+ result is retried like a raised error, with the reason fed back into the
426
+ query, bounded by `max_subtask_retries`. After retries are exhausted the
427
+ last result is returned with its `error` set (content preserved). A
428
+ check that itself raises is treated as "accept" so a buggy predicate
429
+ can't wedge the run. Default `None` keeps the exceptions-only behavior.
430
+ New example `examples/robust_link_scraper.py` wires it together with a
431
+ scraping `system_addendum` (parse relative+absolute hrefs, fall back to
432
+ `sitemap.xml` on JS-rendered sites).
433
+
434
+ ## [3.1.4] — 2026-07-26
435
+
436
+ ### Fixed
437
+
438
+ - **A malformed-JSON tool argument no longer crashes the whole agent
439
+ run.** When a model emitted a Python snippet or a Windows path as a
440
+ tool-call argument — `re.findall(r'\d+')`, `C:\Users` — the `\d` / `\U`
441
+ are illegal JSON escapes, and the OpenAI adapter's eager
442
+ `json.loads(call.function.arguments)` raised `JSONDecodeError` and
443
+ `raise`d it, unwinding the entire ReAct loop before the agent's own
444
+ retry machinery could act. Under a Supervisor this surfaced as a bare
445
+ `ERROR: Invalid \escape: line 1 column 598` and the sub-task was
446
+ abandoned. Now:
447
+ - `_parse_tool_arguments` repairs the common case (backslashes that
448
+ don't begin a valid JSON escape are doubled), recovering `\d`,
449
+ `\w`, `\s`, etc. with zero extra round-trips. A backslash before a
450
+ valid-escape letter (`\b`, `\n`, …) remains ambiguous and is left
451
+ as the escape — a documented limit.
452
+ - When repair fails, `call_with_tools` returns a dedicated
453
+ `invalid_tool_args` result and the loop feeds the error back as a
454
+ retryable observation ("your arguments weren't valid JSON — escape
455
+ backslashes and resend"), bounded by `max_iterations`, in all
456
+ three modes across both `AgentRunner` and `AsyncAgentRunner`.
457
+ `Claude` was already immune (its tool inputs arrive pre-parsed).
458
+
459
+ - **A tool-call preamble is no longer returned as the final answer.**
460
+ Models routinely end a turn with an announcement instead of an action
461
+ — "I'll look up your recent scores to get a clear view of your
462
+ communication skills. Just a second!" — and every "no tool call
463
+ found" branch in both runners was coded as *this text is the answer,
464
+ break*. The loop terminated on iteration 1 and the caller got a
465
+ promise instead of a result. Three sites per runner were affected:
466
+ the native-binding path (`type != "tool_use"`), the
467
+ `use_function_calling` path (parser unresolved), and the JSON-text
468
+ path (response didn't parse). `max_iterations` never helped, because
469
+ the break happened before any iteration was spent.
470
+
471
+ The runner now feeds the model one corrective nudge — "your last turn
472
+ had no action, so nothing happened; do it, don't announce it" — and
473
+ continues the loop. Verified against both `AgentRunner` and
474
+ `AsyncAgentRunner` in all three modes.
475
+
476
+ ### Added
477
+
478
+ - **Proactive "act, don't announce" system-prompt clause.** The reactive
479
+ `text_turn_nudges` fix corrects an agent *after* it narrates instead of
480
+ acting; this clause heads it off. When (and only when) an agent has
481
+ tools, its system prompt now tells it to call the tool rather than
482
+ reply "I'll do X / just a second" and stop — and to report what it DID
483
+ in past tense. Injected in all three modes across both runners; skipped
484
+ for tool-less chat agents, where prose is the correct answer. Sits
485
+ before any `system_addendum` so a caller's role instructions still win.
486
+
487
+ - **`max_subtask_retries` on `Supervisor` / `AsyncSupervisor`** (default
488
+ `1`). A sub-task that raised used to be recorded as an error and the
489
+ Supervisor moved straight to synthesis — no second attempt. Now a
490
+ failed sub-task is re-dispatched up to this many times, with the prior
491
+ error appended to the query so the specialist knows what to fix
492
+ ("your previous attempt failed with X — diagnose and try again").
493
+ Bounded and informed: only raised exceptions trigger a retry (a
494
+ sub-task that returns content is accepted as-is, since the Supervisor
495
+ can't tell "terse but correct" from "wrong"), and the error text is
496
+ fed back rather than blindly re-running. Set to `0` for the old
497
+ quit-on-first-failure behavior. Applies in both sequential and
498
+ concurrent async modes.
499
+
500
+ - **`text_turn_nudges` on `AgentRunner` / `AsyncAgentRunner`** (default
501
+ `1`). Caps the re-prompts described above at one extra LLM call per
502
+ run; after the budget is spent the model's text stands as the answer.
503
+ Set to `0` for the previous behavior. Automatically skipped when no
504
+ tools are registered, since a runner with no tools is a plain chat
505
+ call and prose genuinely is the answer there.
506
+
507
+ ## [3.1.3] — 2026-07-22
508
+
509
+ Docs-only patch. No code changes since 3.1.2. Users on 3.1.2 don't
510
+ need to upgrade for functionality; upgrade to pick up the improved
511
+ onboarding docs bundled in the sdist.
512
+
513
+ ### Documentation
514
+
515
+ - **Tools doc rewritten to answer "how do I actually use these?"**
516
+ Added §0 `How each built-in tool is registered` as the entry
517
+ section. Two registration paths — auto vs manual — laid out in a
518
+ table on the first screen. Six runnable subsections covering every
519
+ combination:
520
+ - §0.1 DefaultTools via `Permissions(...)` (auto)
521
+ - §0.2 WebTools via `tools=[web_search_tool(), web_fetch_tool()]`
522
+ - §0.3 RAG via `TextSplitter` -> `VectorStore.add_documents` ->
523
+ `vector_search_tool(store)`
524
+ - §0.4 Handoffs via `handoff_tool` + `HandoffCoordinator`
525
+ - §0.5 Custom `StructuredTool` from scratch
526
+ - §0.6 Fully-loaded runner combining all of the above
527
+ - §0.7 Rules on name collisions, invisible-denied-capabilities,
528
+ async-tool behavior
529
+ The existing inventory + wrapper / controls / cheat-sheet sections
530
+ are unchanged; they now sit after the "how to use them" primer
531
+ instead of before it.
532
+
533
+ ## [3.1.2] — 2026-07-22
534
+
535
+ Patch release. Two independent fixes.
536
+
537
+ ### Fixed
538
+
539
+ - **`llm_judge` correctly parses YES/NO across providers.** The judge
540
+ parser was comparing the reply's first word to the literal string
541
+ `"YES"`. GPT-4o answers `"YES,"` (comma-suffixed), which failed the
542
+ equality check and marked every genuine PASS as FAIL. Claude replies
543
+ `"YES"` without punctuation so the bug hid during local development.
544
+ Fixed by matching `\b(YES|NO)\b` (word-boundary regex, case-
545
+ insensitive) at the start of the reply. Handles every real shape:
546
+ `YES`, `YES.`, `YES!`, `YES, exactly right`, `Yes.`, `yes -- reason`.
547
+ Ambiguous replies (`Maybe`, empty string) still fail closed.
548
+ - Regression test `test_llm_judge_parses_various_verdict_shapes`
549
+ covers 8 YES shapes, 5 NO shapes, and 4 ambiguous replies.
550
+
551
+ ### Added
552
+
553
+ - **`agentx_dev.Tools` is a one-stop tools namespace.** Users no
554
+ longer need to remember which module each tool lives in:
555
+
556
+ ```python
557
+ from agentx_dev.Tools import (
558
+ StandardTool, StructuredTool,
559
+ AsyncStandardTool, AsyncStructuredTool,
560
+ web_search_tool, web_fetch_tool,
561
+ vector_search_tool, handoff_tool,
562
+ DefaultTools, Permissions,
563
+ )
564
+ ```
565
+
566
+ Both this form and the pre-existing `from agentx_dev import X`
567
+ form coexist. Implementation uses PEP 562 module `__getattr__`
568
+ and `__dir__` so re-exports are lazy (no import cost for modules
569
+ the caller doesn't touch) and show up in IDE autocomplete +
570
+ `dir(agentx_dev.Tools)`.
571
+
572
+ ## [3.1.1] — 2026-07-21
573
+
574
+ Second batch of 3.1 features + a full docs + brand pass.
575
+
576
+ ### Added
577
+
578
+ **Streaming through orchestration**
579
+ - `Supervisor.stream()` / `AsyncSupervisor.astream()` emit
580
+ `plan_start` / `plan` / `dispatch` / `subtask_result` /
581
+ `synthesize_start` / `final` / `completion` events.
582
+ - `HandoffCoordinator.stream()` / `.astream()` emit `invoke` /
583
+ `completion` / `handoff` / `final` / `result` events per hop.
584
+ - Legacy `.run()` / `.arun()` refactored to consume the streams (no
585
+ code duplication).
586
+
587
+ **Prompt optimization — `Compiled`**
588
+ - New `agentx_dev.Compiler` module.
589
+ - `Compiled(runner_factory, trainset, ...)` iteratively refines a
590
+ runner's `system_addendum` against the eval harness. Half of
591
+ DSPy's power at a tenth of the surface.
592
+
593
+ **Anthropic Batch API**
594
+ - `Claude.batch(requests)` submits many prompts at Anthropic's 50%-off
595
+ batch rate, polls to completion, returns results in submission order.
596
+ - Per-request error dicts on failure; token usage funneled into
597
+ `TokenUsage` so cost tracking stays a single source of truth.
598
+
599
+ **Vector store adapters — `agentx_dev.VectorStores`**
600
+ - `ChromaVectorStore`, `QdrantVectorStore`, `PgVectorStore` — same
601
+ public shape as the in-memory `VectorStore` (`add` / `search` /
602
+ `delete` / `clear` / `__len__` / `embeddings`).
603
+ - `vector_search_tool()` and `SemanticMemory` accept any of them.
604
+ - SDK imports lazy; friendly `ImportError` when the underlying SDK
605
+ is missing.
606
+
607
+ **Trace viewer (`viewer/`)**
608
+ - Self-hosted single-page app that reads `FileHook` JSONL and renders
609
+ a timeline with type/text filters, summary sidebar, JSON drill-down.
610
+ - Works from `file://`, no server required.
611
+
612
+ **Docs site (`host/`)**
613
+ - Full editorial dark-first design system (JetBrains Mono headings,
614
+ Inter body, `#B8FF3E` electric-lime accent).
615
+ - Command palette (`Cmd+K`) with keyboard navigation and live search.
616
+ - Hero code snippet with hand-tinted syntax highlighting.
617
+ - Reading progress bar, breadcrumbs, header anchor links.
618
+ - Sidebar sliding active marker, collapsible groups.
619
+ - Code copy buttons, language labels.
620
+ - Right-rail auto-TOC with `IntersectionObserver` scrollspy.
621
+ - Dark/light theme toggle, persisted.
622
+ - Cache-busted assets so edits land on refresh without hard-reload.
623
+
624
+ **Brand identity (`brand/`)**
625
+ - Full brand kit: 5 SVG assets (`mark`, `mono`, `wordmark`, `logo-full`,
626
+ `app-icon`), `BRAND.md` strategy doc, rendered brand-kit HTML deck.
627
+ - Copy audit dropped "small" (weak) and "LangChain" references from
628
+ all marketing surfaces.
629
+ - Favicon wired into docs + trace viewer.
630
+
631
+ **Test suite (`tests/`)**
632
+ - Restored + expanded pytest suite: 127 tests passing (3 skipped for
633
+ absent optional SDKs).
634
+ - Coverage: parser + all `AgentType` variants, `ToolRegistry`
635
+ (dispatch / dup-guard / circuit-breaker / timeout), Permissions
636
+ (capability gating + sandbox + traversal), budgets (cost / rate /
637
+ retry / non-retryable HTTP), runner loop (streaming + output_schema
638
+ + chat history), embeddings + `VectorStore` + `SemanticMemory`,
639
+ handoffs (bounded hops + history sanitization), evals harness
640
+ (all assertion helpers + JSON case loaders), vector-store adapter
641
+ shape conformance.
642
+
643
+ **Docs (`docs/`)**
644
+ - Full docs tree (34 pages), including new pages for:
645
+ vector store adapters, prompt optimization, batch API, trace viewer,
646
+ and a **use-cases** landing (13 concrete scenarios with runnable code).
647
+ - Rewrote **Tools** page to enumerate every built-in tool with args,
648
+ return shape, capability flag, and use-case guidance.
649
+ - Rewrote **Agents** page to cover all four orchestration
650
+ architectures (Solo / Supervisor / Handoffs / Compiled) with
651
+ decision trees, worked examples, and cheat sheet.
652
+ - **Agentic RAG chatbot** as use case §13 — multi-query decomposition,
653
+ parallel retrieval, self-critique, citations, user memory.
654
+
655
+ **Examples**
656
+ - `examples/agentic_rag_demo.py` — the runnable version of the
657
+ agentic RAG use case. Auto-seeds a KB if none exists, `--demo` flag
658
+ runs a 3-turn scripted session proving user-notes recall works.
659
+
660
+ **Package**
661
+ - `[chroma]`, `[qdrant]`, `[pgvector]`, `[dev]` extras added.
662
+ - `[anthropic]` bumped to `>=0.36` (Batch API + prompt cache).
663
+
664
+ ### Fixed
665
+
666
+ - `AgentRunner._iter_run` in `bind_tools_natively=True` mode uses a
667
+ minimal system prompt instead of the AgentType template so the
668
+ ReAct `action/action_input` scaffold no longer fights the native
669
+ tool interface. Previously produced JSON-blob answers under GPT.
670
+ - `HandoffCoordinator._sanitize_history_for_next_agent` strips tool
671
+ and function role messages between hops so tool_call_ids from a
672
+ previous agent don't leak into the next model's call (OpenAI 400).
673
+ - Docs site marker positioning uses double-`requestAnimationFrame` +
674
+ `document.fonts.ready` so the sidebar accent bar lands on the
675
+ correct row even on a cold font cache.
676
+ - Primary hero CTA color uses `#doc .hero-cta a.primary` selector to
677
+ outrank `#doc a` link styling (previously rendered lime-on-lime
678
+ and was invisible).
679
+
680
+ ### Notes
681
+
682
+ - Package version bumped from `3.0.6` to `3.1.1`. The 3.1.0 release
683
+ did not ship publicly — 3.1.1 is the first 3.1-tagged PyPI release
684
+ and includes both batches of features.
685
+
686
+ ## [3.1.0] — internal only (commits 52840e7)
687
+
688
+ First batch of 3.1 features. Committed but not released to PyPI.
689
+ Merged into 3.1.1 for the public release.
690
+
691
+ ### Added
692
+ - Anthropic prompt caching (`Claude(enable_prompt_cache=True)`).
693
+ - Parallel per-turn tool dispatch in `AgentRunner`
694
+ (`bind_tools_natively=True`, `parallel_tool_workers`).
695
+ - Semantic memory (`SemanticMemory`, embeddings-backed retrieval).
696
+ - RAG core (`Embeddings`, `HashEmbeddings`, `OpenAIEmbeddings`,
697
+ `VectorStore`, `VectorHit`, `vector_search_tool()`).
698
+ - Agent-to-agent handoffs (`HandoffRequest`, `handoff_tool`,
699
+ `HandoffCoordinator`, `HandoffResult`).
700
+ - Evals harness (`EvalCase`, `EvalRunner`, `EvalReport`, 7 assertion
701
+ helpers, JSON case loader, `python -m agentx_dev.Evals run` CLI).
702
+ - `TokenUsage.cache_hit_ratio` property.
703
+
704
+ ## [3.0.6] — 2026-03 (baseline)
705
+
706
+ Security hardening baseline (SSRF guard on `web_fetch`, HMAC-signed
707
+ persistent state, scrubbed subprocess env, path sanitizer,
708
+ `permissions.json` mode 0o600, ReDoS guard on `grep`,
709
+ `invoke`/`ainvoke` accept bare strings and message lists).