Skip to content

OpenTelemetry span for each agent turn - #43

Draft
asg017 wants to merge 2 commits into
mainfrom
asg017/otel-v2
Draft

asg017 wants to merge 2 commits into
mainfrom
asg017/otel-v2

Conversation

@asg017

@asg017 asg017 commented Sep 25, 2026 •

Copy link
Copy Markdown
🤖 Claude-generated PR description

This replaces #41 with a much thinner layer. llm and datasette-llm now emit their own spans, so datasette-agent only has to add the agent-level unit: one span per turn.

Depends on (unreleased, pinned temporarily):

What's emitted

invoke_agent datasette-agent (INTERNAL) wraps each _run_chain call in run_agent and resume_agent. Its attributes:

  • gen_ai.operation.name=invoke_agent
  • gen_ai.agent.name=datasette-agent
  • gen_ai.conversation.id
  • datasette_agent.mode: chat or resume
  • datasette_agent.outcome: completed, question, browser_task, error or cancelled

Errors set error.type (the class name) and ERROR status. Cancellation is not an error. On Datasette ≥ 1.0a41 the turn nests under core's request span. The resulting trace is:

POST …/stream → invoke_agent datasette-agent → datasette_llm.prompt → chat echo / execute_tool …

Changes

  • agent.py: +11/−6. Only the two _run_chain call sites are wrapped, inside the existing try, so errors reach the span before being turned into SSE. _run_chain itself is untouched.
  • datasette_agent/telemetry.py: new, opentelemetry-api only. It is a no-op without an SDK and imports nothing from datasette.telemetry, so it works on Datasette 1.0a37+.
  • README: a short "Observability" section.
  • Privacy: no message text, actor ids or error messages are recorded.

TEMPORARY pins, remove before release

  • [tool.uv.sources] in pyproject.toml points llm and datasette-llm at their asg017/otel-v2 branches.
  • test.yml and publish.yml install those branches in the same pip install as the package. It has to be the same command, because the branches share version numbers with PyPI, so a separate pip install would keep the PyPI copies.
  • Don't cut a release while the pins exist. Remove them and raise the llm / datasette-llm floors once the upstream PRs ship.

Also included: the one-line Cache-Control test fix from #42, as its own commit. It will drop out on rebase once #42 merges.

Tests: tests/test_telemetry.py covers:

  • a chat turn, with its datasette_llm.prompt and chat descendants
  • a tool call nesting under the turn
  • an ask_user turn ending as question, followed by a resume turn
  • a model error ending as error
  • cancellation (CancelledError and KeyboardInterrupt) ending as cancelled
  • a client write failing mid-chain: the turn ends as error and the caller's context is restored

Results:

  • uv run pytest: 305 passed
  • uv run --with datasette==1.0a37 pytest: 305 passed
  • node --test tests/test_smd.mjs: 14 passed

Client disconnects: when the client disconnects mid-chain, the turn ends as error and the caller's context is restored. datasette-llm#8 now ends its own abandoned prompt span as abandoned without logging detach errors.

Next in the stack: background runs and the explorer as linked trace roots.

🤖 Generated with Claude Code

asg017 and others added 2 commits September 25, 2026 10:49
…nses

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant