@senad-d/observme 0.1.0
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/.env.example +57 -0
- package/CHANGELOG.md +52 -0
- package/LICENSE +21 -0
- package/ObservMe-Production-Docs/00-README.md +79 -0
- package/ObservMe-Production-Docs/01-requirements-and-scope.md +207 -0
- package/ObservMe-Production-Docs/02-reference-architecture.md +306 -0
- package/ObservMe-Production-Docs/03-pi-event-and-session-model.md +266 -0
- package/ObservMe-Production-Docs/04-telemetry-semantic-conventions.md +722 -0
- package/ObservMe-Production-Docs/05-otel-pipeline-and-collector.md +355 -0
- package/ObservMe-Production-Docs/06-security-privacy-redaction.md +294 -0
- package/ObservMe-Production-Docs/07-extension-implementation-blueprint.md +447 -0
- package/ObservMe-Production-Docs/08-query-grafana-integration.md +276 -0
- package/ObservMe-Production-Docs/09-dashboards-alerts-slos.md +640 -0
- package/ObservMe-Production-Docs/10-testing-release-operations.md +319 -0
- package/ObservMe-Production-Docs/11-deployment-runbooks.md +203 -0
- package/ObservMe-Production-Docs/12-configuration-reference.md +337 -0
- package/ObservMe-Production-Docs/13-source-notes.md +33 -0
- package/ObservMe-Production-Docs/pi-session-format.md +427 -0
- package/README.md +356 -0
- package/SECURITY.md +45 -0
- package/dashboards/observme-agent-node-graphs.json +250 -0
- package/dashboards/observme-agents.json +1880 -0
- package/dashboards/observme-alerts.yaml +113 -0
- package/dashboards/observme-branches-compactions.json +1042 -0
- package/dashboards/observme-cost.json +1254 -0
- package/dashboards/observme-errors.json +1659 -0
- package/dashboards/observme-export-health.json +1802 -0
- package/dashboards/observme-latency.json +1494 -0
- package/dashboards/observme-llm-conversations.json +730 -0
- package/dashboards/observme-logs-llm.json +644 -0
- package/dashboards/observme-models.json +933 -0
- package/dashboards/observme-overview.json +2129 -0
- package/dashboards/observme-slo-health.json +737 -0
- package/dashboards/observme-slos.yaml +56 -0
- package/dashboards/observme-tools.json +902 -0
- package/dashboards/observme-trace-journey.json +1793 -0
- package/docs/STRUCTURE.md +49 -0
- package/docs/agent-subagent-observability-requirements.md +997 -0
- package/docs/compatibility-matrix.md +39 -0
- package/docs/configuration-tui-design-standard.md +767 -0
- package/docs/configuration.md +38 -0
- package/docs/review-validation.md +115 -0
- package/docs/validation-flow.md +117 -0
- package/examples/collector.yaml +123 -0
- package/examples/observme.yaml +131 -0
- package/img/demo.gif +0 -0
- package/img/icon.svg +47 -0
- package/package.json +103 -0
- package/src/commands/obs-agents-runtime.ts +150 -0
- package/src/commands/obs-agents.ts +491 -0
- package/src/commands/obs-args.ts +63 -0
- package/src/commands/obs-backfill.ts +1334 -0
- package/src/commands/obs-command-support.ts +43 -0
- package/src/commands/obs-cost.ts +228 -0
- package/src/commands/obs-diagnostics.ts +22 -0
- package/src/commands/obs-errors.ts +156 -0
- package/src/commands/obs-health.ts +301 -0
- package/src/commands/obs-link.ts +90 -0
- package/src/commands/obs-logs.ts +194 -0
- package/src/commands/obs-loki-summary.ts +184 -0
- package/src/commands/obs-session.ts +259 -0
- package/src/commands/obs-status.ts +359 -0
- package/src/commands/obs-tools.ts +274 -0
- package/src/commands/obs-trace.ts +411 -0
- package/src/commands/obs.ts +211 -0
- package/src/config/bootstrap-project-config.ts +300 -0
- package/src/config/defaults.ts +143 -0
- package/src/config/load-config.ts +631 -0
- package/src/config/project-paths.ts +61 -0
- package/src/config/schema.ts +405 -0
- package/src/config/validate.ts +456 -0
- package/src/constants.ts +4 -0
- package/src/diagnostics/sanitize.ts +6 -0
- package/src/extension.ts +38 -0
- package/src/otel/logs.ts +160 -0
- package/src/otel/metrics.ts +165 -0
- package/src/otel/otlp-endpoint.ts +10 -0
- package/src/otel/sdk.ts +114 -0
- package/src/otel/shutdown.ts +102 -0
- package/src/otel/traces.ts +166 -0
- package/src/pi/agent-lineage.ts +378 -0
- package/src/pi/agent-tree-tracker.ts +258 -0
- package/src/pi/event-handlers/agent-turn.ts +155 -0
- package/src/pi/event-handlers/lifecycle.ts +642 -0
- package/src/pi/event-handlers/llm.ts +115 -0
- package/src/pi/event-handlers/session-events.ts +159 -0
- package/src/pi/event-handlers/tool-bash.ts +275 -0
- package/src/pi/handler-internals.ts +2154 -0
- package/src/pi/handler-runtime.ts +633 -0
- package/src/pi/handler-types.ts +261 -0
- package/src/pi/handlers.ts +75 -0
- package/src/pi/subagent-spawn.ts +975 -0
- package/src/pi/subagent-types.ts +29 -0
- package/src/privacy/content-capture.ts +104 -0
- package/src/privacy/hash.ts +93 -0
- package/src/privacy/redact.ts +619 -0
- package/src/privacy/secret-patterns.ts +185 -0
- package/src/privacy/truncate.ts +69 -0
- package/src/query/grafana-readiness.ts +164 -0
- package/src/query/grafana-transport.ts +481 -0
- package/src/query/grafana.ts +371 -0
- package/src/query/loki.ts +332 -0
- package/src/query/prometheus.ts +388 -0
- package/src/query/tempo.ts +332 -0
- package/src/safety/sensitive-input.ts +208 -0
- package/src/semconv/attributes.ts +279 -0
- package/src/semconv/metrics.ts +146 -0
- package/src/semconv/spans.ts +19 -0
- package/src/semconv/values.ts +13 -0
- package/src/util/bounded-map.ts +97 -0
- package/tsconfig.json +15 -0
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
# ObservMe Extension Implementation Blueprint
|
|
2
|
+
|
|
3
|
+
## 1. Runtime Assumptions
|
|
4
|
+
|
|
5
|
+
ObservMe is a TypeScript Pi extension loaded by Pi. It registers event handlers and commands using Pi's extension API, initializes OpenTelemetry SDK components only after `session_start`, and exports telemetry to an OTLP endpoint.
|
|
6
|
+
|
|
7
|
+
Pi extension factories may run in invocations that never start a session. Therefore ObservMe must not start background resources such as metric readers, exporter timers, sockets, or file watchers directly from the factory. The factory should register handlers and commands; session-scoped telemetry startup belongs in `session_start`, and cleanup belongs in `session_shutdown`.
|
|
8
|
+
|
|
9
|
+
## 2. Repository Layout
|
|
10
|
+
|
|
11
|
+
```text
|
|
12
|
+
observme/
|
|
13
|
+
├── package.json
|
|
14
|
+
├── tsconfig.json
|
|
15
|
+
├── README.md
|
|
16
|
+
├── src/
|
|
17
|
+
│ ├── index.ts # Pi extension entrypoint
|
|
18
|
+
│ ├── config/
|
|
19
|
+
│ │ ├── load-config.ts
|
|
20
|
+
│ │ ├── schema.ts
|
|
21
|
+
│ │ └── defaults.ts
|
|
22
|
+
│ ├── otel/
|
|
23
|
+
│ │ ├── sdk.ts
|
|
24
|
+
│ │ ├── traces.ts
|
|
25
|
+
│ │ ├── metrics.ts
|
|
26
|
+
│ │ ├── logs.ts
|
|
27
|
+
│ │ └── shutdown.ts
|
|
28
|
+
│ ├── pi/
|
|
29
|
+
│ │ ├── handlers.ts
|
|
30
|
+
│ │ ├── session.ts
|
|
31
|
+
│ │ ├── event-normalizer.ts
|
|
32
|
+
│ │ ├── agent-lineage.ts
|
|
33
|
+
│ │ ├── agent-tree-tracker.ts
|
|
34
|
+
│ │ └── turn-tracker.ts
|
|
35
|
+
│ ├── semconv/
|
|
36
|
+
│ │ ├── attributes.ts
|
|
37
|
+
│ │ ├── metrics.ts
|
|
38
|
+
│ │ └── spans.ts
|
|
39
|
+
│ ├── privacy/
|
|
40
|
+
│ │ ├── redact.ts
|
|
41
|
+
│ │ ├── secret-patterns.ts
|
|
42
|
+
│ │ ├── hash.ts
|
|
43
|
+
│ │ └── truncate.ts
|
|
44
|
+
│ ├── commands/
|
|
45
|
+
│ │ ├── obs-status.ts
|
|
46
|
+
│ │ ├── obs-health.ts
|
|
47
|
+
│ │ ├── obs-session.ts
|
|
48
|
+
│ │ ├── obs-cost.ts
|
|
49
|
+
│ │ ├── obs-agents.ts
|
|
50
|
+
│ │ └── obs-link.ts
|
|
51
|
+
│ ├── query/
|
|
52
|
+
│ │ ├── grafana.ts
|
|
53
|
+
│ │ ├── tempo.ts
|
|
54
|
+
│ │ ├── loki.ts
|
|
55
|
+
│ │ └── prometheus.ts
|
|
56
|
+
│ └── util/
|
|
57
|
+
│ ├── safe-json.ts
|
|
58
|
+
│ ├── time.ts
|
|
59
|
+
│ ├── trace-context.ts
|
|
60
|
+
│ └── bounded-map.ts
|
|
61
|
+
└── test/
|
|
62
|
+
├── redaction.test.ts
|
|
63
|
+
├── event-mapping.test.ts
|
|
64
|
+
├── metrics.test.ts
|
|
65
|
+
├── exporter-failure.test.ts
|
|
66
|
+
├── agent-lineage.test.ts
|
|
67
|
+
└── cardinality.test.ts
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## 3. Dependencies
|
|
71
|
+
|
|
72
|
+
Required:
|
|
73
|
+
|
|
74
|
+
```json
|
|
75
|
+
{
|
|
76
|
+
"dependencies": {
|
|
77
|
+
"@opentelemetry/api": "1.x",
|
|
78
|
+
"@opentelemetry/api-logs": "0.x",
|
|
79
|
+
"@opentelemetry/sdk-node": "0.x",
|
|
80
|
+
"@opentelemetry/sdk-trace-node": "2.x",
|
|
81
|
+
"@opentelemetry/sdk-metrics": "2.x",
|
|
82
|
+
"@opentelemetry/sdk-logs": "0.x",
|
|
83
|
+
"@opentelemetry/exporter-trace-otlp-proto": "0.x",
|
|
84
|
+
"@opentelemetry/exporter-metrics-otlp-proto": "0.x",
|
|
85
|
+
"@opentelemetry/exporter-logs-otlp-proto": "0.x",
|
|
86
|
+
"@opentelemetry/resources": "2.x",
|
|
87
|
+
"@opentelemetry/semantic-conventions": "1.x"
|
|
88
|
+
},
|
|
89
|
+
"devDependencies": {
|
|
90
|
+
"typescript": "^5.x",
|
|
91
|
+
"vitest": "^latest"
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Pin exact versions in production and test upgrades before release. Do not assume all `@opentelemetry/*` packages share the same major version; OpenTelemetry JS commonly mixes stable API packages with SDK/exporter packages on different major/minor lines. Use the `*-otlp-proto` exporters for OTLP/HTTP protobuf per current OpenTelemetry JS docs, or the `*-otlp-grpc` exporters when using gRPC.
|
|
97
|
+
|
|
98
|
+
## 4. Entrypoint Pattern
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
102
|
+
import { loadBootstrapConfig, loadSessionConfig } from "./config/load-config";
|
|
103
|
+
import { createRuntime } from "./runtime";
|
|
104
|
+
import { registerHandlers } from "./pi/handlers";
|
|
105
|
+
import { registerObsCommands } from "./commands";
|
|
106
|
+
|
|
107
|
+
export default async function observme(pi: ExtensionAPI) {
|
|
108
|
+
const bootstrapConfig = await loadBootstrapConfig();
|
|
109
|
+
const runtime = createRuntime(bootstrapConfig);
|
|
110
|
+
|
|
111
|
+
// Register handlers and commands in the factory, but defer OTEL SDK startup
|
|
112
|
+
// until session_start so background exporters/timers are session-scoped.
|
|
113
|
+
registerHandlers(pi, runtime, loadSessionConfig);
|
|
114
|
+
registerObsCommands(pi, runtime);
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## 5. Telemetry Runtime Interface
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
export interface ObservMeTelemetry {
|
|
122
|
+
tracer: Tracer;
|
|
123
|
+
meter: Meter;
|
|
124
|
+
logger: Logger;
|
|
125
|
+
metrics: ObservMeMetrics;
|
|
126
|
+
spans: SpanRegistry;
|
|
127
|
+
agent: AgentLineageContext;
|
|
128
|
+
flush(timeoutMs: number): Promise<void>;
|
|
129
|
+
shutdown(timeoutMs: number): Promise<void>;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export interface AgentLineageContext {
|
|
133
|
+
workflowId: string;
|
|
134
|
+
workflowRootAgentId: string;
|
|
135
|
+
agentId: string;
|
|
136
|
+
parentAgentId?: string;
|
|
137
|
+
rootAgentId: string;
|
|
138
|
+
depth: number;
|
|
139
|
+
role: "root" | "subagent" | "orchestrator" | "worker" | "reviewer" | "unknown";
|
|
140
|
+
capability?: string;
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## 6. Span Registry
|
|
145
|
+
|
|
146
|
+
Track active spans in memory only.
|
|
147
|
+
|
|
148
|
+
```typescript
|
|
149
|
+
type SpanKey = string;
|
|
150
|
+
|
|
151
|
+
interface SpanRegistry {
|
|
152
|
+
sessionSpan?: Span;
|
|
153
|
+
activeAgentRuns: Map<string, Span>;
|
|
154
|
+
activeTurns: Map<string, Span>;
|
|
155
|
+
activeToolCalls: Map<string, Span>;
|
|
156
|
+
activeLlmRequests: Map<string, Span>;
|
|
157
|
+
activeSubagentSpawns: Map<string, Span>;
|
|
158
|
+
activeAgentWaits: Map<string, Span>;
|
|
159
|
+
activeAgentJoins: Map<string, Span>;
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Bound all maps:
|
|
164
|
+
|
|
165
|
+
```yaml
|
|
166
|
+
limits:
|
|
167
|
+
maxActiveAgentRuns: 16
|
|
168
|
+
maxActiveTurns: 128
|
|
169
|
+
maxActiveToolCalls: 1024
|
|
170
|
+
maxActiveLlmRequests: 128
|
|
171
|
+
maxActiveSubagentSpawns: 128
|
|
172
|
+
maxActiveAgentWaits: 128
|
|
173
|
+
maxActiveAgentJoins: 128
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
When exceeded:
|
|
177
|
+
|
|
178
|
+
- End oldest span with error status `observme.evicted=true`
|
|
179
|
+
- Increment `observme_telemetry_dropped_total{reason="span_registry_full"}`
|
|
180
|
+
|
|
181
|
+
## 7. Event Handler Design
|
|
182
|
+
|
|
183
|
+
### Session Start
|
|
184
|
+
|
|
185
|
+
```typescript
|
|
186
|
+
pi.on("session_start", async (event, ctx) => {
|
|
187
|
+
// Load session-scoped config with ctx.cwd. Only read project-local config when
|
|
188
|
+
// ctx.isProjectTrusted() is true; otherwise use global/env/default config.
|
|
189
|
+
const config = await loadSessionConfig(ctx, runtime.bootstrapConfig);
|
|
190
|
+
const agent = buildAgentLineageContext(config, process.env);
|
|
191
|
+
const telemetry = await runtime.startSession(config, agent, ctx);
|
|
192
|
+
|
|
193
|
+
const attrs = buildSessionAttributes(event, ctx, agent);
|
|
194
|
+
telemetry.spans.sessionSpan = telemetry.tracer.startSpan("pi.session", { attributes: attrs });
|
|
195
|
+
telemetry.metrics.sessionsStarted.add(1, metricLabels(ctx, agent));
|
|
196
|
+
if (agent.role === "root" || agent.role === "orchestrator") {
|
|
197
|
+
telemetry.metrics.workflowsStarted.add(1, workflowMetricLabels(ctx, agent));
|
|
198
|
+
emitLog("workflow.started", attrs);
|
|
199
|
+
}
|
|
200
|
+
telemetry.metrics.activeAgents.add(1, metricLabels(ctx, agent));
|
|
201
|
+
emitLog("session.started", attrs);
|
|
202
|
+
});
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### Agent Run and Turn Start
|
|
206
|
+
|
|
207
|
+
```typescript
|
|
208
|
+
pi.on("agent_start", async (_event, ctx) => {
|
|
209
|
+
const telemetry = runtime.telemetryOrNoop();
|
|
210
|
+
const runId = nextAgentRunId(ctx, telemetry.agent);
|
|
211
|
+
const span = startChildSpan(
|
|
212
|
+
telemetry.spans.sessionSpan,
|
|
213
|
+
"pi.agent.run",
|
|
214
|
+
buildAgentRunAttributes(runId, ctx, telemetry.agent),
|
|
215
|
+
);
|
|
216
|
+
telemetry.spans.activeAgentRuns.set(runId, span);
|
|
217
|
+
telemetry.metrics.agentRuns.add(1, metricLabels(ctx, telemetry.agent));
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
pi.on("turn_start", async (event, ctx) => {
|
|
221
|
+
const telemetry = runtime.telemetryOrNoop();
|
|
222
|
+
const runSpan = currentAgentRunSpan(ctx) ?? telemetry.spans.sessionSpan;
|
|
223
|
+
const turnId = getTurnId(event, ctx, currentAgentRunId(ctx));
|
|
224
|
+
const span = startChildSpan(runSpan, "pi.turn", buildTurnAttributes(event, ctx, telemetry.agent));
|
|
225
|
+
telemetry.spans.activeTurns.set(turnId, span);
|
|
226
|
+
telemetry.metrics.turnsStarted.add(1, metricLabels(ctx, telemetry.agent));
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
pi.on("turn_end", async (event, ctx) => {
|
|
230
|
+
const telemetry = runtime.telemetryOrNoop();
|
|
231
|
+
const turnId = getTurnId(event, ctx, currentAgentRunId(ctx));
|
|
232
|
+
const span = telemetry.spans.activeTurns.get(turnId);
|
|
233
|
+
span?.setAttributes(buildTurnEndAttributes(event, ctx));
|
|
234
|
+
span?.end();
|
|
235
|
+
telemetry.spans.activeTurns.delete(turnId);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
pi.on("agent_end", async (_event, ctx) => {
|
|
239
|
+
const telemetry = runtime.telemetryOrNoop();
|
|
240
|
+
const runId = currentAgentRunId(ctx);
|
|
241
|
+
const span = telemetry.spans.activeAgentRuns.get(runId);
|
|
242
|
+
span?.end();
|
|
243
|
+
telemetry.spans.activeAgentRuns.delete(runId);
|
|
244
|
+
});
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
### Provider Request
|
|
248
|
+
|
|
249
|
+
```typescript
|
|
250
|
+
pi.on("before_provider_request", async (event, ctx) => {
|
|
251
|
+
const telemetry = runtime.telemetryOrNoop();
|
|
252
|
+
const turnId = currentTurnId(ctx);
|
|
253
|
+
const parent = telemetry.spans.activeTurns.get(turnId) ?? currentAgentRunSpan(ctx) ?? telemetry.spans.sessionSpan;
|
|
254
|
+
// Pi's event exposes the provider-specific payload; use ctx.model plus safe payload inspection.
|
|
255
|
+
const span = startChildSpan(parent, "pi.llm.request", buildLlmRequestAttrs(event.payload, ctx.model, ctx));
|
|
256
|
+
telemetry.spans.activeLlmRequests.set(makeLlmKey(event, ctx), span);
|
|
257
|
+
});
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### Tool Execution Start and Tool Call Metadata
|
|
261
|
+
|
|
262
|
+
```typescript
|
|
263
|
+
pi.on("tool_execution_start", async (event, ctx) => {
|
|
264
|
+
const telemetry = runtime.telemetryOrNoop();
|
|
265
|
+
const parent = currentTurnSpan(ctx) ?? currentAgentRunSpan(ctx) ?? telemetry.spans.sessionSpan;
|
|
266
|
+
const span = startChildSpan(parent, "pi.tool.call", buildToolExecutionAttrs(event, ctx, telemetry.agent));
|
|
267
|
+
telemetry.spans.activeToolCalls.set(event.toolCallId, span);
|
|
268
|
+
telemetry.metrics.toolCalls.add(1, { tool_name: safeToolName(event.toolName), agent_role: telemetry.agent.role });
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
pi.on("tool_call", async (event, ctx) => {
|
|
272
|
+
const telemetry = runtime.telemetryOrNoop();
|
|
273
|
+
const span = telemetry.spans.activeToolCalls.get(event.toolCallId);
|
|
274
|
+
span?.setAttributes(buildToolCallInputAttrs(event, ctx));
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
pi.on("tool_execution_end", async (event, ctx) => {
|
|
278
|
+
const telemetry = runtime.telemetryOrNoop();
|
|
279
|
+
const span = telemetry.spans.activeToolCalls.get(event.toolCallId);
|
|
280
|
+
span?.setAttributes(buildToolResultAttrs(event, ctx));
|
|
281
|
+
if (event.isError) span?.setStatus({ code: SpanStatusCode.ERROR, message: safeErrorClass(event.result) });
|
|
282
|
+
span?.end();
|
|
283
|
+
telemetry.spans.activeToolCalls.delete(event.toolCallId);
|
|
284
|
+
});
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
### Subagent Spawn
|
|
288
|
+
|
|
289
|
+
There is no dedicated Pi event for "subagent spawned". Detect this at the point ObservMe wraps a subagent tool/command or an extension intentionally launches another Pi process.
|
|
290
|
+
|
|
291
|
+
```typescript
|
|
292
|
+
async function runSubagent(command: string, args: string[], ctx: ExtensionContext) {
|
|
293
|
+
const telemetry = runtime.telemetryOrNoop();
|
|
294
|
+
const spawnId = newSpawnId();
|
|
295
|
+
const parent = currentTurnSpan(ctx) ?? currentAgentRunSpan(ctx) ?? telemetry.spans.sessionSpan;
|
|
296
|
+
const span = startChildSpan(parent, "pi.agent.spawn", buildSpawnAttrs(spawnId, ctx, telemetry.agent));
|
|
297
|
+
|
|
298
|
+
const env = buildChildEnv(process.env, telemetry.agent, span.spanContext(), spawnId);
|
|
299
|
+
recordAgentTreeSpawn(telemetry, spawnId, ctx);
|
|
300
|
+
try {
|
|
301
|
+
telemetry.metrics.subagentsSpawned.add(1, { agent_role: "subagent", spawn_type: "command" });
|
|
302
|
+
// Use a child_process/spawn helper or tool operation that supports explicit env.
|
|
303
|
+
// pi.exec() is useful for simple commands but does not currently accept env.
|
|
304
|
+
return await spawnProcessWithEnv(command, args, { env, signal: ctx.signal });
|
|
305
|
+
} catch (err) {
|
|
306
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message: safeErrorClass(err) });
|
|
307
|
+
telemetry.metrics.subagentSpawnFailures.add(1, { spawn_type: "command", error_class: safeErrorClass(err) });
|
|
308
|
+
throw err;
|
|
309
|
+
} finally {
|
|
310
|
+
span.end();
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
Propagate W3C `traceparent`/`tracestate`, `OBSERVME_WORKFLOW_ID`, `OBSERVME_PARENT_AGENT_ID`, `OBSERVME_ROOT_AGENT_ID`, `OBSERVME_PARENT_SESSION_ID`, `OBSERVME_AGENT_DEPTH`, and `OBSERVME_SPAWN_ID`. Do not place raw command lines or inherited environment values into telemetry.
|
|
316
|
+
|
|
317
|
+
When the parent waits for a child or receives child results, create `pi.agent.wait` and `pi.agent.join` spans/events with child status, propagated-failure status, active-child count, and join status. These spans make the critical path visible in orchestrator traces.
|
|
318
|
+
|
|
319
|
+
### Session Shutdown
|
|
320
|
+
|
|
321
|
+
```typescript
|
|
322
|
+
pi.on("session_shutdown", async (_event, _ctx) => {
|
|
323
|
+
const telemetry = runtime.telemetryOrNoop();
|
|
324
|
+
endAllActiveSpans(telemetry.spans);
|
|
325
|
+
telemetry.spans.sessionSpan?.end();
|
|
326
|
+
await withTimeout(runtime.shutdownSession(), runtime.config.shutdown.flushTimeoutMs);
|
|
327
|
+
});
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
## 8. Handling Assistant Messages
|
|
331
|
+
|
|
332
|
+
Assistant messages are important because finalized `message_end` events contain provider/model metadata, usage, stop reason, diagnostics, tool calls, and cost. ObservMe should update and end the active GenAI span from the finalized assistant message, not from partial streaming data alone.
|
|
333
|
+
|
|
334
|
+
Pseudo-flow:
|
|
335
|
+
|
|
336
|
+
```typescript
|
|
337
|
+
function onAssistantMessage(message: AssistantMessage, context: PiContext) {
|
|
338
|
+
const span = findCurrentLlmSpan(context);
|
|
339
|
+
span?.setAttributes(extractUsageAttributes(message.usage));
|
|
340
|
+
span?.setAttribute("pi.llm.stop_reason", message.stopReason);
|
|
341
|
+
span?.setAttribute("gen_ai.response.finish_reasons", [mapStopReason(message.stopReason)]);
|
|
342
|
+
|
|
343
|
+
metrics.inputTokens.add(message.usage.input, labels);
|
|
344
|
+
metrics.outputTokens.add(message.usage.output, labels);
|
|
345
|
+
metrics.cacheReadTokens.add(message.usage.cacheRead, labels);
|
|
346
|
+
metrics.cacheWriteTokens.add(message.usage.cacheWrite, labels);
|
|
347
|
+
if (message.usage.cacheWrite1h !== undefined) metrics.cacheWrite1hTokens.add(message.usage.cacheWrite1h, labels);
|
|
348
|
+
if (message.usage.reasoning !== undefined) metrics.reasoningTokens.add(message.usage.reasoning, labels);
|
|
349
|
+
metrics.totalCost.add(message.usage.cost.total, labels);
|
|
350
|
+
|
|
351
|
+
if (message.stopReason === "error") {
|
|
352
|
+
span?.setAttribute("error.type", classifyError(message.errorMessage));
|
|
353
|
+
span?.setAttribute("pi.llm.error_message_hash", hashError(message.errorMessage));
|
|
354
|
+
span?.setStatus({ code: SpanStatusCode.ERROR });
|
|
355
|
+
metrics.llmErrors.add(1, labels);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
span?.end();
|
|
359
|
+
}
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
## 9. Logs
|
|
363
|
+
|
|
364
|
+
Use OTEL logs for structured event streams.
|
|
365
|
+
|
|
366
|
+
```typescript
|
|
367
|
+
logger.emit({
|
|
368
|
+
severityText: "INFO",
|
|
369
|
+
body: "tool.call.completed",
|
|
370
|
+
attributes: {
|
|
371
|
+
"event.name": "tool.call.completed",
|
|
372
|
+
"pi.session.id": sessionId,
|
|
373
|
+
"pi.agent.id": agentId,
|
|
374
|
+
"pi.agent.run.id": agentRunId,
|
|
375
|
+
"pi.turn.id": turnId,
|
|
376
|
+
"pi.tool.name": toolName,
|
|
377
|
+
"pi.tool.success": success,
|
|
378
|
+
},
|
|
379
|
+
});
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
## 10. Error Handling
|
|
383
|
+
|
|
384
|
+
All handlers must catch errors.
|
|
385
|
+
|
|
386
|
+
```typescript
|
|
387
|
+
function safeHandler(name: string, fn: Handler): Handler {
|
|
388
|
+
return async (event, ctx) => {
|
|
389
|
+
try {
|
|
390
|
+
await fn(event, ctx);
|
|
391
|
+
} catch (err) {
|
|
392
|
+
recordInternalError(name, err);
|
|
393
|
+
}
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
Never throw from an ObservMe event handler into Pi.
|
|
399
|
+
|
|
400
|
+
## 11. Config Loading Order
|
|
401
|
+
|
|
402
|
+
1. Built-in defaults
|
|
403
|
+
2. Global config `~/.pi/agent/observme.yaml`
|
|
404
|
+
3. Project config `<cwd>/<CONFIG_DIR_NAME>/observme.yaml` only when `ctx.isProjectTrusted()` is true (normally `.pi/observme.yaml`; use Pi's exported `CONFIG_DIR_NAME` instead of hardcoding `.pi`)
|
|
405
|
+
4. Environment variables
|
|
406
|
+
5. CLI or Pi extension options if available
|
|
407
|
+
|
|
408
|
+
Factory-safe config loading may read only defaults, global config, and environment variables. Session-scoped loading on `session_start` may add trusted project config because it has access to `ctx.cwd` and `ctx.isProjectTrusted()`. Reapply environment/runtime overrides after project config so the precedence order remains correct.
|
|
409
|
+
|
|
410
|
+
## 12. Backfill Command
|
|
411
|
+
|
|
412
|
+
Backfill is optional and disabled by default.
|
|
413
|
+
|
|
414
|
+
Command:
|
|
415
|
+
|
|
416
|
+
```text
|
|
417
|
+
/obs backfill --current-session --since 1h
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
Rules:
|
|
421
|
+
|
|
422
|
+
- Mark telemetry with `observme.replayed=true`
|
|
423
|
+
- Do not backfill content unless capture settings allow it
|
|
424
|
+
- Rate limit export
|
|
425
|
+
- Confirm with user before sending historical content
|
|
426
|
+
|
|
427
|
+
## 13. Versioning
|
|
428
|
+
|
|
429
|
+
Extension versioning:
|
|
430
|
+
|
|
431
|
+
```text
|
|
432
|
+
MAJOR.MINOR.PATCH
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
Breaking semconv changes require MAJOR.
|
|
436
|
+
|
|
437
|
+
## 14. Build Artifact
|
|
438
|
+
|
|
439
|
+
Recommended output:
|
|
440
|
+
|
|
441
|
+
```text
|
|
442
|
+
dist/observme.js
|
|
443
|
+
dist/observme.d.ts
|
|
444
|
+
examples/observme.yaml
|
|
445
|
+
examples/collector.yaml
|
|
446
|
+
dashboards/*.json
|
|
447
|
+
```
|