Compare commits

..

1 Commits

Author SHA1 Message Date
Joao Moura
62d097a761 feat: conversation-aware memory extraction for Agent.kickoff()
When Agent.kickoff() completes, the overridden _save_to_memory now
includes the full conversation history (up to 20 turns) alongside
task metadata and the final result. This produces significantly
better memory extraction for standalone agent sessions where the
conversation thread carries important context.

The base _save_to_memory (used by Crew-based execution) remains
unchanged since each Task already encodes its own context.

Inspired by Iris's memory service which proved that thread-context-
aware extraction is dramatically more accurate than single-content
extraction.

Tests: 8 new tests covering conversation inclusion, task metadata,
fallback behavior, read-only memory, truncation, and scoped saves.
2026-04-25 17:48:32 -07:00
17679 changed files with 43758 additions and 3575277 deletions

5
.github/security.md vendored
View File

@@ -5,10 +5,7 @@ CrewAI ecosystem.
### How to Report
Please submit reports through one of the following channels:
- **crewai-vdp-ess@submit.bugcrowd.com**
- https://security.crewai.com
Please submit reports to **crewai-vdp-ess@submit.bugcrowd.com**
- **Please do not** disclose vulnerabilities via public GitHub issues, pull requests,
or social media

View File

@@ -23,10 +23,10 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6
uses: astral-sh/setup-uv@v6
with:
version: "0.11.3"
python-version: ${{ matrix.python-version }}
@@ -39,7 +39,7 @@ jobs:
echo "Cache populated successfully"
- name: Save uv caches
uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
uses: actions/cache/save@v4
with:
path: |
~/.cache/uv

View File

@@ -59,7 +59,7 @@ jobs:
# your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
steps:
- name: Checkout repository
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
uses: actions/checkout@v4
# Add any setup steps before running the `github/codeql-action/init` action.
# This includes steps like installing compilers or runtimes (`actions/setup-node`
@@ -69,7 +69,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v4.35.5
uses: github/codeql-action/init@v4
with:
languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }}
@@ -98,6 +98,6 @@ jobs:
exit 1
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v4.35.5
uses: github/codeql-action/analyze@v4
with:
category: "/language:${{matrix.language}}"

View File

@@ -13,18 +13,15 @@ on:
- "docs.json"
workflow_dispatch:
permissions:
contents: read
jobs:
check-links:
name: Check broken links
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
- uses: actions/checkout@v4
- name: Set up Node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
uses: actions/setup-node@v4
with:
node-version: "22"

View File

@@ -14,7 +14,6 @@ permissions:
jobs:
generate-specs:
if: github.event_name == 'workflow_dispatch' || github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-latest
env:
PYTHONUNBUFFERED: 1
@@ -22,19 +21,19 @@ jobs:
steps:
- name: Generate GitHub App token
id: app-token
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
uses: tibdex/github-app-token@v2
with:
app-id: ${{ secrets.CREWAI_TOOL_SPECS_APP_ID }}
private-key: ${{ secrets.CREWAI_TOOL_SPECS_PRIVATE_KEY }}
app_id: ${{ secrets.CREWAI_TOOL_SPECS_APP_ID }}
private_key: ${{ secrets.CREWAI_TOOL_SPECS_PRIVATE_KEY }}
- name: Checkout code
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
uses: actions/checkout@v4
with:
ref: ${{ github.head_ref }}
token: ${{ steps.app-token.outputs.token }}
- name: Install uv
uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6
uses: astral-sh/setup-uv@v6
with:
version: "0.11.3"
python-version: "3.12"

View File

@@ -12,8 +12,8 @@ jobs:
outputs:
code: ${{ steps.filter.outputs.code }}
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
- uses: dorny/paths-filter@d1c1ffe0248fe513906c8e24db8ea791d46f8590 # v3
- uses: actions/checkout@v4
- uses: dorny/paths-filter@v3
id: filter
with:
filters: |
@@ -26,11 +26,11 @@ jobs:
if: needs.changes.outputs.code == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
- uses: actions/checkout@v4
- name: Restore global uv cache
id: cache-restore
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
uses: actions/cache/restore@v4
with:
path: |
~/.cache/uv
@@ -41,7 +41,7 @@ jobs:
uv-main-py3.11-
- name: Install uv
uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6
uses: astral-sh/setup-uv@v6
with:
version: "0.11.3"
python-version: "3.11"
@@ -58,7 +58,7 @@ jobs:
- name: Save uv caches
if: steps.cache-restore.outputs.cache-hit != 'true'
uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
uses: actions/cache/save@v4
with:
path: |
~/.cache/uv

View File

