deer-flow/AGENTS.md
Nan Gao 1f792d0f4b
feat(extensions): add middleware plugin foundation (#4636)
* feat(extensions): add middleware plugin foundation

* fix(extensions): stop config resolution from masking extension loading

`create_app()` resolved the configured plugin list inside the fail-open
guard around `load_extensions()`. CI has no `config.yaml` (gitignored and
never generated by the workflow), so `get_app_config()` raised
`FileNotFoundError` there and was swallowed as an extension failure --
`load_extensions()` never ran at all, and the four `create_app()` tests in
`test_extension_app_loading.py` passed locally but failed on every runner.

Resolve the plugin list before the guard. Only an absent `config.yaml` is
tolerated, mirroring `_resolve_trace_enabled_for_app_construction()`:
`create_app()` runs at import time, and lifespan still performs strict
config loading before serving. A `config.yaml` that exists but fails to
parse or validate now propagates instead of being reported as an extension
failure -- reporting it as the latter silently dropped a `required: true`
extension rather than failing the boot.

Make the tests config-independent with an autouse `stub_app_config`
fixture, following the existing pattern in `test_gateway_lifespan_shutdown.py`,
and cover both new branches of the config-resolution boundary.

* fix(extensions): bind the run's extension snapshot through subagent delegation

The lead-agent path resolves one immutable loaded-extension snapshot per run
and binds it through task-store allocation and graph construction, but the
subagent path re-read the process-wide singleton at execution time. In
production both are the same object, yet a `set_loaded_extensions()` between
the lead run's start and a subagent's execution (test teardown, a future
hot-reload path) would let one run mix two extension generations — exactly what
the documented invariant exists to prevent.

The graph-build binding is a ContextVar scoped to synchronous construction, so
it has already exited by the time a tool delegates; the snapshot has to travel
through runtime context instead. The run worker publishes it under the
host-internal `EXTENSION_SNAPSHOT_CONTEXT_KEY` (written after the caller merge,
popped when the run has none, so a caller-supplied value is never
authoritative), `task_tool` reads it back through the type-checking
`resolve_run_extensions()`, and `SubagentExecutor` binds it at construction.

Callers outside the Gateway run path — embedded `DeerFlowClient`, standalone
LangGraph Server — install no snapshot and keep the existing
`get_loaded_extensions()` fallback.

* refactor(extensions): defer the ordering table by call, not by a lying tuple

`CORE_ORDERING_CONSTRAINTS` was a `tuple` subclass that overrode only
`__iter__` and resolved into a class-level `_resolved` side channel. A tuple
cannot populate its own storage after construction, so the instance stayed the
empty tuple it was built as: `len()` was 0, `bool()` was False, `in` was always
False, indexing raised, slicing and `reversed()` came back empty, and it
compared unequal to the plain tuples tests substitute for it — all while
iteration yielded the real constraints. Only `assert_ordering` consumed it, and
only by iterating, so the split went unnoticed.

The sibling `_AnchorTable(dict)` uses the same idea soundly because dict is
mutable: `self.update()` fills the real storage, making every inherited
operation correct. That trick does not survive the port to an immutable type.

Replace it with `core_ordering_constraints()`, matching how `stack.py` defers
the same kind of table via `_anchors()`. The deferral is kept — it is about
dependency direction, not just cycles: `extensions/` is the layer the
middleware layer calls into, so a module-scope `agents.middlewares` import here
points the dependency backwards and closes a cycle as soon as any middleware
imports something under `extensions/` at module level. Resolution stays at
`assert_ordering` time, which already runs inside the middleware builder.

Tests pin both halves: the returned value is a plain tuple whose len/bool/
membership/indexing/reversal/equality agree with iteration, and a subprocess
probe asserts importing `extensions.ordering` does not load the middleware
layer while calling the function does.
2026-08-04 22:33:26 +08:00

9.6 KiB

AGENTS.md

This file provides guidance to AI coding agents (Claude Code, Codex, and others) when working with code in this repository. It is the source of truth; the sibling CLAUDE.md imports it via @AGENTS.md.

It is the monorepo orientation layer: it maps the whole repo and points to the module guides that own the depth. For anything inside a module, read that module's guide rather than expecting full detail here:

  • backend/AGENTS.md — backend depth: harness/app split, agent & middleware chain, sandbox, MCP, skills, memory, IM channels, persistence/migrations, config system, test layout.
  • frontend/AGENTS.md — frontend depth: Next.js App Router layout, thread/streaming data flow, code style, commands.

What is DeerFlow

DeerFlow is a LangGraph-based AI super-agent system with a full-stack architecture. The backend runs a "super agent" with sandboxed execution, persistent memory, subagent delegation, and extensible tools (built-in, MCP, community), all per-thread isolated. The frontend is a Next.js chat UI. External IM platforms (Feishu, Slack, Telegram, Discord, DingTalk) bridge into the same agent through the Gateway.

Service Topology

A single make dev / Docker stack runs four cooperating services:

Service Port Role
Nginx 2026 Unified reverse-proxy entry point — open this in the browser
Gateway API 8001 FastAPI REST API + embedded LangGraph-compatible agent runtime
Frontend 3000 Next.js web interface
Provisioner 8002 Optional — only when sandbox is configured for provisioner/K8s mode

Nginx is the single public entry: it serves the frontend and proxies /api/langgraph/* to the Gateway's LangGraph runtime, rewriting it to Gateway's native /api/* routes; all other /api/* go straight to the Gateway REST routers. See backend/AGENTS.md for the runtime and router detail. It compresses HTML and configured textual assets, while deliberately leaving SSE, fonts, images, audio, and video uncompressed at the proxy layer.

Both compose files publish that entry as "${BIND_HOST:-127.0.0.1}:${PORT:-2026}:2026"loopback by default, matching the README's documented deployment model. A bare "${PORT}:2026" binds 0.0.0.0, which does not. Nginx itself listens default_server on IPv4+IPv6 and the Gateway binds 0.0.0.0:8001 inside the container on purpose — both are container- internal; the published nginx port is the entire external surface, and the Gateway's 8001 is deliberately not published. Any new published port needs an explicit bind address; backend/tests/test_compose_default_bind_host.py pins this for every service in both compose files.

Repository Map

deer-flow/
├── Makefile                        # Root orchestration: drives the full stack (dev/start/stop, docker, setup)
├── config.example.yaml             # Template → copy to config.yaml (gitignored) at repo root
├── extensions_config.example.json  # Template → copy to extensions_config.json (gitignored): MCP servers + skills
├── backend/                        # Python backend — see backend/AGENTS.md
│   ├── Makefile                    # Per-module backend commands (dev, gateway, test, lint, migrate-rev)
│   ├── packages/extension-api/     # deerflow-extension-api package (import: deerflow_extension_api.*) — public extension contract
│   ├── packages/harness/           # deerflow-harness package (import: deerflow.*) — agent framework
│   └── app/                        # FastAPI Gateway + IM channels (import: app.*)
├── frontend/                       # Next.js frontend (pnpm) — see frontend/AGENTS.md
├── docker/                         # docker-compose files, nginx config, provisioner
├── skills/                         # Agent skills: public/ (committed), custom/ (gitignored)
│                                    # Managed integration skill packs are global at .deer-flow/integrations/skills/{provider}/
│                                    # Integration credentials and enabled state remain per-user
├── contracts/                      # Cross-component JSON contracts (e.g. subagent status, skill review)
├── scripts/                        # Root orchestration scripts invoked by the Makefile (check, configure, doctor, support_bundle, serve, nginx, docker, deploy, setup_wizard)
├── tests/                          # Root-level tests (currently tests/skills/ — public skill tests)
└── docs/                           # Cross-cutting docs, plans, and design notes

Third-party extensions are loaded from a top-level plugins: list in config.yaml (operator-controlled on purpose — that list causes code to be imported, so it is deliberately kept out of the API-writable extensions_config.json). See the Extension System section in backend/AGENTS.md.

Runtime config lives at the repo root: copy config.example.yamlconfig.yaml (main app config) and extensions_config.example.jsonextensions_config.json (MCP servers + skills). Both real files are gitignored and may be edited at runtime via the Gateway API. Config schema and resolution order are documented in backend/AGENTS.md.

Skill quality review note:

  • skills/public/skill-reviewer/ is the built-in read-only skill quality reviewer. It uses the harness-layer review_skill_package tool and contracts in contracts/skill_review/. Model-visible review data is compact and tag-neutralized; full raw payloads stay in tool artifacts. See backend/AGENTS.md for the non-activation, SkillScan, and skill-creator ownership boundaries.

Scheduled-task note:

  • The scheduled-task MVP adds a workspace page at /workspace/scheduled-tasks plus a background scheduler service gated by config.yaml -> scheduler.enabled.
  • Scheduled background runs are intentionally non-interactive: they execute through the normal run lifecycle, but the lead-agent toolset excludes ask_clarification when context.non_interactive=true. The key is honored only for internally-authenticated callers (the scheduler launch path); client-supplied context.non_interactive is dropped.

Commands: Root vs. Module

Root make targets drive the whole stack (run from the repo root):

make setup       # Interactive setup wizard (recommended for new users)
make doctor      # Check configuration and system requirements
make support-bundle  # Generate redacted troubleshooting summary, AI issue draft, and optional zip
make config      # Generate local config files from the examples
make check       # Check that required tools are installed
make install     # Install all dependencies (frontend + backend + pre-commit hooks)
make dev         # Start all services with hot-reload (Gateway + Frontend + Nginx)
make start       # Start all services in production mode (local, optimized)
make stop        # Stop all running services
make up / down   # Build/stop the production Docker stack (browser at localhost:2026)
make docker-start / docker-stop / docker-logs   # Docker development environment

Run make help for the full list.

Per-module commands drive a single module (run inside that module):

# Backend (see backend/AGENTS.md for the full set)
cd backend && make dev        # Gateway API with reload (port 8001)
cd backend && make test       # Backend test suite
cd backend && make lint       # ruff check
cd backend && make format     # ruff format

# Frontend (see frontend/AGENTS.md for the full set)
cd frontend && pnpm dev       # Dev server with Turbopack (port 3000)
cd frontend && pnpm check     # Lint + type check (run before committing)
cd frontend && pnpm test      # Unit tests

Rule of thumb: root make = the full application; backend/Makefile and frontend/ (pnpm) = per-module work.

Host-side pnpm consumers, including the root/frontend Makefiles and local diagnostic scripts, must run through scripts/pnpm.py. The runner preserves direct pnpm/pnpm.cmd priority, falls back to corepack pnpm, and is invoked from frontend/ so Corepack honors the package-manager version pinned by that project.

Where to Go Next

Cross-Cutting Conventions

These apply repo-wide; module guides own the module-specific detail.

  • Documentation update policy — keep docs in sync with code: update README.md for user-facing changes and the relevant AGENTS.md for development/architecture changes in the same change set.
  • Test-driven development — features and bug fixes ship with tests. Backend tests live in backend/tests/ (TDD is mandatory there; see backend/AGENTS.md); frontend tests live in frontend/tests/.
  • Format before pushing — run make format (backend) / pnpm check (frontend). Backend CI enforces ruff format --check, so formatting must be clean before a push.