Changelog
All notable changes to ladon-crawl are documented here.
The format follows Keep a Changelog. Versioning follows Semantic Versioning.
Unreleased
Added
- Generic SES protocol types —
Ref[RawT],Expansion[RecordT, ChildRawT],Source,Expander,Sink,CrawlPlugin, andCrawlPlannow preserve concrete adapter types under strict type checking. NewOnLeafCallback/OnPlannedLeafCallbacktype aliases and async equivalents let adapter authors annotate callbacks precisely. SyncHttpClientProtocol/AsyncHttpClientProtocol— public structural HTTP client contracts implemented by both native and curl-cffi backends.run_plugin()/async_run_plugin()— source-driven whole-plugin entry points. They discover roots once, preserve oneRunResultper root, and return aggregate counts and source-indexed errors inPluginRunResult.VerdictandFetchPredicate.evaluate()— three-valued predicate results makeACCEPT,CONTINUE, andREJECTexplicit. Rejected candidates are excluded from acceptance and fallback selection while remaining sources are searched;AllOf,AnyOf, andNotpreserveREJECTas an absolute veto.rejection_info()— optional duck-typed extension point onFetchPredicateimplementations for adding predicate-specific diagnostics topredicate_rejecteddecision-event metadata.
Deprecated
FetchPredicate.accepts() -> bool— useevaluate() -> Verdictinstead. The boolean API remains supported for one full minor release and emits aDeprecationWarningon every legacy invocation.
Removed
RetryableHttpError— the deprecated alias forTransientNetworkErrorannounced for removal in v0.1.0. Three minor releases past that announcement, it is removed in this release. UseTransientNetworkErrordirectly.
Fixed
- Backend-agnostic adapter and runner typing —
Source,Expander,Sink, their async counterparts, and runner signatures now accept either native or curl-cffi factory results under strict type checking. - Polite retry pacing — retries now enforce per-host rate limits, including
robots.txt
Crawl-delayoverrides, on every attempt and merge that wait with Retry-After or backoff into one sleep. The default backoff is now a safe0.5seconds; explicitly setting zero with retries enabled emits a warning. - Consistent runner leaf-exception semantics — sync crawls now record an
unexpected non-fatal
Sink.consume()exception and continue with remaining leaves instead of aborting and losing the partial result. Async crawls now propagate leaf cancellation instead of silently counting it as a failure. Consequently, the CLI now exits with code 2 for an ordinarySink.consume()exception recorded as a partial leaf failure; exit code 1 remains reserved for exceptions the runner does not isolate. - Circuit-breaker HTTP 5xx accounting — returned 5xx responses now count
as origin-health failures without changing their
Ok(...)result contract; non-retryable 4xx responses remain successful breaker outcomes. - Async politeness under concurrency — same-host requests now reserve rate-limit slots instead of waking in a batch, and HALF_OPEN circuit breakers admit exactly one probe. Both guards are cancellation-safe and remain independent across hosts.
Changed
- Breaking: planned-crawl callback keyword renamed —
execute_plan_sync(..., on_leaf=...)andexecute_plan(..., on_leaf=...)must now useon_planned_leaf=, making their(leaf_record, leaf_ref)contract distinct from the runners'(leaf_record, parent_record)callback.
0.3.2 — 2026-06-08
Added
CrawlPlan— immutable Phase 1 output carryingrecord,leaves, anderrors. Filter withexcluding(predicate)orlimited_to(n)before passing toexecute_plan_sync/execute_plan.plan_crawl_sync/plan_crawl— Phase 1 only: traverse all expanders and return aCrawlPlanwithout calling the sink.execute_plan_sync/execute_plan— Phase 3 only: consume an existing plan against the sink.on_leafreceives(leaf_record, leaf_ref)— the leaf ref, not a parent record (ADR-016). Optionalon_progress(done, total)callback for real-time progress reporting.ladon.observability—DecisionEventdataclass,DecisionTrackerProtocol, andNullDecisionTrackerno-op default (same pattern asMetricsBackend/NullMetricsfrom ADR-009). All three re-exported from the top-levelladonnamespace.MultiSourceSink.resolve_multi(run_id=)— optional correlation key (auto-UUID if omitted) shared across all events from one resolution call. Eight event types emitted at five hook points:source_skipped,source_failed,candidate_accepted,candidate_rejected,predicate_rejected,resolved(via_fallback=True/False),no_result.ladon.contrib.sqlite_tracker.SqliteDecisionTracker— append-only SQLite backend with three indexes (run_id, ref, event),query()method for post-run SQL analysis, and context-manager support.
Fixed
MultiSourceSink— non-NotImplementedErrorexceptions from_fetch_from_sourceare now caught, recorded assource_failed, and the loop continues instead of propagating.NotImplementedErroris re-raised to preserve the subclass contract.
0.3.1 — 2026-05-20
Added
ladon.mcp.LadonMCPAdapter— abstract base class for adapter packages that want to expose data-plane MCP tools vialadon-nous. Adapters implementadapter_name,mcp_tools(), and optionallymcp_resources(), then declare themselves via theladon.mcpPython entry-point group. Nofastmcpimport in core — onlyladon-nousrequires that dependency.
0.3.0 — 2026-05-18
Added
-
cffioptional dependency group —pip install ladon-crawl[cffi]installscurl-cffi>=0.11,<1, enabling theCurlHttpClientandAsyncCurlHttpClientbackends for Cloudflare-protected targets (issue #107). -
CurlHttpClient/AsyncCurlHttpClient— sync and async HTTP clients backed by curl-cffi. Mirror all policies ofHttpClient/AsyncHttpClient(retries, exponential backoff, circuit breaker, proxy rotation, rate limiting) but use TLS fingerprint impersonation (JA3/JA4) to bypass Cloudflare L1+L2 challenges without browser automation. Both are exported fromladon.networkingand the top-levelladonnamespace. -
HttpClientConfig(backend=, impersonate=)— two new fields select the HTTP backend without changing call sites.backend="curl-cffi"(requiresimpersonate) returns aCurlHttpClient/AsyncCurlHttpClientfrom the factory helpers. Default isbackend="requests"(unchanged behaviour). -
make_http_client()/make_async_http_client()— factory helpers that instantiate the correct sync or async client based onconfig.backend. Exported fromladon.networkingand the top-levelladonnamespace.
0.2.0 — 2026-04-25
Added
-
Async crawling via
async_run_crawl()— asyncio-native counterpart torun_crawl(). Phase 1 (expander traversal) is sequentialawait; Phase 3 (sink) issues leaf fetches concurrently behindasyncio.Semaphore(config.async_concurrency)(default 10). Each semaphore slot covers the fullsink.consume()+on_leafpair so callbacks are naturally isolated.LeafUnavailableErroris isolated per leaf (other leaves continue);ExpansionNotReadyErrorremains globally fatal.RunConfiggainsasync_concurrency: int = 10;AsyncHttpClientandasync_run_crawlare exported from the top-levelladonnamespace. -
AsyncHttpClient— full async HTTP client backed byhttpx. Mirrors all policies ofHttpClient(retries, exponential backoff, full-jitter, 429/503 Retry-After, circuit breaker, proxy rotation, HTTP auth,default_params,default_headers).respect_robots_txt=TrueraisesNotImplementedErrorat construction time (deferred to a later release). Exported fromladon.networkingand the top-levelladonnamespace. -
Async plugin protocols —
AsyncSource,AsyncExpander,AsyncSink, andAsyncCrawlPluginstructural protocols (PEP 544, all@runtime_checkable). All four are exported fromladon.pluginsand the top-levelladonnamespace. The sync protocol hierarchy is untouched.
0.1.0 — 2026-04-25
Added
-
HTTP authentication —
HttpClientConfig(auth=("user", "pass"))for HTTP Basic Auth;auth=HTTPDigestAuth("user", "pass")or anyrequests.auth.AuthBasesubclass for Digest and custom schemes (HMAC signing, OAuth token injection). Wired directly torequests.Session.auth. Tuple length validated at construction. Bearer tokens and static API keys remain indefault_headersas before. -
Default query parameters —
HttpClientConfig(default_params={"api_key": "..."})injects query parameters into every request. Per-requestparamstake precedence on key collision, matching the same override contract asdefault_headers. Frozen viaMappingProxyType. Useful for API keys that must appear in the query string. -
paramskwarg onpost()anddownload()— symmetry withget()andhead(); merged withdefault_paramsin the same way. -
Proxy rotation via
ProxyPool—HttpClientConfig(proxy_pool=RoundRobinProxyPool([...]))rotates through a list of proxies on every request attempt. Custom rotation strategies are supported through theProxyPoolprotocol (next_proxy()/mark_failure());mark_failure()is called on transport errors and rate-limit responses so implementations can apply cooldowns or exclusions. Mutually exclusive withproxies.validate_proxy(mapping)is exported fromladon.networkingas a public helper for custom pool implementations. -
Static proxy support —
HttpClientConfig(proxies={"https": "http://proxy:8080"})routes all session traffic through a proxy. Followsrequestsconventions; SOCKS proxies supported whenrequests[socks]is installed. Proxy URLs are validated at config construction time (scheme must behttp,https,socks4,socks4h,socks5, orsocks5h). -
HTTP 429 / 503 with Retry-After respect —
HttpClientConfig(retry_on_status=...)automatically retries safe methods on configurable status codes (default{429, 503}). TheRetry-Afterheader is honoured in both delta-seconds and HTTP-date forms (RFC 7231 §7.1.3); capped atmax_retry_after_seconds(default 300 s). RaisesRateLimitedErrorwhen retries are exhausted. -
Full-jitter exponential backoff —
HttpClientConfig(backoff_jitter=True)draws each retry sleep fromuniform(0, base × 2^attempt)instead of the deterministic cap, preventing thundering-herd spikes when multiple crawlers restart simultaneously. -
RateLimitedError— new error class (subclass ofHttpClientError) withstatus_code: intandretry_after: float | Noneattributes; exported at bothladon.networkingandladonlevels.
0.0.1 — 2026-04-17
First public release.
Added
- SES pipeline — Source / Expander / Sink architecture for structured,
typed web crawls (
runner.py,run_crawl()) CrawlPluginprotocol — typed adapter interface enforcing Source, Expander, and Sink roles (ADR-003);ladon-hackernewsis the canonical reference implementationRepository+RunAuditprotocols — persistence layer with structural subtyping;NullRepositoryfor dry runs and testing (ADR-006)LocalFileStorage— zero-config file storage backend- HTTP client — circuit breaker, configurable retry/backoff,
robots.txtsupport (--respect-robots-txtflag) - CLI —
ladon runandladon info; exit codes 0 (success) / 1 (leaf errors) / 2 (fatal) / 3 (robots.txt blocked) RunResultcounters —leaves_consumed,leaves_persisted,leaves_failed(renamed fromleaves_fetchedin this release)py.typedmarker — full type checking support (PEP 561)- Dual-license model — AGPL-3.0-only open source + commercial license
option (
LICENSE-COMMERCIAL); CLA required for contributors (ADR-010)
Known limitations
RunResultcounter semantics are scheduled for redesign in v0.1.0 (issue #62) — the current counters are correct but the model will be simplified- Python 3.11, 3.12, and 3.13 supported; 3.10 and below are not