Project: Offline Research-Brief Flow

Project objective

This project practices the most important engineering boundaries for Crews and Flows without installing CrewAI, calling a model, accessing the network, or using an API key:

  • researcher, writer, and reviewer each perform only one Task;
  • Task input and output are verifiable JSON structures;
  • the Flow records state, contiguous events, an attempt budget, and explicit terminal states;
  • a missing citation receives bounded revision, then human review when the budget is exhausted;
  • publication accepts only a reviewed artifact matching the frozen source catalog; before writing, it recomputes the deterministic reviewer and checks same-content receipt reuse and overwrite refusal.

It is not a replacement for the CrewAI SDK, nor can it prove real-model quality. It first makes deterministic business controls runnable and testable, then lets you replace boundaries with real Agents, Tasks, and Crews one at a time.

Files

FilePurpose
examples/offline_research_flow.pyStrict data validation, three Task stubs, sequential Crew execution, Flow routing, and idempotent publication
examples/sources.jsonTwo offline sources with schema version, stable source IDs, topics, and claims
examples/test_offline_research_flow.py39 unittest cases for catalog, Task, Flow, pre-publication source/reviewer recheck, and CLI behavior

Read sources.json first, then follow load_catalog → run_researcher_task → run_writer_task → run_reviewer_task → run_crew → run_flow. Finish with publish_report to see why side effects need separate preconditions, a same-source-version recheck, and recovery rules. publish_report accepts a trusted catalog rather than state alone: it compares catalog_fingerprint, recomputes the deterministic reviewer, and only then writes a local file.

The operation_id in run_flow is determined from the frozen topic and catalog fingerprint to express the same business intent; it is not a unique Flow UUID per execution. Layer B explicitly separates those identifiers.

Prepare the environment

This project uses only the Python standard library. The following begins at the project root containing docs-CN/, docs-EN/, and .website/. If you want a virtual environment, create it outside the vault, then enter the course using a project-relative path:

$practice = Join-Path $HOME "Projects\crewai-offline-practice"  # Choose an absolute practice directory outside the vault.
New-Item -ItemType Directory -Path $practice -Force | Out-Null  # Create it if needed and suppress unrelated output.
Push-Location -LiteralPath $practice  # Move temporarily so environment files stay out of the course repository.
py -3 -m venv .venv  # Create an isolated environment with the locally default Python 3.
.\.venv\Scripts\Activate.ps1  # Activate it so later python commands use this interpreter.
python --version  # Confirm the Python version actually activated.
Pop-Location  # Return to the project root.
Push-Location -LiteralPath 'docs-EN\crewai'  # Enter the English course directory for the relative paths below.

Note

The course repository must not store .venv. If PowerShell execution policy blocks activation, run later commands through the absolute path to the external environment’s python.exe.

The project has no third-party dependencies, so do not run pip install or weaken machine-wide security settings merely to run it.

First run: normal path

Run all following commands from the crewai course directory:

python -B .\examples\offline_research_flow.py --topic "Agent reliability"  # Run normal input and prevent .pyc creation.

-B prevents .pyc writes. Standard output is JSON. Verify:

  • stage is ready_to_publish;
  • attempt is 1;
  • every claim in result.research.claims has source_ids;
  • result.draft.markdown contains [source-1] and [source-2];
  • event sequence increments contiguously from 1.

The CLI return code 0 means it reached ready_to_publish or published; it does not independently verify real-world facts.

Observe revision and human takeover

First citation missing, second attempt fixed

python -B .\examples\offline_research_flow.py --topic "Agent reliability" --force-revision  # Inject a missing citation first and observe bounded revision routing.

The first writer intentionally omits one citation. The reviewer finds it, the Flow emits routed:revise, then the second attempt adds a revision record and reaches ready_to_publish. Confirm attempt == 2: routing, not an unbounded loop, controls retries.

Both attempts fail, then human handling

python -B .\examples\offline_research_flow.py --topic "Agent reliability" --force-failure  # Force review failure twice and verify human-takeover terminal state.
$LASTEXITCODE  # Print the previous process exit code.

The result should have stage "human_review", attempt 2, and process return code 1. Human takeover is an expected terminal state, not a crash.

No matching source

python -B .\examples\offline_research_flow.py --topic "A topic that is not in the catalog"  # Exercise no-match behavior and verify the system refuses to invent conclusions.

The researcher does not invent a conclusion; it records the gap in unknowns. The reviewer rejects publication with no sourced conclusion, and the Flow ends in human_review.

Publication receipts and repeated calls

Use a unique temporary file so you neither modify course material nor collide with old output:

$output = Join-Path $env:TEMP ("crewai-offline-brief-{0}.md" -f [guid]::NewGuid())  # Create a unique temporary output path.
python -B .\examples\offline_research_flow.py --topic "Agent reliability" --output $output  # Write a publishable brief.
Get-Content -LiteralPath $output  # Inspect the artifact corresponding to the receipt.

