Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
ba5caeb
Add Monocle tracing
imohammedansari Jul 8, 2026
60559dd
Merge branch 'main' into monocle-instrumentation
imohammedansari Jul 9, 2026
39b4d49
Config-gate Monocle telemetry in the Gateway lifespan
imohammedansari Jul 10, 2026
a97ff2c
Merge branch 'main' into monocle-instrumentation
imohammedansari Jul 10, 2026
745409c
Clarify Monocle/Langfuse single-provider guidance
imohammedansari Jul 10, 2026
803be48
Address review: optional extra, exporter validation, off-box warning,…
imohammedansari Jul 11, 2026
c2910f2
docs: align Monocle README section with the other tracing providers
imohammedansari Jul 11, 2026
c1f8bff
Address review: verified Langfuse coexistence, lifespan test, scope docs
imohammedansari Jul 11, 2026
232fd03
Merge branch 'main' of https://github.com/bytedance/deer-flow into mo…
imohammedansari Jul 11, 2026
93f968d
docs: reword Monocle intro sentence
imohammedansari Jul 11, 2026
cb165f8
Merge branch 'main' into monocle-instrumentation
WillemJiang Jul 11, 2026
e0122c9
fix(tests): run the import-time regression in a subprocess
imohammedansari Jul 11, 2026
ec30867
Merge branch 'main' into monocle-instrumentation
imohammedansari Jul 11, 2026
6c36548
Address review: console warning scope, embedded hint, honest naming, …
imohammedansari Jul 13, 2026
96fd6ec
Merge branch 'main' into monocle-instrumentation
imohammedansari Jul 13, 2026
3ac4310
fix(tests): pin the factory logger level in the embedded-hint tests
imohammedansari Jul 14, 2026
2920b60
Address review: co-export disclosure, lifespan failure test, exporter…
imohammedansari Jul 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,6 @@ config.yaml.bak
/frontend/playwright-report/
.gstack/
.worktrees

