{"slug": "i-can-see-your-goose", "title": "I Can See Your Goose!", "summary": "AAIF's Goose agent now supports OpenTelemetry observability, enabling developers to trace and understand AI agent decision-making. The integration, detailed in a tutorial by AAIF, uses Goose v1.43.0 with the Claude ACP provider and Rust's OpenTelemetry SDK to provide visibility into agent actions, addressing common frustrations with opaque AI behavior.", "body_md": "# I Can See Your Goose!\n\n#### Making AAIF's Goose Observable with OpenTelemetry\n\nIf you’ve used AI agents for tech-y things (software development, SRE type stuff, etc), you’ve probably found yourself in a yelling match at some point with your agent trying to understand why the hell it decided to take a particular course of action. Especially if that course of action is destructive or mischievous, like making code changes without permission, for example. Which is why having observable agents is important.\n\nNow, this is a fairly broad topic. There are lots of different agentic ecosystems out there. So today, I’ll dig into one specific use case: enabling [OpenTelemetry (OTel)](https://opentelemetry.io) on [AAIF’s Goose](https://goose-docs.ai) to help make your Goose agents observable.\n\nLet’s dig in!\n\n## Goose Overview\n\n## Enabling OTel on Goose\n\nFor your convenience, I have [a lovely GitHub repository](https://github.com/avillela/goose-otel-enablement) to accompany this tutorial, including the [dev container](/tag/devcontainers) configuration. If you choose to use the dev container, you will have all of the tools that you need for this tutorial.\n\n### Set up Goose\n\nThe instructions in this section are for setting up Goose with with the [Claude ACP](https://goose-docs.ai/docs/guides/acp-providers#claude-acp) provider, which is a wrapper for Claude Code.\n\n✨\n\nFUN FACT:ACP stands for Agent Client Protocol. It’s an open standard that allows AI agents to talk to each other.\n\n#### 1. Install Goose\n\nFirst things first, you need to set up Goose. When the dev container first builds, it donwloads and installs Goose, and then Goose prompts you to set it up.\n\nYou don’t need to run the code snippet below, as it’s already done for you on dev container initial build, but I’ve included it for you so you can see how it’s being installed.\n\n```\nGOOSE_RELEASE=\"v1.43.0\"\ncurl -fsSL https://github.com/aaif-goose/goose/releases/download/${GOOSE_RELEASE}/download_cli.sh | bash\n```\n\n#### 2. Configure your provider\n\nThese steps show you how to set up Goose with the Claude Code provider.\n\nNOTE:If you need to configure your provider again, run`goose configure`\n\n**How would you like to set up your provider?**:`Manual Configuration`\n\n**Which model provider should we use?**:`Claude Code CLI`\n\n**Provider Claude Code CLI requires CLAUDE_CODE_COMMAND, please enter a value**:`claude`\n\n**Select model**:`claude-sonnet-4-6`\n\n**Select thinking effort**:`Low - Better latency, lighter reasoning`\n\nSuccess!!\n\nUnfortunately, as you were making your selection, you may have noticed that the Claude CLI is deprecated. On top of that, we don’t have the option to select `Claude ACP`\n\n. ARRGH!! Fortunately, I found a workaround.\n\nFirst, open your [ Goose config.yaml](~/.config/goose/config.yaml) file, and locate the code snipped below:\n\n```\nactive_provider: claude-code\nproviders:\n  claude-code:\n    enabled: true\n    model: claude-sonnet-4-6\n    configured: true\n```\n\nReplace `claude-code`\n\nwith `claude-acp`\n\n. Your modified block should look like this:\n\n```\nactive_provider: claude-acp\nproviders:\n  claude-acp:\n    enabled: true\n    model: sonnet\n    configured: true\n```\n\nYou also need to globally install the Claude ACP npm package:\n\n```\nnpm install -g @agentclientprotocol/claude-agent-acp\n```\n\nFor more info, check out out the [Goose ACP docs](https://goose-docs.ai/docs/guides/acp-providers/#claude-acp-configuration).\n\n#### 3. Login to Claude\n\nOpen a new terminal window, then start a Claude Code session via the Claude CLI by typing `claude`\n\n, and following the login instructions provided.\n\n#### 4. Start Goose\n\nOnce Claude is authenticated, open a new terminal window and start Goose:\n\n```\ngoose session\n```\n\nFeel free to ask it a test question. I always like to ask “how many folders in my directory?”.\n\n### Enabling OpenTelemetry for Goose\n\nFun fact: Goose is written in Rust, and is it so happens that it ships with built-in OpenTelemetry instrumentation, courtesy of the [Rust OTel SDK](https://opentelemetry.io/docs/languages/rust/). This allows Goose to emit OTel traces, logs, and metrics.\n\nBut you need to enable it.\n\n### 1. Enable telemetry\n\nUpon initial setup, Goose may have given you the following option:\n\n`Enable or disable anonymous usage data collection`\n\nIt’s kind of confusing, and if you’re like me, you might have disabled it initially. It turns out that you need this setting enabled for OTel telemetry emission. Never fear, because there’s a way to enable it after the fact.\n\nOpen [ ~/.config/goose/config.yaml](~/.config/goose/config.yaml), and find the configuration called\n\n`GOOSE_TELEMETRY_ENABLED`\n\n, and set it to `true`\n\n:\n\n```\n# Before\nGOOSE_TELEMETRY_ENABLED: false\n\n# After\nGOOSE_TELEMETRY_ENABLED: true\n```\n\nBut that’s only part of the story. We also need to configure a few OTel environment variables.\n\n### 2. Configure environment variables\n\nThe OTel environment variables tell OTel where to send the telemetry. These are the ones we need:\n\n```\nOTEL_EXPORTER_OTLP_ENDPOINT=\"http://localhost:4318\"\nOTEL_EXPORTER_OTLP_PROTOCOL=\"http/protobuf\"\n```\n\nWhere:\n\n`OTEL_EXPORTER_OTLP_ENDPOINT`\n\nis the destination of our telemetry. In our case, we’re sending the telemetry to an[OTel Collector](https://opentelemetry.io/docs/collector/). The Collector is configured to send telemetry data to[Jaeger](https://www.jaegertracing.io/)(traces only) and[Dynatrace](https://dt-url.net/dt-trial)(traces, logs, metrics).`OTEL_EXPORTER_OTLP_PROTOCOL`\n\nis the protocol we’re using for sending our OTel data. OTel supports both gRPC and HTTP. In this case, we’re using HTTP. If we were sending via gRPC, the value of`OTEL_EXPORTER_OTLP_PROTOCOL`\n\nwould be`grpc`\n\n, and our endpoint would be`http://localhost:4317`\n\n(the OTel Collector listens on port`4317`\n\nfor gRPC).\n\nFull disclosure:I work at Dynatrace and therefore have access to a Dynatrace environment. Obviously, you don’t have to go that route. To make things easier, I have split off the Dynatrace configuration so that you can exclude it if you won’t want to use it.\n\nTo configure the environment variables, create a copy of [ .env.example](.env.example):\n\n```\ncp .env.example .env\n```\n\nIf you don’t want to go the Dynatrace route, you don’t need to make any other environment variable configurations.\n\nIf you choose to also use [Dynatrace](https://dt-url.net/dt-trial) as your observability backend, uncomment and replace the details for the following environment variables:\n\n`DT_ENDPOINT`\n\n:[your Dynatrace endpoint](https://www.dynatrace.com/news/blog/send-opentelemetry-data-to-dynatrace/#your-dynatrace-tenant)`DT_API_TOKEN`\n\n:[your Dynatrace API token](https://www.dynatrace.com/news/blog/send-opentelemetry-data-to-dynatrace/#create-a-dynatrace-access-token)\n\n### 3. Start OTel components\n\nNow we’re ready to start the OTel Collector and Jaeger. This is done via a [ docker-compose.yaml](https://github.com/avillela/goose-otel-enablement/blob/main/docker-compose.yml), which starts two containers:\n\n| Service | Image | Exposed ports |\n|---|---|---|\n`otel-collector` | `otel/opentelemetry-collector-contrib:0.128.0` | `4317` (gRPC), `4318` (HTTP) |\n`jaeger` | `jaegertracing/jaeger:2.7.0` | `16686` (UI) |\n\nNOTE:If you would like to exclude the Dynatrace components, comment out lines referencing the Dynatrace Collector configuration from[:]`docker-compose.yaml`\n\nBefore:\n\n```\notel-collector:\n  image: otel/opentelemetry-collector-contrib:0.128.0\n  command:\n    - \"--config=/etc/otel/config.yaml\"\n    - \"--config=/etc/otel/config-dt.yaml\"\n  volumes:\n    - ./src/otel/otel-collector-config.yaml:/etc/otel/config.yaml\n    - ./src/otel/otel-collector-config-dt.yaml:/etc/otel/config-dt.yaml\n```\n\nAfter:\n\n```\notel-collector:\n  image: otel/opentelemetry-collector-contrib:0.128.0\n  command:\n  - \"--config=/etc/otel/config.yaml\"\n  # - \"--config=/etc/otel/config-dt.yaml\"\n  volumes:\n    - ./src/otel/otel-collector-config.yaml:/etc/otel/config.yaml\n    # - ./src/otel/otel-collector-config-dt.yaml:/etc/otel/config-dt.yaml\n```\n\nNow you’rea ready to run the script:\n\n```\n./src/scripts/start-otel.sh .env\n```\n\nThis starts up Jaeger and the OTel Collector.\n\n**Jaeger UI:** [http://localhost:16686](http://localhost:16686)\n\n**Dynatrace UI:** https://<your_tenant_id>.live.dynatrace.com\n\n### 5. Start Goose with OTel\n\nYou’ll need to ensure that the values from your `.env`\n\nfile are loaded before you start your Goose session, which you can do like this:\n\n```\nexport $(grep -v '^#' .env | xargs) && goose session\n```\n\nThen ask Goose a question! I asked my favourite question:\n\n```\nhow many folders in my directory?\n```\n\nIn your observability backend, you will be able to see traces, logs, and metrics for Goose, by filtering on attribute `service.name`\n\n= `goose`\n\n.\n\nGoose prompts and results are captured in OTel [spans](https://opentelemetry.io/docs/concepts/signals/traces/#spans) (children of a [trace](https://opentelemetry.io/docs/concepts/signals/traces/)):\n\n- Prompts are stored in the\n`trace_input`\n\nand`user_message`\n\nfields of the span - Prompt results are stored in the\n`trace_output`\n\n- The prompt input span is disconnected from the prompt output trace (i.e. they’re not part of the same trace)\n- All prompts in the same Goose session have the same\n`sesison.id`\n\nHere’s a sample trace output from Jaeger:\n\nHere’s a sample trace output from Dynatrace using [Dynatrace Query Language (DQL)](https://docs.dynatrace.com/docs/platform/grail/dynatrace-query-language) in a [Notebook](https://docs.dynatrace.com/docs/analyze-explore-automate/dashboards-and-notebooks/notebooks):\n\nAs you can see, in both Jaeger and Dynatrace, there are separate traces for the request (prompt) and response (result), which kind of sucks for correlation.\n\nGoose also emits logs, which you can see in Dynatrace. Again, I used DQL in a Notebook:\n\nAnd of course, metrics (again, in Dynatrace using DQL in a Notebook):\n\nThe metrics don’t look particularly useful in this format because it’s just the metric name and metadata. I wasn’t looking to do anything fancy other than see that the metrics were being emitted. You’ll find them more useful in a timeseries.\n\n## Final Thoughts\n\nBeing able to capture telemetry from a Goose session is very useful, especially when you’re running your agents in autonomous mode via [Goose Recipes](https://goose-docs.ai/docs/guides/recipes/). You might not be able to keep your agent from doing something completely dumb, but you can at least understand what the hell led to that decision, and most importantly, *what* it did.\n\nAs far as Goose telemetry goes, I didn’t find the Goose logs particularly useful. Most of the meaty bits for my personal use cases were in the spans. As I noted earlier, the only thing that I found annoying is how the prompt trace is not correlated to the prompt output trace, which means that you can’t really correlate them easily unless your observability backend can support fancy querying. Jaeger can’t. Dynatrace may likely be able to, but I have to play around with it a bit more. That’s another adventure for another day!\n\nAnd now, I will leave you with a very on-theme photo of a goose living its best life. Photo taken by my daughter.\n\nUntil next time, peace, love, and code. 🖖💜👩💻", "url": "https://wpnews.pro/news/i-can-see-your-goose", "canonical_source": "https://adrianavillela.netlify.app/post/goose-with-otel/", "published_at": "2026-07-24 00:00:00+00:00", "updated_at": "2026-07-25 00:31:49.399003+00:00", "lang": "en", "topics": ["ai-agents", "developer-tools", "ai-infrastructure"], "entities": ["AAIF", "Goose", "OpenTelemetry", "Claude ACP", "Claude Code", "Rust"], "alternates": {"html": "https://wpnews.pro/news/i-can-see-your-goose", "markdown": "https://wpnews.pro/news/i-can-see-your-goose.md", "text": "https://wpnews.pro/news/i-can-see-your-goose.txt", "jsonld": "https://wpnews.pro/news/i-can-see-your-goose.jsonld"}}