

Also from Kynth Studios


Also from Kynth Studios


Also from Kynth Studios
1### Python Extension System (Runtime and Distribution)23Third-party Python packages can expose an `install(registry, config)` function and be4loaded, in deterministic order, from the startup-only top-level `plugins:` list in5`config.yaml`. Keep this list out of `extensions_config.json`: the latter is writable6through Gateway APIs, while importing Python entry points is an operator-controlled code7execution boundary. A plugin marked `required: true` fails Gateway construction when it8cannot load; optional plugins fail open with attributed diagnostics.910Packaged extensions use one PEP 621 entry point in the11`deerflow.extensions` group, for example12`example = "deerflow_extension_example:install"`. The operator CLI is dispatched from13the existing `deerflow` console script to `extensions/cli.py` and exposes only these14surfaces: `install SOURCE [--yes]`, `list`, `enable NAME`, `disable NAME`, and15`remove NAME`. `NAME` resolves against the entry-point name, distribution name, or16`module:install` value. The root `make extension-*` targets are convenience wrappers;17because they execute from `backend/`, documentation should use absolute local source18paths with `SOURCE=` unless backend-relative behavior is intentional.1920`ExtensionManager` owns the package/config transaction. Install runs a controlled21`uv add --project <backend> --group extensions --no-workspace --no-sync -- <source>`, updates the dedicated22`[dependency-groups].extensions` list and `uv.lock`, discovers exactly one packaging entry23point, and inserts or adopts one24managed `plugins:` record with `name`, `package`, `use`, `enabled`, `required`, and25private `config`. New records are written `required: false`, matching the loader default:26`required: true` turns any later load failure — a broken wheel, a missing native library, a27deleted snapshot — into a Gateway startup abort recoverable only through shell access, so28it is an explicit `install --required` opt-in rather than the managed default. Adoption of29an existing hand-written record preserves whatever `required` the operator already chose.30Enable/disable changes only the host-level `enabled` flag and preserves31private configuration. Remove runs `uv remove --group extensions`, removes the plugin32record, and deletes its managed source snapshot. Install validates the selected config33file before running any uv command, because `uv add`/`uv sync` execute the package's build34backend: a config this manager could never write to must fail before that code runs, not35afterwards through rollback.36Failed install/remove operations restore `pyproject.toml` and `uv.lock` and resynchronize37the restored environment; that second restore runs even when the recovery sync itself fails38(a recovery sync without `--locked` writes a lock while resolving), and a failing recovery39sync reports the original failure alongside it. The restore is deliberately not blanket:40when recovery detects a concurrent external edit to the dependency files or the config it41preserves that edit and raises instead, and `remove` leaves the plugin deactivated in that42case rather than reviving a record whose package declaration may already be gone. A43cancellation skips the recovery sync entirely — the declarations are already restored and44the next locked startup sync reconciles the environment, whereas blocking an interrupt on a45full dependency resolve invites a second interrupt that escapes the handler mid-transaction.46Package mutation is deferred from environment mutation: after `uv add/remove`47updates the declaration and lock, one `uv sync --locked --all-packages` preserves the same48config-/environment-detected optional extras as normal startup. All three uv calls pin the49backend project explicitly and discard UV environment overrides that could redirect the50project, working directory, sync mode, lock policy, or target environment — including51`UV_PYTHON`, which would swap the interpreter that then loads the extension entry point,52and `UV_INSECURE_HOST`, which would remove the TLS validation the HTTPS-only source rule53depends on; index, proxy, cache, and credential-provider settings remain available.54The `--no-workspace` boundary requires uv 0.8.0 or newer. The stock Docker paths pin uv550.11.1, and the manager fails before mutation when the host uv is older.56All install/remove/enable/disable mutations for a checkout hold the cross-process57`.deer-flow/extension-manager.lock`; remove deactivates config before changing the package58declaration, and rollback preserves a concurrent external config edit instead of replacing59it. The MVP has no in-place upgrade: operators retain private config, remove the old60package, install the new source pin, and restore that config.6162Local-directory installs are snapshots, not editable links. The manager validates the63source, derives the destination from the normalized distribution name, and copies it to64`backend/extensions/sources/<distribution>/`. It ignores Git metadata, virtual65environments, Python caches, and bytecode; rejects symbolic links, path-escaping66distribution names, and likely credential files; and the root `.dockerignore` explicitly67re-includes the entire managed tree so package READMEs, native modules, and assets reach68the backend builder. These checks prevent common packaging accidents, not malicious69code. Both Python build hooks and imported extension code execute with Gateway70privileges, so the CLI requires confirmation (or explicit `--yes`) and accepts only71trusted operator sources; source URLs containing embedded credentials are rejected.72Remote direct references are limited to HTTPS, and remote Git sources must use public73Git-over-HTTPS (with loopback HTTP accepted for local tooling). SSH Git URLs are rejected74because the stock Docker builder does not forward host SSH credentials; relative paths and75local wheels must use the managed directory snapshot path instead. Git's SCP-like shorthand76(`git@host:org/repo.git`) carries no URL scheme, so it is detected before the scheme rules77and reported with the same public-HTTPS correction rather than the local-path message.78Local wheel and `file://` sources are rejected because they cannot be reproduced inside the79Docker build context; local code must enter through the directory-snapshot path. Stock80production builds support public package indexes and public HTTPS Git sources reachable by81the builder; authenticated source configuration must not be embedded in the recorded URL.82Source validation alone cannot catch environment-driven resolution (for example a83`UV_FIND_LINKS` wheelhouse turning a plain package requirement into a local wheel84reference), so after every `uv add/remove` the manager audits the new lock before syncing85or enabling anything. Any local reference that the stock backend image build cannot86reproduce — absolute paths, `file:` URLs, or relative paths outside the project root, its87exact workspace members, and the managed `extensions/sources/` snapshots — fails the whole88transaction and rolls back the dependency files, config, snapshot, and environment. A89loopback URL recorded in the lock is warned about rather than rolled back: `127.0.0.1`90inside the image builder is a different machine, so the reference is just as91non-reproducible, but unlike an environment-driven wheelhouse resolution it is a source the92operator typed deliberately. A private-network index is left alone entirely — a builder on93that network can reach it. A94config with duplicate top-level `plugins:` keys is rejected outright rather than managed95against one block while the Gateway reads another.9697The managed `plugins:` block is rewritten in place, and both of its boundaries come from98the YAML parser rather than a key-shaped pattern. `AppConfig` allows extra top-level keys,99so a neighbouring section may be named anything YAML accepts (`my.key`, `2fa`, `$schema`, a100non-ASCII word); a pattern that fails to recognize the next key does not fail loudly, it101reports "no next section" and the rewrite replaces that neighbour and its whole subtree.102Trailing comments below a file-final block are preserved for the same reason — the manager103appends `plugins:` at end of file, so that is the steady-state shape.104105Dependency synchronization has one lock authority: the manager's `uv add/remove` calls106are the only extension workflow allowed to update `backend/uv.lock`, and each mutation is107followed by the local-source audit described above. The `extensions`108group is included in `[tool.uv].default-groups` alongside `dev`. Root/backend install109targets use `uv sync --locked`; direct backend `make dev`/`make gateway` use110`uv run --locked`; the local full-stack launcher and Docker-dev entrypoint perform one111locked sync and then launch with `uv run --no-sync`; the production Docker builder syncs112the same copied backend project and lock, and both image runtime commands use113`--no-sync`. Thus production may download locked remote artifacts while building an114image, but production container startup never resolves or installs an extension from the115network. Local and Docker-dev pre-start syncs may fetch missing locked artifacts.116`docker/dev-entrypoint.sh` retries a failed sync once after recreating `.venv`, but keeps117`--locked` on the retry: that repairs a broken virtualenv, not a stale lock. A second118failure aborts with recovery instructions instead of starting uvicorn against an119environment that does not match the lock, because startup must never silently resolve120dependencies.121That discipline assumes the uv writing the lock and the uv reading it stay compatible, so122uv is pinned rather than floating: `backend/Dockerfile`'s `UV_IMAGE` is the single source of123truth, both compose defaults repeat it, and every `astral-sh/setup-uv` step pins the same124version so CI exercises the manager against the binary production actually runs. Otherwise a125newer uv can bump `uv.lock`'s `revision` (or make `uv lock --check` disagree with a lock126generated elsewhere) while CI stays green, and the pinned uv in the production image then127fails on the committed lock. `backend/tests/test_ci_uv_version_pin.py` keeps the four128locations in step, which makes a uv upgrade one deliberate, reviewable change.129Rebuild the Gateway image after changing the managed set. Every install, enable, disable,130remove, or config mutation also requires a Gateway restart because plugin loading is131startup-only.132The root management wrappers bootstrap the checkout environment without the extension group133via `uv run --frozen --no-group extensions`, so a broken or disappeared extension source cannot134trigger project validation before the operator can list, disable, or remove it, while a135fresh checkout can still install the non-extension environment from the existing lock. After CLI136entry, the manager owns the controlled locked sync.137138The public package is `packages/extension-api/` and must never import `deerflow` or carry139framework dependencies. Extensions declare any FastAPI, LangChain, or LangGraph imports140themselves. Its registry contract exposes five contribution kinds: middleware141contributors, task-lifecycle contributors, system-model-call observers, Gateway-lifetime142services, and eager routers. Middleware contributions declare lead/subagent scope, stable143order, and a semantic placement (`MODEL_LOGICAL`, `MODEL_PHYSICAL`, `TOOL_VISIBLE`,144`TOOL_RAW`, or `STANDARD`) rather than a fragile list index. `extensions/stack.py` is the145single final composition point; do not inject inside146the shared base builder because the lead builder appends more middleware afterward.147`extensions/ordering.py` owns host ordering invariants and validates the final composed148stack. Nothing under `extensions/` may import `agents.middlewares` at module scope: the149middleware layer calls into this one, so a module-scope reference points the dependency150backwards and closes a cycle as soon as any middleware imports something under151`extensions/` at module level. Both tables that need middleware classes therefore resolve152on first use — `ordering.py::core_ordering_constraints()` and `stack.py::_anchors()` —153which is `assert_ordering` / composition time, already inside the middleware builder.154Defer by deferring the *call*; do not fake a resolved value with a lazy container155subclass, which reports one answer when iterated and another when measured.156157Contributed middlewares are wrapped by `IsolatedMiddleware`: extension failures emit158diagnostics and fail open without repeating a downstream model/tool side effect. The159wrapper mirrors lifecycle hooks, tools, transformers, and state schema implemented by160the inner middleware. LangChain treats each sync/async model or tool wrapper pair as one161capability, so a single-sided wrapper receives a pass-through counterpart; implement162both sides when the extension must observe both synchronous and asynchronous execution163paths.164165Lead runs and subagents allocate an `ExtensionData` task store only when middleware,166task-lifecycle, or system-model observation is registered; services and routers are167app-scoped and do not allocate one. Middleware and system-call sites recover the168live store through `EXTENSION_TASK_STORE_KEY` / `task_store_from_runtime()`; lifecycle169contributors receive that same store directly. Each task resolves the immutable170loaded-extension snapshot once and binds that same object through task-store allocation,171hooks, and synchronous agent construction, so a concurrent singleton replacement cannot172mix two extension generations without changing the LangGraph graph-factory ABI. The173graph-build binding is a ContextVar scoped to synchronous construction, so it has already174exited by the time the lead agent delegates; the run worker therefore also publishes the175snapshot on runtime context under the host-internal `EXTENSION_SNAPSHOT_CONTEXT_KEY`,176`task_tool` reads it back through `resolve_run_extensions()` (type-checked — runtime177context is caller-mergeable), and `SubagentExecutor` binds it at construction. That key is178written after the caller merge and popped when the run has none, so a caller-supplied value179is never authoritative. Absent the key — embedded `DeerFlowClient`, standalone LangGraph180Server — the executor keeps its `get_loaded_extensions()` fallback.181182The lead worker awaits `on_task_start` after the run has started and awaits `on_task_stop`183after completion persistence/hooks but before clearing any active finalizing barrier or184publishing the stream end. A subagent with a parent `run_id` wraps its execution with the185same start/stop pair. Outcomes are conservative (`completed`, `aborted`, or `failed`),186contributors run in registration order within one bounded budget, and notification failures187are logged and fail open.188189Fail-open is decided by the *origin* of a failure, not by its base class, because190`CancelledError` reaches a contributor's `except` for two unrelated reasons. Only a genuine191cancellation of the host task increments `asyncio.Task.cancelling()`, so `_notify_each`192propagates on that and contains everything else: a contributor that lets a `CancelledError`193escape — an extension implementing an internal timeout with cancellation, say — must not194skip its successors, and must not reach the worker's deferred-interrupt path, which would195end an otherwise successful run as cancelled. `KeyboardInterrupt` / `SystemExit` still196propagate.197198System-model-call observers cover DeerFlow-owned model invocations that do not pass199through middleware model-call wrappers: goal evaluation, memory extraction, title200generation, and summarization. They receive a request/result snapshot, duration, and the201active task store when one exists; detached system work receives an isolated store. All202three terminal paths are reported without changing the exception the host observes:203success and failure are awaited inline, while cancellation — routine, since204interrupt/rollback admission and shutdown both cancel the run task, with the provider205tokens already spent — is submitted to the notify loop instead of awaited, because a206repeated cancel would interrupt that await before any observer ran. A deployment with no207registered notify loop drops the cancellation observation, exactly as the synchronous208memory bridge does. `SystemModelRequest.messages` normalizes to a tuple at construction: goal and209memory pass a message list while title and summarization pass one prompt string, and a210bare `str` is already a `Sequence`, so without normalization an observer iterating it211would walk characters. Normalizing also copies a live list, which is what makes the frozen212snapshot immutable in fact rather than only by declaration. Gateway registers one canonical extension-notification loop. Awaited lifecycle213hooks and async system observations are dispatched to that loop even when the caller is a214subagent's isolated loop, while synchronous system callbacks submit fire-and-forget work215there. Shutdown stops accepting detached observations before the memory shutdown flush and216resets the loop only after in-flight run/subagent drain ordering is complete.217218Gateway services start in registration order after the persistence engine and session219factory are ready. Each receives the same `ExtensionRuntimeDeps` snapshot containing the220app store, projected host policy, and session factory. Start failures are attributed and221fail open. The runtime captures `app.state.extensions` once, registers cleanup before the222start batch, and stops the attempted service prefix in reverse order after run/subagent223drain but before store, checkpointer, and engine teardown. Each stop has an independent224bounded timeout; failures do not starve later cleanup. A service-originated225`CancelledError` fails open, while a new cancellation of the host task still propagates226through the exit stack. Runtime diagnostics must be appended through227`record_runtime_diagnostics()` so `app.state.extension_diagnostics` remains the canonical228live list.229230Routers are constructed eagerly during `install()` and mounted only after all host routes,231so host handlers always win. The Gateway rejects a contributed router atomically when an232earlier host or extension route provably covers one of its paths for the same HTTP method.233The conservative matcher proves common shadows through normalized parameter names,234static-vs-dynamic matching, known built-in-converter containment, supported compound235segments, full-segment `path` catch-alls, and `Mount` descendants reducible to those same236rules. Relationships requiring general regex-language inclusion are allowed rather than237guessed. Host WebSocket routes do not collide with contributed HTTP routes, but contributed238WebSocket routes are rejected until the host can supply authentication and Origin checks.239Because `include_router()` recompiles contributed routes, preflight projects the converter240registry at include time. Nonstandard converters fail closed against reserved security241paths but otherwise prove a shadow only when their normalized matchers are identical.242Host authentication- and CSRF-exempt paths are reserved, and contributed Mounts, unsupported243route items, startup/shutdown hooks, and custom router lifespans are rejected; lifetime244resources must use `ExtensionService`. Auth and CSRF classify245`get_request_route_path(request)`, the same root-path-adjusted ASGI path Starlette routes246match; do not switch those security predicates back to reconstructed `request.url.path`.247That helper delegates to the private `starlette._utils.get_route_path` on purpose. Its248requirement is not "strip `root_path` correctly" but "return exactly what the router is249matching on", so importing the dispatcher's own implementation keeps the two in lockstep by250construction. Do not vendor a local copy: a private import that disappears fails loudly at251startup, while a stale copy diverges silently at a security boundary. `starlette` is252therefore a declared, bounded direct dependency so the bump is visible in review, and253`tests/test_gateway_request_path.py` pins the agreement independently of the mechanism.254Any preflight, conflict, or include failure rolls back the whole router without preventing255later routers from mounting. Do not introduce a framework-bound `RouterContributor`256contract: the public registry accepts `Sequence[Any]`257to keep extension-api dependency-free.258259The memory kind reaches those observers through a different shape, and the difference is260deliberate rather than an oversight to be "aligned" away. DeerMem must stay vendorable and261cannot import the extension API, so it reports through the `MemoryCallbacks.on_memory_llm_result`262host hook, which the DeerFlow-side callbacks translate into an observation and submit263without awaiting. It also guards its provider call with `BaseException` rather than264`Exception`, which is safe precisely because that whole path runs on a worker thread — the265debounce timer, or the executor `update_memory` offloads to — where cancelling the awaiting266side never interrupts the running thread, so `CancelledError` cannot arrive there at all.267The host hook wrapper around the callback stays at `Exception`: only the hook's own failures268are non-fatal, and an observability path must not swallow `SystemExit` / `KeyboardInterrupt`.269270Gateway `create_app()` loads plugins once, stores the immutable registry on `app.state`271and in the process-wide singleton, mounts contributed routers last, and installs one272canonical live diagnostics list.273Changing `plugins` requires a restart. Any future contribution kind must be added to the274public contract and host runtime in the same slice; never accept a registration method275that the current host silently ignores.276277### Extension Manager Test Repositories278279`test_extension_manager.py` creates temporary Git repositories for local extension sources.280Temporary commits use an empty repository-local hook directory. They must not run developer or CI Git hooks.281Tests for hook behavior must create and invoke their own hook fixtures.282
One repository carrying more than one format is the comparison this product exists for: does anyone actually write different content in each file, or is one a copy of the other?
| Repository | Format | Stack | Covers | Score | Changed |
|---|---|---|---|---|---|
| bytedance/deer-flowAGENTS.md · 80k | AGENTS.md | setuptestlint-formatstyle+1 | 93/100 | today | |
| bytedance/deer-flowbackend/packages/harness/deerflow/subagents/AGENTS.md · 80k | AGENTS.md | archdependenciesmonorepo | 38/100 | today | |
| bytedance/deer-flowbackend/packages/harness/deerflow/tools/AGENTS.md · 80k | AGENTS.md | setuptestarchdependencies+1 | 50/100 | today | |
| bytedance/deer-flowbackend/app/channels/AGENTS.md · 80k | AGENTS.md | monorepo | 29/100 | today | |
| bytedance/deer-flowbackend/app/gateway/AGENTS.md · 80k | AGENTS.md | testing-strategyapimonorepo | 22/100 | today | |
| bytedance/deer-flowbackend/packages/harness/deerflow/AGENTS.md · 80k | AGENTS.md | archdependenciesmonorepo | 55/100 | today | |
| bytedance/deer-flowbackend/packages/harness/deerflow/agents/AGENTS.md · 80k | AGENTS.md | agent-behaviour | 38/100 | today | |
| bytedance/deer-flowbackend/packages/harness/deerflow/agents/memory/AGENTS.md · 80k | AGENTS.md | archdependenciesperformancemonorepo | 18/100 | today | |
| bytedance/deer-flowbackend/packages/harness/deerflow/agents/middlewares/AGENTS.md · 80k | AGENTS.md | no sections | 29/100 | today | |
| bytedance/deer-flowbackend/packages/harness/deerflow/config/AGENTS.md · 80k | AGENTS.md | styletypesdatabaseperformance | 55/100 | today | |
| bytedance/deer-flowbackend/packages/harness/deerflow/mcp/AGENTS.md · 80k | AGENTS.md | archdependenciesmonorepo | 45/100 | today | |
| bytedance/deer-flowbackend/packages/harness/deerflow/models/AGENTS.md · 80k | AGENTS.md | setuparchdependenciesmonorepo | 51/100 | today | |
| bytedance/deer-flowbackend/packages/harness/deerflow/persistence/migrations/AGENTS.md · 80k | AGENTS.md | archtypesdependenciesdatabase+1 | 52/100 | today | |
| bytedance/deer-flowbackend/packages/harness/deerflow/runtime/AGENTS.md · 80k | AGENTS.md | no sections | 32/100 | today | |
| bytedance/deer-flowbackend/packages/harness/deerflow/sandbox/AGENTS.md · 80k | AGENTS.md | testarchdependenciesmonorepo+1 | 22/100 | today | |
| bytedance/deer-flowbackend/packages/harness/deerflow/skills/AGENTS.md · 80k | AGENTS.md | archdependenciesperformancemonorepo | 42/100 | today | |
| bytedance/deer-flowbackend/packages/harness/deerflow/tracing/AGENTS.md · 80k | AGENTS.md | archdependenciesmonorepo | 38/100 | today | |
| bytedance/deer-flowbackend/packages/harness/deerflow/tui/AGENTS.md · 80k | AGENTS.md | testarchdependenciesmonorepo | 46/100 | today | |
| bytedance/deer-flowfrontend/src/AGENTS.md · 80k | AGENTS.md | styleui | 27/100 | today | |
| bytedance/deer-flowscripts/AGENTS.md · 80k | AGENTS.md | testgitdo-not | 67/100 | today |
Same format, overlapping stack, ranked by quality.
| Repository | Format | Stack | Covers | Score | Changed |
|---|---|---|---|---|---|
| vllm-project/vllmAGENTS.md · 89k | AGENTS.md | setuptestlint-formatstyle+5 | 100/100 | 14 days ago | |
| deepseek-ai/deepseek-harnessnative/landlock-run/AGENTS.md · 104k | AGENTS.md | setupteststylearch+3 | 100/100 | today | |
| code-yeongyu/oh-my-openagentpackages/web/AGENTS.md · 68k | AGENTS.md | setupbuildtestlint-format+6 | 100/100 | 13 days ago | |
| TryGhost/Ghoste2e/AGENTS.md · 55k | AGENTS.md | setupteststylearch+2 | 100/100 | today | |
| n8n-io/n8npackages/@n8n/agents/AGENTS.md · 201k | AGENTS.md | buildteststylearch+3 | 100/100 | 14 days ago | |
| duckduckgo/content-scope-scriptsspecial-pages/AGENTS.md · 70 | AGENTS.md | buildteststylearch+3 | 100/100 | 14 days ago | |
| aaif-goose/gooseAGENTS.md · 53k | AGENTS.md | setupbuildtestlint-format+7 | 100/100 | 8 days ago | |
| mui/material-uiAGENTS.md · 99k | AGENTS.md | setupbuildtestlint-format+9 | 100/100 | 14 days ago |
A badge carrying the measured quality of the strongest agent config file in this repository, out of 100. It reads from this index every time somebody loads your page, so it changes when the measurement changes and there is nothing to keep up to date. Free, no account, and the value is not something you or we can set by hand.
[](https://rulestack.kynth.studio/configs/bytedance-deer-flow-backend-packages-harness-deerflow-extensions-agents)Would rather not hotlink us? Every badge is also served in shields.io’s endpoint schema, so shields renders the image and your readers never talk to our domain:
Published by Toolproof, the masthead over this index and eight others. The method behind the number is at toolproof.kynth.studio/methodology, and the whole thing is readable as JSON with no key at /api.