# Monocle agent-observability trace output (local file exporter)
.monocle/
23 changes: 19 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ DeerFlow has newly integrated the intelligent search and crawling toolset indepe
- [IM Channels](#im-channels)
- [LangSmith Tracing](#langsmith-tracing)
- [Langfuse Tracing](#langfuse-tracing)
- [Using Both Providers](#using-both-providers)
- [Monocle Tracing](#monocle-tracing)
- [Using Multiple Providers](#using-multiple-providers)
- [From Deep Research to Super Agent Harness](#from-deep-research-to-super-agent-harness)
- [Core Features](#core-features)
- [Skills \& Tools](#skills--tools)
Expand Down Expand Up @@ -593,11 +594,25 @@ If you are using a self-hosted Langfuse instance, set `LANGFUSE_BASE_URL` to you

These are injected into `RunnableConfig.metadata` at the graph invocation root for both the gateway path (`runtime/runs/worker.py::run_agent`) and the embedded path (`client.py::DeerFlowClient.stream`), so any LangChain-compatible callback can read them. Set `DEER_FLOW_ENV` (or `ENVIRONMENT`) to tag traces by deployment environment.

#### Using Both Providers
#### Monocle Tracing

If both LangSmith and Langfuse are enabled, DeerFlow attaches both tracing callbacks and reports the same model activity to both systems.
DeerFlow also supports [Monocle](https://github.com/monocle2ai/monocle), an OpenTelemetry-based tracer for agentic applications. It records each run end-to-end: LLM calls, agent steps, and tool and MCP invocations, with their inputs, outputs, timings, and token counts.

If a provider is explicitly enabled but missing required credentials, or if its callback fails to initialize, DeerFlow fails fast when tracing is initialized during model creation and the error message names the provider that caused the failure.
Add the following to your `.env` file:

```bash
MONOCLE_TRACING=true
MONOCLE_EXPORTERS=file # file, console, okahu, s3, blob, gcs (default: file)
OKAHU_API_KEY=okh_xxxxxxxx # required only for the `okahu` exporter
```

Each run writes one trace file to `.monocle/`; open it in the [Monocle VS Code extension](https://marketplace.visualstudio.com/items?itemName=OkahuAI.monocle-apptrace) to inspect the span timeline and token counts. Connect to [Okahu](https://www.okahu.ai), an agent-observability platform, to analyze traces across runs and run trace-based and agentic evaluations (via the `okahu` exporter).

Traces capture span inputs and outputs verbatim — prompts, tool arguments, and model responses — plus token usage and timings. The `file` exporter keeps them on local disk and never rotates or cleans them up, so prune `.monocle/` periodically; the remote exporters (`okahu`, `s3`, `blob`, `gcs`) send that same data off-box, so enable only destinations you trust. Monocle is initialized once at Gateway startup: a configuration error (unknown exporter, missing `OKAHU_API_KEY`) is logged there and tracing stays off until the Gateway restarts.

#### Using Multiple Providers

LangSmith and Langfuse attach as LangChain callbacks, so you can enable both and DeerFlow reports each run to both. If an enabled provider is missing required credentials or fails to initialize, DeerFlow fails fast and names it. Monocle uses a global OpenTelemetry provider rather than a callback; Langfuse shares that provider, so all three can run together. Because both span processors sit on the same shared provider, Monocle's exporters also see Langfuse's spans when both are enabled.

For Docker deployments, tracing is disabled by default. Set `LANGSMITH_TRACING=true` and `LANGSMITH_API_KEY` in your `.env` to enable it.

Expand Down
6 changes: 6 additions & 0 deletions backend/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -711,6 +711,12 @@ LangSmith and Langfuse are both supported. The wiring lives in two layers:

Returns `{}` when Langfuse is not in the enabled providers — LangSmith-only deployments are unaffected. Set `DEER_FLOW_ENV` (or `ENVIRONMENT`) to tag traces by deployment environment. Tests live in `tests/test_tracing_factory.py`, `tests/test_tracing_metadata.py`, `tests/test_worker_langfuse_metadata.py`, `tests/test_client_langfuse_metadata.py`, and `tests/test_subagent_executor.py::TestSubagentTracingWiring`.

**Monocle telemetry** is a third provider, structurally unlike LangSmith/Langfuse. It is **not** a LangChain callback: `tracing/monocle.py::setup_monocle_tracing_if_enabled()` calls `monocle_apptrace.setup_monocle_telemetry()` once, which installs a **process-global OTel `TracerProvider`**, patches span serialization, and auto-instruments the openai/langchain/langgraph clients. Because that is a one-time, process-global side effect (not a per-run callback), it is initialized from the **Gateway lifespan** (`app/gateway/app.py`) — never from `build_tracing_callbacks()` — and it is **off by default**. The setup call was deliberately moved out of `agents/__init__.py`, so `import deerflow.agents` must never start tracing (pinned by `tests/test_monocle_tracing.py::test_no_import_time_setup`). The Gateway lifespan is the **sole call site** (pinned by `test_gateway_lifespan_initializes_monocle`), so unlike LangSmith/Langfuse — which attach at the graph roots and cover every path — the embedded `DeerFlowClient` and the TUI are not instrumented; embedded users who want Monocle traces call `setup_monocle_tracing_if_enabled()` themselves before running the agent.

Unlike the Langfuse metadata above, DeerFlow injects **no** per-run fields into Monocle traces — the only attribute it sets is `workflow_name="deer-flow"`; every span attribute (`span.type`, `entity.*`, token usage, span inputs/outputs, `scope.agentic.session`) is produced by Monocle's own metamodel and auto-instrumentation, so there is no DeerFlow trace-attribute layer to maintain here.

Config is env-driven like the others — `MonocleTracingConfig`, built in `get_tracing_config()` and gated by `is_monocle_tracing_enabled()`. `MONOCLE_TRACING` enables it; `MONOCLE_EXPORTERS` selects exporters (default `file` → trace JSON in `.monocle/`; also `console`, `okahu`, `s3`, `blob`, `gcs`, where `okahu` requires `OKAHU_API_KEY`). `setup_monocle_tracing_if_enabled()` stays a thin wrapper on purpose: `monocle_apptrace` already guards duplicate setup (`instrumentor.py::check_duplicate_setup`) and never force-overrides an existing global provider, so the wrapper only gates on config. Coexistence with Langfuse (v4, also OTel-based) is **verified**: whichever library initializes second reuses the existing global `TracerProvider` and attaches its own span processor, so neither side loses spans (pinned by `test_coexists_with_langfuse`). Both processors see all spans, so Monocle's exporters also capture Langfuse's spans when both are enabled. (LangSmith is a plain callback and coexists trivially.) Tests: `tests/test_monocle_tracing.py`.

### Config Schema

**`config.yaml`** key sections:
Expand Down
11 changes: 11 additions & 0 deletions backend/app/gateway/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
from app.gateway.trace_middleware import TraceMiddleware, resolve_trace_enabled
from deerflow.config import app_config as deerflow_app_config
from deerflow.logging_config import DEFAULT_LOG_DATE_FORMAT, DEFAULT_LOG_FORMAT, configure_logging
from deerflow.tracing.monocle import setup_monocle_tracing_if_enabled
from deerflow.uploads.manager import cleanup_stale_upload_staging_files

AppConfig = deerflow_app_config.AppConfig
Expand Down Expand Up @@ -189,6 +190,16 @@ async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
config = get_gateway_config()
logger.info(f"Starting API Gateway on {config.host}:{config.port}")

# Agent observability (Monocle). Off by default; enabled with
# MONOCLE_TRACING. Initialized here at startup — not at import time — so a
# plain `import deerflow.agents` never installs a process-global tracer.
# Unlike LangSmith/Langfuse, whose validation failures abort the agent run,
# a bad Monocle config only logs: the Gateway keeps serving without tracing.
try:
setup_monocle_tracing_if_enabled()
Comment thread
WillemJiang marked this conversation as resolved.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Embedded / TUI / CLI path gets no Monocle tracing.

LangSmith and Langfuse are attached at the graph invocation root for both runtime/runs/worker.py::run_agent (this gateway path) and client.py::DeerFlowClient.stream (embedded). Monocle is initialized only here in the Gateway lifespan, so DeerFlowClient (programmatic library use) and the deerflow TUI get no traces even with MONOCLE_TRACING=true (confirmed: no monocle wiring under client.py or deerflow/tui/).

That's a real asymmetry vs the other two providers — at minimum call it out in AGENTS.md (embedded/TUI users must call setup_monocle_tracing_if_enabled() themselves, or accept that only the Gateway is instrumented).

Also: this lifespan wiring — the one line that makes the feature work — has no test asserting it's invoked during startup. A focused test monkeypatching the helper and asserting it's called from the lifespan would close that gap.

except Exception: # observability must never break startup
logger.exception("Monocle tracing setup failed; continuing without it")
Comment on lines +198 to +201

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Graceful-degrade vs. fail-fast asymmetry — worth documenting. This except Exception swallows the ValueError from monocle.validate() (bad exporter / missing OKAHU_API_KEY) and the RuntimeError for a missing extra, so a misconfigured Monocle setup logs at ERROR and continues without tracing — the Gateway stays up. That's a deliberate contrast with LangSmith/Langfuse, where build_tracing_callbacks() -> validate_enabled_tracing_providers() raises and aborts the agent run.

The behavior is internally consistent with "cannot break startup", but the asymmetry isn't called out in backend/AGENTS.md. A one-line note there (Monocle misconfiguration degrades gracefully rather than failing fast) would make the intent explicit instead of discovered.


# Pre-warm tiktoken encoding cache so the first memory-injection request
# never blocks on the BPE data download (which hits an OpenAI/Azure URL
# that may be unreachable in restricted networks — see issue #3402).
Expand Down
2 changes: 2 additions & 0 deletions backend/packages/harness/deerflow/config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
get_enabled_tracing_providers,
get_explicitly_enabled_tracing_providers,
get_tracing_config,
is_monocle_tracing_enabled,
is_tracing_enabled,
validate_enabled_tracing_providers,
)
Expand All @@ -27,6 +28,7 @@
"get_tracing_config",
"get_explicitly_enabled_tracing_providers",
"get_enabled_tracing_providers",
"is_monocle_tracing_enabled",
"is_tracing_enabled",
"validate_enabled_tracing_providers",
]
51 changes: 51 additions & 0 deletions backend/packages/harness/deerflow/config/tracing_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,47 @@ def validate(self) -> None:
raise ValueError(f"Langfuse tracing is enabled but required settings are missing: {', '.join(missing)}")


# Manual mirror of monocle_apptrace's supported exporters, kept local so a typo
# fails at startup with a clear message instead of an opaque upstream error.
# Update this tuple when a monocle_apptrace bump adds or renames an exporter.
_MONOCLE_EXPORTERS = ("file", "console", "okahu", "s3", "blob", "gcs")
Comment thread
WillemJiang marked this conversation as resolved.


class MonocleTracingConfig(BaseModel):
"""Configuration for Monocle telemetry."""

enabled: bool = Field(...)
exporters: str = Field(...)
okahu_api_key: str | None = Field(...)

@property
def is_enabled(self) -> bool:
# Unlike the siblings' is_configured, no credential check here: that is
# exporter-dependent and lives in validate(), run at Gateway startup.
return self.enabled

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is_configured diverges from the LangSmith/Langfuse pattern.

LangSmith/Langfuse fold credential presence into is_configured (self.enabled and bool(self.api_key)); here it's just self.enabled, so is_monocle_tracing_enabled() returns True even for a config validate() will reject (e.g. okahu without OKAHU_API_KEY). Validation is deferred to setup, where it's caught+logged rather than surfaced as "not enabled."

Defensible as a composite-validation choice, but a one-line comment explaining why Monocle's is_configured is intentionally coarser would stop a future reader from "fixing" it to match the others.


@property
def exporter_list(self) -> list[str]:
"""The configured exporters, parsed once so validation and setup agree."""
return [e.strip() for e in self.exporters.split(",") if e.strip()]

def validate(self) -> None:
if not self.enabled:
return
selected = self.exporter_list
unknown = [e for e in selected if e not in _MONOCLE_EXPORTERS]
if unknown:
raise ValueError(f"MONOCLE_EXPORTERS has unknown exporter(s): {', '.join(unknown)}. Allowed: {', '.join(_MONOCLE_EXPORTERS)}.")
if "okahu" in selected and not self.okahu_api_key:
raise ValueError("Monocle 'okahu' exporter is selected but OKAHU_API_KEY is not set.")


class TracingConfig(BaseModel):
"""Tracing configuration for supported providers."""

langsmith: LangSmithTracingConfig = Field(...)
langfuse: LangfuseTracingConfig = Field(...)
monocle: MonocleTracingConfig = Field(...)

@property
def is_configured(self) -> bool:
Expand Down Expand Up @@ -125,6 +161,11 @@ def get_tracing_config() -> TracingConfig:
secret_key=_first_env_value("LANGFUSE_SECRET_KEY"),
host=_first_env_value("LANGFUSE_BASE_URL") or "https://cloud.langfuse.com",
),
monocle=MonocleTracingConfig(
enabled=_env_flag_preferred("MONOCLE_TRACING"),
exporters=_first_env_value("MONOCLE_EXPORTERS") or "file",
okahu_api_key=_first_env_value("OKAHU_API_KEY"),
),
)
return _tracing_config

Expand All @@ -149,6 +190,16 @@ def is_tracing_enabled() -> bool:
return get_tracing_config().is_configured


def is_monocle_tracing_enabled() -> bool:
"""Whether Monocle OTel observability is enabled (via ``MONOCLE_TRACING``).

Kept separate from :func:`get_enabled_tracing_providers` because Monocle is a
process-global instrumentor activated at startup, not a per-run LangChain
callback.
"""
return get_tracing_config().monocle.is_enabled


def reset_tracing_config() -> None:
"""Discard the cached :class:`TracingConfig` so the next call rebuilds it.

Expand Down
2 changes: 2 additions & 0 deletions backend/packages/harness/deerflow/tracing/__init__.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
from .factory import build_tracing_callbacks
from .metadata import build_langfuse_trace_metadata, inject_langfuse_metadata
from .monocle import setup_monocle_tracing_if_enabled

__all__ = [
"build_langfuse_trace_metadata",
"build_tracing_callbacks",
"inject_langfuse_metadata",
"setup_monocle_tracing_if_enabled",
]
11 changes: 11 additions & 0 deletions backend/packages/harness/deerflow/tracing/factory.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
from __future__ import annotations

import logging
from typing import Any

from deerflow.config import (
get_enabled_tracing_providers,
get_tracing_config,
is_monocle_tracing_enabled,
validate_enabled_tracing_providers,
)
from deerflow.tracing.monocle import is_monocle_setup_completed

logger = logging.getLogger(__name__)


def _create_langsmith_tracer(config) -> Any:
Expand All @@ -32,6 +37,12 @@ def _create_langfuse_handler(config) -> Any:
def build_tracing_callbacks() -> list[Any]:
"""Build callbacks for all explicitly enabled tracing providers."""
validate_enabled_tracing_providers()
# Monocle is not a callback provider; this per-run path is just where an
# embedded process that skipped Gateway-lifespan setup can be told about it.
if is_monocle_tracing_enabled() and not is_monocle_setup_completed():
logger.debug(
"MONOCLE_TRACING is set but Monocle is not initialized in this process — only the Gateway lifespan runs setup automatically; embedded/TUI callers must call deerflow.tracing.setup_monocle_tracing_if_enabled() themselves."
)
enabled_providers = get_enabled_tracing_providers()
if not enabled_providers:
return []
Expand Down
69 changes: 69 additions & 0 deletions backend/packages/harness/deerflow/tracing/monocle.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
"""Monocle telemetry: initialized once from the Gateway lifespan when ``MONOCLE_TRACING`` is set."""

from __future__ import annotations

import logging

from deerflow.config import (
get_enabled_tracing_providers,
get_tracing_config,
is_monocle_tracing_enabled,
)

logger = logging.getLogger(__name__)

# Read by build_tracing_callbacks() to hint embedded/TUI processes that
# enabled MONOCLE_TRACING but never ran the Gateway-lifespan setup.
_setup_completed = False


def is_monocle_setup_completed() -> bool:
"""Whether :func:`setup_monocle_tracing_if_enabled` ran in this process."""
return _setup_completed


def setup_monocle_tracing_if_enabled() -> bool:
"""Initialize Monocle telemetry when ``MONOCLE_TRACING`` is enabled; a no-op otherwise.

``monocle_apptrace.setup_monocle_telemetry()`` is idempotent, so this stays a thin,
config-gated wrapper. Returns ``True`` when enabled.
"""
if not is_monocle_tracing_enabled():
return False

monocle = get_tracing_config().monocle
# Fail fast on an unknown MONOCLE_EXPORTERS value or a missing OKAHU_API_KEY,
# with a clear message, before instrumenting. Validated here (not in the
# per-run callback path) so a config typo never breaks agent runs.
monocle.validate()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Embedded/TUI path gets no signal that Monocle is a no-op. Because validate() lives here (startup-only wrapper) rather than in validate_enabled_tracing_providers() (the per-run path), the embedded DeerFlowClient and TUI — which never hit the Gateway lifespan — neither validate nor set up Monocle. A user who sets MONOCLE_TRACING=true in embedded mode gets silently nothing, with no log.

This matches the docs (embedded users call setup_monocle_tracing_if_enabled() themselves), but a debug-log when enabled-but-uninitialized would help surface it. Alternatively, wiring monocle.validate() into TracingConfig.validate_enabled() would at least fail-fast on a bad config in every path.


# Coexistence with Langfuse (v4, also OTel-based) is verified: whichever
# library initializes second reuses the existing global TracerProvider and
# attaches its own span processor, so neither side loses spans (see
# test_coexists_with_langfuse). Both processors see all spans, so Monocle's
# exporters also capture Langfuse's spans when both are enabled.
exporters = monocle.exporters

# `console` stays on local stdout, so only the remote exporters are flagged.
off_box = [e for e in monocle.exporter_list if e not in ("file", "console")]
if off_box:
# Monocle's exporters see every span on the shared global provider, so a
# co-enabled OTel provider's spans leave the box too.
langfuse_note = " Langfuse is also enabled and shares the global provider, so its spans are exported there as well." if "langfuse" in get_enabled_tracing_providers() else ""
logger.warning(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The README notes that "Monocle's exporters also see Langfuse's spans when both are enabled," but this runtime warning only mentions Monocle's own trace data. A user running Langfuse + MONOCLE_EXPORTERS=okahu won't be warned from the logs that Langfuse's spans also leave the box via the Okahu exporter. Consider extending the message when Langfuse is also enabled, e.g. ... including spans from co-enabled OTel providers such as Langfuse.

"Monocle is exporting trace data (prompts, tool inputs/outputs, completions) beyond the local .monocle/ file via: %s. Make sure that destination is trusted.%s",
", ".join(off_box),
langfuse_note,
)

try:
from monocle_apptrace import setup_monocle_telemetry
except ImportError as exc:
raise RuntimeError("MONOCLE_TRACING is enabled but monocle_apptrace is not installed. Install the 'monocle' extra: `uv sync --extra monocle` in backend/, or `pip install 'deerflow-harness[monocle]'`.") from exc

# monocle_exporters_list takes the comma-separated string as-is (monocle_apptrace's API).
setup_monocle_telemetry(workflow_name="deer-flow", monocle_exporters_list=exporters)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Security: spans capture prompts + tool I/O verbatim, with no DeerFlow-controlled scrubbing layer. Auto-instrumentation records span inputs/outputs — prompts, tool arguments (write_file body, bash command strings), and model responses. DeerFlow's existing tracing (tracing/metadata.py "never copies context") is careful about what it sends; Monocle captures at the SDK layer, which DeerFlow can't scrub. Request-scoped secrets are safe from bash args (they go through env=), but write_file/read_file/web_fetch I/O carries sensitive data and is captured unscrubbed.

Two suggestions: (1) log a loud startup warning whenever a non-file exporter is configured (e.g. okahu ships all of this off-box to an external collector — operators shouldn't be able to miss that); (2) document which DeerFlow data surfaces are/aren't captured. file-by-default + the config.example.yaml disclosure is a good baseline.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This call installs a process-global OTel TracerProvider and patches span serialization for the whole process, and it shares that provider with Langfuse v4. Coexistence is verified by test_coexists_with_langfuse, but that test reaches into OTel SDK privates (provider._active_span_processor._span_processors) and depends on Langfuse reusing the existing provider rather than replacing it. A Langfuse major bump or an OTel-SDK change could silently break span export for both providers.

The mitigation (off by default, optional extra) is sound - just flagging this as the one coupling to weigh against the risk:high label. If a public API to list a provider's span processors ever lands in the OTel SDK, swapping the private introspection for it would harden the test.

global _setup_completed
_setup_completed = True
logger.info("Monocle telemetry enabled (exporters=%s)", exporters)
return True
3 changes: 3 additions & 0 deletions backend/packages/harness/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ postgres = [
redis = ["redis>=5.0.0"]
pymupdf = ["pymupdf4llm>=0.0.17"]
boxlite = ["boxlite>=0.9.7"]
# Agent observability (Monocle). Optional so a default install stays free of the
# OpenTelemetry stack; only pulled in when MONOCLE_TRACING is used.
monocle = ["monocle_apptrace>=0.8.8"]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

monocle_apptrace>=0.8.8 pulls in opentelemetry-instrumentation 0.62b1 (a beta) plus rfc3986 as transitive deps (new in uv.lock). Betas can ship breaking changes between point releases. It's correctly gated behind the optional extra so default/minimal installs stay clean, but anyone opting into [monocle] gets a beta OTel instrumentation in their tree. Worth a note here, or a watch on the OTel instrumentation pin floor.


[build-system]
requires = ["hatchling"]
Expand Down
4 changes: 4 additions & 0 deletions backend/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ dependencies = [
postgres = ["deerflow-harness[postgres]"]
redis = ["deerflow-harness[redis]"]
discord = ["discord.py>=2.7.0"]
monocle = ["deerflow-harness[monocle]"]

[dependency-groups]
dev = [
Expand All @@ -37,6 +38,9 @@ dev = [
"pytest>=9.0.3",
"pytest-asyncio>=1.3.0",
"ruff>=0.14.11",
# Monocle tracer (also the deerflow-harness[monocle] extra); kept in the dev
# group so the tracing tests can import it without forcing it onto installs.
"monocle_apptrace>=0.8.8",
# redis is an optional runtime extra (deerflow-harness[redis]); pin it in the
# dev group so the stream-bridge tests can always import/exercise the redis
# bridge without forcing it onto production installs.
Expand Down
Loading
Loading