DeerFlow
DeerFlow loop detection: normalized calls, fingerprints and bounded state
Walk through the inspected Python helpers, 200-line read buckets, order-independent fingerprints and the distinction between FIFO-style storage and LRU.
What you will learn
- Normalize provider data before comparing calls
- Preserve meaningful differences, discard incidental order
- Execute a small boundary test without starting an agent
Before you start
- Basic Python, HTTP and container concepts
- An owned task with explicit acceptance criteria
Explain the chapter’s implementation boundary and apply its checklist or isolated helper exercise.
Key takeaways
- Argument normalization bridges provider transport differences.
- The fingerprint is order-independent but multiplicity-sensitive.
- BoundedDict insertion eviction is not LRU access eviction.
Normalize provider data before comparing calls
_normalize_tool_call_args accepts dictionaries directly and parses JSON-string arguments defensively. A parsed dictionary becomes ordinary arguments; a non-dictionary JSON value gets a stable fallback string. Invalid JSON is retained as a fallback rather than crashing the detector. This matters because two providers can represent the same semantic call using different transport shapes.
_stable_tool_key treats read_file specially: it normalizes and sorts the start and end lines, clamps them to at least one and groups them into 200-line buckets. Reads of lines 1–199 and 200–1 in the same path share bucket 0–0; a read beginning at 201 belongs to bucket 1–1. Nearby line jitter therefore does not automatically evade repetition detection.
Preserve meaningful differences, discard incidental order
write_file and str_replace include their full arguments because changing content at one path can be legitimate progress. Other tools prefer salient fields such as path, URL, query and command when present. These are heuristics, not semantic proof: ignored fields may matter to a custom tool, and bucketed reads may collapse different useful requests.
_hash_tool_calls sorts normalized name-and-key entries, serializes them and keeps twelve hexadecimal characters from MD5. Reordering a batch preserves its fingerprint, but adding a duplicate changes the multiset and its fingerprint. This short non-cryptographic identifier is useful for repetition tracking; it is neither an authorization token nor a collision-free correctness guarantee.
Execute a small boundary test without starting an agent
Our read-only probe verifies the exact downloaded source hashes, extracts only the three inspected helper functions with Python’s AST and executes them with standard-library dependencies. It confirmed JSON normalization, read buckets, changed-write sensitivity, permutation invariance and preserved multiplicity. The separate BoundedDict module was also exercised: updating a key did not refresh insertion order, so adding c evicted a rather than b.
BoundedDict is used for stop reasons and is not the same policy as the detector’s thread-history LRU. The example below independently models only the read-bucket calculation in JavaScript and can be run without DeerFlow. Neither that example nor the Python helper probe executes the full middleware lifecycle, sandbox, model provider or upstream integration suite.
Implementation steps
- 1
Read the three helper functions at the pinned commit.
- 2
Compare read ranges across the 200-line boundary.
- 3
Test changed writes, reordered batches and duplicate calls separately.
- 4
Keep helper success distinct from full-runtime verification.
Copy-ready example
// Independent model for integer line ranges, not the upstream Python runtime.
function readBucket(path, start = 1, end = start) {
const [lo, hi] = [start, end].sort((a, b) => a - b);
return `${path}:${Math.floor((Math.max(lo, 1) - 1) / 200)}-${Math.floor((Math.max(hi, 1) - 1) / 200)}`;
}
console.log([readBucket("a", 1, 199), readBucket("a", 200, 1), readBucket("a", 201)]);Frequently asked questions
Is the JavaScript example the real DeerFlow implementation?
No. It is a narrow integer-range teaching model. The separate Python probe executed the inspected source helpers with hash checks.
Does the fingerprint prove calls are semantically identical?
No. Salient-field selection and line buckets intentionally approximate repetition, and the truncated digest is not collision-free.
Sources
- README.mdSource checked 2026-09-08
- LICENSESource checked 2026-09-08
- backend/README.mdSource checked 2026-09-08
- backend/pyproject.tomlSource checked 2026-09-08
- backend/docs/middleware-execution-flow.mdSource checked 2026-09-08
- backend/packages/harness/deerflow/agents/lead_agent/agent.pySource checked 2026-09-08
- backend/packages/harness/deerflow/agents/middlewares/loop_detection_middleware.pySource checked 2026-09-08
- backend/packages/harness/deerflow/agents/middlewares/_bounded_dict.pySource checked 2026-09-08
- backend/packages/harness/deerflow/config/loop_detection_config.pySource checked 2026-09-08
- backend/packages/harness/deerflow/sandbox/local/local_sandbox_provider.pySource checked 2026-09-08
- backend/app/gateway/auth_middleware.pySource checked 2026-09-08
- backend/app/gateway/csrf_middleware.pySource checked 2026-09-08
- docker/docker-compose.yamlSource checked 2026-09-08