@@ -5,10 +5,6 @@ on:
- cron: '0 6 * * *' # daily at 6am UTC
workflow_dispatch:
concurrency:
group: nightly-publish
cancel-in-progress: false
jobs:
check:
name: Check for new commits
@@ -18,15 +14,14 @@ jobs:
outputs:
has_changes: ${{ steps.check.outputs.has_changes }}
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Check for recent commits
- name: Check for commits in last 24h
id: check
run: |
# 25h window absorbs cron-vs-commit timing skew at the boundary.
RECENT=$(git log --since="25 hours ago" --oneline | head -1)
RECENT=$(git log --since="24 hours ago" --oneline | head -1)
if [ -n "$RECENT" ]; then
echo "has_changes=true" >> "$GITHUB_OUTPUT"
else
@@ -41,44 +36,36 @@ jobs:
permissions:
contents: read
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install uv
uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6
with:
version: "0.11.3"
python-version: "3.12"
enable-cache: false
uses: astral-sh/setup-uv@v4
- name: Stamp nightly versions
run: |
DATE=$(date +%Y%m%d)
# All workspace packages share the same base version and are released together.
BASE=$(python -c "
import re
print(re.search(r'__version__\s*=\s*\"(.*?)\"', open('lib/crewai/src/crewai/__init__.py').read()).group(1))
")
NIGHTLY="${BASE}.dev${DATE}"
echo "Nightly version: ${NIGHTLY}"
for init_file in \
lib/crewai/src/crewai/__init__.py \
lib/crewai-core/src/crewai_core/__init__.py \
lib/crewai-tools/src/crewai_tools/__init__.py \
lib/crewai-files/src/crewai_files/__init__.py \
lib/cli/src/crewai_cli/__init__.py; do
lib/crewai-files/src/crewai_files/__init__.py; do
CURRENT=$(python -c "
import re
text = open('$init_file').read()
print(re.search(r'__version__\s*=\s*\"(.*?)\"\s*$', text, re.MULTILINE).group(1))
")
NIGHTLY="${CURRENT}.dev${DATE}"
sed -i "s/__version__ = .*/__version__ = \"${NIGHTLY}\"/" "$init_file"
echo "Stamped $init_file -> $NIGHTLY"
echo "$init_file: $CURRENT -> $NIGHTLY"
done
# Update all cross-package dependency pins to the nightly version.
sed -i "s/\"crewai==[^\"]*\"/\"crewai==${NIGHTLY}\"/" lib/crewai-tools/pyproject.toml
sed -i "s/\"crewai-core==[^\"]*\"/\"crewai-core==${NIGHTLY}\"/" lib/crewai/pyproject.toml
sed -i "s/\"crewai-cli==[^\"]*\"/\"crewai-cli==${NIGHTLY}\"/" lib/crewai/pyproject.toml
# Update cross-package dependency pins to nightly versions
sed -i "s/\"crewai-tools==[^\"]*\"/\"crewai-tools==${NIGHTLY}\"/" lib/crewai/pyproject.toml
sed -i "s/\"crewai-files==[^\"]*\"/\"crewai-files==${NIGHTLY}\"/" lib/crewai/pyproject.toml
sed -i "s/\"crewai-core==[^\"]*\"/\"crewai-core==${NIGHTLY}\"/" lib/cli/pyproject.toml
sed -i "s/\"crewai==[^\"]*\"/\"crewai==${NIGHTLY}\"/" lib/crewai-tools/pyproject.toml
echo "Updated cross-package dependency pins to ${NIGHTLY}"
- name: Build packages
@@ -87,7 +74,7 @@ jobs:
rm dist/.gitignore
- name: Upload artifacts
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
uses: actions/upload-artifact@v4
with:
name: dist
path: dist/
@@ -98,19 +85,22 @@ jobs:
runs-on: ubuntu-latest
environment:
name: pypi
url: https://pypi.org/p/crewai
permissions:
id-token: write
contents: read
steps:
- uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6
uses: astral-sh/setup-uv@v6
with:
version: "0.11.3"
python-version: "3.12"
enable-cache: false
- name: Download artifacts
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
uses: actions/download-artifact@v4
with:
name: dist
path: dist
@@ -126,8 +116,7 @@ jobs:
continue
fi
echo "Publishing $package"
# --check-url skips files already on PyPI so manual re-runs on the same day are idempotent.
if ! uv publish --check-url https://pypi.org/simple/ "$package"; then
if ! uv publish "$package"; then
echo "Failed to publish $package"
failed=1
fi

View File

@@ -10,7 +10,7 @@ jobs:
permissions:
pull-requests: write
steps:
- uses: codelytv/pr-size-labeler@095a41fca88b8764fd9e008ad269bcdb82bb38b9 # v1
- uses: codelytv/pr-size-labeler@v1
with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
xs_label: "size/XS"

View File

@@ -12,7 +12,7 @@ jobs:
pr-title:
runs-on: ubuntu-latest
steps:
- uses: amannn/action-semantic-pull-request@e32d7e603df1aa1ba07e981f2a23455dee596825 # v5
- uses: amannn/action-semantic-pull-request@v5
continue-on-error: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -24,17 +24,17 @@ jobs:
echo "tag=" >> $GITHUB_OUTPUT
fi
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
- uses: actions/checkout@v4
with:
ref: ${{ steps.release.outputs.tag || github.ref }}
- name: Set up Python
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install uv
uses: astral-sh/setup-uv@38f3f104447c67c051c4a08e39b64a148898af3a # v4
uses: astral-sh/setup-uv@v4
- name: Build packages
run: |
@@ -42,7 +42,7 @@ jobs:
rm dist/.gitignore
- name: Upload artifacts
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
uses: actions/upload-artifact@v4
with:
name: dist
path: dist/
@@ -58,19 +58,19 @@ jobs:
id-token: write
contents: read
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
- uses: actions/checkout@v4
with:
ref: ${{ inputs.release_tag || github.ref }}
- name: Install uv
uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6
uses: astral-sh/setup-uv@v6
with:
version: "0.11.3"
python-version: "3.12"
enable-cache: false
- name: Download artifacts
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
uses: actions/download-artifact@v4
with:
name: dist
path: dist
@@ -159,7 +159,7 @@ jobs:
- name: Notify Slack
if: success()
uses: slackapi/slack-github-action@b0fa283ad8fea605de13dc3f449259339835fc52 # v2.1.0
uses: slackapi/slack-github-action@v2.1.0
with:
webhook: ${{ secrets.SLACK_WEBHOOK_URL }}
webhook-type: incoming-webhook

View File

@@ -14,7 +14,7 @@ jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # v9.1.0
- uses: actions/stale@v9
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
stale-issue-label: 'no-issue-activity'

View File

@@ -12,8 +12,8 @@ jobs:
outputs:
code: ${{ steps.filter.outputs.code }}
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
- uses: dorny/paths-filter@d1c1ffe0248fe513906c8e24db8ea791d46f8590 # v3
- uses: actions/checkout@v4
- uses: dorny/paths-filter@v3
id: filter
with:
filters: |
@@ -34,13 +34,13 @@ jobs:
group: [1, 2, 3, 4, 5, 6, 7, 8]
steps:
- name: Checkout code
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
uses: actions/checkout@v4
with:
fetch-depth: 0 # Fetch all history for proper diff
- name: Restore global uv cache
id: cache-restore
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
uses: actions/cache/restore@v4
with:
path: |
~/.cache/uv
@@ -51,7 +51,7 @@ jobs:
uv-main-py${{ matrix.python-version }}-
- name: Install uv
uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6
uses: astral-sh/setup-uv@v6
with:
version: "0.11.3"
python-version: ${{ matrix.python-version }}
@@ -61,7 +61,7 @@ jobs:
run: uv sync --all-groups --all-extras
- name: Restore test durations
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
uses: actions/cache/restore@v4
with:
path: .test_durations_py*
key: test-durations-py${{ matrix.python-version }}
@@ -108,7 +108,7 @@ jobs:
- name: Save uv caches
if: steps.cache-restore.outputs.cache-hit != 'true'
uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
uses: actions/cache/save@v4
with:
path: |
~/.cache/uv

View File

@@ -12,8 +12,8 @@ jobs:
outputs:
code: ${{ steps.filter.outputs.code }}
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
- uses: dorny/paths-filter@d1c1ffe0248fe513906c8e24db8ea791d46f8590 # v3
- uses: actions/checkout@v4
- uses: dorny/paths-filter@v3
id: filter
with:
filters: |
@@ -33,11 +33,11 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
uses: actions/checkout@v4
- name: Restore global uv cache
id: cache-restore
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
uses: actions/cache/restore@v4
with:
path: |
~/.cache/uv
@@ -48,7 +48,7 @@ jobs:
uv-main-py${{ matrix.python-version }}-
- name: Install uv
uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6
uses: astral-sh/setup-uv@v6
with:
version: "0.11.3"
python-version: ${{ matrix.python-version }}
@@ -62,7 +62,7 @@ jobs:
- name: Save uv caches
if: steps.cache-restore.outputs.cache-hit != 'true'
uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
uses: actions/cache/save@v4
with:
path: |
~/.cache/uv

View File

@@ -23,11 +23,11 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
uses: actions/checkout@v4
- name: Restore global uv cache
id: cache-restore
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
uses: actions/cache/restore@v4
with:
path: |
~/.cache/uv
@@ -38,7 +38,7 @@ jobs:
uv-main-py${{ matrix.python-version }}-
- name: Install uv
uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6
uses: astral-sh/setup-uv@v6
with:
version: "0.11.3"
python-version: ${{ matrix.python-version }}
@@ -55,14 +55,14 @@ jobs:
- name: Save durations to cache
if: always()
uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
uses: actions/cache/save@v4
with:
path: .test_durations_py*
key: test-durations-py${{ matrix.python-version }}
- name: Save uv caches
if: steps.cache-restore.outputs.cache-hit != 'true'
uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
uses: actions/cache/save@v4
with:
path: |
~/.cache/uv

View File

@@ -16,13 +16,11 @@ jobs:
name: pip-audit
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
with:
persist-credentials: false
- uses: actions/checkout@v4
- name: Restore global uv cache
id: cache-restore
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
uses: actions/cache/restore@v4
with:
path: |
~/.cache/uv
@@ -33,7 +31,7 @@ jobs:
uv-main-py3.11-
- name: Install uv
uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6
uses: astral-sh/setup-uv@v6
with:
version: "0.11.3"
python-version: "3.11"
@@ -48,45 +46,17 @@ jobs:
- name: Run pip-audit
run: |
uv run pip-audit --desc --aliases --skip-editable --format json --output pip-audit-report.json \
--ignore-vuln PYSEC-2024-277 \
--ignore-vuln PYSEC-2026-89 \
--ignore-vuln PYSEC-2026-97 \
--ignore-vuln PYSEC-2025-148 \
--ignore-vuln PYSEC-2025-183 \
--ignore-vuln PYSEC-2025-189 \
--ignore-vuln PYSEC-2025-190 \
--ignore-vuln PYSEC-2025-191 \
--ignore-vuln PYSEC-2025-192 \
--ignore-vuln PYSEC-2025-193 \
--ignore-vuln PYSEC-2025-194 \
--ignore-vuln PYSEC-2025-195 \
--ignore-vuln PYSEC-2025-196 \
--ignore-vuln PYSEC-2025-197 \
--ignore-vuln PYSEC-2025-210 \
--ignore-vuln PYSEC-2026-139 \
--ignore-vuln GHSA-rrmf-rvhw-rf47 \
--ignore-vuln PYSEC-2025-211 \
--ignore-vuln PYSEC-2025-212 \
--ignore-vuln PYSEC-2025-213 \
--ignore-vuln PYSEC-2025-214 \
--ignore-vuln PYSEC-2025-215 \
--ignore-vuln PYSEC-2025-216 \
--ignore-vuln PYSEC-2025-217 \
--ignore-vuln PYSEC-2025-218 \
--ignore-vuln GHSA-f4j7-r4q5-qw2c
--ignore-vuln CVE-2025-69872 \
--ignore-vuln CVE-2026-25645 \
--ignore-vuln CVE-2026-27448 \
--ignore-vuln CVE-2026-27459 \
--ignore-vuln PYSEC-2023-235
# Ignored CVEs:
# PYSEC-2024-277 - joblib 1.5.3: disputed; NumpyArrayWrapper only used with trusted caches
# PYSEC-2026-89 - markdown 3.10.2: DoS via malformed HTML; fix 3.8.1 — already past, advisory range is stale
# PYSEC-2026-97 - nltk 3.9.4: arbitrary file read in filestring(); no fix available
# PYSEC-2025-148 - onnx 1.21.0: path traversal in save_external_data; no fix available
# PYSEC-2025-183 - pyjwt 2.12.1: disputed weak-encryption claim; key length is application-chosen
# PYSEC-2025-189..197 - torch 2.11.0: memory-corruption/DoS in functions only reachable via untrusted models; no fix available
# PYSEC-2025-210, PYSEC-2026-139 - torch 2.11.0: profiler/deserialization issues; no fix available
# GHSA-rrmf-rvhw-rf47 - torch 2.11.0 (CVE-2025-3000, alias of PYSEC-2025-194): memory corruption in torch.jit.script, CVSS 1.9, local-only; affected <=2.12.0, no fix available. pip-audit reports it under the GHSA id so the PYSEC ignore above does not catch it.
# PYSEC-2025-211..218 - transformers 5.5.4: deserialization/code injection via malicious model checkpoints; no fix available
# GHSA-f4j7-r4q5-qw2c - chromadb 1.1.1 (CVE-2026-45829): pre-auth RCE via /api/v2/tenants/{tenant}/databases/{db}/collections when trust_remote_code=true.
# Advisory: vulnerable >=1.0.0,<=1.5.9, firstPatchedVersion=none. We only use chromadb.PersistentClient (lib/crewai/src/crewai/rag/chromadb/factory.py)
# and chromadb.utils.embedding_functions; the chromadb HTTP server is never started, so the vulnerable route is not exposed.
# CVE-2025-69872 - diskcache 5.6.3: no fix available (latest version)
# CVE-2026-25645 - requests 2.32.5: fix requires 2.33.0, blocked by crewai-tools ~=2.32.5 pin
# CVE-2026-27448 - pyopenssl 25.3.0: fix requires 26.0.0, blocked by snowflake-connector-python <26.0.0 pin
# CVE-2026-27459 - pyopenssl 25.3.0: same as above
# PYSEC-2023-235 - couchbase: fixed in 4.6.0 (already upgraded), advisory not yet updated
continue-on-error: true
- name: Display results
@@ -118,14 +88,14 @@ jobs:
- name: Upload pip-audit report
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
uses: actions/upload-artifact@v4
with:
name: pip-audit-report
path: pip-audit-report.json
- name: Save uv caches
if: steps.cache-restore.outputs.cache-hit != 'true'
uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
uses: actions/cache/save@v4
with:
path: |
~/.cache/uv

2
.gitignore vendored
View File

@@ -31,5 +31,3 @@ chromadb-*.lock
blogs/*
secrets/*
UNKNOWN.egg-info/
demos/*
.crewai/*

View File

@@ -19,7 +19,7 @@ repos:
language: system
pass_filenames: true
types: [python]
exclude: ^(lib/crewai/src/crewai/cli/templates/|lib/cli/src/crewai_cli/templates/|lib/cli/tests/|lib/crewai/tests/|lib/crewai-tools/tests/|lib/crewai-files/tests/|lib/devtools/tests/)
exclude: ^(lib/crewai/src/crewai/cli/templates/|lib/crewai/tests/|lib/crewai-tools/tests/|lib/crewai-files/tests/)
- repo: https://github.com/astral-sh/uv-pre-commit
rev: 0.11.3
hooks:
@@ -28,35 +28,7 @@ repos:
hooks:
- id: pip-audit
name: pip-audit
# Keep this ignore list in sync with .github/workflows/vulnerability-scan.yml.
entry: >-
bash -c 'source .venv/bin/activate && uv run pip-audit --skip-editable
--ignore-vuln PYSEC-2024-277
--ignore-vuln PYSEC-2026-89
--ignore-vuln PYSEC-2026-97
--ignore-vuln PYSEC-2025-148
--ignore-vuln PYSEC-2025-183
--ignore-vuln PYSEC-2025-189
--ignore-vuln PYSEC-2025-190
--ignore-vuln PYSEC-2025-191
--ignore-vuln PYSEC-2025-192
--ignore-vuln PYSEC-2025-193
--ignore-vuln PYSEC-2025-194
--ignore-vuln PYSEC-2025-195
--ignore-vuln PYSEC-2025-196
--ignore-vuln PYSEC-2025-197
--ignore-vuln PYSEC-2025-210
--ignore-vuln PYSEC-2026-139
--ignore-vuln GHSA-rrmf-rvhw-rf47
--ignore-vuln PYSEC-2025-211
--ignore-vuln PYSEC-2025-212
--ignore-vuln PYSEC-2025-213
--ignore-vuln PYSEC-2025-214
--ignore-vuln PYSEC-2025-215
--ignore-vuln PYSEC-2025-216
--ignore-vuln PYSEC-2025-217
--ignore-vuln PYSEC-2025-218
--ignore-vuln GHSA-f4j7-r4q5-qw2c' --
entry: bash -c 'source .venv/bin/activate && uv run pip-audit --skip-editable --ignore-vuln CVE-2025-69872 --ignore-vuln CVE-2026-25645 --ignore-vuln CVE-2026-27448 --ignore-vuln CVE-2026-27459 --ignore-vuln PYSEC-2023-235' --
language: system
pass_filenames: false
stages: [pre-push, manual]

142
AGENTS.md
View File

@@ -1,142 +0,0 @@
# Docs contributor guide
The `docs/` directory is published at [docs.crewai.com](https://docs.crewai.com)
by [Mintlify](https://www.mintlify.com/). Mintlify watches `docs/docs.json`
and the MDX files referenced from it.
## TL;DR for editing docs
- Edit MDX under `docs/edge/<lang>/...` (e.g. `docs/edge/en/concepts/agents.mdx`).
- Your change ships under the **Edge** version selector the moment it merges
to `main`. Edge follows `main` and is the channel for unreleased work.
- On release cut, the current Edge state is frozen into `docs/v<X.Y.Z>/` and
that snapshot becomes the new default version in the selector (tag:
`Latest`). Canonical URLs (`/<lang>/...`) auto-redirect to the new default.
- Never modify files under `docs/v*/`. Those are frozen release snapshots
and the `docs-snapshots` CI guard rejects writes. The only exception is a
release-cut PR (auto-generated by `devtools release` or the manual
`scripts/docs/freeze_current_edge.py` wrapper), which uses a
`[docs-freeze]` title prefix to opt out.
- Never delete or rename files under `docs/images/`. Images are append-only.
See [Images](#images) below.
## The version model
The site has one rolling channel (Edge) plus one frozen snapshot per
release.
```
docs/
edge/ <-- Edge sources (you edit here)
en/...
pt-BR/ ko/ ar/
enterprise-api.*.yaml
v1.14.7/ <-- frozen snapshot of v1.14.7
en/...
pt-BR/ ko/ ar/
enterprise-api.*.yaml
v1.14.6/...
...
images/ <-- shared, append-only
docs.json <-- Mintlify config: navigation + redirects
```
`docs/docs.json` lists one navigation block per version per language. Edge
points at `docs/edge/<lang>/...`; every other version points at its own
`docs/v<X.Y.Z>/<lang>/...` subtree. Mintlify scopes both the sidebar and the
in-site search to whichever version the reader selects, so picking
`v1.10.0` genuinely shows the v1.10.0 docs (and only those).
### URLs and canonical redirects
Each Mintlify version corresponds to its own URL prefix:
- Edge: `/edge/<lang>/<page>` (e.g. `/edge/en/concepts/agents`)
- Frozen: `/v<X.Y.Z>/<lang>/<page>` (e.g. `/v1.14.7/en/concepts/agents`)
External links to the old, unversioned `/<lang>/<page>` URLs would 404 under
this layout. To keep them working, `docs.json` ships wildcard redirects:
```jsonc
{ "source": "/en/:slug*", "destination": "/v1.14.7/en/:slug*", "permanent": false }
```
The release-cut step rewrites the destination on every release so canonical
`/<lang>/...` URLs always resolve to the latest stable docs.
## Lifecycle
1. **During development.** You add or edit pages under
`docs/edge/<lang>/...` in normal PRs. They land in Edge as soon as the PR
merges. Both `/edge/<lang>/<page>` and the version selector's `Edge` entry
reflect the change immediately.
2. **Release cut.** The release engineer runs `devtools release X.Y.Z`. As
part of that flow the CLI opens a `[docs-freeze]` PR that copies Edge into
`docs/v<X.Y.Z>/`, rewrites internal OpenAPI references, updates
`docs/docs.json` to make `v<X.Y.Z>` the new default + `Latest`, and rewires
the canonical-URL redirects to the new default. The PR must merge before
the tag and PyPI publish run.
3. **After release.** Edge keeps rolling. Patch fixes to the just-released
docs go into Edge and ship with the next release. We do not back-edit
frozen snapshots.
See [`RELEASING.md`](RELEASING.md) for the full release runbook.
## Images
Snapshots share a single `docs/images/` directory. If an image is deleted
or renamed, every frozen snapshot that referenced it breaks. So the rule
is:
- Adding new images is always fine.
- Deleting or renaming an existing image fails CI unless the PR is a
`[docs-freeze]` release-cut PR.
- If an asset is wrong, add a new file with a new name and reference the
new name in the Edge MDX (`docs/edge/<lang>/...`). Leave the old file
alone.
## Local preview
Install the Mintlify CLI and run from `docs/`:
```bash
npm i -g mintlify
mintlify dev
```
Use the version selector at the top of the rendered page to switch between
Edge and frozen versions.
To check links across every version:
```bash
mintlify broken-links
```
CI runs the broken-links check on every PR that touches `docs/**` via
[`.github/workflows/docs-broken-links.yml`](.github/workflows/docs-broken-links.yml).
## Scripts
- `scripts/docs/freeze_historical_versions.py` — one-time migration that
reconstructed `docs/v1.10.0/` through `docs/v1.14.7/` from git tags. You
should not need to run this again.
- `scripts/docs/prefix_version_paths.py` — one-time migration that switched
`docs/docs.json` to directory-based versioning, inserted Edge, and added
the canonical-URL redirects. You should not need to run this again.
- `scripts/docs/freeze_current_edge.py` — thin CLI wrapper around
`crewai_devtools.docs_versioning.freeze`. `devtools release` calls the
same module during its docs PR step; this script is the manual escape
hatch (e.g. retroactively freezing a forgotten release).
## CI guards
- [`.github/workflows/docs-snapshots.yml`](.github/workflows/docs-snapshots.yml)
enforces the two rules above (frozen snapshots immutable, images
append-only). Both checks accept the `[docs-freeze]` PR-title escape
hatch.
- [`.github/workflows/docs-broken-links.yml`](.github/workflows/docs-broken-links.yml)
runs `mintlify broken-links` against the whole site, so adding a new
page or moving a snapshot file that breaks a link will fail CI.

View File

@@ -601,19 +601,6 @@ CrewAI is open-source and we welcome contributions. If you're looking to contrib
- Send a pull request.
- We appreciate your input!
### Contributing to the docs
The site at [docs.crewai.com](https://docs.crewai.com) is published from
`docs/` by [Mintlify](https://www.mintlify.com/). The docs use directory-based
versioning: edits to `docs/edge/<lang>/...` (e.g.
`docs/edge/en/concepts/agents.mdx`) land under the **Edge** version selector
immediately and are frozen into a new versioned snapshot under
`docs/v<X.Y.Z>/` at the next release cut. Frozen snapshots are immutable — CI
rejects PRs that modify them without a `[docs-freeze]` title prefix. The
release CLI (`devtools release`) handles the freeze automatically; see
[`AGENTS.md`](AGENTS.md) for the full contributor guide and
[`RELEASING.md`](RELEASING.md) for the release-cut runbook.
### Installing Dependencies
```bash

View File

@@ -5,105 +5,12 @@ from collections.abc import Generator
import gzip
import os
from pathlib import Path
import re
import tempfile
from typing import Any
from dotenv import load_dotenv
import pytest
def _patch_vcrpy_aiohttp_compat() -> None:
"""Keep vcrpy's aiohttp stub working under aiohttp 3.14.0.
aiohttp 3.14.0 (pulled in to fix GHSA-jg22-mg44-37j8 and GHSA-hg6j-4rv6-33pg):
* removed ``aiohttp.streams.AsyncStreamReaderMixin`` (folded into ``StreamReader``),
which vcrpy's ``MockStream`` still subclasses -- vcr's patch machinery then raises
``AttributeError`` at collection time; and
* added a required ``stream_writer`` keyword-only arg to ``ClientResponse.__init__``,
which vcrpy's ``MockClientResponse`` does not pass -- raising ``TypeError`` at
cassette playback.
Restore the mixin, then rebuild ``MockClientResponse``'s ``super().__init__`` call from
the live ``ClientResponse`` signature (defaulting every required keyword-only arg to
``None``, mirroring vcrpy's original call) so it also survives future aiohttp additions.
"""
import asyncio
import inspect
from aiohttp import streams
from aiohttp.client_reqrep import ClientResponse
if not hasattr(streams, "AsyncStreamReaderMixin"):
class AsyncStreamReaderMixin:
__slots__ = ()
def __aiter__(self) -> streams.AsyncStreamIterator[bytes]:
return streams.AsyncStreamIterator(self.readline) # type: ignore[attr-defined]
def iter_chunked(self, n: int) -> streams.AsyncStreamIterator[bytes]:
return streams.AsyncStreamIterator(lambda: self.read(n)) # type: ignore[attr-defined]
def iter_any(self) -> streams.AsyncStreamIterator[bytes]:
return streams.AsyncStreamIterator(self.readany) # type: ignore[attr-defined]
def iter_chunks(self) -> streams.ChunkTupleAsyncStreamIterator:
return streams.ChunkTupleAsyncStreamIterator(self) # type: ignore[arg-type]
streams.AsyncStreamReaderMixin = AsyncStreamReaderMixin # type: ignore[attr-defined]
# Importing the stub builds MockStream/MockClientResponse, so it must run after the
# mixin is restored above.
import vcr.stubs.aiohttp_stubs as aiohttp_stubs # type: ignore[import-untyped]
if getattr(aiohttp_stubs.MockClientResponse, "_crewai_aiohttp_patched", False):
return
keyword_only = [
name
for name, param in inspect.signature(ClientResponse.__init__).parameters.items()
if param.kind is inspect.Parameter.KEYWORD_ONLY
]
class _NullStreamWriter:
# aiohttp 3.14.0 reads stream_writer.output_size in the "request already
# sent" branch (writer is None), so None is not enough -- supply a stub.
output_size = 0
fallback_loop: list[asyncio.AbstractEventLoop] = []
def _resolve_loop() -> asyncio.AbstractEventLoop:
# MockClientResponse is normally built inside aiohttp's running loop, so
# prefer that. In a sync context there is no running loop; avoid
# asyncio.get_event_loop(), which on 3.12+ emits a DeprecationWarning
# (and can RuntimeError) when no current loop is set. Use one cached
# loop instead -- the mock only stores it and calls loop.get_debug().
try:
return asyncio.get_running_loop()
except RuntimeError:
if not fallback_loop:
fallback_loop.append(asyncio.new_event_loop())
return fallback_loop[0]
def _mock_client_response_init(
self: Any, method: str, url: Any, request_info: Any = None
) -> None:
kwargs: dict[str, Any] = dict.fromkeys(keyword_only)
kwargs["request_info"] = request_info
if "loop" in kwargs:
kwargs["loop"] = _resolve_loop()
if "stream_writer" in kwargs:
kwargs["stream_writer"] = _NullStreamWriter()
ClientResponse.__init__(self, method, url, **kwargs)
aiohttp_stubs.MockClientResponse.__init__ = _mock_client_response_init
aiohttp_stubs.MockClientResponse._crewai_aiohttp_patched = True
_patch_vcrpy_aiohttp_compat()
from vcr.request import Request # type: ignore[import-untyped] # noqa: E402
from vcr.request import Request # type: ignore[import-untyped]
try:
@@ -113,42 +20,21 @@ except ModuleNotFoundError:
env_test_path = Path(__file__).parent / ".env.test"
load_dotenv(env_test_path, override=False)
load_dotenv(override=False)
BEDROCK_HOST_PLACEHOLDER = "bedrock-runtime.vcr.amazonaws.com"
_BEDROCK_HOST_RE = re.compile(r"^bedrock-runtime\.[a-z0-9-]+\.amazonaws\.com$")
load_dotenv(env_test_path, override=True)
load_dotenv(override=True)
def _normalize_bedrock_host(host: str) -> str:
if _BEDROCK_HOST_RE.match(host):
return BEDROCK_HOST_PLACEHOLDER
return host
def bedrock_host_matcher(r1: Request, r2: Request) -> bool: # type: ignore[no-any-unimported]
"""Match Bedrock requests across AWS regions (CI uses us-east-1, local may use us-west-2)."""
return _normalize_bedrock_host(r1.host or "") == _normalize_bedrock_host(
r2.host or ""
)
def _patched_make_vcr_request(
httpx_request: Any, real_request_body: Any = None, **kwargs: Any
) -> Any:
def _patched_make_vcr_request(httpx_request: Any, **kwargs: Any) -> Any:
"""Patched version of VCR's _make_vcr_request that handles binary content.
The original implementation fails on binary request bodies (like file uploads)
because it assumes all content can be decoded as UTF-8.
"""
raw_body = real_request_body if real_request_body is not None else httpx_request.read()
body: Any = raw_body
if isinstance(raw_body, bytes):
try:
body = raw_body.decode("utf-8")
except UnicodeDecodeError:
body = base64.b64encode(raw_body).decode("ascii")
raw_body = httpx_request.read()
try:
body = raw_body.decode("utf-8")
except UnicodeDecodeError:
body = base64.b64encode(raw_body).decode("ascii")
uri = str(httpx_request.url)
headers = dict(httpx_request.headers)
return Request(httpx_request.method, uri, body, headers)
@@ -168,13 +54,12 @@ _original_from_serialized_response = getattr(
)
if _original_from_serialized_response is not None:
_from_serialized: Any = _original_from_serialized_response
def _patched_from_serialized_response(
request: Any, serialized_response: Any, history: Any = None
) -> Any:
"""Patched version that ensures response._content is properly set."""
response = _from_serialized(request, serialized_response, history)
response = _original_from_serialized_response(request, serialized_response, history)
# Explicitly set _content to avoid ResponseNotRead errors
# The content was passed to the constructor but the mocked read() prevents
# proper initialization of the internal state
@@ -302,7 +187,6 @@ HEADERS_TO_FILTER = {
"anthropic-ratelimit-tokens-remaining": "ANTHROPIC-RATELIMIT-TOKENS-REMAINING-XXX",
"anthropic-ratelimit-tokens-reset": "ANTHROPIC-RATELIMIT-TOKENS-RESET-XXX",
"x-amz-date": "X-AMZ-DATE-XXX",
"x-amz-security-token": "X-AMZ-SECURITY-TOKEN-XXX",
"amz-sdk-invocation-id": "AMZ-SDK-INVOCATION-ID-XXX",
"accept-encoding": "ACCEPT-ENCODING-XXX",
"x-amzn-requestid": "X-AMZN-REQUESTID-XXX",
@@ -327,10 +211,6 @@ def _filter_request_headers(request: Request) -> Request: # type: ignore[no-any
placeholder_host = "fake-azure-endpoint.openai.azure.com"
request.uri = request.uri.replace(original_host, placeholder_host)
# Normalize Bedrock regional endpoints so cassettes work in any AWS region.
if request.host and _BEDROCK_HOST_RE.match(request.host):
request.uri = request.uri.replace(request.host, BEDROCK_HOST_PLACEHOLDER)
return request
@@ -348,11 +228,6 @@ def _filter_response_headers(response: dict[str, Any]) -> dict[str, Any] | None:
if body == "" or body == b"" or content_length == ["0"]:
return None
status_code = response.get("status", {}).get("code")
if isinstance(status_code, int) and status_code >= 400:
# Avoid persisting auth/model errors when re-recording without valid AWS creds.
return None
for encoding_header in ["Content-Encoding", "content-encoding"]:
if encoding_header in headers:
encoding = headers.pop(encoding_header)
@@ -380,8 +255,7 @@ def vcr_cassette_dir(request: Any) -> str:
for parent in test_file.parents:
if (
parent.name
in ("crewai", "crewai-tools", "crewai-files", "cli", "crewai-core")
parent.name in ("crewai", "crewai-tools", "crewai-files")
and parent.parent.name == "lib"
):
package_root = parent
@@ -403,11 +277,6 @@ def vcr_cassette_dir(request: Any) -> str:
return str(cassette_dir)
def pytest_recording_configure(vcr: Any, config: Any) -> None:
"""Register custom VCR matchers for each test cassette session."""
vcr.register_matcher("bedrock_host", bedrock_host_matcher)
@pytest.fixture(scope="module")
def vcr_config(vcr_cassette_dir: str) -> dict[str, Any]:
"""Configure VCR with organized cassette storage."""

View File

@@ -0,0 +1,6 @@
---
title: "GET /{kickoff_id}/status"
description: "الحصول على حالة التنفيذ"
openapi: "/enterprise-api.en.yaml GET /{kickoff_id}/status"
mode: "wide"
---

Some files were not shown because too many files have changed in this diff Show More