Goose Application Session Tracing with Future AGI

Export goose CLI sessions, model calls, and tool calls to Future AGI with goose's built-in OpenTelemetry exporter and a local OpenTelemetry Collector. Prompts, tool bodies, your user name, host name, and local paths stay off by default.

About

goose is an open source AI agent for coding and other tasks, now maintained under the Agentic AI Foundation. It ships its own OpenTelemetry exporter, so there is no plugin, wrapper, or code to add. goose sends its spans to a small OpenTelemetry Collector running next to it, which forwards them to Future AGI:

goose  ──OTLP/HTTP──▶  otelcol-contrib (localhost:4318)  ──▶  Future AGI
                       removes your OS user name, host name,
                       and goose's log-line span events,
                       labels agents, removes duplicate billing totals, adds your keys

Each goose reply (one prompt and everything the agent does to answer it) arrives in Future AGI as one trace with the agent turn, every model call with its token usage, and every tool call. Traces from the same goose session share a session id, so they group together in the Sessions view.

By default goose does not export prompt text, model responses, tool arguments, or tool results. You can turn that on with one variable when you need it. goose always attaches your OS user name and machine host name to its spans and has no switch for them; the collector removes them.

This is not the same as:

  • A model-provider instrumentor. goose calls the model itself and reports each call as a span. Do not also wrap that provider with a traceAI package, or every model call is counted twice.
  • goose anonymous usage telemetry. That is a separate opt-in switch that reports usage to the goose maintainers, not to your Future AGI project.
  • goose Desktop. It runs the same agent code, but an app started from the Dock or Finder does not see variables exported in your shell. The runnable setup is CLI-only; Desktop is outside this recipe.
Versiongoose CLI 1.54.0 on macOS arm64, with the built-in developer extension
Requiresotelcol-contrib 0.144.0 or later (the contrib build: the config uses the transform and filter processors, and the otlp_http exporter name, which older collectors call otlphttp). No goose plugin or package.
Sourceexamples/coding-agents/goose

1. Run the collector

Download the example collector config and start otelcol-contrib with your Future AGI keys (get them from API keys):

curl -fsSLO https://raw.githubusercontent.com/future-agi/docs/main/examples/coding-agents/goose/otel-collector.yaml
export FI_API_KEY="your-futureagi-api-key"
export FI_SECRET_KEY="your-futureagi-secret-key"
export FI_PROJECT_NAME="goose-sessions"
otelcol-contrib --config otel-collector.yaml

It listens on 127.0.0.1:4318 (set GOOSE_OTLP_LISTEN to change it) and sends traces to $FI_BASE_URL/tracer/v1/traces (default https://api.futureagi.com; set FI_BASE_URL for a self-hosted Future AGI). It opens no other port. Keep it running while you use goose, or run it as a service.

Collector stepWhy
Adds X-Api-Key, X-Secret-Key, project_type=observe, and project_name (from FI_PROJECT_NAME, unless goose sends one)Authenticates the export and files the traces under your project, so no Future AGI credential is set in goose’s environment.
Deletes resource user.name and host.name, and span session.user and session.hostgoose 1.54.0 always sets them from $USER and the machine’s host name. See Privacy.
Drops span eventsgoose attaches its own log lines to spans as events, for example Loading config from: "/Users/<you>/.config/goose/config.yaml". They can hold local paths.
Sets gen_ai.span.kind=AGENT on goose’s invoke_agent spansFuture AGI does not map invoke_agent by itself, so reply and reply_stream would show as unknown.
Moves agent-level gen_ai.usage.* / gen_ai.cost.* into goose.agent.usage.* / goose.agent.cost.*Keeps diagnostic reply totals without counting them again alongside the model-call spans.
Drops logs and metricsFuture AGI accepts traces only.
Turns off the collector’s own metrics endpoint (service.telemetry.metrics.level: none)By default the collector also serves its own metrics on localhost:8888, which Jupyter and other collectors use by default too, and exits at start when that port is taken.

It also sends uncompressed (compression: none). Future AGI accepts gzip-encoded OTLP too, so that line is optional.

To send one goose run to a different project with the same keys, set OTEL_RESOURCE_ATTRIBUTES="project_name=<other-project>" in the shell that starts goose; the collector keeps a project name goose sends. For a project with different keys, start a second collector with those keys and another port, for example GOOSE_OTLP_LISTEN=127.0.0.1:4320, and point goose at http://127.0.0.1:4320/v1/traces.


2. Point goose at the collector

In the shell where you start goose, export the standard OpenTelemetry variables goose reads at startup:

export OTEL_EXPORTER_OTLP_TRACES_ENDPOINT="http://127.0.0.1:4318/v1/traces"
export OTEL_TRACES_EXPORTER="otlp"
export OTEL_METRICS_EXPORTER="none"
export OTEL_LOGS_EXPORTER="none"
VariableWhy
OTEL_EXPORTER_OTLP_TRACES_ENDPOINTThe collector’s traces URL; use the same host and port as GOOSE_OTLP_LISTEN if you changed it. goose uses a signal-specific endpoint as is; it only appends /v1/traces to the generic OTEL_EXPORTER_OTLP_ENDPOINT.
OTEL_TRACES_EXPORTER=otlpTurns trace export on.
OTEL_METRICS_EXPORTER=none, OTEL_LOGS_EXPORTER=nonegoose can also export metrics and logs; Future AGI does not ingest them.

goose sends OTLP/HTTP protobuf. It is built without gRPC, so OTEL_EXPORTER_OTLP_PROTOCOL=grpc disables export instead of switching transport.

A copy of these settings is in the docs repo at examples/coding-agents/goose/futureagi-otel.env.

Tip

goose’s config.yaml also accepts otel_exporter_otlp_endpoint and otel_exporter_otlp_timeout, and environment variables override it. The environment variables above are enough; no credential goes into goose’s config.


3. Run goose

Start goose from the same shell. Nothing else changes: your provider, model, and extensions stay as they are.

goose session
# or, headless:
goose run -t "List the files in this folder and read README.md"

goose batches spans and flushes the last batch when the process exits, and the collector forwards every 2 seconds, so a goose run appears in Future AGI a few seconds after it finishes. An interactive goose session exports in batches while it runs.


4. See the trace

Open Observe in Future AGI and pick the goose-sessions project. Each trace is one goose reply, named reply, with its latency. Billable token and cost attributes remain on model-call spans only, so trace/session aggregation counts each call once. A goose run with one prompt that lists files and reads a file looks like this:

reply                                   goose turn: diagnostic totals only
└── reply_stream                        model and provider for the turn
    ├── stream_response_from_provider   model call: model, tokens, finish reason
    ├── dispatch_tool_call              tool: tree
    ├── stream_response_from_provider
    ├── dispatch_tool_call              tool: shell
    └── stream_response_from_provider   model call that writes the answer

The Sessions tab groups the replies of one goose session (same session.id) together.

Direct export is outside this recipe

Keep the collector in the path. Direct export bypasses both privacy filtering and usage de-duplication: goose repeats token totals on reply, reply_stream and model children, and it exports machine identity plus log-line span events. Resource environment overrides alone cannot fix the span attributes or totals.


What is captured

goose spangen_ai.operation.nameShown in Future AGI asAttributes
reply (root)invoke_agentAgentsession.id, gen_ai.agent.name, diagnostic goose.agent.usage.input_tokens / output_tokens totals for the reply
reply_streaminvoke_agentAgentsession.id, gen_ai.conversation.id, gen_ai.agent.name, gen_ai.request.model, gen_ai.provider.name, gen_ai.request.max_tokens, diagnostic goose.agent.usage.* totals. Status ERROR when a model call fails.
stream_response_from_providerchatLLMgen_ai.request.model, gen_ai.response.model, gen_ai.provider.name, gen_ai.usage.input_tokens, gen_ai.usage.output_tokens, cache read and cache creation tokens, gen_ai.response.finish_reasons, gen_ai.response.id
dispatch_tool_callexecute_toolToolgen_ai.tool.name (for example shell, tree, edit), gen_ai.tool.call.id

The collector labels goose’s invoke_agent spans as agent spans (gen_ai.span.kind=AGENT). It renames all agent-level gen_ai.usage.* and gen_ai.cost.* fields into the non-billing goose.agent.usage.* and goose.agent.cost.* namespaces. The original totals remain visible in Attributes, while billable usage comes only from stream_response_from_provider model spans. A sum over all spans in a trace or session therefore counts each model call once; agent token cells are not the reply total.

gen_ai.agent.name is goose, or the recipe’s title when you run a recipe. It is exported even with content capture off.

goose also attaches code-location attributes (code.file.path, thread.name) to spans; they point into goose’s own source, not your files. goose’s log-line span events (for example WAITING_TOOL_END: shell) are dropped by the collector.

Resource attributes on every span: service.name=goose, service.version (the goose version), service.namespace, and the OpenTelemetry SDK name and version, plus project_name and project_type from the collector.

Session grouping: every span carries session.id, goose’s session id (for example 20261015_7). One trace is one reply. A multi-turn session, interactive or resumed with goose run -r, produces one trace per reply, all with the same session.id.

Errors: when a model call fails (for example an unknown model name), goose sets status ERROR on the turn span reply_stream only. The model-call span stream_response_from_provider and the root reply stay UNSET, and the trace list shows the root’s status, so a failed reply shows as UNSET there. Open the trace to find the failed reply_stream span. The error text is not exported: the status message is empty, and the collector drops the log-line span events that carry it.


Privacy

Content capture is off unless OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT is true, in any case (TRUE and True also turn it on). goose reads it from the environment of the goose process. With it unset or any other value, goose exports no prompt text, no model output, no system prompt, no tool arguments, and no tool results. Tool names, model names, token counts, and gen_ai.agent.name (the recipe title when you run a recipe) are still exported.

To include content, for example while debugging a recipe:

export OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT=true

With it set, goose adds gen_ai.input.messages and gen_ai.output.messages to the turn and model-call spans, trace_input, trace_output, and user_message to the turn spans, and gen_ai.tool.call.arguments, gen_ai.tool.call.result, input, and output to tool spans. In Future AGI the text appears in each span’s Attributes, not in the trace list’s Input and Output columns.

Warning

Tool arguments and results can contain file contents, shell output, environment values, and secrets. goose does not redact them. Turn content capture on only for projects where that is acceptable.

Even with content off, goose 1.54.0 exports your OS user name (resource user.name, span session.user), your machine’s host name (resource host.name, span session.host), and log-line span events that include local paths such as your goose config file and error text. goose has no switch for any of them. The collector config removes all of them before anything reaches Future AGI, which is why this page sends goose through it instead of straight to Future AGI. Direct export is outside this recipe because it bypasses those span filters and the duplicate-usage transform.

To keep goose’s log-line span events (for debugging goose itself), remove filter/goose-log-events from the traces pipeline in otel-collector.yaml. The collector still removes user.name, host.name, session.user, and session.host, but the kept events include local paths (with your user name, for example your goose config file), commands from recipes, and error text. Keep them only while debugging.


Not available

ItemStatus
Costgoose does not export cost. Future AGI computes cost only when it knows the model’s price.
Failed tool callsgoose 1.54.0 leaves the tool span status unset when a tool returns an error.
Error status on the traceOnly the turn span reply_stream is set to ERROR, when a model call fails. The root reply and the model-call span stay UNSET, so the trace list shows a failed reply as UNSET.
User prompt and response textOnly with the content opt-in above.
SubagentsOutside this CLI recipe; no parent/child subagent-session grouping is promised.
gRPC exportgoose is built with OTLP/HTTP only.
Metrics and logsgoose can export them, but Future AGI accepts traces only.
Reading goose session files from diskNot part of this integration. Only what goose exports over OTLP is traced.
goose anonymous usage telemetryA different switch that reports to the goose maintainers, not to your Future AGI project.

Tool span timing covers goose’s dispatch of the call. For long-running tools, the parent reply_stream span is the better measure of wall-clock time.


Troubleshooting

SymptomFix
No tracesCheck that the collector is running and that its log shows Everything is ready. Check OTEL_TRACES_EXPORTER=otlp and that OTEL_SDK_DISABLED is not true. Start goose from the shell where you exported the variables.
No traces, endpoint ends in /v1/traces/v1/tracesYou set OTEL_EXPORTER_OTLP_ENDPOINT to the full /v1/traces URL, so goose appended /v1/traces again and the collector answers 404. Use OTEL_EXPORTER_OTLP_TRACES_ENDPOINT for the full URL, or set OTEL_EXPORTER_OTLP_ENDPOINT=http://127.0.0.1:4318.
No traces, collector log shows 401FI_API_KEY or FI_SECRET_KEY is missing or wrong in the shell that started the collector.
Collector exits with address already in use (port 4318 or 8888)Another process holds the port. 4318 is the standard OTLP/HTTP port; the collector from the Future AGI Gemini CLI guide uses the same default, as do other OpenTelemetry tools. Start this collector on another port, for example GOOSE_OTLP_LISTEN=127.0.0.1:4320, and set goose’s OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=http://127.0.0.1:4320/v1/traces to match. Port 8888 is the collector’s own metrics endpoint (also Jupyter’s default port); the current otel-collector.yaml turns it off with service.telemetry.metrics.level: none, so download it again or add that setting to your copy.
Traces land in the wrong projectA project_name in goose’s OTEL_RESOURCE_ATTRIBUTES decides the project; without one, FI_PROJECT_NAME in the collector’s environment does. Unset OTEL_RESOURCE_ATTRIBUTES if you set it for direct export. Restart the collector after changing FI_PROJECT_NAME.
Model and tool spans but no reply rootgoose sends the root spans in its last batch, when it exits. If that export does not finish (process killed, or the collector not running), the trace has no root. Let goose exit normally, and stop the collector gracefully (Ctrl-C) so it flushes its last batch.
Export stopped after setting a protocolgoose has no gRPC transport. Leave OTEL_EXPORTER_OTLP_PROTOCOL unset or set it to http/protobuf.

To see what goose sends before the collector filters it, set OTEL_TRACES_EXPORTER=console and run goose once.


Next Steps

Was this page helpful?

Questions & Discussion