@latitude-data/openclaw-telemetry 0.0.3 → 0.0.5

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.
package/README.md CHANGED
@@ -4,22 +4,23 @@ OpenClaw plugin that streams every agent run to [Latitude](https://latitude.so)
4
4
 
5
5
  ## Install
6
6
 
7
+ Requires OpenClaw **2026.4.25 or newer** on PATH. Older versions are detected up-front and the installer aborts with an upgrade message.
8
+
7
9
  ```bash
8
10
  npx -y @latitude-data/openclaw-telemetry install
11
+ openclaw gateway restart
9
12
  ```
10
13
 
11
14
  The installer prompts for your Latitude API key and project slug, then:
12
15
 
13
- 1. Materializes the plugin's runtime files into `~/.openclaw/extensions/latitude-telemetry/` (where OpenClaw's plugin discovery scans).
14
- 2. Writes the plugin entry to `~/.openclaw/openclaw.json` under `plugins.entries["@latitude-data/openclaw-telemetry"].config` credentials, base URL, and the `allowConversationAccess` flag all live here.
15
-
16
- Restart the OpenClaw gateway after install:
17
-
18
- ```bash
19
- openclaw gateway restart
20
- ```
16
+ 1. Verifies your OpenClaw version (aborts on `< 2026.4.25`).
17
+ 2. Hands plugin placement to OpenClaw via `openclaw plugins install <package-path> --force`. OpenClaw copies files into `~/.openclaw/extensions/<id>/`, writes the install record to `~/.openclaw/plugins/installs.json`, and creates the `plugins.entries[id]` block.
18
+ 3. Layers our config on top in `~/.openclaw/openclaw.json`:
19
+ - **`config.*`** — credentials, baseUrl, and `allowConversationAccess` (the payload-content gate the plugin's runtime reads).
20
+ - **`hooks.allowConversationAccess`** — the dispatch gate OpenClaw's runtime checks before forwarding LLM/tool/agent events to non-bundled plugins. Always mirrored to the same value as `config.allowConversationAccess`.
21
+ 4. Adds the plugin id to `plugins.allow` (running `npx install` is the trust signal). Pass `--no-trust` to opt out.
21
22
 
22
- That's it. Traces show up at `https://console.latitude.so/projects/<your-slug>`.
23
+ Traces show up at `https://console.latitude.so/projects/<your-slug>` once the gateway restarts.
23
24
 
24
25
  ### Install flags
25
26
 
@@ -30,9 +31,10 @@ That's it. Traces show up at `https://console.latitude.so/projects/<your-slug>`.
30
31
  | `--staging` | Target `https://staging.latitude.so` / `https://staging-ingest.latitude.so`. |
31
32
  | `--dev` | Target `http://localhost:3000` / `http://localhost:3002`. |
32
33
  | `--yes` / `--no-prompt` | Skip all prompts. Required for non-TTY / CI invocations. |
33
- | `--no-content` | Skip raw prompt/response/tool I/O capture. Spans still emit with timing, token usage, model name, and ids. |
34
+ | `--no-content` | Skip raw prompt/response/tool I/O capture. Spans still emit with timing, token usage, model name, and ids. Mirrored into both `config.allowConversationAccess` and `hooks.allowConversationAccess`. |
35
+ | `--no-trust` | Skip auto-adding the plugin id to `plugins.allow`. OpenClaw will keep printing `plugins.allow is empty` warnings until you add it manually. |
34
36
 
35
- Re-running `install` is idempotent — existing values are preserved.
37
+ Re-running `install` is idempotent — credentials/baseUrl are overwritten from prompts, but hand-edited `enabled`, `debug`, and `allowConversationAccess` values in `openclaw.json` are preserved unless you pass the corresponding flag.
36
38
 
37
39
  ## Uninstall
38
40
 
@@ -40,66 +42,115 @@ Re-running `install` is idempotent — existing values are preserved.
40
42
  npx -y @latitude-data/openclaw-telemetry uninstall
41
43
  ```
42
44
 
43
- Shows a plan, asks for confirmation, then removes the plugin entry from `~/.openclaw/openclaw.json` and the materialized files at `~/.openclaw/extensions/latitude-telemetry/`. A backup of the settings file is saved at `openclaw.json.latitude-bak`.
45
+ Shows a plan, asks for confirmation, then runs `openclaw plugins uninstall @latitude-data/openclaw-telemetry --force` (which removes files, the install record, the plugin entry, and the `plugins.allow` entry). Defensive cleanup follows for any leftover keys, with a backup at `openclaw.json.latitude-bak`.
44
46
 
45
47
  ## What gets sent
46
48
 
47
- For each agent run, the plugin emits one trace with three span kinds:
49
+ For each agent run, the plugin emits one trace shaped like the actual run:
48
50
 
49
- - **`interaction`** — the agent run. Carries `openclaw.session.key`, `openclaw.agent.id`, `openclaw.agent.name`, aggregated token usage across all LLM calls, run duration, success/error status, and the first user prompt.
50
- - **`llm_request`** — one per LLM call. Carries provider, request/response model, `gen_ai.system_instructions`, `gen_ai.input.messages` (full history + current prompt), `gen_ai.output.messages` (assistant text + tool_call parts), and full token usage (input/output/cache_read/cache_creation/total) — under both canonical `gen_ai.*` keys and legacy aliases.
51
- - **`tool_execution`** one per tool call. Canonical `gen_ai.tool.*` attributes: `name`, `call.id`, `call.arguments`, `call.result`. Failures set `error.type`, `error.message`, and OTel status code 2.
51
+ ```
52
+ agent (root, traceId = hash(runId))
53
+ ├─ compaction (0..1, rare; budget-triggered)
54
+ ├─ model_call (1..N, one per provider API call)
55
+ ├─ tool_call: foo (between model_calls; sibling of agent)
56
+ ├─ model_call
57
+ ├─ tool_call: bar
58
+ ├─ subagent (0..N — the child's full agent tree nests under here)
59
+ │ └─ agent
60
+ │ ├─ model_call
61
+ │ └─ tool_call: ...
62
+ └─ model_call (final)
63
+ ```
64
+
65
+ Five span kinds:
66
+
67
+ - **`agent`** — root of the run. Carries `openclaw.session.key`, `openclaw.agent.id`, `openclaw.agent.name`, aggregated token usage across all generations, run duration, success/error status, the first user prompt, and the full final message list. This is where attempt-aggregate `gen_ai.*` lands.
68
+ - **`model_call`** — one per actual provider API call inside the run. Carries provider, request/response model, `openclaw.api`, `openclaw.transport`, per-call duration, outcome, error category, time-to-first-byte, request payload bytes, response stream bytes, upstream request id hash, and `gen_ai.input.messages` snapshotted at the moment that generation started. Per-call output messages and per-call token usage aren't surfaced by OpenClaw today (attempt-aggregate only); those stay on `agent`.
69
+ - **`tool_call:<name>`** — one per tool invocation. Canonical `gen_ai.tool.*` attributes: `name`, `call.id`, `call.arguments`, `call.result`. Sibling of `agent`, NOT child of `model_call` — tools run between generations, not during them.
70
+ - **`compaction`** — rare; fires when OpenClaw hits the message budget mid-run. Records before/after message counts and the compacted-out count.
71
+ - **`subagent`** — one per child run spawned by this agent. The child's entire `agent` subtree (its own `model_call`s, `tool_call`s, even further-nested `subagent`s) parents itself underneath via cross-runId trace propagation, so a spawn tree is one waterfall in one trace.
52
72
 
53
- Every span carries `openclaw.agent.id` and `openclaw.agent.name`. Multi-agent OpenClaw setups (sub-agents) naturally produce spans tagged with the invoking agent's id, letting you filter and group by agent in the Latitude UI.
73
+ Every span carries `openclaw.agent.id` and `openclaw.agent.name`. Multi-agent setups produce spans tagged with the invoking agent's id, letting you filter and group by agent in the Latitude UI.
54
74
 
55
- All spans share the run id and trace id so they group together.
75
+ All spans share the same `traceId` so they group as one trace per agent run (and one trace per spawn tree, by virtue of the subagent linkage).
76
+
77
+ ### Backend caveat: Codex / Claude-Code-style providers
78
+
79
+ OpenClaw's `model_call_started` / `model_call_ended` hooks fire from its `selection` layer, which wraps the agent's `streamFn` invocation. For "agentic" backends (Codex, Claude Code) the inner generations happen inside the backend's own loop and don't surface as separate `model_call` events. Result: a Codex-backed run shows ONE `model_call` per attempt instead of N. Anthropic and OpenAI direct don't have this issue. The fix is upstream in OpenClaw — out of scope for this plugin.
56
80
 
57
81
  ## How it works
58
82
 
59
- OpenClaw ships typed plugin hooks that fire per-LLM-call with the complete payload (`src/plugins/hook-types.ts` in the OpenClaw source). We subscribe to:
83
+ We subscribe to OpenClaw's typed plugin hooks (`src/plugins/hook-types.ts` upstream). The model is "one span per paired before/after (or start/end) event":
84
+
85
+ | Span | Start hook | End hook |
86
+ | --- | --- | --- |
87
+ | `agent` | `before_agent_start` | `agent_end` |
88
+ | `model_call` | `model_call_started` | `model_call_ended` |
89
+ | `tool_call` | `before_tool_call` | `after_tool_call` |
90
+ | `compaction` | `before_compaction` | `after_compaction` |
91
+ | `subagent` | `subagent_spawned` | `subagent_ended` |
60
92
 
61
- - `llm_input` — full system prompt, prompt text, history messages, provider, model.
62
- - `llm_output` — assistant text, last assistant message, full token usage (input/output/cacheRead/cacheWrite/total), resolved provider/model ref.
63
- - `before_tool_call` / `after_tool_call` — tool name, arguments, result, error, duration.
64
- - `agent_end` — run completion signal. This is when we build the OTLP trace and POST it.
65
- - `session_start` — currently a no-op; reserved for future session-level metadata.
93
+ Two more hooks (`llm_input`, `llm_output`) are subscribed to for **content only** they don't open or close spans, they just enrich the `agent` span with attempt-aggregate data and seed the rolling history snapshot used by per-call `model_call.gen_ai.input.messages`.
66
94
 
67
- OpenClaw runs LLM hooks **fire-and-forget** (see [`src/plugins/hooks.ts`](https://github.com/openclaw/openclaw/blob/main/src/plugins/hooks.ts) `runLlmInput`/`runLlmOutput` are documented as parallel, and the call site in [`src/agents/pi-embedded-runner/run/attempt.ts`](https://github.com/openclaw/openclaw/blob/main/src/agents/pi-embedded-runner/run/attempt.ts) wraps them with `void hookRunner.run*(...).catch(...)`). Our handlers can never slow down the agent loop.
95
+ The hook system runs handlers fire-and-forget (see [`src/plugins/hooks.ts`](https://github.com/openclaw/openclaw/blob/main/src/plugins/hooks.ts) upstream), so nothing we do here can slow the agent loop. The one exception is `before_tool_call`, which is a `runModifyingHook` our handler returns `undefined` so OpenClaw dispatches the tool normally. Returning anything else (e.g. `{block: true}`) would block every tool call.
68
96
 
69
- **No runtime wrapping.** Unlike existing third-party OpenClaw observability plugins that try to monkey-patch `@mariozechner/pi-ai` (and run into jiti's CJS/ESM module isolation), we stay inside the supported plugin API. The hooks give us everything, at lower risk of breaking on OpenClaw updates.
97
+ **No runtime wrapping.** Unlike third-party OpenClaw observability plugins that try to monkey-patch `@mariozechner/pi-ai` (and run into jiti's CJS/ESM module isolation), we stay inside the supported plugin API. The hooks give us everything, at lower risk of breaking on OpenClaw updates.
70
98
 
71
99
  ## Configuration reference
72
100
 
73
- The installer writes the plugin entry under `plugins.entries[id].config`. Every key is optional except `apiKey` and `project`. You can hand-edit `~/.openclaw/openclaw.json` to tweak:
101
+ The installer writes two blocks to `plugins.entries["@latitude-data/openclaw-telemetry"]`:
74
102
 
75
- ### `plugins.entries["@latitude-data/openclaw-telemetry"].config`
103
+ ### `.config` — read by the plugin's runtime
76
104
 
77
105
  | Key | Required | Default | Description |
78
106
  | --- | --- | --- | --- |
79
107
  | `apiKey` | yes | — | Bearer token for Latitude ingestion. |
80
108
  | `project` | yes | — | Slug of the project to route traces into. |
81
109
  | `baseUrl` | no | `https://ingest.latitude.so` | Override OTLP ingest origin. Installer sets this only when you pass `--staging` or `--dev`. |
82
- | `allowConversationAccess` | no | `false` | When `true`, attach raw prompts, assistant responses, system instructions, and tool I/O to spans. When `false`, emit only timing, token usage, model name, agent id, and structural ids — same span tree, scrubbed payloads. |
110
+ | `allowConversationAccess` | no | `false` | When `true`, attach raw prompts, assistant responses, system instructions, and tool I/O to spans. When `false`, emit only timing, token usage, model name, agent id, and structural ids — same span tree, scrubbed payloads. **Must match `hooks.allowConversationAccess` below — see "the two flags".** |
83
111
  | `enabled` | no | `true` | Set to `false` to pause emission without uninstalling. |
84
112
  | `debug` | no | `false` | Log diagnostic lines to stderr (visible in the gateway log). |
85
113
 
114
+ ### `.hooks` — read by OpenClaw's runtime
115
+
116
+ | Key | Required | Default | Description |
117
+ | --- | --- | --- | --- |
118
+ | `allowConversationAccess` | yes (on 2026.4.25+) | — | OpenClaw's hook dispatcher gates `llm_input` / `llm_output` / `before_tool_call` / `after_tool_call` / `agent_end` events on this. When `false` or absent, every typed hook is blocked and the plugin never sees an event — which means no traces, with the gateway log showing `[plugins] typed hook "..." blocked because non-bundled plugins must set plugins.entries.<id>.hooks.allowConversationAccess=true`. |
119
+
120
+ ### The two flags
121
+
122
+ `hooks.allowConversationAccess` and `config.allowConversationAccess` mean different things:
123
+
124
+ - **`hooks.*`** is the **dispatch gate**. `false` → OpenClaw never forwards events to us. No traces.
125
+ - **`config.*`** is the **payload-content gate**. `false` → we emit spans normally but scrub message content from them. Structural-only telemetry.
126
+
127
+ For *this* plugin, we always couple them — the installer writes both from the same source. If you hand-edit, set them to the same value:
128
+
129
+ - **Both `true`**: full content capture (the default).
130
+ - **Both `false`**: structural-only telemetry (set via `--no-content`).
131
+ - Anything else is incoherent: `hooks: false + config: true` means dispatch is off and content gating is moot; `hooks: true + config: false` works but is what you'd get with both `false` plus extra ceremony.
132
+
86
133
  ### Environment variable fallbacks
87
134
 
88
- If a key isn't set in `config`, the runtime falls back to environment variables on the gateway process. `LATITUDE_API_KEY`, `LATITUDE_PROJECT`, `LATITUDE_BASE_URL`, `LATITUDE_DEBUG`, and `LATITUDE_OPENCLAW_ENABLED` are all read this way. The installer doesn't set them — pluginConfig is the canonical surface — but they're useful for kicking debug on/off without editing `openclaw.json`.
135
+ If a `config.*` key isn't set, the runtime falls back to env vars on the gateway process: `LATITUDE_API_KEY`, `LATITUDE_PROJECT`, `LATITUDE_BASE_URL`, `LATITUDE_DEBUG`, `LATITUDE_OPENCLAW_ENABLED`. The installer doesn't set them — pluginConfig is the canonical surface — but they're useful for flipping `debug` without editing `openclaw.json`.
89
136
 
90
137
  ### Manual installation
91
138
 
92
- If the installer doesn't fit your setup, you need two things:
139
+ If you can't run the installer, do exactly what it does:
93
140
 
94
- 1. **The plugin files** under a directory OpenClaw discovers (`~/.openclaw/extensions/<name>/` or any path listed in `plugins.load.paths`). The directory must contain at minimum `openclaw.plugin.json` and the compiled `dist/plugin.js`. Easiest: copy them out of the installed `node_modules/@latitude-data/openclaw-telemetry/`.
95
- 2. **The plugin entry** in `~/.openclaw/openclaw.json`:
141
+ 1. **Hand placement to OpenClaw** with `openclaw plugins install <path-to-extracted-package> --force`. This is what populates `~/.openclaw/extensions/`, writes the install record, and creates the (initially empty) `plugins.entries[id]` block. Don't hand-place files into the extensions directory the persisted plugin index won't see them.
142
+ 2. **Add the config + hooks block** to `~/.openclaw/openclaw.json`:
96
143
 
97
144
  ```jsonc
98
145
  {
99
146
  "plugins": {
147
+ "allow": ["@latitude-data/openclaw-telemetry"],
100
148
  "entries": {
101
149
  "@latitude-data/openclaw-telemetry": {
102
150
  "enabled": true,
151
+ "hooks": {
152
+ "allowConversationAccess": true
153
+ },
103
154
  "config": {
104
155
  "apiKey": "lat_xxx",
105
156
  "project": "my-openclaw-project",
@@ -111,21 +162,24 @@ If the installer doesn't fit your setup, you need two things:
111
162
  }
112
163
  ```
113
164
 
114
- Don't put `LATITUDE_*` keys at top-level `env`OpenClaw's strict zod schema rejects them. Don't put `allowConversationAccess` under `hooks` either — that field is OpenClaw's strict reserved namespace and only accepts `allowPromptInjection` (older versions) or `allowPromptInjection` + `allowConversationAccess` (2026.4.22+). Our config bucket is `plugins.entries[id].config`, which is `record(string, unknown)` and accepted across all versions.
115
-
116
- After editing, run `openclaw config validate` — it should print `valid: true`. Then `openclaw gateway restart`.
165
+ Run `openclaw config validate` — should print `valid: true`. Then `openclaw gateway restart`.
117
166
 
118
167
  ## Privacy
119
168
 
120
- By default we emit **structural telemetry only** span tree, timings, token usage, model name, agent name, run/session ids — but **no prompt or response content**. You opt in to content capture by setting `allowConversationAccess: true` (the default the interactive installer writes).
169
+ There are two defaults at playkeep them straight:
170
+
171
+ - **Installer default**: when you run `npx -y @latitude-data/openclaw-telemetry install` without `--no-content`, the installer writes `allowConversationAccess: true` to both the `hooks` and `config` blocks. **You get full content capture** — prompts, assistant responses, system instructions, tool I/O — shipped to Latitude alongside structural telemetry.
172
+ - **Runtime default for absent keys**: if you hand-write `openclaw.json` and leave `allowConversationAccess` out entirely, `config.allowConversationAccess` falls back to `false` at the plugin's runtime (privacy-preserving) and `hooks.allowConversationAccess` falls back to OpenClaw's default (also `false`, which means dispatch is blocked and you get nothing). **Manual installs without those keys produce no traces, not "structural-only traces".**
173
+
174
+ The installer always writes both keys to the same value, so the installer-driven path is unambiguous. The runtime-default trap only matters for hand-rolled configs.
121
175
 
122
- When `allowConversationAccess` is on, every LLM call's full input messages, assistant output, system instructions, and tool I/O are attached to spans. Pass `--no-content` to the installer (or set the flag to `false` in `openclaw.json`) if you want telemetry without payloads.
176
+ When you pass `--no-content` (or hand-edit both flags to `false`), we emit the same span tree but scrub the content attributes (`gen_ai.input.messages`, `gen_ai.output.messages`, `gen_ai.system_instructions`, `gen_ai.tool.call.arguments`/`result`, the interaction's `user_prompt`). Timings, token usage, model name, agent name, ids, and the `latitude.captured.content: false` boolean still flow.
123
177
 
124
- To pause emission entirely without uninstalling, set `LATITUDE_OPENCLAW_ENABLED=0` in the gateway environment.
178
+ To pause emission entirely without uninstalling, set `LATITUDE_OPENCLAW_ENABLED=0` in the gateway environment, or set `enabled: false` on the plugin entry in `openclaw.json`.
125
179
 
126
180
  ## Supported OpenClaw versions
127
181
 
128
- Requires OpenClaw **2026.3.0+** for the `llm_input` / `llm_output` hooks. Older versions lack these hooks and would only surface metadata via `model.usage` diagnostics not enough for full trace fidelity.
182
+ Requires OpenClaw **2026.4.25 or newer**. The installer detects the version up-front via `openclaw --version` and aborts with an upgrade message on older versions supporting the full range with portability shims would mean shipping known-broken behaviour (≤ 2026.4.21 reject `hooks.allowConversationAccess` outright; 2026.4.22 2026.4.24 have unverified dispatch gating). Run `npm install -g openclaw@latest` to upgrade.
129
183
 
130
184
  ## How it fails
131
185