Runtime overview
Asymptote supports goose — the open-source agent from Block — through two collection paths, and both are wanted on the same machine:--harness codename-goose and --harness block-goose are also accepted, and install the same thing.
The two are not alternatives. goose’s hooks report prompts, tool calls, command lines and file edits; goose’s OpenTelemetry export reports token usage, the model and provider, response ids and the agent’s reasoning. Neither path carries the other’s data, so an endpoint with only one installed is half instrumented.
One integration covers the CLI and the desktop app. goose runs one agent core behind both, they discover plugins from the same directory, and both export under the same
service.name. There is one harness name — goose — rather than one per surface.Prerequisites
Before enabling goose telemetry, make sure:- goose is installed, either the CLI or the desktop app.
beacon endpoint installhas run so shared endpoint config, the collector, and runtime log paths exist. The OTLP path needs the collector actually listening; the hook path does not.- goose is restarted, or a new session started, after installing or removing hooks — plugins are discovered when a session begins.
Collection path 1: plugin hooks
goose discovers plugins as the immediate subdirectories of its plugins directory, names each plugin after its directory, and loads<plugin-root>/hooks/hooks.json from every plugin that is enabled. So Asymptote owns a directory rather than a file, and never reads or writes any other plugin beside it.
$GOOSE_PATH_ROOT relocates the user-scope location when it is set to an absolute path, matching how goose resolves its own directories. A relative value is ignored, exactly as goose ignores it.
beacon endpoint inventory reports both scopes, so an install at either is visible.
.agents/plugins is a shared directory, not goose’s namespace. It is the Open Plugins convention, so another agent implementing that specification would also discover this plugin and run its hooks — and those events would be attributed to goose, because nothing in a hook payload says which host invoked it. goose sets no distinguishing variable and the event schema has no field for the question.This is a stated limitation with no fix on Asymptote’s side. It costs nothing today, because goose is the only supported runtime that reads this directory.Registered events
goose exposes twelve lifecycle events. Asymptote registers seven, and the five it leaves out are covered under Known gaps.PostToolUse and PostToolUseFailure share the post-tool subcommand and are two separate registrations. goose has no single post-tool event: it sends one or the other, with otherwise identical payloads, and the event name is the only thing that distinguishes a write that landed from one that did not.
Asymptote’s hooks carry no matcher. The field is a regex tested against goose’s matcher_context, and that is not a tool name on every event — on UserPromptSubmit it is the prompt text. A matcher that looked like a harmless .* would be a regex run over every prompt, and a narrower one would silently drop prompts that did not match it. Omitting it means always-match, which is what a telemetry hook wants on all seven.
The hook environment
goose runs acommand action through sh -c, exports PLUGIN_ROOT, and sends the event as JSON on stdin. Asymptote passes the endpoint log and config locations to beacon-hooks as --log and --config flags rather than through the environment, so a hook finds them wherever it runs.
Asymptote owns one plugin directory and nothing else
Because goose loads every plugin it discovers, an install is additive by construction: your own plugins keep working, untouched, and Asymptote never parses them. The one thing Asymptote claims is the directory namebeacon-endpoint. If its hooks/hooks.json already holds a hook Asymptote did not write, both install and uninstall refuse with an error naming the hook rather than overwriting or deleting it — move that hook into a plugin of its own and run the install again. A file goose itself could not parse is overwritten, since there are no live hooks in it to protect.
Re-running the install rewrites the file rather than appending to it, so a repair does not end with each event registered twice. Uninstall removes the file and the plugin directory Asymptote created, because a plugin directory with no hooks file is still a plugin goose discovers and records in config.yaml. A directory you put something else into is left alone.
Collection path 2: OpenTelemetry export
goose ships a real OpenTelemetry exporter emitting OTel GenAI semantic-convention spans, andbeacon endpoint install --harness goose points it at the local collector by writing two keys into goose’s own config.yaml:
gen_ai.usage.* — including cache reads and writes — along with gen_ai.request.model, gen_ai.provider.name, gen_ai.response.id, gen_ai.response.finish_reasons and the agent’s reasoning, on chat and execute_tool spans. None of that appears on any hook.
Your config.yaml is edited, not rewritten
config.yaml is a file you write by hand — your provider, your model, your extensions, your permissions. Asymptote changes two keys and preserves comments, key order, values, nesting and two-space indentation, and backs the file up before writing. A config Asymptote cannot parse is refused rather than overwritten.
The one thing not preserved is blank lines between top-level keys, which the YAML library cannot retain. That is the reason for the backup.
The file is not the whole answer
Three environment conditions leave the config file saying “local” while goose exports nothing, sobeacon endpoint status reads the environment as well as the file:
Reporting “enabled” from the file alone would say a runtime is covered when it is silent, so each of these is reported as misconfigured with the variable named.
Discovery and status
Show goose status on both paths
goose harness. Hook events carry harness.collection_method=hook — the events, the payload shapes and the hooks schema are all goose’s, and Asymptote ships no plugin source goose loads and executes — while OTLP events carry harness.collection_method=otlp.
A hooks file on disk is a registration, not a guarantee. goose lets you disable a plugin from the
plugins map in config.yaml or from disabledPlugins in a settings.json at any of three scopes, and a disabled plugin keeps its hooks file. beacon endpoint hooks status reports the registration, which is the narrower claim it can actually make — answering the rest would mean reimplementing goose’s precedence across four files it does not version. Run one session and check the log to confirm events are arriving.Install or configuration support
.agents/plugins/beacon-endpoint/ if you want the hooks to apply to everyone working in the repository — remembering that it then replaces any user-scope install for sessions in that repository.
Telemetry coverage
Tool taxonomy
goose registers itsdeveloper extension with unprefixed_tools, so its tools arrive bare — the model calls shell, not developer__shell. goose’s own resolver also recovers developer__shell and developer.shell from a model that adds the prefix anyway, so all three spellings reach a hook and all three classify identically.
MCP calls are identified by exclusion, and they have to be. goose calls an MCP tool
<extension>__<tool> — no mcp anywhere in the name, no mcp_* argument, and no result to inspect, because goose populates no tool output at all. What identifies the call is that the prefix is not one of goose’s built-in extensions.Without that, a GitHub MCP server’s github__create_issue would be classified by the generic substring rules, which see “create” and would record an API call as a file modification.read_image is the one tool whose argument is ambiguous: source is documented as a local path or an http(s) URL. A URL is never recorded under file.path, because that field is what every rule, git helper and SIEM query treats as a filesystem path.
Diffs, and the guard that makes them trustworthy
goose’sedit states the replaced span as before and after — fragments of the file, not copies of it, because the tool requires before to match the file exactly and uniquely. That is the same shape as Claude Code’s old_string/new_string, and it takes the same fragment-diff builder. write carries the whole new file under content and takes the whole-content builder.
A failed edit is never recorded as a change. goose reports one with the same tool name and the same arguments as a success — its edit fails whenever before does not match — and the event name is the only thing that separates them. Without that guard, a diff would be built from an edit that never landed, and the log would assert a file changed when it did not.
An edit whose before and after are identical produces no diff, for the same reason.
Approvals and enforcement
Asymptote records no approval decisions for goose, and does not synthesize any. goose genuinely does ask the operator —ToolApprovalOperation runs the permission judge, marks calls that need a person as not executable, and stops the turn for an answer — and exposes none of that to a hook. Its HookEvent set has no approval event at all, and PreToolUse fires identically whether the call was pre-approved by goose_mode, waved through, or already confirmed by somebody. An approval.allowed derived from it would claim a decision in exactly the cases where none was made. This is the same call already settled for Cline, Pi, OpenHands, Kiro, and fx.
Enforcement stays behind the optional, off-by-default policy provider seam (BEACON_POLICY_PROVIDER), which is inert unless it names an executable and fails open on any error. goose is a runtime where a provider deny does take effect, and its deny shape is distinctive:
Data handling
goose content is handled like every other runtime. Prompts, tool arguments, command lines, paths, and diffs are retained in local or customer-controlled logs, with local secret redaction and per-string limits applied before writing, and a hash and byte count on each content-bearing event. Events over the 64 KiB limit drop raw and retained content, preserve stable metadata, and setfield_truncated.
The OTLP path carries message content only when OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT=true is set in goose’s environment. That is goose’s own opt-in and is not written by Asymptote — it cannot be: goose reads it from the environment only, never from config.yaml.
Known gaps
- No tool output of any kind. goose’s hook context has a
tool_outputfield and no emission site in the runtime populates it. So there are no command exit codes, no command output, no file-read contents and no MCP results anywhere on the hook path. A command is recorded as executed, with its command line, and nothing about how it went. This is the largest gap on this runtime, and closing it needs a change in goose. - No approval decisions. goose exposes no approval event, and Asymptote does not synthesize one from
PreToolUse. Approval rules and dashboards will show nothing for this runtime. - No reported cost. goose reports token counts and not a price, so
gen_ai.usage.cost_usdis empty. Asymptote does not derive cost from local pricing tables for any runtime. - Token usage needs the OTLP path. Usage arrives on
chatspans, so a hooks-only install reports none. That is expected rather than a fault, andbeacon token-usageclassifies goose accordingly. - No working directory on three events. goose omits it on
SessionStartandSessionEndwhen they come from the agent rather than the CLI session loop, and on aUserPromptSubmitraised by steering mid-turn. Those events carry nosession.working_directoryand no repository. Asymptote deliberately does not fall back to the hook process’s own directory: goose runs hooks withsh -cinheriting its own cwd, which is the application bundle under the desktop app — a path that looks like a repository and resolves to nothing. Every tool event does carry it. - No duplicate shell and file events.
BeforeShellExecution,AfterShellExecution,BeforeReadFileandAfterFileEditare deliberately not subscribed to. Each fires alongsidePreToolUseorPostToolUsefor the same call, with the same tool name and arguments and — the part that makes them worse than redundant — no tool call id at all. Subscribing would record every shell command and file edit twice, with the second copy uncorrelatable. - No policy-decision events.
PreToolUseResultreports what thePreToolUsehook chain decided, which plugin denied, and why. It is real signal with no duplicate elsewhere, but it fires once per tool call to report an outcome that is almost always “allow”, so subscribing as-is would double event volume. It is deferred rather than declined. - No compaction or subagent events. goose emits neither on a hook.
- No transcript path. goose keeps conversation state in its own sessions database and no payload field points at it.
- Another Open Plugins host would run these hooks.
.agents/pluginsis a shared convention directory, and nothing in a payload identifies the host. See Collection path 1.
Deployment notes
Plugins are discovered when a session starts, so an install taken while goose is running takes effect on the next session. Restart goose, or start a new session, after installing or removing hooks. Confirm status, then generate one goose event and check the log:- Confirm the plugin is enabled. Check the
pluginsmap in~/.config/goose/config.yamlanddisabledPluginsin anysettings.json— a disabled plugin keeps its hooks file, so the install looks complete. - Check
$GOOSE_PATH_ROOT: if it is set to an absolute path, the user-scope plugin is under that directory, not~/.agents. - Check for a project-scope install in the repository you are working in. It replaces the user-scope one rather than adding to it.
- Confirm the file contains commands with
--platform goose. - Start a new session so the plugin is rediscovered.
- Confirm
otel_exporter_otlp_endpointis in~/.config/goose/config.yamland names the collector’s HTTP port, not the gRPC one. - Check
OTEL_EXPORTER_OTLP_ENDPOINT,OTEL_SDK_DISABLEDandOTEL_EXPORTER_OTLP_PROTOCOLin goose’s launch environment. Each can silently turn the export off or redirect it, andbeacon endpoint statusnames whichever one is doing it. - Confirm the collector is running and listening on the HTTP port.
Related
Supported agent harnesses
Return to the runtime support overview.
Agent harness integration model
See how Asymptote discovers and configures each runtime.

