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
| File | Purpose |
|---|---|
examples/offline_research_flow.py | Strict data validation, three Task stubs, sequential Crew execution, Flow routing, and idempotent publication |
examples/sources.json | Two offline sources with schema version, stable source IDs, topics, and claims |
examples/test_offline_research_flow.py | 39 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’spython.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:
stageisready_to_publish;attemptis1;- every claim in
result.research.claimshassource_ids; result.draft.markdowncontains[source-1]and[source-2];- event
sequenceincrements 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 bareassert, 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 project | Corresponding CrewAI concept | What cannot be inferred |
|---|---|---|
Pure functions such as run_researcher_task | A limited role fulfilled by an Agent | A real LLM reliably follows that role |
validate_research/draft/review | Task output contract, structured output, guardrail | Correct structure means factual correctness |
run_crew | A three-Task sequential Crew | Multiple Agents are automatically better than one |
run_flow | Flow state, events, routing, and attempt budget | Official decorators have the same signature in every version |
publish_report | Business boundary and same-catalog/reviewer recheck before a side-effecting Tool | Local atomic replacement equals a remote transaction |
sources.json | A minimal Knowledge/retrieval catalog | Two 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.
- Create a new
venvand pin verified CrewAI, Python, and model SDK versions. - Replace only the researcher stub with one real Agent/Task while retaining the same output schema.
- Have the existing deterministic reviewer check it against a fixed evaluation set.
- Replace the writer while retaining source IDs, unknowns, and the attempt budget.
- Organize Tasks with a real
Crew(process=Process.sequential). - Verify imports and behavior of
Flow,@start,@listen, and@routeron the pinned version. - Last, integrate real Tools, Memory, Knowledge, event listeners, and checkpoints.
- Put side-effecting Tools behind preview/sandbox first, then add approval and idempotency keys.
- 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:
- add one topic and two sources to
sources.json; - add an explicit
awaiting_approvalstate that publishes only with a one-time approval object; - simulate one retryable Tool timeout and one non-retryable permission denial;
- ensure total retries are bounded;
- test changed approval content, repeated publication, unknown sources, and an old schema;
- 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
- Objects and sequential execution: Core Objects: Crew, Agent, Task, and Process
- Flow and events: Flow, State, and Events
- Tools and structured output: Tool Boundaries and Structured Output
- Information boundaries: Memory, Knowledge, and Context
- Evaluation: Testing, Evaluation, and Observability
- Production boundaries: Safety, Failure Recovery, and Production Boundaries
Primary references
Sources checked on 2026-07-21: