flightdeck-sensor 0.10.0__tar.gz → 0.11.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/PKG-INFO +12 -10
  2. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/README.md +9 -9
  3. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/flightdeck_sensor/__init__.py +35 -4
  4. flightdeck_sensor-0.11.0/flightdeck_sensor/interceptor/langgraph.py +1299 -0
  5. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/pyproject.toml +9 -1
  6. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/tests/unit/_lazy_patch_child.py +23 -0
  7. flightdeck_sensor-0.11.0/tests/unit/test_interceptor_langgraph.py +1433 -0
  8. flightdeck_sensor-0.11.0/tests/unit/test_interceptor_langgraph_runs.py +1420 -0
  9. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/tests/unit/test_lazy_patch.py +8 -4
  10. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/uv.lock +107 -6
  11. flightdeck_sensor-0.10.0/flightdeck_sensor/interceptor/langgraph.py +0 -347
  12. flightdeck_sensor-0.10.0/tests/unit/test_interceptor_langgraph.py +0 -383
  13. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/.gitignore +0 -0
  14. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/CLAUDE.md +0 -0
  15. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/Makefile +0 -0
  16. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/flightdeck_sensor/compat/__init__.py +0 -0
  17. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/flightdeck_sensor/compat/crewai_mcp.py +0 -0
  18. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/flightdeck_sensor/config.py +0 -0
  19. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/flightdeck_sensor/core/__init__.py +0 -0
  20. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/flightdeck_sensor/core/agent_id.py +0 -0
  21. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/flightdeck_sensor/core/context.py +0 -0
  22. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/flightdeck_sensor/core/errors.py +0 -0
  23. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/flightdeck_sensor/core/exceptions.py +0 -0
  24. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/flightdeck_sensor/core/import_hook.py +0 -0
  25. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/flightdeck_sensor/core/mcp_policy.py +0 -0
  26. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/flightdeck_sensor/core/policy.py +0 -0
  27. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/flightdeck_sensor/core/schemas.py +0 -0
  28. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/flightdeck_sensor/core/session.py +0 -0
  29. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/flightdeck_sensor/core/tool_capture.py +0 -0
  30. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/flightdeck_sensor/core/types.py +0 -0
  31. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/flightdeck_sensor/interceptor/__init__.py +0 -0
  32. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/flightdeck_sensor/interceptor/anthropic.py +0 -0
  33. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/flightdeck_sensor/interceptor/base.py +0 -0
  34. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/flightdeck_sensor/interceptor/claude_agent_sdk.py +0 -0
  35. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/flightdeck_sensor/interceptor/crewai.py +0 -0
  36. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/flightdeck_sensor/interceptor/litellm.py +0 -0
  37. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/flightdeck_sensor/interceptor/llama_index.py +0 -0
  38. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/flightdeck_sensor/interceptor/mcp.py +0 -0
  39. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/flightdeck_sensor/interceptor/mcp_identity.py +0 -0
  40. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/flightdeck_sensor/interceptor/openai.py +0 -0
  41. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/flightdeck_sensor/interceptor/openai_agents.py +0 -0
  42. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/flightdeck_sensor/interceptor/platform.py +0 -0
  43. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/flightdeck_sensor/interceptor/raw_views.py +0 -0
  44. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/flightdeck_sensor/provider.py +0 -0
  45. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/flightdeck_sensor/providers/__init__.py +0 -0
  46. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/flightdeck_sensor/providers/_access.py +0 -0
  47. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/flightdeck_sensor/providers/anthropic.py +0 -0
  48. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/flightdeck_sensor/providers/litellm.py +0 -0
  49. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/flightdeck_sensor/providers/openai.py +0 -0
  50. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/flightdeck_sensor/providers/protocol.py +0 -0
  51. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/flightdeck_sensor/py.typed +0 -0
  52. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/flightdeck_sensor/transport/__init__.py +0 -0
  53. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/flightdeck_sensor/transport/body_limit.py +0 -0
  54. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/flightdeck_sensor/transport/client.py +0 -0
  55. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/flightdeck_sensor/transport/retry.py +0 -0
  56. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/tests/__init__.py +0 -0
  57. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/tests/conftest.py +0 -0
  58. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/tests/unit/__init__.py +0 -0
  59. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/tests/unit/_child_process.py +0 -0
  60. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/tests/unit/_signal_child.py +0 -0
  61. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/tests/unit/test_agent_handoff.py +0 -0
  62. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/tests/unit/test_agent_id.py +0 -0
  63. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/tests/unit/test_agent_type_validation.py +0 -0
  64. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/tests/unit/test_anthropic_resource_patch.py +0 -0
  65. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/tests/unit/test_body_limit.py +0 -0
  66. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/tests/unit/test_compat_crewai_mcp.py +0 -0
  67. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/tests/unit/test_context.py +0 -0
  68. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/tests/unit/test_cross_agent_messages.py +0 -0
  69. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/tests/unit/test_custom_directives.py +0 -0
  70. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/tests/unit/test_deferred_stream_unread.py +0 -0
  71. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/tests/unit/test_egress_url_validation.py +0 -0
  72. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/tests/unit/test_errors.py +0 -0
  73. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/tests/unit/test_framework_attribution.py +0 -0
  74. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/tests/unit/test_identity_vectors.py +0 -0
  75. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/tests/unit/test_import_hook.py +0 -0
  76. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/tests/unit/test_init_defaults.py +0 -0
  77. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/tests/unit/test_interceptor.py +0 -0
  78. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/tests/unit/test_interceptor_claude_agent_sdk.py +0 -0
  79. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/tests/unit/test_interceptor_crewai.py +0 -0
  80. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/tests/unit/test_interceptor_litellm.py +0 -0
  81. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/tests/unit/test_interceptor_litellm_routes.py +0 -0
  82. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/tests/unit/test_interceptor_llama_index.py +0 -0
  83. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/tests/unit/test_interceptor_openai_agents.py +0 -0
  84. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/tests/unit/test_llm_event_enrichment.py +0 -0
  85. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/tests/unit/test_mcp_2x_paths.py +0 -0
  86. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/tests/unit/test_mcp_availability.py +0 -0
  87. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/tests/unit/test_mcp_identity.py +0 -0
  88. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/tests/unit/test_mcp_interceptor.py +0 -0
  89. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/tests/unit/test_mcp_policy_emission_enriched.py +0 -0
  90. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/tests/unit/test_mcp_policy_enforcement.py +0 -0
  91. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/tests/unit/test_mcp_policy_enforcement_all_paths.py +0 -0
  92. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/tests/unit/test_openai_beta_chat.py +0 -0
  93. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/tests/unit/test_openai_constructed_resources.py +0 -0
  94. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/tests/unit/test_openai_parse_responses.py +0 -0
  95. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/tests/unit/test_originating_event_id.py +0 -0
  96. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/tests/unit/test_patch.py +0 -0
  97. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/tests/unit/test_policy.py +0 -0
  98. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/tests/unit/test_policy_decision_summary.py +0 -0
  99. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/tests/unit/test_policy_directive_events.py +0 -0
  100. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/tests/unit/test_policy_emission_enriched.py +0 -0
  101. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/tests/unit/test_policy_events.py +0 -0
  102. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/tests/unit/test_prompt_capture.py +0 -0
  103. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/tests/unit/test_provider_enum.py +0 -0
  104. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/tests/unit/test_providers.py +0 -0
  105. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/tests/unit/test_raw_stream_capture.py +0 -0
  106. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/tests/unit/test_session.py +0 -0
  107. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/tests/unit/test_session_lifecycle_enrichment.py +0 -0
  108. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/tests/unit/test_signal_shutdown.py +0 -0
  109. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/tests/unit/test_stop_reason_errors.py +0 -0
  110. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/tests/unit/test_streaming_usage.py +0 -0
  111. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/tests/unit/test_token_cost_fields.py +0 -0
  112. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/tests/unit/test_tool_capture_destination.py +0 -0
  113. {flightdeck_sensor-0.10.0 → flightdeck_sensor-0.11.0}/tests/unit/test_transport.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: flightdeck-sensor
3
- Version: 0.10.0
3
+ Version: 0.11.0
4
4
  Summary: In-process agent observability sensor for Flightdeck
5
5
  Project-URL: Repository, https://github.com/flightdeckhq/flightdeck
6
6
  Author-email: Omri Arnon <omri.arnon@gmail.com>
@@ -24,6 +24,7 @@ Provides-Extra: dev
24
24
  Requires-Dist: anthropic>=0.86; extra == 'dev'
25
25
  Requires-Dist: claude-agent-sdk<0.3,>=0.2.168; extra == 'dev'
26
26
  Requires-Dist: crewai>=1.14; (python_version < '3.14') and extra == 'dev'
27
+ Requires-Dist: deepagents<0.8,>=0.7; extra == 'dev'
27
28
  Requires-Dist: httpx>=0.25; extra == 'dev'
28
29
  Requires-Dist: langchain-anthropic<2,>=1; extra == 'dev'
29
30
  Requires-Dist: langchain-mcp-adapters<0.4,>=0.3; extra == 'dev'
@@ -37,6 +38,7 @@ Requires-Dist: llama-index-tools-mcp>=0.1; extra == 'dev'
37
38
  Requires-Dist: mcp<2,>=1.28; extra == 'dev'
38
39
  Requires-Dist: mcpadapt>=0.0.6; extra == 'dev'
39
40
  Requires-Dist: mypy>=2.4; extra == 'dev'
41
+ Requires-Dist: nest-asyncio>=1.6; extra == 'dev'
40
42
  Requires-Dist: openai-agents<0.21,>=0.20; extra == 'dev'
41
43
  Requires-Dist: openai>=1.26; extra == 'dev'
42
44
  Requires-Dist: pytest-asyncio>=1.0; extra == 'dev'
@@ -317,7 +319,7 @@ is imported) only if your application imports it. `unpatch()` (and
317
319
  | litellm (router that aggregates many providers behind one function surface) | `patch_litellm_functions` | `litellm.completion`, `litellm.acompletion`, `litellm.text_completion`, `litellm.atext_completion`, `litellm.responses`, `litellm.aresponses` (each with `stream=True`), `litellm.embedding`, `litellm.aembedding` |
318
320
  | MCP (official Python `mcp` SDK, reached transitively by `langchain-mcp-adapters`, `llama-index-tools-mcp` and `mcpadapt`) | `patch_mcp_classes` | `mcp.client.session.ClientSession.initialize` on mcp 1.x / `.adopt` on mcp 2.x (server fingerprint capture, every 2.x handshake path), `.list_tools`, `.call_tool`, `.list_resources`, `.read_resource`, `.list_prompts`, `.get_prompt` (one `mcp_*` event type each), plus the stdio / SSE / streamable-HTTP / websocket transport client factories and mcp 2.x's `Client._build_session` for transport attribution. `call_tool` also enforces the MCP Protection Policy. Supported SDK range: `mcp>=1.28,<3`. |
319
321
  | CrewAI (sub-agent sessions) | `patch_crewai_classes` | `crewai.Agent.execute_task` / `.aexecute_task` — each Crew member's turn lands as a child session (`session_start` / `session_end`) with `parent_session_id`, `agent_role` taken from `Agent.role`, and incoming / outgoing message capture gated on `capture_prompts` |
320
- | LangGraph (sub-agent sessions) | `patch_langgraph_classes` | `langgraph.graph.StateGraph.add_node` — the registered node callable is wrapped so each node run lands as a child session with `agent_role` taken from the node name; every node is treated as agent-bearing unless narrowed with `init(langgraph_agent_node_pattern=...)`. Only a node whose action is a plain callable is wrapped: Runnable, `ToolNode` and compiled-subgraph nodes are not |
322
+ | LangGraph and Deep Agents (sub-agent sessions) | `patch_langgraph_classes` (`patch_langgraph_runs` patches the graph runs alone; `patch()` applies it on the import of `langgraph.pregel`, so a functional-API application is covered) | `langgraph.graph.StateGraph.add_node` — each node of a graph you build, a function or a Runnable (`RunnableLambda`, `RunnableCallable`, an LCEL pipe, your own `ToolNode`), lands as a child session per run with `agent_role` taken from the node name; every such node is treated as agent-bearing unless narrowed with `init(langgraph_agent_node_pattern=...)`. The nodes `create_agent` and `create_react_agent` register (model, tools, middleware and model hooks) and generator nodes are not wrapped. `langgraph.pregel.Pregel.stream` / `astream` — a graph run started inside another graph run or a child (a compiled graph used as a node, a graph or an agent invoked from a node or a tool, each Deep Agents `task` sub-agent) lands as one child session, `agent_role` the run's name (`create_agent(name=...)`, the Deep Agents sub-agent's name), else the node it runs in (unless that node is one of `create_agent`'s own steps), else `subgraph`; a top-level run opens none. Each child nests under the child it runs in. An interrupt closes the child normally; async runs post their child's start and end off the event loop |
321
323
  | LlamaIndex agent workflows (sub-agent sessions, `llama-index-core>=0.14`) | `patch_llama_index` | A span handler on LlamaIndex's root instrumentation dispatcher (no LlamaIndex method is replaced): each agent of a `FunctionAgent`, `ReActAgent`, `CodeActAgent` or `AgentWorkflow` run lands as a child session with `agent_role` the agent's name (the agent's model, tool and MCP calls stay on the sensor's session, as for CrewAI and LangGraph); an `AgentWorkflow` hand-off ends one child, records an `agent_handoff` signal on it and opens the next; a tool call the model wrote as text (ReAct, CodeAct) is reported once as a `tool_call`, its arguments and output under `capture_prompts` |
322
324
  | Claude Agent SDK (`claude-agent-sdk>=0.2.168,<0.3`) | `patch_claude_agent_sdk` | `query()` (however it was imported) and `ClaudeSDKClient` — each run lands as its own `claude_agent_sdk` session keyed by the Claude Code session id, with a `post_call` per assistant message (tokens, cache split, billing mode) and a `tool_call` per tool use, read from the SDK's message stream; a Claude Code plugin loaded into the run stays silent. `PreToolUse` and `UserPromptSubmit` callbacks added after the caller's hooks apply the run's directives (a stop denies the next tool call and blocks prompts; budget warnings reach the run) and the MCP Protection Policy. Install with `pip install flightdeck-sensor[claude-agent-sdk]`. |
323
325
  | OpenAI Agents SDK (`openai-agents>=0.6`, sub-agent sessions) | `patch_openai_agents` | `AgentRunner.run` / `.run_streamed` (so `Runner.run`, `run_sync` and `run_streamed`) and the `run` of the four guardrail classes — each agent that runs lands as a child session with `agent_role` taken from `Agent.name`, an agent used as a tool nests under the agent that called it, a hand-off posts `agent_handoff` on the agent handing off, a guardrail that fires posts `guardrail_tripwire` (names only), and the run's conversation id lands in the child's context as `gen_ai_conversation_id`. Model, tool and MCP calls stay on the sensor's session (the OpenAI and MCP interceptors meter them); a shutdown also stops the run when the next agent starts or hands off. Patched only when `agents` comes from the `openai-agents` distribution. Install with `pip install flightdeck-sensor[openai-agents]`. |
@@ -326,7 +328,7 @@ is imported) only if your application imports it. `unpatch()` (and
326
328
 
327
329
  Any framework that calls a model through the Anthropic or OpenAI SDK or
328
330
  litellm is metered and enforced through the interceptors above, with no
329
- wrap of its own: Deep Agents, Microsoft Agent Framework,
331
+ wrap of its own: Microsoft Agent Framework,
330
332
  Google ADK (on `LiteLlm`, Anthropic or OpenAI models), Pydantic AI and
331
333
  Strands Agents (on their Anthropic, OpenAI and LiteLLM models), Agno,
332
334
  Haystack, smolagents, Semantic Kernel, AutoGen, AG2 and DSPy on its
@@ -335,17 +337,17 @@ litellm engine. Each event names the framework in
335
337
 
336
338
  - **Sub-agent sessions planned**
337
339
  ([#185](https://github.com/flightdeckhq/flightdeck/issues/185)): Google
338
- ADK, Microsoft Agent Framework and Deep Agents (each agent and each
339
- `task` sub-agent).
340
+ ADK and Microsoft Agent Framework.
340
341
  - **LLM calls planned**
341
342
  ([#185](https://github.com/flightdeckhq/flightdeck/issues/185)): a DSPy
342
343
  callback adapter for its default engine, which has its own HTTP
343
344
  transport and bypasses the SDKs and litellm.
344
- - **No sub-agent sessions from `create_agent`**: the LangGraph
345
- interceptor wraps only nodes whose action is a plain callable.
346
- LangChain's `create_agent`, and Deep Agents built on it, register
347
- Runnable and `ToolNode` nodes, so their runs are one session (their LLM
348
- calls are still metered).
345
+ - **No sub-agent sessions from `create_agent`'s steps**: its model, tool
346
+ and middleware nodes are the agent's own steps, not sub-agents, so the
347
+ LangGraph interceptor leaves them alone. A top-level `create_agent`
348
+ run is one session (its LLM calls are still metered); a Deep Agents
349
+ run adds one child per `task` sub-agent run, and an agent run inside
350
+ another graph run is a child of its own.
349
351
  - **Structure through OpenTelemetry instead**: Pydantic AI and Strands
350
352
  Agents export GenAI spans that Flightdeck's OTLP receiver maps into
351
353
  observe-only child sessions. The sensor does not wrap them. Report a
@@ -251,7 +251,7 @@ is imported) only if your application imports it. `unpatch()` (and
251
251
  | litellm (router that aggregates many providers behind one function surface) | `patch_litellm_functions` | `litellm.completion`, `litellm.acompletion`, `litellm.text_completion`, `litellm.atext_completion`, `litellm.responses`, `litellm.aresponses` (each with `stream=True`), `litellm.embedding`, `litellm.aembedding` |
252
252
  | MCP (official Python `mcp` SDK, reached transitively by `langchain-mcp-adapters`, `llama-index-tools-mcp` and `mcpadapt`) | `patch_mcp_classes` | `mcp.client.session.ClientSession.initialize` on mcp 1.x / `.adopt` on mcp 2.x (server fingerprint capture, every 2.x handshake path), `.list_tools`, `.call_tool`, `.list_resources`, `.read_resource`, `.list_prompts`, `.get_prompt` (one `mcp_*` event type each), plus the stdio / SSE / streamable-HTTP / websocket transport client factories and mcp 2.x's `Client._build_session` for transport attribution. `call_tool` also enforces the MCP Protection Policy. Supported SDK range: `mcp>=1.28,<3`. |
253
253
  | CrewAI (sub-agent sessions) | `patch_crewai_classes` | `crewai.Agent.execute_task` / `.aexecute_task` — each Crew member's turn lands as a child session (`session_start` / `session_end`) with `parent_session_id`, `agent_role` taken from `Agent.role`, and incoming / outgoing message capture gated on `capture_prompts` |
254
- | LangGraph (sub-agent sessions) | `patch_langgraph_classes` | `langgraph.graph.StateGraph.add_node` — the registered node callable is wrapped so each node run lands as a child session with `agent_role` taken from the node name; every node is treated as agent-bearing unless narrowed with `init(langgraph_agent_node_pattern=...)`. Only a node whose action is a plain callable is wrapped: Runnable, `ToolNode` and compiled-subgraph nodes are not |
254
+ | LangGraph and Deep Agents (sub-agent sessions) | `patch_langgraph_classes` (`patch_langgraph_runs` patches the graph runs alone; `patch()` applies it on the import of `langgraph.pregel`, so a functional-API application is covered) | `langgraph.graph.StateGraph.add_node` — each node of a graph you build, a function or a Runnable (`RunnableLambda`, `RunnableCallable`, an LCEL pipe, your own `ToolNode`), lands as a child session per run with `agent_role` taken from the node name; every such node is treated as agent-bearing unless narrowed with `init(langgraph_agent_node_pattern=...)`. The nodes `create_agent` and `create_react_agent` register (model, tools, middleware and model hooks) and generator nodes are not wrapped. `langgraph.pregel.Pregel.stream` / `astream` — a graph run started inside another graph run or a child (a compiled graph used as a node, a graph or an agent invoked from a node or a tool, each Deep Agents `task` sub-agent) lands as one child session, `agent_role` the run's name (`create_agent(name=...)`, the Deep Agents sub-agent's name), else the node it runs in (unless that node is one of `create_agent`'s own steps), else `subgraph`; a top-level run opens none. Each child nests under the child it runs in. An interrupt closes the child normally; async runs post their child's start and end off the event loop |
255
255
  | LlamaIndex agent workflows (sub-agent sessions, `llama-index-core>=0.14`) | `patch_llama_index` | A span handler on LlamaIndex's root instrumentation dispatcher (no LlamaIndex method is replaced): each agent of a `FunctionAgent`, `ReActAgent`, `CodeActAgent` or `AgentWorkflow` run lands as a child session with `agent_role` the agent's name (the agent's model, tool and MCP calls stay on the sensor's session, as for CrewAI and LangGraph); an `AgentWorkflow` hand-off ends one child, records an `agent_handoff` signal on it and opens the next; a tool call the model wrote as text (ReAct, CodeAct) is reported once as a `tool_call`, its arguments and output under `capture_prompts` |
256
256
  | Claude Agent SDK (`claude-agent-sdk>=0.2.168,<0.3`) | `patch_claude_agent_sdk` | `query()` (however it was imported) and `ClaudeSDKClient` — each run lands as its own `claude_agent_sdk` session keyed by the Claude Code session id, with a `post_call` per assistant message (tokens, cache split, billing mode) and a `tool_call` per tool use, read from the SDK's message stream; a Claude Code plugin loaded into the run stays silent. `PreToolUse` and `UserPromptSubmit` callbacks added after the caller's hooks apply the run's directives (a stop denies the next tool call and blocks prompts; budget warnings reach the run) and the MCP Protection Policy. Install with `pip install flightdeck-sensor[claude-agent-sdk]`. |
257
257
  | OpenAI Agents SDK (`openai-agents>=0.6`, sub-agent sessions) | `patch_openai_agents` | `AgentRunner.run` / `.run_streamed` (so `Runner.run`, `run_sync` and `run_streamed`) and the `run` of the four guardrail classes — each agent that runs lands as a child session with `agent_role` taken from `Agent.name`, an agent used as a tool nests under the agent that called it, a hand-off posts `agent_handoff` on the agent handing off, a guardrail that fires posts `guardrail_tripwire` (names only), and the run's conversation id lands in the child's context as `gen_ai_conversation_id`. Model, tool and MCP calls stay on the sensor's session (the OpenAI and MCP interceptors meter them); a shutdown also stops the run when the next agent starts or hands off. Patched only when `agents` comes from the `openai-agents` distribution. Install with `pip install flightdeck-sensor[openai-agents]`. |
@@ -260,7 +260,7 @@ is imported) only if your application imports it. `unpatch()` (and
260
260
 
261
261
  Any framework that calls a model through the Anthropic or OpenAI SDK or
262
262
  litellm is metered and enforced through the interceptors above, with no
263
- wrap of its own: Deep Agents, Microsoft Agent Framework,
263
+ wrap of its own: Microsoft Agent Framework,
264
264
  Google ADK (on `LiteLlm`, Anthropic or OpenAI models), Pydantic AI and
265
265
  Strands Agents (on their Anthropic, OpenAI and LiteLLM models), Agno,
266
266
  Haystack, smolagents, Semantic Kernel, AutoGen, AG2 and DSPy on its
@@ -269,17 +269,17 @@ litellm engine. Each event names the framework in
269
269
 
270
270
  - **Sub-agent sessions planned**
271
271
  ([#185](https://github.com/flightdeckhq/flightdeck/issues/185)): Google
272
- ADK, Microsoft Agent Framework and Deep Agents (each agent and each
273
- `task` sub-agent).
272
+ ADK and Microsoft Agent Framework.
274
273
  - **LLM calls planned**
275
274
  ([#185](https://github.com/flightdeckhq/flightdeck/issues/185)): a DSPy
276
275
  callback adapter for its default engine, which has its own HTTP
277
276
  transport and bypasses the SDKs and litellm.
278
- - **No sub-agent sessions from `create_agent`**: the LangGraph
279
- interceptor wraps only nodes whose action is a plain callable.
280
- LangChain's `create_agent`, and Deep Agents built on it, register
281
- Runnable and `ToolNode` nodes, so their runs are one session (their LLM
282
- calls are still metered).
277
+ - **No sub-agent sessions from `create_agent`'s steps**: its model, tool
278
+ and middleware nodes are the agent's own steps, not sub-agents, so the
279
+ LangGraph interceptor leaves them alone. A top-level `create_agent`
280
+ run is one session (its LLM calls are still metered); a Deep Agents
281
+ run adds one child per `task` sub-agent run, and an agent run inside
282
+ another graph run is a child of its own.
283
283
  - **Structure through OpenTelemetry instead**: Pydantic AI and Strands
284
284
  Agents export GenAI spans that Flightdeck's OTLP receiver maps into
285
285
  observe-only child sessions. The sensor does not wrap them. Report a
@@ -78,9 +78,15 @@ if TYPE_CHECKING:
78
78
  from flightdeck_sensor.interceptor.langgraph import (
79
79
  patch_langgraph_classes as patch_langgraph_classes,
80
80
  )
81
+ from flightdeck_sensor.interceptor.langgraph import (
82
+ patch_langgraph_runs as patch_langgraph_runs,
83
+ )
81
84
  from flightdeck_sensor.interceptor.langgraph import (
82
85
  unpatch_langgraph_classes as unpatch_langgraph_classes,
83
86
  )
87
+ from flightdeck_sensor.interceptor.langgraph import (
88
+ unpatch_langgraph_runs as unpatch_langgraph_runs,
89
+ )
84
90
  from flightdeck_sensor.interceptor.litellm import SensorLitellm as SensorLitellm
85
91
  from flightdeck_sensor.interceptor.litellm import (
86
92
  patch_litellm_functions as patch_litellm_functions,
@@ -211,8 +217,12 @@ class _SDKTarget:
211
217
  # keyed on ``langgraph.graph``, the package StateGraph comes from:
212
218
  # ``langgraph`` itself is a namespace package shared with LangGraph's other
213
219
  # distributions, and a namespace package runs no code for the hook to see.
214
- # LlamaIndex is keyed on ``llama_index.core`` for the same reason
215
- # (``llama_index`` is a namespace package).
220
+ # It has a second target, ``langgraph.pregel``, which patches the graph runs
221
+ # alone: an application on LangGraph's functional API imports only that,
222
+ # and the import of ``langgraph.graph`` imports it first, so its patch
223
+ # imports nothing from ``langgraph.graph``. LlamaIndex is keyed on
224
+ # ``llama_index.core`` for the same reason as LangGraph (``llama_index`` is
225
+ # a namespace package).
216
226
  _SDK_TARGETS: tuple[_SDKTarget, ...] = (
217
227
  _SDKTarget(
218
228
  Provider.ANTHROPIC,
@@ -239,6 +249,13 @@ _SDK_TARGETS: tuple[_SDKTarget, ...] = (
239
249
  "patch_crewai_classes",
240
250
  "unpatch_crewai_classes",
241
251
  ),
252
+ _SDKTarget(
253
+ Provider.LANGGRAPH,
254
+ "langgraph.pregel",
255
+ "langgraph",
256
+ "patch_langgraph_runs",
257
+ "unpatch_langgraph_runs",
258
+ ),
242
259
  _SDKTarget(
243
260
  Provider.LANGGRAPH,
244
261
  "langgraph.graph",
@@ -969,8 +986,12 @@ def patch(
969
986
  callbacks: dict[str, Callable[[str], None]] = {}
970
987
  for t in selected:
971
988
  callbacks[t.module] = functools.partial(_patch_on_import, t, quiet)
972
- callbacks[t.interceptor_module] = functools.partial(
973
- _patch_on_interceptor_import, t, quiet
989
+ # Two targets can share an interceptor module (LangGraph's):
990
+ # its import then patches both.
991
+ on_interceptor = functools.partial(_patch_on_interceptor_import, t, quiet)
992
+ earlier = callbacks.get(t.interceptor_module)
993
+ callbacks[t.interceptor_module] = (
994
+ on_interceptor if earlier is None else _both(earlier, on_interceptor)
974
995
  )
975
996
  _import_hook.watch(callbacks)
976
997
  for target in selected:
@@ -1236,6 +1257,16 @@ def _patch_on_import(target: _SDKTarget, quiet: bool, _module: str) -> None:
1236
1257
  _patch_pending()
1237
1258
 
1238
1259
 
1260
+ def _both(first: Callable[[str], None], second: Callable[[str], None]) -> Callable[[str], None]:
1261
+ """An import-hook callback that runs ``first``, then ``second``."""
1262
+
1263
+ def both(module: str) -> None:
1264
+ first(module)
1265
+ second(module)
1266
+
1267
+ return both
1268
+
1269
+
1239
1270
  def _patch_on_interceptor_import(target: _SDKTarget, quiet: bool, _module: str) -> None:
1240
1271
  """The import hook's callback for the interceptor module of
1241
1272
  ``target``, whose code has just finished: patch the SDK if the