Previously, _initialize_batch forced use_ephemeral=True for all
first-time users, bypassing _check_authenticated() entirely. This
meant logged-in users in a new project directory were routed to the
ephemeral endpoint instead of their account's tracing endpoint.
Now _check_authenticated() runs for all users including first-time.
Authenticated first-time users get non-ephemeral tracing (traces
linked to their account); only unauthenticated first-time users
fall back to ephemeral. The deferred backend init in
FirstTimeTraceHandler also reads is_current_batch_ephemeral instead
of hardcoding use_ephemeral=True.
Transient failures (None response, 5xx, network errors) during
_initialize_backend_batch now retry up to 2 times with a 1s backoff.
Non-transient 4xx errors (422 validation, 401 auth) are not retried
since the same payload would fail again. If all retries are exhausted,
trace_batch_id is cleared per the existing safety net.
This runs post-execution when the user has already answered "y" to
view traces, so the ~2s worst-case delay is acceptable.
In first_time_trace_handler._initialize_backend_and_send_events,
backend_initialized was set to True unconditionally after calling
_initialize_backend_batch, regardless of whether the server-side
batch was actually created. This caused _send_events_to_backend and
finalize_batch to run against a non-existent batch.
Now check trace_batch_id after _initialize_backend_batch returns;
if None (batch creation failed), call _gracefully_fail and return
early, skipping event send and finalization.
When _initialize_backend_batch fails (None response, non-2xx status,
or exception), trace_batch_id was left populated with a client-generated
UUID that was never registered server-side. Subsequent calls to
_send_events_to_backend would see the stale ID and POST events to
/ephemeral/batches/{id}/events, resulting in a 404 from the server.
Nullify trace_batch_id on all three failure paths so downstream methods
skip event sending instead of hitting a non-existent batch.
Fix: Add a remember_many() method to the MemoryScope class that delegates to self._memory.remember_many(...) with the scoped path, following the exact same pattern as the existing remember() method.
Problem: When you pass memory=memory.scope("/agent/...") to an Agent, CrewAI's internal code calls remember_many() after every task to persist results. But MemoryScope never implemented remember_many() — only the parent Memory class has it.
Symptom: [ERROR]: Failed to save kickoff result to memory: 'MemoryScope' object has no attribute 'remember_many' — memories are silently never saved after agent tasks.
When a method has both @listen and @human_feedback(emit=[...]),
the FlowMeta metaclass registered it as a router but only used
get_possible_return_constants() to detect paths. This fails for
@human_feedback methods since the paths come from the decorator's
emit param, not from return statements in the source code.
Now checks __router_paths__ first (set by @human_feedback), then
falls back to source code analysis for plain @router methods.
This was causing missing edges in the flow serializer output —
e.g. the whitepaper generator's review_infographic -> handle_cancelled,
send_slack_notification, classify_feedback edges were all missing.
Adds test: @listen + @human_feedback(emit=[...]) generates correct
router edges in serialized output.
Co-authored-by: Joao Moura <joao@crewai.com>
The litellm optional dependency had a wide upper bound (<3) that allowed
any future litellm release to be installed automatically. This means
breaking changes in new litellm versions could affect customers immediately.
Pins the upper bound to <=1.82.6 (current latest known-good version).
When newer litellm versions are tested and validated, bump this bound
explicitly.
* feat: add native OpenAI-compatible providers (OpenRouter, DeepSeek, Ollama, vLLM, Cerebras, Dashscope)
Add a data-driven OpenAI-compatible provider system that enables
native support for multiple third-party APIs that implement the
OpenAI API specification.
New providers:
- OpenRouter: 500+ models via openrouter.ai
- DeepSeek: deepseek-chat, deepseek-coder, deepseek-reasoner
- Ollama: local models (llama3, mistral, codellama, etc.)
- hosted_vllm: self-hosted vLLM servers
- Cerebras: ultra-fast inference
- Dashscope: Alibaba Qwen models (qwen-turbo, qwen-max, etc.)
Architecture:
- Single OpenAICompatibleCompletion class extends OpenAICompletion
- ProviderConfig dataclass stores per-provider settings
- Registry dict makes adding new providers a single config entry
- Handles provider-specific quirks (OpenRouter headers, Ollama
base URL normalization, optional API keys)
Usage:
LLM(model="deepseek/deepseek-chat")
LLM(model="ollama/llama3")
LLM(model="openrouter/anthropic/claude-3-opus")
LLM(model="llama3", provider="ollama")
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: add is_litellm=True to tests that test litellm-specific methods
Tests for _get_custom_llm_provider and _validate_call_params used
openrouter/ model prefix which now routes to native provider.
Added is_litellm=True to force litellm path since these test
litellm-specific internals.
---------
Co-authored-by: Joao Moura <joao@crewai.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
- Token counting: Make TokenCalcHandler standalone class that conditionally
inherits from litellm.CustomLogger when litellm is available, works as
plain object when not installed
- Callbacks: Guard set_callbacks() and set_env_callbacks() behind
LITELLM_AVAILABLE checks - these only affect the litellm fallback path,
native providers emit events via base_llm.py
- Feature detection: Guard supports_function_calling(), supports_stop_words(),
and _validate_call_params() behind LITELLM_AVAILABLE checks with sensible
defaults (True for function calling/stop words since all modern models
support them)
- Error types: Replace litellm.exceptions.ContextWindowExceededError catches
with pattern-based detection using LLMContextLengthExceededError._is_context_limit_error()
This decouples crewAI's internal infrastructure from litellm, allowing the
native providers (OpenAI, Anthropic, Azure, Bedrock, Gemini) to work without
litellm installed. The litellm fallback for niche providers still works when
litellm IS installed.
Co-authored-by: Joao Moura <joao@crewai.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Add `crewai logout` command that clears auth tokens and user settings.
Supports `--reset` flag to also restore all CLI settings to defaults.
Add missing type annotations to all CLI command functions, DeployCommand
and TriggersCommand __init__ methods, and create_flow to resolve all
mypy errors. Remove unused assignments of void telemetry return values.
introduce the agent skills standard for packaging reusable instructions that agents can discover and activate at runtime.
- skills defined via SKILL.md with yaml frontmatter and markdown body
- three-level progressive disclosure: metadata, instructions, resources
- filesystem discovery with directory name validation
- skill lifecycle events (discovery, loaded, activated, failed)
- crew-level skills resolved once and shared across agents
- skill context injected into both task execution and standalone kickoff
* feat: automatic root_scope for hierarchical memory isolation
Crews and flows now automatically scope their memories hierarchically.
The encoding flow's LLM-inferred scope becomes a sub-scope under the
structural root, preventing memory pollution across crews/agents.
Scope hierarchy:
/crew/{crew_name}/agent/{agent_role}/{llm-inferred}
/flow/{flow_name}/{llm-inferred}
Changes:
- Memory class: new root_scope field, passed through remember/remember_many
- EncodingFlow: prepends root_scope to resolved scope in both fast path
(Group A) and LLM path (Group C/D)
- Crew: auto-sets root_scope=/crew/{sanitized_name} on memory creation
- Agent executor: extends crew root with /agent/{sanitized_role} per save
- Flow: auto-sets root_scope=/flow/{sanitized_name} on memory creation
- New utils: sanitize_scope_name, normalize_scope_path, join_scope_paths
Backward compatible — no root_scope means no prefix (existing behavior).
Old memories at '/' remain accessible.
51 new tests, all existing tests pass.
* ci: retrigger tests
* fix: don't auto-set root_scope on user-provided Memory instances
When users pass their own Memory instance to a Crew (memory=mem),
respect their configuration — don't auto-set root_scope.
Auto-scoping only applies when memory=True (Crew creates Memory).
Fixes: test_crew_memory_with_google_vertex_embedder which passes
Memory(embedder=...) to Crew and expects remember(scope='/test')
to produce scope '/test', not '/crew/crew/test'.
* fix: address 6 review comments — true scope isolation for reads, writes, and consolidation
1. Constrain similarity search to root_scope boundary (no cross-crew consolidation)
2. Remove unused self._root_scope from EncodingFlow
3. Apply root_scope to recall/list/info/reset (true read isolation)
4. Only extend agent root_scope when crew has one (backward compat)
5. Fix docstring example for sanitize_scope_name
6. Verify code comments match behavior
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Joao Moura <joao@crewai.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* feat: add flow_structure() serializer for Flow class introspection
Adds a new flow_serializer module that introspects a Flow class and returns
a JSON-serializable dictionary describing its complete graph structure.
This enables Studio UI to render visual flow graphs (analogous to how
crew_structure() works for Crews).
The serializer extracts:
- Method metadata (type, triggers, conditions, router paths)
- Edge graph (listen and route edges between methods)
- State schema (from Pydantic model if typed)
- Human feedback and Crew reference detection
- Flow input detection
Includes 23 comprehensive tests covering linear flows, routers,
AND/OR conditions, human feedback, crew detection, state schemas,
edge cases, and JSON serialization.
* fix: lint — ruff check + format compliance for flow_serializer
* fix: address review — PydanticUndefined bug, FlowCondition tuple handling, dead code cleanup, inheritance tests
1. Fix PydanticUndefined default handling (real bug) — required fields
were serialized with sentinel value instead of null
2. Fix FlowCondition tuple type in _extract_all_methods_from_condition —
tuple conditions now properly extracted
3. Remove dead get_flow_inputs branch that did nothing
4. Document _detect_crew_reference as best-effort heuristic
5. Add 2 inheritance tests (parent→child method propagation)
---------
Co-authored-by: Joao Moura <joao@crewai.com>
When a flow with @human_feedback(llm=create_llm()) pauses for HITL and
later resumes:
1. The LLM object was being serialized to just a model string via
_serialize_llm_for_context() (e.g. 'gemini/gemini-3.1-flash-lite-preview')
2. On resume, resume_async() was creating LLM(model=string) with NO
credentials, project, location, safety_settings, or client_params
3. OpenAI worked by accident (OPENAI_API_KEY from env), but Gemini with
service accounts broke
This fix:
- Stashes the live LLM object on the wrapper as _hf_llm attribute
- On resume, looks up the method and retrieves the live LLM if available
- Falls back to the serialized string for backward compatibility
- Preserves _hf_llm through FlowMethod wrapper decorators
Co-authored-by: Joao Moura <joao@crewai.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* fix: add base_dir path containment to FileWriterTool
os.path.join does not prevent traversal — joining "./" with "../../../etc/cron.d/pwned"
resolves cleanly outside any intended scope. The tool also called os.makedirs on
the unvalidated path, meaning it would create arbitrary directory structures.
Adds a base_dir parameter that uses os.path.realpath() to resolve the final path
(including symlinks) before checking containment. Any filename or directory argument
that resolves outside base_dir is rejected before any filesystem operation occurs.
When base_dir is not set the tool behaves as before — only use that in fully
sandboxed environments.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: make directory relative to base_dir for better UX
When base_dir is set, the directory arg is now treated as a subdirectory
of base_dir rather than an absolute path. This means the LLM only needs
to specify a filename (and optionally a relative subdirectory) — it does
not need to repeat the base_dir path.
FileWriterTool(base_dir="./output")
→ filename="report.txt" writes to ./output/report.txt
→ filename="f.txt", directory="sub" writes to ./output/sub/f.txt
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: remove directory field from LLM schema when base_dir is set
When a developer sets base_dir, they control where files are written.
The LLM should only supply filename and content — not a directory path.
Adds ScopedFileWriterToolInput (no directory field) which is used when
base_dir is provided at construction, following the same pattern as
FileReadTool/ScrapeWebsiteTool.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: prevent path traversal in FileWriterTool without interface changes
Adds containment check inside _run() using os.path.realpath() to ensure
the resolved file path stays within the resolved directory. Blocks ../
sequences, absolute filenames, and symlink escapes transparently —
no schema or interface changes required.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: use Path.is_relative_to() for path containment check
Replaces startswith(real_directory + os.sep) with Path.is_relative_to(),
which does a proper path-component comparison. This avoids the edge case
where real_directory == "/" produces a "//" prefix, and is safe on
case-insensitive filesystems. Also explicitly rejects the case where
the filepath resolves to the directory itself (not a valid file target).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test: fix portability issues in path traversal tests
- test_blocks_traversal_in_filename: use a sibling temp dir instead of
asserting against a potentially pre-existing ../outside.txt
- test_blocks_absolute_path_in_filename: use a temp-dir-derived absolute
path instead of hardcoding /etc/passwd
- test_blocks_symlink_escape: symlink to a temp "outside" dir instead of
/etc, assert target file was not created
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Greyson LaLonde <greyson.r.lalonde@gmail.com>
* Fix lock_store crash when redis package is not installed
`REDIS_URL` being set was enough to trigger a Redis lock, which would
raise `ImportError` if the `redis` package wasn't available. Added
`_redis_available()` to guard on both the env var and the import.
* Simplify tests
* Simplify tests #2