The first run writes through a temporary file and atomic replacement, ending at published. Repeat with exactly the same input:

python -B .\examples\offline_research_flow.py --topic "Agent reliability" --output $output  # Reuse the path to exercise idempotency and conflict protection.

The second run does not rewrite different content; publication.recovered is true. This proves reuse of a local file-content receipt, not CrewAI Flow-state or checkpoint recovery. If existing content differs, the project refuses to overwrite it and returns error code 2.

If the catalog changes after run_flow, or someone changes a draft in state while preserving an old reviewer verdict, the write also fails closed. A real system should extend the same idea: reread trusted sources by version/ACL before an external effect and validate through an independent execution layer instead of trusting a persisted model artifact.

Afterward, delete only the temporary file you created:

Remove-Item -LiteralPath $output  # Delete only the unique temporary brief created by this walkthrough.

Run the complete test suite

Run tests from examples so Python imports the sibling module correctly:

Push-Location .\examples  # Enter the example directory so unittest can import the sibling module directly.
$env:PYTHONDONTWRITEBYTECODE = "1"  # Prevent bytecode caches during testing.
python -B -m unittest -v test_offline_research_flow.py  # Normal mode, with test names.
python -B -O -m unittest -v test_offline_research_flow.py  # Optimization mode: key checks must not depend on bare assert.
python -B -W error -m unittest -q test_offline_research_flow.py  # Treat warnings as failures.
python -B -O -W error -m unittest -q test_offline_research_flow.py  # Combine optimization and strict warning handling.
Pop-Location  # Return from examples to the CrewAI course directory.
Pop-Location  # Return from the course directory to the project root.

The modes provide different evidence:

  • ordinary mode: all 39 behavior tests pass;
  • -O: key checks do not depend on bare assert, which optimization removes;
  • -W error: Python warnings become failures and reveal potential compatibility problems;
  • combined mode: confirms both protections at once.

If a command fails, retain the full name and exception from the first failing test, then make the smallest reproduction. Do not merely repeat a run until it happens to pass.

Mapping the offline implementation to CrewAI concepts

Offline projectCorresponding CrewAI conceptWhat cannot be inferred
Pure functions such as run_researcher_taskA limited role fulfilled by an AgentA real LLM reliably follows that role
validate_research/draft/reviewTask output contract, structured output, guardrailCorrect structure means factual correctness
run_crewA three-Task sequential CrewMultiple Agents are automatically better than one
run_flowFlow state, events, routing, and attempt budgetOfficial decorators have the same signature in every version
publish_reportBusiness boundary and same-catalog/reviewer recheck before a side-effecting ToolLocal atomic replacement equals a remote transaction
sources.jsonA minimal Knowledge/retrieval catalogTwo samples represent real RAG

Migrate incrementally to real CrewAI

Place real integration in a separate project or new isolated example; do not break this offline baseline.

  1. Create a new venv and pin verified CrewAI, Python, and model SDK versions.
  2. Replace only the researcher stub with one real Agent/Task while retaining the same output schema.
  3. Have the existing deterministic reviewer check it against a fixed evaluation set.
  4. Replace the writer while retaining source IDs, unknowns, and the attempt budget.
  5. Organize Tasks with a real Crew(process=Process.sequential).
  6. Verify imports and behavior of Flow, @start, @listen, and @router on the pinned version.
  7. Last, integrate real Tools, Memory, Knowledge, event listeners, and checkpoints.
  8. Put side-effecting Tools behind preview/sandbox first, then add approval and idempotency keys.
  9. Record model, Prompt, dependencies, evaluation set, cost, and unvalidated risk.

Replace one boundary per migration step so a failure can be localized to the model, Tool, Crew orchestration, or Flow control. Adjacent Layer B executes pinned-version Flow decorators and SQLite persistence; this project itself proves only standard-library contracts, and neither layer executes a real model provider.

Integrated task

Extend the project without accessing the network:

  1. add one topic and two sources to sources.json;
  2. add an explicit awaiting_approval state that publishes only with a one-time approval object;
  3. simulate one retryable Tool timeout and one non-retryable permission denial;
  4. ensure total retries are bounded;
  5. test changed approval content, repeated publication, unknown sources, and an old schema;
  6. list commands actually run, test count, and unvalidated items in a README or experiment record.

Acceptance checklist

  • A normal sample passes once and reaches ready_to_publish.
  • The forced-revision sample revises only once and then passes.
  • Forced failure and unknown topic both reach human_review.
  • Every publishable claim has a known source ID and a citation in the draft.
  • Event numbers are contiguous and attempts never exceed budget.
  • Repeated same-content publication recovers; different content is not overwritten.
  • All 39 tests pass in normal, -O, warnings-as-errors, and combined modes.
  • The run creates no .pyc, real credentials, or output inside the course directory.
  • You can state which real CrewAI capabilities the offline project does not validate.

Relationship to the rest of the course

Primary references

Sources checked on 2026-07-21: