mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-09-20 10:03:37 +00:00
cdcf48307baf4fc3a5278443f677bf481ce69892
801 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
143e902178 |
docs: use organization UUIDs in the skill install reference (#7273)
Organization names are not unique, so the documented `@org/name` form can
resolve to the wrong organization and fail to find the skill. Document the
`@org-uuid/name` form instead, and add a note pointing at `crewai org list`
for the UUID.
Applies to the agent-side registry refs too: they resolve through the same
`/skills/:org/:name` endpoint and the same `~/.crewai/skills/{org}/{name}/`
cache path, so leaving them as `@acme` would contradict the install command.
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Vidit Ostwal <110953813+Vidit-Ostwal@users.noreply.github.com>
|
||
|
|
a024115e4e | [docs-freeze] docs: snapshot and changelog for v1.15.20 (#7271) | ||
|
|
04e2efbdab | [docs-freeze] docs: snapshot and changelog for v1.15.19 (#7266) | ||
|
|
b608a3595c |
docs: remove CodeInterpreterTool from AI/ML overview examples (#7100)
Co-authored-by: Vidit Ostwal <110953813+Vidit-Ostwal@users.noreply.github.com> |
||
|
|
f5db5a1788 |
docs: point prompt-template link at its current path (#7101)
Co-authored-by: Vidit Ostwal <110953813+Vidit-Ostwal@users.noreply.github.com> |
||
|
|
818f2624e8 |
[OSS-149] Accept 1/yes/on on telemetry disable flags (#7185)
* fix(telemetry): accept 1/yes/on on disable flags CREWAI_DISABLE_TELEMETRY=1 was ignored because the gate only matched true, so telemetry stayed on with no warning. * fix(telemetry): warn once on unrecognized disable values Stop repeating the same invalid-flag warning on every telemetry check, and drop the undocumented CREWAI_DISABLE_TRACKING alias from docs. --------- Co-authored-by: Lorenze Jay <63378463+lorenzejay@users.noreply.github.com> |
||
|
|
a35fbc864d |
docs: update channels guide to current copilotkit channels api (#7016)
* docs: update channels guide to current copilotkit channels api * docs: translate channels and frontend overview guides to ar, ko, pt-BR --------- Co-authored-by: Lorenze Jay <63378463+lorenzejay@users.noreply.github.com> |
||
|
|
265697b6f8 |
docs: refresh retired Gemini model ids (#7003)
Co-authored-by: Vidit Ostwal <110953813+Vidit-Ostwal@users.noreply.github.com> |
||
|
|
1f6e327b3c |
feat(events): report machine size as a coarse band, not a core count (#7117)
* feat(events): report machine size as a coarse band, not a core count
`runtime_context` says where a process runs but carries no capacity axis, and
its largest bucket is a catch-all: a gunicorn worker on a VM and
`python main.py > out.log` on a MacBook both report `non_interactive`. Docker
Desktop on a laptop reports `container` via /.dockerenv, and a Remote-SSH shell
on a server reports `vscode_terminal`. So "server or laptop" is not answerable
from it today.
Adds `cpu_band` to the common span attributes, so it rides every span the way
`runtime_context` does rather than sitting on `Crew Created` alone - which would
answer nothing for Flow-only, CLI-only or standalone-agent runs.
Six bands, powers of two, top one open-ended: 1-2, 3-4, 5-8, 9-16, 17-32, 33+.
Open-ended because the exact count is the fingerprint - the observed fleet
maximum is 512, and a span reporting 512 identifies one machine. The vocabulary
is closed and asserted, like KNOWN_CODING_AGENTS and KNOWN_RUNTIME_CONTEXTS.
The share_crew-gated exact `cpus` attribute and the four platform* attributes
are untouched. That gating was a deliberate 2024 classification of machine
fingerprint as shareable content (
|
||
|
|
4bc5d29242 | [docs-freeze] docs: snapshot and changelog for v1.15.18 (#7138) | ||
|
|
f90d37b4ac |
feat(flow): promote conversational flows to stable (#7107)
Move the canonical API into crewai.flow while preserving experimental imports and declarative references through compatibility aliases. Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
56e0e85a81 |
[OSS-115] Persist custom conversational replies after fallback append (#7026)
* fix(flows): persist custom conversational replies after fallback append Custom @listen routes that only return a public string were appended after kickoff, so @persist snapshots missed the assistant turn. Snapshot again from the same persist path once the fallback writes state.messages. * test(flows): cover @persist restore of custom conversational replies Add regression coverage for custom @listen returns across fresh Flow instances, no double-append on built-in converse, and a single user/assistant message-added event. * docs: note that public listen returns persist as assistant replies |
||
|
|
4e0b2e2b15 |
fix(events): record task failures as failures, not as successes (#7073)
* fix(telemetry): record task failures as failures, not as successes close_span() sets StatusCode.OK unconditionally, and TaskFailedEvent was routed to Telemetry.task_ended, which calls it. Every failed task was therefore exported as OK, which is why error_count downstream is not merely low but exactly zero: 240.0M task executions across 13 months in crew_task_executions_daily_target, error_count = 0 in every one of them. The same line had a second defect. The span was only ended when source.agent.crew was present, so a task failing without one was popped from the span map and then never closed - never ended, never exported, invisible rather than mislabelled. task_failed takes no crew (it reads nothing off one), so that condition disappears rather than being widened. Only the exception class name is recorded, never the message, which routinely contains prompts, model output, file paths and credentials. close_span_with_error drops any value failing str.isidentifier(), so a message cannot be recorded even if one is passed by mistake. This is the task half of closed PR #6781, re-cut onto main as that PR asked for. The crew half is deliberately left out: crew_execution_span() returns None unless share_crew=True, so crew._execution_span is None for nearly every user and a crew-failure handler would exit immediately for the default population. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RfV2uMqWRcdfufMvtdCVoN * fix(telemetry): take the exception class for error_type, not a free-form string cursor and CodeRabbit both flagged the sanitization, and they were right: the package already had a stronger convention and this change had not used it. Telemetry._safe_error_type takes the exception *class*, and its docstring says why in as many words - "a single-word message such as 'secret_token' is itself a valid identifier", so filtering a string with isidentifier() is not enough. The ported code predates that helper and reinvented the weaker check. TaskFailedEvent.error_type is now type[BaseException] | None, so pydantic itself rejects a message before any of our code runs, and task_failed routes it through _safe_error_type. The identifier check in close_span_with_error stays as the second gate on the derived name, which is the role _safe_error_type's docstring already describes. Also adds producer-level tests, which CodeRabbit correctly identified as missing: every earlier test constructed TaskFailedEvent directly, so a regression in the two emit sites this change touches in task.py would have passed the whole suite. The sync and async producers are driven through Task._execute_core and Task._aexecute_core with a distinctive exception class, and each patches a different agent method (execute_task vs aexecute_task), which is why they can regress independently. Verified by dropping error_type from both producers: all three new tests fail, and pass again when restored. Removes an unused `import os` left behind when the fixture was rewritten. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RfV2uMqWRcdfufMvtdCVoN * test(telemetry): capture producer failures at the emit boundary, not via the bus The producer tests subscribed a handler to crewai_event_bus and asserted on what it received. That passed this file in isolation and every randomized local run, then failed in CI inside a 621-test shard with zero events captured: FAILED tests/telemetry/test_task_failure_instrumentation.py:: test_sync_producer_puts_the_exception_class_on_the_event assert 0 == 1 + where 0 = len([]) task_failed is an "ending" event, and with an empty scope stack - there is no real kickoff in these tests - dispatch is conditional on event-context state that other tests in the same worker process can leave behind. Subscribing made the assertion depend on the bus choosing to dispatch, which is not what these tests are about: they are about what the producer in task.py constructs. Patching crewai_event_bus.emit records the event unconditionally at the point the producer hands it over, with no dispatch involved. Both producers ignore emit's return value, so returning None is faithful. Containment re-verified after the change: dropping error_type from both producers fails exactly these three tests and nothing else. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RfV2uMqWRcdfufMvtdCVoN * fix(events): keep TaskFailedEvent JSON-serializable with a class-valued error_type error_type holds an exception class, which is not a JSON type, so model_dump(mode="json") raised PydanticSerializationError for the whole event - not just that field. Two real consumers depend on it: the checkpoint listener dumps every event through EventRecord, and the tracing listener JSON-POSTs events to AMP. A single task failure therefore took out checkpointing. field_serializer with when_used="json" returns the class name. The "json" scope is load-bearing: event_listener hands the live class to Telemetry.task_failed, which needs it for _safe_error_type, so python-mode dumps must keep the class. The annotation is a module-level _ExceptionClass alias rather than an inline type[BaseException], because TaskFailedEvent declares a field named `type` which shadows the builtin for the rest of the class body - inline, it raises TypeError at import ("task_failed"[BaseException]) and mypy rejects it as "Variable ... is not valid as a type". Quoting satisfies neither tool: ruff flags UP037 and mypy still resolves it in the class scope. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RfV2uMqWRcdfufMvtdCVoN * fix(events): let a dumped error_type restore, instead of degrading the event The serializer added in the previous commit stopped model_dump(mode="json") from raising, but nothing accepted the class-name string back. _resolve_event (state/event_record.py:32-35) wraps cls.model_validate in a bare except and falls back to BaseEvent, so restoring a checkpoint after a task failure silently dropped the whole event -- including `error`, a plain string that would otherwise have survived. Traded a loud failure for a quiet one. Measured before: dumped error_type='ValueError' and error='boom', restored as BaseEvent with neither attribute. After: restores as TaskFailedEvent with error='boom' and error_type is ValueError. A BeforeValidator resolves a name against real exception classes only -- builtins first, then a walk of BaseException.__subclasses__(). So this does not reopen the hole the class-typed field closes: "secret_token" resolves to nothing, is returned unchanged, and is rejected by the field's own type. Asserted for secret_token, sk_live_1234, dict and os. A name whose class is not imported in this process still degrades, which is deliberate: synthesising a class from an arbitrary string is the injection risk this field exists to avoid. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RfV2uMqWRcdfufMvtdCVoN --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-authored-by: Vidit Ostwal <110953813+Vidit-Ostwal@users.noreply.github.com> |
||
|
|
a9cb0bdf02 |
Lorenze/deprecate/answer from history (#7105)
* feat(flows): enhance conversational flow documentation and APIs - Updated the description to clarify the use of `handle_turn` and structured streaming in multi-turn chat applications. - Added a warning about the experimental nature of the conversational features. - Improved the overview section to include structured streaming and refined the explanation of session handling. - Enhanced the API documentation for `handle_turn`, `stream_turn`, and `chat` methods, emphasizing their roles in conversational flows. - Clarified the turn lifecycle and the handling of user messages within the flow. - Updated examples to reflect changes in message handling and session tracing. - Ensured consistency across language versions in the documentation. * feat(flow): deprecate answer_from_history route Guide conversational flows toward the existing converse route while preserving compatibility warnings and schema metadata. Co-authored-by: Cursor <cursoragent@cursor.com> * stacklevel=3 raising it higher --------- Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
9652af6ae0 |
fix(agents): keep message roles when Agent.kickoff gets a conversation (#7065)
* fix(agents): keep message roles when Agent.kickoff gets a conversation
`Agent.kickoff` accepts `str | list[LLMMessage]`, but `_prepare_kickoff` joined
every message's content into one string. Measured with a recording LLM, a
three-turn conversation reached the provider as two messages:
system | You are Support...
user | Current Task: my order id is 42\nthanks, checking\nwhere is it?
So the agent's own previous reply was presented as something the user said, and
the model could not tell who said what. `LiteAgent.kickoff` already did this
correctly, which is why the same list gave four messages with roles intact
there.
The last message is now this turn's request and the ones before it travel as
`inputs["history"]` -- the way `inputs["files"]` already does -- which both
executors splice in after the system prompt and before the user prompt. Memory
recall still runs over the whole conversation text, not just the last turn.
A plain string and a single-message list are byte-identical to before, which is
what every existing caller passes.
Also widens `LiteAgentExecutionStartedEvent.messages` from `list[dict[str, str]]`
to `list[LLMMessage]`: it raised a ValidationError for a message whose content
was `None` or a content-part list, both of which are valid `LLMMessage` shapes
that `Agent.kickoff` already accepts.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(agents): carry history without a system prompt, tool calls, or dup files
Three review findings on the message-role work, all real.
History was spliced only in the branch that builds a system prompt. With
`use_system_prompt=False` or a custom template there is one combined prompt, so
history went nowhere and only the last turn reached the model -- worse than the
flattening this PR replaced, which at least included the text. Both executors
now splice in either branch, via one `_append_history` helper each.
Filtering on truthy content dropped an assistant turn that only requests tool
calls, leaving a `tool` message with no preceding `tool_calls` message -- a
sequence providers reject. A message now counts when it has content, tool
calls, or a tool_call_id.
And `files` were unioned from every message onto the current request while
history messages kept their own, so prior attachments were sent twice. Only the
current request's attachments travel in `inputs["files"]` now.
Found by Cursor and CodeRabbit on #7065.
* fix(agents): treat an attachment as message payload
_carries_payload counted text, tool calls and tool results, but not files.
A final message whose only payload was an attachment was filtered out, so the
previous message became this turn request and the attachment never reached
inputs["files"].
Found independently by Cursor and CodeRabbit on #7065.
* fix(agents): promote the last user message, not the last message
build_agent_context() appends an agent private thread after the current user
turn, so on a later turn the trailing message is an assistant scratch. That
scratch became Current Task while the real question was demoted to history --
reproduced: the request came back as "internal note: checked warehouse".
The request is now the last user message, with everything else kept as history
in order; with no user message the last one stands in, which is what a
single-message caller has always got. Documented on kickoff and kickoff_async.
The old cross-check against LiteAgent only compared roles on a fixture already
ending in a user message, so it could not catch this. It now asserts what holds
for both -- nothing dropped, nothing duplicated -- since Agent has a task slot
in its prompt and LiteAgent does not.
Reported by Vidit-Ostwal on #7065.
* test(agents): cover history placement on the deprecated executor too
`CrewAgentExecutor` carries its own `_setup_messages`, and `Agent.kickoff`
builds an `AgentExecutor` unconditionally, so nothing reached the twin's
two `_append_history` sites. This drives that executor directly with the
real `SystemPromptResult` / `StandardPromptResult` shapes, so both its
branches are pinned.
Verified by mutation: removing either `_append_history` call in either
executor now fails the matching test.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VwxiogL9mQg9q8cx4qLfYJ
* fix(agents): keep turns that follow the request after it
`_prepare_kickoff` packed every non-request message into one `history`
list, and both executors spliced that list before the current user
prompt. A conversation ending on a tool result therefore reached the
provider as `assistant tool_calls -> tool -> user`, hoisting the tool
pair above the question it answers. `LiteAgent` sends the same list as
`user -> assistant -> tool`.
Splits the carried messages at the request instead: what came before
stays `history`, what came after travels as `trailing` and is appended
after the user prompt. Promoting the last user message to `{input}` is
unchanged.
`test_a_tool_call_sequence_survives` missed this because it ends on a
follow-up user line, so the tool pair was already before the request.
Reported by lorenzejay, who also confirmed gpt-4o-mini and gpt-5.6-sol
accept the reordered payload -- an order bug, not a provider 400.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VwxiogL9mQg9q8cx4qLfYJ
* test(agents): assert the ordering through kickoff_async too
`kickoff_async` shares `_prepare_kickoff`, and the async executor entry
points reach the same `_setup_messages`, but sharing a code path is not
the same as covering it. Pins the tool-result ordering through the async
path, and lifts the tool conversation to a module-level fixture so the
sync and async assertions cannot drift.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VwxiogL9mQg9q8cx4qLfYJ
* docs(agents): state where a kickoff conversation's turns land
`concepts/agents.mdx` documents the multi-message form but not which
message becomes the request or what happens to the turns around it --
the contract this branch changed. Corrects the `kickoff` /
`kickoff_async` docstrings to match.
en and ko only: the ar and pt-BR pages do not carry the "Multiple
Messages" section at all, which is a pre-existing translation gap rather
than one this change introduces.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VwxiogL9mQg9q8cx4qLfYJ
* feat(flow): let a declarative agent action receive the conversation (#7066)
* feat(flow): let a declarative agent action receive the conversation
A chat handler could only hand its agent a single string, so a declarative
conversational flow's `agent` action never saw prior turns. Both ends blocked
it: `AgentDefinition.input` was `str` with a validator rejecting anything else,
and `AgentAction.run` raised "agent input must render to a string" once a CEL
template rendered to a list.
`input` now takes a string or a list of messages, and the action normalizes
rather than rejecting. A whole-string `${...}` template keeps its evaluated
type, so `state.messages.map(m, {'role': m.role, 'content': m.content})`
renders the exact shape the agent wants -- no new CEL function needed.
Serialized messages carry `name: None` and `metadata` that the agent event
schema rejects, and `message_to_llm_dict` only drops `None` for a model input,
not the plain dicts a CEL render produces. The normalizer drops them, keeping
`content: None` since that is a valid message shape.
Declarative crews are unaffected: they use `CrewAgentDefinition`, which has no
`input` field at all.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* test(flows): pin content=None survival through agent input normalization
A filter-all-None change would silently drop the assistant turn that only
requests tool calls, and no test covered that key.
Found by CodeRabbit on #7066.
* test(flows): cover the agent action through kickoff, not the helper
The existing tests called _normalize_agent_input directly, so nothing pinned
that Expression.render_template -> normalization -> Agent.kickoff_async keeps a
message list intact. Removing the normalization call now fails this test.
Found by CodeRabbit on #7066.
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Lorenze Jay <63378463+lorenzejay@users.noreply.github.com>
|
||
|
|
500ebc7a68 |
feat(flow): let a declaration name the router's response format (#7063)
* feat(flow): let a declaration name the router's response format
`conversational.router.response_format` was typed `Any` and dropped with a
warning, because `_router_response_format` hands its value straight to
`llm.call(response_format=...)`, which needs a real class. So the router always
used its synthesized fallback: `intent: str` with the route labels only in a
field description.
The field now takes the same `{"python": "module.path.Class"}` shape a crew
agent's `response_format` uses, resolved through the same
`_resolve_model_class`. That brings the project-root containment with it -- a
declaration cannot reach outside the project to import code -- and gives the
router a `Literal[...]` of the real route labels instead of a bare string.
The DSL projection now emits that shape too, so a live class on a Python flow
round-trips as `{"python": ...}` rather than an opaque `{"ref": ...}` that
nothing could reload.
A bare `module:qualname` ref is now a load-time validation error instead of
being silently discarded; the test that pinned the old drop-with-warning
behavior is updated to assert that.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(flow): do not project a response format that cannot be reloaded
`_python_reference` emitted a dotted path for any class, including two that
cannot be imported back: a non-Pydantic class, and one defined inside a
function, whose `__qualname__` carries `<locals>`. The definition then held a
ref that only failed when something tried to resolve it.
Both are dropped at projection time with a warning naming why, so a reload
falls back to the synthesized response format. The live class still drives the
running flow; only the projection omits it.
Found by CodeRabbit on #7063.
* test(flows): assert the response_format omission warnings
The projection tests only checked for None, so removing the warning that tells
an author their response_format was dropped would still pass.
Found by CodeRabbit on #7063.
* fix(flows): only project a response_format ref that imports back
The check rejected <locals> classes but still emitted a path for a nested one.
The loader splits a ref on its last dot, so module.Outer.Route resolves
module.Outer as a module that does not exist - proven: reload raised
JSONProjectError. A create_model() class held only in a local is unreachable
the same way.
Projection now confirms module.qualname resolves back to the class, against the
already-imported module so it never triggers an import.
Found by CodeRabbit on #7063.
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
090633737c |
feat(flows): enhance conversational flow documentation and APIs (#7104)
- Updated the description to clarify the use of `handle_turn` and structured streaming in multi-turn chat applications. - Added a warning about the experimental nature of the conversational features. - Improved the overview section to include structured streaming and refined the explanation of session handling. - Enhanced the API documentation for `handle_turn`, `stream_turn`, and `chat` methods, emphasizing their roles in conversational flows. - Clarified the turn lifecycle and the handling of user messages within the flow. - Updated examples to reflect changes in message handling and session tracing. - Ensured consistency across language versions in the documentation. |
||
|
|
9e9a8577be |
feat(events): report project creation with the id minted for it (#7074)
* feat(telemetry): report project creation with the id minted for it Acquisition was only observable from a project's first run. That misses every project created and never run, and dates the rest to the wrong day. All three scaffolding paths already mint a project_id into the new pyproject.toml. None of them reported it, and two of them - create_crew and create_json_crew, which is the default `crewai create crew` path - emitted no telemetry at all. `Project Created` carries the kind (crew, json_crew, flow) and the id that was just minted, and is emitted after the mint so it can carry it. The attribute is `created_project_id`, not `project_id`, because those are two different things. CommonAttributesSpanProcessor stamps `project_id` on every span from get_project_id(), which reads the current working directory and is cached for the life of the process - during `crewai create` that describes the directory the command was run from, not the project being created. Reusing the name would have given one column two meanings depending on span type. Nothing is emitted for `create_crew(parent_folder=...)`: that adds a crew to a project which already exists, mints no id, and is not an acquisition. The existing `Flow Creation` span is left exactly as it is. Note for whoever reads it: it is emitted from two places with two different meanings - CLI scaffolding (create_flow.py) and runtime flow construction (event_listener.py on FlowCreatedEvent) - so it cannot separate acquisition from usage. Not changed here because it is a live series and renaming it would break continuity. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RfV2uMqWRcdfufMvtdCVoN * test(telemetry): stop the creation-span tests exporting to the real collector The recorded_spans fixture has to enable telemetry for its assertions to mean anything - a disabled Telemetry never builds self.provider, so every assertion would pass vacuously against a non-recording span. But enabling it is exactly what makes __init__ wire a BatchSpanProcessor around the real SafeOTLPSpanExporter, pointed at the production collector. Measured, with a spy on both SafeOTLPSpanExporter classes reporting at interpreter exit: before this change the file made 3 real export calls, handed 3 synthetic Project Created spans with invented created_project_id values to the production exporter, and completed 3 connects to the collector. After: 0, 0, 0. Sampling at pytest_sessionfinish reports 0 either way and is how this was missed - BatchSpanProcessor flushes on a background timer, and with no provider.shutdown() the flush lands in the atexit handler, which runs after sessionfinish. --block-network does not prevent it: it is function-scoped and only swaps socket.connect, which a background batch thread outlives. Follows telemetry_with_exporter in tests/telemetry/test_tracer_isolation.py: _NullExporter swapped in before construction, _register_shutdown_handlers suppressed so no atexit hook is left behind, and provider.shutdown() in finally. Patch target is crewai_core because that is where this Telemetry comes from. Also disambiguates the docs rows: the minted ID belongs to the new project, not to the directory the command ran in, and the two can differ. Reworded in all four languages rather than renaming the token to created_project_id - this table documents data, never span-attribute keys (kind and crewai_version on the same row are unnamed), and `project_id` is already the page's name for the pyproject.toml key. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RfV2uMqWRcdfufMvtdCVoN * docs(ar): use tanwin fath on the letter, not on the alif مشروعًا / جديدًا rather than مشروعاً / جديداً, in the row added by this PR. Both forms appear in docs/edge/ar (3 each), so this is not a house convention being broken either way; the corrected form is the more standard one and the text is mine. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RfV2uMqWRcdfufMvtdCVoN * docs: stop the creation row implying the span's project_id holds the minted value CodeRabbit re-raised this as Major after I rejected the first version, and it was right to. My rejection argued the page never names wire keys, so introducing created_project_id would be its only one. That part still holds -- grep finds no attribute key anywhere in the four files. But it was the wrong conclusion: the row still used the token `project_id` for a value the span does NOT carry under that key, while the same span's real project_id holds the cwd-derived value. The row also already exposes literal wire values (`crew`, `json_crew`, `flow` are the actual kind values), so "this page has no wire detail" was overstated. Dropping the token resolves the ambiguity without adding the page's only key name: the row now says "the project ID minted for that new project", and names `project_id` only to say the minted value is recorded separately from it. All four languages. No docs/v*/ touched. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RfV2uMqWRcdfufMvtdCVoN * docs: use an em dash in the creation row, matching the rest of the page The two ASCII `--` occurrences in these files were both mine, introduced by this PR: the page otherwise uses em dashes throughout (en 4, ar 4, ko 2, pt-BR 4). CodeRabbit flagged pt-BR; the same slip was in en, so both are fixed. Now zero ASCII `--` across all four language files. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RfV2uMqWRcdfufMvtdCVoN --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-authored-by: Vidit Ostwal <110953813+Vidit-Ostwal@users.noreply.github.com> |
||
|
|
f4731f5025 |
feat(events): record whether a run had inputs, without recording the inputs (#7072)
* feat(telemetry): record whether a run had inputs, without recording the inputs
The `crew_inputs` payload is gated behind `share_crew` and stays that way, so the
only way to tell a parameterised run from an unparameterised one was to read a
gated key: it is present on roughly 0.02% of spans, all of them opt-in sharers.
That is a measurement of people who opted into sharing, not of users.
`crew_inputs_present` carries just the answer -- "true"/"false" -- on the
already-ungated `Crew Created` span. The payload stays inside the `share_crew`
branch, so nothing new about the contents of anyone's inputs is collected.
A string, for the reason `crew_memory` is a string, and the encoding matters
more here because the majority case is the empty one. Measured over a single day
(312,424,709 spans): `vInt64='0'` occurs 0 times and `vBool='false'` occurs 0
times, while `vStr='0'` does occur. proto3 omits the zero value for ints as well
as bools, so an integer key count would have silently dropped every
unparameterised run -- and among sharers, 54.46% of runs pass `{}`.
`{}` and `None` are both "false": an empty dict parameterises nothing, so
truthiness is the question being asked.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RfV2uMqWRcdfufMvtdCVoN
* test(telemetry): assert input keys are absent too, not only input values
The gating test checked only the input value. A regression that emitted the input
keys - json.dumps(sorted(inputs)) or similar - would have passed it, and key
names are user data as much as values are.
Verified by injecting exactly that regression: the new assertion fails on it and
passes once reverted.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RfV2uMqWRcdfufMvtdCVoN
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
4718b190d1 |
fix(cli): open the conversational TUI for a declarative chat flow (#7060)
* fix(cli): open the conversational TUI for a declarative chat flow `crewai run` refused a declarative conversational flow and told the user to drive it from Python. That was wrong: the conversational TUI already exists and already does this job. `CrewRunApp(conversational=True)` renders a chat pane and drives `handle_turn` per message (crew_run_tui.py:833-935), and `kickoff_flow._run_conversational_flow_tui` launches it for a Python conversational Flow. A declaration-built flow satisfies everything that TUI needs -- `handle_turn`, a settable `defer_trace_finalization`, and `finalize_session_traces()` -- so it now routes there instead of exiting. A chat loop still needs a terminal. A headless run (`is_interactive()` false, which folds in CREWAI_DMN) says what it would have needed rather than kicking off a single turn and presenting that as the whole conversation. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(cli): do not send a human-feedback chat flow to the Textual TUI A declaration can carry both a `conversational:` block and a method with `human_feedback:` -- verified, both predicates return True on the same flow. Routing it to the chat TUI hangs: the runtime collects feedback with a blocking `input()` (flow/runtime/__init__.py:3719) that Textual cannot service, so the prompt is never shown. The STEPS TUI already declines these for exactly this reason. Such a flow now falls back to the terminal REPL, which can prompt. Also updates the guide in en/ar/ko/pt-BR: it still said `crewai run` has no chat loop and exits, which is now the opposite of what the CLI does. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(cli): reject --inputs on a conversational flow instead of dropping it The conversational branch returns before `_resolve_flow_inputs`, and the TUI calls `handle_turn(message)` -- which owns the kickoff inputs, passing `{"id": session_id}` itself. So any `--inputs` value was silently discarded and the conversation ran as if it had been applied. It now errors, and says that resuming a session by id is not wired up yet rather than implying it worked. Also corrects the Arabic guide: `مُوجّه محجوز` reads as "reserved router", not the blocking prompt it describes. Both found by CodeRabbit on #7060. * fix(cli): document the conversational routing exceptions Three review follow-ups: - The Arabic guide read خدمته (masculine) against the feminine مُطالبة introduced by the last fix. - Both docstrings described a routing path that now has exceptions: a conversational declaration rejects --inputs and skips state-schema resolution, and a human-feedback one uses the terminal REPL. - The --inputs rejection test accepted SystemExit(0); it now pins code 1. Found by CodeRabbit on #7060. * fix(cli): reject --inputs on a chat flow even when it parses empty parse_inputs_json returns {} both when the option is absent and when the user passes --inputs "{}", so the falsy check started the TUI for the second case while the docs said it was unsupported. The conversational path now takes whether the option was supplied, not what it parsed to. Documents the restriction in en, ar, ko and pt-BR. Found by CodeRabbit on #7060. * test(cli): pin the headless conversational exit status pytest.raises(SystemExit) also accepts SystemExit(0), so the error path could regress to a successful exit unnoticed. Found by CodeRabbit on #7060. --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
89c04a0a08 |
Clarify Arize Phoenix observability docs (#7069)
Co-authored-by: Dat Ngo <datngo@Mac.digi.box> |
||
|
|
b3ab193c31 | [docs-freeze] docs: snapshot and changelog for v1.15.17 (#7055) | ||
|
|
4dfd074fae |
docs(flow): document declarative conversational flows (#7035)
* test(flow): unskip the conversational end-to-end suite The `conversational_graph_broken` marker parked 21 end-to-end conversational tests with the reason "the definition-first start migration intentionally stopped scanning inherited methods, so that graph no longer registers". That is no longer true: `_iter_flow_methods` walks the MRO for `__conversational_only__` methods (dsl/_utils.py:406-420), so a `conversational = True` subclass does register `route_conversation`, `converse_turn`, `end_conversation` and `answer_from_history_turn` — which `test_flow_definition.py:391-407` already asserts. Removing the marker takes the file from 47 passed / 21 skipped to 68 passed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(flow): make conversational opt-in unmistakable Opting a Flow into chat took two statements, and forgetting one failed silently. With `@ConversationConfig(...)` but no `conversational = True`: `FlowDefinition.conversational` came back `None`, the built-in graph never registered, and `handle_turn()` returned `None` without appending a message or raising — while `chat()` reported "only available on conversational flows" on a class that was literally decorated with a conversational config. Three changes: - `ConversationConfig.__call__` now also sets `conversational = True`. Every field on the config is consumed only by the conversational graph, so a decorated non-conversational Flow could only ever discard it. - `FlowConversationalDefinition.enabled` defaults to True. The block is absent on non-conversational flows, so declaring it is the opt-in; `enabled: false` remains an explicit opt-out. - `handle_turn()` raises like `chat()` and `stream_turn()` already do instead of silently returning `None`. Setting `conversational = True` by hand still works and is still the way to opt in without a config. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(flow): let a declaration drive conversational mode `FlowDefinition.conversational` was written by the DSL projection and read by nothing: every conversational gate resolved through `type(self).flow_definition()` — the class projection — instead of `self._definition`, the declaration a flow was actually built from. So `Flow.from_declaration()` on a definition with a conversational block produced a flow that reported itself non-conversational, dropped the user message, and never registered its declared routes. Resolution rules, applied consistently: - Structure (enabled, methods, route labels, builtin/internal routes) comes from `self._definition`, which is the loaded declaration for a declarative flow and the class projection otherwise. Both paths now agree. - Behavior (`conversational_config`) still prefers the class attribute, which can hold live objects — a configured LLM, a custom BaseLLM, a response_format model class — that the serializable definition degrades to a config dict or a `module:qualname` ref. Reading the definition first would silently downgrade every decorated Python flow. A declaration-built flow has no class config, so `_config_from_definition` supplies one, cached for stable identity. - A declared `state:` block is never replaced. `_create_default_extension_state` is consulted before `_create_definition_state`, so returning `ConversationState` there discarded every field the declaration asked for. It now yields to a declared state and only supplies the default when nothing else does. The class-scoped `_is_conversational` / `_conversational_definition` classmethods are gone; the existing instance-scoped `_is_conversational_enabled` is the single gate. A router `response_format` that survived serialization as a ref or schema dict is dropped with a warning rather than handed to `llm.call()`, which needs a real class. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(flow): synthesize the built-in conversational methods for declarations A declaration carrying `conversational: {}` loaded clean and then ran zero methods and returned `None`, because the four built-in graph handlers are inherited from `_ConversationalMixin` and a declaration has nothing to inherit from. Authors had to name `crewai.experimental.conversational_mixin:_Conversational Mixin.route_conversation` and three siblings by hand. `Flow._extend_definition` is a new runtime extension hook, called once `_definition` is resolved and before methods are bound. The conversational mixin overrides it to fill in `route_conversation`, `converse_turn`, `end_conversation` and `answer_from_history_turn` when they are missing, using the same code refs the DSL projection already emits so a declaration and a class projection of the same flow produce identical method definitions. Synthesis is deliberately a runtime concern, not a contract one: `FlowDefinition` stays independent of the authoring layer and of the engine, as `test_flow_definition_contract_is_dsl_agnostic` requires, and a loaded declaration still serializes back to exactly what its author wrote. Route descriptions are now carried by the contract. The DSL projects a handler docstring's first line into `FlowMethodDefinition.description`, and the router catalog reads that before falling back to the live docstring. This also fixes a real defect: for a declarative flow `getattr(type(self), handler_name, None)` is `None`, and the old code read `None.__doc__` — so the router LLM was told a route's description was "The type of the None singleton." Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(flow): let an agent or crew handler reply in a conversation (#7034) `handle_turn` promotes a handler's return value to the assistant message when the handler did not append one itself, but the check required `isinstance(result, str)`. Declarative `agent` and `crew` actions return `LiteAgentOutput` and `CrewOutput`, whose text lives on `.raw` — so the most natural declarative handler was exactly the one whose reply never reached the transcript. `_is_public_turn_result` now unwraps `.raw` before deciding, matching `_stringify_result`, which already did. The routing-artefact guards are applied to the unwrapped text, so an output echoing a route label or this turn's intent is still not promoted. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(flow): keep privately recorded agent results out of the transcript Unwrapping `.raw` in `_is_public_turn_result` made the end-of-turn fallback promote `LiteAgentOutput` / `CrewOutput` objects that the handler had already recorded via `append_agent_result` with the default private visibility. That call does not set `_assistant_reply_appended`, so the fallback republished the very object the handler asked to keep private — defeating `visible_agent_outputs`. Reproduced against `main` for contrast: no leak before the unwrap, leak after. `append_agent_result` now remembers the object it recorded for the duration of the turn, and the fallback skips anything already routed that way. The check is identity-based on purpose: a handler that records scratch work privately and then returns a user-facing summary still gets that summary promoted, which a simple "handler already handled it" flag would have broken. Found by Cursor Bugbot on #7033. * feat(flow): mark a declarative chat flow conversational on the instance A declaration enables chat through `conversational.enabled`, without the `conversational = True` class attribute. Callers outside this package capability-check that attribute -- the AG-UI serving guide states it as a requirement -- so it disagreed with `_is_conversational_enabled()` and a declarative conversational flow looked non-conversational from outside. `_extend_definition` now sets it on the instance when the definition enables chat. Instance-only on purpose: the DSL projection reads the attribute off the *class* to decide whether to emit a conversational block, so setting it there would make every later subclass look conversational. Verified on a real declarative flow: `conversational` and `stream_turn` both now satisfy the documented capability check, while `Flow.conversational` and any later subclass stay False. * refactor(flow): derive routing-artefact labels from the effective routes `_is_public_turn_result` matched a literal set of route labels, duplicating knowledge that `_effective_builtin_routes()` already owns. A declaration that adds a builtin route was not covered, so a handler echoing that label could be promoted into the transcript -- the same class of divergence already fixed for `route_turn`. Verified the derived set is byte-identical to the old literal one for a class-based flow, so this is a pure generalization: `conversation` and `route_to_flow` stay explicit because neither is a route. Also replaces a tuple-index lambda in the chat REPL test with a named `input_fn`; it relied on tuple evaluation order and on the list being mutated before its length was read. Both found by CodeRabbit on #7033. * docs(flow): document declarative conversational flows The authoring skill told LLM authors "use top-level `conversational` only when the user asks for a chat flow" while documenting none of its 19 fields — there was no ModelSpec for either conversational model, so the API reference appendix skipped them entirely. - Adds both conversational models to the skill reference, with field descriptions, and registers them under the existing `conversational` skip so `skills(skips=["conversational"])` still suppresses the whole block. - Adds authoring rules: do not declare the built-in graph, do not name a handler after the route it listens to, do not declare state unless it needs extra fields, and give every route handler a description. - Documents the declarative form in the conversational-flows guide across en, ar, ko and pt-BR, including what is supplied automatically, how to run it, and what a declaration cannot express (live LLM objects, a response_format class, route_turn overrides). - `crewai run` on a conversational declaration now says it has no chat loop and points at handle_turn/chat, instead of quietly running a single turn and exiting. It fails closed: a flow that cannot be inspected runs normally. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(flow): render the conversational router section in the skill Both conversational models shared one `Conversational` section, and the template renders only the first model of a non-union section. The router's fields were therefore dropped from the API reference and the generated link to them pointed at a heading that did not exist. Also softens the built-in-handler rule: `_extend_definition` keeps an author-supplied entry and the guide documents that override, so the skill should say to omit those handlers by default rather than never declare them. Adds regression tests for both sections rendering, for every field of both models appearing, and for `skips=["conversational"]` suppressing both. Both found by CodeRabbit on #7035. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs(flow): correct the route-description rule in the authoring skill The rule said every route handler must define `description`, but `conversational.router.route_descriptions` is the higher-precedence source -- `_build_route_catalog` checks the overrides before falling back to the method description. Either one describes a route; the rule now says so, and says what happens when a route has neither. --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-authored-by: ViditOstwal <viditostwal@gmail.com> Co-authored-by: Vidit Ostwal <110953813+Vidit-Ostwal@users.noreply.github.com> |
||
|
|
9b1f4938f0 |
fix(tools): pin SSRF checks to each redirect hop and peer IP (#6981)
* fix(tools): pin SSRF checks to each redirect hop and peer IP validate_url only inspected the original URL string, so scraping fetches could follow a 302 to an internal address or rebind DNS between check and connect. Route safe_get through an HTTPAdapter that re-validates every hop and connects to the authorised sockaddr, and let FORCE_SAFE_PATHS ignore a tenant-supplied escape hatch on managed workers. Co-authored-by: Rip&Tear <theCyberTech@users.noreply.github.com> * Potential fix for pull request finding 'Except block handles 'BaseException'' Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com> * test(azure): use a plain stand-in for Responses API delegate mocks MagicMock instances are not reliably stored on Pydantic PrivateAttr via BaseLLM.__setattr__, which left _responses_delegate as None and failed last_response_id / reset_chain assertions on CI. Co-authored-by: Rip&Tear <theCyberTech@users.noreply.github.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Rip&Tear <theCyberTech@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com> |
||
|
|
808ecc95f5 | [docs-freeze] docs: snapshot and changelog for v1.15.16 (#6991) | ||
|
|
4b9b8bcbb9 |
feat(events): record what kind of exception ended a flow (#6982)
* feat(telemetry): record what kind of exception ended a flow Flow failures are visible but undiagnosable. Live data shows roughly 17% of flows ending in outcome=failed, and 72% of AgentExecutor failures completing in under 200ms - far too fast to be an LLM call - but nothing records what the failure actually is, so there is no way to tell a real defect from a user pressing Ctrl-C. Record the exception's class name as error_type on Flow Completed and Flow Method Failed. The class name only: str(error) is never read, because it routinely carries prompts, model output, file paths and credentials. The isidentifier() check is the allowlist that enforces it - any message text reaching that argument carries a space or punctuation and is dropped - and it lives inside Telemetry rather than at the call site so a future caller cannot bypass it. Method names and flow state remain unrecorded. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ASfWmW3RGy4qAQm6s8U9jH * docs(frontend): point the frontend guides at their edge paths The frontend guides added in #6686 exist only under edge - they are not in any frozen version snapshot - but their 80 internal links use the bare /en/guides/frontend/... form, which resolves against the released versions where those pages do not exist. mint broken-links fails on every one of them, which blocks every open PR, not only the one that added them. Use the /edge/en/... form the repo already uses for other edge-only pages (concepts/streaming, learn/execution-boundary-hooks). Anchors are preserved. No page content changes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ASfWmW3RGy4qAQm6s8U9jH --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
27083f4131 |
docs: add Frontend guides (CopilotKit + AG-UI) (#6686)
Some checks failed
CodeQL Advanced / Analyze (actions) (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Vulnerability Scan / Detect changes (push) Has been cancelled
Vulnerability Scan / pip-audit (push) Has been cancelled
Check Documentation Broken Links / Check broken links (push) Has been cancelled
Nightly Canary Release / Check for new commits (push) Has been cancelled
Nightly Canary Release / Build nightly packages (push) Has been cancelled
Nightly Canary Release / Publish nightly to PyPI (push) Has been cancelled
* docs: add Frontend guides section (CopilotKit + AG-UI) Add a Frontend sub-group under Guides documenting how to build user interfaces for CrewAI Crews and Flows with CopilotKit over the AG-UI protocol. Pages: overview, generative UI, tool-based generative UI, agentic generative UI, human-in-the-loop, shared state, frontend actions, predictive state updates, and channels. * docs: mirror Frontend guides into v1.15.5 (Latest) Also register the Frontend sub-group and pages under the default v1.15.5 version so the section is visible without switching to Edge. * docs(frontend): address audit — correct APIs and claims - Use useRenderTool for display-only tool rendering (was useFrontendTool) - Correct state 'auto-streams' claims: snapshot at step boundaries, document copilotkit_emit_state for mid-step progress - Fix setState usage to spread full state (replace, not merge) - Add tool description to the frontend-action example - Rewrite Channels with the real @copilotkit/channels createBot API (Slack + Discord adapters); drop unsupported platform claims - Note self-hosted vs managed CopilotKit paths and pin package versions * docs(frontend): remove versions callout from overview * docs(frontend): drop package-generation framing from emit_state note * docs(frontend): add generative UI spectrum (A2UI, reasoning) + Conversational Flows Rewrite generative-ui as the controlled/declarative/open-ended spectrum; add A2UI (declarative), Reasoning (controlled), and a Conversational Flows page; add a backend-tools section to tool-based; note the three execution shapes in the overview. * docs(frontend): address review — edge-only, attribute access, safe defaults Remove the docs/v1.15.5 mirror (versioned snapshots are cut from edge by the release tooling; the docs-snapshots CI guard rejects manual docs/v* writes). Use attribute access on the LiteLLM message in shared-state, guard setState against undefined agent/recipe, and use Field(default_factory=list) for the agent-state list. |
||
|
|
77a929ecf5 |
feat(tracing): record when a trace batch is shared with amp (#6966)
* feat(tracing): record when a trace batch is shared with amp A trace batch is sent to AMP on every traced run, but nothing on the OSS side recorded that it happened, so a project's first touch with AMP was invisible in telemetry. Emit a Feature Usage span on successful finalization: tracing:ephemeral_sent before the user has an account, tracing:authenticated_sent after. Emitted on finalize rather than init because a batch that initializes and then fails to send never lands in AMP. Reading is_ephemeral from batch state at finalize also means a run that starts authenticated and falls back to ephemeral on a 401 reports ephemeral, which is what happened. Rides the existing Feature Usage span, so no new pipeline is needed to read it, and it carries project_id and coding_agent for free through the common attributes processor. Records only that a batch arrived - never trace contents, crew or flow names, inputs, or outputs. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ASfWmW3RGy4qAQm6s8U9jH * docs(telemetry): disclose the attributes the trace signal carries The new row said only that a batch arrived was recorded, which reads as excluding the common attributes every span carries. project_id and the coding assistant are disclosed in the Execution Environment row, but "only" actively contradicted that. Name them here too. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ASfWmW3RGy4qAQm6s8U9jH --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
93d7a07422 |
feat(telemetry): count deployments from any origin and record where they started (#6974)
Deployments were only countable through two span types that no aggregation reads, and cli_usage:deploy counted the TUI button rather than deployments. Emit deploy:created and deploy:pushed alongside the existing spans so a deployment is countable from the feature-usage aggregation no matter how it was started, and tag Create Crew Deployment / Start Deployment with source=cli|tui so the two origins stay distinguishable. Separately, the TUI's `t` and `d` key bindings dispatch straight to action_view_traces / action_deploy_crew, which never recorded anything - only on_button_pressed did. Every keyboard-driven trace view and deploy was therefore invisible. Move the recording into the actions, which both input paths funnel through, and past the completed guard so a mid-run keypress that does nothing is not counted as usage. Claude-Session: https://claude.ai/code/session_01ASfWmW3RGy4qAQm6s8U9jH Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
8b646620be |
fix(events): stop a failed turn from marking the next one failed (#6965)
* fix(telemetry): stop a failed turn from marking the next one failed
A conversational session that opts out of deferred finalization ends each
turn with its own FlowFailedEvent, emitted inside kickoff() before
handle_turn() emits ConversationTurnFailedEvent. The flag was therefore set
after the run that owned it had already cleared it, survived on the
instance, and reported the next healthy turn as failed.
Gate the flag on the run still having its start stamp: a deferring session
keeps it (no per-turn terminal event), so it still reports a failed turn at
session end.
Also aligns the Flow Lifecycle Signals privacy row with the rest of the
telemetry table, which qualifies every user-authored field it records with
"should not include personal info", and fixes a telemetry test that built
InputResponse with an unsupported `value` keyword - ask() swallowed the
TypeError, so the test asserted the signals while exercising the
provider-error path.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* test(telemetry): cover the streamed turn emitter of the failure flag
stream_turn() is the second emitter of ConversationTurnFailedEvent and
leaks the same flag as handle_turn(). Both regression tests fail on
|
||
|
|
28d868c4f4 |
[docs-freeze] docs: snapshot and changelog for v1.15.15 (#6964)
Some checks failed
CodeQL Advanced / Analyze (actions) (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Check Documentation Broken Links / Check broken links (push) Has been cancelled
Vulnerability Scan / Detect changes (push) Has been cancelled
Vulnerability Scan / pip-audit (push) Has been cancelled
Mark stale issues and pull requests / stale (push) Has been cancelled
|
||
|
|
7642e615a3 |
feat(flow): report flow outcome, duration and human-in-the-loop signals (#6961)
Some checks failed
CodeQL Advanced / Analyze (actions) (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Check Documentation Broken Links / Check broken links (push) Has been cancelled
Vulnerability Scan / Detect changes (push) Has been cancelled
Vulnerability Scan / pip-audit (push) Has been cancelled
Nightly Canary Release / Check for new commits (push) Has been cancelled
Nightly Canary Release / Build nightly packages (push) Has been cancelled
Nightly Canary Release / Publish nightly to PyPI (push) Has been cancelled
* feat(flow): report flow outcome and human-in-the-loop signals A flow reported only that it started. FlowFinishedEvent, FlowFailedEvent, MethodExecutionFailedEvent, MethodExecutionPausedEvent and FlowPausedEvent all reached the console formatter and stopped there, and FlowInputRequestedEvent, FlowInputReceivedEvent and ConversationTurnFailedEvent had no listener at all - so success rate, failure rate and every HITL pause were unmeasurable. Adds flow:completed, flow:failed, flow:method_failed, flow:paused, flow:hitl_paused, flow:input_requested, flow:input_received and flow:conversation_turn_failed as feature-usage spans, which the existing feature-usage aggregation already reads. Deliberately does not hold the Flow Execution span open to measure duration: flow_executions_daily_target counts those spans at start, so a run that never finishes would disappear from the count entirely. Duration needs its own span. Counts only - flow names, method names, error text and flow state are never recorded. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ASfWmW3RGy4qAQm6s8U9jH * feat(flow): record how long a flow ran Adds a Flow Completed span carrying flow_name, duration_ms and outcome, emitted when a flow finishes or fails. Elapsed time comes from a monotonic stamp taken at flow start and cleared on use. Kept separate from the Flow Execution span rather than holding that one open: it is emitted and closed at start and the daily aggregate counts it, so holding it would drop every run that is killed or crashes from the execution count. A killed run now simply has no Flow Completed row, and the count is unaffected. Elapsed time is an explicit duration_ms attribute rather than the span's own duration, which the ingestion pipeline stores as a suffixed string ("0.0000184s") that downstream aggregation parses to zero. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ASfWmW3RGy4qAQm6s8U9jH * feat(flow): tag flow origin and report resumed runs Two gaps found while testing the pause/resume path end to end. Resumed runs were invisible. There is no resume event: a restored run re-enters through kickoff(), so it looked identical to a fresh start. flow:resumed is derived from _is_execution_resuming at flow start, which makes flow:paused - flow:resumed the abandonment rate. Flow counts are dominated by CrewAI's own AgentExecutor, which is itself a Flow and runs once per agent execution - it is the top flow in the warehouse by a wide margin. Nothing distinguished it from a user's flows except guessing at the name. Both Flow Execution and Flow Completed now carry origin: "internal" when the flow class is defined under crewai.*, "user" otherwise. Tagging only the new span would have left the existing daily count unsplittable. Both span methods take origin with a default, so their signatures stay backward compatible. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ASfWmW3RGy4qAQm6s8U9jH * fix(flow): scope outcome and resume signals to user flows Two findings from review, both confirmed against the code. Outcome features counted CrewAI's own flows. The agent executor, memory encoding and memory recall are all Flows and all set suppress_flow_events; they run far more often than anything a user wrote, so flow:completed, flow:failed and flow:method_failed were mostly bookkeeping. Those three are now emitted only for flows the caller wrote. Internal outcomes are still recorded on the Flow Completed span, which carries origin. flow:resumed counted checkpoint restores. _is_execution_resuming is set both by from_pending (a human pause) and by a checkpoint restore that never paused for anyone, so resumes could exceed pauses and the abandonment rate was unusable. Keyed off _pending_feedback_context instead, which only from_pending sets. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ASfWmW3RGy4qAQm6s8U9jH * fix(flow): declare internal flows instead of inferring them Three findings from review, all confirmed against the code. Gating on suppress_flow_events was wrong. That flag asks for console quiet and is a public field, so a caller who set it on their own flow silently lost flow:completed, flow:failed and flow:method_failed. Deciding origin from the defining module was also wrong. Flow.from_declaration() returns a Flow typed in crewai.flow.flow, so a caller's declarative flow was reported as one of CrewAI's own - the inversion this split exists to prevent. Both had the same root cause: the discriminator was inferred. Flow now declares is_crewai_internal, set on the agent executor and the memory encoding/recall flows, and one helper serves both origin and the outcome gate. A failed conversational session was reported as completed. Its session closes with FlowFinishedEvent whatever happened, so a failed turn produced flow:conversation_turn_failed and flow:completed together. The turn failure is now recorded on the flow and read back when the session finishes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ASfWmW3RGy4qAQm6s8U9jH * refactor(flow): report flow lifecycle as spans, not feature usage Flow start, completion, pause and method failure are lifecycle facts, and the lifecycle is reported as spans everywhere else. Reporting them through feature usage put them in a table that aggregates on the feature string alone - it cannot carry origin, duration or outcome, so those signals could never be split between a user's flows and the ones CrewAI runs for itself. Adds Flow Paused and Flow Method Failed spans, and a resumed marker on Flow Execution so a run restored from a pause is not counted as a second fresh start. Removes the duplicate feature rows for completed, failed, method_failed, paused and resumed - every one of those facts is now on a span, with more attached to it than the feature row ever carried. Feature usage keeps only genuine adoption signals: flow:hitl_paused, flow:input_requested, flow:input_received and flow:conversation_turn_failed. Also clears the conversational turn-failure flag on every terminal path. A turn that failed without deferred finalization ends via FlowFailedEvent, and the flag left set there marked the next run on that instance as failed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ASfWmW3RGy4qAQm6s8U9jH * test(flow): update the flow_execution_span caller for the resumed argument Adding the resumed marker changed a signature that tests/utilities/test_events.py asserts on exactly, and that assertion was not re-run before pushing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ASfWmW3RGy4qAQm6s8U9jH * test(flow): make the checkpoint-restore guard actually guard The test asserted that flow:resumed was absent from feature usage, but that signal moved onto the Flow Execution span. The assertion could no longer fail, so a regression that mis-tagged checkpoint restores as resumes would have gone unnoticed. Now asserts the resumed attribute, and waits for the handlers: the manual emit dispatches asynchronously, so the previous shape also read its result before the listener had run. Confirmed it discriminates - keying resumed off _is_execution_resuming again fails it with [('RestoredFlow', True)] == [('RestoredFlow', False)]. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ASfWmW3RGy4qAQm6s8U9jH * fix(telemetry): record the resumed marker as a string Verified end to end against the live collector and ClickHouse: the pipeline encodes a boolean attribute as the presence of a vBool key, so false arrives as the key simply being absent. That is invisible in the schema and easy to read wrongly - crew_memory is extracted as "the attribute exists" and consequently reports 1 for 99.8% of crews against a field that defaults to False. A string leaves nothing to infer. Confirmed in the warehouse: the emitted span reads resumed = "false". Adds direct coverage for the attributes each flow span records, including both resumed values, and resets the Telemetry singleton in the helper so more than one span method can be exercised per session. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ASfWmW3RGy4qAQm6s8U9jH --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
65a4b7cede |
fix: emit FlowStartedEvent when a boundary hook aborts the flow (#6953)
* fix: emit FlowStartedEvent when a boundary hook aborts the flow A HookAborted at EXECUTION_START or INPUT propagated before `FlowStartedEvent` was emitted, so a policy deny left logs but no record of the execution. On abort, stamp the state id and open the flow scope before re-raising: the deny surfaces as a started -> failed execution while normal runs keep the existing ordering — the started event carries hook-resolved inputs and `id` rewrites keep redirecting persistence restoration. * docs: translate execution-boundary-hooks page to ar, ko, and pt-BR The English page updated on this branch had never been localized. Translate it into the three supported locales following `DOCS_TRANSLATIONS.md` and register the page in each locale's navigation in `docs/docs.json`. Untranslated link targets (the step-hooks page and the aborting-an-operation anchor) are omitted rather than pointed at English, matching the locale navigation convention. |
||
|
|
6c19669d63 |
refactor: update date injection functionality in agents (#6850)
* refactor: update date injection functionality in agents - Changed the description of the parameter to clarify that it injects the current date into the agent's prompt instead of tasks. - Removed the method as it was no longer needed. - Implemented a new method in the class to handle date injection directly into the prompt. - Updated tests to ensure the date is correctly injected into the system prompt and user messages based on the flag. * translations * nit |
||
|
|
11890e6701 |
Standardize CLI flags to kebab-case (#6880)
Some checks failed
CodeQL Advanced / Analyze (actions) (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Check Documentation Broken Links / Check broken links (push) Has been cancelled
Vulnerability Scan / Detect changes (push) Has been cancelled
Vulnerability Scan / pip-audit (push) Has been cancelled
Build uv cache / build-cache (3.10) (push) Has been cancelled
Build uv cache / build-cache (3.11) (push) Has been cancelled
Build uv cache / build-cache (3.12) (push) Has been cancelled
Build uv cache / build-cache (3.13) (push) Has been cancelled
* Standardize CLI flags to kebab-case with deprecated snake_case aliases. Unify active long-option naming across create, train, test, and replay while keeping hidden backward-compatible aliases and documenting the migration in edge docs and AGENTS.md. * Emit deprecation warnings when snake_case CLI flag aliases are used. Route hidden legacy flags through separate internal params so warnings fire only when the alias is supplied, and extend CLI tests for create, replay, and --help coverage. * Merge CLI deprecation warn helpers into warn_deprecated(kind=...). Replace warn_deprecated_command and warn_deprecated_flag with one helper that accepts kind="command" or kind="flag". |
||
|
|
094b94e8d0 |
fix(core): scope span export to our own tracer provider (#6954)
Some checks failed
CodeQL Advanced / Analyze (actions) (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Check Documentation Broken Links / Check broken links (push) Has been cancelled
Vulnerability Scan / Detect changes (push) Has been cancelled
Vulnerability Scan / pip-audit (push) Has been cancelled
Nightly Canary Release / Check for new commits (push) Has been cancelled
Nightly Canary Release / Build nightly packages (push) Has been cancelled
Nightly Canary Release / Publish nightly to PyPI (push) Has been cancelled
Mark stale issues and pull requests / stale (push) Has been cancelled
* fix(telemetry): stop exporting third-party spans to the collector set_tracer() installed CrewAI's TracerProvider as the global one, so every OTel-instrumented library in the host process - HTTP servers, Redis clients, ORMs - resolved trace.get_tracer() to our provider and exported to CrewAI's endpoint. A 20M-row sample of the telemetry table found 18,866 distinct operation names under our serviceName; CrewAI emits 21. The same wiring lost data in the other direction: when an application had already installed its own provider, our spans were created by theirs and went to their collector, so CrewAI received nothing from instrumented processes. Spans are now created from the private provider in both packages. Deletes _attach_common_attributes and its WeakSet/lock, whose multi-provider dedupe guarded a state that can no longer occur. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ASfWmW3RGy4qAQm6s8U9jH * fix(telemetry): keep process context on crewai-core spans Isolating each package to its own TracerProvider removed an accident the CLI spans depended on: crewai_core.telemetry had no CommonAttributesSpanProcessor, so its spans only ever carried coding_agent/runtime_context/project_id by riding the global provider that crewai installed at import. A differential capture of every span reaching the exporter showed 8 of 54 spans losing those attributes - Feature Usage (cli_usage:*), Start Deployment, Template Installed, Create Crew Deployment, Get Crew Logs, Remove Crew, Deploy Signup Error and Flow Creation. Moves the marker tables and the detect_* helpers to crewai_core.runtime_env and the processor plus common_span_attributes() to crewai_core.telemetry, so both implementations share one source of truth. crewai.telemetry.utils and crewai.utilities.constants re-export the moved names, so their import paths are unchanged. Also fixes a gap that predates the isolation change: a CLI-only process never imports crewai, so it never reported either attribute. It does now. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ASfWmW3RGy4qAQm6s8U9jH * fix(core): type test helpers and stop tests reaching the collector mypy runs over lib/crewai-core/tests (the one test tree not excluded), so the new test file needed full annotations and a narrowed span.attributes. Also patches SafeOTLPSpanExporter before Telemetry is constructed and shuts the provider down afterwards: __init__ wires a BatchSpanProcessor around the real OTLP exporter, so each test was attempting a live export and leaving its batch worker thread running. Corrects the marker-precedence docstring, which named Cursor third when the table checks it last so that assistants running inside its terminal are not masked. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ASfWmW3RGy4qAQm6s8U9jH * style(core): use one import form per module in telemetry tests Both test modules imported their telemetry module twice - once aliased for the monkeypatch target and once via from-import for the names. Dropping the alias in favour of monkeypatch's dotted-string target leaves a single import form and removes the need to qualify every reference. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ASfWmW3RGy4qAQm6s8U9jH * docs(core): drop comments that restate the code The TRACER_NAME constants were annotated with what their name and set_tracer()'s docstring already say, and the test fixtures narrated provider.shutdown() and the exporter patch at more length than either needed. Keeps the ones carrying something the code cannot: the resource-attribute ingestion quirk, why the marker tables moved packages, and the two ordering traps the fixtures exist to avoid. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ASfWmW3RGy4qAQm6s8U9jH --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
f7ba8e3521 |
[docs-freeze] docs: snapshot and changelog for v1.15.14 (#6877)
Some checks failed
CodeQL Advanced / Analyze (actions) (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Vulnerability Scan / Detect changes (push) Has been cancelled
Vulnerability Scan / pip-audit (push) Has been cancelled
Check Documentation Broken Links / Check broken links (push) Has been cancelled
Nightly Canary Release / Check for new commits (push) Has been cancelled
Nightly Canary Release / Build nightly packages (push) Has been cancelled
Nightly Canary Release / Publish nightly to PyPI (push) Has been cancelled
Mark stale issues and pull requests / stale (push) Has been cancelled
|
||
|
|
92012aec55 |
feat: split runtime context from coding agent, add project id (#6867)
Some checks failed
CodeQL Advanced / Analyze (actions) (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Check Documentation Broken Links / Check broken links (push) Has been cancelled
Vulnerability Scan / Detect changes (push) Has been cancelled
Vulnerability Scan / pip-audit (push) Has been cancelled
Build uv cache / build-cache (3.10) (push) Has been cancelled
Build uv cache / build-cache (3.11) (push) Has been cancelled
Build uv cache / build-cache (3.12) (push) Has been cancelled
Build uv cache / build-cache (3.13) (push) Has been cancelled
Mark stale issues and pull requests / stale (push) Has been cancelled
* feat(telemetry): split runtime context from coding agent, add project id The coding-agent field answered two questions at once. A run with no TTY reported "non_interactive" and an editor's integrated terminal reported "vscode_terminal", both in the same field as the assistant name, so a run that never had an assistant to detect was indistinguishable from one whose assistant we failed to recognize. Together those two values were the majority of what the field reported. detect_coding_agent now answers only which assistant, returning "unknown" when no marker matches. detect_runtime_context answers where the process runs: ci, serverless, hosted_ide, notebook, container, the editor terminals, and the interactive/non_interactive fallback. Both ride on every span, so an assistant running inside CI reports both rather than one masking the other. The runtime markers are published platform contracts - CI providers, container and serverless runtimes, hosted IDEs - so unlike the assistant table they need no per-tool verification step. Presence is checked; no value is read. The assistant table is unchanged: its entries still require a confirmed, session-scoped variable, and the existing guard test still enforces that. Spans also carry project_id when the project declares one. It is read through the read-only accessor, since minting an id belongs to the CLI commands a user invoked rather than to a library call during execution, and it is omitted entirely for projects without one. The attributes are computed once per process and memoized, so the project file is not re-read for each provider. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs: document execution environment telemetry attributes Adds the execution-environment row to the data table in en, ar, ko and pt-BR. Covers the assistant and runtime fields this branch splits apart and the project id, and states that detection reads only whether known environment variables are set. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(telemetry): detect runtime markers by presence, split paas from serverless Three findings from the CodeRabbit, code-quality and Cursor reviews. The runtime loop tested truthiness while constants.py documented presence, so a platform exporting a bare CI= fell through to the TTY fallback and was mislabelled as an ordinary local run. Presence is now what it says. The assistant markers keep truthiness deliberately: there an empty value means the tool set a placeholder rather than claiming the session. DYNO and WEBSITE_INSTANCE_ID marked Heroku dynos and Azure App Service instances as serverless, and since serverless is checked first they could never reach the container label. They move to a paas context, which is what they are: long-lived containers rather than per-invocation functions. AWS_EXECUTION_ENV is dropped entirely - it is set on ECS and EC2 as well as Lambda, and AWS_LAMBDA_FUNCTION_NAME already covers Lambda without the collision. The container probe no longer wraps os.path.exists in a try/except. os.path.exists handles OSError internally and returns False, so the handler guarded a condition that cannot occur and only hid the intent. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(telemetry): widen assistant detection from published marker sets The table previously covered three assistants because the rest were unverified. They are documented after all: vercel/detect-agent publishes a machine-readable detection matrix (agents.json), corroborated by the proposal in agentsmd/agents.md#136 and by microsoft/vscode#311734. Adds cline, gemini_cli, augment, opencode, antigravity and junie, plus CLAUDE_CODE alongside CLAUDECODE. Gemini's marker is confirmed by its own docs, which state that run_shell_command sets GEMINI_CLI=1 in the subprocess environment. Rule 2 excluded several entries those sources list. Goose's GOOSE_PROVIDER and Copilot's COPILOT_MODEL and COPILOT_GITHUB_TOKEN are user configuration, and a committed .env carrying one would relabel every ordinary run - the AIDER_MODEL trap the guard test already pins, now parametrized over all four. Replit's REPL_ID names a hosted environment rather than an assistant, so it stays a runtime context. Copilot sets no session marker at all today; that is an open request upstream. The new assistants are ordered ahead of Cursor, since CURSOR_* is set for every integrated terminal and would otherwise mask anything spawned inside it - the same ordering Codex already needed. Also adds the proposed cross-vendor AI_AGENT marker as a last resort, reported as "other". It establishes that an assistant is present without naming one, and its value is an arbitrary vendor string, so the value is never read. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(deps): raise gitpython and pypdf floors for new advisories gitpython 3.1.57 carries GHSA-9rj7-rf2p-w77r, GHSA-4gmw-gg2m-w46p, GHSA-hh9p-6wh2-4mfc, GHSA-wvpp-8hx9-p66j and GHSA-jm78-9fvv-mhgr: further unguarded git option forwarding in Repo.init, read-tree and git-config, plus arbitrary file read via --pathspec-from-file. Fixed in 3.1.58. pypdf 6.14.2 carries GHSA-fwg2-594c-jp42 and GHSA-fp3f-mc75-235c, unbounded runtime and memory on large content and /ToUnicode streams. Fixed in 6.15.0. Both floors were already pinned, so only the versions move. Their exclude-newer-package cutoffs had to move with them - 3.1.58 landed 2026-08-04 and 6.15.0 on 2026-08-06, both past the existing dates, so the resolver could not have seen either release. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(telemetry): share assistant precedence with the env-context path Four findings from the Cursor and CodeRabbit reviews, three of them the same root cause. get_env_context restated the precedence the shared table already defines, so every marker added for telemetry was invisible to it: a session exposing only CLAUDE_CODE reported claude_code on spans while emitting DefaultEnvEvent, and an assistant running inside a Cursor terminal reported that assistant on spans while emitting CursorEnvEvent. It now walks CODING_AGENT_ENV_MARKERS and maps the three assistants that have an event class of their own, defaulting the rest to DefaultEnvEvent. A test now asserts the two paths agree for every marker in the table, so they cannot drift again. The generic AI_AGENT marker was documented as presence-only but ran through the truthiness loop with everything else, so an empty value fell through to unknown. It moves out of the table and is checked by presence after it, which also keeps the named markers' truthiness intact. Azure Functions run on the App Service host and inherit WEBSITE_INSTANCE_ID, so moving that marker to paas would have relabelled them. The FUNCTIONS_* markers are checked first to keep them serverless. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(telemetry): stop export assertions depending on test order test_all_common_attributes_land_on_exported_spans failed in CI with an IndexError on an empty span list, and only in one shard: the suite runs with OTEL_SDK_DISABLED set, so TracerProvider hands out no-op tracers and an export-based assertion sees zero spans rather than a wrong attribute. It passed only when it happened to run after a test whose fixture flips the variable, which random ordering decides. Adds an otel_enabled fixture that sets the variable for the four tests asserting on exported spans. Three of them predate this branch and had the same latent dependency - they are fixed here because the new test made the ordering hit reachable, and leaving them would keep the required check red. Verified by running every test in the file individually, all of which previously exposed the dependency, and the telemetry suite three times under random ordering. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
5fa010450b |
[docs-freeze] docs: snapshot and changelog for v1.15.13 (#6866)
Some checks failed
CodeQL Advanced / Analyze (actions) (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Check Documentation Broken Links / Check broken links (push) Has been cancelled
Vulnerability Scan / Detect changes (push) Has been cancelled
Vulnerability Scan / pip-audit (push) Has been cancelled
Nightly Canary Release / Check for new commits (push) Has been cancelled
Nightly Canary Release / Build nightly packages (push) Has been cancelled
Nightly Canary Release / Publish nightly to PyPI (push) Has been cancelled
|
||
|
|
988f060c93 |
Fix Anthropic cache token usage underreporting (#6844)
* Fix Anthropic native provider to include cache tokens in input totals. Anthropic reports cache read and cache creation separately from input_tokens; fold them into input_tokens and total_tokens so billed usage is not underreported on cached workloads. * Reconcile unreconciled Anthropic cache tokens in UsageMetrics. LiteLLM and flow event paths can pass raw Anthropic usage where input_tokens excludes cache counters; fold them into prompt and total tokens without double-counting native provider payloads. Co-authored-by: Cursor <cursoragent@cursor.com> * Document UsageMetrics token field semantics for flows and crews. Clarify that total_tokens is prompt plus completion, breakdown fields are not additive, and Anthropic cache counters are folded into prompt_tokens. Co-authored-by: Cursor <cursoragent@cursor.com> * Document Anthropic cache token accounting in LLM provider docs. Explain how split Anthropic input counters map to UsageMetrics and link to the flows field semantics section. Co-authored-by: Cursor <cursoragent@cursor.com> * Restore cache_creation_tokens breakdown in UsageMetrics normalization. Keep cache writes as a separate breakdown field while they remain folded into prompt_tokens for billed totals. * Replace cross-page doc links with plain-text section references. Avoid internal hyperlinks between concept pages for SEO; point readers to section names in prose instead. * Apply ruff formatting to usage metrics helpers. * Sync ar, ko, and pt-BR docs for Anthropic usage metrics updates. Translate UsageMetrics semantics and Anthropic cache token accounting changes in crews, flows, and llms concept pages. --------- Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
18c52c4e1d |
[docs-freeze] docs: snapshot and changelog for v1.15.12 (#6836)
Some checks failed
Build uv cache / build-cache (3.10) (push) Has been cancelled
Build uv cache / build-cache (3.11) (push) Has been cancelled
Build uv cache / build-cache (3.12) (push) Has been cancelled
Build uv cache / build-cache (3.13) (push) Has been cancelled
CodeQL Advanced / Analyze (actions) (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Check Documentation Broken Links / Check broken links (push) Has been cancelled
Vulnerability Scan / Detect changes (push) Has been cancelled
Vulnerability Scan / pip-audit (push) Has been cancelled
Mark stale issues and pull requests / stale (push) Has been cancelled
Nightly Canary Release / Check for new commits (push) Has been cancelled
Nightly Canary Release / Build nightly packages (push) Has been cancelled
Nightly Canary Release / Publish nightly to PyPI (push) Has been cancelled
|
||
|
|
8320178697 |
fix(flow): clarify conversational route/handler name collision errors (#6825)
* fix(flow): clarify route/handler name collision validation errors
When @listen(...) includes the handler's own name, FlowDefinition validation
fails. This change improves the error text and how it surfaces for Python
Flow classes.
What changed
- _self_listen_error in flow_definition.py: two message variants (conversational
vs default), both include the listen condition
- build_flow_definition in dsl/_utils.py: wraps FlowDefinition ValidationError
with the Python Flow class name
- Tests for declarative and DSL-built flows; docs follow in a separate commit
When each error surfaces
1. Conversational message — FlowDefinition validation when
conversational.enabled is true and listen references the handler name.
Example: @listen("create_video") on def create_video in a conversational flow.
Surfaces via:
- FlowDefinition.from_declaration(dict/yaml) → pydantic ValidationError for
FlowDefinition (Value error, methods.create_video.listen listen condition...)
- MyFlow.flow_definition() / MyFlow() → ValueError Invalid flow definition
for MyFlow: ... (wrapped by pydantic as ValidationError for MyFlow on
instantiation)
2. Default (non-conversational) message — same trigger check when the flow is
not conversational. Example: @listen("publish") on def publish.
Surfaces via the same paths as (1).
3. Class-name wrapper — only on the Python DSL path when build_flow_definition
catches FlowDefinition ValidationError. Prepends Invalid flow definition for
{ClassName}: to the underlying message from (1) or (2). Does not apply to
from_declaration without a Flow class.
* docs(flow): explain conversational handler naming vs route labels
Document why @listen route labels must differ from handler method names and
recommend the handle_* naming pattern.
* docs(cli): warn against matching @listen labels to handler names
Add AGENTS.md guidance for crew and flow scaffolding so coding assistants
do not name handlers the same as their @listen route or event labels.
* docs(cli): clarify @listen self-reference fails at validation
Document that matching @listen labels to handler names raises a validation
error at flow instantiation, and that the runtime loop only occurs if
validation is bypassed.
|
||
|
|
fef32f43a0 |
feat(cli): unify scaffolding under crewai create <resource> (#6821)
* feat(cli): add canonical `crewai create tool` command Unify tool scaffolding under the create verb and deprecate `crewai tool create` with a yellow warning while keeping backward compatibility. * feat(cli): add canonical `crewai create skill` command Unify skill scaffolding under the create verb and deprecate `crewai skill create` with a yellow warning while keeping backward compatibility. * feat(cli): add canonical `crewai create template` command Unify template scaffolding under the create verb and deprecate `crewai template add` with a yellow warning while keeping backward compatibility. * docs: document unified `crewai create` scaffolding commands Document canonical create forms for tool, skill, and template projects, note deprecated aliases, and update skills and agents-md guides. * feat(cli): extend create picker and DMN guidance for all types Show tool, skill, and template in the interactive create picker and list every supported type in the CREWAI_DMN usage error. * fix(cli): allow create tool/skill/template in CREWAI_DMN mode Only set skip_provider in DMN mode for crew creation, since tool, skill, and template paths reject that flag as a crew-only option. * test(cli): patch TemplateCommand at cli lookup site in DMN test create() resolves TemplateCommand from crewai_cli.cli, not from remote_template.main directly. |
||
|
|
b5bf858b5c | [docs-freeze] docs: snapshot and changelog for v1.15.11 (#6817) | ||
|
|
c8f441cffa |
feat(crewai-tools): add IBM Db2 search tool (#5885)
Some checks failed
CodeQL Advanced / Analyze (actions) (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Check Documentation Broken Links / Check broken links (push) Has been cancelled
Vulnerability Scan / pip-audit (push) Has been cancelled
Build uv cache / build-cache (3.11) (push) Has been cancelled
Build uv cache / build-cache (3.12) (push) Has been cancelled
Build uv cache / build-cache (3.13) (push) Has been cancelled
Build uv cache / build-cache (3.10) (push) Has been cancelled
Nightly Canary Release / Check for new commits (push) Has been cancelled
Nightly Canary Release / Build nightly packages (push) Has been cancelled
Nightly Canary Release / Publish nightly to PyPI (push) Has been cancelled
Mark stale issues and pull requests / stale (push) Has been cancelled
* feat(crewai-tools): add db2 search tool * refactor(crewai-tools): improve db2 search tool implementation * feat(tools): improve DB2VectorSearchTool validation, security, and configurability * docs: add DB2SearchTool documentation * feat: add DB2 search tool * docs: update DB2SearchTool documentation * fix: address CodeRabbit review feedback * fix: validate non-empty filter_by in DB2ToolSchema * chore: trigger CodeRabbit re-review * feat: fortify DB2 tool; fixed JSON response shape, added input guards and config validation * refactor(db2): replace DB2Config with connection_string field * refactor(db2): remove dead _setup_db2 validator and importlib import * refactor(db2): remove dead guard in _connect as _disconnect() is called at the end of every _run, so self.connection is always None when _connect is called next. The 'if not self.connection' guard was dead code. * fix(db2): tighten _validate_identifier regex. Old regex allowed leading digits, multiple periods and dot-only strings (e.g. '.....' passed). * fix(db2): replace __import__ with importlib.import_module in _generate_embedding as keeping openai as a lazy optional import since it is not always required. * perf(db2): cache OpenAI client in _openai_client to avoid re-instantiation as OpenAI(api_key=...) was recreated on every _generate_embedding call. Extract into _get_openai_client() which lazily initialises and caches self._openai_client on first use, reusing it for all subsequent queries. * docs(db2): clarify tool description to mention embedding fallback * docs(db2): update README supported features to clarify embedding behaviour. 'OpenAI embedding fallback' implied it was optional. Replaced with 'Uses a custom embedding function if supplied, otherwise OpenAI embeddings.' * updated both code examples to use the correct import path and public run() method. * feat(crewai-tools): add db2 search tool * refactor(crewai-tools): improve db2 search tool implementation * feat(tools): improve DB2VectorSearchTool validation, security, and configurability * docs: add DB2SearchTool documentation * feat: add DB2 search tool * docs: update DB2SearchTool documentation * fix: address CodeRabbit review feedback * fix: validate non-empty filter_by in DB2ToolSchema * chore: trigger CodeRabbit re-review * feat: fortify DB2 tool; fixed JSON response shape, added input guards and config validation * fix(db2): address ruff and mypy linter errors * style(db2): apply ruff format to db2_search_tool.py * fix(db2-search-tool): address PR review comments - Restore DirectoryReadTool export accidentally removed; add DB2VectorSearchTool and DB2ToolSchema to crewai_tools.tools __init__ and __all__ - Align _ALLOWED_METRICS whitelist with Db2 VECTOR_DISTANCE API: replace DOT_PRODUCT/L2_DISTANCE with EUCLIDEAN_SQUARED/DOT/HAMMING/MANHATTAN - Replace ImportString fields for db2_package/db2_dbi_package with plain Any + lazy importlib.import_module in new _resolve_db2_packages() to avoid Pydantic default-validation gap where strings were never resolved at construction time - Move docs from frozen docs/v1.13.0/ snapshot to docs/edge/en/tools/database-data/ and register in docs/docs.json; update examples to match actual API (connection_string constructor, not DB2Config), correct return format, and align documented distance metrics with the whitelist * fix(db2-search-tool): resolve default and string db2 package imports dynamically * fix(db2-search-tool): export DB2VectorSearchTool and DB2ToolSchema from package-level crewai_tools * docs(db2-search-tool): fix installation command and import path in README --------- Co-authored-by: priyanshu-krishnan1 <priyanshu.krishnan1@ibm.com> Co-authored-by: GeetikaChugh24 <geetika@ibm.com> Co-authored-by: Lorenze Jay <63378463+lorenzejay@users.noreply.github.com> Co-authored-by: Dhruv Chaturvedi <dhruv_insights@Dhruvs-MacBook-Pro.local> |
||
|
|
3932d3fea6 | [docs-freeze] docs: snapshot and changelog for v1.15.10 (#6756) | ||
|
|
3266932f00 | [COR-636] Remove migrated AMP documentation (#6730) | ||
|
|
112762a7fa |
[docs-freeze] docs: snapshot and changelog for v1.15.9 (#6726)
Some checks failed
CodeQL Advanced / Analyze (actions) (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Check Documentation Broken Links / Check broken links (push) Has been cancelled
Vulnerability Scan / pip-audit (push) Has been cancelled
Mark stale issues and pull requests / stale (push) Has been cancelled
|
||
|
|
453676c61a |
feat(tools): surface tool failures instead of reporting them as success (#6712)
Some checks failed
CodeQL Advanced / Analyze (actions) (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Check Documentation Broken Links / Check broken links (push) Has been cancelled
Vulnerability Scan / pip-audit (push) Has been cancelled
Nightly Canary Release / Check for new commits (push) Has been cancelled
Nightly Canary Release / Build nightly packages (push) Has been cancelled
Nightly Canary Release / Publish nightly to PyPI (push) Has been cancelled
* feat(tools): surface tool failures instead of reporting them as success
A tool can finish without raising and still fail to do what it was asked.
Slack answers HTTP 200 with `{"ok": false, "error": "channel_not_found"}`;
an MCP server sets `isError`; a CrewAI AMP action returns
`API request failed: ...`. In every case the call "worked", so the error
text reached the agent as an ordinary result, the agent narrated the
problem in prose, and the run was recorded as a success.
Concretely: five failed `slackbot_send_message` calls each rendered as
"Tool Execution Completed", the task passed, and the crew passed -- with
the only evidence being a sentence in the final answer. Nothing
downstream could tell the difference, and an agent that keeps going on a
step that silently did nothing builds the rest of its work on it.
Give that outcome a type and a reaction:
- `ToolFailure` -- what a tool returns instead of an error string. The
agent still reads prose via `as_agent_message()`, so model behavior is
unchanged; the framework now knows the call failed.
- `ToolFailurePolicy` -- `ignore` (previous behavior), `warn` (default:
record + emit, keep going), `raise` (abort with
`ToolExecutionFailedError`). Resolved most-specific-first: tool, task,
agent, crew.
- `ToolFailureDetectedEvent` -- emitted before a `raise` aborts, so
subscribers always observe the failure. `ToolUsageFinishedEvent` also
carries a `failure` field so a trace UI can mark the call failed
without correlating two events.
- `tool_failures` on `TaskOutput`, `CrewOutput` and `LiteAgentOutput`,
plus `has_tool_failures`, so consumers never parse a string.
Detection is strictly declarative -- no string sniffing, so a tool that
legitimately returns text about an error is never misread as failing.
Failures come from a returned `ToolFailure`, a raised exception, MCP
`isError`, a spent `max_usage_count`, or an unknown tool.
Wired into all four tool-execution paths (the ReAct path and the three
native function-calling implementations). Sources updated to report
structurally: `MCPClient.call_tool_result()` preserves `isError` that
`call_tool()` dropped, and `CrewAIPlatformActionTool` returns a
`ToolFailure` for non-2xx and for caught exceptions.
Two latent bugs fixed along the way: `ToolUsage` assumed every agent has
a `fingerprint` (LiteAgent does not), and policy resolution now tolerates
malformed values rather than letting telemetry take down a tool call.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ETacm2dMASfpMAYUiDu5YG
* fix(tools): address review round 1 on tool-failure signalling
Five real defects from Bugbot, none of them cosmetic.
Tool-scoped policy never applied (high). `resolve_tool_failure_policy`
read `tool_failure_policy` off the object handed to it, but every
execution path passes the `CrewStructuredTool` wrapper, which never
carried the attribute -- and `BaseTool` never declared it in the first
place. A tool-scoped `raise`/`ignore` was silently ignored while the
docs and a unit test claimed otherwise; the test passed only because it
called the resolver directly with an authored tool. Declared the field on
`BaseTool`, propagated it through `to_structured_tool()` and
`CrewStructuredTool`, and made resolution fall back through
`_original_tool` so either shape works.
A failed call still printed the green "Completed" panel, then the red
one. That is the terminal version of the exact bug this PR is about.
Suppressed the success panel when the call reported failure.
A raised tool printed twice: `ToolUsageErrorEvent` already renders a red
panel, and the new failure panel repeated it. The event is still emitted
-- policy and traces need it -- but the duplicate console output is gone.
Both decisions now live in named predicates on `ConsoleFormatter` rather
than inline in the listener closure, so they are directly testable.
Unknown tools were reported on the ReAct path but silently ignored on all
three native paths, so the same miss was loud or silent depending on
executor style. Native paths now record `UNKNOWN_TOOL` too. This also
surfaced a live `NameError`: ruff had pruned `ToolFailureReason` from
`agent_utils` as unused, so the new branch would have crashed at runtime.
`LiteAgentOutput` had `tool_failures` but not `has_tool_failures`, which
the PR promised on all three output types -- an `AttributeError` for any
caller sharing one check across result types.
Testing: 16 further tests, 45 total. Two console tests were passing
vacuously because `emit()` dispatches sync handlers on a thread pool, so
the assertions raced the handler; they now assert on the predicates
directly, and the native-path test drains the bus with `flush()` and
checks the synchronously-written record. Full suite still matches
baseline exactly at 377 pre-existing failures.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ETacm2dMASfpMAYUiDu5YG
* chore: update tool specifications
* fix(tools): address review round 2 and fix CI type failure
CI caught a type error I should have: widening `agent` to accept a
`LiteAgent` (so a standalone LiteAgent resolves its own policy) left the
declared signatures behind. Widened `execute_tool_and_check_finality`,
its async twin, and `ToolCallHookContext` to `Agent | BaseAgent |
LiteAgent | None`, which is what those actually receive now.
Seven CodeRabbit findings, all verified against the code first:
`raise` was being downgraded by three enclosing handlers. With
`max_execution_time` set, `_execute_with_timeout` wrapped every exception
in `RuntimeError`, so `_check_execution_error` no longer recognized the
passthrough and sent the task through the retry loop instead of aborting.
`StepExecutor.execute` turned it into `StepResult(success=False)` and let
the plan continue. `LiteAgent.kickoff` ran it through
`handle_unknown_error` and printed "This is likely a bug - please report
it" for what is a deliberate, configured stop.
Failure records were dropped on two paths. `reset_tool_failures()` only
ran in `_prepare_task_execution`, so `Agent.kickoff()` / `kickoff_async()`
— which enter through `_prepare_kickoff` — accumulated records across
runs. And a guardrail retry calls `execute_task` again, which resets the
agent, so a tool that failed on a blocked attempt vanished from the final
output entirely: a run could report zero failures having demonstrably
failed one. Failures now accumulate across guardrail attempts.
Writing the tests for that surfaced a further miss of my own:
`Agent.kickoff()` builds its `LiteAgentOutput` in `agent/core.py` via
`AgentExecutor`, not through `LiteAgent`, so `tool_failures` was always
empty there regardless of the recording fix. Wired up, and the LiteAgent
path now reads from whichever agent the executor was handed
(`original_agent` under kickoff, `self` standalone) rather than assuming.
`last_tool_failures` returns a copy, so a caller cannot mutate the
agent's record or watch it shift mid-run.
Testing: 7 further tests, 52 total, covering the timeout wrapper, the
retry limit, kickoff reset, the kickoff output path, copy semantics and
guardrail accumulation. Full suite matches baseline exactly at 377
pre-existing failures; mypy clean on every changed file.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ETacm2dMASfpMAYUiDu5YG
* fix(tools): make crew-scoped policy real and close the last raise leak
Two findings, and the first was a documented feature that never worked.
`resolve_tool_failure_policy` consulted a crew, and the docs advertised
crew as a scope, but `Crew` had no `tool_failure_policy` field at all --
and even with one it was unreachable, because `BaseAgent` defaulted the
policy to `WARN` rather than `None`, so resolution always stopped at the
agent. Crew-level configuration was silently ignored.
Fixed by making "inherit" the default everywhere instead of baking `warn`
into one layer: `Crew` gains the field, and `BaseAgent`/`LiteAgent`
default to `None` like `Task` and `BaseTool` already did. The resolver
owns the single fallback, so the chain is genuinely
tool > task > agent > crew > warn and the effective default with nothing
configured is still `warn`. Reading `agent.tool_failure_policy` now
returns `None` (meaning "inherit") rather than `WARN`.
The other: `StepExecutor` re-raised `ToolExecutionFailedError` from its
outer handler, but the nested handler around the native-to-text tooling
fallback still caught it and returned `StepResult(success=False)`. An
agent whose LLM lacked native tool calling would therefore not abort
under `raise`. That is the third distinct place this exception was being
downgraded; it now re-raises there too.
Testing: 8 further tests, 60 total, including the full precedence chain
walked one level at a time and crew-scoped `raise`/`ignore` driven
end-to-end through `kickoff()` rather than only through the resolver --
the gap that let the original crew bug pass review. Full suite matches
baseline exactly at 377 pre-existing failures; mypy clean on every
changed file.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ETacm2dMASfpMAYUiDu5YG
* docs: trim comments and docstrings on tool-failure signalling
Prose only -- no behavior change. Cut the module docstring, the longer
class and method docstrings, the multi-line inline comments, and the
verbose Field descriptions down to what actually earns its place. Net 87
lines lighter.
Kept the "why" in every case where the reason is non-obvious (why the
event fires before a raise, why the policy reads through the tool wrapper,
why the bus needs draining in tests) and dropped the restatements of what
the code already says.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ETacm2dMASfpMAYUiDu5YG
* fix(tools): make ignore truly silent, stop caching failures, close 4 gaps
Six findings from the latest review round, all verified against the code
before touching it.
`ignore` was not silent. `ToolUsageFinishedEvent.failure` was set before
the policy ran, so traces still saw a failed call under a policy documented
as surfacing nothing. Worse, the console then showed *no* panel at all:
green was suppressed because `failure` was present, red was skipped because
`ignore` never emits `ToolFailureDetectedEvent`. New `reportable_failure()`
resolves the policy before the finished event and drops the flag under
`ignore`; wired into all four execution paths.
Failures were being cached. `CacheHandler.add` stored a `ToolFailure` like
any other result, so a transient error became permanent for the rest of the
run and every later hit re-reported a call that never re-ran. The cache now
refuses to store declared failures -- fixed at the single choke point rather
than at each of the four call sites.
A spent `max_usage_count` was invisible on the shared native path.
`BaseTool._claim_usage` returned a bare string that only the executors
recognising that exact message treated as a failure. It now returns a
`ToolFailure` with `USAGE_LIMIT`, so every path records it.
A guardrail returning a whole `TaskOutput` replaced the output without
carrying accumulated failures over, so earlier attempts vanished from
`CrewOutput.tool_failures`. New `merge_tool_failures()` combines and
deduplicates, and the retry-rebuild path uses it too.
A hook-blocked call inherited a cached failure and attributed it to a call
that never ran. Now cleared. Not reachable through the built-in cache once
failures stop being cached, so the test injects a custom cache handler that
does retain them -- verified to fail without the guard.
Also removed a `datetime` import left unused by the earlier console-test
rewrite.
Testing: 13 further tests, 73 total. Full suite matches baseline exactly at
377 pre-existing failures; the usage-limit suites that `_claim_usage`
touches pass unchanged; mypy clean on every changed file.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ETacm2dMASfpMAYUiDu5YG
* fix(tools): let raise through the parallel native path, guard all handlers
Chasing down CodeRabbit's note about callers of
execute_single_native_tool_call turned up a fifth place this exception was
being downgraded: the experimental executor's parallel branch wrapped
future.result() in a broad except and folded the abort into a fake tool
result, so the remaining parallel calls carried on. The sequential path and
crew_agent_executor's parallel branch were already fine.
Five separate handlers have swallowed this during review, so added a guard
test asserting the passthrough at every site rather than trusting the next
one gets spotted.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ETacm2dMASfpMAYUiDu5YG
* fix(tools): keep a failed tool out of the final answer, finish crew scope
Three more findings, all confirmed against the code.
A failed `result_as_answer` tool still became the task's output. The native
paths already excluded raised errors and hook blocks from short-circuiting,
but not declared failures -- so an error message silently became the answer,
which is the exact shape of bug this PR exists to prevent. Fixed on all
paths, and there were three independent override points, not one:
`ToolResult.result_as_answer` in tool_utils, the `execution_result`
finality checks in both executors, and `process_tool_results()`, which
reads `agent.tools_results` back separately. The first two fixes alone left
the behavior unchanged; only the third made the test pass.
`ToolUsage` never received a crew, so a crew-level `ignore` half-applied:
recording and `ToolFailureDetectedEvent` stayed quiet, but the flag was
still attached to `ToolUsageFinishedEvent`. It now takes and stores `crew`.
`CrewAgentExecutor.invoke`/`ainvoke` routed a deliberate stop through
`handle_unknown_error`, printing "An unknown error occurred" on verbose
runs. LiteAgent already special-cased this; both now do.
Testing: 5 further tests, 79 total, including that a *successful*
`result_as_answer` tool still short-circuits. Full suite matches baseline
exactly at 377 pre-existing failures; mypy clean on every changed file.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ETacm2dMASfpMAYUiDu5YG
* fix(tools): report malformed tool args, correlate the failure event
Two findings from the latest round.
Malformed native tool arguments returned early with a plain error dict and
never reported a failure, so `ToolFailureReason.INVALID_INPUT` was declared
but unreferenced -- a bad tool call was absent from records, events and
`raise` aborts. `parse_tool_call_args` now carries an INVALID_INPUT failure
on the error dict and both executors report it before returning.
`ToolFailureDetectedEvent` never set `agent_id`, so a trace could not tie it
to a specific agent instance. Fixing that exposed the same gap running the
other way: `ToolUsage`'s own started/finished/error events never set
`agent_id` either, so on the ReAct path the paired finished event had
nothing to correlate against. Both now set it.
Set explicitly rather than via `from_agent`, which would also overwrite
`agent_role` and lose the `_original_role` preference those events already
apply -- a behavior change that has nothing to do with correlation.
Testing: 5 further tests, 84 total, asserting the ids match between the
failure event and its paired finished event. One existing test pinned the
exact key set of the parse-error dict and was updated for the new key. Full
suite matches baseline at 377 pre-existing failures; the one apparent
addition was the known `test_trace_enable_disable` order-flake, confirmed by
re-running rather than assumed.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ETacm2dMASfpMAYUiDu5YG
* fix(tools): scope failure accumulation per execution, drop deprecated executor
Two review requests from @lorenzejay.
Accumulation no longer lives as mutable state on the shared agent. A
ContextVar collector is opened around each execution -- task, kickoff, and
each guardrail retry -- and the output reads that collector directly instead
of copying the agent's list. ContextVars are copied per asyncio task and per
thread, so concurrent executions cannot see each other's records, and
nesting is safe for retries. `last_tool_failures` prefers the active
collector and falls back to the last completed execution, so the accessor is
correct during a run too. The per-execution reset that caused the erasure is
gone.
Reproducing this took some digging and the finding is worth recording: crew
tasks *cannot* hit it, because `AgentExecutor` refuses concurrent reuse of
one instance and raises. `agent.kickoff()` has no such guard, and there the
bug reproduces exactly as reported -- two concurrent kickoffs each returned
two records. The regression test forces the overlap with a barrier so it is
deterministic rather than timing-dependent, and I verified it reports [2, 2]
against the old behavior and [1, 1] now.
Removed the tool-failure integration from `CrewAgentExecutor` entirely; that
file is back to its state on main. Note the shared ReAct helper it calls
still records failures, since that is common code rather than new behavior in
the deprecated file -- so a `raise` policy will be swallowed by that
executor's generic handler. Flagged on the PR rather than papered over.
Testing: 89 total. Two tests I wrote for this were vacuous on the first
attempt -- they passed against the simulated pre-fix code -- so each
concurrency test was checked against the old behavior before being kept.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ETacm2dMASfpMAYUiDu5YG
* fix(tools): report malformed calls everywhere, drop the unused block reason
Four findings.
`execute_single_native_tool_call` swallowed a JSON decode error into an empty
args dict and ran the tool with no input at all -- worse than not reporting
it. It now routes through `parse_tool_call_args` like the executors do, so
the StepExecutor/planning path reports INVALID_INPUT and returns instead of
executing. That also removes a duplicated inline parse.
The ReAct path returned a `ToolUsageError` message as an ordinary result
without reporting it, so a malformed call there was invisible while the
equivalent native failure was recorded. Now reported as INVALID_INPUT too.
`Agent.kickoff` opened a collector but no longer reset the agent-level list,
so `last_tool_failures` grew across kickoffs. Reset restored, matching task
execution.
`ToolFailureReason.BLOCKED_BY_HOOK` was declared and never produced. Rather
than start reporting hook blocks as failures, the member is removed: a block
is a deliberate decision by the hook author, and treating it as a failure
would make `raise` abort on an intentional veto. Added a guard test that every
remaining reason is actually produced somewhere, so a dead member cannot
reappear -- the same smell that flagged INVALID_INPUT last round.
Also switched the deprecation guard test to a single import style.
Testing: 6 further tests, 95 total, including that the tool does not run when
its args fail to parse. Full suite matches baseline at 377 pre-existing
failures; mypy clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ETacm2dMASfpMAYUiDu5YG
* fix(tools): merge failures across kickoff guardrail retries, cancel siblings
Kickoff guardrail retries discarded the blocked attempt's failures. Each
retry calls `_execute_and_build_output`, which opens a fresh collector and
builds a new output, so a run could report zero failures having demonstrably
failed one -- the same bug already fixed on the task guardrail path, which
merges. Now merged there too. Verified the test fails without the fix.
Under `raise`, one parallel native tool aborting left its siblings running:
the pool waited for them and pending ones still started. It now shuts the
pool down with `cancel_futures=True` so a not-yet-started sibling never runs.
Threads already in flight cannot be interrupted in Python, so a concurrent
tool may still complete before the abort surfaces; that is noted at the call
site rather than left implied.
Also satisfied CodeQL by materialising the enum in the guard test's loop.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ETacm2dMASfpMAYUiDu5YG
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Lorenze Jay <63378463+lorenzejay@users.noreply.github.com>
|
||
|
|
d52d0a1628 |
feat: emit FlowFailedEvent when a flow execution fails (#6718)
Some checks failed
* feat: emit FlowFailedEvent when a flow execution fails A failed flow never emitted a terminal lifecycle event, so the `flow_started` scope stayed open and consumers such as tracing closed the root span with a generic orphaned message instead of the real error. `kickoff_async` and the resume path now emit `FlowFailedEvent`, paired with `flow_started` and carrying the exception, after draining pending handlers and background memory writes. The resume path also emits the `MethodExecutionStartedEvent` it was missing for the method being resumed, so its finished or failed event pairs with its own scope instead of popping the flow's. * fix: skip FlowFailedEvent when the run never opened a scope The `kickoff_async` try block starts before `FlowStartedEvent` is emitted, so an abort in the execution-start hooks, in input handling or in state restore emitted a `flow_failed` with no opener, which pops an unrelated scope and warns about an empty scope stack. The failure event is now gated on the flow scope actually being open, either from this kickoff's `flow_started` or from a restored deferred session scope. |